Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/supervision/detection/utils/iou_and_nms.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,9 +381,9 @@ def oriented_box_iou_batch(
boxes_true = boxes_true.reshape(-1, 4, 2)
boxes_detection = boxes_detection.reshape(-1, 4, 2)

max_height = int(max(boxes_true[:, :, 0].max(), boxes_detection[:, :, 0].max()) + 1)
# adding 1 because we are 0-indexed
max_width = int(max(boxes_true[:, :, 1].max(), boxes_detection[:, :, 1].max()) + 1)
max_width = int(max(boxes_true[:, :, 0].max(), boxes_detection[:, :, 0].max()) + 1)
max_height = int(max(boxes_true[:, :, 1].max(), boxes_detection[:, :, 1].max()) + 1)

mask_true = np.zeros((boxes_true.shape[0], max_height, max_width), dtype=np.uint8)
for box_idx, box_true in enumerate(boxes_true):
Expand Down
17 changes: 17 additions & 0 deletions tests/detection/utils/test_iou_and_nms.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
box_non_max_suppression,
mask_non_max_merge,
mask_non_max_suppression,
oriented_box_iou_batch,
)
from tests.helpers import _generate_random_boxes

Expand Down Expand Up @@ -1128,3 +1129,19 @@ def test_box_iou_batch_symmetric_large(
rtol=1e-6,
atol=1e-12,
)


def test_oriented_box_iou_batch_is_invariant_to_non_square_scaling() -> None:
boxes_true = np.array([[[1, 0], [0, 1], [3, 4], [4, 3]]], dtype=np.float32)
boxes_detection = np.array([[[1, 1], [2, 0], [4, 2], [3, 3]]], dtype=np.float32)

baseline_iou = oriented_box_iou_batch(boxes_true, boxes_detection)
scaled_iou = oriented_box_iou_batch(
boxes_true * np.array([[10, 1]], dtype=np.float32),
boxes_detection * np.array([[10, 1]], dtype=np.float32),
)

assert baseline_iou.shape == (1, 1)
assert scaled_iou.shape == (1, 1)
assert baseline_iou[0, 0] > 0.35
assert np.allclose(scaled_iou, baseline_iou, rtol=0.03, atol=0.02)
28 changes: 28 additions & 0 deletions tests/metrics/test_mean_average_precision.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import numpy as np

from supervision.config import ORIENTED_BOX_COORDINATES
from supervision.detection.core import Detections
from supervision.metrics.core import MetricTarget
from supervision.metrics.mean_average_precision import MeanAveragePrecision


Expand Down Expand Up @@ -33,6 +35,32 @@ def test_multiple_perfect_detections(self):
# Should be perfect 1.0 mAP
assert abs(result.map50_95 - 1.0) < 1e-6

def test_perfect_non_square_oriented_boxes_get_full_map(self):
"""Perfect OBB detections must not fail when the canvas is non-square."""
obb = np.array(
[[[10, 0], [0, 1], [30, 4], [40, 3]]],
dtype=np.float32,
)
detections = Detections(
xyxy=np.array([[0, 0, 40, 4]], dtype=np.float64),
class_id=np.array([0]),
confidence=np.array([0.9]),
data={ORIENTED_BOX_COORDINATES: obb},
)
targets = Detections(
xyxy=np.array([[0, 0, 40, 4]], dtype=np.float64),
class_id=np.array([0]),
data={ORIENTED_BOX_COORDINATES: obb},
)

metric = MeanAveragePrecision(
metric_target=MetricTarget.ORIENTED_BOUNDING_BOXES
)
metric.update([detections], [targets])
result = metric.compute()

assert abs(result.map50_95 - 1.0) < 1e-6

def test_batch_updates_perfect_detections(self, detections_50_50, targets_50_50):
"""Test that batch updates with perfect detections get 1.0 mAP"""
metric = MeanAveragePrecision()
Expand Down
47 changes: 47 additions & 0 deletions tests/metrics/test_oriented_bounding_box_metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import numpy as np
import pytest

from supervision.config import ORIENTED_BOX_COORDINATES
from supervision.detection.core import Detections
from supervision.metrics.core import MetricTarget
from supervision.metrics.f1_score import F1Score
from supervision.metrics.mean_average_precision import MeanAveragePrecision
from supervision.metrics.mean_average_recall import MeanAverageRecall
from supervision.metrics.precision import Precision
from supervision.metrics.recall import Recall


def _non_square_obb_detections(confidence: bool = False) -> Detections:
obb = np.array(
[[[10, 0], [0, 1], [30, 4], [40, 3]]],
dtype=np.float32,
)
return Detections(
xyxy=np.array([[0, 0, 40, 4]], dtype=np.float64),
class_id=np.array([0]),
confidence=np.array([0.9]) if confidence else None,
data={ORIENTED_BOX_COORDINATES: obb},
)


@pytest.mark.parametrize(
("metric_cls", "score_name"),
[
(Precision, "precision_at_50"),
(Recall, "recall_at_50"),
(F1Score, "f1_50"),
(MeanAveragePrecision, "map50_95"),
(MeanAverageRecall, "mAR_at_100"),
],
)
def test_perfect_non_square_oriented_boxes_score_as_perfect(
metric_cls: type,
score_name: str,
) -> None:
predictions = _non_square_obb_detections(confidence=True)
targets = _non_square_obb_detections()

metric = metric_cls(metric_target=MetricTarget.ORIENTED_BOUNDING_BOXES)
result = metric.update([predictions], [targets]).compute()

assert getattr(result, score_name) == pytest.approx(1.0)