github roboflow/supervision 0.30.6
supervision-0.30.6

3 hours ago

0.30.6: Pillow-conversion, dataset-export, and line-crossing correctness fixes

supervision 0.30.6 is a patch release closing 12 library correctness bugs and landing 2 behavior refinements across datasets, annotators, key points, line-crossing counting, and the Pillow-based fallback backend, plus a new docs guide and a notebook dead-link fix. No new public API, no breaking changes. The broadest-reach fix rewrites pillow_to_cv2, the entry point every annotator except RichLabelAnnotator and several image helpers use to convert a Pillow image: it used to pass raw mode bytes straight through, so a 1-bit mask, a 16-bit depth map, an LA/PA image, or a CMYK JPEG all drew wrong or crashed. Two fixes close silent-wrong-data bugs rather than crashes: LineZone.trigger let unconfirmed tracks inflate crossing counts, and DetectionDataset.as_yolo/.as_pascal_voc silently dropped box-only objects from a mixed polygon/box COCO dataset. DetectionsSmoother no longer crashes when two tracked objects first seen on different frames carry different metadata (e.g. RF-DETR's source_image); its own docstring pipeline was broken. Every library fix ships a regression test.

✨ Spotlights / highlights

sv.pillow_to_cv2 now converts every Pillow mode the way cv2.imread would

Raw mode bytes used to pass straight through: a 1-bit mask came back as 0/1 instead of 0/255, a 16-bit depth map wrapped modulo 256 and drew as noise, an LA/PA image crashed in cvtColor on its 2-channel array, and a CMYK JPEG drew its cyan/magenta/yellow ink as red/green/blue. This function is the entry point for every annotator except RichLabelAnnotator (which stays on the Pillow path) plus crop_image, resize_image, letterbox_image, scale_image, tint_image, grayscale_image, and plot_image whenever handed a Pillow image, so the bug reached the whole drawing surface, not just one call site. A single-channel grayscale image also drew from a read-only buffer view before this fix. Annotating into it silently failed; it now hands back a writable copy.

from PIL import Image

depth_map = Image.open("depth.tif")  # mode "I;16"
scene = sv.pillow_to_cv2(depth_map)
# clipped to the 16-bit range and keeps its high byte, instead of wrapping mod 256

RGB, RGBA, grayscale, and palette images are unchanged.

sv.LineZone.trigger no longer lets unconfirmed tracks inflate counts

Detections with a negative tracker_id (how trackers like ByteTrackTracker mark an unconfirmed track) all keyed under one shared id. Two distinct unconfirmed objects crossing on opposite sides in the same frame read as one track oscillating, silently inflating in_count/out_count with crossings no confirmed track ever made. Confirmed tracks (tracker_id >= 0) are counted exactly as before.

sv.DetectionDataset.as_yolo / .as_pascal_voc stop dropping box-only objects

from_coco gives an all-zero mask to any annotation without its own segmentation when a sibling annotation has one, and to every annotation when force_masks=True. The exporters only ever wrote the polygon traced from a mask, so a mixed polygon/box COCO dataset silently lost its box-only objects on export, and a box-only COCO dataset loaded with force_masks=True wrote empty YOLO label files or object-less Pascal VOC files. A detection with an empty or contour-less mask now exports as its bounding box instead.

sv.DetectionsSmoother no longer crashes on a second tracked object

Each smoothed track was built on the oldest frame in its window, so two objects first seen on different frames carried different frames' metadata (exactly what the RF-DETR / inference connectors attach), and Detections.merge rejected the mismatch. class_id and data (e.g. class_name) now follow the current frame instead of lagging up to length - 1 frames behind a class change; xyxy, confidence, and oriented-box corners are still averaged as before.

Dataset splits reject an out-of-range ratio instead of silently mis-splitting

sv.DetectionDataset.split, sv.ClassificationDataset.split (both take split_ratio), and the internal train_test_split helper they call (takes train_ratio) never validated the ratio. A finite out-of-range value looked like a successful split: for 10 images, a ratio of -0.2 returned 8/2 via negative slicing, and 1.2 (or an accidental percentage like 80) returned 10/0 with no held-out data, no error either way.

train, test = dataset.split(split_ratio=80)  # meant 80%
# now raises ValueError naming the problem, instead of silently returning
# every image as train and none as test

0 and 1 keep their existing meanings. Not an API break: no signature changed, only previously-silent input now raises.

🔄 Migration guide

No breaking changes in this release.

No deprecations or removals landed in 0.30.6 either. All scheduled remove_in markers in the codebase target 0.31.0 or 0.32.0 and are untouched by this patch release.

Two entries change output values rather than only fixing a crash or a silently-wrong count; not API breaks, but worth checking if your code depends on the old values:

  • sv.KeyPoints.from_ultralytics / .from_inference / .from_detectron2: as_detections().confidence now carries the model's own detection score, not the mean of the per-keypoint confidences.
  • sv.DetectionsSmoother: class_id and data fields on a smoothed track now follow the current frame instead of lagging behind a class change.

📝 Notable changes

🔧 Fixed

Annotators / key points

  • sv.VertexEllipseHaloAnnotator now draws the whole halo of a key point whose covariance ellipse is not horizontal. A vertical or diagonal ellipse used to be clipped to a thin band because the fade box was sized as if the major axis always ran along the image x axis. Horizontal ellipses are drawn exactly as before. (#2634)
  • sv.KeyPoints.from_ultralytics, .from_inference and .from_detectron2 now keep each object's detection score as detection_confidence instead of dropping it, which previously made sv.KeyPoints.with_nms raise ValueError and as_detections() report the mean keypoint confidence instead of the model's own score. (#2633)
  • sv.LabelAnnotator now sizes the label background correctly for a label containing a blank line. Text drew each blank line at full height while the background measured it as zero, pushing the last line outside the box. Labels without blank lines are unchanged. (#2632)
  • sv.IconAnnotator now draws a palette icon that carries its own alpha channel (Pillow PA mode, storable in TIFF) instead of raising ValueError. Palettes without alpha, and every read with OpenCV installed, are unchanged. (#2624)

Datasets

  • sv.DetectionDataset.as_yolo / .as_pascal_voc now write a detection whose mask is empty or has no valid contour as its bounding box instead of silently leaving it out of the label file. (#2631)
  • sv.DetectionDataset.from_yolo now loads a label row carrying a trailing confidence or tracker id (written by Ultralytics save_txt with save_conf=True or tracking on) instead of raising ValueError, for both box and segmentation rows. The extra token is ignored; box and polygon geometry are unchanged. An odd-length polygon row with no extra field now loses its last value rather than raising, since coordinate parity is the only signal separating the two cases. (#2619, #2626, #2635)

Tracking

  • sv.DetectionsSmoother no longer raises ValueError: Conflicting metadata when a second tracked object, first seen on a different frame, enters the smoothing window. (#2628)
  • sv.LineZone.trigger now ignores detections with a negative tracker_id (the value trackers such as ByteTrackTracker report for an unconfirmed track) instead of letting them silently inflate in_count/out_count. (#2623)

Detection utils

  • sv.polygon_to_mask now accepts a list/tuple/array-like of [x, y] vertices (not just a NumPy array) and returns an all-zero mask for an empty or under-3-vertex polygon instead of crashing inside OpenCV/NumPy with an opaque error. A malformed polygon now raises ValueError naming the problem. (#2622)
  • sv.Detections.from_sam3 now keeps SAM 3 PVS contour fragments with fewer than 3 vertices instead of dropping them. Single points and 2-point edges are rasterized directly into the mask and bounding box. (#2625)

Image / IO

  • sv.pillow_to_cv2 now converts every Pillow mode to the 8-bit array cv2.imread would produce, reaching every annotator plus crop_image, resize_image, letterbox_image, scale_image, tint_image, grayscale_image, and plot_image. (#2614)
  • sv.CSVSink now writes UTF-8 on every platform, preserving non-English detection labels and custom fields on Windows. (#2615)

Docs

  • Dead documentation links fixed across the published notebooks (quickstart.ipynb, annotate-video-with-detections.ipynb, underestand-visitors-with-yolo-world.ipynb). (#2630)

🌱 Changed

  • sv.DetectionDataset.split, sv.ClassificationDataset.split, and the internal train_test_split helper they call now reject a ratio outside [0, 1] (NaN and ±inf included) with a ValueError naming the problem, instead of silently mis-splitting or dropping the held-out set. (#2611)
  • sv.tint_image and sv.grayscale_image now accept a single-channel (H, W) array or grayscale Image, as sv.letterbox_image already did. (#2614)
  • New docs guide: "Evaluate targets from a PyTorch DataLoader" in docs/metrics/mean_average_precision.md, cross-linked from docs/how_to/benchmark_a_model.md. Detections.from_transformers gained a docstring note that it expects post-processed predictions, not raw DataLoader targets. (#2620)

🏆 Contributors

  • Mohammad Hijjawi (@MohammadHijjawi97, LinkedIn) — the DetectionsSmoother conflicting-metadata crash, the KeyPoints detection-score fix, the YOLO/Pascal VOC empty-mask export fix, the VertexEllipseHaloAnnotator rotated-ellipse fix, the LabelAnnotator blank-line background fix, and the polygon_to_mask crash fix
  • Raghav Rathi (@raghav-rathi, LinkedIn) — the pillow_to_cv2 rewrite for every Pillow mode
  • Zachary Alexander (@zachsplat) — the LineZone unconfirmed-track fix
  • NIKHIL (@Nikhi00718) — the YOLO segmentation-label trailing-value fix
  • Manoj Kumar Thapa (@iammanoj807, LinkedIn) — the palette-with-alpha IconAnnotator fix
  • kevin (@kevin9327) — the YOLO box-label trailing-value fix
  • Andrew Barnes (@Bortlesboat, LinkedIn) — the CSV Unicode export fix
  • 4rch1e (@archie0732) — the dataset split-ratio validation
  • Salman Ansari (@salmanwnl44, LinkedIn) — the PyTorch DataLoader-targets docs guide
  • Pratik Gandhi (@pratikgx, LinkedIn) — dead-link fixes in the published notebooks
  • Jirka Borovec (@Borda, LinkedIn) — the from_sam3 degenerate-fragment fix

Full changelog: 0.30.5...0.30.6

Don't miss a new supervision release

NewReleases is sending notifications on new releases.