github roboflow/supervision 0.30.0
supervision-0.30.0

4 hours ago

v0.30.0: Run supervision without OpenCV

supervision 0.30.0 makes OpenCV optional. A new private _cv2/ backend (NumPy and Pillow, with PyAV for the video path) reimplements every OpenCV call the library needs, so supervision now runs on opencv-python-headless — or no OpenCV wheel at all — instead of crashing on import. This release also adds Soft-NMS, LabelMe and CreateML dataset formats, GeoTIFF-aware windowed reads for InferenceSlicer, and ships five breaking changes, most notably OpenCV no longer being installed by default, JSONSink switching to native JSON types, and mask_non_max_merge computing exact mask overlap instead of a downscaled approximation. Python 3.9 support is dropped — 3.10 is now the minimum.

✨ Spotlights / highlights

Run supervision without OpenCV

import supervision as sv

window = sv.ImageWindow("frame")
for frame in sv.get_video_frames_generator("input.mp4"):
    window.show(frame)
    if window.wait_key(1) == "q":
        break

The largest change in this release: OpenCV stays the default backend when installed, but supervision no longer requires it — there's no opencv-python extra anymore either. sv.ImageWindow replaces cv2.imshow/cv2.waitKey for display. av>=14.2 is now a required dependency for the PyAV video path during this transition. See the OpenCV migration guide.

Soft-NMS

detections = sv.Detections.from_ultralytics(result)
softened = detections.with_soft_nms(sigma=0.5)
filtered = detections.with_soft_nms(sigma=0.5, score_threshold=0.3)

sv.Detections.with_soft_nms (plus sv.box_soft_non_max_suppression / sv.mask_soft_non_max_suppression) rescales overlapping detections' confidence instead of discarding them outright — useful in crowded scenes where hard NMS drops valid overlapping objects.

New dataset formats + GeoTIFF-aware, batched slicing

dataset = sv.DetectionDataset.from_labelme(
    images_directory_path="images/",
    annotations_directory_path="annotations/",
)

import rasterio

with rasterio.open("RGB.byte.tif") as raster:
    slicer = sv.InferenceSlicer(callback=my_model_callback, batch_size=4)
    detections = slicer(raster)

DetectionDataset.from_labelme/as_labelme and from_createml/as_createml join the existing COCO/YOLO/Pascal-VOC converters. sv.InferenceSlicer can now read an open rasterio dataset window-by-window for multi-GB aerial/drone GeoTIFFs without loading the whole image (pip install "supervision[geotiff]"), and accepts batch_size for batched-callback inference.

sv.load_image_from_url

image = sv.load_image_from_url("https://media.roboflow.com/notebooks/examples/dog.jpeg")

Load an image straight from an HTTP(S) URL as an OpenCV array, with optional on-disk caching.

🔄 Migration guide

Five breaking changes. Most require no code changes beyond a type check or threshold recalibration. The two that need action from most users: the OpenCV install change below, and the Python 3.10 floor.

OpenCV is no longer installed by default. If a compatible cv2 is already importable in your environment, nothing changes for you — it's still preferred automatically. Otherwise install one wheel family yourself (pip install opencv-python or opencv-python-headless) if you need OpenCV-specific behavior, then restart the process — cv2 is detected once at import time. sv.ImageWindow replaces cv2.imshow/cv2.waitKey. Full guide: docs/how_to/opencv_migration.md.

Python 3.10+ is now required — 3.9 reached end-of-life in October 2025.

sv.JSONSink now emits native JSON types, not strings:

# before 0.30.0
row["score"] == "0.85"  # str
row["is_valid"] == "True"  # str

# after 0.30.0
row["score"] == 0.85  # float
row["is_valid"] is True  # bool

sv.CSVSink stays textual, but its per-row custom-data slicing now matches JSONSink.

sv.mask_non_max_merge computes exact mask overlap, not a downscaled approximation, and ignores the now-deprecated mask_dimension parameter (kept for signature compatibility, removal in 0.33.0). Re-tune your overlap threshold after upgrading. Passing overlap_metric/mask_dimension positionally still works — the values are still honored — but now emits a DeprecationWarning; pass them by keyword to silence it. More than five positional arguments raises TypeError.

Detections.merge() on mixed dense + CompactMask inputs now returns a CompactMask, not a plain ndarray:

merged = sv.Detections.merge([dense_detections, compact_mask_detections])
isinstance(merged.mask, np.ndarray)  # was True, now False — it's a CompactMask

Only affects code that explicitly merges a CompactMask-carrying Detections object with a dense-mask one yourself — InferenceSlicer, DetectionsSmoother, and with_nms/with_nmm always merge type-homogeneous lists internally, so they're unaffected. The all-dense merge path is also unchanged. This is a substantial performance win: ~2500× less peak memory, ~13× faster on a 1080p frame with 40 detections. If you need the old return type without touching every call site: call merged.mask = merged.mask.to_dense() right after merge(), or avoid producing CompactMask in the first place (Detections.from_inference(compact_masks=False), the default).

supervision also now requires av>=14.2 as an install-time dependency for the PyAV cv2-free video path — this doesn't change any API, so it isn't counted as breaking, but pinned/vendored environments should account for it.

Deprecation removals pushed back one release: ByteTrack, supervision.keypoint, normalized_xyxy, and supervision.dataset.utils RLE compatibility shims — originally scheduled for removal in 0.30.0 — are now scheduled for 0.31.0 instead, giving a full transition window.

📝 Notable changes

🚀 Added

  • sv.load_image_from_url — load an HTTP(S) image as an OpenCV array, with optional on-disk caching (#2372)
  • cv2-free PyAV video fallback + private _cv2 backend facade — image/geometry/drawing/text/video without OpenCV (#2430, #2431, #2432, #2433, #2435, #2438, #2439, #2440, #2441, #2443)
  • sv.ImageWindow — tkinter+Pillow desktop window replacing cv2.imshow/cv2.waitKey (#2320)
  • Soft-NMSsv.box_soft_non_max_suppression, sv.mask_soft_non_max_suppression, sv.Detections.with_soft_nms (#1624)
  • sv.VLM.GOOGLE_GEMINI_3_5Detections.from_vlm parses Gemini 3.5 output (#2449)
  • get_video_frames_generator(prefetch=...) — background-thread decode into a bounded queue (#2273)
  • PolygonZone(require_all_anchors=...) — toggle all-anchors vs. any-anchor containment (#2272)
  • KeyPoints.merge() — combine a list of KeyPoints, mirroring Detections.merge (#2412)
  • BaseAnnotator.requires_mask — class-level flag on all annotators (#2370)
  • CompactMask.from_coco_rle + Detections.from_inference(compact_masks=True) (#2367)
  • CompactMask.image_shape property (#2383)
  • sv.mask_to_roi — exclusive mask-bound helper for slicing/crops (#2416)
  • DetectionDataset.from_labelme/as_labelme (#2299)
  • DetectionDataset.from_createml/as_createml (#2284)
  • InferenceSlicer GeoTIFF supportsv.WindowedRasterDataset, pip install "supervision[geotiff]" (#2281)
  • InferenceSlicer(batch_size=...) — batched callback contract (#1239)
  • ConfusionMatrix.benchmark(save_directory_path=...) — adaptive TP/FP/FN validation-mosaic export (#2271)
  • HeatMapAnnotator.reset(), TraceAnnotator.reset(), DetectionsSmoother.reset() — clear accumulated per-stream state, so a single instance can be reused across independent streams (#2418)
  • AREA_DATA_FIELD config constant (#2428)
  • sv.denormalize_boxes and sv.xyxyxyxy_to_xyxy now exported at the top level

⚠️ Breaking Changes

  • OpenCV no longer installed by default; no OpenCV extra (#2443)
  • Python 3.10+ required — 3.9 dropped (#2260, #2381)
  • sv.JSONSink emits native JSON types instead of strings; sv.CSVSink custom-data slicing now matches JSONSink (#2400)
  • sv.mask_non_max_merge computes exact overlap, ignores mask_dimension, positional overlap_metric/mask_dimension deprecated (#2400)
  • Detections.merge() on mixed dense + CompactMask inputs returns CompactMask (#2383)

🌱 Changed

  • DetectionDataset/ClassificationDataset equality now compares ordered classes lists, not an unordered set
  • supervision now requires av>=14.2 as an install-time dependency for the cv2-free video fallback — no API change (#2438)
  • Deprecation-window delays: ByteTrack, supervision.keypoint, normalized_xyxy, dataset-utils RLE compat removals moved 0.30.00.31.0
  • Perf: count_nonzero mask pixel counts (#2361), vectorized box_iou_batch_with_jaccard (#2359), faster mask-annotation ROI blending (#2368), fewer corner circles on square label backgrounds (#2346), less compact-mask materialization in the polygon annotator (#2369)
  • Geometry-aware IoU/area dispatch centralized (#2374)

🔧 Fixed

  • sv.Recall tracks prediction-only classes, matching Precision/F1Score (#2467, #2468)
  • DetectionDataset.from_pascal_voc no longer raises on background images, with or without force_masks=True (#2463, #2469)
  • import supervision no longer surfaces the deprecated ByteTrack warning
  • Reopening sv.CSVSink/sv.JSONSink starts a fresh session — no stale rows or header (#2459)
  • from_vlm Gemini 2.0/2.5/3.5 salvages valid entries from partially malformed JSON arrays (#2449)
  • save_coco_annotations/as_coco read image sizes from headers, no pixel decode for labels-only export (#2442)
  • sv.F1Score no longer emits a spurious div-by-zero RuntimeWarning (#2437)
  • Size-bucketed Precision/Recall/F1Score no longer miscount out-of-bucket detections (#2427, #2428, #2408)
  • sv.box_iou_batch upcasts corners to float64, fixing int32-coordinate overflow into a wrong 0.0 IoU (#2418)
  • from_tensorflow scales boxes by correct axes (#2360); from_inference stays aligned on partial masks (#2362) and partial tracker_id (#2353)
  • get_anchors_coordinates is OBB-aware (#2382)
  • Annotator clipping: CropAnnotator (#2391), HeatMapAnnotator uint8 wrap (#2393), BackgroundOverlayAnnotator negative coords (#2396); get_video_frames_generator releases capture via try/finally (#2393)
  • ByteTrack no longer mutates input Detections; hardened edge cases (#2413)
  • KeyPoints.as_detections accepts numpy/tuple/generator indices (#2402)
  • hex_to_rgba rejects multiple leading # (#2421); Color(...) validates RGBA range (#2407)
  • ColorPalette.by_idx() on empty palette raises ValueError, not ZeroDivisionError (#2407)
  • Metrics scoring hardening: greedy matching (#2380), COCO 101-point AP, ConfusionMatrix rejects invalid class ids, per-class recall per max-det cutoff, user ignore flags preserved; FP counted on empty-GT images (#2397)
  • Dataset IO hardening — no caller mutation, class-id validation, optional COCO fields, VOC determinized, basename-collision preflight, RGBA/palette PNG support (#2394, #2410, #2416)
  • Classifications.from_timm softmaxes logits; download_assets verifies MD5 + retries once (#2414)
  • ImageSink.save_image() raises OSError on write failure (#2416)
  • Replaced deprecated 2-D np.cross with explicit determinant (#2386); removed defensive asserts in image annotators (#2354)
  • cv2-free fallback correctness fixes across border/blend/polygon/text/color operations (#2431, #2433, #2439, #2440, #2441)

🏆 Contributors

  • Abhijith Neil Abraham (@abhijithneilabraham, LinkedIn) — added KeyPoints.merge(); fixed out-of-bucket metric scoring and key_points edge cases
  • Agis Kounelis (@kounelisagis, LinkedIn) — made get_anchors_coordinates OBB-aware; kept from_inference aligned on partial data
  • Andrew Barnes (@Bortlesboat, LinkedIn) — fixed sink state on reopen
  • Arthi Arumugam (@arthi-arumugam-git, LinkedIn) — fixed the Recall metric to track prediction-only classes
  • Dylan Parsons (@dylanparsons, LinkedIn) — converted Detections doctests to runnable examples
  • Erik (@Erol444) — added sv.load_image_from_url
  • Yann Hallouard (@YHallouard, LinkedIn) — added Soft-NMS
  • Lee Clement (@leeclemnet) — fixed COCO export to read image sizes from headers
  • Linas Kondrackis (@LinasKo, LinkedIn) — added batching to InferenceSlicer
  • Madhav-C (@madhavcodez, LinkedIn) — added LabelMe and CreateML dataset formats, GeoTIFF InferenceSlicer support
  • Mahbod (@Ace3Z) — added prefetch to get_video_frames_generator, require_all_anchors to PolygonZone
  • Matt Van Horn (@mvanhorn, LinkedIn) — centralized geometry-aware IoU/area dispatch
  • Murillo Rodrigues (@murillo-ro-silva, LinkedIn) — added show_progress to dataset load/save (0.29.1)
  • Nick Herrig (@NickHerrig, LinkedIn) — added the face-blurring cookbook
  • Piotr Skalski (@SkalskiP, LinkedIn) — added Gemini 3.5 Flash VLM support
  • Ruben (@RubenHaisma) — perf fixes across mask counting, box IoU, from_tensorflow
  • Saif Khan (@K-saif, LinkedIn) — added the adaptive TP/FP/FN validation mosaic export
  • Shadow_Lu (@LuShadowX) — fixed class_id to stay integral for VOC background images
  • shao (@shaoming11, LinkedIn) — improved draw/utils.py doctests
  • Shehzad Waseem (@Shehzad3684) — fixed a division-by-zero warning in F1Score
  • Teïlo M (@teilomillet) — fixed the hex parser accepting multiple leading prefixes
  • Vikas Saini (@vikassaini77, LinkedIn) — converted fenced examples to doctests; removed defensive asserts in annotators
  • Jirka Borovec (@Borda, LinkedIn) — built the cv2-free OpenCV-optional backend (image, geometry, drawing, text, and PyAV video fallback) end to end, plus various hardening fixes across detection, dataset, and metrics modules; release maintainer

Full changelog: 0.29.1...0.30.0

Don't miss a new supervision release

NewReleases is sending notifications on new releases.