yuched2 commited on
Commit
317270f
·
verified ·
1 Parent(s): de0347b

First version

Browse files
.gitattributes CHANGED
@@ -33,3 +33,7 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ images/furniture.jpeg filter=lfs diff=lfs merge=lfs -text
37
+ images/Mona_Lisa.jpeg filter=lfs diff=lfs merge=lfs -text
38
+ images/road_sign_art.jpeg filter=lfs diff=lfs merge=lfs -text
39
+ images/strawberries.jpeg filter=lfs diff=lfs merge=lfs -text
app.py ADDED
@@ -0,0 +1,452 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from PIL import Image, ImageDraw
2
+ import numpy as np
3
+ import gradio as gr
4
+ import os
5
+ import random
6
+ from skimage.metrics import structural_similarity as ssim
7
+
8
+ # ============================================================
9
+ # Image Selection & Preprocessing helpers
10
+ # ============================================================
11
+
12
+
13
+ def load_image(path_or_img, size=(512, 512)):
14
+ """
15
+ Load an image from disk, convert it to RGB,
16
+ and resize it to a fixed resolution (default 512x512).
17
+ """
18
+ if isinstance(path_or_img, str): # if input is a file path
19
+ img = Image.open(path_or_img).convert("RGB")
20
+ else: # if input is already a PIL image
21
+ img = path_or_img.convert("RGB")
22
+ img = img.resize(size, Image.LANCZOS)
23
+ return img
24
+
25
+
26
+ def quantize_pillow(img, k=16):
27
+ """
28
+ Apply color quantization using Pillow.
29
+ Reduces the number of unique colors in the image
30
+ (simplifies variations, makes mosaic more consistent).
31
+ """
32
+ return img.convert("P", palette=Image.ADAPTIVE, colors=k).convert("RGB")
33
+
34
+ # ============================================================
35
+ # Image Griding & Thresholding helpers
36
+ # ============================================================
37
+
38
+
39
+ def variance(arr):
40
+ """
41
+ Compute variance of pixel intensities in a cell.
42
+ - Convert RGB to grayscale by averaging channels
43
+ - Variance tells us how "detailed" this cell is
44
+ (high variance = detailed region, low variance = flat region).
45
+ """
46
+ gray = np.mean(arr, axis=2)
47
+ return np.var(gray)
48
+
49
+
50
+ def get_average_color(arr):
51
+ """
52
+ Compute the average RGB color of all pixels in a cell.
53
+ Used to classify the cell into a representative color.
54
+ """
55
+ return tuple(np.mean(arr.reshape(-1, 3), axis=0).astype(np.uint8))
56
+
57
+
58
+ def adaptive_tile_size(var, base_cell, min_size):
59
+ """
60
+ Adjust tile size dynamically based on local variance.
61
+ High variance → smaller tiles
62
+ Low variance → larger tiles
63
+ """
64
+ if var > 800: # very detailed region
65
+ return max(min_size, base_cell // 2)
66
+ elif var > 400: # medium detail
67
+ return max(min_size, int(base_cell * 0.75))
68
+ else: # flat region
69
+ return base_cell
70
+
71
+
72
+ def blend_tile_with_avg(tile, avg_color, alpha=0.6):
73
+ """
74
+ Blend the tile color with the cell's average color.
75
+ alpha = fraction of tile color; (1-alpha) = fraction of avg_color
76
+ """
77
+ tile_arr = np.array(tile).astype(np.float32)
78
+ avg_arr = np.full_like(tile_arr, avg_color, dtype=np.float32)
79
+ blended = (alpha * tile_arr + (1-alpha) * avg_arr).astype(np.uint8)
80
+ return Image.fromarray(blended)
81
+
82
+
83
+ def process_cell_with_tiles(
84
+ result_img, arr, x, y, w, h, tiles, min_size, var_thresh):
85
+ """
86
+ Recursively process one cell of the grid:
87
+ 1. Extract region from the image
88
+ 2. Measure variance
89
+ 3. If variance > threshold → subdivide into 4 smaller cells
90
+ 4. Otherwise → classify by average color and replace with nearest tile
91
+ """
92
+ cell = arr[y:y+h, x:x+w]
93
+ v = variance(cell)
94
+
95
+ if v > var_thresh and w > min_size and h > min_size:
96
+ # Subdivide into 4 quadrants for higher detail representation
97
+ w2, h2 = w // 2, h // 2
98
+ process_cell_with_tiles(
99
+ result_img, arr, x, y, w2, h2, tiles, min_size, var_thresh)
100
+ process_cell_with_tiles(
101
+ result_img, arr, x+w2, y, w-w2, h2, tiles, min_size, var_thresh)
102
+ process_cell_with_tiles(
103
+ result_img, arr, x, y+h2, w2, h-h2, tiles, min_size, var_thresh)
104
+ process_cell_with_tiles(
105
+ result_img, arr, x+w2, y+h2, w-w2, h-h2, tiles, min_size,
106
+ var_thresh)
107
+ else:
108
+ # Flat or small region → replace with nearest colored tile
109
+ avg_color = get_average_color(cell)
110
+
111
+ # Find the tile whose color is closest to the average color
112
+ best_tile, _ = min(
113
+ tiles,
114
+ key=lambda t: np.linalg.norm(np.array(t[1]) - np.array(avg_color))
115
+ )
116
+
117
+ # Determine adaptive tile size based on variance
118
+ cell_tile_size = adaptive_tile_size(v, w, min_size)
119
+ tile_resized = best_tile.resize((cell_tile_size, cell_tile_size))
120
+
121
+ # Blend tile with average color
122
+ tile_resized = blend_tile_with_avg(tile_resized, avg_color, alpha=0.6)
123
+
124
+ # Paste into mosaic result
125
+ result_img.paste(tile_resized, (x, y))
126
+
127
+
128
+ def get_tiles(tile_size=32, palette="default"):
129
+ """
130
+ Unified function to get tiles:
131
+ - If palette == "photo_tiles" → load real image tiles
132
+ - Otherwise → generate simple colored tiles
133
+ """
134
+ if palette == "photo_tiles":
135
+ return load_tile_images(folder="tiles", tile_size=tile_size)
136
+ else:
137
+ return generate_colored_tiles(
138
+ tile_size=tile_size, palette=palette)
139
+
140
+
141
+ def load_tile_images(folder="tiles", tile_size=32):
142
+ """
143
+ Load all images in a folder and resize them into tiles.
144
+ Returns a list of (tile_image, avg_color).
145
+ """
146
+ tiles = []
147
+ for fname in os.listdir(folder):
148
+ if fname.lower().endswith(("flower.jpeg", "lady.jpg",
149
+ "landscape1.jpeg", "landscape2.jpeg",
150
+ "model.jpg")):
151
+ path = os.path.join(folder, fname)
152
+ img = Image.open(path).convert("RGB")
153
+ img = img.resize((tile_size, tile_size), Image.LANCZOS)
154
+
155
+ # compute average color for matching
156
+ arr = np.array(img)
157
+ avg_color = tuple(
158
+ np.mean(arr.reshape(-1, 3), axis=0).astype(np.uint8))
159
+ tiles.append((img, avg_color))
160
+ return tiles
161
+
162
+
163
+ def mosaic_with_tiles(img, tiles, base_cell=32, min_size=8, var_thresh=500):
164
+ """
165
+ Generate a mosaic reconstruction of the image:
166
+ - Start with a grid of base_cell (default 32x32)
167
+ - For each cell, run recursive subdivision & replacement
168
+ """
169
+ arr = np.array(img) # convert PIL → numpy
170
+ h, w, _ = arr.shape
171
+ result_img = Image.new("RGB", (w, h), (0, 0, 0)) # empty canvas
172
+
173
+ # Loop over grid cells
174
+ for y in range(0, h, base_cell):
175
+ for x in range(0, w, base_cell):
176
+ w_cell = min(base_cell, w - x) # handle right edge
177
+ h_cell = min(base_cell, h - y) # handle bottom edge
178
+ process_cell_with_tiles(
179
+ result_img, arr, x, y, w_cell, h_cell, tiles, min_size,
180
+ var_thresh)
181
+
182
+ return result_img
183
+
184
+
185
+ def segmented_image(img, base_cell=32, min_size=8, var_thresh=500):
186
+ """
187
+ Create a segmented version of the image:
188
+ - Subdivide cells recursively based on variance
189
+ - Fill each cell with its average color (no tiles)
190
+ """
191
+ arr = np.array(img)
192
+ h, w, _ = arr.shape
193
+ result_img = Image.new("RGB", (w, h), (0, 0, 0))
194
+
195
+ def process_cell(result_img, arr, x, y, w, h):
196
+ v = variance(arr[y:y+h, x:x+w])
197
+ if v > var_thresh and w > min_size and h > min_size:
198
+ w2, h2 = w // 2, h // 2
199
+ process_cell(result_img, arr, x, y, w2, h2)
200
+ process_cell(result_img, arr, x+w2, y, w-w2, h2)
201
+ process_cell(result_img, arr, x, y+h2, w2, h-h2)
202
+ process_cell(result_img, arr, x+w2, y+h2, w-w2, h-h2)
203
+ else:
204
+ avg_color = get_average_color(arr[y:y+h, x:x+w])
205
+ block = Image.new("RGB", (w, h), avg_color)
206
+ result_img.paste(block, (x, y))
207
+
208
+ for yy in range(0, h, base_cell):
209
+ for xx in range(0, w, base_cell):
210
+ w_cell = min(base_cell, w - xx)
211
+ h_cell = min(base_cell, h - yy)
212
+ process_cell(result_img, arr, xx, yy, w_cell, h_cell)
213
+
214
+ return result_img
215
+
216
+
217
+ # ============================================================
218
+ # Tile Preparation
219
+ # ============================================================
220
+
221
+
222
+ def generate_colored_tiles(tile_size=32, palette="default"):
223
+ """
224
+ Generate a set of colored square tiles
225
+ Palettes can be switched (default, pastel, warm, random, etc.)
226
+ """
227
+ palettes = {
228
+ "default": [
229
+ (255, 0, 0), (0, 255, 0), (0, 0, 255), # primary
230
+ (255, 255, 0), (255, 165, 0), (128, 0, 128), # vivid
231
+ (0, 255, 255), (255, 192, 203), (128, 128, 128), # misc
232
+ (255, 255, 255), (0, 0, 0) # white + black
233
+ ],
234
+ "pastel": [
235
+ (255, 179, 186), (255, 223, 186),
236
+ (255, 255, 186), (186, 255, 201),
237
+ (186, 225, 255)
238
+ ],
239
+ "warm": [ # 🔥 new warm palette
240
+ (255, 140, 0), (255, 69, 0), (255, 99, 71),
241
+ (205, 92, 92), (139, 69, 19)
242
+ ],
243
+ "cool": [ # ❄️ cool tones
244
+ (0, 128, 255), (0, 255, 255),
245
+ (135, 206, 250), (70, 130, 180),
246
+ (25, 25, 112)
247
+ ],
248
+ "grayscale": [ # 🖤 shades of gray
249
+ (0, 0, 0), (64, 64, 64), (128, 128, 128),
250
+ (192, 192, 192), (255, 255, 255)
251
+ ],
252
+ "neon": [ # 🌈 bright neon effect
253
+ (57, 255, 20), (0, 255, 255), (255, 20, 147),
254
+ (255, 255, 0), (255, 0, 255)
255
+ ]
256
+ }
257
+
258
+ # 🎲 Random palette: pick N random RGB colors
259
+ if palette == "random":
260
+ colors = [
261
+ (random.randint(0, 255),
262
+ random.randint(0, 255),
263
+ random.randint(0, 255))
264
+ for _ in range(10) # generate 10 random colors
265
+ ]
266
+ else:
267
+ colors = palettes.get(palette, palettes["default"])
268
+
269
+ tiles = []
270
+ for c in colors:
271
+ tile = Image.new("RGB", (tile_size, tile_size), c)
272
+ tiles.append((tile, c)) # store both tile image and its color value
273
+ return tiles
274
+
275
+
276
+ def get_error_image(msg="⚠️ Error"):
277
+ """
278
+ Generate a simple placeholder image with an error message.
279
+ """
280
+ img = Image.new("RGB", (512, 512), color=(200, 50, 50)) # red background
281
+ draw = ImageDraw.Draw(img)
282
+ draw.text((20, 250), msg, fill=(255, 255, 255))
283
+ return img
284
+
285
+
286
+ # ============================================================
287
+ # Performance Metrics functions
288
+ # ============================================================
289
+
290
+
291
+ def mse(img1, img2):
292
+ """
293
+ Compute Mean Squared Error (MSE) between two images.
294
+ Lower = better similarity.
295
+ """
296
+ arr1 = np.array(img1).astype(np.float32)
297
+ arr2 = np.array(img2).astype(np.float32)
298
+ return np.mean((arr1 - arr2) ** 2)
299
+
300
+
301
+ def compute_ssim(img1, img2):
302
+ """
303
+ Compute Structural Similarity Index (SSIM).
304
+ Range: -1 to 1
305
+ - 1.0 means images are identical
306
+ - Higher values = more perceptually similar
307
+ """
308
+ arr1 = np.array(img1)
309
+ arr2 = np.array(img2)
310
+ return ssim(arr1, arr2, channel_axis=2, data_range=255)
311
+
312
+
313
+ # ============================================================
314
+ # Gradio Interface
315
+ # ============================================================
316
+
317
+
318
+ def mosaic_pipeline(input_img, base_cell, min_size, var_thresh, palette):
319
+ """
320
+ Full pipeline for Gradio interface:
321
+ 1. Resize and quantize input
322
+ 2. Generate tiles
323
+ 3. Reconstruct mosaic
324
+ 4. Compute similarity metrics
325
+ """
326
+
327
+ if input_img is None:
328
+ error_img = get_error_image("Please upload an image first")
329
+ return error_img, error_img, "⚠️ Please upload an image first!"
330
+
331
+ # Step 1 preprocessing
332
+ img = load_image(input_img, size=(512, 512))
333
+ img_q = quantize_pillow(img, k=16)
334
+
335
+ # Step 2 segmentation
336
+ seg_img = segmented_image(img_q, base_cell, min_size, var_thresh)
337
+
338
+ # Step 3 tiles
339
+ tiles = get_tiles(tile_size=32, palette=palette)
340
+
341
+ # Step 4 mosaic construction
342
+ mosaic = mosaic_with_tiles(
343
+ img_q, tiles, base_cell=base_cell, min_size=min_size,
344
+ var_thresh=var_thresh)
345
+
346
+ # Step 5 metrics
347
+ mse_val = mse(img_q, mosaic)
348
+ ssim_val = compute_ssim(img_q, mosaic)
349
+
350
+ return seg_img, mosaic, f"MSE: {mse_val:.2f}, SSIM: {ssim_val:.3f}"
351
+
352
+
353
+ # Define interface
354
+ with gr.Blocks() as demo:
355
+ gr.Markdown("<h1 style='font-size:40px;'>🎨 IMAGE MOSAIC GENERATOR</h1>")
356
+
357
+ with gr.Row():
358
+ with gr.Column():
359
+ gr.Markdown("<h2>⭐ UPLOAD an image to start⭐</h2>")
360
+ input_img = gr.Image(type="pil", label="Upload an image")
361
+ gr.Examples(examples=[
362
+ ["images/ballycastle.jpeg"],
363
+ ["images/furniture.jpeg"],
364
+ ["images/Mona_Lisa.jpeg"],
365
+ ["images/road_sign_art.jpeg"],
366
+ ["images/strawberries.jpeg"]
367
+ ],
368
+ inputs=[input_img],
369
+ label="Choose an example image from below to start🎉",
370
+ examples_per_page=5 # number of images per row
371
+ )
372
+
373
+ # Organize controls in a 2x2 grid
374
+ with gr.Row():
375
+ with gr.Column():
376
+ gr.Markdown("<h2>CUSTOMIZE your MOSAIC style!⭐</h2>")
377
+ base_cell = gr.Slider(
378
+ 8, 64, value=32, step=8, label="Base Grid Size (px)"
379
+ )
380
+ gr.Markdown(
381
+ """
382
+ - 👉 Larger grid size = bigger mosaic tiles (less detail).
383
+ - 👉 Smaller grid size = more detailed mosaic.
384
+ """
385
+ )
386
+
387
+ var_thresh = gr.Slider(
388
+ 100, 1000, value=500, step=50, label="Variance Threshold"
389
+ )
390
+ gr.Markdown(
391
+ """
392
+ - 👉 Controls image detail variance before splitting tile
393
+ - 👉 Higher = smoother look. Lower = more detail.
394
+ """
395
+ )
396
+
397
+ with gr.Column():
398
+ min_size = gr.Slider(
399
+ 4, 32, value=8, step=4, label="Minimum Cell Size (px)"
400
+ )
401
+ gr.Markdown(
402
+ """
403
+ - 👉 Minimum size a tile can shrink to.
404
+ - 👉 Smaller = allows more variability in tile shapes.
405
+ """
406
+ )
407
+
408
+ palette = gr.Dropdown(
409
+ ["default", "pastel", "warm", "cool",
410
+ "grayscale", "neon", "random", "photo-tiles"],
411
+ value="default",
412
+ label="Tile Palette"
413
+ )
414
+ gr.Markdown(
415
+ """
416
+ - 👉 Choose the color palette for the tiles.
417
+ """
418
+ )
419
+
420
+ run_btn = gr.Button("Generate my mosaic NOW!👀")
421
+
422
+ with gr.Column():
423
+ gr.Markdown("<h2>⭐CHECK amazing results HERE!⭐</h2>")
424
+ seg_img = gr.Image(type="pil", label="Segmented Image")
425
+ output_img = gr.Image(type="pil", label="Mosaic Output")
426
+
427
+ gr.Markdown("<h2>💭How WELL it worked?💭</h2>")
428
+ metrics = gr.Textbox(label="MSE & SSIM")
429
+ gr.Markdown(
430
+ """
431
+ - **MSE (Mean Squared Error):** Lower values = mosaic is closer
432
+ to the original image.
433
+ - **SSIM (Structural Similarity Index):** Higher values (closer
434
+ to 1) = better structural similarity to the original.
435
+ """
436
+ )
437
+
438
+ # Bind function to button click
439
+ run_btn.click(
440
+ fn=mosaic_pipeline,
441
+ inputs=[input_img, base_cell, min_size, var_thresh, palette],
442
+ outputs=[seg_img, output_img, metrics]
443
+ )
444
+
445
+
446
+ # ============================================================
447
+ # Main function
448
+ # ============================================================
449
+
450
+
451
+ if __name__ == "__main__":
452
+ demo.launch()
images/Mona_Lisa.jpeg ADDED

Git LFS Details

  • SHA256: dd257c6293fcc588173483623e1765c1c691c8a9a2375b3d14d11ec89419bb72
  • Pointer size: 131 Bytes
  • Size of remote file: 125 kB
images/ballycastle.jpeg ADDED
images/furniture.jpeg ADDED

Git LFS Details

  • SHA256: fcc50fba111cc6945736d713b7056296bdf5f4b847ea88c6a6ce2c75e00967a4
  • Pointer size: 131 Bytes
  • Size of remote file: 117 kB
images/road_sign_art.jpeg ADDED

Git LFS Details

  • SHA256: 226bc9a9b2fa519b19cf202274310d3c08945247a201fa5b49fb7d141be3b34f
  • Pointer size: 131 Bytes
  • Size of remote file: 694 kB
images/strawberries.jpeg ADDED

Git LFS Details

  • SHA256: 65479bdd333a7954ae69a0be1df3419dd696da5373585567c3d0404f3ce70b0e
  • Pointer size: 131 Bytes
  • Size of remote file: 571 kB
requirements.txt ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aiofiles==24.1.0
2
+ altair==5.1.2
3
+ annotated-types==0.7.0
4
+ anyio==4.11.0
5
+ attrs==23.1.0
6
+ bcrypt==4.0.1
7
+ blinker==1.7.0
8
+ Brotli==1.1.0
9
+ cachetools==5.3.2
10
+ certifi==2023.7.22
11
+ charset-normalizer==3.3.1
12
+ click==8.1.7
13
+ contourpy==1.3.3
14
+ coverage==7.3.2
15
+ cycler==0.12.1
16
+ dog.py==0.2.0
17
+ extra-streamlit-components==0.1.60
18
+ fastapi==0.117.1
19
+ ffmpy==0.6.1
20
+ filelock==3.19.1
21
+ fonttools==4.60.0
22
+ frozendict==2.3.9
23
+ fsspec==2025.9.0
24
+ gitdb==4.0.11
25
+ GitPython==3.1.40
26
+ gradio==5.47.1
27
+ gradio_client==1.13.2
28
+ groovy==0.1.2
29
+ h11==0.16.0
30
+ hf-xet==1.1.10
31
+ httpcore==1.0.9
32
+ httpx==0.28.1
33
+ huggingface-hub==0.35.1
34
+ idna==3.4
35
+ imageio==2.37.0
36
+ importlib-metadata==6.8.0
37
+ iniconfig==2.0.0
38
+ Jinja2==3.1.2
39
+ joblib==1.5.2
40
+ jsonschema==4.19.2
41
+ jsonschema-specifications==2023.11.1
42
+ kiwisolver==1.4.9
43
+ lazy_loader==0.4
44
+ lxml==4.9.3
45
+ markdown-it-py==3.0.0
46
+ MarkupSafe==2.1.3
47
+ matplotlib==3.10.6
48
+ mdurl==0.1.2
49
+ networkx==3.5
50
+ node==1.2.2
51
+ npm==0.1.1
52
+ numpy==1.26.2
53
+ odict==1.9.0
54
+ optional-django==0.1.0
55
+ orjson==3.11.3
56
+ packaging==23.2
57
+ pandas==2.1.3
58
+ Pillow==10.1.0
59
+ pluggy==1.3.0
60
+ plumber==1.7
61
+ protobuf==4.25.0
62
+ pyarrow==14.0.1
63
+ pydantic==2.11.9
64
+ pydantic_core==2.33.2
65
+ pydeck==0.8.1b0
66
+ pydub==0.25.1
67
+ Pygments==2.16.1
68
+ PyJWT==2.8.0
69
+ PyLD==2.0.3
70
+ pyparsing==3.2.5
71
+ pytest==7.4.3
72
+ pytest-cov==4.1.0
73
+ python-dateutil==2.8.2
74
+ python-multipart==0.0.20
75
+ python-pptx==0.6.23
76
+ pytz==2023.3.post1
77
+ PyYAML==6.0.1
78
+ referencing==0.31.0
79
+ report==0.0.1
80
+ requests==2.31.0
81
+ rich==13.7.0
82
+ rpds-py==0.12.0
83
+ ruff==0.13.2
84
+ safehttpx==0.1.6
85
+ scikit-image==0.25.2
86
+ scikit-learn==1.7.2
87
+ scipy==1.16.2
88
+ semantic-version==2.10.0
89
+ shellingham==1.5.4
90
+ six==1.16.0
91
+ smmap==5.0.1
92
+ sniffio==1.3.1
93
+ starlette==0.48.0
94
+ streamlit==1.29.0
95
+ streamlit-authenticator==0.2.3
96
+ tenacity==8.2.3
97
+ threadpoolctl==3.6.0
98
+ tifffile==2025.9.20
99
+ toml==0.10.2
100
+ tomlkit==0.13.3
101
+ toolz==0.12.0
102
+ tornado==6.3.3
103
+ tqdm==4.67.1
104
+ typer==0.19.2
105
+ typing-inspection==0.4.1
106
+ typing_extensions==4.15.0
107
+ tzdata==2023.3
108
+ tzlocal==5.2
109
+ urllib3==2.0.7
110
+ uvicorn==0.37.0
111
+ validators==0.22.0
112
+ watchdog==3.0.0
113
+ websockets==15.0.1
114
+ XlsxWriter==3.1.9
115
+ zipp==3.17.0
116
+ zope.component==6.0
117
+ zope.deferredimport==5.0
118
+ zope.deprecation==5.0
119
+ zope.event==5.0
120
+ zope.hookable==7.0
121
+ zope.interface==7.1.0
122
+ zope.lifecycleevent==5.0
123
+ zope.proxy==6.1
tiles/flower.jpeg ADDED
tiles/lady.jpg ADDED
tiles/landscape1.jpg ADDED
tiles/landscape2.jpeg ADDED
tiles/model.jpg ADDED