feat: Detections.from_inference supports compressed RLE masks#2178
feat: Detections.from_inference supports compressed RLE masks#2178
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #2178 +/- ##
=======================================
Coverage 77% 77%
=======================================
Files 66 66
Lines 8115 8189 +74
=======================================
+ Hits 6232 6303 +71
- Misses 1883 1886 +3 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Adds compressed COCO RLE mask support across Supervision’s detection inference pipeline and converters, enabling sv.Detections.from_inference() to decode masks provided as compressed RLE strings/bytes (e.g., SAM3 / semantic segmentation outputs) without adding dependencies.
Changes:
- Implement pure-Python COCO compressed RLE string encode/decode and extend
rle_to_mask/mask_to_rleto support compressed formats. - Update inference result processing to accept RLE mask payloads (
rle/rle_mask) and buildDetections.maskaccordingly. - Move RLE utilities into
detection.utils.converters, update exports/docs, and relocate tests accordingly.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/detection/utils/test_internal.py | Adds inference parsing test cases for compressed RLE masks (incl. non-contiguous + tracker_id). |
| tests/detection/utils/test_converters.py | Adds unit tests for compressed RLE codec and mask↔RLE conversions. |
| tests/dataset/test_utils.py | Removes RLE tests from dataset utils (now covered under converters). |
| src/supervision/detection/utils/internal.py | Extends Roboflow/inference result parsing to decode RLE masks. |
| src/supervision/detection/utils/converters.py | Adds COCO compressed RLE codec + compressed support in converters. |
| src/supervision/dataset/utils.py | Removes old implementations and re-exports converters versions for compatibility. |
| src/supervision/dataset/formats/coco.py | Updates imports to use the new converter locations. |
| src/supervision/init.py | Exposes mask_to_rle / rle_to_mask from converters at top-level API. |
| mkdocs.yml | Removes datasets utils docs page from nav. |
| docs/detection/utils/converters.md | Documents rle_to_mask / mask_to_rle under converters. |
| docs/datasets/utils.md | Removes the old dataset utils docs page. |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- Add bounds check in _decode_coco_rle_string() so malformed/truncated compressed RLE strings raise ValueError instead of IndexError - Guard process_roboflow_result against non-dict, missing-key, and malformed-counts RLE payloads — all fall through to box-only detection - Fix stale masks type annotation (uint8 → bool_) in internal.py - Add warn_deprecated() wrappers on rle_to_mask/mask_to_rle re-exports in dataset/utils for consistency with project deprecation pattern - Add negative-path tests: malformed counts, missing size/counts keys, non-dict rle, rle_mask fallback key, truncated compressed strings - Add CHANGELOG entries under develop Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Change color=1 to color=(1,) for mypy type compatibility with cv2 stubs --- Co-authored-by: Claude Code <noreply@anthropic.com>
When a roboflow_result mixes RLE-encoded predictions (decoded to rle_data["size"]) and polygon predictions (decoded to image dims), np.array(masks) raises ValueError on inhomogeneous shapes. Resize RLE masks with INTER_NEAREST to (image_height, image_width) when the decoded RLE dimensions differ from the reported image size. --- Co-authored-by: Claude Code <noreply@anthropic.com>
[resolve #5] /review finding by sw-engineer (report: .temp/output-review-from-inference-rle-mask-2026-04-14.md): "coco.py:88-89 wraps segmentation["counts"] in np.array() before passing to rle_to_mask. For compressed string counts, np.array("52203") produces a 0-d string ndarray that bypasses isinstance(rle, str)..." --- Co-authored-by: Claude Code <noreply@anthropic.com>
[resolve #8] /review finding by qa-specialist (report: .temp/output-review-from-inference-rle-mask-2026-04-14.md): "No test covers mixed RLE + box-only predictions in the same batch. Produces misaligned masks (1 mask for 2 detections)..." --- Co-authored-by: Claude Code <noreply@anthropic.com>
[resolve #10] /review finding by qa-specialist (report: .temp/output-review-from-inference-rle-mask-2026-04-14.md): "No test covers DetectionDataset.from_coco with compressed RLE iscrowd annotations..." --- Co-authored-by: Claude Code <noreply@anthropic.com>
[resolve #1] Review comment by @Copilot (gh): "mask from rle_to_mask is a boolean array, but masks is annotated as list[npt.NDArray[np.uint8]]..." --- Co-authored-by: Claude Code <noreply@anthropic.com>
[resolve #6] /review finding by sw-engineer (report: .temp/output-review-from-inference-rle-mask-2026-04-14.md): "Broad exception swallowing at internal.py:107 silently drops RLE decode failures with no warning..." --- Co-authored-by: Claude Code <noreply@anthropic.com>
[resolve #7] /review finding by sw-engineer (report: .temp/output-review-from-inference-rle-mask-2026-04-14.md): "rle_to_mask uses assert for input validation which can be disabled with python -O; raise ValueError instead..." --- Co-authored-by: Claude Code <noreply@anthropic.com>
[resolve #9] /review finding by qa-specialist (report: .temp/output-review-from-inference-rle-mask-2026-04-14.md): "Consider adding a test with bytes that would fail UTF-8 decode to verify error handling..." --- Co-authored-by: Claude Code <noreply@anthropic.com>
[resolve #11] /review finding by doc-scribe (report: .temp/output-review-from-inference-rle-mask-2026-04-14.md): "Changelog entry for rle_to_mask / mask_to_rle relocation doesn't mention the dtype change from uint8 to bool..." --- Co-authored-by: Claude Code <noreply@anthropic.com>
np.int_ accepts only the platform-default int dtype; np.integer[Any] accepts any integer-typed array (int32, int64, uint16, etc.), which matches what the implementation actually handles. --- Co-authored-by: Claude Code <noreply@anthropic.com>
No call site needs Literal-based return-type narrowing, so the @overload pair (and the Literal/overload imports) are dead weight. The single signature returns list[int] | str; the one internal call site in coco.py uses cast() to satisfy mypy. --- Co-authored-by: Claude Code <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
src/supervision/detection/utils/internal.py:132
process_roboflow_resultbuildsmasksonly for predictions that include RLE/polygon data. If a batch mixes masked and box-only predictions,masks_arrends up shorter thanxyxy_arr, andDetections.from_inference()will raise during validation becausemask.shape[0] != len(xyxy). Consider keeping masks aligned by appending an empty mask for box-only predictions whenever any mask is present in the batch (or, alternatively, dropping masks entirely when they can’t be produced for all predictions).
if rle_data is not None:
xyxy.append([x_min, y_min, x_max, y_max])
class_id.append(prediction["class_id"])
class_name.append(prediction["class"])
confidence.append(prediction["confidence"])
masks.append(mask)
if "tracker_id" in prediction:
tracker_ids.append(prediction["tracker_id"])
elif "points" not in prediction:
xyxy.append([x_min, y_min, x_max, y_max])
class_id.append(prediction["class_id"])
class_name.append(prediction["class"])
confidence.append(prediction["confidence"])
if "tracker_id" in prediction:
tracker_ids.append(prediction["tracker_id"])
elif len(prediction["points"]) >= 3:
Both functions implement the COCO pycocotools RLE format which uses column-major (Fortran) pixel ordering — the opposite of NumPy's default row-major layout. Make this explicit in the docstrings so callers understand the format and cannot confuse it with row-major RLE variants. --- Co-authored-by: Claude Code <noreply@anthropic.com>
…le/rle_to_mask; add pydeprecate dependency
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
@leeclemnet could you pls check that updated version? |
…nused argument warnings
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Before submitting
Description
Add RLE mask support to
sv.Detections.from_inferencefor models that return compressed COCO RLE (e.g. SAM3, semantic segmentation), and extendrle_to_mask/mask_to_rlewith compressed string encoding/decoding — implemented as a pure Python COCO RLE codec with no new dependencies.cc @PawelPeczek-Roboflow
Type of Change
Motivation and Context
Workflows currently supports COCO RLE compressed string serialization for segmentation masks, originally for SAM3 instance segmentation and now also for semantic segmentation models where masks may not be contiguous and therefore can't be represented as polygons. We would like
sv.Detections.from_inference()to handle such data and automatically decode it into binary masks as it currently does for polygon mask data.The existing
rle_to_maskutil supports this conversion but not from compressed strings (see #1952).Closes #1952
Changes Made
Testing
Google Colab (optional)
Colab link:
Screenshots/Videos (optional)
Additional Notes