CoolFace
Apppublic

algoryn/validation

sourceHugging Facemitupdated 1y agoView on Hugging Face
0likes
api.py362 linesDownload Raw Back to validate
1"""2FastAPI endpoint for identity validation service.3 4This module provides the main API endpoint for identity validation,5accepting ID photos, user videos, and gesture requirements to perform6comprehensive identity verification.7"""8 9import os10import json11import tempfile12import time13import logging14from typing import Optional15from datetime import datetime, timezone16 17from fastapi import FastAPI, UploadFile, File, Form, HTTPException, Depends18from fastapi.responses import ORJSONResponse19 20from .models import ValidationRequest, ValidationResponse, ValidationStatus21from .facial_validator import FacialValidator22from .gesture_validator import GestureValidator23from .config import config24 25logger = logging.getLogger(__name__)26 27# Create FastAPI app28app = FastAPI(29    title="Identity Validation API",30    description="API for identity verification using facial recognition and gesture validation",31    version="1.0.0",32    default_response_class=ORJSONResponse33)34 35# Initialize validators36facial_validator = FacialValidator()37gesture_validator = GestureValidator()38 39 40def get_validation_request(41    gestures: str = Form(...),42    # Gesture validation parameters (optional, fallback to env vars)43    error_margin: str = Form("default"),44    min_gesture_duration: str = Form("default"),45    require_all_gestures: str = Form("default"),46    confidence_threshold: str = Form("default"),47    # Facial recognition parameters (optional, fallback to env vars)48    similarity_threshold: str = Form("default"),49    frame_sample_rate: str = Form("default"),50    # Response parameters51    include_details: bool = Form(False)52) -> ValidationRequest:53    """54    Parse and validate the validation request from form data.55 56    All parameters are optional and will fall back to environment variable57    defaults if not provided. This allows for flexible configuration at both58    the request level and server level.59 60    Parameters61    ----------62    gestures : str63        JSON string containing the list of required gestures64    error_margin : Optional[float]65        Error margin for gesture validation (0.0-1.0). Uses DEFAULT_ERROR_MARGIN env var if None66    min_gesture_duration : Optional[int]67        Minimum duration for gesture detection. Uses MIN_GESTURE_DURATION env var if None68    require_all_gestures : Optional[bool]69        Whether all gestures must be present. Uses REQUIRE_ALL_GESTURES env var if None70    confidence_threshold : Optional[float]71        Minimum confidence threshold for gesture detection. Uses CONFIDENCE_THRESHOLD env var if None72    similarity_threshold : Optional[float]73        Minimum similarity threshold for facial matching. Uses SIMILARITY_THRESHOLD env var if None74    frame_sample_rate : Optional[int]75        Rate for sampling video frames for face detection. Uses FRAME_SAMPLE_RATE env var if None76    include_details : Optional[bool]77        Whether to include detailed results in response78 79    Returns80    -------81    ValidationRequest82        Parsed and validated request object with environment fallbacks83 84    Raises85    ------86    HTTPException87        If request validation fails88    """89    try:90        # Parse gestures JSON91        gesture_list = json.loads(gestures)92        if not isinstance(gesture_list, list):93            raise ValueError("gestures must be a list")94        if not gesture_list:95            raise ValueError("gestures list cannot be empty")96 97        # Validate gesture names (basic validation)98        for gesture in gesture_list:99            if not isinstance(gesture, str) or not gesture.strip():100                raise ValueError(f"Invalid gesture name: {gesture}")101 102    except json.JSONDecodeError as e:103        raise HTTPException(104            status_code=400,105            detail=f"Invalid JSON in gestures field: {str(e)}"106        )107    except ValueError as e:108        raise HTTPException(109            status_code=400,110            detail=f"Invalid gestures data: {str(e)}"111        )112 113    # Parse and convert parameters, using config defaults when "default" is provided114    def parse_param(value, default_value, value_type):115        """Parse parameter value, using default if 'default' string is provided."""116        # Handle FastAPI Form objects - extract the actual value from .default117        if hasattr(value, 'default'):118            actual_value = value.default119        else:120            actual_value = value121 122        if actual_value == "default" or actual_value is None:123            return default_value124        try:125            if value_type == float:126                return float(actual_value)127            elif value_type == int:128                return int(actual_value)129            elif value_type == bool:130                return str(actual_value).lower() in ('true', '1', 'yes', 'on')131            else:132                return actual_value133        except (ValueError, TypeError, AttributeError):134            raise HTTPException(135                status_code=400,136                detail=f"Invalid value for parameter: {actual_value}"137            )138 139    final_error_margin = parse_param(error_margin, config.default_error_margin, float)140    final_min_gesture_duration = parse_param(min_gesture_duration, config.min_gesture_duration, int)141    final_require_all_gestures = parse_param(require_all_gestures, config.require_all_gestures, bool)142    final_confidence_threshold = parse_param(confidence_threshold, config.confidence_threshold, float)143    final_similarity_threshold = parse_param(similarity_threshold, config.similarity_threshold, float)144    final_frame_sample_rate = parse_param(frame_sample_rate, config.frame_sample_rate, int)145 146    # Parse include_details parameter147    final_include_details = parse_param(include_details, False, bool)148 149    return ValidationRequest(150        asked_gestures=gesture_list,151        error_margin=final_error_margin,152        min_gesture_duration=final_min_gesture_duration,153        require_all_gestures=final_require_all_gestures,154        confidence_threshold=final_confidence_threshold,155        similarity_threshold=final_similarity_threshold,156        frame_sample_rate=final_frame_sample_rate,157        include_details=final_include_details158    )159 160 161@app.post("/", response_model=ValidationResponse)162async def validate_identity(163    photo: UploadFile = File(...),164    video: UploadFile = File(...),165    request: ValidationRequest = Depends(get_validation_request)166):167    """168    Validate user identity using facial recognition and gesture validation.169 170    This endpoint accepts an ID document photo, a user video containing171    the person's face and required gestures, and a list of gestures that172    must be performed. It returns validation results for both facial173    recognition and gesture compliance.174 175    Parameters176    ----------177    photo : UploadFile178        ID document photo file (image format)179    video : UploadFile180        User video file containing face and gestures (video format)181    request : ValidationRequest182        Validation configuration and gesture requirements183 184    Returns185    -------186    ValidationResponse187        Validation results with success indicators and optional details188 189    Raises190    ------191    HTTPException192        If validation fails or processing errors occur193    """194    start_time = time.time()195    logger.info(f"Identity validation request received for {request.asked_gestures}")196 197    # Validate file types198    if not photo.content_type or not photo.content_type.startswith(('image/', 'application/')):199        raise HTTPException(200            status_code=400,201            detail="Photo file must be an image"202        )203 204    if not video.content_type or not video.content_type.startswith('video/'):205        raise HTTPException(206            status_code=400,207            detail="Video file must be a video"208        )209 210    # Validate file sizes (basic check)211    MAX_FILE_SIZE = 100 * 1024 * 1024  # 100MB212    if photo.size and photo.size > MAX_FILE_SIZE:213        raise HTTPException(214            status_code=413,215            detail="Photo file too large (max 100MB)"216        )217 218    if video.size and video.size > MAX_FILE_SIZE:219        raise HTTPException(220            status_code=413,221            detail="Video file too large (max 100MB)"222        )223 224    # Create temporary files for processing225    temp_photo = None226    temp_video = None227 228    try:229        # Save uploaded files to temporary location230        with tempfile.NamedTemporaryFile(delete=False, suffix=f"_photo.{photo.filename.split('.')[-1] if '.' in photo.filename else 'jpg'}") as temp_photo_file:231            temp_photo = temp_photo_file.name232            photo_content = await photo.read()233            temp_photo_file.write(photo_content)234 235        with tempfile.NamedTemporaryFile(delete=False, suffix=f"_video.{video.filename.split('.')[-1] if '.' in video.filename else 'mp4'}") as temp_video_file:236            temp_video = temp_video_file.name237            video_content = await video.read()238            temp_video_file.write(video_content)239 240        logger.info(f"Files saved: photo={temp_photo}, video={temp_video}")241 242        # Perform facial validation243        logger.info("Starting facial validation")244 245        # Update facial validator with request-specific parameters if provided246        if request.similarity_threshold is not None:247            facial_validator.similarity_threshold = request.similarity_threshold248        if request.frame_sample_rate is not None:249            facial_validator.frame_sample_rate = request.frame_sample_rate250 251        face_result = facial_validator.validate_facial_match(temp_photo, temp_video)252 253        # Perform gesture validation254        logger.info("Starting gesture validation")255        gesture_result = gesture_validator.validate_gestures(256            temp_video,257            request.asked_gestures,258            error_margin=request.error_margin,259            require_all=request.require_all_gestures260        )261 262        # Update gesture validator with request-specific parameters if provided263        if request.confidence_threshold is not None:264            gesture_validator.confidence_threshold = request.confidence_threshold265        if request.min_gesture_duration is not None:266            gesture_validator.min_gesture_duration = request.min_gesture_duration267 268        # Determine overall result269        overall_success = face_result.success and gesture_result.success270        overall_status = ValidationStatus.SUCCESS if overall_success else ValidationStatus.PARTIAL271 272        # Calculate processing time273        processing_time_ms = int((time.time() - start_time) * 1000)274 275        # Build response276        response = ValidationResponse(277            face=face_result.success,278            gestures=gesture_result.success,279            overall=overall_success,280            status=overall_status,281            face_result=face_result if request.include_details else None,282            gesture_result=gesture_result if request.include_details else None,283            processing_time_ms=processing_time_ms,284            timestamp=datetime.now(timezone.utc).isoformat()285        )286 287        # Log results288        logger.info(289            "Identity validation completed",290            extra={291                "face_success": face_result.success,292                "gesture_success": gesture_result.success,293                "overall_success": overall_success,294                "processing_time_ms": processing_time_ms,295                "requested_gestures": request.asked_gestures296            }297        )298 299        return response300 301    except Exception as e:302        logger.error(f"Error during identity validation: {str(e)}", exc_info=True)303        raise HTTPException(304            status_code=500,305            detail=f"Internal server error during validation: {str(e)}"306        )307 308    finally:309        # Clean up temporary files310        for temp_file in [temp_photo, temp_video]:311            if temp_file and os.path.exists(temp_file):312                try:313                    os.unlink(temp_file)314                    logger.debug(f"Cleaned up temporary file: {temp_file}")315                except Exception as e:316                    logger.warning(f"Failed to clean up temporary file {temp_file}: {e}")317 318 319@app.get("/health")320async def health_check():321    """322    Health check endpoint for the validation service.323 324    Returns325    -------326    dict327        Health status information328    """329    return {330        "status": "healthy",331        "service": "identity-validation",332        "version": "1.0.0",333        "timestamp": datetime.now(timezone.utc).isoformat(),334        "components": {335            "facial_validator": "initialized",336            "gesture_validator": "initialized"337        }338    }339 340 341@app.get("/")342async def root():343    """344    Root endpoint providing API information.345 346    Returns347    -------348    dict349        API information and usage instructions350    """351    return {352        "name": "Identity Validation API",353        "version": "1.0.0",354        "description": "Identity verification using facial recognition and gesture validation",355        "endpoints": {356            "POST /": "Perform identity validation",357            "GET /health": "Health check",358            "GET /": "API information"359        },360        "documentation": "/docs"361    }362