"""Image preprocessing utilities.""" import numpy as np from PIL import Image import hashlib from typing import Tuple, Optional def preprocess_image(image_input, max_size: int = 1024) -> Image.Image: """ Preprocess any image input into a standardized PIL RGB image. Args: image_input: PIL Image, numpy array, or file path max_size: maximum dimension for resizing Returns: PIL Image in RGB mode, resized to max_size if needed """ if isinstance(image_input, str): img = Image.open(image_input).convert("RGB") elif isinstance(image_input, np.ndarray): if image_input.ndim == 2: img = Image.fromarray(image_input).convert("RGB") elif image_input.ndim == 3: img = Image.fromarray(image_input).convert("RGB") else: raise ValueError(f"Unexpected array shape: {image_input.shape}") elif isinstance(image_input, Image.Image): img = image_input.convert("RGB") else: raise TypeError(f"Unsupported image type: {type(image_input)}") # Resize if too large w, h = img.size if max(w, h) > max_size: scale = max_size / max(w, h) new_w, new_h = int(w * scale), int(h * scale) img = img.resize((new_w, new_h), Image.LANCZOS) return img def compute_image_hash(img: Image.Image) -> str: """Compute SHA256 hash of image pixels for caching.""" return hashlib.sha256(np.array(img).tobytes()).hexdigest() def get_image_size(img: Image.Image) -> Tuple[int, int]: """Return (width, height) of image.""" return img.size def validate_image(img: Image.Image, min_size: int = 50) -> Optional[str]: """ Validate image meets minimum requirements. Returns error message if invalid, None if valid. """ w, h = img.size if w < min_size or h < min_size: return f"Image too small: {w}x{h}. Minimum {min_size}x{min_size} required." return None