{"record":{"id":"55e675d08fd46957","repo":"roboflow/supervision","slug":"all-keypoints-must-have-the-same-coordinate-depth","errorCode":null,"errorMessage":"All KeyPoints must have the same coordinate depth per skeleton to be merged; got depths {sorted(keypoint_depths)}.","messagePattern":"All KeyPoints must have the same coordinate depth per skeleton to be merged; got depths (.+?)\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/key_points/core.py","lineNumber":1268,"sourceCode":"            _validate_keypoints_fields(\n                xy=key_points.xy,\n                class_id=key_points.class_id,\n                confidence=key_points.keypoint_confidence,\n                detection_confidence=key_points.detection_confidence,\n                visible=key_points.visible,\n                data=key_points.data,\n            )\n\n        keypoint_counts = {key_points.xy.shape[1] for key_points in key_points_list}\n        if len(keypoint_counts) > 1:\n            raise ValueError(\n                \"All KeyPoints must have the same number of keypoints per \"\n                f\"skeleton to be merged; got counts {sorted(keypoint_counts)}.\"\n            )\n\n        keypoint_depths = {key_points.xy.shape[2] for key_points in key_points_list}\n        if len(keypoint_depths) > 1:\n            raise ValueError(\n                \"All KeyPoints must have the same coordinate depth per \"\n                f\"skeleton to be merged; got depths {sorted(keypoint_depths)}.\"\n            )\n\n        xy = np.vstack([key_points.xy for key_points in key_points_list])\n\n        def stack_or_none(name: str) -> npt.NDArray[np.generic] | None:\n            values = [getattr(key_points, name) for key_points in key_points_list]\n            if all(value is None for value in values):\n                return None\n            if any(value is None for value in values):\n                raise ValueError(f\"All or none of the '{name}' fields must be None\")\n            return cast(npt.NDArray[np.generic], np.concatenate(values, axis=0))\n\n        class_id = cast(npt.NDArray[np.int_] | None, stack_or_none(\"class_id\"))\n        keypoint_confidence = cast(\n            npt.NDArray[np.float32] | None, stack_or_none(\"keypoint_confidence\")\n        )","sourceCodeStart":1250,"sourceCodeEnd":1286,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/key_points/core.py#L1250-L1286","documentation":"Raised by KeyPoints.merge() when the KeyPoints objects being merged have inconsistent xy array depths (xy.shape[2]). In supervision, KeyPoints.xy has shape (N, num_keypoints, depth) where depth is 2 for x,y coordinates or 3 for x,y,visibility. Because merge() vertically stacks the xy arrays with np.vstack, all inputs must share the same depth or the resulting array would be ragged and downstream math would break.","triggerScenarios":"Calling sv.KeyPoints.merge([kp2d, kp3d]) where one KeyPoints was built from a 2-column coordinate array (e.g. from_mediapipe output without visibility) and another from a 3-column array (e.g. from_ultralytics with confidence/visibility included).","commonSituations":"Combining pose estimation results from different model connectors (MediaPipe gives 3D landmarks, YOLO-pose gives 2D + confidence), or batching predictions where one source stored only x,y and another stored x,y,confidence.","solutions":["Normalize every KeyPoints to the same depth before merging: if some have shape[2]==3, either drop the third channel (kp.xy = kp.xy[..., :2]) or reconstruct the missing one (e.g. set visibility/confidence-derived third channel).","Merge only KeyPoints produced by the same connector/model so the depth is guaranteed identical.","Check depths up front: {kp.xy.shape[2] for kp in key_points_list} and branch or raise a clear error in your own pipeline."],"exampleFix":"// before\nmerged = sv.KeyPoints.merge([kp_from_mediapipe, kp_from_yolo])  # depths {3, 2}\n\n// after\n# normalize to 2D before merging\nkps = [kp if kp.xy.shape[2] == 2 else sv.KeyPoints(xy=kp.xy[..., :2], class_id=kp.class_id, ...) for kp in [kp_from_mediapipe, kp_from_yolo]]\nmerged = sv.KeyPoints.merge(kps)","handlingStrategy":"validation","validationCode":"def assert_uniform_depth(key_points_list):\n    depths = {kp.xy.shape[2] for kp in key_points_list}\n    assert len(depths) == 1, f\"Mixed KeyPoints depths: {depths}\"\n\nassert_uniform_depth([kp_a, kp_b])\nmerged = sv.KeyPoints.merge([kp_a, kp_b])","typeGuard":"def same_depth(key_points_list: list[sv.KeyPoints]) -> bool:\n    return len({kp.xy.shape[2] for kp in key_points_list}) == 1","tryCatchPattern":"try:\n    merged = sv.KeyPoints.merge(kps)\nexcept ValueError as e:\n    if \"coordinate depth\" in str(e):\n        kps = [kp if kp.xy.shape[2] == 2 else replace(kp, xy=kp.xy[..., :2]) for kp in kps]\n        merged = sv.KeyPoints.merge(kps)\n    else:\n        raise","preventionTips":["Merge KeyPoints produced by the same model connector only.","Normalize xy to a fixed depth (2 or 3) in a single adapter function before any merge call.","Assert {kp.xy.shape[2] for kp in kps} has exactly one element in tests covering merge paths."],"tags":["keypoints","pose-estimation","merge","validation"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}