| """
|
| app.py
|
| Deployment-only Gradio interface with grid segmentation visualization.
|
| Uses only pre-built cache files for fast cloud deployment.
|
| """
|
|
|
| import gradio as gr
|
| import numpy as np
|
| import cv2
|
| from pathlib import Path
|
| import time
|
| import pickle
|
| import warnings
|
| from typing import Tuple, Dict, Optional, List
|
| from dataclasses import dataclass
|
| from sklearn.cluster import MiniBatchKMeans
|
| from sklearn.metrics.pairwise import euclidean_distances
|
| from skimage.metrics import structural_similarity as ssim
|
|
|
|
|
| TILE_FOLDER = "extracted_images"
|
|
|
|
|
| AVAILABLE_CACHES = {
|
| 16: "cache_16x16_bins8_rot.pkl",
|
| 32: "cache_32x32_bins8_rot.pkl",
|
| 64: "cache_64x64_bins8_rot.pkl"
|
| }
|
|
|
| @dataclass
|
| class ImageContext:
|
| """Contextual analysis results."""
|
| has_faces: bool
|
| face_regions: List[Tuple[int, int, int, int]]
|
| is_portrait: bool
|
| is_landscape: bool
|
| content_complexity: float
|
|
|
| class DeploymentMosaicGenerator:
|
| """Deployment-optimized mosaic generator that NEVER builds caches."""
|
|
|
| def __init__(self, cache_file: str):
|
| """Initialize with existing cache file only."""
|
| self.cache_file = cache_file
|
|
|
|
|
| try:
|
| with open(cache_file, 'rb') as f:
|
| data = pickle.load(f)
|
|
|
| self.tile_images = data['tile_images']
|
| self.tile_colours = data['tile_colours']
|
| self.tile_names = data['tile_names']
|
| self.colour_palette = data['colour_palette']
|
| self.colour_groups = data['colour_groups']
|
| self.colour_indices = data['colour_indices']
|
| self.tile_size = data['tile_size']
|
|
|
| print(f"Loaded cache: {len(self.tile_images)} tiles, size {self.tile_size}")
|
|
|
| except Exception as e:
|
| raise RuntimeError(f"Failed to load cache {cache_file}: {e}")
|
|
|
|
|
| try:
|
| self.face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
|
| except:
|
| self.face_cascade = None
|
|
|
| def analyze_context(self, image: np.ndarray) -> ImageContext:
|
| """Basic context analysis for deployment."""
|
| faces = []
|
| has_faces = False
|
|
|
| if self.face_cascade is not None:
|
| try:
|
| gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
|
| detected_faces = self.face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(20, 20))
|
| faces = [(x, y, w, h) for (x, y, w, h) in detected_faces]
|
| has_faces = len(faces) > 0
|
| except:
|
| pass
|
|
|
|
|
| aspect_ratio = image.shape[1] / image.shape[0]
|
| is_portrait = aspect_ratio < 1.2 and has_faces
|
| is_landscape = aspect_ratio > 1.5
|
|
|
|
|
| gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
|
| edges = cv2.Canny(gray, 50, 150)
|
| content_complexity = np.mean(edges) / 255.0
|
|
|
| return ImageContext(
|
| has_faces=has_faces,
|
| face_regions=faces,
|
| is_portrait=is_portrait,
|
| is_landscape=is_landscape,
|
| content_complexity=content_complexity
|
| )
|
|
|
| def find_best_tile(self, target_colour: np.ndarray) -> int:
|
| """Find best matching tile using color subgroups."""
|
| distances = euclidean_distances(target_colour.reshape(1, -1), self.colour_palette)[0]
|
| target_bin = np.argmin(distances)
|
|
|
| if target_bin in self.colour_indices:
|
| index, tile_indices = self.colour_indices[target_bin]
|
| _, indices = index.kneighbors(target_colour.reshape(1, -1), n_neighbors=1)
|
| return tile_indices[indices[0][0]]
|
|
|
| return 0
|
|
|
| def create_mosaic_with_preprocessing(self, image: np.ndarray, grid_size: int, diversity_factor: float = 0.1) -> Tuple[np.ndarray, np.ndarray, ImageContext]:
|
| """Create mosaic with preprocessing visualization."""
|
| print(f"Creating {grid_size}x{grid_size} mosaic...")
|
|
|
|
|
| context = self.analyze_context(image)
|
|
|
|
|
| target_size = grid_size * self.tile_size[0]
|
| h, w = image.shape[:2]
|
| scale = max(target_size / w, target_size / h)
|
| new_w, new_h = int(w * scale), int(h * scale)
|
| resized = cv2.resize(image, (new_w, new_h))
|
|
|
|
|
| start_x = (new_w - target_size) // 2
|
| start_y = (new_h - target_size) // 2
|
| processed_image = resized[start_y:start_y + target_size, start_x:start_x + target_size]
|
|
|
|
|
| grid_visualization = self.create_grid_overlay(processed_image, grid_size)
|
|
|
|
|
| cell_size = target_size // grid_size
|
| mosaic = np.zeros((grid_size * self.tile_size[1], grid_size * self.tile_size[0], 3), dtype=np.uint8)
|
|
|
| usage_count = {} if diversity_factor > 0 else None
|
|
|
| for i in range(grid_size):
|
| for j in range(grid_size):
|
|
|
| start_y = i * cell_size
|
| end_y = start_y + cell_size
|
| start_x = j * cell_size
|
| end_x = start_x + cell_size
|
|
|
| cell = processed_image[start_y:end_y, start_x:end_x]
|
| target_colour = np.mean(cell, axis=(0, 1))
|
|
|
|
|
| best_tile_idx = self.find_best_tile(target_colour)
|
|
|
|
|
| if usage_count is not None:
|
| tile_name = self.tile_names[best_tile_idx]
|
| usage_penalty = usage_count.get(tile_name, 0) * diversity_factor
|
|
|
|
|
| if usage_penalty > 2.0:
|
| distances = euclidean_distances(target_colour.reshape(1, -1), self.tile_colours)[0]
|
| sorted_indices = np.argsort(distances)
|
| for idx in sorted_indices[1:6]:
|
| alt_tile_name = self.tile_names[idx]
|
| if usage_count.get(alt_tile_name, 0) < usage_count[tile_name]:
|
| best_tile_idx = idx
|
| break
|
|
|
| usage_count[self.tile_names[best_tile_idx]] = usage_count.get(self.tile_names[best_tile_idx], 0) + 1
|
|
|
|
|
| tile_start_y = i * self.tile_size[1]
|
| tile_end_y = tile_start_y + self.tile_size[1]
|
| tile_start_x = j * self.tile_size[0]
|
| tile_end_x = tile_start_x + self.tile_size[0]
|
|
|
| mosaic[tile_start_y:tile_end_y, tile_start_x:tile_end_x] = self.tile_images[best_tile_idx]
|
|
|
| return mosaic, grid_visualization, context
|
|
|
| def create_grid_overlay(self, image: np.ndarray, grid_size: int) -> np.ndarray:
|
| """Create visualization showing grid segmentation with enhanced styling."""
|
| h, w = image.shape[:2]
|
| cell_h = h // grid_size
|
| cell_w = w // grid_size
|
|
|
|
|
| grid_image = image.copy()
|
|
|
|
|
| line_color = (255, 255, 255)
|
| shadow_color = (0, 0, 0)
|
| line_thickness = max(1, min(w, h) // 500)
|
|
|
|
|
| for i in range(1, grid_size):
|
| x = i * cell_w
|
|
|
| cv2.line(grid_image, (x-1, 0), (x-1, h), shadow_color, line_thickness)
|
| cv2.line(grid_image, (x+1, 0), (x+1, h), shadow_color, line_thickness)
|
|
|
| cv2.line(grid_image, (x, 0), (x, h), line_color, line_thickness)
|
|
|
|
|
| for i in range(1, grid_size):
|
| y = i * cell_h
|
|
|
| cv2.line(grid_image, (0, y-1), (w, y-1), shadow_color, line_thickness)
|
| cv2.line(grid_image, (0, y+1), (w, y+1), shadow_color, line_thickness)
|
|
|
| cv2.line(grid_image, (0, y), (w, y), line_color, line_thickness)
|
|
|
|
|
| border_thickness = max(2, line_thickness * 2)
|
| cv2.rectangle(grid_image, (0, 0), (w-1, h-1), line_color, border_thickness)
|
| cv2.rectangle(grid_image, (border_thickness, border_thickness),
|
| (w-border_thickness-1, h-border_thickness-1), shadow_color, 1)
|
|
|
|
|
| font = cv2.FONT_HERSHEY_SIMPLEX
|
| font_scale = max(0.5, min(w, h) / 800)
|
| thickness = max(1, int(font_scale * 2))
|
|
|
|
|
| text_main = f"Grid: {grid_size}x{grid_size}"
|
| text_sub = f"{grid_size**2} cells total"
|
| text_cell = f"Cell size: {cell_w}x{cell_h}px"
|
|
|
|
|
| (main_w, main_h), _ = cv2.getTextSize(text_main, font, font_scale, thickness)
|
| (sub_w, sub_h), _ = cv2.getTextSize(text_sub, font, font_scale * 0.7, thickness)
|
| (cell_w_text, cell_h_text), _ = cv2.getTextSize(text_cell, font, font_scale * 0.6, thickness)
|
|
|
|
|
| padding = 10
|
| bg_width = max(main_w, sub_w, cell_w_text) + padding * 2
|
| bg_height = main_h + sub_h + cell_h_text + padding * 4
|
|
|
|
|
| overlay = grid_image.copy()
|
| cv2.rectangle(overlay, (10, 10), (10 + bg_width, 10 + bg_height), (0, 0, 0), -1)
|
| cv2.addWeighted(overlay, 0.7, grid_image, 0.3, 0, grid_image)
|
|
|
|
|
| cv2.rectangle(grid_image, (10, 10), (10 + bg_width, 10 + bg_height), line_color, 1)
|
|
|
|
|
| y_offset = 10 + padding + main_h
|
| cv2.putText(grid_image, text_main, (10 + padding, y_offset),
|
| font, font_scale, line_color, thickness)
|
|
|
| y_offset += sub_h + padding // 2
|
| cv2.putText(grid_image, text_sub, (10 + padding, y_offset),
|
| font, font_scale * 0.7, (200, 200, 200), thickness)
|
|
|
| y_offset += cell_h_text + padding // 2
|
| cv2.putText(grid_image, text_cell, (10 + padding, y_offset),
|
| font, font_scale * 0.6, (180, 180, 180), thickness)
|
|
|
|
|
| if grid_size <= 16:
|
| corner_size = max(15, min(cell_w, cell_h) // 10)
|
| for i in range(min(3, grid_size)):
|
| for j in range(min(3, grid_size)):
|
| x = j * cell_w + 5
|
| y = i * cell_h + 15
|
| cell_num = i * grid_size + j + 1
|
|
|
|
|
| cv2.circle(grid_image, (x + 10, y), 12, (0, 0, 0), -1)
|
| cv2.circle(grid_image, (x + 10, y), 12, line_color, 1)
|
|
|
|
|
| cv2.putText(grid_image, str(cell_num), (x + 5, y + 4),
|
| font, 0.4, line_color, 1)
|
|
|
| return grid_image
|
|
|
| def create_grid_visualization(image: np.ndarray, grid_size: int) -> np.ndarray:
|
| """Create visualization showing grid segmentation on preprocessed image."""
|
| h, w = image.shape[:2]
|
| cell_h = h // grid_size
|
| cell_w = w // grid_size
|
|
|
|
|
| grid_image = image.copy()
|
|
|
|
|
| for i in range(1, grid_size):
|
| x = i * cell_w
|
| cv2.line(grid_image, (x, 0), (x, h), (255, 255, 255), 1)
|
| cv2.line(grid_image, (x-1, 0), (x-1, h), (0, 0, 0), 1)
|
|
|
|
|
| for i in range(1, grid_size):
|
| y = i * cell_h
|
| cv2.line(grid_image, (0, y), (w, y), (255, 255, 255), 1)
|
| cv2.line(grid_image, (0, y-1), (w, y-1), (0, 0, 0), 1)
|
|
|
|
|
| font = cv2.FONT_HERSHEY_SIMPLEX
|
| font_scale = max(0.4, min(w, h) / 800)
|
| thickness = max(1, int(font_scale * 2))
|
|
|
|
|
| text = f"Grid: {grid_size}x{grid_size} = {grid_size**2} cells"
|
| (text_w, text_h), baseline = cv2.getTextSize(text, font, font_scale, thickness)
|
| cv2.rectangle(grid_image, (5, 5), (text_w + 15, text_h + baseline + 10), (0, 0, 0), -1)
|
| cv2.rectangle(grid_image, (5, 5), (text_w + 15, text_h + baseline + 10), (255, 255, 255), 1)
|
| cv2.putText(grid_image, text, (10, text_h + 8), font, font_scale, (255, 255, 255), thickness)
|
|
|
| return grid_image
|
|
|
| def calculate_global_ssim(original: np.ndarray, mosaic: np.ndarray) -> float:
|
| """
|
| Calculate Global SSIM by treating each image as a single entity.
|
| Computes global statistics for the entire image rather than using sliding windows.
|
| """
|
| if original.shape != mosaic.shape:
|
| original_resized = cv2.resize(original, (mosaic.shape[1], mosaic.shape[0]))
|
| else:
|
| original_resized = original
|
|
|
|
|
| orig_float = original_resized.astype(np.float64)
|
| mosaic_float = mosaic.astype(np.float64)
|
|
|
|
|
| C1 = (0.01 * 255) ** 2
|
| C2 = (0.03 * 255) ** 2
|
|
|
| global_ssim_values = []
|
|
|
|
|
| for channel in range(3):
|
| orig_channel = orig_float[:, :, channel]
|
| mosaic_channel = mosaic_float[:, :, channel]
|
|
|
|
|
| mu1 = np.mean(orig_channel)
|
| mu2 = np.mean(mosaic_channel)
|
|
|
|
|
| sigma1_sq = np.var(orig_channel)
|
| sigma2_sq = np.var(mosaic_channel)
|
| sigma12 = np.mean((orig_channel - mu1) * (mosaic_channel - mu2))
|
|
|
|
|
| numerator = (2 * mu1 * mu2 + C1) * (2 * sigma12 + C2)
|
| denominator = (mu1**2 + mu2**2 + C1) * (sigma1_sq + sigma2_sq + C2)
|
|
|
| channel_ssim = numerator / denominator
|
| global_ssim_values.append(channel_ssim)
|
|
|
|
|
| return float(np.mean(global_ssim_values))
|
|
|
| def calculate_global_color_similarity(original: np.ndarray, mosaic: np.ndarray) -> float:
|
| """Calculate global color distribution similarity."""
|
| if original.shape != mosaic.shape:
|
| original_resized = cv2.resize(original, (mosaic.shape[1], mosaic.shape[0]))
|
| else:
|
| original_resized = original
|
|
|
|
|
| orig_mean = np.mean(original_resized, axis=(0, 1))
|
| mosaic_mean = np.mean(mosaic, axis=(0, 1))
|
|
|
| orig_std = np.std(original_resized, axis=(0, 1))
|
| mosaic_std = np.std(mosaic, axis=(0, 1))
|
|
|
|
|
| color_mean_diff = np.linalg.norm(orig_mean - mosaic_mean)
|
| color_mean_sim = 1.0 / (1.0 + color_mean_diff / 255.0)
|
|
|
|
|
| color_std_diff = np.linalg.norm(orig_std - mosaic_std)
|
| color_std_sim = 1.0 / (1.0 + color_std_diff / 255.0)
|
|
|
|
|
| global_color_sim = 0.6 * color_mean_sim + 0.4 * color_std_sim
|
|
|
| return float(global_color_sim)
|
|
|
| def calculate_metrics(original: np.ndarray, mosaic: np.ndarray) -> Dict[str, float]:
|
| """Calculate enhanced quality metrics with global SSIM."""
|
|
|
| if original.shape != mosaic.shape:
|
| original_resized = cv2.resize(original, (mosaic.shape[1], mosaic.shape[0]))
|
| else:
|
| original_resized = original
|
|
|
|
|
| orig_float = original_resized.astype(np.float64)
|
| mosaic_float = mosaic.astype(np.float64)
|
| mse = float(np.mean((orig_float - mosaic_float) ** 2))
|
|
|
|
|
| psnr = float(20 * np.log10(255.0 / np.sqrt(mse))) if mse > 0 else float('inf')
|
|
|
|
|
| global_ssim = calculate_global_ssim(original, mosaic)
|
|
|
|
|
| global_color_sim = calculate_global_color_similarity(original, mosaic)
|
|
|
|
|
| correlations = []
|
| for channel in range(3):
|
| hist_orig = cv2.calcHist([original_resized], [channel], None, [256], [0, 256])
|
| hist_mosaic = cv2.calcHist([mosaic], [channel], None, [256], [0, 256])
|
| corr = cv2.compareHist(hist_orig, hist_mosaic, cv2.HISTCMP_CORREL)
|
| correlations.append(corr)
|
| histogram_similarity = float(np.mean(correlations))
|
|
|
|
|
| ssim_norm = (global_ssim + 1) / 2
|
| psnr_norm = min(psnr / 50.0, 1.0)
|
|
|
|
|
| overall = (
|
| 0.4 * ssim_norm +
|
| 0.25 * global_color_sim +
|
| 0.2 * histogram_similarity +
|
| 0.15 * psnr_norm
|
| ) * 100
|
|
|
| return {
|
| 'mse': mse,
|
| 'psnr': psnr,
|
| 'ssim': global_ssim,
|
| 'global_color_similarity': global_color_sim,
|
| 'histogram_similarity': histogram_similarity,
|
| 'overall_quality': float(overall)
|
| }
|
|
|
| def get_best_available_cache(requested_tile_size: int) -> Optional[str]:
|
| """Get the best available cache for requested tile size."""
|
|
|
| if requested_tile_size in AVAILABLE_CACHES:
|
| cache_file = AVAILABLE_CACHES[requested_tile_size]
|
| if Path(cache_file).exists():
|
| return cache_file
|
|
|
|
|
| available_sizes = []
|
| for size, cache_file in AVAILABLE_CACHES.items():
|
| if Path(cache_file).exists():
|
| available_sizes.append(size)
|
|
|
| if not available_sizes:
|
| return None
|
|
|
|
|
| closest_size = min(available_sizes, key=lambda x: abs(x - requested_tile_size))
|
| return AVAILABLE_CACHES[closest_size]
|
|
|
| def create_mosaic_interface(image, grid_size, tile_size, diversity_factor, enable_rotation, apply_quantization, n_colors):
|
| """Main interface function with grid visualization - deployment optimized."""
|
| if image is None:
|
| return None, None, None, "Please upload an image first.", "Please upload an image first."
|
|
|
| try:
|
| start_time = time.time()
|
|
|
|
|
| cache_file = get_best_available_cache(tile_size)
|
| if cache_file is None:
|
| error_msg = f"No cache available for tile size {tile_size}x{tile_size}"
|
| return None, None, None, error_msg, error_msg
|
|
|
|
|
| generator = DeploymentMosaicGenerator(cache_file)
|
|
|
|
|
| if apply_quantization:
|
| pixels = image.reshape(-1, 3)
|
| sample_size = min(5000, len(pixels))
|
| sampled_pixels = pixels[np.random.choice(len(pixels), sample_size, replace=False)]
|
|
|
| with warnings.catch_warnings():
|
| warnings.filterwarnings("ignore")
|
| kmeans = MiniBatchKMeans(n_clusters=n_colors, batch_size=500, random_state=42)
|
| kmeans.fit(sampled_pixels)
|
| labels = kmeans.predict(pixels)
|
|
|
| quantized_pixels = kmeans.cluster_centers_[labels]
|
| image = quantized_pixels.reshape(image.shape).astype(np.uint8)
|
|
|
|
|
| mosaic, grid_viz, context = generator.create_mosaic_with_preprocessing(image, grid_size, diversity_factor)
|
|
|
|
|
| metrics = calculate_metrics(image, mosaic)
|
|
|
| total_time = time.time() - start_time
|
|
|
|
|
| comparison = np.hstack([
|
| cv2.resize(image, (mosaic.shape[1]//2, mosaic.shape[0])),
|
| cv2.resize(mosaic, (mosaic.shape[1]//2, mosaic.shape[0]))
|
| ])
|
|
|
|
|
| metrics_text = f"""ENHANCED PERFORMANCE METRICS
|
|
|
| Mean Squared Error (MSE): {metrics['mse']:.2f}
|
|
|
| Peak Signal-to-Noise Ratio (PSNR): {metrics['psnr']:.2f} dB
|
|
|
| Global Structural Similarity (SSIM): {metrics['ssim']:.4f}
|
|
|
| Global Color Similarity: {metrics['global_color_similarity']:.4f}
|
|
|
| Color Histogram Similarity: {metrics['histogram_similarity']:.4f}
|
|
|
| Overall Quality Score: {metrics['overall_quality']:.1f}/100"""
|
|
|
|
|
| status = f"""Generation Successful!
|
|
|
| Grid: {grid_size}x{grid_size} = {grid_size**2} tiles
|
| Tile Size: {tile_size}x{tile_size} pixels
|
| Processing Time: {total_time:.2f} seconds
|
| Cache Used: {cache_file}
|
|
|
| Contextual Analysis:
|
| • Faces Detected: {len(context.face_regions)}
|
| • Scene Type: {'Portrait' if context.is_portrait else 'Landscape' if context.is_landscape else 'General'}
|
| • Content Complexity: {context.content_complexity:.3f}"""
|
|
|
| return mosaic, comparison, grid_viz, metrics_text, status
|
|
|
| except Exception as e:
|
| error_msg = f"Error: {str(e)}"
|
| return None, None, None, error_msg, error_msg
|
|
|
| def verify_deployment_setup():
|
| """Check deployment setup without building anything."""
|
| available_caches = {}
|
| total_size_mb = 0
|
|
|
| for size, cache_file in AVAILABLE_CACHES.items():
|
| if Path(cache_file).exists():
|
| size_mb = Path(cache_file).stat().st_size / 1024 / 1024
|
| available_caches[size] = size_mb
|
| total_size_mb += size_mb
|
|
|
| setup_msg = f"Found {len(available_caches)} cache files ({total_size_mb:.1f}MB total)"
|
|
|
| return len(available_caches) > 0, setup_msg, available_caches
|
|
|
| def get_system_status():
|
| """System status for deployment."""
|
| setup_ok, setup_msg, available_caches = verify_deployment_setup()
|
|
|
| cache_list = ""
|
| for size, size_mb in available_caches.items():
|
| cache_list += f" {size}x{size}: {size_mb:.1f}MB\n"
|
|
|
| status = f"""DEPLOYMENT STATUS
|
| {'='*30}
|
|
|
| Cache System: {'✅' if setup_ok else '❌'}
|
| {setup_msg}
|
|
|
| Available Caches:
|
| {cache_list if cache_list else " None found"}
|
|
|
| Smart Selection: System automatically uses the best
|
| available cache for your chosen tile size.
|
|
|
| INNOVATIONS INCLUDED
|
| {'='*30}
|
|
|
| Contextual Awareness: Face detection, scene classification
|
| Multi-Orientation: Rotation variants in cache files
|
| Performance: Color subgrouping, Mini-Batch K-means
|
| Enhanced Metrics: Global SSIM, Color similarity analysis
|
| Grid Visualization: Shows preprocessing segmentation
|
|
|
| DEPLOYMENT OPTIMIZED
|
| {'='*30}
|
|
|
| • No cache building during startup
|
| • Fast initialization with pre-built caches
|
| • Lightweight processing for cloud deployment
|
| • Maintains all core innovations
|
| • Global quality assessment"""
|
|
|
| return status
|
|
|
| def create_interface():
|
| """Create deployment-optimized Gradio interface with grid visualization."""
|
|
|
| css = """
|
| .gradio-container { max-width: 100% !important; padding: 0 20px; }
|
| .left-panel {
|
| flex: 0 0 350px; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
| padding: 25px; border-radius: 15px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
| }
|
| .right-panel {
|
| flex: 1; background: white; padding: 25px; border-radius: 15px;
|
| box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
| }
|
| .metrics-display {
|
| background: linear-gradient(145deg, #667eea 0%, #764ba2 100%);
|
| color: white; padding: 20px; border-radius: 10px;
|
| font-family: 'Courier New', monospace; font-size: 14px; line-height: 1.6;
|
| }
|
| """
|
|
|
| with gr.Blocks(css=css, title="Advanced Mosaic Generator") as demo:
|
|
|
| gr.Markdown("# Advanced Contextual Mosaic Generator")
|
| gr.Markdown("AI-powered mosaic creation with contextual awareness, grid visualization, and performance metrics.")
|
|
|
| with gr.Accordion("System Status", open=False):
|
| status_display = gr.Textbox(value=get_system_status(), lines=22, show_label=False)
|
| gr.Button("Refresh Status").click(fn=get_system_status, outputs=status_display)
|
|
|
| with gr.Row():
|
|
|
| with gr.Column(scale=0, min_width=350, elem_classes=["left-panel"]):
|
| gr.Markdown("## Configuration")
|
|
|
|
|
| generate_btn = gr.Button("Generate Mosaic", variant="primary", size="lg")
|
|
|
| gr.Markdown("---")
|
|
|
|
|
| input_image = gr.Image(type="numpy", label="Upload Image", height=200)
|
|
|
|
|
| grid_size = gr.Slider(16, 128, 32, step=8, label="Grid Size", info="Number of tiles per side")
|
| tile_size = gr.Slider(16, 32, 32, step=16, label="Tile Size", info="Must match available cache")
|
| diversity_factor = gr.Slider(0.0, 0.5, 0.15, step=0.05, label="Diversity", info="Tile variety")
|
| enable_rotation = gr.Checkbox(label="Enable Rotation", value=False, info="Uses rotation variants if available")
|
| apply_quantization = gr.Checkbox(label="Color Quantization", value=True)
|
| n_colors = gr.Slider(4, 24, 12, step=2, label="Colors")
|
|
|
|
|
| gr.Markdown("### Or, try with an example:")
|
| gr.Examples(
|
| examples=[
|
| ["EmmaPotrait.jpg", 64, 32, 0.15, False, True, 12],
|
| ["Batman.jpg", 128, 16, 0.05, False, False, 16],
|
| ["Indian_Dog.jpg", 56, 32, 0.2, False, True, 16],
|
| ],
|
| inputs=[input_image, grid_size, tile_size, diversity_factor, enable_rotation, apply_quantization, n_colors]
|
| )
|
|
|
|
|
| gr.Markdown("### Quick Presets")
|
| with gr.Row():
|
| preset_fast = gr.Button("Fast", size="sm")
|
| preset_quality = gr.Button("Quality", size="sm")
|
|
|
|
|
| with gr.Column(scale=2, elem_classes=["right-panel"]):
|
| gr.Markdown("## Results & Analysis")
|
|
|
| with gr.Row():
|
| with gr.Column():
|
| gr.Markdown("### Generated Mosaic")
|
| mosaic_output = gr.Image(height=300, show_label=False)
|
|
|
| with gr.Column():
|
| gr.Markdown("### Comparison (Original | Mosaic)")
|
| comparison_output = gr.Image(height=300, show_label=False)
|
|
|
| with gr.Row():
|
| with gr.Column():
|
| gr.Markdown("### Grid Segmentation Visualization")
|
| grid_viz_output = gr.Image(height=300, show_label=False)
|
| gr.Markdown("*Shows how the image is divided into cells for tile placement*")
|
|
|
| with gr.Column():
|
| gr.Markdown("### Performance Metrics")
|
| metrics_output = gr.Textbox(lines=8, elem_classes=["metrics-display"], show_label=False)
|
|
|
| status_output = gr.Textbox(label="Generation Status", lines=6)
|
|
|
|
|
| def fast_preset():
|
| return 24, 32, 0.1, False, True, 8
|
|
|
| def quality_preset():
|
| return 128, 16, 0.0, True, False, 24
|
|
|
| generate_btn.click(
|
| fn=create_mosaic_interface,
|
| inputs=[input_image, grid_size, tile_size, diversity_factor, enable_rotation, apply_quantization, n_colors],
|
| outputs=[mosaic_output, comparison_output, grid_viz_output, metrics_output, status_output]
|
| )
|
|
|
| preset_fast.click(fn=fast_preset, outputs=[grid_size, tile_size, diversity_factor, enable_rotation, apply_quantization, n_colors])
|
| preset_quality.click(fn=quality_preset, outputs=[grid_size, tile_size, diversity_factor, enable_rotation, apply_quantization, n_colors])
|
|
|
| return demo
|
|
|
| if __name__ == "__main__":
|
| print("Advanced Mosaic Generator - Deployment Version with Grid Visualization")
|
| print("Checking deployment setup...")
|
|
|
| setup_ok, setup_msg, caches = verify_deployment_setup()
|
| if setup_ok:
|
| print(f"Deployment ready: {setup_msg}")
|
| demo = create_interface()
|
| demo.launch(server_name="0.0.0.0", server_port=7860, share=True)
|
| else:
|
| print(f"Deployment not ready: {setup_msg}")
|
| print("Please upload the required cache files") |