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.DepthMapmeasures the distance to every detection and colours depth over any image, no OpenCV needed.PolygonZoneandLineZonekeep counting in the right place while the camera pans.KeyPointMeanAveragePrecisionscores pose models the waypycocotoolsdoes.- Kosmos-2, Gemini 3.6/3.7 and MediaPipe hands now load straight into
DetectionsandKeyPoints. MeanAveragePrecisionruns 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
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.0More 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.DepthClipRangeandsv.DepthCamera, withto_depth,measure_detections,resize,crop,value_at,DepthAnnotator(scale="metric"), and loadersfrom_inference,from_ultralytics,from_transformers,from_png16andfrom_pfm. (#2691, #2692, #2693, #2694) - Camera-motion-aware zones.
PolygonZone.triggerandLineZone.triggeracceptcoord_transform, typed by the newsv.CoordinatesTransformationprotocol. (#2657) - COCO keypoint mAP.
sv.metrics.KeyPointMeanAveragePrecisionforsv.KeyPoints, matchingpycocotoolskeypoint evaluation. (#2687) - Configurable mAP detection limits. Keyword-only
max_detection_thresholdsonMeanAveragePrecision; the default(1, 10, 100)is unchanged. (#2716) - Mask confusion matrix.
sv.ConfusionMatrix.from_detectionsandbenchmarkacceptmetric_target=MetricTarget.MASKSand match by mask IoU, on dense masks orCompactMask. (#2612) - Metric result aggregation.
MetricResultbase class for metric results, plusaggregate_metric_results()andplot_aggregate_metric_results()to compare several results in one table or grouped bar chart. (#2498) - Public matcher.
sv.match_detectionsreturns one-to-one greedy IoU matches and the unmatched indices on each side, optionally per class. (#2557) - Kosmos-2.
sv.VLM.KOSMOS_2insv.Detections.from_vlm, one detection per grounded region. (#1903) - Gemini 3.6 and 3.7.
sv.VLM.GOOGLE_GEMINI_3_6andsv.VLM.GOOGLE_GEMINI_3_7parse the structured{"boxes": [...]}format, including normalized polygon masks. (#2504) - MediaPipe hands.
sv.KeyPoints.from_mediapipeloads hand landmarks from the TasksHandLandmarkerand the legacyHandssolution, with a new hand skeleton,class_id0 for left and 1 for right, and the handedness score indata["handedness_score"]. (#2283)
🌱 Changed
sv.Detectionsvalidatesxyxy: non-finite (NaN, inf) or non-numeric boxes now raiseValueErrorinstead of failing later. (#2503)sv.ConfusionMatrix.from_detectionsraises whenpredictionsandtargetsdiffer in length, instead of scoring the shorter list. (#2612)sv.CSVSinkwrites NumPy arrays indataandcustom_dataas JSON lists instead of NumPy's truncatedstr()form; parse array cells withjson.loads. (#2707)sv.Detections.with_nmmandsv.mask_non_max_mergemergeCompactMaskunions 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
MeanAveragePrecisionandKeyPointMeanAveragePrecisionis vectorized; results are unchanged. (#2733) sv.ComparisonAnnotatorbuilds its mask union fromCompactMaskcrops 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.ByteTrackand thesupervision.trackerpackage (usetrackers.ByteTrackTracker), thesupervision.keypointmodule (usesupervision.key_points),sv.LMMandDetections.from_lmm(usesv.VLMandfrom_vlm), the legacysv.MeanAveragePrecision(usesv.metrics.MeanAveragePrecision),create_tiles,overlay_image,ensure_cv2_image_for_annotation,ensure_pil_image_for_annotationandensure_cv2_image_for_processing(use theensure_*_for_class_method/ensure_cv2_image_for_standalone_functiondecorators),validate_keypoint_confidence,validate_keypoints_fields, thenormalized_xyxyargument ofsv.denormalize_boxes(usexyxy), and thesupervision.dataset.utilsimport path formask_to_rle/rle_to_mask. (#2582)
🔧 Fixed
sv.Detections.from_ultralyticsno longer shifts or truncates segmentation masks when letterbox padding is fractional. (#2739)- Indexing
sv.Detectionswith a Python list of booleans now selects list-valueddatawith the same mask as the arrays; it was read as positions. (#2737) sv.move_boxesandInferenceSlicerno longer wrap integer coordinates when an offset crosses the dtype's range. (#2734)sv.Detections.box_aspect_rationo longer overflows on integer boxes at large coordinates. (#2729)BlurAnnotatorandPixelateAnnotatorkeep a(H, W, 1)grayscale channel axis instead of failing with a broadcasting error. (#2725)- Without OpenCV, thick
line,polylinesanddrawContoursoutlines match OpenCV'sLINE_8pixels forthickness2 or more. (#2727) - Without OpenCV,
fillPolypaints the same boundary pixels as OpenCV, sopolygon_to_maskandPolygonZoneagree across backends. (#2730) - Without OpenCV,
resizewithINTER_NEARESTsamples the same source pixels as OpenCV, which also alignssv.CompactMask.resizeacross backends. (#2722) sv.process_videostops 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=Trueseparate labels whose boxes share a center; before, only the last label was visible. (#2718) - Oriented-box
MeanAveragePrecisionkeeps corner precision at large coordinates and puts objects in the right size bucket. (#2717) sv.KeyPointskeeps 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.CompactMaskaccepts NumPy'scopykeyword, so NumPy 2 no longer warns on conversion. (#2708)sv.CompactMask.resizematches dense nearest-neighbour resizing, including crops that become empty. (#2697, #2705)DetectionDataset.as_yolo,as_pascal_vocandas_labelmeexport a mask with holes as one instance instead of one extra instance per hole;approximate_mask_with_polygonsinsupervision.dataset.utilsgains the opt-inbridge_holesparameter that does this. (#2703)sv.metrics.MeanAverageRecallapplies themAR @ Klimit per class per image, as COCO does; multi-class scores were too low. (#2701)sv.metrics.MeanAveragePrecisionscores perfect predictions as exactly1.0instead of 0.99999988. (#2687)from_vlmwith 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_vlmwith Gemini 2.5 or 3.5 keeps masks for the detections that passclasseswhen a filtered-out item has no mask. (#2504)from_vlmkeepsclass_idinteger-typed when aclassesfilter removes every detection for PaliGemma, DeepSeek-VL2 and Gemini 2.0. (#2556)from_ultralyticsforDetections,KeyPointsandClassificationsaccepts NumPy-backed results, includingResults.numpy(). (#2710)
🏆 Contributors
- JANG BYUNGKUN (@8rulerstar) — added
KeyPointMeanAveragePrecisionand fixed perfect-prediction mAP scoring. - Caio Viotti (@cfviotti, LinkedIn) — added depth support:
DepthMap,DepthAnnotator,DepthClipRange,DepthCameraand model loaders. - Kari Pikkarainen (@kari-pikkarainen, LinkedIn) — made zones follow camera motion with
coord_transform. - Christoph Deil (@cdeil, LinkedIn) — added
max_detection_thresholdstoMeanAveragePrecision. - Raghav Rathi (@raghav-rathi, LinkedIn) — added mask targets to
ConfusionMatrix. - shao (@shaoming11, LinkedIn) — added metric-result aggregation and sped up
ComparisonAnnotatorwith 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
Detectionsreject non-finite or non-numeric boxes. - Avanish Garg (@avanish-garg, LinkedIn) — fixed
CSVSinkarray output,CompactMask.resizeand mask holes in dataset export. - Iuri de Silvio (@iurisilvio) — sped up
Detections.from_inferencewith compact masks. - @trueoneplusone — sped up
with_nmmfor compact masks. - max (@quotentiroler) — vectorized COCO greedy matching in mAP.
- A Aswanth Raj (@aswanth-07, LinkedIn) — fixed
box_aspect_ratiooverflow,CompactMaskconversion and resize, single-pointKeyPointsselection and NumPy-backed Ultralytics results. - Mohammad Hijjawi (@MohammadHijjawi97, LinkedIn) — fixed boolean-list
Detectionsindexing andsmart_positionlabel overlap. - NIKHIL (@Nikhi00718) — fixed Ultralytics letterbox masks, grayscale blur and pixelate,
process_videocleanup 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
resizesampling without OpenCV. - peng xiang (@px12811) — fixed
fillPolyrasterization without OpenCV. - Devulapalli Naga Sri Vaishnavi (@Vaishnavi220506) — fixed integer wrap in box translation and oriented-box mAP precision.
- Muhammad Talha (@Talhax55z) — fixed per-class
MeanAverageRecallscoring. - FootysHands (@ayo0la) — converted documentation examples to doctests.
- Eesha (@eesha5) — fixed imports in docstring examples.
- Jirka Borovec (@Borda, LinkedIn) — removed deprecated APIs, fixed
from_vlmclass_iddtype, followed up onwith_nmmperformance, and maintained docs, CI and release infrastructure.
Automated contributions: @dependabot, @pre-commit-ci, @Copilot
Full changelog: 0.30.0...0.31.0