roboflow/supervision · error · ValueError

All KeyPoints must have the same coordinate depth per skelet

Error message

All KeyPoints must have the same coordinate depth per skeleton to be merged; got depths {sorted(keypoint_depths)}.

What it means

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.

Source

Thrown at src/supervision/key_points/core.py:1268

            _validate_keypoints_fields(
                xy=key_points.xy,
                class_id=key_points.class_id,
                confidence=key_points.keypoint_confidence,
                detection_confidence=key_points.detection_confidence,
                visible=key_points.visible,
                data=key_points.data,
            )

        keypoint_counts = {key_points.xy.shape[1] for key_points in key_points_list}
        if len(keypoint_counts) > 1:
            raise ValueError(
                "All KeyPoints must have the same number of keypoints per "
                f"skeleton to be merged; got counts {sorted(keypoint_counts)}."
            )

        keypoint_depths = {key_points.xy.shape[2] for key_points in key_points_list}
        if len(keypoint_depths) > 1:
            raise ValueError(
                "All KeyPoints must have the same coordinate depth per "
                f"skeleton to be merged; got depths {sorted(keypoint_depths)}."
            )

        xy = np.vstack([key_points.xy for key_points in key_points_list])

        def stack_or_none(name: str) -> npt.NDArray[np.generic] | None:
            values = [getattr(key_points, name) for key_points in key_points_list]
            if all(value is None for value in values):
                return None
            if any(value is None for value in values):
                raise ValueError(f"All or none of the '{name}' fields must be None")
            return cast(npt.NDArray[np.generic], np.concatenate(values, axis=0))

        class_id = cast(npt.NDArray[np.int_] | None, stack_or_none("class_id"))
        keypoint_confidence = cast(
            npt.NDArray[np.float32] | None, stack_or_none("keypoint_confidence")
        )

View on GitHub (pinned to 7f254d9784)

Solutions

  1. 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).
  2. Merge only KeyPoints produced by the same connector/model so the depth is guaranteed identical.
  3. 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.

Example fix

// before
merged = sv.KeyPoints.merge([kp_from_mediapipe, kp_from_yolo])  # depths {3, 2}

// after
# normalize to 2D before merging
kps = [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]]
merged = sv.KeyPoints.merge(kps)
Defensive patterns

Strategy: validation

Validate before calling

def assert_uniform_depth(key_points_list):
    depths = {kp.xy.shape[2] for kp in key_points_list}
    assert len(depths) == 1, f"Mixed KeyPoints depths: {depths}"

assert_uniform_depth([kp_a, kp_b])
merged = sv.KeyPoints.merge([kp_a, kp_b])

Type guard

def same_depth(key_points_list: list[sv.KeyPoints]) -> bool:
    return len({kp.xy.shape[2] for kp in key_points_list}) == 1

Try / catch

try:
    merged = sv.KeyPoints.merge(kps)
except ValueError as e:
    if "coordinate depth" in str(e):
        kps = [kp if kp.xy.shape[2] == 2 else replace(kp, xy=kp.xy[..., :2]) for kp in kps]
        merged = sv.KeyPoints.merge(kps)
    else:
        raise

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of roboflow/supervision@7f254d9784 (2026-08-15). Data as JSON: /api/errors/55e675d08fd46957. Report an issue: GitHub.