"""Garment detection package with pluggable backends. Public API: detect_garments(image_path, backend=None) -> list[BoundingBox] crop_garments(image_path, boxes, max_size=512) -> list[bytes] detect_and_crop(image_path, backend=None) -> list[bytes] """ import io import logging from PIL import Image from ._types import BoundingBox, DetectorBackend from ._registry import get_backend, list_available, list_registered, register from .. import settings logger = logging.getLogger(__name__) MIN_CROP_SIZE = 50 # Import backends to trigger registration from .backends import yolos, yolov8, grounding_dino # noqa: F401, E402 def detect_garments(image_path: str, backend: str | None = None) -> list[BoundingBox]: """Detect garment regions in an image. Uses the configured backend (from settings) unless overridden. Returns a list of BoundingBox — does NOT classify garments. """ if backend is None: backend = settings.get("detection_backend") detector = get_backend(backend) boxes = detector.detect(image_path) valid = [b for b in boxes if b.width >= MIN_CROP_SIZE and b.height >= MIN_CROP_SIZE] logger.info( "Detected %d regions (%d valid, %d too small) via '%s' in: %s", len(boxes), len(valid), len(boxes) - len(valid), backend, image_path, ) return valid def crop_garments( image_path: str, boxes: list[BoundingBox], max_size: int = 512, ) -> list[bytes]: """Crop each bounding box from the image as JPEG bytes. Pure PIL logic — no model involved. Each crop is resized to fit within max_size pixels (longest side). """ img = Image.open(image_path).convert("RGB") crops = [] for box in boxes: cropped = img.crop((box.x1, box.y1, box.x2, box.y2)) cropped.thumbnail((max_size, max_size), Image.LANCZOS) buffer = io.BytesIO() cropped.save(buffer, format="JPEG", quality=85) crops.append(buffer.getvalue()) return crops def detect_and_crop(image_path: str, backend: str | None = None) -> list[bytes]: """Convenience: detect + crop in one call. Maintains backward compatibility with vision.py imports. """ boxes = detect_garments(image_path, backend) return crop_garments(image_path, boxes) __all__ = [ "BoundingBox", "DetectorBackend", "detect_garments", "crop_garments", "detect_and_crop", "list_available", "register", ]