| import numpy as np |
| from dataclasses import dataclass |
| from typing import Optional |
|
|
| @dataclass |
| class ValidationResult: |
| valid: bool |
| error: Optional[str] = None |
|
|
| def validate_landmarks(landmarks) -> ValidationResult: |
| if landmarks is None: |
| return ValidationResult(False, "No face detected") |
| |
| if len(landmarks) != 478: |
| return ValidationResult(False, f"Expected 478 landmarks, got {len(landmarks)}") |
| |
| if np.any(np.isnan(landmarks)) or np.any(np.isinf(landmarks)): |
| return ValidationResult(False, "Landmarks contain NaN or inf values") |
| |
| return ValidationResult(True) |
|
|
| def validate_features(features: dict) -> ValidationResult: |
| EXPECTED_KEYS = { |
| "face_ratio", "jaw_ratio", "jaw_to_height", "eye_ratio", |
| "eye_height", "lip_ratio", "nose_position", "lower_face_ratio", |
| "chin_prominence", "symmetry", "upper_third", |
| "middle_third", "lower_third", "mid_lower_ratio" |
| } |
|
|
| missing = EXPECTED_KEYS - set(features.keys()) |
| if missing: |
| return ValidationResult(False, f"Missing features: {missing}") |
| |
| for key, value in features.items(): |
| if not np.isfinite(value): |
| return ValidationResult(False, f"Feature '{key}' is {value}") |
| if value < 0: |
| return ValidationResult(False, f"Feature '{key}' is negative: {value}") |
| |
| SANITY_BOUNDS = { |
| "face_ratio": (0.5, 2.5), |
| "jaw_ratio": (0.3, 1.2), |
| "eye_ratio": (0.2, 0.9), |
| "symmetry": (0.0, 1.0), |
| "nose_position": (0.2, 0.8), |
| "upper_third": (0.1, 0.6), |
| "middle_third": (0.1, 0.6), |
| "lower_third": (0.1, 0.6), |
| "mid_lower_ratio": (0.2, 3.0), |
| } |
|
|
| for key, (lo, hi) in SANITY_BOUNDS.items(): |
| if key in features and not (lo <= features[key] <= hi): |
| return ValidationResult( |
| False, |
| f"Feature '{key}' = {features[key]:.3f} is outside expected range [{lo}, {hi}]" |
| ) |
| |
| return ValidationResult(True) |