0.31.0

roboflow/supervision0.31.0Oct 16, 2026by Borda

Upgrading?

Full Release Notes

# v0.31.0 — Depth, moving cameras and pose mAP

supervision now sees depth: load depth maps from your model or stereo rig, measure how far each object is, and draw the map.

- **`sv.DepthMap`** measures the distance to every detection and colours depth over any image, no OpenCV needed.
- **`PolygonZone`** and `LineZone` keep counting in the right place while the camera pans.
- **`KeyPointMeanAveragePrecision`** scores pose models the way `pycocotools` does.
- **Kosmos-2**, Gemini 3.6/3.7 and MediaPipe hands now load straight into `Detections` and `KeyPoints`.
- **`MeanAveragePrecision`** runs far faster on crowded images and lets you raise the detection cap.

It's a drop-in upgrade if 0.30.x showed you no deprecation warnings, apart from four output changes. `sv.ByteTrack` with the `supervision.tracker` package, `supervision.keypoint`, `sv.LMM`, the legacy `sv.MeanAveragePrecision` and a few helpers are gone; the Migration guide has the swaps.

## ✨ Spotlights / highlights

<img width="950" height="543" alt="image" src="https://github.com/user-attachments/assets/b3eec2cf-ae25-43ba-8494-aec69766991b" />

### Depth maps: load, measure and draw

`sv.DepthMap` holds metric depth, stereo disparity or relative depth for one frame, and keeps track of which one it is. `sv.DepthCamera` converts disparity to metres, `measure_detections` writes the median depth under each object to `detections.data` (`"depth_m"` when the map can give metres), and `sv.DepthAnnotator` colours the map over an image without OpenCV. `sv.DepthClipRange` keeps one colour scale across a whole video, so colours don't flicker between frames. Loaders read Roboflow Inference, Ultralytics, Transformers, and 16-bit PNG and PFM dataset files such as KITTI and Middlebury. (#2691, #2692, #2693, #2694)

```python
import numpy as np
import supervision as sv

depth = np.full((100, 100), 20.0, dtype=np.float32)
depth[20:40, 20:40] = 4.0
depth_map = sv.DepthMap(depth, kind="depth_m")

detections = sv.Detections(xyxy=np.array([[20, 20, 40, 40], [60, 60, 90, 90]], dtype=float))
detections = depth_map.measure_detections(detections)
detections.data["depth_m"]  # array([ 4., 20.], dtype=float32)

annotated = sv.DepthAnnotator().annotate(np.zeros((100, 100, 3), np.uint8), depth_map)
```

### Zones that follow the camera

Zones used to assume a fixed camera; when it panned, the scene slid out from under the zone. `PolygonZone.trigger` and `LineZone.trigger` now take an optional per-frame `coord_transform` that maps detection anchors back into the frame the zone was drawn on. Any object with `abs_to_rel` and `rel_to_abs` works, as described by the new `sv.CoordinatesTransformation` protocol, including the transformations from the `trackers` motion estimator. Drawing is unchanged, and `coord_transform=None` keeps current results. (#2657)

```python
import numpy as np
import supervision as sv

class CameraShift:
    def abs_to_rel(self, points): return points + [50, 0]
    def rel_to_abs(self, points): return points - [50, 0]

zone = sv.PolygonZone(polygon=np.array([[0, 0], [100, 0], [100, 100], [0, 100]]))
car = sv.Detections(xyxy=np.array([[120.0, 40.0, 140.0, 60.0]]))
zone.trigger(car)                                 # array([False])
zone.trigger(car, coord_transform=CameraShift())  # array([ True])
```

### COCO keypoint mAP for pose models

`sv.metrics.KeyPointMeanAveragePrecision` scores `sv.KeyPoints` predictions by Object Keypoint Similarity at ten thresholds from 0.50 to 0.95, and its scores agree with `pycocotools` `COCOeval(..., "keypoints")`. COCO greedy matching is now vectorized for box and keypoint mAP. In the benchmark reported with that change, one image with 1000 predictions and a 1000-detection cap dropped from 35.9 s to 0.5 s. `MeanAveragePrecision` also takes `max_detection_thresholds` for scenes with more than 100 objects. (#2687, #2733, #2716)

```python
import numpy as np
import supervision as sv
from supervision.metrics import KeyPointMeanAveragePrecision

targets = sv.KeyPoints(
    xy=np.array([[[10, 10], [60, 10], [35, 80]]], dtype=np.float32),
    class_id=np.array([0]),
    data={"area": np.array([4000.0])},
)
predictions = sv.KeyPoints(
    xy=np.array([[[11, 10], [60, 12], [35, 79]]], dtype=np.float32),
    class_id=np.array([0]),
    detection_confidence=np.array([0.9]),
)
metric = KeyPointMeanAveragePrecision(sigmas=[0.25, 0.25, 0.25])
metric.update(predictions, targets).compute().map50  # 1.0
```

### More models, and the matcher behind the metrics

`sv.Detections.from_vlm` parses Kosmos-2 grounding results (`sv.VLM.KOSMOS_2`) and Gemini 3.6 and 3.7 structured boxes and polygon masks. `sv.KeyPoints.from_mediapipe` loads MediaPipe hand landmarks, with `class_id` 0 for a left hand and 1 for a right hand. `sv.match_detections` exposes the one-to-one greedy IoU matcher the metrics use, for comparing two models or auditing labels against predictions. (#1903, #2504, #2283, #2557)

```python
import supervision as sv

caption = "a snowman next to a fire"
entities = [("a snowman", (0, 9), [(0.1, 0.2, 0.4, 0.9)]), ("a fire", (19, 25), [(0.5, 0.6, 0.8, 0.95)])]
detections = sv.Detections.from_vlm(sv.VLM.KOSMOS_2, (caption, entities), resolution_wh=(640, 480))

pairs, unmatched_a, unmatched_b = sv.match_detections(detections_a, detections_b)
```

## 🔄 Migration guide

No breaking changes beyond the APIs deprecated in 0.26–0.28 and scheduled for removal in 0.31.0. If 0.30.x printed no deprecation warnings for your code, only the four output changes at the end of this section can affect it.

| Removed | Use instead |
| -- | -- |
| `sv.ByteTrack` and the `supervision.tracker` package | `ByteTrackTracker` from the `trackers` package (`pip install trackers`); call `update()` instead of `update_with_detections()` |
| `supervision.keypoint` | `supervision.key_points` |
| `sv.LMM`, `Detections.from_lmm` | `sv.VLM`, `Detections.from_vlm` |
| legacy `sv.MeanAveragePrecision` (`supervision.metrics.detection`) | `sv.metrics.MeanAveragePrecision` |
| `sv.denormalize_boxes(normalized_xyxy=...)` | `sv.denormalize_boxes(xyxy=...)` |
| `supervision.dataset.utils.mask_to_rle` / `rle_to_mask` | `sv.mask_to_rle` / `sv.rle_to_mask` |
| `ensure_cv2_image_for_annotation` | `ensure_cv2_image_for_class_method` (`supervision.utils.conversion`) |
| `ensure_pil_image_for_annotation` | `ensure_pil_image_for_class_method` (`supervision.utils.conversion`) |
| `ensure_cv2_image_for_processing` | `ensure_cv2_image_for_standalone_function` (`supervision.utils.conversion`) |
| `create_tiles`, `overlay_image` (also `sv.overlay_image`), `validate_keypoint_confidence`, `validate_keypoints_fields` | no direct replacement |

```python
# before
tracker = sv.ByteTrack()
detections = tracker.update_with_detections(detections)
map_result = sv.MeanAveragePrecision.from_detections(predictions=predictions, targets=targets)

# after
from trackers import ByteTrackTracker
from supervision.metrics import MeanAveragePrecision

detections = ByteTrackTracker().update(detections)
map_result = MeanAveragePrecision().update(predictions, targets).compute()
```

Four behaviour changes can alter output: `sv.Detections` raises on non-finite boxes, `sv.CSVSink` writes arrays as JSON lists, `ConfusionMatrix.from_detections` rejects lists of different length, and multi-class `mAR @ K` scores follow COCO. Details under Changed and Fixed.

## 📝 Notable changes

### 🚀 Added

- **Depth maps.** `sv.DepthMap`, `sv.DepthAnnotator`, `sv.DepthClipRange` and `sv.DepthCamera`, with `to_depth`, `measure_detections`, `resize`, `crop`, `value_at`, `DepthAnnotator(scale="metric")`, and loaders `from_inference`, `from_ultralytics`, `from_transformers`, `from_png16` and `from_pfm`. (#2691, #2692, #2693, #2694)
- **Camera-motion-aware zones.** `PolygonZone.trigger` and `LineZone.trigger` accept `coord_transform`, typed by the new `sv.CoordinatesTransformation` protocol. (#2657)
- **COCO keypoint mAP.** `sv.metrics.KeyPointMeanAveragePrecision` for `sv.KeyPoints`, matching `pycocotools` keypoint evaluation. (#2687)
- **Configurable mAP detection limits.** Keyword-only `max_detection_thresholds` on `MeanAveragePrecision`; the default `(1, 10, 100)` is unchanged. (#2716)
- **Mask confusion matrix.** `sv.ConfusionMatrix.from_detections` and `benchmark` accept `metric_target=MetricTarget.MASKS` and match by mask IoU, on dense masks or `CompactMask`. (#2612)
- **Metric result aggregation.** `MetricResult` base class for metric results, plus `aggregate_metric_results()` and `plot_aggregate_metric_results()` to compare several results in one table or grouped bar chart. (#2498)
- **Public matcher.** `sv.match_detections` returns one-to-one greedy IoU matches and the unmatched indices on each side, optionally per class. (#2557)
- **Kosmos-2.** `sv.VLM.KOSMOS_2` in `sv.Detections.from_vlm`, one detection per grounded region. (#1903)
- **Gemini 3.6 and 3.7.** `sv.VLM.GOOGLE_GEMINI_3_6` and `sv.VLM.GOOGLE_GEMINI_3_7` parse the structured `{"boxes": [...]}` format, including normalized polygon masks. (#2504)
- **MediaPipe hands.** `sv.KeyPoints.from_mediapipe` loads hand landmarks from the Tasks `HandLandmarker` and the legacy `Hands` solution, with a new hand skeleton, `class_id` 0 for left and 1 for right, and the handedness score in `data["handedness_score"]`. (#2283)

### 🌱 Changed

- `sv.Detections` validates `xyxy`: non-finite (NaN, inf) or non-numeric boxes now raise `ValueError` instead of failing later. (#2503)
- `sv.ConfusionMatrix.from_detections` raises when `predictions` and `targets` differ in length, instead of scoring the shorter list. (#2612)
- `sv.CSVSink` writes NumPy arrays in `data` and `custom_data` as JSON lists instead of NumPy's truncated `str()` form; parse array cells with `json.loads`. (#2707)
- `sv.Detections.with_nmm` and `sv.mask_non_max_merge` merge `CompactMask` unions without full-image allocations; the error for mismatched image shapes now names both shapes. (#2606, #2616)
- `sv.Detections.from_inference(compact_masks=True)` crops each polygon mask to the polygon instead of the whole image, cutting time and memory for many polygons on large images. (#2678)
- COCO greedy matching in `MeanAveragePrecision` and `KeyPointMeanAveragePrecision` is vectorized; results are unchanged. (#2733)
- `sv.ComparisonAnnotator` builds its mask union from `CompactMask` crops in place instead of allocating a full `(N, H, W)` stack. (#2495)

### ❌ Removed

- APIs deprecated since 0.26.0–0.28.0 and scheduled for 0.31.0: `sv.ByteTrack` and the `supervision.tracker` package (use `trackers.ByteTrackTracker`), the `supervision.keypoint` module (use `supervision.key_points`), `sv.LMM` and `Detections.from_lmm` (use `sv.VLM` and `from_vlm`), the legacy `sv.MeanAveragePrecision` (use `sv.metrics.MeanAveragePrecision`), `create_tiles`, `overlay_image`, `ensure_cv2_image_for_annotation`, `ensure_pil_image_for_annotation` and `ensure_cv2_image_for_processing` (use the `ensure_*_for_class_method` / `ensure_cv2_image_for_standalone_function` decorators), `validate_keypoint_confidence`, `validate_keypoints_fields`, the `normalized_xyxy` argument of `sv.denormalize_boxes` (use `xyxy`), and the `supervision.dataset.utils` import path for `mask_to_rle` / `rle_to_mask`. (#2582)

### 🔧 Fixed

- `sv.Detections.from_ultralytics` no longer shifts or truncates segmentation masks when letterbox padding is fractional. (#2739)
- Indexing `sv.Detections` with a Python list of booleans now selects list-valued `data` with the same mask as the arrays; it was read as positions. (#2737)
- `sv.move_boxes` and `InferenceSlicer` no longer wrap integer coordinates when an offset crosses the dtype's range. (#2734)
- `sv.Detections.box_aspect_ratio` no longer overflows on integer boxes at large coordinates. (#2729)
- `BlurAnnotator` and `PixelateAnnotator` keep a `(H, W, 1)` grayscale channel axis instead of failing with a broadcasting error. (#2725)
- Without OpenCV, thick `line`, `polylines` and `drawContours` outlines match OpenCV's `LINE_8` pixels for `thickness` 2 or more. (#2727)
- Without OpenCV, `fillPoly` paints the same boundary pixels as OpenCV, so `polygon_to_mask` and `PolygonZone` agree across backends. (#2730)
- Without OpenCV, `resize` with `INTER_NEAREST` samples the same source pixels as OpenCV, which also aligns `sv.CompactMask.resize` across backends. (#2722)
- `sv.process_video` stops reading the source when a callback or frame write fails, instead of decoding the rest of the video before raising. (#2720)
- Label annotators with `smart_position=True` separate labels whose boxes share a center; before, only the last label was visible. (#2718)
- Oriented-box `MeanAveragePrecision` keeps corner precision at large coordinates and puts objects in the right size bucket. (#2717)
- `sv.KeyPoints` keeps all three coordinates when selecting a single key point with a NumPy integer index. (#2715)
- Integer boxes and oriented boxes no longer land in the wrong object-size category when their area overflows the dtype. (#2713)
- `sv.CompactMask` accepts NumPy's `copy` keyword, so NumPy 2 no longer warns on conversion. (#2708)
- `sv.CompactMask.resize` matches dense nearest-neighbour resizing, including crops that become empty. (#2697, #2705)
- `DetectionDataset.as_yolo`, `as_pascal_voc` and `as_labelme` export a mask with holes as one instance instead of one extra instance per hole; `approximate_mask_with_polygons` in `supervision.dataset.utils` gains the opt-in `bridge_holes` parameter that does this. (#2703)
- `sv.metrics.MeanAverageRecall` applies the `mAR @ K` limit per class per image, as COCO does; multi-class scores were too low. (#2701)
- `sv.metrics.MeanAveragePrecision` scores perfect predictions as exactly `1.0` instead of 0.99999988. (#2687)
- `from_vlm` with Gemini 2.5 or 3.5 keeps a mask pixel only where the score exceeds 127 of 255; masks grew by half a mask pixel per side. (#2589)
- `from_vlm` with Gemini 2.5 or 3.5 keeps masks for the detections that pass `classes` when a filtered-out item has no mask. (#2504)
- `from_vlm` keeps `class_id` integer-typed when a `classes` filter removes every detection for PaliGemma, DeepSeek-VL2 and Gemini 2.0. (#2556)
- `from_ultralytics` for `Detections`, `KeyPoints` and `Classifications` accepts NumPy-backed results, including `Results.numpy()`. (#2710)

---

## 🏆 Contributors

- **JANG BYUNGKUN** (@8rulerstar) — added `KeyPointMeanAveragePrecision` and fixed perfect-prediction mAP scoring.
- **Caio Viotti** (@cfviotti, [LinkedIn](https://linkedin.com/in/caioviotti/)) — added depth support: `DepthMap`, `DepthAnnotator`, `DepthClipRange`, `DepthCamera` and model loaders.
- **Kari Pikkarainen** (@kari-pikkarainen, [LinkedIn](https://www.linkedin.com/in/karipikkarainen/recent-activity/all/)) — made zones follow camera motion with `coord_transform`.
- **Christoph Deil** (@cdeil, [LinkedIn](https://www.linkedin.com/in/christophdeil)) — added `max_detection_thresholds` to `MeanAveragePrecision`.
- **Raghav Rathi** (@raghav-rathi, [LinkedIn](https://www.linkedin.com/in/raghavrathi00/)) — added mask targets to `ConfusionMatrix`.
- **shao** (@shaoming11, [LinkedIn](https://www.linkedin.com/in/shaoming-wu/)) — added metric-result aggregation and sped up `ComparisonAnnotator` with compact masks.
- **David** (@David-rn) — co-authored metric-result aggregation from the original proposal.
- **Jabrail** (@jabrailkhalil) — added `sv.match_detections`.
- **Soumik Rakshit** (@soumik12345, [LinkedIn](https://www.linkedin.com/in/soumikrakshit)) — added Kosmos-2 support in `from_vlm`.
- @Xayar145 — added Gemini 3.6 and 3.7 support in `from_vlm`.
- @tarunbommawar27 — added MediaPipe hand landmarks to `KeyPoints.from_mediapipe`.
- **Advait Shukla** (@AdvaitS) — made `Detections` reject non-finite or non-numeric boxes.
- **Avanish Garg** (@avanish-garg, [LinkedIn](https://www.linkedin.com/in/avanish-garg)) — fixed `CSVSink` array output, `CompactMask.resize` and mask holes in dataset export.
- **Iuri de Silvio** (@iurisilvio) — sped up `Detections.from_inference` with compact masks.
- @trueoneplusone — sped up `with_nmm` for compact masks.
- **max** (@quotentiroler) — vectorized COCO greedy matching in mAP.
- **A Aswanth Raj** (@aswanth-07, [LinkedIn](https://www.linkedin.com/in/a-aswanth-raj/)) — fixed `box_aspect_ratio` overflow, `CompactMask` conversion and resize, single-point `KeyPoints` selection and NumPy-backed Ultralytics results.
- **Mohammad Hijjawi** (@MohammadHijjawi97, [LinkedIn](https://www.linkedin.com/in/mohammadhijjawi)) — fixed boolean-list `Detections` indexing and `smart_position` label overlap.
- **NIKHIL** (@Nikhi00718) — fixed Ultralytics letterbox masks, grayscale blur and pixelate, `process_video` cleanup and box-area overflow in metrics.
- **kevin** (@kevin9327) — fixed thick-line drawing without OpenCV and Gemini mask binarization, and typed the test suite.
- **Kaushik Reddy Lingireddy** (@Lingikaushikreddy, [LinkedIn](https://linkedin.com/in/kaushik-reddy-lingireddy)) — fixed nearest-neighbour `resize` sampling without OpenCV.
- **peng xiang** (@px12811) — fixed `fillPoly` rasterization without OpenCV.
- **Devulapalli Naga Sri Vaishnavi** (@Vaishnavi220506) — fixed integer wrap in box translation and oriented-box mAP precision.
- **Muhammad Talha** (@Talhax55z) — fixed per-class `MeanAverageRecall` scoring.
- **FootysHands** (@ayo0la) — converted documentation examples to doctests.
- **Eesha** (@eesha5) — fixed imports in docstring examples.
- **Jirka Borovec** (@Borda, [LinkedIn](https://linkedin.com/in/jirka-borovec)) — removed deprecated APIs, fixed `from_vlm` `class_id` dtype, followed up on `with_nmm` performance, and maintained docs, CI and release infrastructure.

*Automated contributions: @dependabot, @pre-commit-ci, @Copilot*

______________________________________________________________________

**Full changelog**: <https://github.com/roboflow/supervision/compare/0.30.0...0.31.0>

Related Repositories