Skip to content

Releases: roboflow/supervision

supervision-0.29.1

Choose a tag to compare

@Borda Borda released this 23 Jun 19:54

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.

feat-keypoints-with-nms-compressed.mp4

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: 0.29.0...0.29.1

supervision-0.29.0.post

Choose a tag to compare

@Borda Borda released this 17 Jun 16:05

Full Changelog: 0.29.0...0.29.0.post0

supervision-0.29.0

Choose a tag to compare

@SkalskiP SkalskiP released this 15 Jun 21:45

πŸš€ Added

  • Added sv.VertexEllipseAreaAnnotator, sv.VertexEllipseOutlineAnnotator, and sv.VertexEllipseHaloAnnotator for visualizing keypoint uncertainty as covariance ellipses. (#2277, #2286)

    import cv2
    import supervision as sv
    from rfdetr import RFDETRKeypointPreview
    
    image = cv2.imread("<SOURCE_IMAGE_PATH>")
    model = RFDETRKeypointPreview()
    
    key_points = model.predict(image)
    
    annotator = sv.VertexEllipseAreaAnnotator(
        sigma=[1.0, 2.0, 3.0],
        color=[sv.Color.GREEN, sv.Color.YELLOW, sv.Color.RED],
        opacity=0.4,
    )
    annotated = annotator.annotate(image.copy(), key_points)
    rf-detr-ellipse-promo-2.mp4
    import cv2
    import supervision as sv
    from rfdetr import RFDETRKeypointPreview
    
    image = cv2.imread("<SOURCE_IMAGE_PATH>")
    model = RFDETRKeypointPreview()
    
    key_points = model.predict(image)
    
    annotator = sv.VertexEllipseOutlineAnnotator(
        sigma=[1.0, 2.0, 3.0],
        color=[sv.Color.GREEN, sv.Color.YELLOW, sv.Color.RED],
        thickness=2,
    )
    annotated = annotator.annotate(image.copy(), key_points)
    rf-detr-ellipse-promo-3.mp4
    import cv2
    import supervision as sv
    from rfdetr import RFDETRKeypointPreview
    
    image = cv2.imread("<SOURCE_IMAGE_PATH>")
    model = RFDETRKeypointPreview()
    
    key_points = model.predict(image)
    
    annotator = sv.VertexEllipseHaloAnnotator(
        sigma=[1.0, 2.0, 3.0],
        color=[sv.Color.GREEN, sv.Color.YELLOW, sv.Color.RED],
        opacity=0.6,
    )
    annotated = annotator.annotate(image.copy(), key_points)
    rf-detr-ellipse-promo-1.mp4
  • Added sv.oriented_box_non_max_suppression and sv.oriented_box_non_max_merge for performing NMS and NMM directly on oriented bounding boxes. (#2303)

  • Added OBB (Oriented Bounding Box) support to sv.ConfusionMatrix via MetricTarget.ORIENTED_BOUNDING_BOXES. (#2247)

  • Added preserve_audio parameter to sv.process_video. When enabled, the audio stream from the source video is muxed into the output using ffmpeg. (#2252)

  • Added is_obb parameter to sv.DetectionDataset.as_yolo for exporting oriented bounding box annotations in the YOLO OBB format (9-token lines with 4 corner coordinates). (#2302, #2289)

🌱 Changed

box_nms_demo box_nmm_demo obb_nms_demo obb_nmm_demo
  • 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

Deprecated Removal Replacement
KeyPoints.confidence 0.32.0 KeyPoints.keypoint_confidence
merge_inner_detection_object_pair 0.32.0 None (internal use only)
merge_inner_detections_objects 0.32.0 None (internal use only)
merge_inner_detections_objects_without_iou 0.32.0 None (internal use only)
validate_detections_fields 0.32.0 None (internal use only)
validate_vlm_parameters 0.32.0 None (internal use only)
validate_fields_both_defined_or_none 0.32.0 None (internal use only)
validate_xyxy 0.32.0 None (in...
Read more

[RC] supervision

[RC] supervision Pre-release
Pre-release

Choose a tag to compare

@Borda Borda released this 11 Jun 16:40

Full Changelog: 0.28.0...0.29.0rc0

supervision-0.28.0

Choose a tag to compare

@Borda Borda released this 30 Apr 16:00
87b1b86

πŸ”¦ 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.

supervision-sam3
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.

Before
info = sv.VideoInfo.from_video_path("clip.mp4")
buf = collections.deque(maxlen=info.fps)
trace = sv.TraceAnnotator(trace_length=info.fps)
After
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.

Before
tracker = sv.ByteTrack()
detections = tracker.update_with_detections(detections)
After
# 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.ByteTrack β†’ ByteTrackTracker (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.keypoint β†’ supervision.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...

Read more

supervision-0.27.0.post

Choose a tag to compare

@Borda Borda released this 14 Mar 08:11

Full Changelog: 0.27.0...0.27.0.post2

supervision-0.27.0

Choose a tag to compare

@SkalskiP SkalskiP released this 16 Nov 18:07
a61440e

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)
supervision-0.27.0-filter-segments-by-distance.mp4
  • 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]])
supervision-0 27 0-promo-from-qwen-3-vl
  • 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)

supervision-0.27.0-new-offset-generation-logic.mp4
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)

supervision-0.26.1

Choose a tag to compare

@soumik12345 soumik12345 released this 23 Jul 07:14
7ecfc9f

πŸ”§ Fixed

πŸ† Contributors

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

supervision-0.26.0

Choose a tag to compare

@soumik12345 soumik12345 released this 16 Jul 00:43
d8de58d

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)

    vitpose-plus-large.mp4
  • 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)

    supervision-0.26.0-2.mp4
    Snap (25)
  • 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])
Snap (27)
  • 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 ...
Read more

supervision-0.25.0

Choose a tag to compare

@LinasKo LinasKo released this 12 Nov 22:11
eaeb51a

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)
hb-final.mp4
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
)
track-keypoints-with-smoothing.mp4

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)
fish-z.mp4
  • 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 ([Pra...

Read more