Spaces:
Running
Running
| """ | |
| Advanced Face Capture API Endpoints | |
| Integrating WebRTC capture with TimescaleDB and real-time processing | |
| """ | |
| from flask import Blueprint, request, jsonify, render_template | |
| import base64 | |
| import io | |
| import cv2 | |
| import numpy as np | |
| from PIL import Image | |
| import asyncio | |
| import json | |
| from datetime import datetime | |
| import logging | |
| from typing import Dict, Any, Optional | |
| from src.webrtc.advanced_face_capture import AdvancedFaceCapture, FaceQualityMetrics | |
| from src.telemetry import log_performance_metric | |
| from src.api_utils import handle_api_error, validate_request_data | |
| logger = logging.getLogger(__name__) | |
| # Create Blueprint for advanced face capture APIs | |
| advanced_face_bp = Blueprint('advanced_face', __name__, url_prefix='/api') | |
| # Global capture system instance | |
| capture_system: Optional[AdvancedFaceCapture] = None | |
| def get_capture_system(): | |
| """Get or create the advanced face capture system""" | |
| global capture_system | |
| if capture_system is None: | |
| config = { | |
| 'database': { | |
| 'host': 'localhost', | |
| 'port': 5432, | |
| 'database': 'morphguard', | |
| 'user': 'morphguard', | |
| 'password': 'morphguard123' | |
| }, | |
| 'models': { | |
| 'face_mesh_confidence': 0.7, | |
| 'face_detection_confidence': 0.7, | |
| 'pose_confidence': 0.7 | |
| }, | |
| 'quality': { | |
| 'sharpness_threshold': 0.6, | |
| 'illumination_threshold': 0.6, | |
| 'pose_angle_threshold': 25.0, | |
| 'eye_distance_min': 60, | |
| 'eye_distance_max': 120 | |
| } | |
| } | |
| capture_system = AdvancedFaceCapture(config) | |
| return capture_system | |
| def advanced_capture_page(): | |
| """Serve the advanced capture interface""" | |
| return render_template('advanced_capture.html') | |
| def store_face_metrics(): | |
| """Store real-time face quality metrics in TimescaleDB""" | |
| try: | |
| data = request.get_json() | |
| # Validate required fields | |
| required_fields = ['sessionId', 'frameNumber', 'timestamp', 'qualityMetrics'] | |
| missing_fields = [field for field in required_fields if field not in data] | |
| if missing_fields: | |
| return jsonify({ | |
| 'success': False, | |
| 'error': f'Missing required fields: {missing_fields}' | |
| }), 400 | |
| # Extract metrics data | |
| session_id = data['sessionId'] | |
| frame_number = data['frameNumber'] | |
| timestamp = datetime.fromisoformat(data['timestamp'].replace('Z', '+00:00')) | |
| quality_metrics = data['qualityMetrics'] | |
| pose_angles = data.get('poseAngles', {}) | |
| is_frontal = data.get('isFrontal', False) | |
| landmarks = data.get('landmarks', []) | |
| performance_stats = data.get('performanceStats', {}) | |
| # Get capture system and store metrics | |
| system = get_capture_system() | |
| # Create FaceQualityMetrics object | |
| metrics = FaceQualityMetrics( | |
| sharpness_score=quality_metrics.get('sharpnessScore', 0), | |
| illumination_score=quality_metrics.get('illuminationScore', 0), | |
| pose_score=quality_metrics.get('poseScore', 0), | |
| eye_distance_score=quality_metrics.get('eyeDistanceScore', 0), | |
| motion_blur_score=quality_metrics.get('motionBlurScore', 0), | |
| overall_score=quality_metrics.get('overallScore', 0), | |
| confidence=quality_metrics.get('confidence', 0), | |
| timestamp=timestamp, | |
| processing_time_ms=performance_stats.get('processingTime', 0) | |
| ) | |
| # Store metrics asynchronously | |
| loop = asyncio.new_event_loop() | |
| asyncio.set_event_loop(loop) | |
| try: | |
| loop.run_until_complete( | |
| system.store_metrics(session_id, metrics, landmarks, pose_angles, is_frontal) | |
| ) | |
| finally: | |
| loop.close() | |
| # Log performance metrics for monitoring | |
| log_performance_metric( | |
| 'face_capture_frame_processed', | |
| metrics.processing_time_ms, | |
| { | |
| 'session_id': session_id, | |
| 'quality_score': metrics.overall_score, | |
| 'is_frontal': is_frontal, | |
| 'fps': performance_stats.get('fps', 0) | |
| } | |
| ) | |
| return jsonify({ | |
| 'success': True, | |
| 'message': 'Metrics stored successfully', | |
| 'frame_number': frame_number | |
| }) | |
| except Exception as e: | |
| logger.error(f"Error storing face metrics: {e}") | |
| return jsonify({ | |
| 'success': False, | |
| 'error': str(e) | |
| }), 500 | |
| def process_captured_face(): | |
| """Process a captured face image with comprehensive analysis""" | |
| try: | |
| data = request.get_json() | |
| # Validate required fields | |
| if 'image' not in data: | |
| return jsonify({ | |
| 'success': False, | |
| 'error': 'No image data provided' | |
| }), 400 | |
| # Decode base64 image | |
| image_data = data['image'] | |
| if image_data.startswith('data:image'): | |
| image_data = image_data.split(',')[1] | |
| image_bytes = base64.b64decode(image_data) | |
| image = Image.open(io.BytesIO(image_bytes)) | |
| # Convert to OpenCV format | |
| cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) | |
| # Extract metadata | |
| session_id = data.get('sessionId', 'unknown') | |
| quality_metrics = data.get('qualityMetrics', {}) | |
| # Get capture system | |
| system = get_capture_system() | |
| # Process the image | |
| loop = asyncio.new_event_loop() | |
| asyncio.set_event_loop(loop) | |
| try: | |
| result = loop.run_until_complete( | |
| system.process_frame(cv_image, session_id) | |
| ) | |
| finally: | |
| loop.close() | |
| if result is None: | |
| return jsonify({ | |
| 'success': False, | |
| 'error': 'No face detected in the captured image' | |
| }), 400 | |
| # Prepare response with comprehensive analysis | |
| response_data = { | |
| 'success': True, | |
| 'message': 'Face processed successfully', | |
| 'analysis': { | |
| 'quality_metrics': { | |
| 'overall_score': result.quality_metrics.overall_score, | |
| 'sharpness_score': result.quality_metrics.sharpness_score, | |
| 'illumination_score': result.quality_metrics.illumination_score, | |
| 'pose_score': result.quality_metrics.pose_score, | |
| 'eye_distance_score': result.quality_metrics.eye_distance_score, | |
| 'motion_blur_score': result.quality_metrics.motion_blur_score, | |
| 'confidence': result.quality_metrics.confidence | |
| }, | |
| 'pose_angles': result.pose_angles, | |
| 'is_frontal': result.is_frontal, | |
| 'face_box': result.face_box, | |
| 'landmark_count': len(result.landmarks), | |
| 'processing_time_ms': result.quality_metrics.processing_time_ms | |
| }, | |
| 'session_id': session_id, | |
| 'timestamp': result.quality_metrics.timestamp.isoformat() | |
| } | |
| # Store the processed result | |
| await_store_processed_result(result, session_id) | |
| # Log successful processing | |
| log_performance_metric( | |
| 'face_image_processed', | |
| result.quality_metrics.processing_time_ms, | |
| { | |
| 'session_id': session_id, | |
| 'quality_score': result.quality_metrics.overall_score, | |
| 'is_frontal': result.is_frontal, | |
| 'success': True | |
| } | |
| ) | |
| return jsonify(response_data) | |
| except Exception as e: | |
| logger.error(f"Error processing captured face: {e}") | |
| return jsonify({ | |
| 'success': False, | |
| 'error': str(e) | |
| }), 500 | |
| def await_store_processed_result(result, session_id): | |
| """Store processed face result in database""" | |
| try: | |
| system = get_capture_system() | |
| if system.db_conn: | |
| with system.db_conn.cursor() as cursor: | |
| cursor.execute(""" | |
| INSERT INTO processed_face_captures ( | |
| timestamp, session_id, quality_score, pose_angles, | |
| is_frontal, face_box, landmark_count, processing_time_ms | |
| ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) | |
| """, ( | |
| result.quality_metrics.timestamp, | |
| session_id, | |
| result.quality_metrics.overall_score, | |
| json.dumps(result.pose_angles), | |
| result.is_frontal, | |
| json.dumps(result.face_box), | |
| len(result.landmarks), | |
| result.quality_metrics.processing_time_ms | |
| )) | |
| system.db_conn.commit() | |
| except Exception as e: | |
| logger.error(f"Failed to store processed result: {e}") | |
| def get_session_metrics(session_id): | |
| """Get comprehensive metrics for a capture session""" | |
| try: | |
| system = get_capture_system() | |
| if not system.db_conn: | |
| return jsonify({ | |
| 'success': False, | |
| 'error': 'Database connection not available' | |
| }), 500 | |
| with system.db_conn.cursor() as cursor: | |
| # Get session summary | |
| cursor.execute(""" | |
| SELECT | |
| COUNT(*) as total_frames, | |
| AVG(overall_score) as avg_quality, | |
| MAX(overall_score) as max_quality, | |
| MIN(overall_score) as min_quality, | |
| AVG(processing_time_ms) as avg_processing_time, | |
| COUNT(CASE WHEN is_frontal THEN 1 END) as frontal_frames | |
| FROM face_quality_metrics | |
| WHERE session_id = %s | |
| """, (session_id,)) | |
| summary = cursor.fetchone() | |
| # Get quality timeline | |
| cursor.execute(""" | |
| SELECT timestamp, overall_score, is_frontal, processing_time_ms | |
| FROM face_quality_metrics | |
| WHERE session_id = %s | |
| ORDER BY timestamp | |
| LIMIT 100 | |
| """, (session_id,)) | |
| timeline = cursor.fetchall() | |
| if summary is None: | |
| return jsonify({ | |
| 'success': False, | |
| 'error': 'Session not found' | |
| }), 404 | |
| response_data = { | |
| 'success': True, | |
| 'session_id': session_id, | |
| 'summary': { | |
| 'total_frames': summary[0] or 0, | |
| 'avg_quality': float(summary[1] or 0), | |
| 'max_quality': float(summary[2] or 0), | |
| 'min_quality': float(summary[3] or 0), | |
| 'avg_processing_time': float(summary[4] or 0), | |
| 'frontal_frames': summary[5] or 0, | |
| 'frontal_percentage': (summary[5] or 0) / max(summary[0] or 1, 1) * 100 | |
| }, | |
| 'timeline': [ | |
| { | |
| 'timestamp': row[0].isoformat(), | |
| 'quality_score': float(row[1]), | |
| 'is_frontal': row[2], | |
| 'processing_time_ms': float(row[3]) | |
| } | |
| for row in timeline | |
| ] | |
| } | |
| return jsonify(response_data) | |
| except Exception as e: | |
| logger.error(f"Error getting session metrics: {e}") | |
| return jsonify({ | |
| 'success': False, | |
| 'error': str(e) | |
| }), 500 | |
| def get_performance_stats(): | |
| """Get real-time performance statistics""" | |
| try: | |
| system = get_capture_system() | |
| stats = system.get_performance_stats() | |
| return jsonify({ | |
| 'success': True, | |
| 'performance_stats': stats, | |
| 'timestamp': datetime.now().isoformat() | |
| }) | |
| except Exception as e: | |
| logger.error(f"Error getting performance stats: {e}") | |
| return jsonify({ | |
| 'success': False, | |
| 'error': str(e) | |
| }), 500 | |
| def get_system_health(): | |
| """Get comprehensive system health status""" | |
| try: | |
| system = get_capture_system() | |
| # Check database connection | |
| db_status = 'connected' if system.db_conn else 'disconnected' | |
| # Check models status | |
| models_status = { | |
| 'face_mesh': hasattr(system, 'face_mesh') and system.face_mesh is not None, | |
| 'face_detection': hasattr(system, 'face_detection') and system.face_detection is not None, | |
| 'pose_detection': hasattr(system, 'pose') and system.pose is not None, | |
| 'dlib_predictor': system.dlib_predictor is not None | |
| } | |
| # Get recent performance metrics | |
| performance = system.get_performance_stats() | |
| # Calculate health score | |
| health_factors = [ | |
| db_status == 'connected', | |
| all(models_status.values()), | |
| performance.get('avg_processing_time_ms', 0) < 200, # Processing under 200ms | |
| performance.get('fps', 0) > 10 # FPS above 10 | |
| ] | |
| health_score = sum(health_factors) / len(health_factors) | |
| response_data = { | |
| 'success': True, | |
| 'health_score': health_score, | |
| 'status': 'healthy' if health_score > 0.75 else 'degraded' if health_score > 0.5 else 'unhealthy', | |
| 'components': { | |
| 'database': { | |
| 'status': db_status, | |
| 'healthy': db_status == 'connected' | |
| }, | |
| 'models': { | |
| 'status': models_status, | |
| 'healthy': all(models_status.values()) | |
| }, | |
| 'performance': { | |
| 'status': performance, | |
| 'healthy': performance.get('avg_processing_time_ms', 0) < 200 | |
| } | |
| }, | |
| 'timestamp': datetime.now().isoformat() | |
| } | |
| return jsonify(response_data) | |
| except Exception as e: | |
| logger.error(f"Error getting system health: {e}") | |
| return jsonify({ | |
| 'success': False, | |
| 'error': str(e) | |
| }), 500 | |
| def optimize_settings(): | |
| """AI-powered settings optimization based on recent performance""" | |
| try: | |
| data = request.get_json() | |
| session_id = data.get('sessionId') | |
| if not session_id: | |
| return jsonify({ | |
| 'success': False, | |
| 'error': 'Session ID required' | |
| }), 400 | |
| system = get_capture_system() | |
| # Get recent capture results for this session | |
| # This would typically analyze recent performance and suggest optimizations | |
| recommendations = { | |
| 'quality_threshold': 0.7, | |
| 'processing_interval': 100, | |
| 'camera_settings': { | |
| 'brightness': 'auto', | |
| 'contrast': 'auto', | |
| 'focus': 'continuous' | |
| }, | |
| 'suggestions': [ | |
| 'Maintain current lighting conditions', | |
| 'Keep face within optimal distance range', | |
| 'Ensure stable device positioning' | |
| ] | |
| } | |
| return jsonify({ | |
| 'success': True, | |
| 'recommendations': recommendations, | |
| 'session_id': session_id, | |
| 'timestamp': datetime.now().isoformat() | |
| }) | |
| except Exception as e: | |
| logger.error(f"Error optimizing settings: {e}") | |
| return jsonify({ | |
| 'success': False, | |
| 'error': str(e) | |
| }), 500 | |
| # Error handlers | |
| def not_found_error(error): | |
| return jsonify({ | |
| 'success': False, | |
| 'error': 'Endpoint not found' | |
| }), 404 | |
| def internal_error(error): | |
| return jsonify({ | |
| 'success': False, | |
| 'error': 'Internal server error' | |
| }), 500 | |
| # Cleanup function | |
| def cleanup_capture_system(): | |
| """Cleanup capture system resources""" | |
| global capture_system | |
| if capture_system: | |
| capture_system.cleanup() | |
| capture_system = None |