hairstyle-recommender-api / src /validation.py
juras3k's picture
Deploy backend API to HF Space
c4ba2f9
Raw
History Blame Contribute Delete
2.03 kB
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)