0.30.0
lfnovo/open-notebook0.30.0Aug 4, 2026by Borda
AI Summary
A major release making OpenCV optional by introducing a private `_cv2` backend, adding Soft-NMS and new dataset formats (LabelMe, CreateML), and dropping Python 3.9 support.
Key Highlights
- Run supervision without OpenCV using the new `_cv2` backend (NumPy/Pillow/PyAV) and `sv.ImageWindow`.
- Introduction of Soft-NMS for non-maximum suppression that rescales confidence instead of discarding detections.
- New dataset formats: `DetectionDataset.from_labelme` and `from_createml`.
- GeoTIFF-aware, batched slicing support for `InferenceSlicer`.
- `sv.load_image_from_url` added for loading images directly from HTTP(S).
Breaking Changes
- OpenCV is no longer installed by default; users must install it manually if needed.
- Python 3.10+ is now required (3.9 support dropped).
- `sv.JSONSink` now emits native JSON types (float/bool) instead of strings.
- `sv.mask_non_max_merge` computes exact mask overlap instead of a downscaled approximation.
- `Detections.merge()` on mixed dense + `CompactMask` inputs now returns a `CompactMask` object.
New Features
- cv2-free PyAV video fallback + private `_cv2` backend facade.
- `sv.ImageWindow` for display.
- Soft-NMS functions (`sv.box_soft_non_max_suppression`, `sv.mask_soft_non_max_suppression`).
- `sv.VLM.GOOGLE_GEMINI_3_5` support.
- `get_video_frames_generator(prefetch=...)` for background-thread decoding.
- `PolygonZone(require_all_anchors=...)` toggle.
- `KeyPoints.merge()` method.
- `BaseAnnotator.requires_mask` class-level flag.
- `CompactMask` features (`from_coco_rle`, `image_shape`).
- `sv.mask_to_roi` helper.
- `DetectionDataset` LabelMe and CreateML formats.
- `InferenceSlicer` GeoTIFF support and `batch_size` parameter.
- `ConfusionMatrix.benchmark(save_directory_path=...)`.
- Reset methods for `HeatMapAnnotator`, `TraceAnnotator`, and `DetectionsSmoother`.
Full Release Notes
# 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
```python
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](docs/how_to/opencv_migration.md).
### Soft-NMS
```python
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
```python
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`
```python
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](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:
```python
# 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`:
```python
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-NMS** — `sv.box_soft_non_max_suppression`, `sv.mask_soft_non_max_suppression`, `sv.Detections.with_soft_nms` (#1624)
- **`sv.VLM.GOOGLE_GEMINI_3_5`** — `Detections.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 support** — `sv.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.0` → `0.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](https://www.linkedin.com/in/abhijith-neil-abraham-765165141)) — added `KeyPoints.merge()`; fixed out-of-bucket metric scoring and `key_points` edge cases
- **Agis Kounelis** (@kounelisagis, [LinkedIn](https://linkedin.com/in/kounelisagis)) — made `get_anchors_coordinates` OBB-aware; kept `from_inference` aligned on partial data
- **Andrew Barnes** (@Bortlesboat, [LinkedIn](https://www.linkedin.com/in/andrew-barnes-705a08195/)) — fixed sink state on reopen
- **Arthi Arumugam** (@arthi-arumugam-git, [LinkedIn](https://www.linkedin.com/in/arthiarumugam99/)) — fixed the Recall metric to track prediction-only classes
- **Dylan Parsons** (@dylanparsons, [LinkedIn](https://www.linkedin.com/in/dylanparsons)) — converted `Detections` doctests to runnable examples
- **Erik** (@Erol444) — added `sv.load_image_from_url`
- **Yann Hallouard** (@YHallouard, [LinkedIn](https://www.linkedin.com/in/yann-hallouard/)) — added Soft-NMS
- **Lee Clement** (@leeclemnet) — fixed COCO export to read image sizes from headers
- **Linas Kondrackis** (@LinasKo, [LinkedIn](https://www.linkedin.com/in/LinasKo)) — added batching to `InferenceSlicer`
- **Madhav-C** (@madhavcodez, [LinkedIn](https://www.linkedin.com/in/madhav-s-c)) — 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](https://www.linkedin.com/in/mattvanhorn)) — centralized geometry-aware IoU/area dispatch
- **Murillo Rodrigues** (@murillo-ro-silva, [LinkedIn](https://www.linkedin.com/in/murillo-rodrigues/)) — added `show_progress` to dataset load/save (0.29.1)
- **Nick Herrig** (@NickHerrig, [LinkedIn](https://www.linkedin.com/in/nickherrig/)) — added the face-blurring cookbook
- **Piotr Skalski** (@SkalskiP, [LinkedIn](https://www.linkedin.com/in/skalskip92/)) — added Gemini 3.5 Flash VLM support
- **Ruben** (@RubenHaisma) — perf fixes across mask counting, box IoU, `from_tensorflow`
- **Saif Khan** (@K-saif, [LinkedIn](https://www.linkedin.com/in/saif-khan-396348231/)) — 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](https://www.linkedin.com/in/shaoming-wu/)) — 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](https://www.linkedin.com/in/vikas-saini1/)) — converted fenced examples to doctests; removed defensive asserts in annotators
- **Jirka Borovec** (@Borda, [LinkedIn](https://linkedin.com/in/jirka-borovec)) — 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**: https://github.com/roboflow/supervision/compare/0.29.1...0.30.0