v0.31.3 — Grayscale annotators, OpenCV-exact fallbacks, and merged YOLO polygons
Grayscale scenes work with more annotators, the pure NumPy fallback matches OpenCV pixel for pixel, and YOLO export handles multi-part masks.
- YOLO export merges multi-part masks into one polygon and skips crowd annotations, with flags for the old output.
- Background overlay and comparison annotators draw on grayscale scenes.
- Without OpenCV, resize, blur, blending, drawing and connected components give the same pixels as OpenCV.
- Invalid input fails early:
ImageSink, the video frame generator and depth arrays now raise clear errors. - Short hex colors such as
"#f0f"work in annotators.
Drop-in upgrade unless you export YOLO labels: the default output changes, see the Migration guide.
✨ Spotlights / highlights
YOLO export merges multi-part masks and skips crowd annotations
DetectionDataset.as_yolo now omits detections whose iscrowd flag is set and writes a mask with several disconnected parts as one merged polygon line instead of one line per part. Both are on by default. The merge is done by the new sv.merge_multi_segment, which joins the parts through zero-width seams at their nearest vertices. (#2786)
import supervision as sv
dataset = sv.DetectionDataset(...)
dataset.as_yolo(
images_directory_path="images",
annotations_directory_path="labels",
data_yaml_path="data.yaml",
) # 0.31.3: crowd skipped, one merged polygon line per mask
dataset.as_yolo(
images_directory_path="images",
annotations_directory_path="labels",
data_yaml_path="data.yaml",
ignore_crowd=False,
merge_segments=False,
) # the 0.31.2 outputsv.merge_multi_segment
New public function. Pass it the polygon parts of one mask and get back a single polygon. (#2786)
import numpy as np
import supervision as sv
a = np.array([[0, 0], [10, 0], [10, 10], [0, 10]])
b = np.array([[30, 30], [40, 30], [40, 40], [30, 40]])
sv.merge_multi_segment([a, b]).shape
# (10, 2)Annotators on grayscale scenes
BackgroundOverlayAnnotator and ComparisonAnnotator now draw on scenes shaped (H, W) or (H, W, 1), or given as a Pillow "L" image, instead of raising ValueError. Short hex colors such as "#f0f" now work in annotators and sv.hex_to_rgba. (#2832, #2803, #2801)
import numpy as np
import supervision as sv
scene = np.zeros((100, 100), dtype=np.uint8)
detections = sv.Detections(
xyxy=np.array([[20, 20, 60, 60]], dtype=float), class_id=np.array([0])
)
sv.BackgroundOverlayAnnotator().annotate(scene.copy(), detections).shape
# (100, 100) (0.31.2: ValueError)The NumPy fallback matches OpenCV pixel for pixel
Without OpenCV installed, resize (INTER_LINEAR on uint8), blur, addWeighted, thin lines, circles, filled polygons that leave the canvas, connectedComponents and mean now give the same pixels as OpenCV. BlurAnnotator, HaloAnnotator, HeatMapAnnotator, PixelateAnnotator, the blending annotators, sv.resize_image, sv.letterbox_image and PolygonZone masks are affected. (#2833, #2836, #2811, #2808, #2810, #2812, #2820)
Bad input fails early
sv.ImageSink(overwrite=False) raises FileExistsError instead of replacing an existing image, sv.get_video_frames_generator rejects a stride below 1, and DepthMap.from_ultralytics rejects unwrapped integer arrays. (#2800, #2819, #2795)
import supervision as sv
for frame in sv.get_video_frames_generator("video.mp4", stride=0):
...
# ValueError: stride must be >= 1, got 0🔄 Migration guide
One default behavior changes: YOLO export. No public symbol was removed or renamed in this release, and nothing is deprecated.
YOLO export defaults affect sv.DetectionDataset.as_yolo, and save_yolo_annotations and detections_to_yolo_annotations in supervision.dataset.formats.yolo. Before 0.31.3 export skipped nothing and wrote a mask with several disconnected parts as one polygon line per part. From 0.31.3 export drops detections whose iscrowd flag is set in detections.data, and writes the parts of a mask as one merged polygon line. Label files exported with 0.31.3 can therefore differ from those exported with 0.31.2 for the same dataset. (#2786)
- Nothing to do if you want crowd annotations dropped and one polygon line per mask.
- To keep the 0.31.2 output, pass
ignore_crowd=Falseandmerge_segments=Falsetoas_yolo. - Re-export datasets you trained on from 0.31.2 labels only if the crowd or multi-part masks matter to your training run.
Three bug fixes also turn silent behavior into an error: ImageSink(overwrite=False) raises FileExistsError for an existing image (#2800), get_video_frames_generator raises ValueError for a stride below 1 (#2819), and DepthMap.from_ultralytics rejects unwrapped integer arrays (#2795).
📝 Notable changes
🚀 Added
sv.merge_multi_segment— joins the disconnected polygon segments of one mask into a single polygon. (#2786)ignore_crowdandmerge_segments— new parameters onDetectionDataset.as_yolo,save_yolo_annotationsanddetections_to_yolo_annotations. (#2786)- Depth showcase example —
examples/depth_showcaseruns Depth Anything V2 Large, Apple Depth Pro and a YOLO depth model throughsv.DepthAnnotatoron video files, webcams and streams. (#2804, #2818)
🌱 Changed
DetectionDataset.as_yolonow skips crowd annotations and writes multi-part masks as one merged polygon line. Passignore_crowd=Falseandmerge_segments=Falsefor the previous output. (#2786)
🔧 Fixed
Annotators
BackgroundOverlayAnnotatorandComparisonAnnotatorraisedValueErroron grayscale scenes. (#2832, #2803)- Annotators and
sv.hex_to_rgbarejected 3- and 4-digit short hex colors such as"#f0f". (#2801)
Input validation, I/O and metrics
ImageSink(overwrite=False)silently replaced an existing image, including through a dangling symlink; it now raisesFileExistsError, and a path with an embedded null character raisesValueError. (#2800)get_video_frames_generatorcould yield frames pastendforstride=0or a negative stride; it now raisesValueError. (#2819)DepthMap.from_ultralyticsaccepted unwrapped integer arrays as depth values. (#2795)KeyPoints.as_detectionsandwith_nmslet an extra coordinate change the boxes; they now use x/y only. (#2817)MeanAverageRecall.compute()raisedIndexErrorwhen filtering by object size with shared scalar metadata. (#2791)download_assets(VideoAssets.GROCERY_STORE)failed MD5 verification; the catalog now holds the checksum of the hosted file. (#2804)
NumPy fallback without OpenCV
resizewithINTER_LINEARonuint8images now matches OpenCV pixel for pixel; arm64 builds that hand some grayscale and RGBA resizes to the Carotene HAL can differ by up to two levels. (#2833)addWeightedrounds 8-bit, 16-bit and float32 blends like OpenCV. (#2836)blurrounds 8-bit results like OpenCV. (#2811)line,polylines,drawContoursandcircleat thickness 1, and filled circles, match OpenCV; a negative circle radius raisesValueError. (#2808)fillPolyand filleddrawContoursmatch OpenCV for polygons that leave the canvas. (#2810)connectedComponentsandconnectedComponentsWithStatsnumber 8-connected components in OpenCV's order. (#2812)meankeeps small values when large positive and negativefloat32pixels cancel. (#2820)
🏆 Contributors
Welcome to our first-time contributors: Oke Iyanuoluwa Enoch, Maidul Hasan Masum, Jooyoung Lim, Zhenhuan Wang, Shreya Boyane, Carlos Marañes, Vuln3rat0r and reuben rouse!
- Oke Iyanuoluwa Enoch (@Iyanuoluwa007, LinkedIn) — made the NumPy fallback match OpenCV for thin lines, circles, polygons, blur and connected components, and added strict type checking across the tests.
- LEE, WEI-HONG (@AuricTW) — made
ImageSinkrefuse to overwrite existing images, rejected non-positive frame strides, and keptfloat32precision in the mean fallback. - kevin (@kevin9327) — made the NumPy fallback
resizeandaddWeightedmatch OpenCV. - A Aswanth Raj (@aswanth-07, LinkedIn) — built key point boxes from x/y coordinates and kept scalar metadata in recall size buckets.
- Vuln3rat0r (@Vuln3rat0r) — added
sv.merge_multi_segmentand merged multi-part masks in YOLO export. - Mohammad Hijjawi (@MohammadHijjawi97, LinkedIn) — made
BackgroundOverlayAnnotatorwork on grayscale scenes. - Shreya Boyane (@Sonica-B, LinkedIn) — made
ComparisonAnnotatorwork on grayscale scenes. - Carlos Marañes (@cmaranes) — accepted short hex colors in annotators.
- reuben rouse (@reubenjrouse, LinkedIn) — rejected unwrapped integer arrays in
DepthMap.from_ultralytics. - Jooyoung Lim (@mmporong, LinkedIn) — fixed docstring examples that were not valid Python.
- Maidul Hasan Masum (@mh-masum6875) — added type checking to the dataset, metrics and detection tests.
- Zhenhuan Wang (@NaCr05) — enabled strict typing for the conversion and iterable tests.
- Jirka Borovec (@Borda, LinkedIn) — added the depth showcase example and prepared the release.
Automated contributions: Dependabot, Copilot, Claude, Codex, pre-commit.ci
Full changelog: 0.31.2...0.31.3