Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ecfc9f475 | ||
|
|
317ac0810e | ||
|
|
cb6018ef04 | ||
|
|
580fcdde4c | ||
|
|
ad4f44644c | ||
|
|
0c8a9d34a6 | ||
|
|
d067f32678 | ||
|
|
d251677774 | ||
|
|
704652a0d2 | ||
|
|
6e3001194d | ||
|
|
d7774381cc | ||
|
|
40d4c6f554 | ||
|
|
bf88636487 | ||
|
|
a444b3bde8 | ||
|
|
4009f9d886 | ||
|
|
3f926c618a | ||
|
|
71d6670e7d |
+1
-1
@@ -1,4 +1,4 @@
|
||||
# These owners will be the default owners for everything in
|
||||
# the repo. They will be requested for review when someone
|
||||
# opens a pull request.
|
||||
* @SkalskiP @onuralpszr
|
||||
* @SkalskiP @soumik12345
|
||||
|
||||
+19
-7
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
### 0.26.1 <small>Jul 22, 2025</small>
|
||||
|
||||
- Fixed [1894](https://github.com/roboflow/supervision/pull/1894): Error in [`sv.MeanAveragePrecision`](https://supervision.roboflow.com/0.26.1/metrics/mean_average_precision/#supervision.metrics.mean_average_precision.MeanAveragePrecision) where the area used for size-specific evaluation (small / medium / large) was always zero unless explicitly provided in `sv.Detections.data`.
|
||||
|
||||
- Fixed [1895](https://github.com/roboflow/supervision/pull/1895): `ID=0` bug in [`sv.MeanAveragePrecision`](https://supervision.roboflow.com/0.26.1/metrics/mean_average_precision/#supervision.metrics.mean_average_precision.MeanAveragePrecision) where objects were getting `0.0` mAP despite perfect IoU matches due to a bug in annotation ID assignment.
|
||||
|
||||
- Fixed [1898](https://github.com/roboflow/supervision/pull/1898): Issue where [`sv.MeanAveragePrecision`](https://supervision.roboflow.com/0.26.1/metrics/mean_average_precision/#supervision.metrics.mean_average_precision.MeanAveragePrecision) could return negative values when certain object size categories have no data.
|
||||
|
||||
- Fixed [1901](https://github.com/roboflow/supervision/pull/1901): `match_metric` support for [`sv.Detections.with_nms`](https://supervision.roboflow.com/0.26.1/metrics/mean_average_precision/#supervision.detection.core.Detections.with_nms).
|
||||
|
||||
- Fixed [1906](https://github.com/roboflow/supervision/pull/1906): `border_thickness` parameter usage for [`sv.PercentageBarAnnotator`](https://supervision.roboflow.com/0.26.1/metrics/mean_average_precision/#supervision.annotators.core.PercentageBarAnnotator).
|
||||
|
||||
### 0.26.0 <small>Jul 16, 2025</small>
|
||||
|
||||
!!! failure "Removed"
|
||||
@@ -153,7 +165,7 @@
|
||||
|
||||
- Changed [#1786](https://github.com/roboflow/supervision/pull/1786): Significantly improved the speed of HSV color mapping in [`sv.HeatMapAnnotator`](https://supervision.roboflow.com/0.26.0/detection/annotators/#supervision.annotators.core.HeatMapAnnotator), achieving approximately 28x faster performance on 1920x1080 frames.
|
||||
|
||||
- Fix [#1834](https://github.com/roboflow/supervision/pull/1834): Supervision’s [`sv.MeanAveragePrecision`](https://supervision.roboflow.com/0.26.0/metrics/mean_average_precision/#supervision.metrics.mean_average_precision.MeanAveragePrecision) is now fully aligned with [pycocotools](https://github.com/ppwwyyxx/cocoapi), the official COCO evaluation tool, ensuring accurate and standardized metrics. This update enabled us to launch a new version of the [Computer Vision Model Leaderboard](https://leaderboard.roboflow.com/).
|
||||
- Fixed [#1834](https://github.com/roboflow/supervision/pull/1834): Supervision’s [`sv.MeanAveragePrecision`](https://supervision.roboflow.com/0.26.0/metrics/mean_average_precision/#supervision.metrics.mean_average_precision.MeanAveragePrecision) is now fully aligned with [pycocotools](https://github.com/ppwwyyxx/cocoapi), the official COCO evaluation tool, ensuring accurate and standardized metrics. This update enabled us to launch a new version of the [Computer Vision Model Leaderboard](https://leaderboard.roboflow.com/).
|
||||
|
||||
```python
|
||||
import supervision as sv
|
||||
@@ -173,7 +185,7 @@
|
||||
# Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.629
|
||||
```
|
||||
|
||||
- Fix [#1767](https://github.com/roboflow/supervision/pull/1767): Fixed losing `sv.Detections.data` when detections filtering.
|
||||
- Fixed [#1767](https://github.com/roboflow/supervision/pull/1767): Fixed losing `sv.Detections.data` when detections filtering.
|
||||
|
||||
### 0.25.0 <small>Nov 12, 2024</small>
|
||||
|
||||
@@ -557,9 +569,9 @@ detections = sv.Detections.from_sam(sam_result=sam_result)
|
||||
|
||||
- Changed [#1434](https://github.com/roboflow/supervision/pull/1434): [`InferenceSlicer`](https://supervision.roboflow.com/0.23.0/detection/tools/inference_slicer/) now features an `overlap_wh` parameter, making it easier to compute slice sizes when handling overlapping slices.
|
||||
|
||||
- Fix [#1448](https://github.com/roboflow/supervision/pull/1448): Various annotator type issues have been resolved, supporting expanded error handling.
|
||||
- Fixed [#1448](https://github.com/roboflow/supervision/pull/1448): Various annotator type issues have been resolved, supporting expanded error handling.
|
||||
|
||||
- Fix [#1348](https://github.com/roboflow/supervision/pull/1348): Introduced a new method for [seeking to a specific video frame](https://supervision.roboflow.com/0.23.0/utils/video/#supervision.utils.video.get_video_frames_generator), addressing cases where traditional seek methods were failing. It can be enabled with `iterative_seek=True`.
|
||||
- Fixed [#1348](https://github.com/roboflow/supervision/pull/1348): Introduced a new method for [seeking to a specific video frame](https://supervision.roboflow.com/0.23.0/utils/video/#supervision.utils.video.get_video_frames_generator), addressing cases where traditional seek methods were failing. It can be enabled with `iterative_seek=True`.
|
||||
|
||||
```python
|
||||
import supervision as sv
|
||||
@@ -572,7 +584,7 @@ for frame in sv.get_video_frames_generator(
|
||||
...
|
||||
```
|
||||
|
||||
- Fix [#1424](https://github.com/roboflow/supervision/pull/1424): `plot_image` function now clearly indicates that the size is in inches.
|
||||
- Fixed [#1424](https://github.com/roboflow/supervision/pull/1424): `plot_image` function now clearly indicates that the size is in inches.
|
||||
|
||||
!!! failure "Removed"
|
||||
|
||||
@@ -1285,7 +1297,7 @@ array([
|
||||
|
||||
### 0.11.1 <small>June 29, 2023</small>
|
||||
|
||||
- Fix [#165](https://github.com/roboflow/supervision/pull/165): [`as_folder_structure`](/0.11.1/dataset/core/#supervision.dataset.core.ClassificationDataset.as_folder_structure) fails to save [`sv.ClassificationDataset`](/0.11.1/dataset/core/#classificationdataset) when it is result of inference.
|
||||
- Fixed [#165](https://github.com/roboflow/supervision/pull/165): [`as_folder_structure`](/0.11.1/dataset/core/#supervision.dataset.core.ClassificationDataset.as_folder_structure) fails to save [`sv.ClassificationDataset`](/0.11.1/dataset/core/#classificationdataset) when it is result of inference.
|
||||
|
||||
### 0.11.0 <small>June 28, 2023</small>
|
||||
|
||||
@@ -1331,7 +1343,7 @@ array([
|
||||
|
||||
- Added [#162](https://github.com/roboflow/supervision/pull/162): additional `start` and `end` arguments to [`sv.get_video_frames_generator`](/0.11.0/utils/video/#get_video_frames_generator) allowing to generate frames only for a selected part of the video.
|
||||
|
||||
- Fix [#157](https://github.com/roboflow/supervision/pull/157): incorrect loading of YOLO dataset class names from `data.yaml`.
|
||||
- Fixed [#157](https://github.com/roboflow/supervision/pull/157): incorrect loading of YOLO dataset class names from `data.yaml`.
|
||||
|
||||
### 0.10.0 <small>June 14, 2023</small>
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
name = "supervision"
|
||||
description = "A set of easy-to-use utils that will come in handy in any Computer Vision project"
|
||||
license = { text = "MIT" }
|
||||
version = "0.26.1rc0"
|
||||
version = "0.26.1"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
authors = [
|
||||
|
||||
@@ -2239,8 +2239,11 @@ class PercentageBarAnnotator(BaseAnnotator):
|
||||
self.position: Position = position
|
||||
self.color_lookup: ColorLookup = color_lookup
|
||||
|
||||
if border_thickness is None:
|
||||
self.border_thickness = int(0.15 * self.height)
|
||||
self.border_thickness = (
|
||||
border_thickness
|
||||
if border_thickness is not None
|
||||
else int(0.15 * self.height)
|
||||
)
|
||||
|
||||
@ensure_cv2_image_for_annotation
|
||||
def annotate(
|
||||
|
||||
@@ -1261,7 +1261,7 @@ class MeanAveragePrecision(Metric):
|
||||
for image_id, image_targets in enumerate(targets):
|
||||
if self._image_indices is not None:
|
||||
image_id = self._image_indices[image_id]
|
||||
for target in image_targets:
|
||||
for target_idx, target in enumerate(image_targets):
|
||||
xyxy = target[0] # or xyxy = prediction[0]; xyxy[2:4] -= xyxy[0:2]
|
||||
xywh = [xyxy[0], xyxy[1], xyxy[2] - xyxy[0], xyxy[3] - xyxy[1]]
|
||||
# Get "area" and "iscrowd" (default 0) from data
|
||||
@@ -1271,8 +1271,15 @@ class MeanAveragePrecision(Metric):
|
||||
category_id = self._class_mapping[target[3].item()]
|
||||
else:
|
||||
category_id = target[3].item()
|
||||
|
||||
# Use area from data if available (e.g., COCO datasets)
|
||||
# Otherwise use Detections.area property
|
||||
area = data.get("area") if data else None
|
||||
if area is None:
|
||||
area = image_targets.area[target_idx]
|
||||
|
||||
dict_annotation = {
|
||||
"area": data.get("area", 0),
|
||||
"area": area,
|
||||
"iscrowd": data.get("iscrowd", 0),
|
||||
"image_id": image_id,
|
||||
"bbox": xywh,
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
from __future__ import annotations
|
||||
|
||||
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
|
||||
):
|
||||
"""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):
|
||||
"""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"
|
||||
)
|
||||
Reference in New Issue
Block a user