---
comments: true
description: Use CompactMask for memory-efficient instance segmentation in supervision — ingest COCO RLE payloads, skip mask materialisation, and merge mixed dense and compact detections without allocating a full pixel stack.
authors:
  - name: Borda
    role: Open Source Engineer, Roboflow
    github: https://github.com/borda
date_modified: 2026-07-01
---

# Use Compact Masks for Memory-Efficient Segmentation

[CompactMask][supervision.detection.compact_mask.CompactMask] stores each instance mask as a run-length encoding of its bounding-box **crop** rather than a full `(H, W)` boolean frame. For high-resolution images with many sparse masks this can reduce memory from tens of gigabytes to tens of megabytes, and eliminates full-frame decode work in annotators that only need the cropped region.

!!! Note

    `sv.mask_to_xyxy` keeps supervision's inclusive max-coordinate convention for compatibility with `CompactMask` and current box-based adapters. Use `sv.mask_to_roi` when you need exclusive slice bounds for NumPy indexing or crop extraction.

This guide covers the four main integration points:

1. [Ingesting COCO RLE payloads directly as CompactMask](#ingest-coco-rle-payloads)
2. [Parsing Roboflow Inference results without a dense stack](#parse-inference-results)
3. [Skipping mask materialisation for box/label annotators](#skip-unnecessary-materialisation)
4. [Merging mixed dense and compact detections](#merge-mixed-detections)

---

## Ingest COCO RLE Payloads

If your model or API returns masks in the COCO RLE format (`{"size": [H, W], "counts": "..."}`) you can convert them directly to `CompactMask` without allocating an `(N, H, W)` boolean array:

```python
import numpy as np
import supervision as sv
from supervision.detection.compact_mask import CompactMask

# Example: two COCO RLE masks for a 720×1280 frame.
# Replace the counts strings with actual compressed RLE payloads from your
# model or API — e.g., from pycocotools mask.encode() or an Inference response.
rles = [
    {"size": [720, 1280], "counts": "YOUR_RLE_COUNTS_STRING_HERE"},
    {"size": [720, 1280], "counts": "YOUR_RLE_COUNTS_STRING_HERE"},
]
xyxy = np.array(
    [
        [100.0, 50.0, 400.0, 300.0],
        [500.0, 200.0, 900.0, 600.0],
    ]
)

compact = CompactMask.from_coco_rle(rles, xyxy, image_shape=(720, 1280))

detections = sv.Detections(
    xyxy=xyxy,
    mask=compact,
    class_id=np.array([0, 1]),
)
```

`from_coco_rle` uses run-length arithmetic scoped to each bounding box so no dense pixel array is ever created. Uncompressed integer count lists are also accepted in place of compressed strings.

---

## Parse Inference Results

`Detections.from_inference` accepts a `compact_masks=True` flag that routes the Roboflow RLE payload through `CompactMask.from_coco_rle` instead of decoding to a dense stack:

```python
import supervision as sv

# result: a Roboflow Inference v2 response dict with instance masks.
detections = sv.Detections.from_inference(result, compact_masks=True)

from supervision.detection.compact_mask import CompactMask

assert isinstance(detections.mask, CompactMask)
```

!!! Warning

    `compact_masks=True` crops each mask to its detector bounding box. Pixels outside the box are silently dropped. For masks that extend meaningfully beyond the reported bounding box, use the default `compact_masks=False` (dense decode) to preserve all pixels.

To convert an existing dense-mask `Detections` to compact at any point:

```python
detections_compact = detections.to_compact_masks()
```

---

## Skip Unnecessary Materialisation

Annotators that do not draw masks (box, label, circle, ellipse, trace, keypoint) expose `requires_mask = False`. Integrations can branch on this flag to avoid decoding compact or RLE masks before annotation:

```python
import supervision as sv

annotators = [
    sv.BoxAnnotator(),
    sv.LabelAnnotator(),
    sv.MaskAnnotator(),  # requires_mask = True
]

for ann in annotators:
    if ann.requires_mask:
        # Annotator reads mask pixels — CompactMask decodes lazily per crop.
        scene = ann.annotate(scene, detections)
    else:
        # Annotator ignores masks — strip mask field to eliminate any decode cost.
        det_no_mask = sv.Detections(
            xyxy=detections.xyxy,
            confidence=detections.confidence,
            class_id=detections.class_id,
        )
        scene = ann.annotate(scene, det_no_mask)
```

Annotators that set `requires_mask = True`: [MaskAnnotator][supervision.annotators.core.MaskAnnotator], [PolygonAnnotator][supervision.annotators.core.PolygonAnnotator], [HaloAnnotator][supervision.annotators.core.HaloAnnotator].

All others default to `requires_mask = False`.

!!! Note

    `PolygonAnnotator` and `MaskAnnotator` both operate directly on `CompactMask` without materialising the full `(N, H, W)` frame — passing compact detections to them is already efficient.

---

## Merge Mixed Detections

When merging `Detections` objects that mix dense `ndarray` masks and `CompactMask` instances, `Detections.merge` converts dense inputs to `CompactMask` automatically. No full `(N, H, W)` stack is allocated:

```python
import numpy as np
import supervision as sv
from supervision.detection.compact_mask import CompactMask

H, W = 720, 1280

# Compact detections from an RLE-based source.
# Replace the counts string with a real compressed RLE payload from your model or API.
rles = [{"size": [H, W], "counts": "YOUR_RLE_COUNTS_STRING_HERE"}]
xyxy_a = np.array([[100.0, 50.0, 400.0, 300.0]])
cm = CompactMask.from_coco_rle(rles, xyxy_a, image_shape=(H, W))
det_a = sv.Detections(xyxy=xyxy_a, mask=cm, class_id=np.array([0]))

# Dense detections from a different source.
masks_b = np.zeros((1, H, W), dtype=bool)
masks_b[0, 200:400, 500:800] = True
xyxy_b = np.array([[500.0, 200.0, 799.0, 399.0]])
det_b = sv.Detections(xyxy=xyxy_b, mask=masks_b, class_id=np.array([1]))

# Output is CompactMask regardless of input order.
merged = sv.Detections.merge([det_a, det_b])
assert isinstance(merged.mask, CompactMask)
assert len(merged) == 2
```

Merge rules:

| Inputs                                | Output mask type                |
| ------------------------------------- | ------------------------------- |
| All `CompactMask`                     | `CompactMask`                   |
| Mixed `CompactMask` + dense `ndarray` | `CompactMask`                   |
| All dense `ndarray`                   | `ndarray` (backward compatible) |

All `CompactMask` inputs must share the same `image_shape`; mismatches raise `ValueError`.

---

## Performance Notes

These estimates apply to the **parsing and annotation stage**, not end-to-end pipeline FPS. Model inference typically dominates total runtime.

| Optimisation                 | Realistic gain             | Applies when                                                  |
| ---------------------------- | -------------------------- | ------------------------------------------------------------- |
| `from_coco_rle` ingestion    | 25–60% faster parse        | Full-frame COCO RLE payload; current dense decode path        |
| `MaskAnnotator` ROI blending | 10–35% faster annotation   | Many small, sparse masks on high-res frames                   |
| `PolygonAnnotator` crop path | 15–45% faster polygon draw | Many compact masks; full-frame materialise was the bottleneck |
| Mixed-mask merge             | 5–20% faster merge         | Mix of compact and dense sources (e.g. multi-camera stitch)   |

Upper-end gains assume: ≥1080p frames, tens to hundreds of instances, masks covering less than ~20% of total pixels.

---

## API Reference

- [CompactMask][supervision.detection.compact_mask.CompactMask]
- [CompactMask.from_coco_rle][supervision.detection.compact_mask.CompactMask.from_coco_rle]
- [CompactMask.from_dense][supervision.detection.compact_mask.CompactMask.from_dense]
- [Detections.from_inference][supervision.detection.core.Detections.from_inference]
- [Detections.to_compact_masks][supervision.detection.core.Detections.to_compact_masks]
- [Detections.merge][supervision.detection.core.Detections.merge]
- [BaseAnnotator.requires_mask][supervision.annotators.base.BaseAnnotator]
