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
130 lines
4.3 KiB
Python
130 lines
4.3 KiB
Python
import numpy as np
|
|
import pytest
|
|
|
|
from supervision.detection.core import Detections
|
|
from supervision.metrics.mean_average_precision import MeanAveragePrecision
|
|
|
|
|
|
class TestMeanAveragePrecisionArea:
|
|
"""Test area calculation in MeanAveragePrecision."""
|
|
|
|
@pytest.mark.parametrize(
|
|
("xyxy", "expected_areas", "expected_size_maps"),
|
|
[
|
|
(
|
|
np.array(
|
|
[
|
|
[10, 10, 40, 40], # Small: 900
|
|
[100, 100, 200, 150], # Medium: 5000
|
|
[300, 300, 500, 400], # Large: 20000
|
|
],
|
|
dtype=np.float32,
|
|
),
|
|
[900.0, 5000.0, 20000.0],
|
|
{"small": True, "medium": True, "large": True},
|
|
),
|
|
(
|
|
np.array([[0, 0, 10, 10]], dtype=np.float32), # Small: 100
|
|
[100.0],
|
|
{"small": True, "medium": False, "large": False},
|
|
),
|
|
(
|
|
np.array([[0, 0, 50, 50]], dtype=np.float32), # Medium: 2500
|
|
[2500.0],
|
|
{"small": False, "medium": True, "large": False},
|
|
),
|
|
(
|
|
np.array([[0, 0, 100, 100]], dtype=np.float32), # Large: 10000
|
|
[10000.0],
|
|
{"small": False, "medium": False, "large": True},
|
|
),
|
|
],
|
|
)
|
|
def test_area_calculation_and_size_specific_map(
|
|
self, xyxy, expected_areas, expected_size_maps
|
|
) -> None:
|
|
"""Test area calculation and size-specific mAP functionality."""
|
|
gt = Detections(
|
|
xyxy=xyxy,
|
|
class_id=np.arange(len(xyxy)),
|
|
)
|
|
pred = Detections(
|
|
xyxy=gt.xyxy.copy(),
|
|
class_id=gt.class_id.copy(),
|
|
confidence=np.full(len(xyxy), 0.9),
|
|
)
|
|
|
|
map_metric = MeanAveragePrecision()
|
|
map_metric.update([pred], [gt])
|
|
|
|
# Test area calculation
|
|
prepared_targets = map_metric._prepare_targets(map_metric._targets_list)
|
|
areas = [ann["area"] for ann in prepared_targets["annotations"]]
|
|
assert np.allclose(areas, expected_areas), (
|
|
f"Expected {expected_areas}, got {areas}"
|
|
)
|
|
|
|
# Test size-specific mAP
|
|
result = map_metric.compute()
|
|
|
|
if expected_size_maps["small"]:
|
|
assert result.small_objects.map50 > 0.9, (
|
|
"Small objects should have high mAP"
|
|
)
|
|
else:
|
|
assert result.small_objects.map50 == -1.0, (
|
|
"Small objects should have no data"
|
|
)
|
|
|
|
if expected_size_maps["medium"]:
|
|
assert result.medium_objects.map50 > 0.9, (
|
|
"Medium objects should have high mAP"
|
|
)
|
|
else:
|
|
assert result.medium_objects.map50 == -1.0, (
|
|
"Medium objects should have no data"
|
|
)
|
|
|
|
if expected_size_maps["large"]:
|
|
assert result.large_objects.map50 > 0.9, (
|
|
"Large objects should have high mAP"
|
|
)
|
|
else:
|
|
assert result.large_objects.map50 == -1.0, (
|
|
"Large objects should have no data"
|
|
)
|
|
|
|
def test_area_preserved_from_data(self) -> None:
|
|
"""Test that area from data field is preserved (COCO case)."""
|
|
gt = Detections(
|
|
xyxy=np.array(
|
|
[[100, 100, 200, 150]], dtype=np.float32
|
|
), # Would calculate to 5000
|
|
class_id=np.array([0]),
|
|
)
|
|
# Override with custom area
|
|
gt.data = {"area": np.array([3000.0])}
|
|
|
|
pred = Detections(
|
|
xyxy=gt.xyxy.copy(),
|
|
class_id=gt.class_id.copy(),
|
|
confidence=np.array([0.9]),
|
|
)
|
|
pred.data = {"area": np.array([3000.0])}
|
|
|
|
map_metric = MeanAveragePrecision()
|
|
map_metric.update([pred], [gt])
|
|
|
|
prepared_targets = map_metric._prepare_targets(map_metric._targets_list)
|
|
used_area = prepared_targets["annotations"][0]["area"]
|
|
|
|
assert np.allclose(used_area, 3000.0), (
|
|
f"Should use provided area 3000.0, got {used_area}"
|
|
)
|
|
|
|
# Verify it's different from what would be calculated
|
|
calculated_area = (200 - 100) * (150 - 100) # 100 * 50 = 5000
|
|
assert not np.allclose(used_area, calculated_area), (
|
|
"Should use provided area, not calculated"
|
|
)
|