v0.31.4 — Stricter dataset export, COCO-exact recall buckets, and a closer NumPy fallback
Fourteen bug fixes: stricter dataset export, COCO-exact recall buckets, sturdier VLM parsing, and a NumPy fallback closer to OpenCV.
- CreateML and Pascal VOC export raise a clear
ValueErrorfor aclass_idoutsideclasses. DetectionDataset.mergekeeps annotations that have no class IDs instead of raising.- MeanAverageRecall counts boundary-area objects in both COCO size buckets, like
MeanAveragePrecision. - Qwen and PaliGemma parsing keep valid boxes and class labels beside malformed ones.
- Without OpenCV, image reads, gray conversion, drawing colors and keyword parameters match OpenCV.
Drop-in upgrade; see the Migration guide if you export out-of-range class IDs or compare mAR values or fallback pixels against stored references.
✨ Spotlights / highlights
Dataset export rejects out-of-range class IDs
detections_to_createml_annotations and detections_to_pascal_voc now raise ValueError for a class_id outside classes, as detections_to_labelme_shapes already does. A negative id was exported under the last class name, and an id past the end raised IndexError. (#2859)
import numpy as np
import supervision as sv
from supervision.dataset.formats.createml import detections_to_createml_annotations
detections = sv.Detections(
xyxy=np.array([[10.0, 10.0, 50.0, 50.0]]), class_id=np.array([-1])
)
detections_to_createml_annotations(detections, classes=["cat", "dog"])
# ValueError: class_id -1 at detection index 0 is out of range for classes list of length 2.
# 0.31.3: exported with the label "dog"DetectionDataset.merge keeps annotations without class IDs
Merging datasets used to raise ValueError when some annotations had no class_id. They are now kept as they are while labeled annotations are remapped. (#2858)
import numpy as np
import supervision as sv
labeled = sv.DetectionDataset(
classes=["a"],
images=["x.jpg"],
annotations={
"x.jpg": sv.Detections(xyxy=np.array([[0.0, 0, 5, 5]]), class_id=np.array([0]))
},
)
unlabeled = sv.DetectionDataset(
classes=["b"],
images=["y.jpg"],
annotations={"y.jpg": sv.Detections(xyxy=np.array([[0.0, 0, 5, 5]]))},
)
merged = sv.DetectionDataset.merge([labeled, unlabeled])
merged.classes
# ['a', 'b'] (0.31.3: ValueError)MeanAverageRecall size buckets match COCO
An object whose area is exactly 32² or 96² now counts in both neighbouring size buckets, the inclusive ranges pycocotools and MeanAveragePrecision use. It used to land in only one, so mAR and mAP disagreed on the small, medium and large results. (#2848)
import numpy as np
import supervision as sv
from supervision.metrics import MeanAverageRecall
box = np.array([[0.0, 0.0, 32.0, 32.0]]) # area is exactly 32²
target = sv.Detections(xyxy=box, class_id=np.array([0]))
prediction = sv.Detections(xyxy=box, class_id=np.array([0]), confidence=np.array([0.9]))
result = MeanAverageRecall().update([prediction], [target]).compute()
result.small_objects.mAR_at_100, result.medium_objects.mAR_at_100
# (1.0, 1.0) (0.31.3: (0.0, 1.0))🔄 Migration guide
No public symbol was removed or renamed, and nothing is deprecated. A few fixes change what existing code returns or raises:
- New error: CreateML and Pascal VOC export raise
ValueErrorfor aclass_idoutsideclasses. Make sure every exported id indexes into yourclasseslist; filter or remap ids such as-1first. (#2859) - Different numbers:
MeanAverageRecallsmall, medium and large results can change for datasets with objects of area exactly32²or96². (#2848) - Different pixels without OpenCV: drawing colors saturate instead of wrapping,
uint16gray conversion and 16-bit grayscale reads follow OpenCV, andIMREAD_GRAYSCALEreturns a two-dimensionaluint8image. Regenerate stored reference images from 0.31.4. (#2854, #2855, #2840, #2841)
📝 Notable changes
🔧 Fixed
Datasets
detections_to_createml_annotationsanddetections_to_pascal_vocraiseValueErrorfor aclass_idoutsideclasses; a negative id was exported under the last class name and a too-large id raisedIndexError. (#2859)DetectionDataset.mergeraisedValueErrorwhen some annotations had no class IDs; it now keeps them and remaps the labeled ones. (#2858)
Metrics
MeanAverageRecallcounts objects of area exactly32²or96²in both neighbouring COCO size buckets, asMeanAveragePrecisiondoes. (#2848)
Detections, VLM and slicing
Detections.from_ultralyticspassed an empty crop to image resizing when restoring masks from thin letterboxed images; it now keeps at least one content pixel per axis. (#2857)scale_boxeskeeps representable corners when opposite-sign float coordinates overflow the intermediate box width, including float16 boxes from-40000to40000scaled by0.5. (#2853)- Qwen parsing skips malformed or non-finite boxes and keeps the valid detections from the same response. (#2851)
- PaliGemma parsing keeps punctuation in class labels such as
person (left)andcat/dog, so class filtering no longer drops them. (#2852) Detections.from_vlmreturns an integerclass_idwhen a Geminiclassesfilter removes every detection and when a DeepSeek-VL2 response has none; it was afloat64array. (#2839)InferenceSlicer(compact_masks=True)keeps the dense masks on callback-owned detections, so callbacks that reuse their output buffers no longer raiseAttributeErrorafter the first tile. (#2847)
NumPy fallback without OpenCV
- Image reads honor
IMREAD_GRAYSCALEfor files and bytes, includingsv.load_image_from_url(..., cv_imread_flags=0), and keep the high byte of 16-bit grayscale PNG and TIFF images. (#2841, #2840) cvtColor(..., COLOR_BGR2GRAY)convertsuint16images with OpenCV's fixed-point weights. (#2855)- Drawing operations and
copyMakeBordersaturate colors and round floating-point inputs for integer images; out-of-range border constants no longer raiseOverflowErrorunder NumPy 2.x. (#2854) blur,approxPolyDPandintersectConvexConvextake OpenCV's parameter names, so keyword calls work, andintersectConvexConvex(handleNested=False)returns no intersection for strictly nested polygons. (#2856)
🏆 Contributors
- kevin (@kevin9327) — made
MeanAverageRecallsize buckets match COCO, converteduint16images to gray like OpenCV, named theblur,approxPolyDPandintersectConvexConvexfallback parameters, and rejected out-of-range class IDs in dataset export. - NIKHIL (@Nikhi00718, LinkedIn) — kept valid Qwen boxes beside malformed ones, preserved punctuation in PaliGemma labels, and fixed
scale_boxescorner overflow. - A Aswanth Raj (@aswanth-07, LinkedIn) — restored masks from thin Ultralytics images and kept slicer callback masks reusable.
- LEE, WEI-HONG (@AuricTW) — honored grayscale read flags and kept the high byte of 16-bit image reads.
- Vuln3rat0r (@Vuln3rat0r) — made fallback drawing colors saturate and round like OpenCV.
- Devulapalli Naga Sri Vaishnavi (@Vaishnavi220506, LinkedIn) — made
DetectionDataset.mergekeep annotations without class IDs. - Mohammad Hijjawi (@MohammadHijjawi97, LinkedIn) — kept
class_idintegral when Gemini or DeepSeek-VL2 return nothing. - Zhenhuan Wang (@NaCr05) — enabled strict typing for the video tests.
- Oke Iyanuoluwa Enoch (@Iyanuoluwa007, LinkedIn) — type checked the metrics tests.
- Jirka Borovec (@Borda, LinkedIn) — prepared the release.
Automated contributions: Dependabot, Codex, pre-commit.ci
Full changelog: 0.31.3...0.31.4
Co-authored with /oss:release.