roboflow/supervision · error · ValueError
Cannot merge an empty list of CompactMask objects.
Error message
Cannot merge an empty list of CompactMask objects.
What it means
Raised by CompactMask.merge when masks_list is empty. Merging zero objects has no meaningful result (there is no image shape to inherit), so the API refuses rather than guessing. Pass at least one CompactMask.
Source
Thrown at src/supervision/detection/compact_mask.py:1356
Examples:
```pycon
>>> import numpy as np
>>> from supervision.detection.compact_mask import CompactMask
>>> masks1 = np.zeros((2, 50, 50), dtype=bool)
>>> masks2 = np.zeros((3, 50, 50), dtype=bool)
>>> xyxy1 = np.array([[0,0,10,10],[10,10,20,20]], dtype=np.float32)
>>> xyxy2 = np.array(
... [[0,0,5,5],[5,5,10,10],[10,10,15,15]], dtype=np.float32)
>>> cm1 = CompactMask.from_dense(masks1, xyxy1, image_shape=(50, 50))
>>> cm2 = CompactMask.from_dense(masks2, xyxy2, image_shape=(50, 50))
>>> len(CompactMask.merge([cm1, cm2]))
5
```
"""
if not masks_list:
raise ValueError("Cannot merge an empty list of CompactMask objects.")
image_shape = masks_list[0]._image_shape
for cm in masks_list[1:]:
if cm._image_shape != image_shape:
raise ValueError(
f"Cannot merge CompactMask objects with different image shapes: "
f"{image_shape} vs {cm._image_shape}"
)
# list.extend is a C-level call and avoids the per-element Python
# bytecode overhead of a flat list comprehension. This matters under
# GIL contention when multiple threads call merge concurrently.
new_rles: list[npt.NDArray[np.int32]] = []
for cm in masks_list:
new_rles.extend(cm._rles)
# np.concatenate handles (0, 2) arrays correctly.
# No .astype() needed — _crop_shapes and _offsets are already int32.View on GitHub (pinned to 7f254d9784)
Solutions
- Skip the merge when the list is empty: if not masks_list: continue / return an empty CompactMask built from the known image_shape.
- If detections were expected, debug why the producing step yielded no CompactMask objects before calling merge.
- Construct an empty result explicitly via CompactMask.from_dense(np.zeros((0, h, w), bool), np.empty((0, 4)), image_shape) when an empty object is needed.
Example fix
# before
merged = CompactMask.merge(tile_masks)
# after
if tile_masks:
merged = CompactMask.merge(tile_masks)
else:
merged = CompactMask.from_dense(
np.zeros((0, h, w), dtype=bool), np.empty((0, 4), dtype=np.float32),
image_shape=(h, w)) Defensive patterns
Strategy: validation
Validate before calling
if not tile_masks:
merged = sv.CompactMask.from_dense(
np.zeros((0, h, w), dtype=bool),
np.empty((0, 4), dtype=np.float32),
image_shape=(h, w),
)
else:
merged = sv.CompactMask.merge(tile_masks) Type guard
def is_mergeable(masks_list) -> bool:
return isinstance(masks_list, (list, tuple)) and len(masks_list) > 0 Try / catch
try:
merged = sv.CompactMask.merge(tile_masks)
except ValueError as e:
if "empty list" in str(e):
merged = None # nothing detected on any tile
else:
raise Prevention
- Guard merges behind `if masks_list:` whenever inputs come from detection loops that can yield zero results.
- Return a canonical empty CompactMask for the known image_shape instead of calling merge with [].
- Log the tile count before merging in slicer pipelines to notice all-empty batches.
When it happens
Trigger: Calling CompactMask.merge([]) — typically because a loop collecting per-tile or per-batch CompactMask objects produced an empty list (no detections anywhere, empty directory, all tiles filtered out).
Common situations: Aggregating slicer outputs when the model returned nothing on any tile; merging per-image masks in a loop over an empty folder; a guard upstream filtered everything.
Related errors
- COCO RLE counts cannot be empty.
- new_image_shape must contain positive dimensions
- Cannot merge CompactMask objects with different image shapes
- All KeyPoints must have the same coordinate depth per skelet
- All or none of the '{name}' fields must be None
AI-assisted analysis of roboflow/supervision@7f254d9784 (2026-08-15).
Data as JSON: /api/errors/d70b42e0746470f7.
Report an issue: GitHub.