feat(compressors): add OWLv2 bbox crop to HashPipeline and refactor image utilities

- Add crop_batch method to HashPipeline for cropping images using OWLv2 detection boxes
- Integrate crop_batch into pipeline forward pass (extract_hash and extract_features)
- Move extract_masked_region from compressors/proposal/utils.py to utils/image.py
- Add crop_image_by_bbox utility function in utils/image.py
- Update type annotations to use dict[str, Any] instead of bare dict
- Update .justfile to add memory server command
- Update marimo dependency to >=0.22.0
- Update nvidia CUDA package markers for platform compatibility
This commit is contained in:
2026-04-03 19:23:11 +08:00
parent 4e16e38f32
commit 5f41cf5794
7 changed files with 111 additions and 44 deletions

View File

@@ -4,7 +4,7 @@ from .feature_extractor import (
extract_single_image_feature,
infer_vector_dim,
)
from .image import numpy_to_pil
from .image import crop_image_by_bbox, extract_masked_region, numpy_to_pil
__all__ = [
"get_device",
@@ -13,4 +13,6 @@ __all__ = [
"extract_single_image_feature",
"extract_batch_features",
"numpy_to_pil",
"extract_masked_region",
"crop_image_by_bbox",
]

View File

@@ -1,12 +1,64 @@
"""Image conversion utilities."""
"""Image utilities for conversion, masking, and cropping."""
from __future__ import annotations
from collections.abc import Sequence
import numpy as np
from numpy.typing import NDArray
from PIL import Image
def numpy_to_pil(rgb: np.ndarray) -> Image.Image:
def extract_masked_region(
image: Image.Image,
mask: NDArray[np.bool_],
) -> Image.Image:
"""Extract masked region from image.
Args:
image: Original PIL Image.
mask: Binary mask as numpy array (True = keep).
Returns:
PIL Image with only the masked region visible.
"""
image_np = np.array(image.convert("RGB"))
masked_np = image_np * mask[:, :, np.newaxis]
return Image.fromarray(masked_np.astype(np.uint8))
def crop_image_by_bbox(
image: Image.Image,
bbox: Sequence[float],
) -> Image.Image:
"""Crop an image by bounding box [x1, y1, x2, y2].
Args:
image: Source PIL image.
bbox: OWLv2-style box coordinates [x1, y1, x2, y2].
Returns:
Cropped PIL image. Returns the original image when bbox is invalid.
"""
if len(bbox) != 4:
return image
x1, y1, x2, y2 = tuple(float(v) for v in bbox)
if not np.isfinite([x1, y1, x2, y2]).all():
return image
left = max(0, int(np.floor(x1)))
top = max(0, int(np.floor(y1)))
right = min(image.width, int(np.ceil(x2)))
bottom = min(image.height, int(np.ceil(y2)))
if right <= left or bottom <= top:
return image
return image.crop((left, top, right, bottom))
def numpy_to_pil(rgb: NDArray[np.uint8]) -> Image.Image:
"""Convert an RGB numpy array to a PIL Image.
Handles arrays with 4 channels (RGBA) by dropping the alpha channel.