Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

### develop

- Changed [#2178](https://github.com/roboflow/supervision/pull/2178): [`sv.Detections.from_inference`](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections.from_inference) now supports compressed COCO RLE masks. Inference responses with `rle` or `rle_mask` fields containing a compressed counts string (as produced by `pycocotools`) are decoded directly into binary masks, avoiding a lossy polygon round-trip.

- Changed [#2178](https://github.com/roboflow/supervision/pull/2178): [`sv.rle_to_mask`](https://supervision.roboflow.com/latest/detection/utils/converters/#supervision.detection.utils.converters.rle_to_mask) and [`sv.mask_to_rle`](https://supervision.roboflow.com/latest/detection/utils/converters/#supervision.detection.utils.converters.mask_to_rle) moved to `supervision.detection.utils.converters`. The old import path `supervision.dataset.utils` continues to work but is deprecated.

### 0.27.0 <small>Nov 16, 2025</small>

- Added [#2008](https://github.com/roboflow/supervision/pull/2008): [`sv.filter_segments_by_distance`](https://supervision.roboflow.com/0.27.0/detection/utils/masks/#supervision.detection.utils.masks.filter_segments_by_distance) to keep the largest connected component and nearby components within an absolute or relative distance threshold. Useful for cleaning segmentation predictions from models such as SAM, SAM2, YOLO segmentation, and RF-DETR segmentation.
Expand Down
17 changes: 0 additions & 17 deletions docs/datasets/utils.md

This file was deleted.

12 changes: 12 additions & 0 deletions docs/detection/utils/converters.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,15 @@ status: new
</div>

:::supervision.detection.utils.converters.xyxy_to_mask

<div class="md-typeset">
<h2><a href="#supervision.detection.utils.converters.rle_to_mask">rle_to_mask</a></h2>
</div>

:::supervision.detection.utils.converters.rle_to_mask

<div class="md-typeset">
<h2><a href="#supervision.detection.utils.converters.mask_to_rle">mask_to_rle</a></h2>
</div>

:::supervision.detection.utils.converters.mask_to_rle
1 change: 0 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ nav:
- Trackers: trackers.md
- Datasets:
- Core: datasets/core.md
- Utils: datasets/utils.md
- Metrics:
- mAP: metrics/mean_average_precision.md
- mAR: metrics/mean_average_recall.md
Expand Down
3 changes: 2 additions & 1 deletion src/supervision/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
DetectionDataset,
)
from supervision.dataset.formats.coco import get_coco_class_index_mapping
from supervision.dataset.utils import mask_to_rle, rle_to_mask
from supervision.detection.core import Detections
from supervision.detection.line_zone import (
LineZone,
Expand All @@ -65,9 +64,11 @@
)
from supervision.detection.utils.converters import (
mask_to_polygons,
mask_to_rle,
mask_to_xyxy,
polygon_to_mask,
polygon_to_xyxy,
rle_to_mask,
xcycwh_to_xyxy,
xywh_to_xyxy,
xyxy_to_mask,
Expand Down
6 changes: 4 additions & 2 deletions src/supervision/dataset/formats/coco.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
from supervision.dataset.utils import (
approximate_mask_with_polygons,
map_detections_class_id,
)
from supervision.detection.core import Detections
from supervision.detection.utils.converters import (
mask_to_rle,
polygon_to_mask,
rle_to_mask,
)
from supervision.detection.core import Detections
from supervision.detection.utils.converters import polygon_to_mask
from supervision.detection.utils.masks import contains_holes, contains_multiple_segments
from supervision.utils.file import read_json_file, save_json_file

Expand Down
146 changes: 18 additions & 128 deletions src/supervision/dataset/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,28 @@

from supervision.detection.core import Detections
from supervision.detection.utils.converters import mask_to_polygons
from supervision.detection.utils.converters import (
mask_to_rle as _mask_to_rle,
)
from supervision.detection.utils.converters import (
rle_to_mask as _rle_to_mask,
)
from supervision.detection.utils.polygons import (
approximate_polygon,
filter_polygons_by_area,
)
from supervision.utils.internal import deprecated


@deprecated("Import mask_to_rle from supervision.detection.utils.converters instead.")
def mask_to_rle(*args, **kwargs): # type: ignore[no-untyped-def]
return _mask_to_rle(*args, **kwargs)


@deprecated("Import rle_to_mask from supervision.detection.utils.converters instead.")
def rle_to_mask(*args, **kwargs): # type: ignore[no-untyped-def]
return _rle_to_mask(*args, **kwargs)


if TYPE_CHECKING:
from supervision.dataset.core import DetectionDataset
Expand Down Expand Up @@ -137,131 +155,3 @@ def train_test_split(

split_index = int(len(data) * train_ratio)
return data[:split_index], data[split_index:]


def rle_to_mask(
rle: npt.NDArray[np.int_] | list[int], resolution_wh: tuple[int, int]
) -> npt.NDArray[np.bool_]:
"""
Converts run-length encoding (RLE) to a binary mask.

Args:
rle: The 1D RLE array, the format
used in the COCO dataset (column-wise encoding, values of an array with
even indices represent the number of pixels assigned as background,
values of an array with odd indices represent the number of pixels
assigned as foreground object).
resolution_wh: The width (w) and height (h)
of the desired binary mask.

Returns:
The generated 2D Boolean mask of shape `(h, w)`, where the foreground object is
marked with `True`'s and the rest is filled with `False`'s.

Raises:
AssertionError: If the sum of pixels encoded in RLE differs from the
number of pixels in the expected mask (computed based on resolution_wh).

Examples:
```pycon
>>> import numpy as np
>>> import supervision as sv
>>> mask = sv.rle_to_mask([5, 2, 2, 2, 5], (4, 4))
>>> mask # doctest: +NORMALIZE_WHITESPACE
array([[0, 0, 0, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 0, 0, 0]], dtype=uint8)

```
"""
if isinstance(rle, list):
rle = np.array(rle, dtype=int)

width, height = resolution_wh

assert width * height == np.sum(rle), (
"the sum of the number of pixels in the RLE must be the same "
"as the number of pixels in the expected mask"
)

zero_one_values = np.zeros(shape=(rle.size, 1), dtype=np.uint8)
zero_one_values[1::2] = 1

decoded_rle = np.repeat(zero_one_values, rle, axis=0)
decoded_rle = np.append(
decoded_rle, np.zeros(width * height - len(decoded_rle), dtype=np.uint8)
)
return decoded_rle.reshape((height, width), order="F")


def mask_to_rle(mask: npt.NDArray[np.bool_]) -> list[int]:
"""
Converts a binary mask into a run-length encoding (RLE).

Args:
mask: 2D binary mask where `True` indicates foreground
object and `False` indicates background.

Returns:
The run-length encoded mask. Values of a list with even indices
represent the number of pixels assigned as background (`False`), values
of a list with odd indices represent the number of pixels assigned
as foreground object (`True`).

Raises:
AssertionError: If input mask is not 2D or is empty.

Examples:
```pycon
>>> import numpy as np
>>> import supervision as sv
>>> mask = np.array([
... [True, True, True, True],
... [True, True, True, True],
... [True, True, True, True],
... [True, True, True, True],
... ])
>>> rle = sv.mask_to_rle(mask)
>>> [int(x) for x in rle]
[0, 16]

```

```pycon
>>> import numpy as np
>>> import supervision as sv
>>> mask = np.array([
... [False, False, False, False],
... [False, True, True, False],
... [False, True, True, False],
... [False, False, False, False],
... ])
>>> rle = sv.mask_to_rle(mask)
>>> [int(x) for x in rle]
[5, 2, 2, 2, 5]

```

![mask_to_rle](https://media.roboflow.com/supervision-docs/
mask-to-rle.png){ align=center width="800" }
"""
assert mask.ndim == 2, "Input mask must be 2D"
assert mask.size != 0, "Input mask cannot be empty"

on_value_change_indices = np.where(
mask.ravel(order="F") != np.roll(mask.ravel(order="F"), 1)
)[0]

on_value_change_indices = np.append(on_value_change_indices, mask.size)
# need to add 0 at the beginning when the same value is in the first and
# last element of the flattened mask
if on_value_change_indices[0] != 0:
on_value_change_indices = np.insert(on_value_change_indices, 0, 0)

rle = np.diff(on_value_change_indices)

if mask[0][0] == 1:
rle = np.insert(rle, 0, 0)

return list(rle)
Loading
Loading