0.30.4: Dataset, connector, and key point correctness fixes
supervision 0.30.4 is a patch release closing 14 correctness and crash bugs across dataset loaders/exporters, model connectors, and key point/annotator/video handling — no new public API, no breaking changes. The most consequential fixes are silent, not crashes: COCO polygon masks loaded shifted by up to a pixel, EXIF-rotated photos loaded with swapped width/height across two loaders and both image backends, and non-ASCII class names were mangled or rejected on Windows across four loaders and one writer. The remaining fixes close hard crashes in the Transformers v4/v5 connectors, Ultralytics pose loading, TraceAnnotator, iterative_seek, and three more dataset-format edge cases. Every fix ships a regression test.
✨ Spotlights / highlights
sv.DetectionDataset.from_coco no longer shifts mask polygons by up to a pixel
COCO polygons commonly hold sub-pixel float coordinates, but the loader cast them straight to int32, which truncates rather than rounds. Every polygon mask loaded shifted up and to the left by up to a pixel — the same polygon loaded one pixel apart depending on the format it was stored in (a square with corners at 2.6/7.6 covered pixels 2–7 from COCO but 3–8 from LabelMe, an IoU of 0.53 between the two masks). Silent, not a crash — training data was quietly misaligned.
dataset = sv.DetectionDataset.from_coco(
images_directory_path="images",
annotations_path="annotations.json",
) # polygon vertices now rounded to nearest pixel, matching from_yolo/from_labelme/from_pascal_vocA non-finite vertex now raises ValueError naming the annotation id, instead of silently propagating.
from_yolo/as_coco no longer swap width and height on EXIF-rotated photos
Photos from phones are often stored sideways with an EXIF orientation tag. cv2.imread applies that tag, but the size read used Pillow's file-header read, which doesn't. For a quarter-turned photo, from_yolo scaled normalized boxes and polygons by the swapped width/height, so they landed outside the image, and as_coco wrote the swapped dimensions. The OpenCV-free fallback backend now also applies the orientation tag, matching how OpenCV itself handles every read except IMREAD_UNCHANGED — the same file previously loaded with a different shape depending on whether opencv-python was installed.
Dataset loaders/writers no longer mangle non-ASCII class names on Windows
from_coco, from_labelme, from_createml, from_yolo read JSON/YAML, and as_pascal_voc wrote XML, using the platform's default encoding — cp1252 on Windows. A class name like café loaded as café; 고양이 raised UnicodeDecodeError. All five now read/write UTF-8 explicitly.
sv.Detections.from_transformers now loads Mask2Former/MaskFormer overlap-safe binary maps
post_process_instance_segmentation(return_binary_maps=True) — the option Transformers recommends when instances can overlap — returns a (num_instances, H, W) stack of binary maps, but the v5 instance path compared it against each segment's id as if it were an id-map, producing a 4-D array mask_to_xyxy rejected outright. Each segment now indexes the stack at its own id, so overlapping instances keep their full masks.
detections = sv.Detections.from_transformers(
transformers_results=processor.post_process_instance_segmentation(
result, target_sizes=[image.size[::-1]], return_binary_maps=True
)[0],
id2label=model.config.id2label,
) # overlapping-instance results now load instead of crashingsv.DetectionDataset.from_pascal_voc no longer silently drops bmp/tif/webp images
The loader only listed .jpg, .jpeg, .png, so every other image was left out without a warning — even though from_yolo/from_folder_structure load those formats and as_pascal_voc writes annotations for them. A dataset exported to Pascal VOC and read back came back smaller than it went out.
🔄 Migration guide
No breaking changes in this release.
No deprecations or removals landed in 0.30.4 either. Eight scheduled-removal commits (ByteTrack, supervision.keypoint, create_tiles/overlay_image, keypoint validators, the legacy MeanAveragePrecision, LMM/from_lmm, and others) plus a new MetricResult ABC exist on develop but are not part of this cherry-picked patch release — they target 0.31.0 and will get their own migration guide when that release ships.
Two internal (non-underscored but unexported) helper functions changed signature as part of fixes in this release: parse_polygon_points (dataset/formats/pascal_voc.py) now returns float64 instead of int, and detections_from_xml_obj's xyxy is now list[list[float]] instead of list[list[int]]. Neither is re-exported from supervision.__init__ or supervision.dataset.__init__, so this is not a public API change — no action needed unless you import these paths directly.
📝 Notable changes
🔧 Fixed
Dataset loaders / exporters
sv.DetectionDataset.from_coconow rounds polygon vertices to the nearest pixel before rasterising masks, instead of truncating them toint32, asfrom_yolo/from_labelme/from_pascal_vocalready do. Truncation shifted every mask up and to the left by up to a pixel — a polygon with corners at2.6/7.6covered pixels 2 to 7 from COCO but 3 to 8 from LabelMe, an IoU of 0.53 between the two masks. A non-finite vertex now raisesValueErrornaming the annotation id; integer vertices and RLE masks load as before. (#2587)sv.DetectionDataset.from_coco/from_labelme/from_createml/from_yolonow read JSON/YAML as UTF-8, andas_pascal_vocwrites XML as UTF-8, instead of the platform default (cp1252 on Windows). A COCO category or YOLOdata.yamlname outside ASCII broke on Windows:caféloaded ascafé,고양이failed withUnicodeDecodeError, andas_pascal_voceither failed withUnicodeEncodeErroror wrote a filefrom_pascal_vocrejected withParseError: not well-formed (invalid token)— Linux/macOS, already UTF-8 by default, are unaffected. (#2585)sv.ClassificationDataset.as_folder_structurenow copies source image files unchanged instead of round-tripping them throughcv2.imread/cv2.imwrite, which dropped alpha channels, downcast 16-bit PNGs to 8-bit, and recompressed JPEGs on every export — matchingsv.DetectionDataset's exports (as_yolo/as_pascal_voc/as_coco), which already copied; exporting into the folder the dataset was loaded from previously rewrote its own images the same lossy way. Images held in memory are still encoded withcv2.imwrite. (#2584)sv.DetectionDataset.from_labelmenow finds images for LabelMe files saved on Windows when loading on Linux or macOS.imagePathis written with\, but the loader took onlyPath(...).name, which doesn't split on\on POSIX — the whole value became the filename and reading failed withValueError: Could not read image from path. The filename is now taken with either separator on every system, as LabelMe itself does when reading its own files; forward-slash paths, and every file on Windows, are unchanged. (#2581)sv.DetectionDataset.from_yolonow loads label files whose class ids are written as decimals (e.g.1.0 0.5 0.5 0.2 0.4), which previously aborted the whole load withValueError: invalid literal for int()—np.savetxtwrites floats by default and Ultralytics tolerates them. Whole numbers load in any notation now; fractional, non-finite, or non-numeric ids still raise, naming the offending id. (#2580)sv.DetectionDataset.from_yolo/as_coconow size EXIF-oriented images the waycv2.imreadloads them (swapping width/height for orientations 5 to 8), instead of reading the un-rotated file-header size via Pillow — quarter-turned photos previously scaled boxes/polygons by the swapped dimensions and produced mismatched mask shapes. The OpenCV-free fallback backend'simread/imdecodenow apply EXIF orientation too, matching OpenCV's behavior for every read exceptIMREAD_UNCHANGED— previously the same file loaded with a different shape depending on whetheropencv-pythonwas installed. (#2577)sv.DetectionDataset.from_pascal_vocno longer fails on annotations with decimal coordinates (e.g.<xmin>48.5</xmin>), which aborted the whole load withValueError: invalid literal for int()— Datumaro, which CVAT uses for its exports, writes VOC this way. Box coordinates are now read as floats and keep their precision; polygon vertices are rounded after the 1-index offset, as the YOLO and LabelMe loaders already do; non-finite values are still rejected. (#2568)sv.DetectionDataset.from_pascal_vocno longer skips.bmp,.tif,.tiff, and.webpimages without a warning — the loader only listed.jpg/.jpeg/.png, even thoughfrom_yolo/from_folder_structureload those formats andas_pascal_vocwrites annotations for them, so a dataset exported to Pascal VOC and read back came back smaller than it went out. It now accepts the same extensions assv.ClassificationDataset.from_folder_structure. (#2569)
Model connectors
sv.Detections.from_transformersnow loads Transformers v5return_binary_maps=Trueinstance results (a(num_instances, H, W)stack), which the v5 path previously compared against each segment'sidas if it were an id-map, producing a 4-D array thatmask_to_xyxyrejected withValueError: too many values to unpack (expected 3). Each segment now indexes the stack at its ownid, keeping full masks for overlapping instances; segment-id-map results are unchanged. (#2576)sv.Detections.from_transformersno longer crashes on a Transformers v4 panoptic result with no segments —post_process_panoptic's emptysegments_infoproduced a(0,)mask array instead of(0, H, W), andmask_to_xyxyraisedValueError: not enough values to unpack (expected 3, got 1). The path now builds a(0, H, W)mask stack and an integerclass_id, matching the v5 paths, and yields emptyDetections. (#2571)
Key points / annotators / video
sv.KeyPoints.from_inferencenow places each key point at the slot given by itsclass_id(skeleton index) instead of appending in received order — Inference drops key points belowkeypoint_confidenceand multi-skeleton models report different counts per object, so stacking as-received either raisedValueError: ... inhomogeneous shapeor silently slid later key points into earlier slots, joining the wrong joints. Omitted slots now stay(0, 0)at zero confidence, already skipped by the key point annotators andas_detections; a result with every key point omitted now loads with zero key points instead of failing validation. (#2575)sv.KeyPoints.from_ultralyticsno longer crashes on pose models whose key points carry no visibility score (kpt_shape=[K,2], whereResults.keypoints.confisNone) — the connector called.cpu()on it unconditionally, raisingAttributeError: 'NoneType' object has no attribute 'cpu'on every non-empty frame. Such results now load withkeypoint_confidence=None; models that do report visibility are unaffected. (#2570)sv.TraceAnnotator.annotateno longer raisesValueError: Length of color lookup 3 does not match length of detections 2when acustom_color_lookupis passed alongside pending (tracker_id == -1) tracks — the annotator skips those detections but still resolved colors against the full-length lookup, although the other annotators accept the same detections and lookup. The lookup is now filtered alongside the detections, so each confirmed track keeps its own color; frames without pending tracks, and calls withoutcustom_color_lookup, are unchanged. (#2586)sv.get_video_frames_generatorno longer readsstartframes pastendwheniterative_seek=True— it countedstartdown to zero, then measuredendfrom that zero instead of fromstart(start=2, end=5yielded frames 2 to 6 instead of 2 to 4;start=4, end=6yielded frames 4 to 9 instead of 4 to 6). A separate counter now keeps both seek modes aligned;start=0and non-iterative calls are unchanged. (#2583)
🏆 Contributors
- kevin (@kevin9327) — every fix in this release (#2568–#2587), including the COCO polygon-vertex rounding, UTF-8 encoding for dataset annotation files on Windows, and EXIF-oriented image sizing across the YOLO/COCO loaders and the cv2-free fallback backend
Full changelog: 0.30.3...0.30.4