refactor(rle): consolidate RLE primitives#2230
Merged
Merged
Conversation
- Split _decode/_encode_coco_rle_string into _base48_decode, _base48_encode, _delta_decode, _delta_encode — each independently testable - Add _mask_to_rle_counts / _rle_counts_to_mask as shared numpy helpers; both mask_to_rle/rle_to_mask and CompactMask._rle_encode/_rle_decode now delegate to these (no duplicate logic) - Export is_compressed_rle to public API for format detection before calling rle_to_mask - Replace np.roll-based encoding in mask_to_rle with np.diff approach from _mask_to_rle_counts (more efficient) - Add parametrized tests for all new primitives in test_converters.py --- Co-authored-by: Claude Code <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR refactors COCO RLE (Run-Length Encoding) mask encode/decode logic by extracting shared low-level primitives in supervision.detection.utils.converters, reusing them from CompactMask, and adding helper utilities for COCO’s compressed (string) pipeline.
Changes:
- Added modular base-48 and delta encode/decode helpers, plus
is_compressed_rle, and refactored_encode/_decode_coco_rle_stringto use them. - Introduced shared
_mask_to_rle_counts/_rle_counts_to_maskprimitives and refactoredmask_to_rle,rle_to_mask, andCompactMaskinternals to delegate to them. - Expanded unit tests to cover the new helper functions and round-trips.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/supervision/detection/utils/converters.py |
Extracts/centralizes RLE primitives and compressed-string helpers; refactors public RLE conversion functions to use them. |
src/supervision/detection/compact_mask.py |
Removes duplicate RLE logic by delegating to the new shared converters helpers. |
tests/detection/utils/test_converters.py |
Adds focused unit tests for the new helpers and behaviors. |
src/supervision/__init__.py |
Exposes is_compressed_rle in the package-level public API. |
- Remove _rle_encode/_rle_decode from compact_mask.py; inline _mask_to_rle_counts/_rle_counts_to_mask at all 8 call sites - Remove _encode_coco_rle_string/_decode_coco_rle_string from converters.py; inline _base48_encode/_delta_encode and _base48_decode/_delta_decode at call sites in mask_to_rle/rle_to_mask - Update tests: drop stale imports and dead test_coco_rle_encode_decode_round_trip - Fix _rle_area docstring: stale :func:`_rle_encode` ref updated --- Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…into rle/refactor
- Wrong input `[5, 2, 2, 5, 5]` gave actual result `[5, 2, 2, 3, 3]`, not the documented `[5, 2, 2, 0, 3]`; correct inverse of the _delta_decode example is input `[5, 2, 2, 2, 5]` [resolve #2] Review comment by @Copilot (gh): "The doctest example for _delta_encode uses an input/output pair that..." --- Co-authored-by: Claude Code <noreply@anthropic.com>
…ized RLE - test_base48_round_trip: add [100], [1000] (multi-byte continuation), [-3], [-1, 0, -100] (negative values exercising sign-bit path) - test_rle_counts_to_mask: add case where sum(rle) > h*w to cover truncation via flat[:num_pixels] [resolve #3] /review finding by qa-specialist (report: .temp/output-review-rle-refactor-2026-04-22.md): "Missing test coverage for multi-byte base-48 values, negative delta values..." --- Co-authored-by: Claude Code <noreply@anthropic.com>
[resolve #4] /review finding by oss (report: .temp/output-review-rle-refactor-2026-04-22.md): "CHANGELOG: Not updated. Should be updated before merge..." --- Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: Codex <codex@openai.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #2230 +/- ##
=======================================
Coverage 77% 77%
=======================================
Files 66 66
Lines 8211 8211
=======================================
+ Hits 6350 6351 +1
+ Misses 1861 1860 -1 🚀 New features to boost your workflow:
|
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…ounts - Calling .view(np.uint8) on a non-1-byte dtype (int32, float32) reinterprets the underlying bytes and produces incorrect run boundaries (silent mis-encoding). Using np.asarray(mask_2d, dtype=np.bool_) before ravel() keeps the fast uint8-view path while accepting any truthy array. [resolve #4] Review comment by @Copilot (PR #2230): "_mask_to_rle_counts computes np.diff(flat.view(np.uint8)) without ensuring flat is a 1-byte-per-element boolean/uint8 array..." --- Co-authored-by: Claude Code <noreply@anthropic.com>
Borda
commented
Apr 22, 2026
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request significantly refactors and improves the handling of COCO RLE (Run-Length Encoding) mask encoding and decoding in the
supervision.detection.utils.convertersmodule. The main changes include extracting and refactoring the core RLE logic into dedicated low-level functions, improving code clarity and maintainability, and providing new utility functions for working with compressed and uncompressed RLE formats. The public API is unchanged, but the implementation is now more modular and robust.Refactoring and Core Logic Extraction:
_mask_to_rle_countsand_rle_counts_to_maskas shared low-level functions for encoding and decoding boolean masks to/from COCO RLE int32 arrays, used by both the main API andCompactMask. (F75eb6f2L394R636)_rle_encodeand_rle_decodeincompact_mask.pyto delegate to these shared functions, removing duplicate logic. [1] [2] [3]COCO Compressed RLE (String) Utilities:
_base48_encode,_base48_decode,_delta_encode, and_delta_decodefor modular base-48 and delta encoding/decoding, mirroring pycocotools' compressed RLE pipeline. [1] [2] [3]_encode_coco_rle_stringand_decode_coco_rle_stringto use these new helpers, clarifying the encoding/decoding process. [1] [2]API and Utility Enhancements:
is_compressed_rleutility function to detect compressed RLE strings, and exposed it in the package's public API. (F75eb6f2L394R636, [1] [2]mask_to_rleandrle_to_maskto use the new core helpers, simplifying their logic and improving reliability. [1] [2]Testing and Imports:
These changes make the codebase more modular, easier to maintain, and more robust when handling both compressed and uncompressed COCO RLE mask formats.