Spaces:
Sleeping
Sleeping
intermediary changes
Browse files- __pycache__/mosaic.cpython-313.pyc +0 -0
- app.py +51 -21
- logic/__pycache__/imgGrid.cpython-313.pyc +0 -0
- logic/__pycache__/imgPreprocess.cpython-313.pyc +0 -0
- logic/__pycache__/mosaic_logic.cpython-313.pyc +0 -0
- logic/__pycache__/perfMetric.cpython-313.pyc +0 -0
- logic/__pycache__/tileMapping.cpython-313.pyc +0 -0
- logic/imgGrid.py +17 -0
- logic/imgPreprocess.py +22 -22
- logic/mosaic_logic.py +56 -0
- logic/perfMetric.py +20 -0
- logic/tileMapping.py +24 -0
- resize_image.py +0 -12
__pycache__/mosaic.cpython-313.pyc
ADDED
|
Binary file (4.97 kB). View file
|
|
|
app.py
CHANGED
|
@@ -3,35 +3,65 @@ import cv2
|
|
| 3 |
import numpy as np
|
| 4 |
from PIL import Image
|
| 5 |
import os
|
| 6 |
-
from logic.imgPreprocess import resize_img
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
-
def
|
| 9 |
-
#
|
|
|
|
|
|
|
|
|
|
| 10 |
resized_np = resize_img(image, size=(size, size))
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
# Only show example images for upload, no grid or extra info
|
| 17 |
example_dir = "data/images"
|
| 18 |
example_files = [os.path.join(example_dir, f) for f in [
|
| 19 |
"bird.JPG", "cheetah.JPG", "lion.JPG", "oryx.JPG", "ostrich.JPG", "rhino.JPG", "zebra.JPG"
|
| 20 |
]]
|
|
|
|
| 21 |
|
| 22 |
-
|
| 23 |
-
gr.
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
-
demo = gr.Interface(
|
| 31 |
-
fn=preprocess_image,
|
| 32 |
-
inputs=inputs,
|
| 33 |
-
outputs=outputs,
|
| 34 |
-
examples=examples,
|
| 35 |
-
title="Image Resizer"
|
| 36 |
-
)
|
| 37 |
demo.launch()
|
|
|
|
| 3 |
import numpy as np
|
| 4 |
from PIL import Image
|
| 5 |
import os
|
| 6 |
+
from logic.imgPreprocess import resize_img, color_quantize
|
| 7 |
+
from logic.imgGrid import segment_image_grid
|
| 8 |
+
from logic.tileMapping import load_tile_images, map_tiles_to_grid
|
| 9 |
+
from logic.perfMetric import mse, ssim_metric, timed
|
| 10 |
|
| 11 |
+
def preprocess_and_mosaic(image: Image.Image, size: int, grid_size: int, n_colors: int):
|
| 12 |
+
# Apply color quantization
|
| 13 |
+
quantized_img, color_centers = color_quantize(image, n_colors)
|
| 14 |
+
image = Image.fromarray(cv2.cvtColor(quantized_img, cv2.COLOR_BGR2RGB))
|
| 15 |
+
# Resize image
|
| 16 |
resized_np = resize_img(image, size=(size, size))
|
| 17 |
+
cell_size = size // grid_size
|
| 18 |
+
crop_size = cell_size * grid_size
|
| 19 |
+
# Crop resized image to match mosaic size
|
| 20 |
+
resized_np_cropped = resized_np[:crop_size, :crop_size]
|
| 21 |
+
# Segment grid and classify colors
|
| 22 |
+
grid_labels, seg_time = timed(segment_image_grid)(resized_np_cropped, grid_size, color_centers)
|
| 23 |
+
# Load/generate tiles
|
| 24 |
+
tiles = load_tile_images('data/tiles', n_colors, cell_size)
|
| 25 |
+
# Map tiles to grid
|
| 26 |
+
mosaic_img, mosaic_time = timed(map_tiles_to_grid)(grid_labels, tiles, cell_size)
|
| 27 |
+
# Metrics
|
| 28 |
+
mse_val = mse(resized_np_cropped, mosaic_img)
|
| 29 |
+
ssim_val = ssim_metric(resized_np_cropped, mosaic_img)
|
| 30 |
+
# Convert for display
|
| 31 |
+
orig_disp = Image.fromarray(cv2.cvtColor(resized_np_cropped, cv2.COLOR_BGR2RGB))
|
| 32 |
+
mosaic_disp = Image.fromarray(cv2.cvtColor(mosaic_img, cv2.COLOR_BGR2RGB))
|
| 33 |
+
# Segmented image: show each cell as its cluster color
|
| 34 |
+
seg_img = np.zeros_like(resized_np_cropped)
|
| 35 |
+
for i in range(grid_size):
|
| 36 |
+
for j in range(grid_size):
|
| 37 |
+
seg_img[i*cell_size:(i+1)*cell_size, j*cell_size:(j+1)*cell_size] = color_centers[grid_labels[i,j]]
|
| 38 |
+
seg_disp = Image.fromarray(cv2.cvtColor(seg_img, cv2.COLOR_BGR2RGB))
|
| 39 |
+
# Info
|
| 40 |
+
info = f"MSE: {mse_val:.2f}\nSSIM: {ssim_val:.3f}\nSegmentation: {seg_time:.3f}s\nMosaic: {mosaic_time:.3f}s"
|
| 41 |
+
return orig_disp, seg_disp, mosaic_disp, info
|
| 42 |
|
| 43 |
# Only show example images for upload, no grid or extra info
|
| 44 |
example_dir = "data/images"
|
| 45 |
example_files = [os.path.join(example_dir, f) for f in [
|
| 46 |
"bird.JPG", "cheetah.JPG", "lion.JPG", "oryx.JPG", "ostrich.JPG", "rhino.JPG", "zebra.JPG"
|
| 47 |
]]
|
| 48 |
+
examples = [[f, 400, 32, 8] for f in example_files]
|
| 49 |
|
| 50 |
+
with gr.Blocks() as demo:
|
| 51 |
+
gr.Markdown("# Image Mosaic Generator")
|
| 52 |
+
with gr.Row():
|
| 53 |
+
image_input = gr.Image(type="pil", label="Upload or Select Example Image")
|
| 54 |
+
size_slider = gr.Slider(100, 800, value=400, step=10, label="Resize (pixels)")
|
| 55 |
+
grid_slider = gr.Slider(8, 64, value=32, step=1, label="Grid Size (NxN)")
|
| 56 |
+
color_slider = gr.Slider(2, 16, value=8, step=1, label="Number of Colors (Quantization)")
|
| 57 |
+
mosaic_btn = gr.Button("Generate Mosaic")
|
| 58 |
+
with gr.Row():
|
| 59 |
+
orig_out = gr.Image(label="Original (Resized)")
|
| 60 |
+
seg_out = gr.Image(label="Segmented Grid")
|
| 61 |
+
mosaic_out = gr.Image(label="Mosaic Image")
|
| 62 |
+
info_out = gr.Textbox(label="Metrics & Timing", interactive=False)
|
| 63 |
+
gr.Examples(examples=examples, inputs=[image_input, size_slider, grid_slider, color_slider], label="Example Images")
|
| 64 |
+
|
| 65 |
+
mosaic_btn.click(preprocess_and_mosaic, inputs=[image_input, size_slider, grid_slider, color_slider], outputs=[orig_out, seg_out, mosaic_out, info_out])
|
| 66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
demo.launch()
|
logic/__pycache__/imgGrid.cpython-313.pyc
ADDED
|
Binary file (1.36 kB). View file
|
|
|
logic/__pycache__/imgPreprocess.cpython-313.pyc
CHANGED
|
Binary files a/logic/__pycache__/imgPreprocess.cpython-313.pyc and b/logic/__pycache__/imgPreprocess.cpython-313.pyc differ
|
|
|
logic/__pycache__/mosaic_logic.cpython-313.pyc
ADDED
|
Binary file (4.63 kB). View file
|
|
|
logic/__pycache__/perfMetric.cpython-313.pyc
ADDED
|
Binary file (1.5 kB). View file
|
|
|
logic/__pycache__/tileMapping.cpython-313.pyc
ADDED
|
Binary file (2.22 kB). View file
|
|
|
logic/imgGrid.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from PIL import Image
|
| 3 |
+
import cv2
|
| 4 |
+
from sklearn.cluster import KMeans
|
| 5 |
+
|
| 6 |
+
def segment_image_grid(image: np.ndarray, grid_size: int, color_centers: np.ndarray):
|
| 7 |
+
h, w, c = image.shape
|
| 8 |
+
cell_h, cell_w = h // grid_size, w // grid_size
|
| 9 |
+
image_cropped = image[:cell_h*grid_size, :cell_w*grid_size]
|
| 10 |
+
cells = image_cropped.reshape(grid_size, cell_h, grid_size, cell_w, c)
|
| 11 |
+
cells = cells.transpose(0,2,1,3,4).reshape(grid_size*grid_size, cell_h*cell_w, c)
|
| 12 |
+
cell_means = cells.mean(axis=1)
|
| 13 |
+
# Assign each cell to nearest color center
|
| 14 |
+
dists = np.linalg.norm(cell_means[:, None, :] - color_centers[None, :, :], axis=2)
|
| 15 |
+
labels = np.argmin(dists, axis=1)
|
| 16 |
+
grid_labels = labels.reshape(grid_size, grid_size)
|
| 17 |
+
return grid_labels
|
logic/imgPreprocess.py
CHANGED
|
@@ -21,25 +21,25 @@ def resize_img(image: Image.Image, size=(400, 400)) -> np.ndarray:
|
|
| 21 |
resized_np = cv2.resize(image_np, size, interpolation=cv2.INTER_LANCZOS4)
|
| 22 |
return resized_np
|
| 23 |
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
#
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
|
|
|
| 21 |
resized_np = cv2.resize(image_np, size, interpolation=cv2.INTER_LANCZOS4)
|
| 22 |
return resized_np
|
| 23 |
|
| 24 |
+
def color_quantize(image: Image.Image, n_colors: int = 8):
|
| 25 |
+
"""
|
| 26 |
+
Apply color quantization to an image using KMeans clustering.
|
| 27 |
+
Args:
|
| 28 |
+
image (PIL.Image.Image): Input image.
|
| 29 |
+
n_colors (int): Number of colors for quantization.
|
| 30 |
+
Returns:
|
| 31 |
+
quantized_img (np.ndarray): Quantized image as np array (BGR format)
|
| 32 |
+
color_centers (np.ndarray): Array of color centers
|
| 33 |
+
"""
|
| 34 |
+
img_np = np.array(image)
|
| 35 |
+
shape = img_np.shape
|
| 36 |
+
img_flat = img_np.reshape(-1, 3)
|
| 37 |
+
kmeans = KMeans(n_clusters=n_colors, random_state=42)
|
| 38 |
+
labels = kmeans.fit_predict(img_flat)
|
| 39 |
+
quantized_flat = kmeans.cluster_centers_[labels].astype(np.uint8)
|
| 40 |
+
quantized_img = quantized_flat.reshape(shape)
|
| 41 |
+
# Convert to BGR for consistency
|
| 42 |
+
if quantized_img.shape[-1] == 3:
|
| 43 |
+
quantized_img = cv2.cvtColor(quantized_img, cv2.COLOR_RGB2BGR)
|
| 44 |
+
color_centers = kmeans.cluster_centers_.astype(np.uint8)
|
| 45 |
+
return quantized_img, color_centers
|
logic/mosaic_logic.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from PIL import Image
|
| 3 |
+
import cv2
|
| 4 |
+
import time
|
| 5 |
+
import os
|
| 6 |
+
from skimage.metrics import structural_similarity as ssim
|
| 7 |
+
|
| 8 |
+
def segment_image_grid(image: np.ndarray, grid_size: int, n_colors: int = 8):
|
| 9 |
+
h, w, c = image.shape
|
| 10 |
+
cell_h, cell_w = h // grid_size, w // grid_size
|
| 11 |
+
image_cropped = image[:cell_h*grid_size, :cell_w*grid_size]
|
| 12 |
+
cells = image_cropped.reshape(grid_size, cell_h, grid_size, cell_w, c)
|
| 13 |
+
cells = cells.transpose(0,2,1,3,4).reshape(grid_size*grid_size, cell_h*cell_w, c)
|
| 14 |
+
cell_means = cells.mean(axis=1)
|
| 15 |
+
from sklearn.cluster import KMeans
|
| 16 |
+
kmeans = KMeans(n_clusters=n_colors, random_state=42)
|
| 17 |
+
labels = kmeans.fit_predict(cell_means)
|
| 18 |
+
grid_labels = labels.reshape(grid_size, grid_size)
|
| 19 |
+
return grid_labels, kmeans.cluster_centers_.astype(np.uint8)
|
| 20 |
+
|
| 21 |
+
def load_tile_images(tile_dir, n_colors, cell_size):
|
| 22 |
+
tiles = []
|
| 23 |
+
if os.path.isdir(tile_dir):
|
| 24 |
+
files = sorted([os.path.join(tile_dir, f) for f in os.listdir(tile_dir) if f.lower().endswith(('.png','.jpg','.jpeg'))])
|
| 25 |
+
for f in files[:n_colors]:
|
| 26 |
+
tile = np.array(Image.open(f).resize((cell_size, cell_size)))
|
| 27 |
+
tiles.append(tile)
|
| 28 |
+
while len(tiles) < n_colors:
|
| 29 |
+
color = np.random.randint(0,255,3)
|
| 30 |
+
tile = np.ones((cell_size, cell_size, 3), dtype=np.uint8) * color
|
| 31 |
+
tiles.append(tile)
|
| 32 |
+
return tiles
|
| 33 |
+
|
| 34 |
+
def map_tiles_to_grid(grid_labels, tiles, cell_size):
|
| 35 |
+
grid_size = grid_labels.shape[0]
|
| 36 |
+
mosaic = np.zeros((grid_size*cell_size, grid_size*cell_size, 3), dtype=np.uint8)
|
| 37 |
+
for i in range(grid_size):
|
| 38 |
+
for j in range(grid_size):
|
| 39 |
+
mosaic[i*cell_size:(i+1)*cell_size, j*cell_size:(j+1)*cell_size] = tiles[grid_labels[i,j]]
|
| 40 |
+
return mosaic
|
| 41 |
+
|
| 42 |
+
def mse(img1, img2):
|
| 43 |
+
return np.mean((img1.astype(np.float32) - img2.astype(np.float32))**2)
|
| 44 |
+
|
| 45 |
+
def ssim_metric(img1, img2):
|
| 46 |
+
img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
|
| 47 |
+
img2_gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
|
| 48 |
+
return ssim(img1_gray, img2_gray)
|
| 49 |
+
|
| 50 |
+
def timed(func):
|
| 51 |
+
def wrapper(*args, **kwargs):
|
| 52 |
+
start = time.time()
|
| 53 |
+
result = func(*args, **kwargs)
|
| 54 |
+
elapsed = time.time() - start
|
| 55 |
+
return result, elapsed
|
| 56 |
+
return wrapper
|
logic/perfMetric.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import cv2
|
| 3 |
+
from skimage.metrics import structural_similarity as ssim
|
| 4 |
+
import time
|
| 5 |
+
|
| 6 |
+
def mse(img1, img2):
|
| 7 |
+
return np.mean((img1.astype(np.float32) - img2.astype(np.float32))**2)
|
| 8 |
+
|
| 9 |
+
def ssim_metric(img1, img2):
|
| 10 |
+
img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
|
| 11 |
+
img2_gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
|
| 12 |
+
return ssim(img1_gray, img2_gray)
|
| 13 |
+
|
| 14 |
+
def timed(func):
|
| 15 |
+
def wrapper(*args, **kwargs):
|
| 16 |
+
start = time.time()
|
| 17 |
+
result = func(*args, **kwargs)
|
| 18 |
+
elapsed = time.time() - start
|
| 19 |
+
return result, elapsed
|
| 20 |
+
return wrapper
|
logic/tileMapping.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import os
|
| 3 |
+
from PIL import Image
|
| 4 |
+
|
| 5 |
+
def load_tile_images(tile_dir, n_colors, cell_size):
|
| 6 |
+
tiles = []
|
| 7 |
+
if os.path.isdir(tile_dir):
|
| 8 |
+
files = sorted([os.path.join(tile_dir, f) for f in os.listdir(tile_dir) if f.lower().endswith(('.png','.jpg','.jpeg'))])
|
| 9 |
+
for f in files[:n_colors]:
|
| 10 |
+
tile = np.array(Image.open(f).resize((cell_size, cell_size)))
|
| 11 |
+
tiles.append(tile)
|
| 12 |
+
while len(tiles) < n_colors:
|
| 13 |
+
color = np.random.randint(0,255,3)
|
| 14 |
+
tile = np.ones((cell_size, cell_size, 3), dtype=np.uint8) * color
|
| 15 |
+
tiles.append(tile)
|
| 16 |
+
return tiles
|
| 17 |
+
|
| 18 |
+
def map_tiles_to_grid(grid_labels, tiles, cell_size):
|
| 19 |
+
grid_size = grid_labels.shape[0]
|
| 20 |
+
mosaic = np.zeros((grid_size*cell_size, grid_size*cell_size, 3), dtype=np.uint8)
|
| 21 |
+
for i in range(grid_size):
|
| 22 |
+
for j in range(grid_size):
|
| 23 |
+
mosaic[i*cell_size:(i+1)*cell_size, j*cell_size:(j+1)*cell_size] = tiles[grid_labels[i,j]]
|
| 24 |
+
return mosaic
|
resize_image.py
CHANGED
|
@@ -1,12 +0,0 @@
|
|
| 1 |
-
from PIL import Image
|
| 2 |
-
|
| 3 |
-
def resize_image(image: Image.Image, size: tuple = (800, 800)) -> Image.Image:
|
| 4 |
-
"""
|
| 5 |
-
Resize the input image to a fixed size (default 800x800 pixels).
|
| 6 |
-
Args:
|
| 7 |
-
image (PIL.Image.Image): Input image.
|
| 8 |
-
size (tuple): Target size as (width, height).
|
| 9 |
-
Returns:
|
| 10 |
-
PIL.Image.Image: Resized image.
|
| 11 |
-
"""
|
| 12 |
-
return image.resize(size, Image.LANCZOS)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|