github roboflow/supervision 0.31.3
supervision-0.31.3

5 hours ago

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 output

sv.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=False and merge_segments=False to as_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_crowd and merge_segments — new parameters on DetectionDataset.as_yolo, save_yolo_annotations and detections_to_yolo_annotations. (#2786)
  • Depth showcase example — examples/depth_showcase runs Depth Anything V2 Large, Apple Depth Pro and a YOLO depth model through sv.DepthAnnotator on video files, webcams and streams. (#2804, #2818)

🌱 Changed

  • DetectionDataset.as_yolo now skips crowd annotations and writes multi-part masks as one merged polygon line. Pass ignore_crowd=False and merge_segments=False for the previous output. (#2786)

🔧 Fixed

Annotators

  • BackgroundOverlayAnnotator and ComparisonAnnotator raised ValueError on grayscale scenes. (#2832, #2803)
  • Annotators and sv.hex_to_rgba rejected 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 raises FileExistsError, and a path with an embedded null character raises ValueError. (#2800)
  • get_video_frames_generator could yield frames past end for stride=0 or a negative stride; it now raises ValueError. (#2819)
  • DepthMap.from_ultralytics accepted unwrapped integer arrays as depth values. (#2795)
  • KeyPoints.as_detections and with_nms let an extra coordinate change the boxes; they now use x/y only. (#2817)
  • MeanAverageRecall.compute() raised IndexError when 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

  • resize with INTER_LINEAR on uint8 images 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)
  • addWeighted rounds 8-bit, 16-bit and float32 blends like OpenCV. (#2836)
  • blur rounds 8-bit results like OpenCV. (#2811)
  • line, polylines, drawContours and circle at thickness 1, and filled circles, match OpenCV; a negative circle radius raises ValueError. (#2808)
  • fillPoly and filled drawContours match OpenCV for polygons that leave the canvas. (#2810)
  • connectedComponents and connectedComponentsWithStats number 8-connected components in OpenCV's order. (#2812)
  • mean keeps small values when large positive and negative float32 pixels 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 ImageSink refuse to overwrite existing images, rejected non-positive frame strides, and kept float32 precision in the mean fallback.
  • kevin (@kevin9327) — made the NumPy fallback resize and addWeighted match 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_segment and merged multi-part masks in YOLO export.
  • Mohammad Hijjawi (@MohammadHijjawi97, LinkedIn) — made BackgroundOverlayAnnotator work on grayscale scenes.
  • Shreya Boyane (@Sonica-B, LinkedIn) — made ComparisonAnnotator work 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

Don't miss a new supervision release

NewReleases is sending notifications on new releases.