Files
roboflow--supervision/tests/detection/test_inference_slicer_compact.py
wehub-resource-sync 9194ef5abd
Docs/Test Workflow / Test docs build (push) Failing after 0s
Check links & references / links-check (push) Failing after 1s
Pytest/Test Workflow / Import Test and Pytest Run (ubuntu-latest, 3.10) (push) Failing after 0s
Pytest/Test Workflow / Import Test and Pytest Run (ubuntu-latest, 3.11) (push) Failing after 0s
PR Conflict Labeler / main (push) Failing after 2s
Pytest/Test Workflow / Import Test and Pytest Run (ubuntu-latest, 3.12) (push) Failing after 2s
Pytest/Test Workflow / Import Test and Pytest Run (ubuntu-latest, 3.13) (push) Failing after 0s
Pytest/Test Workflow / Build this Package (push) Failing after 5s
Pytest/Test Workflow / Import Test and Pytest Run (macos-latest, 3.10) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (macos-latest, 3.11) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (macos-latest, 3.12) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (macos-latest, 3.13) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (windows-latest, 3.10) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (windows-latest, 3.11) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (windows-latest, 3.12) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (windows-latest, 3.13) (push) Has been cancelled
Pytest/Test Workflow / testing-guardian (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:06:10 +08:00

196 lines
7.0 KiB
Python

"""Integration tests for InferenceSlicer with compact_masks=True.
Verifies that with compact_masks=True:
- Masks stay as CompactMask throughout the pipeline (no dense materialisation).
- NMS is computed via RLE IoU (no resize, no dense (N,H,W) alloc).
- Final detections are pixel-identical to the compact_masks=False path.
"""
import numpy as np
import supervision as sv
from supervision.detection.compact_mask import CompactMask
from supervision.detection.core import Detections
def _fake_seg_callback(tile: np.ndarray) -> Detections:
"""Return two non-overlapping segmentation detections for any tile."""
h, w = tile.shape[:2]
masks = np.zeros((2, h, w), dtype=bool)
masks[0, : h // 3, : w // 3] = True
masks[1, h // 2 :, w // 2 :] = True
xyxy = np.array([[0, 0, w // 3, h // 3], [w // 2, h // 2, w, h]], dtype=np.float32)
return Detections(
xyxy=xyxy,
mask=masks,
confidence=np.array([0.9, 0.8], dtype=np.float32),
class_id=np.array([0, 1]),
)
class TestInferenceSlicerCompactMasks:
"""Tests that compact_masks=True keeps masks in RLE form end-to-end.
The pipeline inside InferenceSlicer goes:
callback → CompactMask.from_dense (tile coords)
→ with_offset (full-image coords)
→ CompactMask.merge (all tiles)
→ mask_non_max_suppression → compact_mask_iou_batch (RLE IoU)
None of those steps materialise a full (N, H, W) dense array.
"""
def test_compact_masks_flag_converts_dense_to_compact(self) -> None:
"""Masks returned from callback are CompactMask after _run_callback."""
image = np.zeros((200, 200, 3), dtype=np.uint8)
slicer = sv.InferenceSlicer(
callback=_fake_seg_callback,
slice_wh=200,
overlap_wh=0,
overlap_filter=sv.OverlapFilter.NONE,
compact_masks=True,
)
result = slicer(image)
assert isinstance(result.mask, CompactMask), (
f"compact_masks=True must produce a CompactMask, got {type(result.mask)}"
)
def test_compact_masks_false_keeps_dense(self) -> None:
"""Default (compact_masks=False) keeps dense ndarray masks."""
image = np.zeros((200, 200, 3), dtype=np.uint8)
slicer = sv.InferenceSlicer(
callback=_fake_seg_callback,
slice_wh=200,
overlap_wh=0,
overlap_filter=sv.OverlapFilter.NONE,
compact_masks=False,
)
result = slicer(image)
assert isinstance(result.mask, np.ndarray)
assert not isinstance(result.mask, CompactMask)
def test_compact_and_dense_pipelines_give_same_masks(self) -> None:
"""compact_masks=True and False must produce pixel-identical final masks."""
image = np.zeros((300, 300, 3), dtype=np.uint8)
slicer_dense = sv.InferenceSlicer(
callback=_fake_seg_callback,
slice_wh=150,
overlap_wh=0,
overlap_filter=sv.OverlapFilter.NON_MAX_SUPPRESSION,
iou_threshold=0.3,
compact_masks=False,
)
slicer_compact = sv.InferenceSlicer(
callback=_fake_seg_callback,
slice_wh=150,
overlap_wh=0,
overlap_filter=sv.OverlapFilter.NON_MAX_SUPPRESSION,
iou_threshold=0.3,
compact_masks=True,
)
det_dense = slicer_dense(image)
det_compact = slicer_compact(image)
assert len(det_dense) == len(det_compact)
dense_masks = det_dense.mask
compact_masks_arr = np.asarray(det_compact.mask)
# Sort both by xyxy to align order (NMS order may differ).
def _sort_key(d: Detections) -> np.ndarray:
return d.xyxy[:, 0] * 10000 + d.xyxy[:, 1]
order_d = np.argsort(_sort_key(det_dense))
order_c = np.argsort(_sort_key(det_compact))
np.testing.assert_array_equal(
dense_masks[order_d],
compact_masks_arr[order_c],
err_msg="compact_masks pipeline produced different mask pixels than dense",
)
def test_compact_masks_preserve_pixels_outside_detector_box(self) -> None:
"""compact_masks=True crops to the full tile, so mask pixels outside the
detection xyxy box (but inside the tile) are preserved."""
image = np.zeros((100, 100, 3), dtype=np.uint8)
def callback(tile: np.ndarray) -> Detections:
h, w = tile.shape[:2]
masks = np.zeros((1, h, w), dtype=bool)
masks[0, 0, 0] = True
masks[0, h - 1, w - 1] = True
return Detections(
xyxy=np.array([[0, 0, 10, 10]], dtype=np.float32),
mask=masks,
confidence=np.array([0.9], dtype=np.float32),
class_id=np.array([0]),
)
dense = sv.InferenceSlicer(
callback=callback,
slice_wh=100,
overlap_wh=0,
overlap_filter=sv.OverlapFilter.NONE,
compact_masks=False,
)(image)
compact = sv.InferenceSlicer(
callback=callback,
slice_wh=100,
overlap_wh=0,
overlap_filter=sv.OverlapFilter.NONE,
compact_masks=True,
)(image)
assert isinstance(compact.mask, CompactMask)
np.testing.assert_array_equal(compact.mask.to_dense(), dense.mask)
def test_nms_with_overlapping_tiles_uses_rle_iou(self) -> None:
"""With overlapping tiles, NMS must suppress duplicates using RLE IoU."""
image = np.zeros((300, 300, 3), dtype=np.uint8)
call_count = 0
def counting_callback(tile: np.ndarray) -> Detections:
nonlocal call_count
call_count += 1
return _fake_seg_callback(tile)
slicer = sv.InferenceSlicer(
callback=counting_callback,
slice_wh=200,
overlap_wh=100, # heavy overlap → many duplicate detections
overlap_filter=sv.OverlapFilter.NON_MAX_SUPPRESSION,
iou_threshold=0.3,
compact_masks=True,
)
result = slicer(image)
assert call_count > 1, "Should have run on multiple tiles"
assert isinstance(result.mask, CompactMask), (
"Result mask must remain CompactMask after cross-tile NMS"
)
def test_no_mask_callback_unaffected(self) -> None:
"""compact_masks=True must not crash when callback returns no masks."""
def box_only_callback(tile: np.ndarray) -> Detections:
h, w = tile.shape[:2]
return Detections(
xyxy=np.array([[0, 0, w // 2, h // 2]], dtype=np.float32),
confidence=np.array([0.9]),
class_id=np.array([0]),
)
image = np.zeros((200, 200, 3), dtype=np.uint8)
slicer = sv.InferenceSlicer(
callback=box_only_callback,
slice_wh=200,
overlap_wh=0,
overlap_filter=sv.OverlapFilter.NONE,
compact_masks=True,
)
result = slicer(image)
assert result.mask is None