github roboflow/supervision 0.30.4
supervision-0.30.4

5 hours ago

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_voc

A 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 crashing

sv.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_coco now rounds polygon vertices to the nearest pixel before rasterising masks, instead of truncating them to int32, as from_yolo/from_labelme/from_pascal_voc already do. Truncation shifted every mask up and to the left by up to a pixel — a polygon with corners at 2.6/7.6 covered 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 raises ValueError naming the annotation id; integer vertices and RLE masks load as before. (#2587)
  • sv.DetectionDataset.from_coco/from_labelme/from_createml/from_yolo now read JSON/YAML as UTF-8, and as_pascal_voc writes XML as UTF-8, instead of the platform default (cp1252 on Windows). A COCO category or YOLO data.yaml name outside ASCII broke on Windows: café loaded as café, 고양이 failed with UnicodeDecodeError, and as_pascal_voc either failed with UnicodeEncodeError or wrote a file from_pascal_voc rejected with ParseError: not well-formed (invalid token) — Linux/macOS, already UTF-8 by default, are unaffected. (#2585)
  • sv.ClassificationDataset.as_folder_structure now copies source image files unchanged instead of round-tripping them through cv2.imread/cv2.imwrite, which dropped alpha channels, downcast 16-bit PNGs to 8-bit, and recompressed JPEGs on every export — matching sv.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 with cv2.imwrite. (#2584)
  • sv.DetectionDataset.from_labelme now finds images for LabelMe files saved on Windows when loading on Linux or macOS. imagePath is written with \, but the loader took only Path(...).name, which doesn't split on \ on POSIX — the whole value became the filename and reading failed with ValueError: 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_yolo now 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 with ValueError: invalid literal for int()np.savetxt writes 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_coco now size EXIF-oriented images the way cv2.imread loads 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's imread/imdecode now apply EXIF orientation too, matching OpenCV's behavior for every read except IMREAD_UNCHANGED — previously the same file loaded with a different shape depending on whether opencv-python was installed. (#2577)
  • sv.DetectionDataset.from_pascal_voc no longer fails on annotations with decimal coordinates (e.g. <xmin>48.5</xmin>), which aborted the whole load with ValueError: 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_voc no longer skips .bmp, .tif, .tiff, and .webp images without a warning — the loader only listed .jpg/.jpeg/.png, even though from_yolo/from_folder_structure load those formats and as_pascal_voc writes 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 as sv.ClassificationDataset.from_folder_structure. (#2569)

Model connectors

  • sv.Detections.from_transformers now loads Transformers v5 return_binary_maps=True instance results (a (num_instances, H, W) stack), which the v5 path previously compared against each segment's id as if it were an id-map, producing a 4-D array that mask_to_xyxy rejected with ValueError: too many values to unpack (expected 3). Each segment now indexes the stack at its own id, keeping full masks for overlapping instances; segment-id-map results are unchanged. (#2576)
  • sv.Detections.from_transformers no longer crashes on a Transformers v4 panoptic result with no segments — post_process_panoptic's empty segments_info produced a (0,) mask array instead of (0, H, W), and mask_to_xyxy raised ValueError: not enough values to unpack (expected 3, got 1). The path now builds a (0, H, W) mask stack and an integer class_id, matching the v5 paths, and yields empty Detections. (#2571)

Key points / annotators / video

  • sv.KeyPoints.from_inference now places each key point at the slot given by its class_id (skeleton index) instead of appending in received order — Inference drops key points below keypoint_confidence and multi-skeleton models report different counts per object, so stacking as-received either raised ValueError: ... inhomogeneous shape or 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 and as_detections; a result with every key point omitted now loads with zero key points instead of failing validation. (#2575)
  • sv.KeyPoints.from_ultralytics no longer crashes on pose models whose key points carry no visibility score (kpt_shape=[K,2], where Results.keypoints.conf is None) — the connector called .cpu() on it unconditionally, raising AttributeError: 'NoneType' object has no attribute 'cpu' on every non-empty frame. Such results now load with keypoint_confidence=None; models that do report visibility are unaffected. (#2570)
  • sv.TraceAnnotator.annotate no longer raises ValueError: Length of color lookup 3 does not match length of detections 2 when a custom_color_lookup is 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 without custom_color_lookup, are unchanged. (#2586)
  • sv.get_video_frames_generator no longer reads start frames past end when iterative_seek=True — it counted start down to zero, then measured end from that zero instead of from start (start=2, end=5 yielded frames 2 to 6 instead of 2 to 4; start=4, end=6 yielded frames 4 to 9 instead of 4 to 6). A separate counter now keeps both seek modes aligned; start=0 and 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

Don't miss a new supervision release

NewReleases is sending notifications on new releases.