roboflow/supervision · error · ValueError

Masks must be shaped (N, H, W)

Error message

Masks must be shaped (N, H, W)

What it means

Raised by get_mask_size_category() when the mask input is a plain ndarray that is not 3-D (N, H, W) — one binary mask per detection. The function counts True pixels per mask to derive areas; anything but a 3-D bool array (e.g. a single 2-D mask or a 4-D video tensor) breaks that per-instance counting. CompactMask inputs bypass this check because they carry their own area attribute.

Source

Thrown at src/supervision/metrics/utils/object_size.py:197

    Example:
        ```pycon
        >>> import numpy as np
        >>> from supervision.metrics.utils.object_size import get_mask_size_category
        >>> mask = np.zeros((3, 100, 100), dtype=bool)
        >>> mask[0, 0:10, 0:10] = True   # 100 (Small)
        >>> mask[1, 0:50, 0:50] = True   # 2500 (Medium)
        >>> mask[2, 0:100, 0:100] = True # 10000 (Large)
        >>> get_mask_size_category(mask)
        array([1, 2, 3])

        ```
    """
    if isinstance(mask, CompactMask):
        areas = mask.area
    else:
        if len(mask.shape) != 3:
            raise ValueError("Masks must be shaped (N, H, W)")
        # count_mask_pixels uses np.count_nonzero (no axis), which dispatches
        # to SIMD popcount over the bool buffer and is ~6x faster than the
        # vectorized np.sum(mask, axis=(1, 2)). Do not "simplify" back to
        # np.sum(axis=(1,2)); benchmark before reverting. dtype=np.int64 keeps
        # areas consistent across platforms (Windows NumPy defaults to int32).
        areas = count_mask_pixels(mask)

    result = np.full(areas.shape, ObjectSizeCategory.ANY.value)
    SM, LG = SIZE_THRESHOLDS
    result[areas < SM] = ObjectSizeCategory.SMALL.value
    result[(areas >= SM) & (areas < LG)] = ObjectSizeCategory.MEDIUM.value
    result[areas >= LG] = ObjectSizeCategory.LARGE.value
    return result


def get_obb_size_category(xyxyxyxy: npt.NDArray[np.number]) -> npt.NDArray[np.int_]:
    """
    Get the size category of a oriented bounding boxes array.

View on GitHub (pinned to 7f254d9784)

Solutions

  1. Stack per-instance masks: mask = np.stack(instance_masks) so shape is (N, H, W)
  2. For a single mask, wrap it: mask[None, :, :]
  3. Ensure boolean dtype and one channel per detection, not an RGB or label-map tensor

Example fix

# before
mask = np.zeros((480, 640), dtype=bool)   # single 2-D mask
get_mask_size_category(mask)

# after
mask = np.zeros((1, 480, 640), dtype=bool)  # (N=1, H, W)
get_mask_size_category(mask)
Defensive patterns

Strategy: validation

Validate before calling

mask = np.asarray(mask)
if mask.ndim == 2:
    mask = mask[None, :, :]
assert mask.ndim == 3, 'masks must be (N, H, W)'
cats = get_mask_size_category(mask.astype(bool))

Type guard

import numpy as np

def is_instance_masks(arr: np.ndarray) -> bool:
    """True when arr is an (N, H, W) mask stack."""
    return arr.ndim == 3

Try / catch

try:
    cats = get_mask_size_category(mask)
except ValueError as e:
    if '(N, H, W)' in str(e):
        cats = get_mask_size_category(np.asarray(mask)[None if np.asarray(mask).ndim == 2 else Ellipsis])
    else:
        raise

Prevention

When it happens

Trigger: Calling get_mask_size_category with a single (H, W) mask, an (N, H, W, 3) array, or masks stacked along the wrong axis.

Common situations: Passing one full-image segmentation mask instead of per-detection instance masks; forgetting np.stack(masks_list) so a list or a 2-D array is passed; masks from a semantic segmentation model that outputs a single channel.

Related errors


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