Spaces:
Sleeping
Sleeping
Replace Space with latest LAB1 code
Browse files
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import
|
| 2 |
from pathlib import Path
|
| 3 |
from typing import List, Tuple, Optional, Dict
|
| 4 |
|
|
@@ -9,17 +9,17 @@ from skimage.metrics import structural_similarity as ssim
|
|
| 9 |
from skimage.color import rgb2lab
|
| 10 |
from sklearn.cluster import KMeans
|
| 11 |
|
| 12 |
-
# ---- Hugging Face dataset: hard-wired ----
|
| 13 |
from datasets import load_dataset
|
| 14 |
|
| 15 |
-
HF_DATASET = "
|
| 16 |
HF_SPLIT = "train"
|
| 17 |
-
TILE_LIMIT =
|
| 18 |
-
BASE_TILE_SIZE = 32
|
| 19 |
|
| 20 |
# Global caches
|
| 21 |
-
_TILES_RAW_32: Optional[List[np.ndarray]] = None
|
| 22 |
-
_TILE_CACHE_BY_SIZE: Dict[int, Tuple[List[np.ndarray], np.ndarray]] = {} #
|
| 23 |
|
| 24 |
# =======================
|
| 25 |
# Image utils
|
|
@@ -53,10 +53,10 @@ def mse(a: np.ndarray, b: np.ndarray) -> float:
|
|
| 53 |
return float(np.mean((a.astype(np.float32) - b.astype(np.float32))**2))
|
| 54 |
|
| 55 |
# =======================
|
| 56 |
-
#
|
| 57 |
# =======================
|
| 58 |
def _load_tiles_raw_32(limit: int = TILE_LIMIT) -> List[np.ndarray]:
|
| 59 |
-
"""Load 32x32 tiles (RGB uint8) from
|
| 60 |
global _TILES_RAW_32
|
| 61 |
if _TILES_RAW_32 is not None:
|
| 62 |
return _TILES_RAW_32
|
|
@@ -64,15 +64,16 @@ def _load_tiles_raw_32(limit: int = TILE_LIMIT) -> List[np.ndarray]:
|
|
| 64 |
ds = load_dataset(HF_DATASET, split=HF_SPLIT)
|
| 65 |
tiles = []
|
| 66 |
for i, ex in enumerate(ds):
|
| 67 |
-
|
|
|
|
| 68 |
continue
|
| 69 |
-
img: Image.Image = ex["
|
| 70 |
-
# dataset already 32x32; enforce in case
|
| 71 |
if img.size != (BASE_TILE_SIZE, BASE_TILE_SIZE):
|
| 72 |
img = img.resize((BASE_TILE_SIZE, BASE_TILE_SIZE), Image.BILINEAR)
|
| 73 |
tiles.append(np.asarray(img))
|
| 74 |
-
if limit and len(tiles) >= limit:
|
| 75 |
break
|
|
|
|
| 76 |
if len(tiles) == 0:
|
| 77 |
raise gr.Error(f"No tiles loaded from {HF_DATASET}.")
|
| 78 |
_TILES_RAW_32 = tiles
|
|
@@ -85,22 +86,20 @@ def _average_color_lab(tile: np.ndarray) -> np.ndarray:
|
|
| 85 |
def _tiles_for_cell_size(cell_size: int) -> Tuple[List[np.ndarray], np.ndarray]:
|
| 86 |
"""
|
| 87 |
Return (tiles_resized, tiles_lab_means) for the requested cell size.
|
| 88 |
-
Caches results to avoid
|
| 89 |
"""
|
| 90 |
if cell_size in _TILE_CACHE_BY_SIZE:
|
| 91 |
return _TILE_CACHE_BY_SIZE[cell_size]
|
| 92 |
|
| 93 |
raw_tiles = _load_tiles_raw_32()
|
| 94 |
-
# Resize to cell_size if needed
|
| 95 |
if cell_size == BASE_TILE_SIZE:
|
| 96 |
tiles_resized = raw_tiles
|
| 97 |
else:
|
| 98 |
-
tiles_resized = [
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
tiles_lab = np.array([_average_color_lab(t) for t in tiles_resized], dtype=np.float32)
|
| 103 |
-
|
| 104 |
_TILE_CACHE_BY_SIZE[cell_size] = (tiles_resized, tiles_lab)
|
| 105 |
return tiles_resized, tiles_lab
|
| 106 |
|
|
@@ -167,11 +166,11 @@ def segment_preview(src: np.ndarray, cell: int) -> np.ndarray:
|
|
| 167 |
return out.astype(np.uint8)
|
| 168 |
|
| 169 |
# =======================
|
| 170 |
-
# Full pipeline (tiles always from
|
| 171 |
# =======================
|
| 172 |
def build_mosaic(
|
| 173 |
input_image: Image.Image,
|
| 174 |
-
cell_size: int = 32,
|
| 175 |
use_vectorized: bool = True,
|
| 176 |
quant_k: int = 0,
|
| 177 |
similarity_metric: str = "SSIM",
|
|
@@ -196,7 +195,7 @@ def build_mosaic(
|
|
| 196 |
mean_rgb, R, C = grid_mean_colors_loop(src, cell_size)
|
| 197 |
t_grid = time.perf_counter() - t0
|
| 198 |
|
| 199 |
-
# 4) Tiles from
|
| 200 |
tiles, tiles_lab = _tiles_for_cell_size(cell_size)
|
| 201 |
|
| 202 |
# 5) Map & build mosaic
|
|
@@ -259,12 +258,12 @@ if not (EXAMPLES_DIR / "gradient1.png").exists():
|
|
| 259 |
grad1 = np.dstack([g1, np.flipud(g1).copy(), np.roll(g1, 160, axis=1)])
|
| 260 |
Image.fromarray(grad1).save(EXAMPLES_DIR/"gradient1.png")
|
| 261 |
|
| 262 |
-
with gr.Blocks(title="Image Mosaic (
|
| 263 |
gr.Markdown(
|
| 264 |
f"""
|
| 265 |
# 🧩 Image Mosaic Generator (tiles from `{HF_DATASET}`)
|
| 266 |
-
- Tiles
|
| 267 |
-
- Upload an image and generate a mosaic
|
| 268 |
"""
|
| 269 |
)
|
| 270 |
with gr.Row():
|
|
@@ -275,7 +274,8 @@ with gr.Blocks(title="Image Mosaic (ImageNet32 tiles)", css="footer {visibility:
|
|
| 275 |
inputs=[inp],
|
| 276 |
label="Example"
|
| 277 |
)
|
| 278 |
-
cell = gr.Slider(16,
|
|
|
|
| 279 |
quant_k = gr.Slider(0, 24, value=0, step=1, label="Optional color quantization (k-means K)")
|
| 280 |
similarity = gr.Radio(choices=["SSIM", "MSE"], value="SSIM", label="Similarity metric")
|
| 281 |
vec = gr.Checkbox(value=True, label="Use vectorized NumPy (uncheck for loop baseline)")
|
|
@@ -307,6 +307,5 @@ if __name__ == "__main__":
|
|
| 307 |
try:
|
| 308 |
_load_tiles_raw_32(TILE_LIMIT)
|
| 309 |
except Exception as e:
|
| 310 |
-
# Gradio will still start; you'll see an error if tiles can't be loaded
|
| 311 |
print("Warning: failed to preload tiles:", e)
|
| 312 |
demo.launch()
|
|
|
|
| 1 |
+
import time
|
| 2 |
from pathlib import Path
|
| 3 |
from typing import List, Tuple, Optional, Dict
|
| 4 |
|
|
|
|
| 9 |
from skimage.color import rgb2lab
|
| 10 |
from sklearn.cluster import KMeans
|
| 11 |
|
| 12 |
+
# ---- Hugging Face dataset: hard-wired to CIFAR-100 ----
|
| 13 |
from datasets import load_dataset
|
| 14 |
|
| 15 |
+
HF_DATASET = "uoft-cs/cifar100" # tiles come from here
|
| 16 |
HF_SPLIT = "train"
|
| 17 |
+
TILE_LIMIT = 0 # cap to keep mapping fast (adjust as needed)
|
| 18 |
+
BASE_TILE_SIZE = 32 # CIFAR-100 images are 32x32
|
| 19 |
|
| 20 |
# Global caches
|
| 21 |
+
_TILES_RAW_32: Optional[List[np.ndarray]] = None # list of 32x32 RGB uint8 arrays
|
| 22 |
+
_TILE_CACHE_BY_SIZE: Dict[int, Tuple[List[np.ndarray], np.ndarray]] = {} # size -> (tiles_resized, lab_means)
|
| 23 |
|
| 24 |
# =======================
|
| 25 |
# Image utils
|
|
|
|
| 53 |
return float(np.mean((a.astype(np.float32) - b.astype(np.float32))**2))
|
| 54 |
|
| 55 |
# =======================
|
| 56 |
+
# CIFAR-100: load & cache base tiles (32x32)
|
| 57 |
# =======================
|
| 58 |
def _load_tiles_raw_32(limit: int = TILE_LIMIT) -> List[np.ndarray]:
|
| 59 |
+
"""Load 32x32 tiles (RGB uint8) from CIFAR-100 (uoft-cs/cifar100)."""
|
| 60 |
global _TILES_RAW_32
|
| 61 |
if _TILES_RAW_32 is not None:
|
| 62 |
return _TILES_RAW_32
|
|
|
|
| 64 |
ds = load_dataset(HF_DATASET, split=HF_SPLIT)
|
| 65 |
tiles = []
|
| 66 |
for i, ex in enumerate(ds):
|
| 67 |
+
# CIFAR-100 column name is "img"
|
| 68 |
+
if "img" not in ex:
|
| 69 |
continue
|
| 70 |
+
img: Image.Image = ex["img"].convert("RGB")
|
|
|
|
| 71 |
if img.size != (BASE_TILE_SIZE, BASE_TILE_SIZE):
|
| 72 |
img = img.resize((BASE_TILE_SIZE, BASE_TILE_SIZE), Image.BILINEAR)
|
| 73 |
tiles.append(np.asarray(img))
|
| 74 |
+
if limit > 0 and len(tiles) >= limit:
|
| 75 |
break
|
| 76 |
+
|
| 77 |
if len(tiles) == 0:
|
| 78 |
raise gr.Error(f"No tiles loaded from {HF_DATASET}.")
|
| 79 |
_TILES_RAW_32 = tiles
|
|
|
|
| 86 |
def _tiles_for_cell_size(cell_size: int) -> Tuple[List[np.ndarray], np.ndarray]:
|
| 87 |
"""
|
| 88 |
Return (tiles_resized, tiles_lab_means) for the requested cell size.
|
| 89 |
+
Caches results to avoid recomputation on each run.
|
| 90 |
"""
|
| 91 |
if cell_size in _TILE_CACHE_BY_SIZE:
|
| 92 |
return _TILE_CACHE_BY_SIZE[cell_size]
|
| 93 |
|
| 94 |
raw_tiles = _load_tiles_raw_32()
|
|
|
|
| 95 |
if cell_size == BASE_TILE_SIZE:
|
| 96 |
tiles_resized = raw_tiles
|
| 97 |
else:
|
| 98 |
+
tiles_resized = [
|
| 99 |
+
np.asarray(Image.fromarray(t).resize((cell_size, cell_size), Image.BILINEAR))
|
| 100 |
+
for t in raw_tiles
|
| 101 |
+
]
|
| 102 |
tiles_lab = np.array([_average_color_lab(t) for t in tiles_resized], dtype=np.float32)
|
|
|
|
| 103 |
_TILE_CACHE_BY_SIZE[cell_size] = (tiles_resized, tiles_lab)
|
| 104 |
return tiles_resized, tiles_lab
|
| 105 |
|
|
|
|
| 166 |
return out.astype(np.uint8)
|
| 167 |
|
| 168 |
# =======================
|
| 169 |
+
# Full pipeline (tiles always from CIFAR-100)
|
| 170 |
# =======================
|
| 171 |
def build_mosaic(
|
| 172 |
input_image: Image.Image,
|
| 173 |
+
cell_size: int = 32,
|
| 174 |
use_vectorized: bool = True,
|
| 175 |
quant_k: int = 0,
|
| 176 |
similarity_metric: str = "SSIM",
|
|
|
|
| 195 |
mean_rgb, R, C = grid_mean_colors_loop(src, cell_size)
|
| 196 |
t_grid = time.perf_counter() - t0
|
| 197 |
|
| 198 |
+
# 4) Tiles from CIFAR-100 (cached & resized to cell_size)
|
| 199 |
tiles, tiles_lab = _tiles_for_cell_size(cell_size)
|
| 200 |
|
| 201 |
# 5) Map & build mosaic
|
|
|
|
| 258 |
grad1 = np.dstack([g1, np.flipud(g1).copy(), np.roll(g1, 160, axis=1)])
|
| 259 |
Image.fromarray(grad1).save(EXAMPLES_DIR/"gradient1.png")
|
| 260 |
|
| 261 |
+
with gr.Blocks(title="Image Mosaic (CIFAR-100 tiles)", css="footer {visibility: hidden}") as demo:
|
| 262 |
gr.Markdown(
|
| 263 |
f"""
|
| 264 |
# 🧩 Image Mosaic Generator (tiles from `{HF_DATASET}`)
|
| 265 |
+
- Tiles auto-loaded from **Hugging Face** dataset: `{HF_DATASET}` (split `{HF_SPLIT}`, limit {TILE_LIMIT}).
|
| 266 |
+
- Upload an image and generate a mosaic immediately.
|
| 267 |
"""
|
| 268 |
)
|
| 269 |
with gr.Row():
|
|
|
|
| 274 |
inputs=[inp],
|
| 275 |
label="Example"
|
| 276 |
)
|
| 277 |
+
cell = gr.Slider(16, 128, value=32, step=2, label="Grid cell size (px)")
|
| 278 |
+
|
| 279 |
quant_k = gr.Slider(0, 24, value=0, step=1, label="Optional color quantization (k-means K)")
|
| 280 |
similarity = gr.Radio(choices=["SSIM", "MSE"], value="SSIM", label="Similarity metric")
|
| 281 |
vec = gr.Checkbox(value=True, label="Use vectorized NumPy (uncheck for loop baseline)")
|
|
|
|
| 307 |
try:
|
| 308 |
_load_tiles_raw_32(TILE_LIMIT)
|
| 309 |
except Exception as e:
|
|
|
|
| 310 |
print("Warning: failed to preload tiles:", e)
|
| 311 |
demo.launch()
|