What’s New

supervision

AI

supervision release notes.

Latest 0.29.1 · by supervisionWebsiteroboflow/supervision

Changelog

0.29.1

supervision-0.29.1

Added
  • Add `KeyPoints.with_nms()` method to remove duplicate skeletons using non-maximum suppression on axis-aligned bounding boxes derived from keypoints, supporting `class_agnostic` mode and any `OverlapMetric`
Changed
  • Optimize `sv.HaloAnnotator` to use the same optimized CompactMask paint path as `MaskAnnotator`, reducing memory materialization
  • Optimize mask IoU computation to use matrix multiplication on flattened masks instead of explicit tensor allocation
  • Vectorize `sv.mask_to_xyxy` and `sv.KeyPoints.as_detections` to use batched NumPy operations instead of per-element loops
Fixed
  • Fix `sv.DetectionDataset.as_pascal_voc` mutating bounding boxes by shifting them +1 px in-place on each export
  • Fix `sv.Precision` and `sv.F1Score` to correctly count background false positives under `MICRO` and `MACRO` averaging modes
  • Fix `sv.DetectionsSmoother` raising when detections have no confidence scores
  • Fix `sv.Detections.from_vlm` to degrade gracefully on malformed Gemini and Qwen output instead of raising `TypeError`
  • Fix `sv.JSONSink` serializing NumPy scalars in `custom_data` by converting them to JSON-compatible types
  • Fix `sv.approximate_polygon` exceeding the point-count budget and validate `epsilon_step` to be positive
  • Fix COCO export to preserve all polygon segments for multi-part masks instead of writing only the first polygon
What's new
🚀 KeyPoints.with_nms() — NMS for pose estimation
import supervision as sv

key_points = model.predict(image)  # sv.KeyPoints
key_points = key_points.with_nms(threshold=0.5)  # removes duplicate skeletons

Derives axis-aligned bounding boxes from each skeleton's valid (non-zero and visible) keypoints, then applies standard box NMS. Supports class_agnostic mode and any OverlapMetric (IOU, IOS). Raises ValueError if detection_confidence is not set.

https://github.com/user-attachments/assets/ed7bd310-4868-4275-ae04-c88e7a0c2561


Notable changes
Bug fixes
  • sv.DetectionDataset.as_pascal_voc no longer mutates bounding boxes (#2341) Previously, every export shifted every bounding box by +1 px in-place. A second call compounded the shift. Fixed by rebinding to a new array; on-disk XML output is unchanged.

  • sv.Precision and sv.F1Score correctly count background false positives (#2331) Predictions on images with no ground-truth objects, and predictions of classes absent from any annotation, were previously ignored. Under MICRO and MACRO averaging they are now counted as false positives. WEIGHTED averaging is unchanged. Users should re-evaluate existing metric results after upgrading.

  • sv.DetectionsSmoother works with confidence-free detections (#2333) The smoother no longer raises when detections have no confidence scores. Confidence is averaged over the frames that carry it; tracks without any confidence produce None.

  • sv.Detections.from_vlm is robust to malformed Gemini/Qwen output (#2342) Valid JSON that is not a list, or whose elements are not dicts, now degrades to empty Detections instead of raising TypeError. A malformed mask value in Gemini 2.5 responses no longer misaligns the xyxy/confidence/masks arrays.

  • sv.JSONSink serializes NumPy scalars in custom_data (#2334) np.int64 frame indices and other NumPy scalars in custom_data no longer raise TypeError at flush time. NumPy arrays are serialized as lists. The file handle closes even when serialization fails.

  • sv.approximate_polygon respects the point-count budget (#2332) The function now returns at most floor(N * (1 - percentage)) points (minimum 3). Previously it could return more points than requested. epsilon_step is now validated to be positive.

  • COCO export preserves all segments for multi-part masks (#2322) Previously, only the first polygon was written when a non-crowd detection had disjoint mask segments. All polygon parts are now written.

Performance
  • sv.HaloAnnotator is ~4× faster with CompactMask detections (#2339) HaloAnnotator now uses the same optimized CompactMask paint path as MaskAnnotator. Previously it materialized each mask full-frame; now it operates on the bounding-box crop. Annotated output is unchanged.

  • Mask IoU uses less peak memory (#2323) Mask IoU computation now uses matrix multiplication on flattened masks instead of an explicit (N, M, H, W) tensor. For masks larger than 4096×4096 px, computation promotes to float64 automatically. Results are numerically identical.

  • sv.mask_to_xyxy and sv.KeyPoints.as_detections vectorized (#2330) Both functions now use batched NumPy operations instead of per-element loops. Outputs are bit-identical.


Contributors
  • Ruben Haisma (@RubenHaisma, LinkedIn) — VLM robustness, Pascal VOC export fix, DetectionsSmoother, JSONSink, metrics correctness, polygon budgeting, vectorization
  • Agis Kounelis (@kounelisagis, LinkedIn) — HaloAnnotator perf, mask IoU matmul, mask_to_xyxy/KeyPoints.as_detections vectorization, OBB cookbook
  • Piotr Skalski (@SkalskiP, LinkedIn) — KeyPoints.with_nms()
  • Abdelrahman Gomaa (@abdogomaa201099, LinkedIn) — COCO multi-polygon export

Full Changelog: https://github.com/roboflow/supervision/compare/0.29.0...0.29.1

0.29.0

supervision-0.29.0

Added
  • Added sv.VertexEllipseAreaAnnotator, sv.VertexEllipseOutlineAnnotator, and sv.VertexEllipseHaloAnnotator for visualizing keypoint uncertainty as covariance ellipses
  • Added sv.oriented_box_non_max_suppression and sv.oriented_box_non_max_merge for performing NMS and NMM directly on oriented bounding boxes
  • Added OBB (Oriented Bounding Box) support to sv.ConfusionMatrix via MetricTarget.ORIENTED_BOUNDING_BOXES
  • Added preserve_audio parameter to sv.process_video to mux the audio stream from the source video into the output using ffmpeg
  • Added is_obb parameter to sv.DetectionDataset.as_yolo for exporting oriented bounding box annotations in the YOLO OBB format
Changed
  • sv.EdgeAnnotator and sv.VertexAnnotator now respect the visible mask, skipping invisible keypoints and their edges during rendering
  • sv.EdgeAnnotator and sv.VertexLabelAnnotator now support per-class skeleton definitions for correct rendering with multiple skeleton topologies
  • sv.Detections.with_nms and sv.Detections.with_nmm are now OBB-aware, using oriented-box IoU automatically when oriented box coordinates are present
🚀 Added
🌱 Changed
  • sv.Detections.area is now OBB-aware. When oriented box coordinates are present, the property returns the polygon area of the rotated bounding box (via the shoelace formula) instead of the axis-aligned box area. (#2306)

  • sv.InferenceSlicer now detects OBB outputs from callbacks and automatically falls back to sequential processing to avoid thread-safety issues when thread_workers > 1. (#2256)

  • Fixed sv.oriented_box_iou_batch to correctly handle non-square canvases. Previously, rasterization assumed square dimensions, leading to incorrect IoU values for tall or wide images. (#2282)

🔧 Fixed
  • Fixed sv.process_video audio stream handling. The audio muxing path now correctly creates temp files on the same filesystem, decodes ffmpeg errors, and avoids muxing incomplete output. (#2252)

  • Fixed sv.Detections.from_vlm returning None for class_id on empty VLM parses. Now returns an empty int ndarray. (#2239)

  • Fixed sv.Detections.from_inference to preserve class_name as a string-dtype array when predictions are empty. Previously it returned an untyped empty array. (#2270)

  • Fixed sv.HeatMapAnnotator divide-by-zero crash when called with empty detections. (#2269)

  • Fixed COCO export emitting 0-indexed category_id values. Now correctly emits 1-indexed IDs as per the COCO specification. (#2276)

  • Fixed COCO annotation and image IDs not being chainable across dataset splits. IDs are now sequential across train/val/test. (#2267)

  • Fixed sv.DetectionDataset.as_yolo losing OBB rotation when exporting oriented bounding boxes. (#2289)

  • Fixed YOLO dataset loading to sort class names by numeric keys when the data.yaml uses integer class IDs. (#2296)

  • Fixed letterbox utility to support grayscale images. (#2297)

  • Fixed file extension filters to normalize casing (e.g. .JPG now matches .jpg). (#2298)

⚠️ Deprecated
DeprecatedRemovalReplacement
KeyPoints.confidence0.32.0KeyPoints.keypoint_confidence
merge_inner_detection_object_pair0.32.0None (internal use only)
merge_inner_detections_objects0.32.0None (internal use only)
merge_inner_detections_objects_without_iou0.32.0None (internal use only)
validate_detections_fields0.32.0None (internal use only)
validate_vlm_parameters0.32.0None (internal use only)
validate_fields_both_defined_or_none0.32.0None (internal use only)
validate_xyxy0.32.0None (internal use only)
validate_mask0.32.0None (internal use only)
validate_class_id0.32.0None (internal use only)
validate_confidence0.32.0None (internal use only)
validate_tracker_id0.32.0None (internal use only)
validate_data0.32.0None (internal use only)
validate_xy0.32.0None (internal use only)
validate_key_point_confidence0.32.0None (internal use only)
validate_key_points_fields0.32.0None (internal use only)
validate_resolution0.32.0None (internal use only)
validate_custom_values0.32.0None (internal use only)
validate_input_tensors0.32.0None (internal use only)
validate_labels0.32.0None (internal use only)
🏆 Contributors

@SkalskiP (Piotr Skalski), @Borda (Jirka Borovec), @kounelisagis (Agis Kounelis), @RitwijParmar (Ritwij Aryan Parmar), @Khanz9664 (Shahid Ul Islam), @satishkc7 (SATISH K C), @Ace3Z (Mahbod Tajdini), @Madhav-C, @RubenHaisma (Ruben Haisma), @adhavan18 (Tamil Adhavan), @Bortlesboat (Andrew Barnes), @Lourdhu02, @tarunbommawar27, @YousefZahran1 (Youssef Ibrahim), @JFrench-Enterprise, @Patel-Prem (Premkumar Patel)

0.28.0

supervision-0.28.0

Added
  • Add sv.CompactMask for memory-efficient storage of segmentation masks using tight bounding-box crops and RLE encoding, reducing memory usage by 10–100× while maintaining API compatibility with existing mask operations
  • Add sv.Detections.from_sam3() to parse SAM3 text-prompted segmentation responses in both PCS (multi-prompt) and PVS (video) formats into standard Detections objects
  • Add SAM3 detection and point-video-segmentation output parsing to sv.Detections.from_inference() for both local inference package and Roboflow-hosted server responses
  • Add support for compressed COCO RLE masks in sv.Detections.from_inference() to decode RLE or rle_mask fields directly into binary masks
  • Replace print-based diagnostic output with standard logging module under the supervision logger
  • Add RGBA hex code support to sv.Color.from_hex() for 8-digit hex values and add sv.hex_to_rgba(), sv.rgba_to_hex(), and sv.is_valid_hex() helper functions
  • Add dynamic kernel sizing to BlurAnnotator and PixelateAnnotator that computes kernel size per detection as a fraction of the bounding-box side for consistent results across object scales
Changed
  • Change sv.VideoInfo.fps from int to float to preserve true NTSC frame rates (23.976, 29.97, 59.94) instead of truncating them
Deprecated
  • Deprecate sv.ByteTrack in favor of ByteTrackTracker from the dedicated trackers package, with removal planned for version 0.30.0
🔦 Spotlight
Memory-efficient masks with sv.CompactMask

Segmentation models produce one full-resolution bitmap per instance. On a 1920×1080 image with 28 detections that is ~55 MB of mask data. Most pixels are background. sv.CompactMask stores only the tight bounding-box crop, RLE-encoded — the same 28 masks drop to ~237 KB of crops, a 240× reduction before RLE kicks in.

It's a drop-in replacement: annotators, filters, and area all work unchanged.

import supervision as sv

# any segmentation model — RF-DETR Seg, YOLO-Seg, SAM3
detections = model.predict(image)  # sv.Detections with dense masks

dense_mb = detections.mask.nbytes / 1024 / 1024
compact = sv.CompactMask.from_dense(
    masks=detections.mask,
    xyxy=detections.xyxy,
    image_shape=image.shape[:2],
)
detections.mask = compact  # swap in — API unchanged

# filter by pixel area without materialising dense masks
large = detections[compact.area > 1000]

# annotators call .to_dense() internally
annotated = sv.MaskAnnotator().annotate(image.copy(), detections)

SAM3 text-prompted segmentation

SAM3 segments objects by free-text prompt — no class list, no bounding boxes. sv.Detections.from_sam3() parses both PCS (multi-prompt) and PVS (video) response formats into a standard sv.Detections, with class_id set to the prompt index.

import requests, base64
import supervision as sv

PROMPTS = ["person", "bag"]

with open("image.jpg", "rb") as f:
    img_b64 = base64.b64encode(f.read()).decode()

response = requests.post(
    f"https://api.roboflow.com/inferenceproxy/seg-preview?api_key={API_KEY}",
    json={
        "image": {"type": "base64", "value": img_b64},
        "prompts": [{"type": "text", "text": p} for p in PROMPTS],
    },
    headers={"Content-Type": "application/json"},
)
sam3_result = response.json()

h, w = cv2.imread("image.jpg").shape[:2]
detections = sv.Detections.from_sam3(sam3_result=sam3_result, resolution_wh=(w, h))
# class_id == 0 → "person", class_id == 1 → "bag"

🔄 Migration
VideoInfo.fps is now float

NTSC frame rates (23.976, 29.97, 59.94) were silently truncated. fps is now the true float — cast at call sites that need an integer.

info = sv.VideoInfo.from_video_path("clip.mp4")
buf = collections.deque(maxlen=info.fps)
trace = sv.TraceAnnotator(trace_length=info.fps)
info = sv.VideoInfo.from_video_path("clip.mp4")
buf = collections.deque(maxlen=int(info.fps))
trace = sv.TraceAnnotator(trace_length=int(info.fps))
sv.ByteTrack deprecated — use ByteTrackTracker

Tracker implementations now live in the dedicated trackers package. sv.ByteTrack remains available in 0.28–0.29 with DeprecationWarning; removal in 0.30.0.

tracker = sv.ByteTrack()
detections = tracker.update_with_detections(detections)
# pip install trackers
from trackers import ByteTrackTracker

tracker = ByteTrackTracker()
detections = tracker.update(detections)

🚀 Added
  • Memory-efficient masks with sv.CompactMask. Sparse segmentation masks are now stored as a crop region plus RLE-encoded data instead of full-resolution bitmaps, cutting memory use by 10–100× for typical instance-segmentation outputs. It's a drop-in change — sv.Detections.mask, filtering, merging, and area all keep working without materialising the full array. (#2159)

  • SAM3 detection and PVS support in from_inference. sv.Detections.from_inference now parses SAM3 detection and point-video-segmentation outputs, both from the local inference package and from Roboflow-hosted server responses. (#2103, #2152)

  • Compressed COCO RLE masks in from_inference. Inference responses with rle or rle_mask fields containing a compressed counts string (as produced by pycocotools) are decoded directly into binary masks, skipping the lossy polygon round-trip. (#2178)

  • Standard logging module instead of print. Diagnostic output is now emitted under the supervision logger, so applications can capture, filter, or silence it through standard logging configuration. (#2154)

  • RGBA hex codes in sv.Color. sv.Color.from_hex accepts 8-digit hex (#ff00ff80), and Color.as_hex() round-trips alpha when not fully opaque. New top-level helpers: sv.hex_to_rgba, sv.rgba_to_hex, and sv.is_valid_hex. (#2004)

  • Dynamic kernel sizing in blur and pixelate annotators. BlurAnnotator(kernel_size=None) and PixelateAnnotator(pixel_size=None) (the new default) compute the kernel per detection as a fraction of the shorter bounding-box side, giving visually consistent results across object scales. (#709)

  • sv.ImageAssets for sample images. A counterpart to the existing video assets — downloads sample images for examples and tutorials. (#932)

  • Boundary warnings in InferenceSlicer. Emits a warning when callback detections fall outside tile boundaries, helping you spot coordinate-system bugs in custom callbacks early. (#2186)

⚠️ Breaking Changes
  • sv.VideoInfo.fps is now float, not int. Frame rates like 23.976, 29.97, and 59.94 are no longer truncated. If you pass fps to APIs that require an integer (deque(maxlen=...), TraceAnnotator(trace_length=...)), wrap with int(...). (#2210)

  • sv.rle_to_mask returns bool, not uint8. This matches the long-declared signature. Code that does mask * 255 still works via NumPy broadcasting, but explicit casts like mask.view(np.uint8) will break. Add .astype(np.uint8) if you relied on the undocumented integer output. (#2178)

See the migration guide below for before/after snippets.

🌱 Changed
  • Metric arrays use float32 instead of float64. sv.MeanAveragePrecisionResult and related arrays (mAP_scores, ap_per_class, iou_thresholds, precision/recall) drop to float32, reducing memory and speeding up computation. Numerical results may differ in the last few digits. (#2169)

  • rle_to_mask and mask_to_rle moved. New canonical path: supervision.detection.utils.converters. The old supervision.dataset.utils import still works but is deprecated. (#2178)

🗑️ Deprecated
  • normalized_xyxy argument renamed to xyxy in denormalize_boxes. sv.denormalize_boxes(normalized_xyxy=...) still works but emits a FutureWarning; switch to xyxy=. Scheduled for removal in 0.30.0.

  • sv.ByteTrackByteTrackTracker (external trackers package). Install with pip install trackers; the method renames from update_with_detections() to update(). Scheduled for removal in 0.30.0. (#2215)

  • supervision.keypointsupervision.key_points. Also deprecated: the LMM enum (use VLM), from_lmm (use from_vlm), create_tiles in supervision.utils.image, ensure_cv2_image_for_processing in supervision.utils.conversion, and the keypoint validators in supervision.validators. (#2214)

🔧 Fixed
  • PolygonZone no longer double-counts overlapping zones. When two polygons contain the same anchor, each zone now reflects its own containment instead of every zone claiming the detection. (#1991)

  • LineZone respects class identity across reused tracker IDs. Trackers that recycle tracker_id across classes no longer leak crossing state from one object to another. (#1868)

  • process_video raises immediately on callback errors. Previously the exception was swallowed and the process hung until the writer was flushed. (#2022)

  • DetectionDataset populates class_name. Loaded annotations now carry data["class_name"], matching what model connectors produce. (#2156)

  • ByteTrack preserves externally assigned tracker_id. No longer overwrites caller-assigned IDs on the first update. (#1364)

  • Confusion matrix double-counting fixed. evaluate_detection_batch now correctly matches multiple predictions to the same target, so FP/FN counts match expectations. (#1853)

  • MeanAverageRecall mAR@K is now COCO-compliant. Computed using top-K detections per image; previous values were inflated relative to pycocotools. (#2136)

  • Detections.is_empty() handles empty tracker_id. Returns True for zero-row detections regardless of whether tracker_id is None or an empty array. (#2209)

  • CSVSink and JSONSink slice custom_data per row. NumPy arrays, lists, and tuples whose length matches the detection count are now indexed per row, instead of being written whole for every detection. (#2199, #2216)

  • TraceAnnotator smooth mode handles stationary tracks. Deduplicates anchor points and falls back to a raw polyline when splprep cannot fit fewer than 4 unique points. (#2217)

  • load_coco_annotations rejects path-traversal annotations. Refuses file_name entries that escape the images directory via ../ or absolute paths. (#2218)

  • OBB datasets no longer blow up memory. Loading oriented-bounding-box datasets stopped allocating full-image masks per box. (#2187)

  • KeyPoints boolean mask indexing fixed. Uniform-count selection now works correctly when all instances share the same keypoint count. (#2188)

  • DetectionDataset.as_coco() preserves area and iscrowd. No longer dropped silently in the round-trip. (#2185)

  • force_mask=True precision and COCO empty-polygon export. Annotation conversion no longer loses precision, and COCO export tolerates empty polygons across formats. (#1746, #1086, #265)


🏆 Contributors

A huge thank you to everyone who shipped this release:

  • @Erol444 — SAM3 detection and PVS parsing
  • @leeclemnet (LinkedIn) — compressed COCO RLE masks and rle_to_mask correctness
  • @abritton2002 — VideoInfo.fps as float and Detections.is_empty() fix
  • @shaun0927 (LinkedIn) — sink slicing, trace annotator, COCO path-traversal hardening
  • @happyhj (LinkedIn) — class_name in DetectionDataset
  • @farukalamai (LinkedIn) — CSVSink NumPy slicing
  • @stop1one (LinkedIn) — COCO-compliant MeanAverageRecall
  • @Adithi-Sreenath (LinkedIn) — PolygonZone overlap fix
  • @JESUSROYETH — LineZone class-aware tracker IDs
  • @realh4m — process_video error propagation
  • @rolson24 (LinkedIn) — ByteTrack preserves external tracker IDs
  • @panagiotamoraiti (LinkedIn) — confusion matrix correctness
  • @Youho99, @kirilllzaitsev — COCO empty polygons and force_masks consistency
  • @aza-ali — RGBA hex support in sv.Color
  • @Clemens-E — dynamic kernel sizing for blur and pixelate annotators
  • @NickHerrig (LinkedIn) — sv.ImageAssets
  • @0xD4rky — force_mask=True precision fix
  • @Borda (LinkedIn) — CompactMask, metrics float32, deprecations

Full changelog: https://github.com/roboflow/supervision/compare/0.27.0...0.28.0

0.27.0

supervision-0.27.0

Added
  • Added sv.filter_segments_by_distance to keep the largest connected component and any nearby components within an absolute or relative distance threshold
  • Added sv.edit_distance for Levenshtein distance between two strings supporting insert, delete, and substitute operations
  • Added sv.fuzzy_match_index to find the first close match in a list using edit distance
  • Added sv.get_image_resolution_wh as a unified way to read image width and height from NumPy and PIL inputs
  • Added sv.tint_image to apply a solid color overlay to an image at a specified opacity, supporting both NumPy and PIL inputs
  • Added sv.grayscale_image to convert an image to 3-channel grayscale for compatibility with color-based drawing utilities
  • Added sv.xyxy_to_mask to convert bounding boxes into 2D boolean masks
Changed
  • Added Qwen3-VL support in sv.Detections.from_vlm and legacy from_lmm mapping
  • Added DeepSeek-VL2 support in sv.Detections.from_vlm and legacy from_lmm mapping
  • Improved sv.Detections.from_vlm parsing for Qwen 2.5 VL outputs to handle incomplete or truncated JSON responses
  • sv.InferenceSlicer now uses new offset generation logic that removes redundant tiles and ensures clean border aligned slicing, reducing the number of tiles processed and lowering inference time

Description

🚀 Added
  • Added sv.filter_segments_by_distance to keep the largest connected component and any nearby components within an absolute or relative distance threshold. This helps you clean up predictions from segmentation models like SAM, SAM2, YOLO segmentation, and RF-DETR segmentation. (#2008)

https://github.com/user-attachments/assets/2bdfd45d-b235-414b-91a3-6544d7c2b4ec

  • Added sv.edit_distance for Levenshtein distance between two strings. Supports insert, delete, substitute. (#1912)

    import supervision as sv
    
    sv.edit_distance("hello", "hello")
    # 0
    
    sv.edit_distance("hello world", "helloworld")
    # 1
    
    sv.edit_distance("YOLO", "yolo", case_sensitive=True)
    # 4
    
  • Added sv.fuzzy_match_index to find the first close match in a list using edit distance. (#1912)

    import supervision as sv
    
    sv.fuzzy_match_index(["cat", "dog", "rat"], "dat", threshold=1)
    # 0
    
    sv.fuzzy_match_index(["alpha", "beta", "gamma"], "bata", threshold=1)
    # 1
    
    sv.fuzzy_match_index(["one", "two", "three"], "ten", threshold=2)
    # None
    
  • Added sv.get_image_resolution_wh as a unified way to read image width and height from NumPy and PIL inputs. (#2014)

  • Added sv.tint_image to apply a solid color overlay to an image at a specified opacity. Works with both NumPy and PIL inputs. (#1943)

  • Added sv.grayscale_image to convert an image to 3-channel grayscale for compatibility with color-based drawing utilities. (#1943)

  • Added sv.xyxy_to_mask to convert bounding boxes into 2D boolean masks. Each mask corresponds to one bounding box. (#2006)

🌱 Changed
  • Added Qwen3-VL support in sv.Detections.from_vlm and legacy from_lmm mapping. Use vlm=sv.QWEN_3_VL. (#2015)

    import supervision as sv
    
    response = """```json
    [
        {"bbox_2d": [220, 102, 341, 206], "label": "taxi"},
        {"bbox_2d": [30, 606, 171, 743], "label": "taxi"},
        {"bbox_2d": [192, 451, 318, 581], "label": "taxi"},
        {"bbox_2d": [358, 908, 506, 1000], "label": "taxi"},
        {"bbox_2d": [735, 359, 873, 480], "label": "taxi"},
        {"bbox_2d": [758, 508, 885, 617], "label": "taxi"},
        {"bbox_2d": [857, 263, 988, 374], "label": "taxi"},
        {"bbox_2d": [735, 243, 838, 351], "label": "taxi"},
        {"bbox_2d": [303, 291, 434, 417], "label": "taxi"},
        {"bbox_2d": [426, 273, 552, 382], "label": "taxi"}
    ]
    ```"""
    
    detections = sv.Detections.from_vlm(
        vlm=sv.VLM.QWEN_3_VL,
        result=response,
        resolution_wh=(1023, 682)
    )
    
    detections.xyxy
    # array([[ 225.06 ,   69.564,  348.843,  140.492],
    #        [  30.69 ,  413.292,  174.933,  506.726],
    #        [ 196.416,  307.582,  325.314,  396.242],
    #        [ 366.234,  619.256,  517.638,  682.   ],
    #        [ 751.905,  244.838,  893.079,  327.36 ],
    #        [ 775.434,  346.456,  905.355,  420.794],
    #        [ 876.711,  179.366, 1010.724,  255.068],
    #        [ 751.905,  165.726,  857.274,  239.382],
    #        [ 309.969,  198.462,  443.982,  284.394],
    #        [ 435.798,  186.186,  564.696,  260.524]])
    
  • Added DeepSeek-VL2 support in sv.Detections.from_vlm and legacy from_lmm mapping. Use vlm=sv.VLM.DEEPSEEK_VL_2. (#1884)

  • Improved sv.Detections.from_vlm parsing for Qwen 2.5 VL outputs. The function now handles incomplete or truncated JSON responses. (#2015)

  • sv.InferenceSlicer now uses a new offset generation logic that removes redundant tiles and ensures clean border aligned slicing. This reduces the number of tiles processed, lowering inference time without hurting detection quality. (#2014)

https://github.com/user-attachments/assets/0141ff44-0269-472c-900d-610f47330d57

import supervision as sv
from PIL import Image
from rfdetr import RFDETRMedium

model = RFDETRMedium()

def callback(tile):
    return model.predict(tile)

slicer = sv.InferenceSlicer(callback, slice_wh=512, overlap_wh=128)

image = Image.open("example.png")
detections = slicer(image)
  • sv.Detections now includes a box_aspect_ratio property for vectorized aspect ratio computation. You use it to filter detections based on box shape. (#2016)
import numpy as np
import supervision as sv

xyxy = np.array([
    [10, 10, 50, 50],
    [60, 10, 180, 50],
    [10, 60, 50, 180],
])

detections = sv.Detections(xyxy=xyxy)

ar = detections.box_aspect_ratio
# array([1.0, 3.0, 0.33333333])

detections[(ar < 2.0) & (ar > 0.5)].xyxy
# array([[10., 10., 50., 50.]])
  • Improved the performance of sv.box_iou_batch. Processing runs about 2x to 5x faster. (#2001)

  • sv.process_video now uses a threaded reader, processor, and writer pipeline. This removes I/O stalls and improves throughput while keeping the callback single threaded and safe for stateful models. (#1997)

  • sv.denormalize_boxes now supports batch conversion of bounding boxes. The function now accepts arrays of shape (N, 4) and returns a batch of absolute pixel coordinates.

  • sv.LabelAnnotator and sv.RichLabelAnnotator now accepts text_offset=(x, y) to shift the label relative to text_position. Works with smart label position and line wrapping. (#1917)

❌ Removed
  • Removed the deprecated overlap_ratio_wh argument from sv.InferenceSlicer. Use the pixel based overlap_wh argument to control slice overlap. (#2014)

[!TIP] Convert your old ratio based overlap to pixel based overlap. Multiply each ratio by the slice dimensions.

# before

slice_wh = (640, 640)
overlap_ratio_wh = (0.25, 0.25)

slicer = sv.InferenceSlicer(
    callback=callback,
    slice_wh=slice_wh,
    overlap_ratio_wh=overlap_ratio_wh,
    overlap_filter=sv.OverlapFilter.NON_MAX_SUPPRESSION,
)

# after

overlap_wh = (
    int(overlap_ratio_wh[0] * slice_wh[0]),
    int(overlap_ratio_wh[1] * slice_wh[1]),
)

slicer = sv.InferenceSlicer(
    callback=callback,
    slice_wh=slice_wh,
    overlap_wh=overlap_wh,
    overlap_filter=sv.OverlapFilter.NON_MAX_SUPPRESSION,
)
🏆 Contributors

@SkalskiP (Piotr Skalski), @onuralpszr (Onuralp SEZER), @soumik12345 (Soumik Rakshit), @rcvsq, @AlexBodner (Alex Bodner), @Ashp116, @kshitijaucharmal (Kshitij Aucharmal), @ernestlwt, @AnonymDevOSS, @jackiehimel (Jackie Himel ), @dominikWin (Dominik Winecki)

0.26.1

supervision-0.26.1

Fixed
  • Fixed error in sv.MeanAveragePrecision where the area used for size-specific evaluation (small / medium / large) was always zero unless explicitly provided in sv.Detections.data
  • Fixed ID=0 bug in sv.MeanAveragePrecision where objects were getting 0.0 mAP despite perfect IoU matches due to a bug in annotation ID assignment
  • Fixed issue where sv.MeanAveragePrecision could return negative values when certain object size categories have no data
  • Fixed match_metric support for sv.Detections.with_nms
  • Fixed border_thickness parameter usage for sv.PercentageBarAnnotator
🔧 Fixed
🏆 Contributors

@balthazur (Balthasar Huber), @onuralpszr (Onuralp SEZER), @rafaelpadilla (Rafael Padilla), @soumik12345 (Soumik Rakshit), @SkalskiP (Piotr Skalski)

0.26.0

supervision-0.26.0

Added
  • Add support for creating sv.KeyPoints objects from ViTPose and ViTPose++ inference results via sv.KeyPoints.from_transformers
  • Add support for the IOS (Intersection over Smallest) overlap metric in sv.Detections.with_nms, sv.Detections.with_nmm, sv.box_iou_batch, and sv.mask_iou_batch
  • Add sv.box_iou function to efficiently compute the Intersection over Union between two individual bounding boxes
  • Add support for frame limitations and progress bar in sv.process_video
  • Add sv.xyxy_to_xcycarh function to convert bounding box coordinates from (x_min, y_min, x_max, y_max) to (center x, center y, aspect ratio, height) format
  • Add sv.xyxy_to_xywh function to convert bounding box coordinates from (x_min, y_min, x_max, y_max) format to (x, y, width, height) format
Changed
  • Upgrade all code to Python 3.9 syntax style
  • sv.LabelAnnotator now supports the smart_position parameter to automatically keep labels within frame boundaries and the max_line_length parameter to control text wrapping
  • sv.LabelAnnotator now supports non-string labels
  • sv.Detections.from_vlm now supports parsing bounding boxes and segmentation masks from responses generated by Google Gemini models
Removed
  • Drop Python 3.8 support

[!WARNING]
supervision-0.26.0 drops python3.8 support and upgrade all codes to python3.9 syntax style.

[!TIP] Our docs page now has a fresh look that is consistent with the documentations of all Roboflow open-source projects. (#1858)

🚀 Added
  • Added support for creating sv.KeyPoints objects from ViTPose and ViTPose++ inference results via sv.KeyPoints.from_transformers. (#1788)

    https://github.com/user-attachments/assets/f1917032-29d8-4b88-b871-65c2e28a756e

  • Added support for the IOS (Intersection over Smallest) overlap metric that measures how much of the smaller object is covered by the larger one in sv.Detections.with_nms, sv.Detections.with_nmm, sv.box_iou_batch, and sv.mask_iou_batch. (#1774)

    import numpy as np
    import supervision as sv
    
    boxes_true = np.array([
        [100, 100, 200, 200],
        [300, 300, 400, 400]
    ])
    boxes_detection = np.array([
        [150, 150, 250, 250],
        [320, 320, 420, 420]
    ])
    
    sv.box_iou_batch(
        boxes_true=boxes_true, 
        boxes_detection=boxes_detection, 
        overlap_metric=sv.OverlapMetric.IOU
    )
    
    # array([[0.14285714, 0.        ],
    #        [0.        , 0.47058824]])
    
    sv.box_iou_batch(
        boxes_true=boxes_true, 
        boxes_detection=boxes_detection, 
        overlap_metric=sv.OverlapMetric.IOS
    )
    
    # array([[0.25, 0.  ],
    #        [0.  , 0.64]])
    
  • Added sv.box_iou that efficiently computes the Intersection over Union (IoU) between two individual bounding boxes. (#1874)

  • Added support for frame limitations and progress bar in sv.process_video. (#1816)

  • Added sv.xyxy_to_xcycarh function to convert bounding box coordinates from (x_min, y_min, x_max, y_max) into measurement space to format (center x, center y, aspect ratio, height), where the aspect ratio is width / height. (#1823)

  • Added sv.xyxy_to_xywh function to convert bounding box coordinates from (x_min, y_min, x_max, y_max) format to (x, y, width, height) format. (#1788)

🌱 Changed
  • sv.LabelAnnotator now supports the smart_position parameter to automatically keep labels within frame boundaries, and the max_line_length parameter to control text wrapping for long or multi-line labels. (#1820)

    https://github.com/user-attachments/assets/361c17c7-0810-466d-907d-c752e91bc6f7

  • sv.LabelAnnotator now supports non-string labels. (#1825)

  • sv.Detections.from_vlm now supports parsing bounding boxes and segmentation masks from responses generated by Google Gemini models. You can test Gemini prompting, result parsing, and visualization with Supervision using this example notebook. (#1792)

     import supervision as sv
    
     gemini_response_text = """```json
         [
             {"box_2d": [543, 40, 728, 200], "label": "cat", "id": 1},
             {"box_2d": [653, 352, 820, 522], "label": "dog", "id": 2}
         ]
     ```"""
    
     detections = sv.Detections.from_vlm(
         sv.VLM.GOOGLE_GEMINI_2_5,
         gemini_response_text,
         resolution_wh=(1000, 1000),
         classes=['cat', 'dog'],
     )
    
     detections.xyxy
     # array([[543., 40., 728., 200.], [653., 352., 820., 522.]])
     
     detections.data
     # {'class_name': array(['cat', 'dog'], dtype='<U26')}
     
     detections.class_id
     # array([0, 1])
    
  • sv.Detections.from_vlm now supports parsing bounding boxes from responses generated by Moondream. (#1878)

    import supervision as sv
    
    moondream_result = {
        'objects': [
            {
                'x_min': 0.5704046934843063,
                'y_min': 0.20069346576929092,
                'x_max': 0.7049859315156937,
                'y_max': 0.3012596592307091
            },
            {
                'x_min': 0.6210969910025597,
                'y_min': 0.3300672620534897,
                'x_max': 0.8417936339974403,
                'y_max': 0.4961046129465103
            }
        ]
    }
    
    detections = sv.Detections.from_vlm(
        sv.VLM.MOONDREAM,
        moondream_result,
        resolution_wh=(1000, 1000),
    )
    
    detections.xyxy
    # array([[1752.28,  818.82, 2165.72, 1229.14],
    #        [1908.01, 1346.67, 2585.99, 2024.11]])
    
  • sv.Detections.from_vlm now supports parsing bounding boxes from responses generated by Qwen-2.5 VL. You can test Qwen2.5-VL prompting, result parsing, and visualization with Supervision using this example notebook. (#1709)

    import supervision as sv
    
    qwen_2_5_vl_result = """```json
    [
        {"bbox_2d": [139, 768, 315, 954], "label": "cat"},
        {"bbox_2d": [366, 679, 536, 849], "label": "dog"}
    ]
    ```"""
    
    detections = sv.Detections.from_vlm(
        sv.VLM.QWEN_2_5_VL,
        qwen_2_5_vl_result,
        input_wh=(1000, 1000),
        resolution_wh=(1000, 1000),
        classes=['cat', 'dog'],
    )
    
    detections.xyxy
    # array([[139., 768., 315., 954.], [366., 679., 536., 849.]])
    
    detections.class_id
    # array([0, 1])
    
    detections.data
    # {'class_name': array(['cat', 'dog'], dtype='<U10')}
    
    detections.class_id
    # array([0, 1])
    
  • Significantly improved the speed of HSV color mapping in sv.HeatMapAnnotator, achieving approximately 28x faster performance on 1920x1080 frames. (#1786)

🔧 Fixed
  • Supervision’s sv.MeanAveragePrecision is now fully aligned with pycocotools, the official COCO evaluation tool, ensuring accurate and standardized metrics. (#1834)

    import supervision as sv
    from supervision.metrics import MeanAveragePrecision
    
    predictions = sv.Detections(...)
    targets = sv.Detections(...)
    
    map_metric = MeanAveragePrecision()
    map_metric.update(predictions, targets).compute()
    
    # Average Precision (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.464
    # Average Precision (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 0.637
    # Average Precision (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 0.203
    # Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.284
    # Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.497
    # Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.629
    

[!TIP] The updated mAP implementation enabled us to build an updated version of the Computer Vision Model Leaderboard.

  • Fix #1767: Fixed losing sv.Detections.data when detections filtering.
⚠️ Deprecated
❌ Removed
  • The sv.DetectionDataset.images property has been removed in supervision-0.26.0. Please loop over images with for path, image, annotation in dataset:, as that does not require loading all images into memory.
  • Cconstructing sv.DetectionDataset with parameter images as Dict[str, np.ndarray] is deprecated and has been removed in supervision-0.26.0. Please pass a list of paths List[str] instead.
  • The name sv.BoundingBoxAnnotator is deprecated and has been removed in supervision-0.26.0. It has been renamed to sv.BoxAnnotator.
🏆 Contributors

@onuralpszr (Onuralp SEZER), @SkalskiP (Piotr Skalski), @SunHao-AI (Hao Sun), @rafaelpadilla Rafael Padilla, @Ashp116 (Ashp116), @capjamesg (James Gallagher), @blakeburch (Blake Burch), @hidara2000 (hidara2000), @Armaggheddon (Alessandro Brunello), @soumik12345 (Soumik Rakshit).

0.25.0

supervision-0.25.0

Added
  • Add `minimum_crossing_threshold` argument to `LineZone` to confirm crossings over multiple frames and reduce false positives from jittering detections
  • Enable tracking of objects detected as `KeyPoints` by converting them to `Detections`
  • Add `is_empty` method to `KeyPoints` to check if there are any keypoints in the object
  • Add `as_detections` method to `KeyPoints` to convert `KeyPoints` to `Detections`
  • Add new skiing video asset to supervision[assets]
  • Support Python 3.13 compatibility including execution without Global Interpreter Lock (GIL)
  • Add `Mean Average Recall` (mAR) metric that returns a recall score averaged over IoU thresholds, detected object classes, and limits on maximum considered detections
  • Add `Precision` metric for comparing model outputs to ground truth or another model
  • Add `Recall` metric for comparing model outputs to ground truth or another model
Changed
  • All Metrics now support Oriented Bounding Boxes (OBB)

Supervision 0.25.0 is here! Featuring a more robust LineZone crossing counter, support for tracking KeyPoints, Python 3.13 compatibility, and 3 new metrics: Precision, Recall and Mean Average Recall. The update also includes smart label positioning, improved Oriented Bounding Box support, and refined error handling. Thank you to all contributors - especially those who answered the call of Hacktoberfest!

Changelog

🚀 Added
  • Essential update to the LineZone: when computing line crossings, detections that jitter might be counted twice (or more!). This can now be solved with the minimum_crossing_threshold argument. If you set it to 2 or more, extra frames will be used to confirm the crossing, improving the accuracy significantly. (#1540)

https://github.com/user-attachments/assets/89ca2ee6-93c9-41e6-a432-e16c4c69c695

import numpy as np
import supervision as sv
from ultralytics import YOLO

model = YOLO("yolov8m-pose.pt")
tracker = sv.ByteTrack()
trace_annotator = sv.TraceAnnotator()

def callback(frame: np.ndarray, _: int) -> np.ndarray:
    results = model(frame)[0]
    key_points = sv.KeyPoints.from_ultralytics(results)

    detections = key_points.as_detections()
    detections = tracker.update_with_detections(detections)

    annotated_image = trace_annotator.annotate(frame.copy(), detections)
    return annotated_image

sv.process_video(
    source_path="input_video.mp4",
    target_path="output_video.mp4",
    callback=callback
)

https://github.com/user-attachments/assets/4c3bdf54-391e-4633-9164-f15878ddfb33

See the guide for the full code used to make the video

  • Added is_empty method to KeyPoints to check if there are any keypoints in the object. (#1658)

  • Added as_detections method to KeyPoints that converts KeyPoints to Detections. (#1658)

  • Added a new video to supervision[assets]. (#1657)

from supervision.assets import download_assets, VideoAssets

path_to_video = download_assets(VideoAssets.SKIING)
  • Supervision can now be used with Python 3.13. The most renowned update is the ability to run Python without Global Interpreter Lock (GIL). We expect support for this among our dependencies to be inconsistent, but if you do attempt it - let us know the results! (#1595)

py3-13

  • Added Mean Average Recall mAR metric, which returns a recall score, averaged over IoU thresholds, detected object classes, and limits imposed on maximum considered detections. (#1661)
import supervision as sv
from supervision.metrics import MeanAverageRecall

predictions = sv.Detections(...)
targets = sv.Detections(...)

map_metric = MeanAverageRecall()
map_result = map_metric.update(predictions, targets).compute()

map_result.plot()

mAR_plot_example

  • Added Precision and Recall metrics, providing a baseline for comparing model outputs to ground truth or another model (#1609)
import supervision as sv
from supervision.metrics import Recall

predictions = sv.Detections(...)
targets = sv.Detections(...)

recall_metric = Recall()
recall_result = recall_metric.update(predictions, targets).compute()

recall_result.plot()

recall-plot

  • All Metrics now support Oriented Bounding Boxes (OBB) (#1593)
import supervision as sv
from supervision.metrics import F1_Score

predictions = sv.Detections(...)
targets = sv.Detections(...)

f1_metric = MeanAverageRecall(metric_target=sv.MetricTarget.ORIENTED_BOUNDING_BOXES)
f1_result = f1_metric.update(predictions, targets).compute()

OBB example

import supervision as sv
from ultralytics import YOLO

image = cv2.imread("image.jpg")

label_annotator = sv.LabelAnnotator(smart_position=True)

model = YOLO("yolo11m.pt")
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)

annotated_frame = label_annotator.annotate(first_frame.copy(), detections)
sv.plot_image(annotated_frame)

https://github.com/user-attachments/assets/ef768db4-867d-4305-b905-80e690bb1ea7

  • Added the metadata variable to Detections. It allows you to store custom data per-image, rather than per-detected-object as was possible with data variable. For example, metadata could be used to store the source video path, camera model or camera parameters. (#1589)
import supervision as sv
from ultralytics import YOLO

model = YOLO("yolov8m")

result = model("image.png")[0]
detections = sv.Detections.from_ultralytics(result)

# Items in `data` must match length of detections
object_ids = [num for num in range(len(detections))]
detections.data["object_number"] = object_ids

# Items in `metadata` can be of any length.
detections.metadata["camera_model"] = "Luxonis OAK-D"
  • Added a py.typed type hints metafile. It should provide a stronger signal to type annotators and IDEs that type support is available. (#1586)
🌱 Changed
  • ByteTrack no longer requires detections to have a class_id (#1637)
  • draw_line, draw_rectangle, draw_filled_rectangle, draw_polygon, draw_filled_polygon and PolygonZoneAnnotator now comes with a default color (#1591)
  • Dataset classes are treated as case-sensitive when merging multiple datasets. (#1643)
  • Expanded metrics documentation with example plots and printed results (#1660)
  • Added usage example for polygon zone (#1608)
  • Small improvements to error handling in polygons: (#1602)
🔧 Fixed
  • Updated ByteTrack, removing shared variables. Previously, multiple instances of ByteTrack would share some date, requiring liberal use of tracker.reset(). (#1603), (#1528)
  • Fixed a bug where class_agnostic setting in MeanAveragePrecision would not work. (#1577) hacktoberfest
  • Removed welcome workflow from our CI system. (#1596)
✅ No removals or deprecations this time!
⚙️ Internal Changes
  • Large refactor of ByteTrack (#1603)
    • STrack moved to separate class
    • Remove superfluous BaseTrack class
    • Removed unused variables
  • Large refactor of RichLabelAnnotator, matching its contents with LabelAnnotator. (#1625)

🏆 Contributors

@onuralpszr (Onuralp SEZER), @kshitijaucharmal (KshitijAucharmal), @grzegorz-roboflow (Grzegorz Klimaszewski), @Kadermiyanyedi (Kader Miyanyedi), @PrakharJain1509 (Prakhar Jain), @DivyaVijay1234 (Divya Vijay), @souhhmm (Soham Kalburgi), @joaomarcoscrs (João Marcos Cardoso Ramos da Silva), @AHuzail (Ahmad Huzail Khan), @DemyCode (DemyCode), @ablazejuk (Andrey Blazejuk), @LinasKo (Linas Kondrackis)

A special thanks goes out to everyone who joined us for Hacktoberfest! We hope it was a rewarding experience and look forward to seeing you continue contributing and growing with our community. Keep building, keep innovating—your efforts make a difference! 🚀