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

170 lines
5.8 KiB
Python

from contextlib import ExitStack as DoesNotRaise
import numpy as np
import pytest
from supervision.detection.utils.polygons import (
approximate_polygon,
filter_polygons_by_area,
)
@pytest.mark.parametrize(
("polygons", "min_area", "max_area", "expected_result", "exception"),
[
(
[np.array([[0, 0], [0, 10], [10, 10], [10, 0]])],
None,
None,
[np.array([[0, 0], [0, 10], [10, 10], [10, 0]])],
DoesNotRaise(),
), # single polygon without area constraints
(
[np.array([[0, 0], [0, 10], [10, 10], [10, 0]])],
50,
None,
[np.array([[0, 0], [0, 10], [10, 10], [10, 0]])],
DoesNotRaise(),
), # single polygon with min_area constraint
(
[np.array([[0, 0], [0, 10], [10, 10], [10, 0]])],
None,
50,
[],
DoesNotRaise(),
), # single polygon with max_area constraint
(
[
np.array([[0, 0], [0, 10], [10, 10], [10, 0]]),
np.array([[0, 0], [0, 20], [20, 20], [20, 0]]),
],
200,
None,
[np.array([[0, 0], [0, 20], [20, 20], [20, 0]])],
DoesNotRaise(),
), # two polygons with min_area constraint
(
[
np.array([[0, 0], [0, 10], [10, 10], [10, 0]]),
np.array([[0, 0], [0, 20], [20, 20], [20, 0]]),
],
None,
200,
[np.array([[0, 0], [0, 10], [10, 10], [10, 0]])],
DoesNotRaise(),
), # two polygons with max_area constraint
(
[
np.array([[0, 0], [0, 10], [10, 10], [10, 0]]),
np.array([[0, 0], [0, 20], [20, 20], [20, 0]]),
],
200,
200,
[],
DoesNotRaise(),
), # two polygons with both area constraints
(
[
np.array([[0, 0], [0, 10], [10, 10], [10, 0]]),
np.array([[0, 0], [0, 20], [20, 20], [20, 0]]),
],
100,
100,
[np.array([[0, 0], [0, 10], [10, 10], [10, 0]])],
DoesNotRaise(),
), # two polygons with min_area and
# max_area equal to the area of the first polygon
(
[
np.array([[0, 0], [0, 10], [10, 10], [10, 0]]),
np.array([[0, 0], [0, 20], [20, 20], [20, 0]]),
],
400,
400,
[np.array([[0, 0], [0, 20], [20, 20], [20, 0]])],
DoesNotRaise(),
), # two polygons with min_area and
# max_area equal to the area of the second polygon
],
)
def test_filter_polygons_by_area(
polygons: list[np.ndarray],
min_area: float | None,
max_area: float | None,
expected_result: list[np.ndarray],
exception: Exception,
) -> None:
with exception:
result = filter_polygons_by_area(
polygons=polygons, min_area=min_area, max_area=max_area
)
assert len(result) == len(expected_result)
for result_polygon, expected_result_polygon in zip(result, expected_result):
assert np.array_equal(result_polygon, expected_result_polygon)
def _regular_polygon(num_points: int, radius: float = 40.0) -> np.ndarray:
angles = np.linspace(0, 2 * np.pi, num_points, endpoint=False)
return np.stack(
[50 + radius * np.cos(angles), 50 + radius * np.sin(angles)], axis=1
).astype(np.float32)
class TestApproximatePolygon:
@pytest.mark.parametrize("num_points", [20, 50, 100, 200])
@pytest.mark.parametrize("percentage", [0.1, 0.5, 0.75, 0.9])
def test_within_budget_and_valid(self, num_points: int, percentage: float) -> None:
"""The result stays a valid polygon and respects the point budget.
The exception is the 3-point floor: keeping a valid polygon (at least 3
points) can leave more points than the budget by an arbitrary margin,
since a single epsilon step may jump straight from above budget to below
3 points.
"""
polygon = _regular_polygon(num_points)
target_points = max(int(num_points * (1 - percentage)), 3)
result = approximate_polygon(polygon, percentage=percentage)
assert result.ndim == 2
assert result.shape[1] == 2
assert 3 <= len(result) <= num_points
if target_points > 3:
assert len(result) <= target_points
def test_zero_percentage_keeps_polygon(self) -> None:
"""A percentage of 0 removes no points."""
polygon = _regular_polygon(40)
result = approximate_polygon(polygon, percentage=0.0)
assert len(result) == len(polygon)
@pytest.mark.parametrize(
"percentage",
[
pytest.param(-0.001, id="just-below-zero"),
pytest.param(-1.0, id="negative-one"),
pytest.param(1.0, id="exactly-one"),
pytest.param(1.5, id="above-one"),
],
)
def test_raises_on_out_of_range_percentage(self, percentage: float) -> None:
"""Percentage outside [0, 1) must raise ValueError."""
polygon = _regular_polygon(20)
with pytest.raises(ValueError, match="Percentage must be in the range"):
approximate_polygon(polygon, percentage=percentage)
@pytest.mark.parametrize(
"epsilon_step",
[
pytest.param(0.0, id="zero"),
pytest.param(-0.05, id="negative"),
],
)
def test_raises_on_non_positive_epsilon_step(self, epsilon_step: float) -> None:
"""Non-positive epsilon_step must raise ValueError."""
polygon = _regular_polygon(20)
with pytest.raises(ValueError, match="epsilon_step must be positive"):
approximate_polygon(polygon, percentage=0.5, epsilon_step=epsilon_step)