Download Numba_Scripts/ImagePreprocessor.py from NiranjanSathish/Interactive_Mosaic_Generator_Optimized: direct link, hf CLI and curl.
- Browser
- Download file 5.91 kB
-
https://huggingface.co/spaces/NiranjanSathish/Interactive_Mosaic_Generator_Optimized/resolve/main/Numba_Scripts/ImagePreprocessor.py
- Command line
-
hf download hf://spaces/NiranjanSathish/Interactive_Mosaic_Generator_Optimized/Numba_Scripts/ImagePreprocessor.py
-
curl -L -o ImagePreprocessor.py https://huggingface.co/spaces/NiranjanSathish/Interactive_Mosaic_Generator_Optimized/resolve/main/Numba_Scripts/ImagePreprocessor.py
5.91 kB
| """ | |
| ImagePreprocessor.py - FULLY OPTIMIZED | |
| Minimal overhead, fast operations, no unnecessary prints. | |
| """ | |
| import cv2 | |
| import numpy as np | |
| from PIL import Image | |
| from typing import Tuple, Optional | |
| from sklearn.cluster import MiniBatchKMeans | |
| import warnings | |
| class ImagePreprocessor: | |
| """OPTIMIZED image preprocessing for mosaic generation.""" | |
| def __init__(self, target_resolution: Tuple[int, int] = (800, 600), | |
| grid_size: Tuple[int, int] = (20, 15), | |
| verbose: bool = False): | |
| """Initialize preprocessor - MINIMAL overhead.""" | |
| self.target_resolution = target_resolution | |
| self.grid_size = grid_size | |
| self.verbose = verbose | |
| # Pre-compute (avoid division later) | |
| self.tile_width = target_resolution[0] // grid_size[0] | |
| self.tile_height = target_resolution[1] // grid_size[1] | |
| self.adjusted_width = self.tile_width * grid_size[0] | |
| self.adjusted_height = self.tile_height * grid_size[1] | |
| def load_and_preprocess_image(self, image_path: str, | |
| apply_quantization: bool = False, | |
| n_colors: int = 16) -> Optional[np.ndarray]: | |
| """Load and preprocess - OPTIMIZED for speed.""" | |
| try: | |
| # Load and convert | |
| image = cv2.imread(image_path) | |
| if image is None: | |
| raise ValueError(f"Could not load image: {image_path}") | |
| image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) | |
| processed_image = self._resize_and_crop(image) | |
| # Only quantize if explicitly requested | |
| if apply_quantization and n_colors < 256: | |
| processed_image = self._apply_color_quantization(processed_image, n_colors) | |
| return processed_image | |
| except Exception as e: | |
| if self.verbose: | |
| print(f"Error processing {image_path}: {str(e)}") | |
| return None | |
| def preprocess_numpy_image(self, image: np.ndarray, | |
| apply_quantization: bool = False, | |
| n_colors: int = 16) -> Optional[np.ndarray]: | |
| """Preprocess numpy image (for Gradio).""" | |
| try: | |
| if len(image.shape) != 3 or image.shape[2] != 3: | |
| raise ValueError("Image must be RGB (H, W, 3)") | |
| processed_image = self._resize_and_crop(image) | |
| if apply_quantization and n_colors < 256: | |
| processed_image = self._apply_color_quantization(processed_image, n_colors) | |
| return processed_image | |
| except Exception as e: | |
| if self.verbose: | |
| print(f"Error processing image: {str(e)}") | |
| return None | |
| def _resize_and_crop(self, image: np.ndarray) -> np.ndarray: | |
| """Resize and crop - OPTIMIZED.""" | |
| h, w = image.shape[:2] | |
| target_w, target_h = self.adjusted_width, self.adjusted_height | |
| # Scale to fill | |
| scale = max(target_w / w, target_h / h) | |
| new_w = int(w * scale) | |
| new_h = int(h * scale) | |
| # Use INTER_LINEAR for speed (faster than INTER_AREA) | |
| resized = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_LINEAR) | |
| # Center crop | |
| start_x = (new_w - target_w) // 2 | |
| start_y = (new_h - target_h) // 2 | |
| cropped = resized[start_y:start_y + target_h, start_x:start_x + target_w] | |
| return cropped | |
| def _apply_color_quantization(self, image: np.ndarray, n_colors: int) -> np.ndarray: | |
| """OPTIMIZED color quantization with aggressive sampling.""" | |
| h, w, c = image.shape | |
| pixels = image.reshape(-1, c) | |
| total_pixels = len(pixels) | |
| # Aggressive sampling | |
| max_sample_size = 10000 | |
| if total_pixels > max_sample_size: | |
| # Sample for fitting | |
| sample_indices = np.random.randint(0, total_pixels, size=max_sample_size) | |
| sampled_pixels = pixels[sample_indices].astype(np.float32) | |
| batch_size = min(max(len(sampled_pixels) // 10, 500), 2000) | |
| with warnings.catch_warnings(): | |
| warnings.filterwarnings("ignore") | |
| kmeans = MiniBatchKMeans( | |
| n_clusters=n_colors, batch_size=batch_size, | |
| random_state=42, n_init=1, max_iter=50 | |
| ) | |
| # Fit on sample, predict on full | |
| kmeans.fit(sampled_pixels) | |
| labels = kmeans.predict(pixels.astype(np.float32)) | |
| else: | |
| # Small image | |
| batch_size = min(max(total_pixels // 100, 500), 5000) | |
| with warnings.catch_warnings(): | |
| warnings.filterwarnings("ignore") | |
| kmeans = MiniBatchKMeans( | |
| n_clusters=n_colors, batch_size=batch_size, | |
| random_state=42, n_init=1, max_iter=50 | |
| ) | |
| labels = kmeans.fit_predict(pixels.astype(np.float32)) | |
| # Apply quantization | |
| quantized_pixels = kmeans.cluster_centers_[labels] | |
| quantized_image = quantized_pixels.reshape(h, w, c).astype(np.uint8) | |
| return quantized_image | |
| def save_preprocessed_image(self, image: np.ndarray, output_path: str): | |
| """Save preprocessed image.""" | |
| try: | |
| pil_image = Image.fromarray(image) | |
| pil_image.save(output_path, quality=95) | |
| except Exception as e: | |
| if self.verbose: | |
| print(f"Error saving image: {str(e)}") |