{"record":{"id":"afe49b27a6996ebc","repo":"roboflow/supervision","slug":"connected-component-input-must-be-a-two-dimensiona","errorCode":null,"errorMessage":"Connected-component input must be a two-dimensional image","messagePattern":"Connected-component input must be a two-dimensional image","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/_cv2/_components.py","lineNumber":15,"sourceCode":"\"\"\"Private connected-component and mask-topology fallbacks.\"\"\"\n\nfrom __future__ import annotations\n\nfrom typing import Any, cast\n\nimport numpy as np\nimport numpy.typing as npt\n\n\ndef _validate_binary_image(image: npt.NDArray[Any]) -> npt.NDArray[np.bool_]:\n    \"\"\"Validate and normalize a two-dimensional component image.\"\"\"\n    values = np.asarray(image)\n    if values.ndim != 2:\n        raise ValueError(\"Connected-component input must be a two-dimensional image\")\n    return cast(npt.NDArray[np.bool_], values != 0)\n\n\ndef _label(\n    image: npt.NDArray[Any], connectivity: int\n) -> tuple[int, npt.NDArray[np.int32]]:\n    \"\"\"Label foreground pixels with the requested four- or eight-way topology.\"\"\"\n    if connectivity not in (4, 8):\n        raise ValueError(\"Only 4- and 8-connectivity are supported\")\n\n    from scipy import ndimage\n\n    structure = ndimage.generate_binary_structure(2, 1 if connectivity == 4 else 2)\n    labels, count = ndimage.label(_validate_binary_image(image), structure=structure)\n    return int(count), np.ascontiguousarray(labels, dtype=np.int32)\n\n\ndef _connected_components(","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/_cv2/_components.py#L1-L33","documentation":"Connected-component labeling is inherently a 2D operation; the fallback validates that the input array has exactly two dimensions before converting it to a boolean mask. 3D arrays (video volumes, multi-channel images) or 1D/0D arrays are rejected.","triggerScenarios":"Calling cv2.connectedComponents on a (H, W, 3) BGR image, a (T, H, W) video stack, or a squeezed-to-1D mask.","commonSituations":"Forgetting cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) before labeling; passing a batch of masks at once; masks with a spurious trailing channel dimension (H, W, 1).","solutions":["Convert color input to grayscale first: gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY).","Squeeze channel/batch dims: mask = mask.reshape(h, w) or mask.squeeze().","Loop over frames/batch items and label each 2D slice separately."],"exampleFix":"# before\ncount, labels = cv2.connectedComponents(bgr_image)  # (H, W, 3)\n\n# after\ngray = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2GRAY)\ncount, labels = cv2.connectedComponents(gray)","handlingStrategy":"validation","validationCode":"img2d = image if image.ndim == 2 else image.reshape(image.shape[:2])\ncount, labels = cv2.connectedComponents(img2d)","typeGuard":"def is_labelable(img: np.ndarray) -> bool:\n    return np.asarray(img).ndim == 2","tryCatchPattern":null,"preventionTips":["Convert BGR to grayscale before labeling","Squeeze (H, W, 1) masks to 2D","Label video/multi-channel data per 2D slice"],"tags":["opencv-fallback","connected-components","dimensionality","input-validation"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}