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
- 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.
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
- 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.
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
- All or none of the '{name}' fields must be None
- KeyPoints detection_confidence must be given for NMS to be e
- KeyPoints class_id must be given for NMS to be executed. If
- Cannot pass both 'confidence' and 'keypoint_confidence'. 'co
- Class {class_name} not found in target classes. source_class
AI-assisted analysis of roboflow/supervision@7f254d9784 (2026-08-15).
Data as JSON: /api/errors/55e675d08fd46957.
Report an issue: GitHub.