"""ADR-117 P2 tests — Keypoint + KeypointType binding round-trips. Run with: cd python && .venv/Scripts/python -m pytest tests/test_keypoint.py -v """ from __future__ import annotations import pytest from wifi_densepose import Keypoint, KeypointType # ─── KeypointType ──────────────────────────────────────────────────── def test_keypoint_type_all_returns_17() -> None: """COCO standard defines exactly 17 keypoints.""" assert len(KeypointType.all()) == 17 def test_keypoint_type_index_matches_coco_ordering() -> None: """Indexes 0..16 match the COCO canonical ordering.""" expected = [ (KeypointType.Nose, 0), (KeypointType.LeftEye, 1), (KeypointType.RightEye, 2), (KeypointType.LeftEar, 3), (KeypointType.RightEar, 4), (KeypointType.LeftShoulder, 5), (KeypointType.RightShoulder, 6), (KeypointType.LeftElbow, 7), (KeypointType.RightElbow, 8), (KeypointType.LeftWrist, 9), (KeypointType.RightWrist, 10), (KeypointType.LeftHip, 11), (KeypointType.RightHip, 12), (KeypointType.LeftKnee, 13), (KeypointType.RightKnee, 14), (KeypointType.LeftAnkle, 15), (KeypointType.RightAnkle, 16), ] for kp, idx in expected: assert kp.index == idx, f"{kp} expected index {idx} got {kp.index}" def test_keypoint_type_snake_name() -> None: """snake_name follows COCO convention.""" assert KeypointType.Nose.snake_name == "nose" assert KeypointType.LeftShoulder.snake_name == "left_shoulder" assert KeypointType.RightAnkle.snake_name == "right_ankle" def test_keypoint_type_is_face() -> None: """is_face() matches the 5 facial keypoints.""" face = { KeypointType.Nose, KeypointType.LeftEye, KeypointType.RightEye, KeypointType.LeftEar, KeypointType.RightEar, } for kp in KeypointType.all(): assert kp.is_face() == (kp in face) def test_keypoint_type_is_upper_body() -> None: """is_upper_body() catches shoulders, elbows, wrists.""" assert KeypointType.LeftShoulder.is_upper_body() assert KeypointType.RightShoulder.is_upper_body() assert KeypointType.LeftElbow.is_upper_body() assert KeypointType.LeftWrist.is_upper_body() assert not KeypointType.LeftHip.is_upper_body() def test_keypoint_type_eq() -> None: """Equality + identity work across calls.""" assert KeypointType.Nose == KeypointType.Nose assert KeypointType.Nose != KeypointType.LeftEye def test_keypoint_type_repr() -> None: """repr is a useful Python expression.""" assert repr(KeypointType.Nose) == "KeypointType.Nose" assert repr(KeypointType.LeftWrist) == "KeypointType.LeftWrist" # ─── Keypoint ──────────────────────────────────────────────────────── def test_keypoint_2d_construct() -> None: """Default 2D keypoint.""" kp = Keypoint(KeypointType.Nose, 0.5, 0.3, 0.95) assert kp.x == pytest.approx(0.5) assert kp.y == pytest.approx(0.3) assert kp.z is None assert kp.confidence == pytest.approx(0.95) assert kp.keypoint_type == KeypointType.Nose assert kp.is_visible def test_keypoint_3d_construct() -> None: """3D keypoint with kwarg z.""" kp = Keypoint(KeypointType.LeftWrist, 0.2, 0.4, 0.8, z=0.1) assert kp.position_3d == pytest.approx((0.2, 0.4, 0.1)) assert kp.z == pytest.approx(0.1) def test_keypoint_position_2d_tuple() -> None: kp = Keypoint(KeypointType.RightHip, 0.6, 0.7, 0.99) assert kp.position_2d == pytest.approx((0.6, 0.7)) def test_keypoint_position_3d_none_for_2d() -> None: """2D keypoints return None for position_3d, not a default z.""" kp = Keypoint(KeypointType.Nose, 0.5, 0.5, 0.99) assert kp.position_3d is None def test_keypoint_is_visible_below_threshold() -> None: """Confidence under 0.5 is NOT visible (default threshold).""" kp_low = Keypoint(KeypointType.Nose, 0.0, 0.0, 0.3) kp_high = Keypoint(KeypointType.Nose, 0.0, 0.0, 0.7) assert not kp_low.is_visible assert kp_high.is_visible def test_keypoint_confidence_validation_too_high() -> None: """Confidence > 1.0 rejected.""" with pytest.raises(ValueError, match="Confidence must be in"): Keypoint(KeypointType.Nose, 0.0, 0.0, 1.5) def test_keypoint_confidence_validation_negative() -> None: """Negative confidence rejected.""" with pytest.raises(ValueError, match="Confidence must be in"): Keypoint(KeypointType.Nose, 0.0, 0.0, -0.1) def test_keypoint_distance_2d() -> None: """Euclidean distance in 2D.""" a = Keypoint(KeypointType.Nose, 0.0, 0.0, 1.0) b = Keypoint(KeypointType.LeftEye, 3.0, 4.0, 1.0) assert a.distance_to(b) == pytest.approx(5.0) def test_keypoint_distance_3d() -> None: """Euclidean distance in 3D when both have z.""" a = Keypoint(KeypointType.Nose, 0.0, 0.0, 1.0, z=0.0) b = Keypoint(KeypointType.LeftEye, 1.0, 2.0, 1.0, z=2.0) # sqrt(1 + 4 + 4) = 3.0 assert a.distance_to(b) == pytest.approx(3.0) def test_keypoint_distance_falls_back_to_2d_if_mixed() -> None: """Mixing 2D and 3D keypoints uses 2D distance only.""" a = Keypoint(KeypointType.Nose, 0.0, 0.0, 1.0) # 2D b = Keypoint(KeypointType.LeftEye, 3.0, 4.0, 1.0, z=99.0) # 3D # Should be 5.0 (2D distance), not include the z=99 term assert a.distance_to(b) == pytest.approx(5.0) def test_keypoint_repr_2d() -> None: kp = Keypoint(KeypointType.Nose, 0.5, 0.3, 0.95) r = repr(kp) assert "KeypointType.Nose" in r assert "x=0.5" in r assert "y=0.3" in r assert "z" not in r # no z field for 2D def test_keypoint_repr_3d() -> None: kp = Keypoint(KeypointType.Nose, 0.5, 0.3, 0.95, z=0.1) r = repr(kp) assert "z=0.1" in r def test_keypoint_eq() -> None: """Two keypoints with same fields compare equal.""" a = Keypoint(KeypointType.Nose, 0.5, 0.3, 0.95) b = Keypoint(KeypointType.Nose, 0.5, 0.3, 0.95) assert a == b def test_keypoint_neq_different_type() -> None: a = Keypoint(KeypointType.Nose, 0.5, 0.3, 0.95) b = Keypoint(KeypointType.LeftEye, 0.5, 0.3, 0.95) assert a != b def test_keypoint_neq_different_position() -> None: a = Keypoint(KeypointType.Nose, 0.5, 0.3, 0.95) b = Keypoint(KeypointType.Nose, 0.6, 0.3, 0.95) assert a != b def test_build_features_marks_p2() -> None: """The P2 marker is now in the wheel's feature list.""" import wifi_densepose assert "p2-keypoint-bindings" in wifi_densepose.__build_features__