{"record":{"id":"be7328c6140fe517","repo":"roboflow/supervision","slug":"masks-must-be-shaped-n-h-w","errorCode":null,"errorMessage":"Masks must be shaped (N, H, W)","messagePattern":"Masks must be shaped \\(N, H, W\\)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/metrics/utils/object_size.py","lineNumber":197,"sourceCode":"\n    Example:\n        ```pycon\n        >>> import numpy as np\n        >>> from supervision.metrics.utils.object_size import get_mask_size_category\n        >>> mask = np.zeros((3, 100, 100), dtype=bool)\n        >>> mask[0, 0:10, 0:10] = True   # 100 (Small)\n        >>> mask[1, 0:50, 0:50] = True   # 2500 (Medium)\n        >>> mask[2, 0:100, 0:100] = True # 10000 (Large)\n        >>> get_mask_size_category(mask)\n        array([1, 2, 3])\n\n        ```\n    \"\"\"\n    if isinstance(mask, CompactMask):\n        areas = mask.area\n    else:\n        if len(mask.shape) != 3:\n            raise ValueError(\"Masks must be shaped (N, H, W)\")\n        # count_mask_pixels uses np.count_nonzero (no axis), which dispatches\n        # to SIMD popcount over the bool buffer and is ~6x faster than the\n        # vectorized np.sum(mask, axis=(1, 2)). Do not \"simplify\" back to\n        # np.sum(axis=(1,2)); benchmark before reverting. dtype=np.int64 keeps\n        # areas consistent across platforms (Windows NumPy defaults to int32).\n        areas = count_mask_pixels(mask)\n\n    result = np.full(areas.shape, ObjectSizeCategory.ANY.value)\n    SM, LG = SIZE_THRESHOLDS\n    result[areas < SM] = ObjectSizeCategory.SMALL.value\n    result[(areas >= SM) & (areas < LG)] = ObjectSizeCategory.MEDIUM.value\n    result[areas >= LG] = ObjectSizeCategory.LARGE.value\n    return result\n\n\ndef get_obb_size_category(xyxyxyxy: npt.NDArray[np.number]) -> npt.NDArray[np.int_]:\n    \"\"\"\n    Get the size category of a oriented bounding boxes array.","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/metrics/utils/object_size.py#L179-L215","documentation":"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.","triggerScenarios":"Calling get_mask_size_category with a single (H, W) mask, an (N, H, W, 3) array, or masks stacked along the wrong axis.","commonSituations":"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.","solutions":["Stack per-instance masks: mask = np.stack(instance_masks) so shape is (N, H, W)","For a single mask, wrap it: mask[None, :, :]","Ensure boolean dtype and one channel per detection, not an RGB or label-map tensor"],"exampleFix":"# before\nmask = np.zeros((480, 640), dtype=bool)   # single 2-D mask\nget_mask_size_category(mask)\n\n# after\nmask = np.zeros((1, 480, 640), dtype=bool)  # (N=1, H, W)\nget_mask_size_category(mask)","handlingStrategy":"validation","validationCode":"mask = np.asarray(mask)\nif mask.ndim == 2:\n    mask = mask[None, :, :]\nassert mask.ndim == 3, 'masks must be (N, H, W)'\ncats = get_mask_size_category(mask.astype(bool))","typeGuard":"import numpy as np\n\ndef is_instance_masks(arr: np.ndarray) -> bool:\n    \"\"\"True when arr is an (N, H, W) mask stack.\"\"\"\n    return arr.ndim == 3","tryCatchPattern":"try:\n    cats = get_mask_size_category(mask)\nexcept ValueError as e:\n    if '(N, H, W)' in str(e):\n        cats = get_mask_size_category(np.asarray(mask)[None if np.asarray(mask).ndim == 2 else Ellipsis])\n    else:\n        raise","preventionTips":["Always np.stack() per-instance masks rather than passing one semantic mask","Keep a shape assert in your segmentation post-processing: masks.ndim == 3"],"tags":["metrics","object-size","mask","shape-validation"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}