roboflow/supervision · error · ValueError

Cannot merge CompactMask objects with different image shapes

Error message

Cannot merge CompactMask objects with different image shapes: {image_shape} vs {cm._image_shape}

What it means

Raised by CompactMask.merge when the CompactMask objects in masks_list were built with different _image_shape values. A merged result needs one coherent canvas; merging masks defined on different resolutions would silently misplace crops, so all inputs must share the same (height, width).

Source

Thrown at src/supervision/detection/compact_mask.py:1361

            >>> 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.
        new_crop_shapes: npt.NDArray[np.int32] = np.concatenate(
            [cm._crop_shapes for cm in masks_list], axis=0
        )
        new_offsets: npt.NDArray[np.int32] = np.concatenate(
            [cm._offsets for cm in masks_list], axis=0

View on GitHub (pinned to 7f254d9784)

Solutions

  1. Resize all CompactMask objects to a common shape before merging: [cm.resize(common_shape) for cm in masks_list].
  2. Ensure every producing call (from_dense/from_coco_rle/with_offset) receives the same image_shape.
  3. Group masks by image_shape and merge per group if heterogeneous sizes are expected.

Example fix

# before
merged = CompactMask.merge([cm_a, cm_b])  # shapes (720,1280) vs (1080,1920)

# after
target = cm_a.shape[1:]
merged = CompactMask.merge([cm_a, cm_b.resize(target)])
Defensive patterns

Strategy: validation

Validate before calling

shapes = {cm.shape[1:] for cm in masks_list}
assert len(shapes) == 1, f"mixed image shapes: {shapes}"

Try / catch

try:
    merged = sv.CompactMask.merge(masks_list)
except ValueError as e:
    if "different image shapes" in str(e):
        target = masks_list[0].shape[1:]
        merged = sv.CompactMask.merge([cm.resize(target) for cm in masks_list])
    else:
        raise

Prevention

When it happens

Trigger: Calling CompactMask.merge([cm_720p, cm_1080p]); merging per-tile masks where one tile was resized before embedding; merging masks from from_coco_rle calls that received different image_shape values.

Common situations: Slicer pipelines that resize some tiles; mixed sources (one mask from from_dense on the original image, another after resize); heterogeneous image sizes in a folder processed with a single fixed shape.

Related errors


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