0.30.4

triggerdotdev/trigger.dev0.30.4Sep 17, 2026by Borda

Upgrading?

AI Summary

A patch release fixing dataset loader correctness issues, specifically regarding mask polygon shifting, EXIF rotation, and non-ASCII text handling.

Key Highlights

  • Fixed COCO polygon mask shifting by rounding vertices to the nearest pixel.
  • Fixed EXIF-rotated photos where width and height were swapped in loaders.
  • Fixed non-ASCII class name handling on Windows by enforcing UTF-8 encoding.
  • Fixed crashes in Transformers v4/v5 connectors and Ultralytics pose loading.

Full Release Notes

# 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.

```python
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.

```python
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](https://github.com/roboflow/supervision/pull/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](https://github.com/roboflow/supervision/pull/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](https://github.com/roboflow/supervision/pull/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](https://github.com/roboflow/supervision/pull/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](https://github.com/roboflow/supervision/pull/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](https://github.com/roboflow/supervision/pull/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](https://github.com/roboflow/supervision/pull/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](https://github.com/roboflow/supervision/pull/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](https://github.com/roboflow/supervision/pull/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](https://github.com/roboflow/supervision/pull/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](https://github.com/roboflow/supervision/pull/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](https://github.com/roboflow/supervision/pull/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](https://github.com/roboflow/supervision/pull/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](https://github.com/roboflow/supervision/pull/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**: https://github.com/roboflow/supervision/compare/0.30.3...0.30.4

Related Repositories