ImageMosaicGenerator / logic /validation.py
meryadri's picture
code improvements
388efcc
"""Utility validators for user-provided parameters and resources.
This module centralizes sanity checks for user supplied values so they can
be reused from both the Gradio callbacks and lower level logic modules.
"""
from __future__ import annotations
from pathlib import Path
from typing import Iterable
from PIL import Image
class ValidationError(ValueError):
"""Raised when user parameters cannot be processed safely."""
def ensure_image(image: Image.Image | None) -> Image.Image:
"""Return a validated PIL image.
Args:
image (PIL.Image.Image | None): User uploaded image.
Returns:
PIL.Image.Image: The validated image converted to RGB mode.
Raises:
ValidationError: If the image is missing.
"""
if image is None:
raise ValidationError("Please upload an image or select an example before generating a mosaic.")
if image.mode != "RGB":
image = image.convert("RGB")
return image
def ensure_positive_int(value: int, name: str, minimum: int = 1, maximum: int | None = None) -> int:
"""Validate that an integer parameter falls within an accepted range.
Args:
value (int): Parameter to validate.
name (str): Human readable parameter name for error messages.
minimum (int, optional): Inclusive lower bound. Defaults to 1.
maximum (int | None, optional): Inclusive upper bound when provided.
Returns:
int: The validated integer.
Raises:
ValidationError: If the value is outside of the allowed range.
"""
if not isinstance(value, int):
raise ValidationError(f"{name} must be an integer.")
if value < minimum:
raise ValidationError(f"{name} must be at least {minimum}.")
if maximum is not None and value > maximum:
raise ValidationError(f"{name} must be less than or equal to {maximum}.")
return value
def ensure_grid_divisibility(image_size: int, grid_size: int) -> bool:
"""Return whether the grid divides the image size, without raising.
Mosaic generation crops the resized image to the nearest size that fits the
requested grid. Instead of blocking users when the division is imperfect,
this helper simply returns ``False`` so callers may display an informational
warning if desired while still proceeding with the closest valid size.
Args:
image_size (int): Target square image size in pixels.
grid_size (int): Number of grid cells per axis.
Returns:
bool: ``True`` if divisible, ``False`` otherwise.
"""
if grid_size <= 0 or image_size <= 0:
raise ValidationError("Image and grid sizes must be positive integers.")
if grid_size > image_size:
raise ValidationError("Grid size cannot exceed the resized image dimensions.")
return image_size % grid_size == 0
def ensure_file_exists(path: str | Path, description: str) -> Path:
"""Validate that a file path exists.
Args:
path (str | Path): Path to the required resource.
description (str): Friendly description for the user.
Returns:
pathlib.Path: Normalized path.
Raises:
FileNotFoundError: If the path does not exist.
"""
normalized = Path(path)
if not normalized.exists():
raise FileNotFoundError(f"{description} not found at {normalized}.")
return normalized
def ensure_non_empty(collection: Iterable, description: str) -> None:
"""Verify that an iterable contains elements.
Args:
collection (Iterable): Collection to check.
description (str): Description of the expected content.
Raises:
ValidationError: If the iterable is empty.
"""
if not any(True for _ in collection):
raise ValidationError(description)