File size: 2,455 Bytes
746317a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
"""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",
]