Skip to content

refactor(rle): consolidate RLE primitives#2230

Merged
Borda merged 12 commits into
developfrom
rle/refactor
Apr 22, 2026
Merged

refactor(rle): consolidate RLE primitives#2230
Borda merged 12 commits into
developfrom
rle/refactor

Conversation

@Borda

@Borda Borda commented Apr 22, 2026

Copy link
Copy Markdown
Member

This pull request significantly refactors and improves the handling of COCO RLE (Run-Length Encoding) mask encoding and decoding in the supervision.detection.utils.converters module. 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:

  • Introduced _mask_to_rle_counts and _rle_counts_to_mask as shared low-level functions for encoding and decoding boolean masks to/from COCO RLE int32 arrays, used by both the main API and CompactMask. (F75eb6f2L394R636)
  • Refactored _rle_encode and _rle_decode in compact_mask.py to delegate to these shared functions, removing duplicate logic. [1] [2] [3]

COCO Compressed RLE (String) Utilities:

  • Added _base48_encode, _base48_decode, _delta_encode, and _delta_decode for modular base-48 and delta encoding/decoding, mirroring pycocotools' compressed RLE pipeline. [1] [2] [3]
  • Refactored _encode_coco_rle_string and _decode_coco_rle_string to use these new helpers, clarifying the encoding/decoding process. [1] [2]

API and Utility Enhancements:

  • Added is_compressed_rle utility function to detect compressed RLE strings, and exposed it in the package's public API. (F75eb6f2L394R636, [1] [2]
  • Updated mask_to_rle and rle_to_mask to use the new core helpers, simplifying their logic and improving reliability. [1] [2]

Testing and Imports:

  • Updated test imports to cover all new helpers, ensuring test coverage for the refactored and new utility functions.

These changes make the codebase more modular, easier to maintain, and more robust when handling both compressed and uncompressed COCO RLE mask formats.

- 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>
@Borda Borda requested a review from SkalskiP as a code owner April 22, 2026 08:13
Copilot AI review requested due to automatic review settings April 22, 2026 08:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_string to use them.
  • Introduced shared _mask_to_rle_counts / _rle_counts_to_mask primitives and refactored mask_to_rle, rle_to_mask, and CompactMask internals 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.

Comment thread src/supervision/detection/utils/converters.py Outdated
Comment thread src/supervision/detection/utils/converters.py
Comment thread src/supervision/detection/utils/converters.py Outdated
Borda and others added 8 commits April 22, 2026 10:28
- 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>
- 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

codecov Bot commented Apr 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77%. Comparing base (5a59dcd) to head (0e7c9dc).
⚠️ Report is 1 commits behind head on develop.

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:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment thread src/supervision/detection/utils/converters.py Outdated
Comment thread docs/changelog.md Outdated
Borda and others added 2 commits April 22, 2026 12:19
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>
Comment thread docs/changelog.md Outdated
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
@Borda Borda merged commit de87030 into develop Apr 22, 2026
27 checks passed
@Borda Borda deleted the rle/refactor branch April 22, 2026 10:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants