881 lines
30 KiB
Python
881 lines
30 KiB
Python
from __future__ import annotations
|
|
|
|
import warnings
|
|
from contextlib import ExitStack as DoesNotRaise
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from supervision.detection.core import Detections, merge_inner_detection_object_pair
|
|
from supervision.geometry.core import Position
|
|
from supervision.utils.internal import SupervisionWarnings
|
|
from tests.helpers import _create_detections
|
|
|
|
PREDICTIONS = np.array(
|
|
[
|
|
[2254, 906, 2447, 1353, 0.90538, 0],
|
|
[2049, 1133, 2226, 1371, 0.59002, 56],
|
|
[727, 1224, 838, 1601, 0.51119, 39],
|
|
[808, 1214, 910, 1564, 0.45287, 39],
|
|
[6, 52, 1131, 2133, 0.45057, 72],
|
|
[299, 1225, 512, 1663, 0.45029, 39],
|
|
[529, 874, 645, 945, 0.31101, 39],
|
|
[8, 47, 1935, 2135, 0.28192, 72],
|
|
[2265, 813, 2328, 901, 0.2714, 62],
|
|
],
|
|
dtype=np.float32,
|
|
)
|
|
|
|
DETECTIONS = Detections(
|
|
xyxy=PREDICTIONS[:, :4],
|
|
confidence=PREDICTIONS[:, 4],
|
|
class_id=PREDICTIONS[:, 5].astype(int),
|
|
)
|
|
|
|
|
|
# Merge test
|
|
TEST_MASK = np.zeros((1000, 1000), dtype=bool)
|
|
TEST_MASK[300:351, 200:251] = True
|
|
TEST_DET_1 = Detections(
|
|
xyxy=np.array([[10, 10, 20, 20], [30, 30, 40, 40], [50, 50, 60, 60]]),
|
|
mask=np.array([TEST_MASK, TEST_MASK, TEST_MASK]),
|
|
confidence=np.array([0.1, 0.2, 0.3]),
|
|
class_id=np.array([1, 2, 3]),
|
|
tracker_id=np.array([1, 2, 3]),
|
|
data={
|
|
"some_key": [1, 2, 3],
|
|
"other_key": [["1", "2"], ["3", "4"], ["5", "6"]],
|
|
},
|
|
)
|
|
TEST_DET_2 = Detections(
|
|
xyxy=np.array([[70, 70, 80, 80], [90, 90, 100, 100]]),
|
|
mask=np.array([TEST_MASK, TEST_MASK]),
|
|
confidence=np.array([0.4, 0.5]),
|
|
class_id=np.array([4, 5]),
|
|
tracker_id=np.array([4, 5]),
|
|
data={
|
|
"some_key": [4, 5],
|
|
"other_key": [["7", "8"], ["9", "10"]],
|
|
},
|
|
)
|
|
TEST_DET_1_2 = Detections(
|
|
xyxy=np.array(
|
|
[
|
|
[10, 10, 20, 20],
|
|
[30, 30, 40, 40],
|
|
[50, 50, 60, 60],
|
|
[70, 70, 80, 80],
|
|
[90, 90, 100, 100],
|
|
]
|
|
),
|
|
mask=np.array([TEST_MASK, TEST_MASK, TEST_MASK, TEST_MASK, TEST_MASK]),
|
|
confidence=np.array([0.1, 0.2, 0.3, 0.4, 0.5]),
|
|
class_id=np.array([1, 2, 3, 4, 5]),
|
|
tracker_id=np.array([1, 2, 3, 4, 5]),
|
|
data={
|
|
"some_key": [1, 2, 3, 4, 5],
|
|
"other_key": [["1", "2"], ["3", "4"], ["5", "6"], ["7", "8"], ["9", "10"]],
|
|
},
|
|
)
|
|
TEST_DET_ZERO_LENGTH = Detections(
|
|
xyxy=np.empty((0, 4), dtype=np.float32),
|
|
mask=np.empty((0, *TEST_MASK.shape), dtype=bool),
|
|
confidence=np.empty((0,)),
|
|
class_id=np.empty((0,)),
|
|
tracker_id=np.empty((0,)),
|
|
data={
|
|
"some_key": [],
|
|
"other_key": [],
|
|
},
|
|
)
|
|
TEST_DET_NONE = Detections(
|
|
xyxy=np.empty((0, 4), dtype=np.float32),
|
|
)
|
|
TEST_DET_DIFFERENT_FIELDS = Detections(
|
|
xyxy=np.array([[88, 88, 99, 99]]),
|
|
mask=np.array([np.logical_not(TEST_MASK)]),
|
|
confidence=None,
|
|
class_id=None,
|
|
tracker_id=np.array([9]),
|
|
data={"some_key": [9], "other_key": [["11", "12"]]},
|
|
)
|
|
TEST_DET_DIFFERENT_DATA = Detections(
|
|
xyxy=np.array([[88, 88, 99, 99]]),
|
|
mask=np.array([np.logical_not(TEST_MASK)]),
|
|
confidence=np.array([0.9]),
|
|
class_id=np.array([9]),
|
|
tracker_id=np.array([9]),
|
|
data={
|
|
"never_seen_key": [9],
|
|
},
|
|
)
|
|
TEST_DET_WITH_METADATA = Detections(
|
|
xyxy=np.array([[10, 10, 20, 20]]),
|
|
class_id=np.array([1]),
|
|
metadata={"source": "camera1"},
|
|
)
|
|
|
|
TEST_DET_WITH_METADATA_2 = Detections(
|
|
xyxy=np.array([[30, 30, 40, 40]]),
|
|
class_id=np.array([2]),
|
|
metadata={"source": "camera1"},
|
|
)
|
|
TEST_DET_NO_METADATA = Detections(
|
|
xyxy=np.array([[10, 10, 20, 20]]),
|
|
class_id=np.array([1]),
|
|
)
|
|
TEST_DET_DIFFERENT_METADATA = Detections(
|
|
xyxy=np.array([[50, 50, 60, 60]]),
|
|
class_id=np.array([3]),
|
|
metadata={"source": "camera2"},
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("mask_dtype", [bool, np.bool_])
|
|
def test_detections_bool_mask_types_do_not_warn(mask_dtype) -> None:
|
|
with warnings.catch_warnings(record=True) as recorded_warnings:
|
|
warnings.simplefilter("always")
|
|
Detections(
|
|
xyxy=np.array([[1, 2, 3, 4]]),
|
|
mask=np.array([[[1, 0], [0, 1]]], dtype=mask_dtype),
|
|
)
|
|
assert not any(
|
|
warning.category is SupervisionWarnings for warning in recorded_warnings
|
|
)
|
|
|
|
|
|
def test_detections_non_bool_mask_warns_with_migration_path() -> None:
|
|
with pytest.warns(
|
|
SupervisionWarnings,
|
|
match="supervision-0.28.0.*ValueError.*astype\\(bool\\)",
|
|
):
|
|
Detections(
|
|
xyxy=np.array([[1, 2, 3, 4]]),
|
|
mask=np.array([[[1, 0], [0, 1]]], dtype=np.uint8),
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("detections", "index", "expected_result", "exception"),
|
|
[
|
|
# Scenario: Filter detections by class ID using a boolean mask.
|
|
# Expected: Only detections matching the class ID are retained.
|
|
(
|
|
DETECTIONS,
|
|
DETECTIONS.class_id == 0,
|
|
_create_detections(
|
|
xyxy=[[2254, 906, 2447, 1353]], confidence=[0.90538], class_id=[0]
|
|
),
|
|
DoesNotRaise(),
|
|
),
|
|
# Scenario: Filter detections by confidence score threshold.
|
|
# Expected: Only high-confidence detections are kept, filtering out noise.
|
|
(
|
|
DETECTIONS,
|
|
DETECTIONS.confidence > 0.5,
|
|
_create_detections(
|
|
xyxy=[
|
|
[2254, 906, 2447, 1353],
|
|
[2049, 1133, 2226, 1371],
|
|
[727, 1224, 838, 1601],
|
|
],
|
|
confidence=[0.90538, 0.59002, 0.51119],
|
|
class_id=[0, 56, 39],
|
|
),
|
|
DoesNotRaise(),
|
|
),
|
|
# Scenario: Select all detections using a full boolean mask.
|
|
# Expected: Result is identical to input.
|
|
(
|
|
DETECTIONS,
|
|
np.array(
|
|
[True, True, True, True, True, True, True, True, True], dtype=bool
|
|
),
|
|
DETECTIONS,
|
|
DoesNotRaise(),
|
|
),
|
|
# Scenario: Select no detections using an empty boolean mask.
|
|
# Expected: An empty Detections object with correct shapes.
|
|
(
|
|
DETECTIONS,
|
|
np.array(
|
|
[False, False, False, False, False, False, False, False, False],
|
|
dtype=bool,
|
|
),
|
|
Detections(
|
|
xyxy=np.empty((0, 4), dtype=np.float32),
|
|
confidence=np.array([], dtype=np.float32),
|
|
class_id=np.array([], dtype=int),
|
|
),
|
|
DoesNotRaise(),
|
|
),
|
|
# Scenario: Select specific detections using a list of integer indices.
|
|
# Expected: Only requested indices are returned in specified order.
|
|
(
|
|
DETECTIONS,
|
|
[0, 2],
|
|
_create_detections(
|
|
xyxy=[[2254, 906, 2447, 1353], [727, 1224, 838, 1601]],
|
|
confidence=[0.90538, 0.51119],
|
|
class_id=[0, 39],
|
|
),
|
|
DoesNotRaise(),
|
|
),
|
|
# Scenario: Select specific detections using a NumPy array of indices.
|
|
# Expected: Only requested indices are returned.
|
|
(
|
|
DETECTIONS,
|
|
np.array([0, 2]),
|
|
_create_detections(
|
|
xyxy=[[2254, 906, 2447, 1353], [727, 1224, 838, 1601]],
|
|
confidence=[0.90538, 0.51119],
|
|
class_id=[0, 39],
|
|
),
|
|
DoesNotRaise(),
|
|
),
|
|
# Scenario: Select a single detection using an integer index.
|
|
# Expected: A Detections object containing only that element.
|
|
(
|
|
DETECTIONS,
|
|
0,
|
|
_create_detections(
|
|
xyxy=[[2254, 906, 2447, 1353]], confidence=[0.90538], class_id=[0]
|
|
),
|
|
DoesNotRaise(),
|
|
),
|
|
# Scenario: Select a range of detections using a slice.
|
|
# Expected: Detections within the slice range are returned.
|
|
(
|
|
DETECTIONS,
|
|
slice(1, 3),
|
|
_create_detections(
|
|
xyxy=[[2049, 1133, 2226, 1371], [727, 1224, 838, 1601]],
|
|
confidence=[0.59002, 0.51119],
|
|
class_id=[56, 39],
|
|
),
|
|
DoesNotRaise(),
|
|
),
|
|
# Scenario: Index out of range.
|
|
# Expected: IndexError is raised.
|
|
(DETECTIONS, 10, None, pytest.raises(IndexError, match="index 10 is out")),
|
|
(
|
|
DETECTIONS,
|
|
[0, 2, 10],
|
|
None,
|
|
pytest.raises(IndexError, match="out of bounds for axis 0"),
|
|
),
|
|
(
|
|
DETECTIONS,
|
|
np.array([0, 2, 10]),
|
|
None,
|
|
pytest.raises(IndexError, match="axis 0 with size"),
|
|
),
|
|
(
|
|
DETECTIONS,
|
|
np.array(
|
|
[True, True, True, True, True, True, True, True, True, True, True]
|
|
),
|
|
None,
|
|
pytest.raises(IndexError, match="boolean index did not match"),
|
|
),
|
|
# Scenario: Filter an empty Detections object.
|
|
# Expected: Returns an empty Detections object without crashing.
|
|
(
|
|
Detections.empty(),
|
|
np.isin(Detections.empty()["class_name"], ["cat", "dog"]),
|
|
Detections.empty(),
|
|
DoesNotRaise(),
|
|
),
|
|
],
|
|
)
|
|
def test_getitem(
|
|
detections: Detections,
|
|
index: int | slice | list[int] | np.ndarray,
|
|
expected_result: Detections | None,
|
|
exception: Exception,
|
|
) -> None:
|
|
"""
|
|
Ensures that `Detections.__getitem__` (indexing/slicing) works correctly for various
|
|
input types. This is a core feature that allows users to filter and manipulate
|
|
detection results easily.
|
|
"""
|
|
with exception:
|
|
result = detections[index]
|
|
assert result == expected_result
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("detections_list", "expected_result", "exception"),
|
|
[
|
|
([], Detections.empty(), DoesNotRaise()), # empty detections list
|
|
(
|
|
[Detections.empty()],
|
|
Detections.empty(),
|
|
DoesNotRaise(),
|
|
), # single empty detections
|
|
(
|
|
[Detections.empty(), Detections.empty()],
|
|
Detections.empty(),
|
|
DoesNotRaise(),
|
|
), # two empty detections
|
|
(
|
|
[TEST_DET_1],
|
|
TEST_DET_1,
|
|
DoesNotRaise(),
|
|
), # single detection with fields
|
|
(
|
|
[TEST_DET_NONE],
|
|
TEST_DET_NONE,
|
|
DoesNotRaise(),
|
|
), # Single weakly-defined detection
|
|
(
|
|
[TEST_DET_1, TEST_DET_2],
|
|
TEST_DET_1_2,
|
|
DoesNotRaise(),
|
|
), # Fields with same keys
|
|
(
|
|
[TEST_DET_1, Detections.empty()],
|
|
TEST_DET_1,
|
|
DoesNotRaise(),
|
|
), # single detection with fields
|
|
(
|
|
[
|
|
TEST_DET_1,
|
|
TEST_DET_ZERO_LENGTH,
|
|
],
|
|
TEST_DET_1,
|
|
DoesNotRaise(),
|
|
), # Single detection and empty-array fields
|
|
(
|
|
[TEST_DET_ZERO_LENGTH, TEST_DET_ZERO_LENGTH],
|
|
TEST_DET_ZERO_LENGTH,
|
|
DoesNotRaise(),
|
|
), # Zero-length fields across all Detections
|
|
(
|
|
[
|
|
TEST_DET_1,
|
|
TEST_DET_NONE,
|
|
],
|
|
None,
|
|
pytest.raises(ValueError, match="mask' fields must be None"),
|
|
), # Empty detection, but not Detections.empty()
|
|
# Errors: Non-zero-length differently defined keys & data
|
|
(
|
|
[TEST_DET_1, TEST_DET_DIFFERENT_FIELDS],
|
|
None,
|
|
pytest.raises(ValueError, match="confidence' fields must be None"),
|
|
), # Non-empty detections with different fields
|
|
(
|
|
[TEST_DET_1, TEST_DET_DIFFERENT_DATA],
|
|
None,
|
|
pytest.raises(ValueError, match="same keys to merge"),
|
|
), # Non-empty detections with different data keys
|
|
(
|
|
[
|
|
_create_detections(
|
|
xyxy=[[10, 10, 20, 20]],
|
|
class_id=[1],
|
|
mask=[np.zeros((4, 4), dtype=bool)],
|
|
),
|
|
Detections.empty(),
|
|
],
|
|
_create_detections(
|
|
xyxy=np.array([[10, 10, 20, 20]]),
|
|
class_id=[1],
|
|
mask=[np.zeros((4, 4), dtype=bool)],
|
|
),
|
|
DoesNotRaise(),
|
|
), # Segmentation + Empty
|
|
# Metadata
|
|
(
|
|
[
|
|
Detections(
|
|
xyxy=np.array([[10, 10, 20, 20]]),
|
|
class_id=np.array([1]),
|
|
metadata={"source": "camera1"},
|
|
),
|
|
Detections.empty(),
|
|
],
|
|
Detections(
|
|
xyxy=np.array([[10, 10, 20, 20]]),
|
|
class_id=np.array([1]),
|
|
metadata={"source": "camera1"},
|
|
),
|
|
DoesNotRaise(),
|
|
), # Metadata merge with empty detections
|
|
(
|
|
[
|
|
Detections(
|
|
xyxy=np.array([[10, 10, 20, 20]]),
|
|
class_id=np.array([1]),
|
|
metadata={"source": "camera1"},
|
|
),
|
|
Detections(xyxy=np.array([[30, 30, 40, 40]]), class_id=np.array([2])),
|
|
],
|
|
None,
|
|
pytest.raises(ValueError, match="metadata dictionaries must have the same"),
|
|
), # Empty and non-empty metadata
|
|
(
|
|
[
|
|
Detections(
|
|
xyxy=np.array([[10, 10, 20, 20]]),
|
|
class_id=np.array([1]),
|
|
metadata={"source": "camera1"},
|
|
)
|
|
],
|
|
Detections(
|
|
xyxy=np.array([[10, 10, 20, 20]]),
|
|
class_id=np.array([1]),
|
|
metadata={"source": "camera1"},
|
|
),
|
|
DoesNotRaise(),
|
|
), # Single detection with metadata
|
|
(
|
|
[
|
|
Detections(
|
|
xyxy=np.array([[10, 10, 20, 20]]),
|
|
class_id=np.array([1]),
|
|
metadata={"source": "camera1"},
|
|
),
|
|
Detections(
|
|
xyxy=np.array([[30, 30, 40, 40]]),
|
|
class_id=np.array([2]),
|
|
metadata={"source": "camera1"},
|
|
),
|
|
],
|
|
Detections(
|
|
xyxy=np.array([[10, 10, 20, 20], [30, 30, 40, 40]]),
|
|
class_id=np.array([1, 2]),
|
|
metadata={"source": "camera1"},
|
|
),
|
|
DoesNotRaise(),
|
|
), # Multiple metadata entries with identical values
|
|
(
|
|
[
|
|
Detections(
|
|
xyxy=np.array([[10, 10, 20, 20]]),
|
|
class_id=np.array([1]),
|
|
metadata={"source": "camera1"},
|
|
),
|
|
Detections(
|
|
xyxy=np.array([[50, 50, 60, 60]]),
|
|
class_id=np.array([3]),
|
|
metadata={"source": "camera2"},
|
|
),
|
|
],
|
|
None,
|
|
pytest.raises(
|
|
ValueError, match="Conflicting metadata for key: 'source'\\."
|
|
),
|
|
), # Different metadata values
|
|
(
|
|
[
|
|
Detections(
|
|
xyxy=np.array([[10, 10, 20, 20]]),
|
|
metadata={"source": "camera1", "resolution": "1080p"},
|
|
),
|
|
Detections(
|
|
xyxy=np.array([[30, 30, 40, 40]]),
|
|
metadata={"source": "camera1", "resolution": "1080p"},
|
|
),
|
|
],
|
|
Detections(
|
|
xyxy=np.array([[10, 10, 20, 20], [30, 30, 40, 40]]),
|
|
metadata={"source": "camera1", "resolution": "1080p"},
|
|
),
|
|
DoesNotRaise(),
|
|
), # Large metadata with multiple identical entries
|
|
(
|
|
[
|
|
Detections(
|
|
xyxy=np.array([[10, 10, 20, 20]]), metadata={"source": "camera1"}
|
|
),
|
|
Detections(
|
|
xyxy=np.array([[30, 30, 40, 40]]), metadata={"source": ["camera1"]}
|
|
),
|
|
],
|
|
None,
|
|
pytest.raises(ValueError, match="metadata for key: 'source'"),
|
|
), # Inconsistent types in metadata values
|
|
(
|
|
[
|
|
Detections(
|
|
xyxy=np.array([[10, 10, 20, 20]]), metadata={"source": "camera1"}
|
|
),
|
|
Detections(
|
|
xyxy=np.array([[30, 30, 40, 40]]), metadata={"location": "indoor"}
|
|
),
|
|
],
|
|
None,
|
|
pytest.raises(ValueError, match="same keys to merge"),
|
|
), # Metadata key mismatch
|
|
(
|
|
[
|
|
Detections(
|
|
xyxy=np.array([[10, 10, 20, 20]]),
|
|
metadata={
|
|
"source": "camera1",
|
|
"settings": {"resolution": "1080p", "fps": 30},
|
|
},
|
|
),
|
|
Detections(
|
|
xyxy=np.array([[30, 30, 40, 40]]),
|
|
metadata={
|
|
"source": "camera1",
|
|
"settings": {"resolution": "1080p", "fps": 30},
|
|
},
|
|
),
|
|
],
|
|
Detections(
|
|
xyxy=np.array([[10, 10, 20, 20], [30, 30, 40, 40]]),
|
|
metadata={
|
|
"source": "camera1",
|
|
"settings": {"resolution": "1080p", "fps": 30},
|
|
},
|
|
),
|
|
DoesNotRaise(),
|
|
), # multi-field metadata
|
|
(
|
|
[
|
|
Detections(
|
|
xyxy=np.array([[10, 10, 20, 20]]),
|
|
metadata={"calibration_matrix": np.array([[1, 0], [0, 1]])},
|
|
),
|
|
Detections(
|
|
xyxy=np.array([[30, 30, 40, 40]]),
|
|
metadata={"calibration_matrix": np.array([[1, 0], [0, 1]])},
|
|
),
|
|
],
|
|
Detections(
|
|
xyxy=np.array([[10, 10, 20, 20], [30, 30, 40, 40]]),
|
|
metadata={"calibration_matrix": np.array([[1, 0], [0, 1]])},
|
|
),
|
|
DoesNotRaise(),
|
|
), # Identical 2D numpy arrays in metadata
|
|
(
|
|
[
|
|
Detections(
|
|
xyxy=np.array([[10, 10, 20, 20]]),
|
|
metadata={"calibration_matrix": np.array([[1, 0], [0, 1]])},
|
|
),
|
|
Detections(
|
|
xyxy=np.array([[30, 30, 40, 40]]),
|
|
metadata={"calibration_matrix": np.array([[2, 0], [0, 2]])},
|
|
),
|
|
],
|
|
None,
|
|
pytest.raises(ValueError, match="calibration_matrix"),
|
|
), # Mismatching 2D numpy arrays in metadata
|
|
],
|
|
)
|
|
def test_merge(
|
|
detections_list: list[Detections],
|
|
expected_result: Detections | None,
|
|
exception: Exception,
|
|
) -> None:
|
|
with exception:
|
|
result = Detections.merge(detections_list=detections_list)
|
|
assert result == expected_result, f"Expected: {expected_result}, Got: {result}"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("detections", "anchor", "expected_result", "exception"),
|
|
[
|
|
(
|
|
Detections.empty(),
|
|
Position.CENTER,
|
|
np.empty((0, 2), dtype=np.float32),
|
|
DoesNotRaise(),
|
|
), # empty detections
|
|
(
|
|
_create_detections(xyxy=[[10, 10, 20, 20]]),
|
|
Position.CENTER,
|
|
np.array([[15, 15]], dtype=np.float32),
|
|
DoesNotRaise(),
|
|
), # single detection; center anchor
|
|
(
|
|
_create_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]),
|
|
Position.CENTER,
|
|
np.array([[15, 15], [25, 25]], dtype=np.float32),
|
|
DoesNotRaise(),
|
|
), # two detections; center anchor
|
|
(
|
|
_create_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]),
|
|
Position.CENTER_LEFT,
|
|
np.array([[10, 15], [20, 25]], dtype=np.float32),
|
|
DoesNotRaise(),
|
|
), # two detections; center left anchor
|
|
(
|
|
_create_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]),
|
|
Position.CENTER_RIGHT,
|
|
np.array([[20, 15], [30, 25]], dtype=np.float32),
|
|
DoesNotRaise(),
|
|
), # two detections; center right anchor
|
|
(
|
|
_create_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]),
|
|
Position.TOP_CENTER,
|
|
np.array([[15, 10], [25, 20]], dtype=np.float32),
|
|
DoesNotRaise(),
|
|
), # two detections; top center anchor
|
|
(
|
|
_create_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]),
|
|
Position.TOP_LEFT,
|
|
np.array([[10, 10], [20, 20]], dtype=np.float32),
|
|
DoesNotRaise(),
|
|
), # two detections; top left anchor
|
|
(
|
|
_create_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]),
|
|
Position.TOP_RIGHT,
|
|
np.array([[20, 10], [30, 20]], dtype=np.float32),
|
|
DoesNotRaise(),
|
|
), # two detections; top right anchor
|
|
(
|
|
_create_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]),
|
|
Position.BOTTOM_CENTER,
|
|
np.array([[15, 20], [25, 30]], dtype=np.float32),
|
|
DoesNotRaise(),
|
|
), # two detections; bottom center anchor
|
|
(
|
|
_create_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]),
|
|
Position.BOTTOM_LEFT,
|
|
np.array([[10, 20], [20, 30]], dtype=np.float32),
|
|
DoesNotRaise(),
|
|
), # two detections; bottom left anchor
|
|
(
|
|
_create_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]),
|
|
Position.BOTTOM_RIGHT,
|
|
np.array([[20, 20], [30, 30]], dtype=np.float32),
|
|
DoesNotRaise(),
|
|
), # two detections; bottom right anchor
|
|
],
|
|
)
|
|
def test_get_anchor_coordinates(
|
|
detections: Detections,
|
|
anchor: Position,
|
|
expected_result: np.ndarray,
|
|
exception: Exception,
|
|
) -> None:
|
|
result = detections.get_anchors_coordinates(anchor)
|
|
with exception:
|
|
assert np.array_equal(result, expected_result)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("detections_a", "detections_b", "expected_result"),
|
|
[
|
|
(
|
|
Detections.empty(),
|
|
Detections.empty(),
|
|
True,
|
|
), # empty detections
|
|
(
|
|
_create_detections(xyxy=[[10, 10, 20, 20]]),
|
|
_create_detections(xyxy=[[10, 10, 20, 20]]),
|
|
True,
|
|
), # detections with xyxy field
|
|
(
|
|
_create_detections(xyxy=[[10, 10, 20, 20]], confidence=[0.5]),
|
|
_create_detections(xyxy=[[10, 10, 20, 20]], confidence=[0.5]),
|
|
True,
|
|
), # detections with xyxy, confidence fields
|
|
(
|
|
_create_detections(xyxy=[[10, 10, 20, 20]], confidence=[0.5]),
|
|
_create_detections(xyxy=[[10, 10, 20, 20]]),
|
|
False,
|
|
), # detection with xyxy field + detection with xyxy, confidence fields
|
|
(
|
|
_create_detections(xyxy=[[10, 10, 20, 20]], data={"test": [1]}),
|
|
_create_detections(xyxy=[[10, 10, 20, 20]], data={"test": [1]}),
|
|
True,
|
|
), # detections with xyxy, data fields
|
|
(
|
|
_create_detections(xyxy=[[10, 10, 20, 20]], data={"test": [1]}),
|
|
_create_detections(xyxy=[[10, 10, 20, 20]]),
|
|
False,
|
|
), # detection with xyxy field + detection with xyxy, data fields
|
|
(
|
|
_create_detections(xyxy=[[10, 10, 20, 20]], data={"test_1": [1]}),
|
|
_create_detections(xyxy=[[10, 10, 20, 20]], data={"test_2": [1]}),
|
|
False,
|
|
), # detections with xyxy, and different data field names
|
|
(
|
|
_create_detections(xyxy=[[10, 10, 20, 20]], data={"test_1": [1]}),
|
|
_create_detections(xyxy=[[10, 10, 20, 20]], data={"test_1": [3]}),
|
|
False,
|
|
), # detections with xyxy, and different data field values
|
|
],
|
|
)
|
|
def test_equal(
|
|
detections_a: Detections, detections_b: Detections, expected_result: bool
|
|
) -> None:
|
|
assert (detections_a == detections_b) == expected_result
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("detection_1", "detection_2", "expected_result", "exception"),
|
|
[
|
|
(
|
|
_create_detections(
|
|
xyxy=[[10, 10, 30, 30]],
|
|
),
|
|
_create_detections(
|
|
xyxy=[[10, 10, 30, 30]],
|
|
),
|
|
_create_detections(
|
|
xyxy=[[10, 10, 30, 30]],
|
|
),
|
|
DoesNotRaise(),
|
|
), # Merge with self
|
|
(
|
|
_create_detections(
|
|
xyxy=[[10, 10, 30, 30]],
|
|
),
|
|
Detections.empty(),
|
|
None,
|
|
pytest.raises(ValueError, match="exactly 1 detected object"),
|
|
), # merge with empty: error
|
|
(
|
|
_create_detections(
|
|
xyxy=[[10, 10, 30, 30]],
|
|
),
|
|
_create_detections(
|
|
xyxy=[[10, 10, 30, 30], [40, 40, 60, 60]],
|
|
),
|
|
None,
|
|
pytest.raises(ValueError, match="Both Detections should have"),
|
|
), # merge with 2+ objects: error
|
|
(
|
|
_create_detections(
|
|
xyxy=[[10, 10, 30, 30]],
|
|
confidence=[0.1],
|
|
class_id=[1],
|
|
mask=[np.array([[1, 1, 0], [1, 1, 0], [0, 0, 0]], dtype=bool)],
|
|
tracker_id=[1],
|
|
data={"key_1": [1]},
|
|
),
|
|
_create_detections(
|
|
xyxy=[[20, 20, 40, 40]],
|
|
confidence=[0.1],
|
|
class_id=[2],
|
|
mask=[np.array([[0, 0, 0], [0, 1, 1], [0, 1, 1]], dtype=bool)],
|
|
tracker_id=[2],
|
|
data={"key_2": [2]},
|
|
),
|
|
_create_detections(
|
|
xyxy=[[10, 10, 40, 40]],
|
|
confidence=[0.1],
|
|
class_id=[1],
|
|
mask=[np.array([[1, 1, 0], [1, 1, 1], [0, 1, 1]], dtype=bool)],
|
|
tracker_id=[1],
|
|
data={"key_1": [1]},
|
|
),
|
|
DoesNotRaise(),
|
|
), # Same confidence - merge box & mask, tie-break to detection_1
|
|
(
|
|
_create_detections(
|
|
xyxy=[[0, 0, 20, 20]],
|
|
confidence=[0.1],
|
|
class_id=[1],
|
|
mask=[np.array([[1, 1, 0], [1, 1, 0], [0, 0, 0]], dtype=bool)],
|
|
tracker_id=[1],
|
|
data={"key_1": [1]},
|
|
),
|
|
_create_detections(
|
|
xyxy=[[10, 10, 50, 50]],
|
|
confidence=[0.2],
|
|
class_id=[2],
|
|
mask=[np.array([[0, 0, 0], [0, 1, 1], [0, 1, 1]], dtype=bool)],
|
|
tracker_id=[2],
|
|
data={"key_2": [2]},
|
|
),
|
|
_create_detections(
|
|
xyxy=[[0, 0, 50, 50]],
|
|
confidence=[(1 * 0.1 + 4 * 0.2) / 5],
|
|
class_id=[2],
|
|
mask=[np.array([[1, 1, 0], [1, 1, 1], [0, 1, 1]], dtype=bool)],
|
|
tracker_id=[2],
|
|
data={"key_2": [2]},
|
|
),
|
|
DoesNotRaise(),
|
|
), # Different confidence, different area
|
|
(
|
|
_create_detections(
|
|
xyxy=[[10, 10, 30, 30]],
|
|
confidence=None,
|
|
class_id=[1],
|
|
mask=[np.array([[1, 1, 0], [1, 1, 0], [0, 0, 0]], dtype=bool)],
|
|
tracker_id=[1],
|
|
data={"key_1": [1]},
|
|
),
|
|
_create_detections(
|
|
xyxy=[[20, 20, 40, 40]],
|
|
confidence=None,
|
|
class_id=[2],
|
|
mask=[np.array([[0, 0, 0], [0, 1, 1], [0, 1, 1]], dtype=bool)],
|
|
tracker_id=[2],
|
|
data={"key_2": [2]},
|
|
),
|
|
_create_detections(
|
|
xyxy=[[10, 10, 40, 40]],
|
|
confidence=None,
|
|
class_id=[1],
|
|
mask=[np.array([[1, 1, 0], [1, 1, 1], [0, 1, 1]], dtype=bool)],
|
|
tracker_id=[1],
|
|
data={"key_1": [1]},
|
|
),
|
|
DoesNotRaise(),
|
|
), # No confidence at all
|
|
(
|
|
_create_detections(
|
|
xyxy=[[0, 0, 20, 20]],
|
|
confidence=None,
|
|
),
|
|
_create_detections(
|
|
xyxy=[[10, 10, 30, 30]],
|
|
confidence=[0.2],
|
|
),
|
|
None,
|
|
pytest.raises(ValueError, match="Field 'confidence'"),
|
|
), # confidence: None + [x]
|
|
(
|
|
_create_detections(
|
|
xyxy=[[0, 0, 20, 20]],
|
|
mask=[np.array([[1, 1, 0], [1, 1, 0], [0, 0, 0]], dtype=bool)],
|
|
),
|
|
_create_detections(
|
|
xyxy=[[10, 10, 30, 30]],
|
|
mask=None,
|
|
),
|
|
None,
|
|
pytest.raises(ValueError, match="Field 'mask'"),
|
|
), # mask: None + [x]
|
|
(
|
|
_create_detections(xyxy=[[0, 0, 20, 20]], tracker_id=[1]),
|
|
_create_detections(
|
|
xyxy=[[10, 10, 30, 30]],
|
|
tracker_id=None,
|
|
),
|
|
None,
|
|
pytest.raises(ValueError, match="Field 'tracker_id'"),
|
|
), # tracker_id: None + []
|
|
(
|
|
_create_detections(xyxy=[[0, 0, 20, 20]], class_id=[1]),
|
|
_create_detections(
|
|
xyxy=[[10, 10, 30, 30]],
|
|
class_id=None,
|
|
),
|
|
None,
|
|
pytest.raises(ValueError, match="Field 'class_id'"),
|
|
), # class_id: None + []
|
|
],
|
|
)
|
|
def test_merge_inner_detection_object_pair(
|
|
detection_1: Detections,
|
|
detection_2: Detections,
|
|
expected_result: Detections | None,
|
|
exception: Exception,
|
|
):
|
|
with exception:
|
|
result = merge_inner_detection_object_pair(detection_1, detection_2)
|
|
assert result == expected_result
|