roboflow/supervision · error · ValueError

masks_true and masks_detection must be 3D (N, H, W); got ndi

Error message

masks_true and masks_detection must be 3D (N, H, W); got ndim={masks_true.ndim} and ndim={masks_detection.ndim}.

What it means

Raised by sv.mask_iou_batch when either masks_true or masks_detection is not a 3-D array of shape (N, H, W). The function computes pairwise mask overlap, so each input must be a stack of N binary masks; 2-D single masks or flattened representations are rejected. CompactMask inputs are materialized to dense arrays before this check, so a CompactMask encoding a non-3-D shape also lands here.

Source

Thrown at src/supervision/detection/utils/iou_and_nms.py:848

        >>> masks_detection = np.zeros((1, 4, 4), dtype=bool)
        >>> masks_detection[:, :3, :3] = True
        >>> sv.mask_iou_batch(masks_true, masks_detection)
        array([[0.44444445]])

        ```
    """

    if isinstance(masks_true, CompactMask) and isinstance(masks_detection, CompactMask):
        return compact_mask_iou_batch(masks_true, masks_detection, overlap_metric)

    # Materialise any CompactMask that was passed alongside a dense array.
    if isinstance(masks_true, CompactMask):
        masks_true = np.asarray(masks_true)
    if isinstance(masks_detection, CompactMask):
        masks_detection = np.asarray(masks_detection)

    if masks_true.ndim != 3 or masks_detection.ndim != 3:
        raise ValueError(
            "masks_true and masks_detection must be 3D (N, H, W); got "
            f"ndim={masks_true.ndim} and ndim={masks_detection.ndim}."
        )
    if masks_true.shape[1:] != masks_detection.shape[1:]:
        raise ValueError(
            "masks_true and masks_detection must share the same (H, W); got "
            f"{masks_true.shape[1:]} and {masks_detection.shape[1:]}."
        )
    # A single pass already handles empty inputs and avoids np.vstack([]) below.
    if masks_true.shape[0] == 0 or masks_detection.shape[0] == 0:
        return _mask_iou_batch_split(masks_true, masks_detection, overlap_metric)

    # Peak memory of a single matmul pass: the flattened detection masks (shared
    # across chunks) plus, per true-mask row, its flattened pixels and the three
    # (N, M) matrices it touches (intersection, denominator and output). The
    # previous (N, M, H, W) estimate overcounted by a factor of M and forced
    # needless chunking now that the intersection is a matmul.
    pixels = masks_true.shape[1] * masks_true.shape[2]

View on GitHub (pinned to 7f254d9784)

Solutions

  1. Add the batch axis: mask = mask[np.newaxis, ...] for a single mask
  2. Reshape flattened data back: masks.reshape(n, h, w) before the call
  3. Index the batch, not a single mask: pass detections.mask (N,H,W), not detections.mask[0]

Example fix

# before
iou = sv.mask_iou_batch(single_mask, other_batch)  # single_mask is (H, W)
# after
iou = sv.mask_iou_batch(single_mask[np.newaxis, ...], other_batch)  # (1, H, W)
Defensive patterns

Strategy: type-guard

Validate before calling

import numpy as np

def as_mask_batch(masks):
    arr = np.asarray(masks)
    if arr.ndim == 2:
        arr = arr[np.newaxis, ...]
    assert arr.ndim == 3, f"masks must be (N, H, W), got {arr.shape}"
    return arr

Type guard

def is_mask_batch(masks) -> bool:
    import numpy as np
    return np.asarray(masks).ndim == 3

Prevention

When it happens

Trigger: sv.mask_iou_batch(mask_a, mask_b) where one argument is a single (H, W) mask (missing the batch axis) or a (N, H*W) flattened stack; also a CompactMask whose RLE decodes to 2-D.

Common situations: Feeding one segmentation mask from annotator/debug code where a batch is expected; flattening masks for storage/transport and forgetting to reshape to (N, H, W); mixing per-detection mask slices (detections.mask[i] is 2-D) with batch APIs.

Related errors


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