{"record":{"id":"b8cc73d3aa3b53d5","repo":"roboflow/supervision","slug":"at-least-one-channel-is-required","errorCode":null,"errorMessage":"At least one channel is required","messagePattern":"At least one channel is required","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/_cv2/_color.py","lineNumber":93,"sourceCode":"    green = np.choose(sector_index, (x, chroma, chroma, x, zeros, zeros))\n    blue = np.choose(sector_index, (zeros, zeros, x, chroma, chroma, x))\n    bgr = np.stack((blue + match, green + match, red + match), axis=-1) * 255\n    return _cast_array_like_opencv(bgr, image.dtype)\n\n\ndef _split(image: npt.NDArray[Any]) -> tuple[npt.NDArray[Any], ...]:\n    \"\"\"Split an image into contiguous single-channel arrays.\"\"\"\n    if image.ndim == 2:\n        return (np.ascontiguousarray(image),)\n    return tuple(\n        np.ascontiguousarray(image[..., index]) for index in range(image.shape[2])\n    )\n\n\ndef _merge(channels: Sequence[npt.NDArray[Any]]) -> npt.NDArray[Any]:\n    \"\"\"Merge single-channel arrays along their final axis.\"\"\"\n    if not channels:\n        raise ValueError(\"At least one channel is required\")\n    return np.ascontiguousarray(np.stack(channels, axis=-1))\n","sourceCodeStart":75,"sourceCodeEnd":95,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/_cv2/_color.py#L75-L95","documentation":"The fallback `cv2.merge` at src/supervision/_cv2/_color.py:93 stacks a sequence of single-channel arrays along the last axis; merging an empty sequence has no defined shape/dtype, so it raises ValueError. Mirrors OpenCV's own requirement of at least one channel.","triggerScenarios":"Calling `cv2.merge([])` or `cv2.merge(channels)` where `channels` is an empty list/tuple — commonly when channels are produced by filtering/slicing that can return zero elements (e.g. `cv2.split(img)[::3]` style code, or a loop building channels from a zero-length iterable).","commonSituations":"Dynamic channel selection where the selection set is empty for some inputs; iterating over contours/masks that yield no channel arrays and forwarding the result unconditionally.","solutions":["Guard before merging: `if not channels: skip or raise a domain error`","Fix the upstream filter so it always yields >= 1 channel","Default to a concrete channel list when the dynamic selection is empty"],"exampleFix":"// before\nmerged = cv2.merge(selected_channels)  # selected_channels may be []\n\n// after\nif not selected_channels:\n    raise ValueError(f'no channels selected from {source}')\nmerged = cv2.merge(selected_channels)","handlingStrategy":"validation","validationCode":"def merge_channels(channels):\n    \"\"\"Merge only a non-empty channel sequence.\"\"\"\n    if not channels:\n        raise ValueError(\"cannot merge zero channels\")\n    return cv2.merge(channels)","typeGuard":"def is_mergeable(channels) -> bool:\n    \"\"\"cv2.merge requires at least one channel array.\"\"\"\n    return len(channels) > 0","tryCatchPattern":null,"preventionTips":["Guard dynamic channel lists for emptiness before merging","Log the source collection size when a channel filter can produce zero results"],"tags":["cv2-fallback","channels","merge","validation"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}