wardrobe-us / src /detector /__init__.py
Ox1's picture
refactor(detector): replace monolithic detector with pluggable multi-backend package
746317a
Raw
History Blame Contribute Delete
2.46 kB
"""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",
]