roboflow/supervision · error · ValueError

Malformed compressed RLE string: unexpected end at position

Error message

Malformed compressed RLE string: unexpected end at position {i}

What it means

The compressed-RLE decoder reads a base-48-style variable-length integer stream where each character contributes 5 bits and bit 0x20 marks continuation. If the string ends in the middle of a multi-character integer (continuation bit still set), the stream is truncated and the decoder raises with the offending position so you can locate the cut.

Source

Thrown at src/supervision/detection/utils/converters.py:439

        ValueError: If the string is truncated mid-integer.

    Examples:
        ```pycon
        >>> from supervision.detection.utils.converters import _base48_decode
        >>> _base48_decode("52203")
        [5, 2, 2, 0, 3]

        ```
    """
    values: list[int] = []
    i = 0
    while i < len(s):
        x = 0
        k = 0
        more = True
        while more:
            if i >= len(s):
                raise ValueError(
                    f"Malformed compressed RLE string: unexpected end at position {i}"
                )
            c = ord(s[i]) - 48
            x |= (c & 0x1F) << (5 * k)
            more = bool(c & 0x20)
            i += 1
            k += 1
            if not more and (c & 0x10):
                x |= ~0 << (5 * k)
        values.append(x)
    return values


def _base48_encode(values: list[int]) -> str:
    """Encode raw (delta-encoded) integers to a COCO base-48 string.

    The inverse of :func:`_base48_decode`. Applies the same variable-length
    base-48 codec used by pycocotools.

View on GitHub (pinned to 7f254d9784)

Solutions

  1. Re-encode the mask with sv.mask_to_rle(mask, compressed=True) and compare lengths to detect truncation.
  2. Store compressed RLEs in binary-safe columns or verify a checksum alongside the string.
  3. If decoding untrusted input, wrap in try/except ValueError and drop/re-request the record.
  4. Pass counts as a plain list[int] (compressed=False) where transport safety is uncertain.

Example fix

# before
 mask = sv.rle_to_mask(rle=row["rle"][:255], resolution_wh=wh)  # column truncated

# after
 mask = sv.rle_to_mask(rle=row["rle"], resolution_wh=wh)  # full string from TEXT/CLOB column
Defensive patterns

Strategy: try-catch

Validate before calling

def looks_like_compressed_rle(s: str) -> bool:
    # base-48 alphabet: chars 48..95 of ASCII; and must not end mid-integer
    return bool(s) and all(48 <= ord(c) <= 95 for c in s)

Type guard

def is_complete_compressed_rle(s: str) -> bool:
    if not s:
        return False
    if any(ord(c) < 48 or ord(c) > 95 for c in s):
        return False
    return not bool(ord(s[-1]) - 48 & 0x20)  # last char must not set continuation bit

Try / catch

try:
    mask = sv.rle_to_mask(rle=rle_string, resolution_wh=wh)
except ValueError as e:
    logger.error("Corrupt RLE record %s: %s", record_id, e)
    return None  # or re-fetch the record

Prevention

When it happens

Trigger: Passing a compressed RLE string that was truncated by a length limit, character-set filtering (e.g. non-ASCII-safe transport stripping chars), URL/JSON escaping damage, or manual copy-paste that dropped trailing characters.

Common situations: RLE strings stored in text columns with truncation; transporting via systems that mangle the alphabet (code-page conversions); concatenating or slicing encoded strings; stale data encoded by an older encoder version.

Understand the failure class

Related errors


AI-assisted analysis of roboflow/supervision@7f254d9784 (2026-08-15). Data as JSON: /api/errors/39551b69a2d033c0. Report an issue: GitHub.