{"record":{"id":"8fcf1003c07fc5ab","repo":"roboflow/supervision","slug":"input-mask-must-be-2d","errorCode":null,"errorMessage":"Input mask must be 2D","messagePattern":"Input mask must be 2D","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/detection/utils/converters.py","lineNumber":792,"sourceCode":"        ...     [False, False, False, False],\n        ...     [False, True,  True,  False],\n        ...     [False, True,  True,  False],\n        ...     [False, False, False, False],\n        ... ])\n        >>> rle = sv.mask_to_rle(mask)\n        >>> [int(x) for x in rle]\n        [5, 2, 2, 2, 5]\n\n        >>> sv.mask_to_rle(mask, compressed=True)\n        '52203'\n\n        ```\n\n    ![mask_to_rle](https://media.roboflow.com/supervision-docs/\n    mask-to-rle.png){ align=center width=\"800\" }\n    \"\"\"\n    if mask.ndim != 2:\n        raise ValueError(\"Input mask must be 2D\")\n    if mask.size == 0:\n        raise ValueError(\"Input mask cannot be empty\")\n\n    counts: list[int] = cast(list[int], _mask_to_rle_counts(mask).tolist())\n    if compressed:\n        return _base48_encode(_delta_encode(counts))\n    return counts\n\n\ndef polygon_to_xyxy(polygon: npt.NDArray[np.number]) -> npt.NDArray[np.number]:\n    \"\"\"\n    Converts a polygon represented by a NumPy array into a bounding box.\n\n    Args:\n        polygon: A polygon represented by a NumPy array of shape `(N, 2)`,\n            containing the `x`, `y` coordinates of the points.\n\n    Returns:","sourceCodeStart":774,"sourceCodeEnd":810,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/detection/utils/converters.py#L774-L810","documentation":"mask_to_rle encodes a single 2D binary mask (H x W) into alternating run counts; a 3D array (e.g. a batch of masks with shape (N, H, W)) or a 1D vector has no well-defined scan order for a single mask, so ndim != 2 raises immediately.","triggerScenarios":"Passing detections.mask (shape (N, H, W)) directly instead of one slice detections.mask[i]; passing a single row/column vector; passing a mask with a trailing channel dimension (H, W, 1) from a tensor conversion.","commonSituations":"Iterating batches and forgetting to index; converting torch tensors with .numpy() that keep a batch or channel dim; feeding model output directly.","solutions":["Index one mask from the batch: mask_to_rle(detections.mask[i]).","Squeeze spurious dims right before the call: np.squeeze(mask) then assert .ndim == 2.","For a (H, W, 1) tensor conversion, use [..., 0]."],"exampleFix":"# before\n rles = [sv.mask_to_rle(m) for m in detections.mask]\n\n# after\n rles = [sv.mask_to_rle(detections.mask[i]) for i in range(len(detections))]","handlingStrategy":"type-guard","validationCode":"assert mask.ndim == 2, f\"expected 2D mask, got shape {mask.shape}\"","typeGuard":"def is_single_2d_mask(mask: npt.NDArray) -> bool:\n    return mask.ndim == 2","tryCatchPattern":null,"preventionTips":["Index the batch: detections.mask[i], never pass the (N, H, W) array.","After tensor -> numpy conversions, assert ndim before encoding.","Squeeze channel dims at the pipeline boundary."],"tags":["rle","mask","shape","batch-dim"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}