Files
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

186 lines
6.6 KiB
Python

from contextlib import ExitStack as DoesNotRaise
import numpy as np
import pytest
import supervision as sv
from tests.helpers import _create_detections
DETECTION_BOXES = np.array(
[
[35.0, 35.0, 65.0, 65.0],
[60.0, 60.0, 90.0, 90.0],
[85.0, 85.0, 115.0, 115.0],
[110.0, 110.0, 140.0, 140.0],
[135.0, 135.0, 165.0, 165.0],
[160.0, 160.0, 190.0, 190.0],
[185.0, 185.0, 215.0, 215.0],
[210.0, 210.0, 240.0, 240.0],
[235.0, 235.0, 265.0, 265.0],
],
dtype=np.float32,
)
DETECTIONS = _create_detections(
xyxy=DETECTION_BOXES, class_id=np.array([0, 0, 0, 0, 0, 0, 0, 0, 0])
)
POLYGON = np.array([[100, 100], [200, 100], [200, 200], [100, 200]])
class TestPolygonZoneInit:
@pytest.mark.parametrize(
("polygon", "triggering_anchors", "exception"),
[
(POLYGON, [sv.Position.CENTER], DoesNotRaise()),
(
POLYGON,
[],
pytest.raises(ValueError, match="Triggering anchors cannot be empty"),
),
],
)
def test_empty_anchors_raises(self, polygon, triggering_anchors, exception) -> None:
with exception:
sv.PolygonZone(polygon, triggering_anchors=triggering_anchors)
class TestPolygonZoneTrigger:
@pytest.mark.parametrize(
("detections", "polygon_zone", "expected_results", "exception"),
[
(
DETECTIONS,
sv.PolygonZone(
POLYGON,
triggering_anchors=(
sv.Position.TOP_LEFT,
sv.Position.TOP_RIGHT,
sv.Position.BOTTOM_LEFT,
sv.Position.BOTTOM_RIGHT,
),
),
np.array(
[False, False, False, True, True, True, False, False, False],
dtype=bool,
),
DoesNotRaise(),
), # all four corners must be inside
(
DETECTIONS,
sv.PolygonZone(POLYGON),
np.array(
[False, False, True, True, True, True, False, False, False],
dtype=bool,
),
DoesNotRaise(),
), # default anchor (BOTTOM_CENTER)
(
DETECTIONS,
sv.PolygonZone(
POLYGON,
triggering_anchors=[sv.Position.BOTTOM_CENTER],
),
np.array(
[False, False, True, True, True, True, False, False, False],
dtype=bool,
),
DoesNotRaise(),
), # explicit BOTTOM_CENTER matches default
(
sv.Detections.empty(),
sv.PolygonZone(POLYGON),
np.array([], dtype=bool),
DoesNotRaise(),
), # empty detections return empty array
],
)
def test_anchor_configurations(
self,
detections: sv.Detections,
polygon_zone: sv.PolygonZone,
expected_results: np.ndarray,
exception: Exception,
) -> None:
with exception:
in_zone = polygon_zone.trigger(detections)
assert np.all(in_zone == expected_results)
assert polygon_zone.current_count == int(np.sum(expected_results))
def test_straddling_detection_assigned_to_one_zone(self) -> None:
"""Detection straddling two adjacent zones is counted in exactly one zone.
The old implementation clipped each detection box to the zone's bounding box
before computing anchors, so a box straddling two adjacent zones got a
different anchor per zone and was double-counted. The fix computes anchors
from the original unclipped box, giving a single consistent position.
Setup: zone_left covers x 0-99, zone_right covers x 100-200.
Detection [60, 80, 140, 120] has BOTTOM_CENTER = round((60+140)/2), round(120)
= (100, 120), which falls in zone_right only.
"""
zone_left = sv.PolygonZone(
np.array([[0, 0], [99, 0], [99, 200], [0, 200]], dtype=np.int32)
)
zone_right = sv.PolygonZone(
np.array([[100, 0], [200, 0], [200, 200], [100, 200]], dtype=np.int32)
)
detections = _create_detections(
xyxy=[[60.0, 80.0, 140.0, 120.0]],
class_id=[0],
)
results = np.array(
[zone.trigger(detections)[0] for zone in [zone_left, zone_right]],
dtype=bool,
)
assert np.sum(results) == 1, (
"Detection should appear in exactly one zone, not zero or multiple"
)
assert results[1], "BOTTOM_CENTER (100, 120) should be in zone_right"
def test_out_of_bounds_anchor_excluded(self) -> None:
"""Anchors with negative coordinates are excluded, not wrapped or clamped."""
zone = sv.PolygonZone(
np.array([[0, 0], [100, 0], [100, 100], [0, 100]]),
triggering_anchors=[sv.Position.CENTER],
)
# CENTER = (round((-50+0)/2), round((25+75)/2)) = (-25, 50) — x < 0.
detections = _create_detections(
xyxy=[[-50.0, 25.0, 0.0, 75.0]],
class_id=[0],
)
result = zone.trigger(detections)
assert not result[0]
def test_anchor_on_polygon_boundary_included(self) -> None:
"""An anchor landing exactly on a polygon corner is counted as inside."""
polygon = np.array([[0, 0], [100, 0], [100, 100], [0, 100]])
zone = sv.PolygonZone(polygon, triggering_anchors=[sv.Position.BOTTOM_RIGHT])
# BOTTOM_RIGHT = (round(x2), round(y2)) = (100, 100) — the polygon corner.
detections = _create_detections(
xyxy=[[50.0, 50.0, 100.0, 100.0]],
class_id=[0],
)
result = zone.trigger(detections)
assert result[0]
def test_half_pixel_anchor_uses_nearest_pixel(self) -> None:
"""Half-pixel anchors should not be biased toward the larger x and y."""
zone_left = sv.PolygonZone(
np.array([[0, 0], [100, 0], [100, 200], [0, 200]], dtype=np.int32)
)
zone_right = sv.PolygonZone(
np.array([[101, 0], [200, 0], [200, 200], [101, 200]], dtype=np.int32)
)
detections = _create_detections(
xyxy=[[60.0, 80.0, 141.0, 120.0]],
class_id=[0],
)
left_result = zone_left.trigger(detections)[0]
right_result = zone_right.trigger(detections)[0]
assert left_result
assert not right_result