github roboflow/supervision 0.31.0
supervision-0.31.0

3 hours ago

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

image

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)

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)

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)

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)

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
# 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) — added depth support: DepthMap, DepthAnnotator, DepthClipRange, DepthCamera and model loaders.
  • Kari Pikkarainen (@kari-pikkarainen, LinkedIn) — made zones follow camera motion with coord_transform.
  • Christoph Deil (@cdeil, LinkedIn) — added max_detection_thresholds to MeanAveragePrecision.
  • Raghav Rathi (@raghav-rathi, LinkedIn) — added mask targets to ConfusionMatrix.
  • shao (@shaoming11, LinkedIn) — 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) — 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) — 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) — fixed box_aspect_ratio overflow, CompactMask conversion and resize, single-point KeyPoints selection and NumPy-backed Ultralytics results.
  • Mohammad Hijjawi (@MohammadHijjawi97, LinkedIn) — 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) — 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) — 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: 0.30.0...0.31.0

Don't miss a new supervision release

NewReleases is sending notifications on new releases.