Skip to content

Use Compact Masks for Memory-Efficient Segmentation

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
  2. Parsing Roboflow Inference results without a dense stack
  3. Skipping mask materialisation for box/label annotators
  4. Merging mixed dense and compact 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:

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:

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:

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:

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, PolygonAnnotator, 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:

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

Comments