Skip to content

Changelog

Unreleased upcoming

Python 3.9 Support Terminated

With the upcoming supervision-0.30.0 release, we are terminating official support for Python 3.9, which reached end-of-life in October 2025. The minimum supported Python version is now 3.10.

Users on Python 3.9 should upgrade their environment before updating supervision.

Breaking Changes

  • sv.JSONSink now emits native JSON types for numeric and boolean data fields instead of stringified values. Fields previously serialized as "True"/"False", "1"/"0.85", or "400.0" are now true/false, 1/0.85, 400.0. Downstream consumers that compare field values as strings (e.g. row["score"] == "1") or use strict string-typed schema validators must be updated. sv.CSVSink remains textual, but its custom-data slicing now matches sv.JSONSink: NumPy arrays, lists, and tuples are sliced per row only when their length matches the detection count; mismatched-length values are broadcast unchanged (#2400).
  • sv.mask_non_max_merge now computes exact mask overlap at the original mask resolution and ignores the deprecated mask_dimension parameter. Code that relied on downscaled mask overlap should recalibrate thresholds; passing mask_dimension positionally now emits a deprecation warning, and the parameter is scheduled for removal in 0.33.0 (#2400).

Fixed

  • sv.box_iou_batch now upcasts box corners to float64 before computing areas and intersections, returning float32. This fixes integer-dtype overflow (e.g. int32 coordinates around 50_000 could previously wrap to a negative area and produce an incorrect 0.0 IoU) and gives full float64 precision to callers that pass float64/int64 coordinates directly. It does not recover precision already lost when coordinates are stored as float32 before this function is called (e.g. Detections.xyxy, which is float32 throughout the library) — such callers must upcast their own arrays to float64/int64 before calling box_iou_batch to benefit from this fix. Results for small-coordinate inputs are unchanged.
  • Legacy COCO prediction loading in sv.EvaluationDataset.load_predictions now raises ValueError for image ids absent from the ground-truth COCO set instead of relying on a bare assert, so the check is no longer silently skipped under python -O.
  • sv.HeatMapAnnotator now exposes a reset() method to clear accumulated heat, so a single annotator instance can be reused across independent streams without carrying over heat from a previous stream.
  • sv.TraceAnnotator and sv.DetectionsSmoother now expose a reset() method for interface consistency with sv.HeatMapAnnotator.reset(), clearing their accumulated per-track history so a single instance can be reused across independent streams.
  • Fixed #2416: sv.process_video no longer risks hanging during shutdown; the sentinel enqueue is best-effort and worker joins are bounded.
  • Fixed #2416: COCO and CreateML dataset loaders now canonicalize resolved image paths and reject duplicate aliases for the same file.
  • Fixed #2416: DetectionDataset.as_pascal_voc() now preflights image and annotation basename collisions before writing, so exports fail fast instead of producing partial output.
  • import supervision no longer surfaces the deprecated ByteTrack warning; the top-level tracker alias now resolves lazily when accessed explicitly.
  • Fixed dataset export edge cases: DetectionDataset.split() and DetectionDataset.merge() now preserve in-memory image payloads without re-emitting the deprecation warning, and COCO/CreateML exports now reject duplicate image basenames instead of silently collapsing distinct paths into the same output key.
  • Fixed: sv.Color(...) now validates direct RGBA channel values and raises ValueError when any channel falls outside the 0-255 byte range.
  • Fixed: approximate_mask_with_polygons now defaults to no polygon simplification, matching the public dataset export methods.
  • Fixed: ImageSink.save_image() now raises OSError when cv2.imwrite() fails, and deprecation-warning control accepts the correct SUPERVISION_DEPRECATION_WARNING environment variable while still honoring the legacy misspelled alias.
  • sv.Classifications.from_timm now softmaxes model logits before exposing confidence scores, matching sv.Classifications.from_clip and keeping timm confidences on a normalized probability scale. Thresholds calibrated against raw logits may need retuning.
  • sv.download_assets now verifies MD5 hashes after fresh downloads and retries once when the downloaded payload is corrupted instead of accepting a bad file.
  • Fixed metrics scoring edge cases: legacy sv.MeanAveragePrecision now uses COCO 101-point AP averaging, sv.ConfusionMatrix rejects invalid class ids instead of wrapping them through int16/negative indexing, sv.MeanAveragePrecision preserves user-provided target ignore flags, and sv.MeanAverageRecallResult.recall_per_class now exposes per-class recall for each max-detection cutoff.
  • Fixed #2408: sv.Precision, sv.Recall, sv.F1Score, and sv.MeanAverageRecall now score size buckets by filtering targets only while leaving predictions eligible to match bucket targets. This preserves bucket matches that would otherwise be stolen by out-of-bucket filtering and keeps mAR top-K ranking intact.
  • sv.ByteTrack no longer mutates input Detections while assigning tracker IDs. It now keeps detections at the activation-threshold boundary eligible for matching, avoids impossible new-track thresholds above score 1.0, ignores invalid zero-area/non-finite tensor boxes before Kalman updates, and does not emit unconfirmed -1 IDs from first-frame tensor updates.
  • Fixed #2402: sv.KeyPoints.as_detections now accepts NumPy arrays, tuples, and generators in selected_keypoint_indices without ambiguous truth-value errors; empty index iterables select all keypoints. Valid zero-area skeletons are preserved, while all-zero and non-finite-only skeletons are filtered out.
  • Changed: delayed sv.ByteTrack, supervision.keypoint, normalized_xyxy for sv.denormalize_boxes, and supervision.dataset.utils RLE compatibility removals from supervision-0.30.0 to supervision-0.31.0 so the deprecated APIs keep a full transition window.
  • Fixed #2407: sv.ColorPalette.by_idx() now raises a clear ValueError when called on an empty palette instead of leaking a ZeroDivisionError. Non-empty palettes keep the existing index-wrapping behavior.
  • Fixed #2393: sv.CropAnnotator.annotate no longer raises cv2.error when detections extend outside the scene; out-of-bounds boxes are clipped to scene bounds and zero-area results are skipped silently.
  • Fixed #2393: sv.HeatMapAnnotator.annotate no longer blanks the hottest region when the per-pixel hit count exceeds 255; the heat mask is now derived from the float32 accumulator directly, avoiding uint8 wrap-around.
  • Fixed #2393: sv.get_video_frames_generator now releases the underlying cv2.VideoCapture via try/finally, so the decoder is freed when a consumer breaks out of iteration early rather than waiting for garbage collection.
  • Fixed #2382: sv.Detections.get_anchors_coordinates now uses oriented bounding box corners (data["xyxyxyxy"]) when OBB data is present, instead of falling back to the axis-aligned envelope. Anchors on rotated detections now lie on the oriented body rather than drifting to the envelope. Non-OBB detections and Position.CENTER_OF_MASS (which requires a mask) are unaffected.
  • Fixed #2396: sv.BackgroundOverlayAnnotator.annotate no longer leaves detection regions tinted when bounding boxes have negative coordinates (extend outside the left or top scene boundary); boxes are now clipped to scene bounds before the detection region is restored.
  • Fixed: dataset IO/export edge cases now avoid mutating caller-owned Detections during DetectionDataset construction, reject non-integer and out-of-range class ids with a clear ValueError, load COCO annotations that omit optional iscrowd/area fields, expose DetectionDataset.from_coco(use_iscrowd=...) without changing the existing positional show_progress argument, export mask pixel area to COCO when no stored area is present, ignore folder-structure root clutter and non-image files inside class folders, and accept PIL-readable YOLO images such as RGBA or palette PNGs.

Added

  • KeyPoints.merge — combine a list of KeyPoints objects into one, mirroring Detections.merge. Empty inputs are ignored; all non-empty inputs must share the same number of keypoints per skeleton. Completes the merge-then-suppress workflow introduced by KeyPoints.with_nms (#2412)
  • BaseAnnotator.requires_mask — class-level bool flag on all annotators; True for MaskAnnotator, PolygonAnnotator, and HaloAnnotator; False for all others. Integrations can inspect this before materializing expensive mask payloads (#2370)
  • CompactMask.from_coco_rle — efficient COCO RLE ingestion into crop-scoped compact mask format without materializing dense (N, H, W) arrays (#2367)
  • Detections.from_inference(compact_masks=True) — opt-in compact mask representation for Roboflow/Inference segmentation results; masks are cropped to detector bounding boxes (#2367)
  • CompactMask.image_shape — new public property returning (H, W) of the full image the mask is scoped to (#2383)
  • sv.mask_to_roi — explicit exclusive mask-bound helper for NumPy slicing and crop extraction. sv.mask_to_xyxy stays inclusive for compatibility with CompactMask and current box-based adapters, so the coordinate-convention migration path is now explicit instead of implicit.

Changed

  • Performance #2383: sv.Detections.merge() on mixed dense ndarray + CompactMask inputs now returns a CompactMask instead of a dense ndarray. Previously (0.29.0/0.29.1) the mixed path fell back to np.vstack, allocating a full (N, H, W) array; the new path converts dense inputs to CompactMask without materialising the full stack (~2 500× less peak memory, ~13× faster on 1080p / 40 detections). Behavior change: code that checks isinstance(merged.mask, np.ndarray) or calls bare ndarray methods (.astype, .reshape, .ravel) on a mixed-merge result will need to be updated. The all-dense path is unchanged and still returns ndarray.
  • DetectionDataset and ClassificationDataset equality now compare the ordered classes lists directly instead of treating class labels as an unordered set. This keeps equality aligned with class_id indexing semantics, where class position is part of the dataset contract.

0.29.1 Jun 23, 2026

  • Fixed #2353: sv.Detections.from_inference no longer raises TypeError when the Inference package returns a mixed batch where only some predictions carry a tracker_id. detections.tracker_id is None for the full result in that case; fully-tracked and fully-untracked batches are unchanged.

  • Added #2275: show_progress: bool = False parameter to all sv.DetectionDataset load and save methods — from_coco, from_yolo, from_pascal_voc, as_coco, as_yolo, as_pascal_voc, and save_dataset_images. When True, a tqdm.auto progress bar is shown (works in terminal and Jupyter). Defaults to False for full backward compatibility; no new dependencies.

  • Added #2027: sv.InferenceSlicer now accepts an open rasterio-style dataset in addition to in-memory images. Each tile is read lazily via a windowed read instead of loading the whole image, enabling tiled inference on multi-GB aerial/drone GeoTIFFs without running out of memory. Detection is duck-typed, so rasterio stays an optional dependency installable via pip install "supervision[geotiff]" and the core library imports no rasterio symbols. A geographic (non-projected) CRS raises ValueError.

  • Added #2338: sv.KeyPoints.with_nms — non-maximum suppression for keypoint detections. Derives axis-aligned bounding boxes from valid (non-zero and visible) keypoints and applies box_non_max_suppression. Requires detection_confidence; supports class-aware and class-agnostic modes via threshold, class_agnostic, and overlap_metric.

  • Fixed #2342: sv.Detections.from_vlm with sv.VLM.GOOGLE_GEMINI_2_0, sv.VLM.GOOGLE_GEMINI_2_5, and sv.VLM.QWEN_2_5_VL no longer raises when the model returns valid JSON of the wrong shape (non-list top-level or non-dict elements). A non-string or malformed "mask" value in Gemini 2.5 output no longer triggers AttributeError; invalid base64 or non-PNG mask data falls back to an empty mask, keeping xyxy, confidence, and masks arrays aligned.

  • Fixed #2341: sv.DetectionDataset.as_pascal_voc no longer mutates the source Detections.xyxy by the 1-index offset on every call. Previously, repeated exports accumulated a +1 shift in the caller's bounding boxes.

  • Fixed #2334: sv.JSONSink now serializes NumPy scalars (e.g. np.int64 frame indices) in custom_data as JSON numbers instead of raising TypeError at close time. File handle is now guaranteed to close even when serialization fails.

  • Fixed #2333: sv.DetectionsSmoother no longer raises when smoothing detections without confidence. Confidence is now averaged over the frames that carry it; when tracks in the same frame disagree on confidence presence, confidence is set to None for all smoothed detections.

  • Fixed #2332: sv.approximate_polygon now returns a polygon within the requested point-count budget (at most floor(N * (1 - percentage)) points, minimum 3). The function now also validates that epsilon_step > 0.

  • Fixed #2331: sv.Precision and sv.F1Score now count predictions on background images (empty target set) as false positives, and count predictions of classes absent from ground truth as false positives under MICRO and MACRO averaging. Previously both edge cases were silently ignored, inflating scores. WEIGHTED averaging is unchanged — absent classes retain weight 0, consistent with scikit-learn. Users relying on previous scores should re-evaluate after upgrading; no API change is required.

  • Added #2299: DetectionDataset.from_labelme and DetectionDataset.as_labelme for loading and exporting LabelMe per-image JSON annotations, following the existing COCO/YOLO/VOC convention. rectangle shapes load as boxes and polygon shapes as masks; unsupported shape types are skipped with a warning. The mask round-trip is a polygon approximation, not bit-exact.

  • Fixed #2322: COCO export now preserves all polygon parts for multi-component masks. Previously, only the first polygon was written when a non-crowd mask had disjoint segments; all parts are now included.

  • Performance #2339: sv.HaloAnnotator now uses the same CompactMask painting path as sv.MaskAnnotator via a shared _paint_masks_by_area helper. On a 1080p frame with 30 CompactMask detections, HaloAnnotator runs approximately 4× faster; annotated output is unchanged.

  • Added #2284: DetectionDataset.from_createml and DetectionDataset.as_createml add load and export support for the CreateML object-detection JSON format, alongside the existing COCO, YOLO, and Pascal VOC formats.

  • Performance #2330: sv.mask_to_xyxy and sv.KeyPoints.as_detections are now vectorized. mask_to_xyxy uses batched occupancy-profile reductions instead of per-mask pixel scans; KeyPoints.as_detections computes all bounding boxes in a single batch operation. Both produce bit-identical results.

  • Performance #2323: Mask IoU computation now uses matrix multiplication on flattened masks instead of an explicit (N, M, H, W) intersection tensor, reducing peak memory for large mask sets. For masks larger than 4096×4096 pixels, computation automatically promotes to float64 to preserve exact pixel counts. Results are numerically identical.

0.29.0.post0 Jun 17, 2026

  • Fixed #2335: sv.KeyPoints(confidence=...) now works again. The 0.29.0 refactor accidentally dropped the deprecated confidence constructor kwarg; it is now accepted and mapped to keypoint_confidence with a deprecation warning.

0.29.0 Jun 15, 2026

  • Added #2314: new cookbook Oriented Bounding Boxes showing how an oriented box differs from an axis-aligned one on a marina of boats: DOTA-pretrained detection, the effect on with_nms and Detections.area, and YOLO OBB dataset export.

  • Fixed #2306: sv.Detections.area now returns the rotated body's area for detections carrying data["xyxyxyxy"] (oriented box corners) instead of the area of the derived axis-aligned bounding box, which overestimates by up to ~2x at 45° rotation. Affects annotator z-ordering inside MaskAnnotator and HaloAnnotator, and any user code that filters or sorts OBB detections by area. The mask path and the non-OBB AABB fallback are unchanged.

  • Added #2277, #2286: sv.VertexEllipseAreaAnnotator, sv.VertexEllipseOutlineAnnotator, and sv.VertexEllipseHaloAnnotator for visualizing keypoint uncertainty as covariance ellipses. Requires models that output keypoint uncertainty (e.g. RF-DETR keypoint models).

  • Added #2303: sv.oriented_box_non_max_suppression and sv.oriented_box_non_max_merge for performing NMS and NMM directly on oriented bounding boxes using oriented-box IoU instead of axis-aligned IoU.

  • Added #2247: sv.ConfusionMatrix now supports MetricTarget.ORIENTED_BOUNDING_BOXES, computing IoU via oriented_box_iou_batch on xyxyxyxy corners. Previously, OBB inputs silently fell back to axis-aligned bounding-box IoU, producing incorrect match scores for rotated detections.

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

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

  • Added #2312: sv.xyxyxyxy_to_xyxy — vectorised utility that converts oriented bounding box corners (N, 4, 2) to axis-aligned bounding boxes (N, 4).

  • Changed #2286: sv.KeyPoints now separates keypoint-level and detection-level confidence into distinct fields: keypoint_confidence (shape (n, m)) and detection_confidence (shape (n,)). A new visible mask (shape (n, m)) controls per-keypoint visibility. The legacy KeyPoints.confidence property still works but is deprecated.

  • Changed #2286: sv.EdgeAnnotator and sv.VertexAnnotator now respect the visible mask. Invisible keypoints and their edges are skipped during rendering.

  • Changed #2286: sv.EdgeAnnotator and sv.VertexLabelAnnotator now support per-class skeleton definitions, enabling correct rendering when multiple skeleton topologies (e.g. person + animal) coexist in one frame.

  • Changed #2303: sv.Detections.with_nms and sv.Detections.with_nmm now use oriented-box IoU when data["xyxyxyxy"] coordinates are present, instead of axis-aligned box IoU. Callers relying on the previous axis-aligned behaviour should remove data["xyxyxyxy"] before calling, or recalibrate any IoU thresholds.

  • Changed #2312: sv.Detections.with_nmm now computes the merged oriented bounding box as the tightest rectangle at the winner's orientation enclosing all corners from every detection in a merge group.

  • Changed #2325: sv.VertexEllipseAreaAnnotator, sv.VertexEllipseOutlineAnnotator, and sv.VertexEllipseHaloAnnotator now draw sigma levels level-by-level (outermost first) across all points, ensuring correct visual layering when ellipses overlap.

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

  • Changed #2324: Project-wide deprecation policy unified to a minimum 3-minor-release window. All current deprecations (including KeyPoints.confidence and validate_* helpers) are scheduled for removal in 0.32.0.

  • Fixed #2252: sv.process_video audio muxing path now correctly creates temp files on the same filesystem, decodes ffmpeg errors, and avoids muxing incomplete output.

  • Fixed #2282, #2317: sv.oriented_box_iou_batch now computes exact IoU via convex polygon intersection (cv2.intersectConvexConvex) and uses an axis-aligned bounding box envelope gate to skip pairs that cannot overlap, improving both accuracy and performance. Previously, rasterization on a discrete grid was used, which assumed square dimensions and introduced quantisation noise.

  • Fixed #2239: sv.Detections.from_vlm no longer returns None for class_id on empty VLM parses; now returns an empty int ndarray.

  • Fixed #2270: sv.Detections.from_inference now preserves class_name as a string-dtype array when predictions are empty.

  • Fixed #2269: sv.HeatMapAnnotator no longer crashes with a divide-by-zero when called with empty detections.

  • Fixed #2276: COCO export now emits 1-indexed category_id values as required by the COCO specification.

  • Fixed #2267: COCO annotation and image IDs are now sequential across train/val/test splits via starting_image_id and starting_annotation_id parameters.

  • Fixed #2289: sv.DetectionDataset.as_yolo no longer loses OBB rotation when exporting oriented bounding boxes.

  • Fixed #2296: YOLO dataset loading now sorts class names by numeric keys when data.yaml uses integer class IDs.

  • Fixed #2297: Letterbox utility now supports grayscale images.

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

  • Fixed #2321: sv.DetectionDataset.as_coco() now round-trips polygon and RLE segmentation data. Segmentations loaded from COCO annotations are preserved in detections.data["coco_raw_segmentation"] and written back on export, preventing data loss in train/val/test split workflows.

  • Deprecated: KeyPoints.confidence (use KeyPoints.keypoint_confidence), merge_inner_detection_object_pair, merge_inner_detections_objects, merge_inner_detections_objects_without_iou, validate_detections_fields, validate_vlm_parameters, validate_fields_both_defined_or_none, validate_xyxy, validate_mask, validate_class_id, validate_confidence, validate_tracker_id, validate_data, validate_xy, validate_key_point_confidence, validate_key_points_fields, validate_resolution, validate_custom_values, validate_input_tensors, and validate_labels are deprecated in 0.29.0 and will be removed in 0.32.0.

0.28.0 Apr 30, 2026

  • Added #2159: sv.CompactMask for memory-efficient mask storage. Masks are stored as crop-region bounding boxes plus RLE-encoded data instead of full-resolution bitmaps, reducing memory by up to 240× for sparse masks. Integrates transparently with sv.Detections.mask — filtering, merging, and area all work without materialising the full array.

  • Added #2227: sv.CompactMask.resize(new_image_shape) rescales all stored crops to match a new image resolution, enabling use across frames or after image resizing pipelines.

  • Added #2178: sv.Detections.from_inference now supports compressed COCO RLE masks. Inference responses with rle or rle_mask fields containing a compressed counts string (as produced by pycocotools) are decoded directly into binary masks, avoiding a lossy polygon round-trip.

  • Added #2004: sv.Color.from_hex now accepts 8-digit hexadecimal RGBA codes (e.g. #ff00ff80). Color.as_hex() serialises back, including alpha when not fully opaque. New utility functions sv.hex_to_rgba, sv.rgba_to_hex, and sv.is_valid_hex are exported at the top level.

  • Added #709: sv.BlurAnnotator and sv.PixelateAnnotator now support dynamic sizing. When kernel_size=None or pixel_size=None (the new default), the size is computed per detection as a fraction of the shorter bounding-box dimension, producing consistent visual results across objects of different sizes.

  • Added #2186: sv.InferenceSlicer now emits a warning when detections returned by the callback fall outside the tile boundaries, helping catch coordinate-system bugs in custom callbacks.

  • Added #2103, #2152: New sv.Detections.from_sam3() classmethod parses SAM3 PCS (text-prompted) and PVS (visual-prompted video segmentation) response formats into a standard sv.Detections, both from the local inference package and from Roboflow-hosted server responses.

  • Added #2154: The library now uses Python's logging module instead of print for diagnostic output. Messages are emitted under the supervision logger so applications can capture, filter, or silence them through standard logging configuration.

  • Added #932: sv.ImageAssets for downloading sample images alongside existing video assets, useful for examples and tutorials.

  • Changed #2169: sv.MeanAveragePrecisionResult and related metric arrays (mAP_scores, ap_per_class, iou_thresholds, precision/recall) are now float32 instead of float64. Reduces memory and speeds up computation; numerical results may differ in the last few digits.

  • Changed #2178: sv.rle_to_mask and sv.mask_to_rle moved to supervision.detection.utils.converters. The old import path supervision.dataset.utils continues to work but is deprecated.

  • Fixed #2178: sv.rle_to_mask now returns NDArray[bool] as declared in its signature. Previously the implementation returned uint8 despite the bool annotation; code that relied on the undocumented uint8 output (e.g. mask * 255 producing uint8) should wrap the result with .astype(np.uint8).

  • Fixed #2210: sv.VideoInfo.fps now returns a float instead of a truncated int. Previously, frame rates like 23.976, 29.97, and 59.94 were silently truncated, causing frame-timing drift that accumulates over long videos. The type of VideoInfo.fps has changed from int to float; callers that pass fps to APIs requiring an integer (such as deque(maxlen=...) or TraceAnnotator(trace_length=...)) should wrap the value with int().

  • Fixed #2209: sv.Detections.is_empty() now returns True for detections filtered down to zero rows, even when tracker_id is an empty array. Previously this case incorrectly returned False.

  • Fixed #2199: sv.CSVSink now correctly slices numpy array values in custom_data per row. Previously the full array was written for every detection.

  • Fixed #2216: sv.CSVSink and sv.JSONSink now slice plain Python list and tuple values in custom_data per detection row. Lists and tuples matching the detection count are indexed per row, consistent with np.ndarray behavior.

  • Fixed #2217: sv.TraceAnnotator no longer crashes in smooth mode when a tracker remains stationary. Duplicate consecutive points caused splprep to fail; the annotator now deduplicates anchor points and falls back to a raw polyline when fewer than 4 unique points are available.

  • Fixed #2218: load_coco_annotations now rejects COCO annotations whose file_name escapes the images directory via ../ traversal or absolute paths, preventing path-traversal attacks from malicious annotation files.

  • Fixed #2187: Extreme memory usage when loading OBB (oriented bounding box) datasets, caused by allocating full-image masks for each rotated box, has been resolved.

  • Fixed #2188: sv.KeyPoints boolean mask indexing now works correctly when all instances have the same keypoint count (uniform-count selection).

  • Fixed #2185: sv.DetectionDataset.as_coco() now preserves area and iscrowd fields instead of silently dropping them in the round-trip.

  • Fixed #1746: Precision loss when converting annotations with force_mask=True in dataset format converters.

  • Fixed #1991: sv.PolygonZone no longer double-counts the same object when multiple zones overlap. Detection bounding boxes were incorrectly clipped to each zone's ROI before anchor computation, causing the same detection to appear at a different anchor point in each zone; anchor is now computed from the original bounding box so containment is independent per zone.

  • Fixed #1868: sv.LineZone no longer mis-attributes crossings when a tracker reuses the same tracker_id across different classes. Class-aware bookkeeping prevents a new object from inheriting another class's prior crossing state.

  • Fixed #2022: sv.process_video now raises immediately when the user callback throws, instead of silently swallowing the exception and hanging until the writer is flushed.

  • Fixed #2156: sv.DetectionDataset now populates data["class_name"] on every loaded annotation, matching what model connectors produce. Downstream code can rely on class_name being present whether detections come from a dataset or a model.

  • Fixed #1364: sv.ByteTrack now preserves externally assigned tracker_id values instead of overwriting them with internal ids on the first update.

  • Fixed #1853: sv.ConfusionMatrix evaluate_detection_batch now matches predictions to ground truth correctly when multiple detections fall on the same target. Previously, double-counting inflated false-positive and false-negative counts.

  • Fixed #2136: sv.MeanAverageRecall now computes mAR@K using the top-K detections per image, matching the COCO definition. Previous values were inflated relative to pycocotools.

  • Fixed #1086, #265: COCO export and force_masks behaviour are now consistent across dataset formats. Empty polygons no longer raise during as_coco, and force_masks=True produces masks regardless of source format.

  • Deprecated #2215: sv.ByteTrack is deprecated in favour of ByteTrackTracker from the external trackers package (pip install trackers). The update method is renamed from update_with_detections() to update(). Removal is now planned for supervision-0.31.0.

  • Deprecated #2214: supervision.keypoint module is deprecated; use supervision.key_points instead. create_tiles in supervision.utils.image, ensure_cv2_image_for_processing in supervision.utils.conversion, and keypoint validation utilities in supervision.validators are deprecated. The LMM enum (use VLM) and from_lmm method (use from_vlm) were deprecated in 0.26.0; this release migrates their deprecation mechanism to pydeprecate.

  • Deprecated: normalized_xyxy argument in sv.denormalize_boxes renamed to xyxy. Passing normalized_xyxy= now emits a FutureWarning; support will be removed in supervision-0.31.0.

0.27.0 Nov 16, 2025

  • Added #2008: sv.filter_segments_by_distance to keep the largest connected component and nearby components within an absolute or relative distance threshold. Useful for cleaning segmentation predictions from models such as SAM, SAM2, YOLO segmentation, and RF-DETR segmentation.

  • Added #2006: sv.xyxy_to_mask to convert bounding boxes into 2D boolean masks, where each mask corresponds to a single box.

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

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

  • Added #2014: sv.get_image_resolution_wh as a unified way to read image width and height from NumPy and PIL inputs.

  • Added #1912: sv.edit_distance for Levenshtein distance between two strings. Supports insert, delete, and substitute operations.

  • Added #1912: sv.fuzzy_match_index to find the first close match in a list using edit distance.

  • Changed #2015: sv.Detections.from_vlm and legacy from_lmm now support Qwen3 VL via vlm=sv.VLM.QWEN_3_VL.

  • Changed #1884: sv.Detections.from_vlm and legacy from_lmm now support DeepSeek VL 2 via vlm=sv.VLM.DEEPSEEK_VL_2.

  • Changed #2015: sv.Detections.from_vlm now parses Qwen 2.5 VL outputs more robustly and handles incomplete or truncated JSON responses.

  • Changed #2014: sv.InferenceSlicer now uses a new offset generation logic that removes redundant tiles and aligns borders cleanly. This reduces the number of processed tiles and shortens inference time without hurting detection quality.

  • Changed #2016: sv.Detections now includes a box_aspect_ratio property for vectorized aspect ratio computation, useful for filtering detections based on box shape.

  • Changed #2001: Significantly improved the performance of sv.box_iou_batch. On internal benchmarks, processing runs approximately 2x to 5x faster.

  • Changed #1997: 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.

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

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

Removed

Removed the deprecated overlap_ratio_wh argument from sv.InferenceSlicer. Use the pixel based overlap_wh argument to control slice overlap.

Tip

Convert your old ratio based overlap to pixel based overlap by multiplying 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,
)

0.26.1 Jul 22, 2025

0.26.0 Jul 16, 2025

Removed

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

Tip

Supervision’s documentation theme now has a fresh look that is consistent with the documentations of all Roboflow open-source projects. (#1858)

  • Added #1774: 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.

    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 #1874: sv.box_iou that efficiently computes the Intersection over Union (IoU) between two individual bounding boxes.

  • Added #1816: Support for frame limitations and progress bar in sv.process_video.

  • Added #1788: Support for creating sv.KeyPoints objects from ViTPose and ViTPose++ inference results via sv.KeyPoints.from_transformers.

  • Added #1823: 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.

  • Added #1788: 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 #1820: 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.

  • Changed #1825: sv.LabelAnnotator now supports non-string labels.

  • Changed #1792: sv.Detections.from_vlm now supports parsing bounding boxes and segmentation masks from responses generated by Google Gemini models.

    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])
    
  • Changed #1878: sv.Detections.from_vlm now supports parsing bounding boxes from responses generated by Moondream.

    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]])
    
  • Changed #1709: sv.Detections.from_vlm now supports parsing bounding boxes from responses generated by Qwen-2.5 VL.

    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])
    
  • Changed #1786: Significantly improved the speed of HSV color mapping in sv.HeatMapAnnotator, achieving approximately 28x faster performance on 1920x1080 frames.

  • Fixed #1834: Supervision’s sv.MeanAveragePrecision is now fully aligned with pycocotools, the official COCO evaluation tool, ensuring accurate and standardized metrics. This update enabled us to launch a new version of the Computer Vision Model Leaderboard.

    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
    
  • Fixed #1767: Fixed losing sv.Detections.data when detections filtering.

0.25.0 Nov 12, 2024

  • No removals or deprecations in this release!

  • 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)

  • It is now possible to track objects detected as KeyPoints. See the complete step-by-step guide in the Object Tracking Guide. (#1658)

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
)
  • 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 the supervision.assets download catalog. (#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)

  • 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()
  • 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()
  • 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()
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)
  • 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)

  • 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)

  • 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)

  • Large refactor of ByteTrack: STrack moved to separate class, removed superfluous BaseTrack class, removed unused variables (#1603)

  • Large refactor of RichLabelAnnotator, matching its contents with LabelAnnotator. (#1625)

0.24.0 Oct 4, 2024

  • Added F1 score as a new metric for detection and segmentation. #1521
import supervision as sv
from supervision.metrics import F1Score

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

f1_metric = F1Score()
f1_result = f1_metric.update(predictions, targets).compute()

print(f1_result)
print(f1_result.f1_50)
print(f1_result.small_objects.f1_50)
import supervision as sv
import cv2

image = cv2.imread("<SOURCE_IMAGE_PATH>")

line_zone = sv.LineZone(
    start=sv.Point(0, 100),
    end=sv.Point(50, 200)
)
line_zone_annotator = sv.LineZoneAnnotator(
    text_orient_to_line=True,
    display_text_box=False,
    text_centered=False
)

annotated_frame = line_zone_annotator.annotate(
    frame=image.copy(), line_counter=line_zone
)

sv.plot_image(frame)
  • Added per-class counting capabilities to LineZone and introduced LineZoneAnnotatorMulticlass for visualizing the counts per class. This feature allows tracking of individual classes crossing a line, enhancing the flexibility of use cases like traffic monitoring or crowd analysis. #1555
import supervision as sv
import cv2

image = cv2.imread("<SOURCE_IMAGE_PATH>")

line_zone = sv.LineZone(
    start=sv.Point(0, 100),
    end=sv.Point(50, 200)
)
line_zone_annotator = sv.LineZoneAnnotatorMulticlass()

frame = line_zone_annotator.annotate(
    frame=frame, line_zones=[line_zone]
)

sv.plot_image(frame)
  • Added from_easyocr, allowing integration of OCR results into the supervision framework. EasyOCR is an open-source optical character recognition (OCR) library that can read text from images. #1515
import supervision as sv
import easyocr
import cv2

image = cv2.imread("<SOURCE_IMAGE_PATH>")

reader = easyocr.Reader(["en"])
result = reader.readtext("<SOURCE_IMAGE_PATH>", paragraph=True)
detections = sv.Detections.from_easyocr(result)

box_annotator = sv.BoxAnnotator(color_lookup=sv.ColorLookup.INDEX)
label_annotator = sv.LabelAnnotator(color_lookup=sv.ColorLookup.INDEX)

annotated_image = image.copy()
annotated_image = box_annotator.annotate(scene=annotated_image, detections=detections)
annotated_image = label_annotator.annotate(scene=annotated_image, detections=detections)

sv.plot_image(annotated_image)
  • Added oriented_box_iou_batch function to detection.utils. This function computes Intersection over Union (IoU) for oriented or rotated bounding boxes (OBB). #1502
import numpy as np

boxes_true = np.array([[[1, 0], [0, 1], [3, 4], [4, 3]]])
boxes_detection = np.array([[[1, 1], [2, 0], [4, 2], [3, 3]]])
ious = sv.oriented_box_iou_batch(boxes_true, boxes_detection)
print("IoU between true and detected boxes:", ious)
  • Extended PolygonZoneAnnotator to allow setting opacity when drawing zones, providing enhanced visualization by filling the zone with adjustable transparency. #1527
import cv2
from ncnn.model_zoo import get_model
import supervision as sv

image = cv2.imread("<SOURCE_IMAGE_PATH>")
model = get_model(
    "yolov8s",
    target_size=640,
    prob_threshold=0.5,
    nms_threshold=0.45,
    num_threads=4,
    use_gpu=True,
)
result = model(image)
detections = sv.Detections.from_ncnn(result)

Removed

The frame_resolution_wh parameter in PolygonZone has been removed.

Removed

Supervision installation methods "headless" and "desktop" were removed, as they are no longer needed. pip install supervision[headless] will install the base library and harmlessly warn of non-existent extras.

  • Supervision now depends on opencv-python rather than opencv-python-headless. #1530

  • Fixed the COCO 101 point Average Precision algorithm to correctly interpolate precision, providing a more precise calculation of average precision without averaging out intermediate values. #1500

  • Resolved miscellaneous issues highlighted when building documentation. This mostly includes whitespace adjustments and type inconsistencies. Updated documentation for clarity and fixed formatting issues. Added explicit version for mkdocstrings-python. #1549

  • Enabled and fixed Ruff rules for code formatting, including changes like avoiding unnecessary iterable allocations and using Optional for default mutable arguments. #1526

0.23.0 Aug 28, 2024

  • Added #930: IconAnnotator, a new annotator that allows drawing icons on each detection. Useful if you want to draw a specific icon for each class.
import supervision as sv
from inference import get_model

image = <SOURCE_IMAGE_PATH>
icon_dog = <DOG_PNG_PATH>
icon_cat = <CAT_PNG_PATH>

model = get_model(model_id="yolov8n-640")
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)

icon_paths = []
for class_name in detections.data["class_name"]:
    if class_name == "dog":
        icon_paths.append(icon_dog)
    elif class_name == "cat":
        icon_paths.append(icon_cat)
    else:
        icon_paths.append("")

icon_annotator = sv.IconAnnotator()
annotated_frame = icon_annotator.annotate(
    scene=image.copy(),
    detections=detections,
    icon_path=icon_paths
)
import supervision as sv
from inference import get_model

image = <SOURCE_IMAGE_PATH>

model = get_model(model_id="yolov8n-640")
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)

background_overlay_annotator = sv.BackgroundOverlayAnnotator()
annotated_frame = background_overlay_annotator.annotate(
    scene=image.copy(),
    detections=detections
)
  • Added #1386: Support for Transformers v5 functions in sv.Detections.from_transformers. This includes the DetrImageProcessor methods post_process_object_detection, post_process_panoptic_segmentation, post_process_semantic_segmentation, and post_process_instance_segmentation.
import torch
import supervision as sv
from PIL import Image
from transformers import DetrImageProcessor, DetrForObjectDetection

processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")

image = Image.open(<SOURCE_IMAGE_PATH>)
inputs = processor(images=image, return_tensors="pt")

with torch.no_grad():
    outputs = model(**inputs)

width, height = image.size
target_size = torch.tensor([[height, width]])
results = processor.post_process_object_detection(
    outputs=outputs, target_sizes=target_size)[0]
detections = sv.Detections.from_transformers(
    transformers_results=results,
    id2label=model.config.id2label)
import supervision as sv
from segment_anything import (
    sam_model_registry,
    SamAutomaticMaskGenerator
)
sam_model_reg = sam_model_registry[MODEL_TYPE]
sam = sam_model_reg(checkpoint=CHECKPOINT_PATH).to(device=DEVICE)
mask_generator = SamAutomaticMaskGenerator(sam)
sam_result = mask_generator.generate(IMAGE)
detections = sv.Detections.from_sam(sam_result=sam_result)
import supervision as sv

for frame in sv.get_video_frames_generator(
    source_path=<SOURCE_VIDEO_PATH>,
    start=60,
    iterative_seek=True
):
    ...
  • Fixed #1424: plot_image function now clearly indicates that the size is in inches.

Removed

The track_buffer, track_thresh, and match_thresh parameters in ByteTrack are deprecated and were removed as of supervision-0.23.0. Use lost_track_buffer, track_activation_threshold, and minimum_matching_threshold instead.

Removed

The triggering_position parameter in sv.PolygonZone was removed as of supervision-0.23.0. Use triggering_anchors instead.

Deprecated

overlap_filter_strategy in InferenceSlicer.__init__ is deprecated and will be removed in supervision-0.27.0. Use overlap_strategy instead.

Deprecated

overlap_ratio_wh in InferenceSlicer.__init__ is deprecated and will be removed in supervision-0.27.0. Use overlap_wh instead.

0.22.0 Jul 12, 2024

Deprecated

Constructing DetectionDataset with parameter images as Dict[str, np.ndarray] is deprecated and will be removed in supervision-0.26.0. Please pass a list of paths List[str] instead.

Deprecated

The DetectionDataset.images property is deprecated and will be 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.

import roboflow
from roboflow import Roboflow
import supervision as sv

roboflow.login()
rf = Roboflow()

project = rf.workspace(<WORKSPACE_ID>).project(<PROJECT_ID>)
dataset = project.version(<PROJECT_VERSION>).download("coco")

ds_train = sv.DetectionDataset.from_coco(
    images_directory_path=f"{dataset.location}/train",
    annotations_path=f"{dataset.location}/train/_annotations.coco.json",
)

path, image, annotation = ds_train[0]
    # loads image on demand

for path, image, annotation in ds_train:
    # loads image on demand
import supervision as sv
from detectron2 import model_zoo
from detectron2.engine import DefaultPredictor
from detectron2.config import get_cfg
import cv2

image = cv2.imread(<SOURCE_IMAGE_PATH>)
cfg = get_cfg()
cfg.merge_from_file(model_zoo.get_config_file("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml"))
cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml")
predictor = DefaultPredictor(cfg)

result = predictor(image)
detections = sv.Detections.from_detectron2(result)

mask_annotator = sv.MaskAnnotator()
annotated_frame = mask_annotator.annotate(scene=image.copy(), detections=detections)
  • Added #1277: if you provide a font that supports symbols of a language, sv.RichLabelAnnotator will draw them on your images.
  • Various other annotators have been revised to ensure proper in-place functionality when used with numpy arrays. Additionally, we fixed a bug where sv.ColorAnnotator was filling boxes with solid color when used in-place.
import cv2
import supervision as sv
import

image = cv2.imread(<SOURCE_IMAGE_PATH>)

model = get_model(model_id="yolov8n-640")
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)

rich_label_annotator = sv.RichLabelAnnotator(font_path=<TTF_FONT_PATH>)
annotated_image = rich_label_annotator.annotate(scene=image.copy(), detections=detections)
  • Added #1227: Added support for loading Oriented Bounding Boxes dataset in YOLO format.
import supervision as sv

train_ds = sv.DetectionDataset.from_yolo(
    images_directory_path="/content/dataset/train/images",
    annotations_directory_path="/content/dataset/train/labels",
    data_yaml_path="/content/dataset/data.yaml",
    is_obb=True,
)

_, image, detections in train_ds[0]

obb_annotator = OrientedBoxAnnotator()
annotated_image = obb_annotator.annotate(scene=image.copy(), detections=detections)

Removed

BoxAnnotator was removed, however BoundingBoxAnnotator has been renamed to BoxAnnotator. Use a combination of BoxAnnotator and LabelAnnotator to simulate old BoundingBox behavior.

Deprecated

The name BoundingBoxAnnotator has been deprecated and will be removed in supervision-0.26.0. It has been renamed to BoxAnnotator.

  • Added #975 📝 New Cookbooks: serialize detections into json and csv.

  • Added #1290: Mostly an internal change, our file utility function now support both str and pathlib paths.

  • Added #1340: Two new methods for converting between bounding box formats - xywh_to_xyxy and xcycwh_to_xyxy

Removed

from_roboflow method has been removed due to deprecation. Use from_inference instead.

Removed

Color.white() has been removed due to deprecation. Use color.WHITE instead.

Removed

Color.black() has been removed due to deprecation. Use color.BLACK instead.

Removed

Color.red() has been removed due to deprecation. Use color.RED instead.

Removed

Color.green() has been removed due to deprecation. Use color.GREEN instead.

Removed

Color.blue() has been removed due to deprecation. Use color.BLUE instead.

Removed

ColorPalette.default() has been removed due to deprecation. Use ColorPalette.DEFAULT instead.

Removed

FPSMonitor.__call__ has been removed due to deprecation. Use the attribute FPSMonitor.fps instead.

0.21.0 Jun 5, 2024

import supervision as sv

paligemma_result = "<loc0256><loc0256><loc0768><loc0768> cat"
detections = sv.Detections.from_lmm(
    sv.LMM.PALIGEMMA,
    paligemma_result,
    resolution_wh=(1000, 1000),
    classes=["cat", "dog"],
)
detections.xyxy
# array([[250., 250., 750., 750.]])

detections.class_id
# array([0])
import supervision as sv

image = ...
key_points = sv.KeyPoints(...)

edge_annotator = sv.EdgeAnnotator(
    color=sv.Color.GREEN,
    thickness=5
)
annotated_frame = edge_annotator.annotate(
    scene=image.copy(),
    key_points=key_points
)
import cv2
import numpy as np
import supervision as sv
from inference import get_model

model = get_model(model_id="yolov8x-seg-640")
image = cv2.imread(<SOURCE_IMAGE_PATH>)

def callback(image_slice: np.ndarray) -> sv.Detections:
    results = model.infer(image_slice)[0]
    return sv.Detections.from_inference(results)

slicer = sv.InferenceSlicer(callback = callback)
detections = slicer(image)

mask_annotator = sv.MaskAnnotator()
label_annotator = sv.LabelAnnotator()

annotated_image = mask_annotator.annotate(
    scene=image, detections=detections)
annotated_image = label_annotator.annotate(
    scene=annotated_image, detections=detections)

0.20.0 April 24, 2024

import cv2
import supervision as sv
from ultralytics import YOLO

image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = YOLO('yolov8l-pose')

result = model(image, verbose=False)[0]
keypoints = sv.KeyPoints.from_ultralytics(result)

edge_annotators = sv.EdgeAnnotator(color=sv.Color.GREEN, thickness=5)
annotated_image = edge_annotators.annotate(image.copy(), keypoints)
  • Changed #1037: sv.LabelAnnotator by adding an additional corner_radius argument that allows for rounding the corners of the bounding box.

  • Changed #1109: sv.PolygonZone such that the frame_resolution_wh argument is no longer required to initialize sv.PolygonZone.

Deprecated

The frame_resolution_wh parameter in sv.PolygonZone is deprecated and will be removed in supervision-0.24.0.

import torch
import supervision as sv
from PIL import Image
from transformers import DetrImageProcessor, DetrForSegmentation

processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50-panoptic")
model = DetrForSegmentation.from_pretrained("facebook/detr-resnet-50-panoptic")

image = Image.open(<SOURCE_IMAGE_PATH>)
inputs = processor(images=image, return_tensors="pt")

with torch.no_grad():
    outputs = model(**inputs)

width, height = image.size
target_size = torch.tensor([[height, width]])
results = processor.post_process_segmentation(
    outputs=outputs, target_sizes=target_size)[0]
detections = sv.Detections.from_transformers(results, id2label=model.config.id2label)

mask_annotator = sv.MaskAnnotator()
label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER)

annotated_image = mask_annotator.annotate(
    scene=image, detections=detections)
annotated_image = label_annotator.annotate(
    scene=annotated_image, detections=detections)

0.19.0 March 15, 2024

  • Added #818: sv.CSVSink allowing for the straightforward saving of image, video, or stream inference results in a .csv file.
import supervision as sv
from ultralytics import YOLO

model = YOLO(<SOURCE_MODEL_PATH>)
csv_sink = sv.CSVSink(<RESULT_CSV_FILE_PATH>)
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>)

with csv_sink:
    for frame in frames_generator:
        result = model(frame)[0]
        detections = sv.Detections.from_ultralytics(result)
        csv_sink.append(detections, custom_data={<CUSTOM_LABEL>:<CUSTOM_DATA>})
  • Added #819: sv.JSONSink allowing for the straightforward saving of image, video, or stream inference results in a .json file.
import supervision as sv
from ultralytics import YOLO

model = YOLO(<SOURCE_MODEL_PATH>)
json_sink = sv.JSONSink(<RESULT_JSON_FILE_PATH>)
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>)

with json_sink:
    for frame in frames_generator:
        result = model(frame)[0]
        detections = sv.Detections.from_ultralytics(result)
        json_sink.append(detections, custom_data={<CUSTOM_LABEL>:<CUSTOM_DATA>})
import cv2
import supervision as sv
from inference import get_model

image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = get_model(model_id="yolov8n-640")

result = model.infer(image)[0]
detections = sv.Detections.from_inference(result)

crop_annotator = sv.CropAnnotator()
annotated_frame = crop_annotator.annotate(
    scene=image.copy(),
    detections=detections
)
  • Changed #827: sv.ByteTrack.reset allowing users to clear trackers state, enabling the processing of multiple video files in sequence.

  • Changed #802: sv.LineZoneAnnotator allowing to hide in/out count using display_in_count and display_out_count properties.

  • Changed #787: sv.ByteTrack input arguments and docstrings updated to improve readability and ease of use.

Deprecated

The track_buffer, track_thresh, and match_thresh parameters in sv.ByteTrack are deprecated and will be removed in supervision-0.23.0. Use lost_track_buffer, track_activation_threshold, and minimum_matching_threshold instead.

  • Changed #910: sv.PolygonZone to now accept a list of specific box anchors that must be in zone for a detection to be counted.

Deprecated

The triggering_position parameter in sv.PolygonZone is deprecated and will be removed in supervision-0.23.0. Use triggering_anchors instead.

  • Changed #875: annotators adding support for Pillow images. All supervision Annotators can now accept an image as either a numpy array or a Pillow Image. They automatically detect its type, draw annotations, and return the output in the same format as the input.

  • Fixed #944: sv.DetectionsSmoother removing tracking_id from sv.Detections.

0.18.0 January 25, 2024

  • Added #720: sv.PercentageBarAnnotator allowing to annotate images and videos with percentage values representing confidence or other custom property.
>>> import supervision as sv

>>> image = ...
>>> detections = sv.Detections(...)

>>> percentage_bar_annotator = sv.PercentageBarAnnotator()
>>> annotated_frame = percentage_bar_annotator.annotate(
...     scene=image.copy(),
...     detections=detections
... )
import cv2
import supervision as sv
from ultralytics import YOLO

image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = YOLO("yolov8n-obb.pt")

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

oriented_box_annotator = sv.OrientedBoxAnnotator()
annotated_frame = oriented_box_annotator.annotate(
    scene=image.copy(),
    detections=detections
)
>>> import supervision as sv

>>> sv.ColorPalette.from_matplotlib('viridis', 5)
ColorPalette(colors=[Color(r=68, g=1, b=84), Color(r=59, g=82, b=139), ...])
  • Changed #770: sv.Detections.from_ultralytics adding support for OBB (Oriented Bounding Boxes).

  • Changed #735: sv.LineZone to now accept a list of specific box anchors that must cross the line for a detection to be counted. This update marks a significant improvement from the previous requirement, where all four box corners were necessary. Users can now specify a single anchor, such as sv.Position.BOTTOM_CENTER, or any other combination of anchors defined as List[sv.Position].

  • Changed #756: sv.Color's and sv.ColorPalette's method of accessing predefined colors, transitioning from a function-based approach (sv.Color.red()) to a more intuitive and conventional property-based method (sv.Color.RED).

Deprecated

sv.ColorPalette.default() is deprecated and will be removed in supervision-0.22.0. Use sv.ColorPalette.DEFAULT instead.

Deprecated

Detections.from_roboflow() is deprecated and will be removed in supervision-0.22.0. Use Detections.from_inference instead.

  • Fixed #735: sv.LineZone functionality to accurately update the counter when an object crosses a line from any direction, including from the side. This enhancement enables more precise tracking and analytics, such as calculating individual in/out counts for each lane on the road.

0.17.0 December 06, 2023

>>> import supervision as sv

>>> image = ...
>>> detections = sv.Detections(...)

>>> polygon_annotator = sv.PolygonAnnotator()
>>> annotated_frame = polygon_annotator.annotate(
...     scene=image.copy(),
...     detections=detections
... )
  • Added #476: sv.assets allowing download of video files that you can use in your demos.
>>> from supervision.assets import download_assets, VideoAssets
>>> download_assets(VideoAssets.VEHICLES)
"vehicles.mp4"

0.16.0 October 19, 2023

>>> import supervision as sv

>>> image = ...
>>> detections = sv.Detections(...)

>>> halo_annotator = sv.HaloAnnotator()
>>> annotated_frame = halo_annotator.annotate(
...     scene=image.copy(),
...     detections=detections
... )
  • Added #466: sv.HeatMapAnnotator allowing to annotate videos with heat maps.

  • Added #492: sv.DotAnnotator allowing to annotate images and videos with dots.

  • Added #449: sv.draw_image allowing to draw an image onto a given scene with specified opacity and dimensions.

  • Added #280: sv.FPSMonitor for monitoring frames per second (FPS) to benchmark latency.

  • Added #454: 🤗 Hugging Face Annotators space.

  • Changed #482: sv.LineZone.trigger now return Tuple[np.ndarray, np.ndarray]. The first array indicates which detections have crossed the line from outside to inside. The second array indicates which detections have crossed the line from inside to outside.

  • Changed #465: Annotator argument name from color_map: str to color_lookup: ColorLookup enum to increase type safety.

  • Changed #426: sv.MaskAnnotator allowing 2x faster annotation.

  • Fixed #477: Poetry env definition allowing proper local installation.

  • Fixed #430: sv.ByteTrack to return np.array([], dtype=int) when svDetections is empty.

Deprecated

sv.Detections.from_yolov8 and sv.Classifications.from_yolov8 as those are now replaced by sv.Detections.from_ultralytics and sv.Classifications.from_ultralytics.

0.15.0 October 5, 2023

>>> import supervision as sv

>>> image = ...
>>> detections = sv.Detections(...)

>>> bounding_box_annotator = sv.BoundingBoxAnnotator()
>>> annotated_frame = bounding_box_annotator.annotate(
...     scene=image.copy(),
...     detections=detections
... )

0.14.0 August 31, 2023

>>> import cv2
>>> import supervision as sv
>>> from ultralytics import YOLO

>>> image = cv2.imread(SOURCE_IMAGE_PATH)
>>> model = YOLO(...)

>>> def callback(image_slice: np.ndarray) -> sv.Detections:
...     result = model(image_slice)[0]
...     return sv.Detections.from_ultralytics(result)

>>> slicer = sv.InferenceSlicer(callback = callback)

>>> detections = slicer(image)

Deprecated

sv.Detections.from_yolov8 and sv.Classifications.from_yolov8 are now deprecated and will be removed with supervision-0.16.0 release.

0.13.0 August 8, 2023

>>> import supervision as sv
>>> from ultralytics import YOLO

>>> dataset = sv.DetectionDataset.from_yolo(...)

>>> model = YOLO(...)
>>> def callback(image: np.ndarray) -> sv.Detections:
...     result = model(image)[0]
...     return sv.Detections.from_yolov8(result)

>>> mean_average_precision = sv.MeanAveragePrecision.benchmark(
...     dataset = dataset,
...     callback = callback
... )

>>> mean_average_precision.map50_95
0.433

Deprecated

sv.Detections.from_yolov8 is now deprecated and will be removed with supervision-0.15.0 release.

0.12.0 July 24, 2023

Python 3.7. Support Terminated

With the supervision-0.12.0 release, we are terminating official support for Python 3.7.

>>> import supervision as sv
>>> from ultralytics import YOLO

>>> dataset = sv.DetectionDataset.from_yolo(...)

>>> model = YOLO(...)
>>> def callback(image: np.ndarray) -> sv.Detections:
...     result = model(image)[0]
...     return sv.Detections.from_yolov8(result)

>>> confusion_matrix = sv.ConfusionMatrix.benchmark(
...     dataset = dataset,
...     callback = callback
... )

>>> confusion_matrix.matrix
array([
    [0., 0., 0., 0.],
    [0., 1., 0., 1.],
    [0., 1., 1., 0.],
    [1., 1., 0., 0.]
])

0.11.1 June 29, 2023

0.11.0 June 28, 2023

>>> import supervision as sv

>>> ds = sv.DetectionDataset.from_coco(
...     images_directory_path='...',
...     annotations_path='...'
... )

>>> ds.as_coco(
...     images_directory_path='...',
...     annotations_path='...'
... )
>>> import supervision as sv

>>> ds_1 = sv.DetectionDataset(...)
>>> len(ds_1)
100
>>> ds_1.classes
['dog', 'person']

>>> ds_2 = sv.DetectionDataset(...)
>>> len(ds_2)
200
>>> ds_2.classes
['cat']

>>> ds_merged = sv.DetectionDataset.merge([ds_1, ds_2])
>>> len(ds_merged)
300
>>> ds_merged.classes
['cat', 'dog', 'person']
  • Added #162: additional start and end arguments to sv.get_video_frames_generator allowing to generate frames only for a selected part of the video.

  • Fixed #157: incorrect loading of YOLO dataset class names from data.yaml.

0.10.0 June 14, 2023

>>> import supervision as sv

>>> cs = sv.ClassificationDataset.from_folder_structure(
...     root_directory_path='...'
... )

>>> cs.as_folder_structure(
...     root_directory_path='...'
... )

0.9.0 June 7, 2023

  • Added #118: ability to select sv.Detections by index, list of indexes or slice. Here is an example illustrating the new selection methods.
>>> import supervision as sv

>>> detections = sv.Detections(...)
>>> len(detections[0])
1
>>> len(detections[[0, 1]])
2
>>> len(detections[0:2])
2
  • Added #101: ability to extract masks from YOLOv8 result using sv.Detections.from_yolov8. Here is an example illustrating how to extract boolean masks from the result of the YOLOv8 model inference.

  • Added #122: ability to crop image using sv.crop. Here is an example showing how to get a separate crop for each detection in sv.Detections.

  • Added #120: ability to conveniently save multiple images into directory using sv.ImageSink. Here is an example showing how to save every tenth video frame as a separate image.

>>> import supervision as sv

>>> with sv.ImageSink(target_dir_path='target/directory/path') as sink:
...     for image in sv.get_video_frames_generator(source_path='source_video.mp4', stride=10):
...         sink.save_image(image=image)
  • Fixed #106: inconvenient handling of sv.PolygonZone coordinates. Now sv.PolygonZone accepts coordinates in the form of [[x1, y1], [x2, y2], ...] that can be both integers and floats.

0.8.0 May 17, 2023

  • Added #100: support for dataset inheritance. The current Dataset got renamed to DetectionDataset. Now DetectionDataset inherits from BaseDataset. This change was made to enforce the future consistency of APIs of different types of computer vision datasets.
  • Added #100: ability to save datasets in YOLO format using DetectionDataset.as_yolo.
>>> import roboflow
>>> from roboflow import Roboflow
>>> import supervision as sv

>>> roboflow.login()

>>> rf = Roboflow()

>>> project = rf.workspace(WORKSPACE_ID).project(PROJECT_ID)
>>> dataset = project.version(PROJECT_VERSION).download("yolov5")

>>> ds = sv.DetectionDataset.from_yolo(
...     images_directory_path=f"{dataset.location}/train/images",
...     annotations_directory_path=f"{dataset.location}/train/labels",
...     data_yaml_path=f"{dataset.location}/data.yaml"
... )

>>> ds.classes
['dog', 'person']
>>> import supervision as sv

>>> ds = sv.DetectionDataset(...)
>>> train_ds, test_ds = ds.split(split_ratio=0.7, random_state=42, shuffle=True)

>>> len(train_ds), len(test_ds)
(700, 300)
  • Changed #100: default value of approximation_percentage parameter from 0.75 to 0.0 in DetectionDataset.as_yolo and DetectionDataset.as_pascal_voc.

0.7.0 May 11, 2023

  • Added #91: Detections.from_yolo_nas to enable seamless integration with YOLO-NAS model.
  • Added #86: ability to load datasets in YOLO format using Dataset.from_yolo.
  • Added #84: Detections.merge to merge multiple Detections objects together.
  • Fixed #81: LineZoneAnnotator.annotate does not return annotated frame.
  • Changed #44: LineZoneAnnotator.annotate to allow for custom text for the in and out tags.

0.6.0 April 19, 2023

  • Added #71: initial Dataset support and ability to save Detections in Pascal VOC XML format.
  • Added #71: new mask_to_polygons, filter_polygons_by_area, polygon_to_xyxy and approximate_polygon utilities.
  • Added #72: ability to load Pascal VOC XML object detections dataset as Dataset.
  • Changed #70: order of Detections attributes to make it consistent with order of objects in __iter__ tuple.
  • Changed #71: generate_2d_mask to polygon_to_mask.

0.5.2 April 13, 2023

  • Fixed #63: LineZone.trigger function expects 4 values instead of 5.

0.5.1 April 12, 2023

  • Fixed Detections.__getitem__ method did not return mask for selected item.
  • Fixed Detections.area crashed for mask detections.

0.5.0 April 10, 2023

  • Added #58: Detections.mask to enable segmentation support.
  • Added #58: MaskAnnotator to allow easy Detections.mask annotation.
  • Added #58: Detections.from_sam to enable native Segment Anything Model (SAM) support.
  • Changed #58: Detections.area behaviour to work not only with boxes but also with masks.

0.4.0 April 5, 2023

  • Added #46: Detections.empty to allow easy creation of empty Detections objects.
  • Added #56: Detections.from_roboflow to allow easy creation of Detections objects from Roboflow API inference results.
  • Added #56: plot_images_grid to allow easy plotting of multiple images on single plot.
  • Added #56: initial support for Pascal VOC XML format with detections_to_voc_xml method.
  • Changed #56: show_frame_in_notebook refactored and renamed to plot_image.

0.3.2 March 23, 2023

  • Changed #50: Allow Detections.class_id to be None.

0.3.1 March 6, 2023

  • Fixed #41: PolygonZone throws an exception when the object touches the bottom edge of the image.
  • Fixed #42: Detections.wth_nms method throws an exception when Detections is empty.
  • Changed #36: Detections.wth_nms support class agnostic and non-class agnostic case.

0.3.0 March 6, 2023

  • Changed: Allow Detections.confidence to be None.
  • Added: Detections.from_transformers and Detections.from_detectron2 to enable seamless integration with Transformers and Detectron2 models.
  • Added: Detections.area to dynamically calculate bounding box area.
  • Added: Detections.wth_nms to filter out double detections with NMS. Initial - only class agnostic - implementation.

0.2.0 February 2, 2023

  • Added: Advanced Detections filtering with pandas-like API.
  • Added: Detections.from_yolov5 and Detections.from_yolov8 to enable seamless integration with YOLOv5 and YOLOv8 models.

0.1.0 January 19, 2023

Say hello to Supervision 👋