import math from src.config_loader import HeadPoseSettings from src.head_pose import HeadPoseEstimator from src.models import FaceLandmark def settings(max_angle: float = 90.0) -> HeadPoseSettings: return HeadPoseSettings(True, "solvepnp", True, "ema", .4, max_angle) def landmarks() -> list[FaceLandmark]: return [FaceLandmark(index, float(index), float(index + 1), 0.0) for index in HeadPoseEstimator.LANDMARK_INDICES] def test_missing_landmarks_and_input_builders() -> None: estimator = HeadPoseEstimator(settings()) assert estimator.estimate([], 640, 480).failure_reason == "insufficient_landmarks" assert len(estimator.build_image_points(landmarks()) or []) == 6 matrix = estimator.build_camera_matrix(640, 480) assert matrix == [[640.0, 0.0, 320.0], [0.0, 640.0, 240.0], [0.0, 0.0, 1.0]] def test_euler_conversion_returns_floats() -> None: angles = HeadPoseEstimator.rotation_matrix_to_euler([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]) assert all(isinstance(value, float) for value in angles) assert angles == (0.0, -0.0, 0.0) def test_angle_range_validation() -> None: estimator = HeadPoseEstimator(settings(45.0)) assert estimator.result_from_angles((10.0, 20.0, 5.0)).success failed = estimator.result_from_angles((10.0, 60.0, 5.0)) assert failed.failure_reason == "angle_out_of_range" assert (failed.pitch, failed.yaw, failed.roll) == (10.0, 60.0, 5.0) assert estimator.result_from_angles((math.nan, 0.0, 0.0)).failure_reason == "angle_out_of_range" def test_face_model_uses_image_coordinate_y_direction() -> None: _, chin, left_eye, right_eye, left_mouth, right_mouth = HeadPoseEstimator.MODEL_POINTS assert chin[1] > 0 assert left_eye[1] < 0 and right_eye[1] < 0 assert left_mouth[1] > 0 and right_mouth[1] > 0 def test_roll_near_180_is_canonicalized_for_upright_face() -> None: estimator = HeadPoseEstimator(settings()) assert estimator.normalize_angles((2.0, -5.0, -170.0)) == (2.0, -5.0, 10.0) assert estimator.normalize_angles((2.0, -5.0, 170.0)) == (2.0, -5.0, -10.0) result = estimator.result_from_angles((2.0, -5.0, -170.0)) assert result.success and result.roll == 10.0