File size: 11,873 Bytes
95db528
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
#!/usr/bin/env python3
"""
Main entry point for the unified gesture detection and identity validation API.
Provides a flat API structure with all endpoints at the root level.
"""
import uvicorn
import os
import sys
import tempfile
import time
import json
import logging
from typing import Optional
from datetime import datetime, timezone

# Add the project root to Python path
project_root = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, project_root)

from fastapi import FastAPI, UploadFile, File, Form, HTTPException, Depends
from fastapi.responses import ORJSONResponse

# Import gesture detection functionality
from src.gesturedetection.api import process_video_for_gestures
from src.gesturedetection.models import GestureResponse

# Import validation functionality
from src.validate.models import ValidationRequest, ValidationResponse, ValidationStatus
from src.validate.facial_validator import FacialValidator
from src.validate.gesture_validator import GestureValidator
from src.validate.api import get_validation_request
from src.validate.config import config

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# Create main FastAPI application
app = FastAPI(
    title="Gesture Detection & Identity Validation API",
    description="Unified API for gesture detection and identity validation services",
    version="1.0.0",
    docs_url="/docs",
    redoc_url="/redoc",
    default_response_class=ORJSONResponse
)

# Initialize validators for validation endpoint
facial_validator = FacialValidator()
gesture_validator = GestureValidator()


@app.get("/")
async def root():
    """
    Root endpoint providing API information.
    
    Returns
    -------
    dict
        API information and available endpoints
    """
    return {
        "name": "Gesture Detection & Identity Validation API",
        "version": "1.0.0",
        "description": "Unified API providing gesture detection and identity validation services",
        "endpoints": {
            "GET /": "API information",
            "GET /health": "Health check",
            "POST /validate": "Validate identity using facial recognition and gestures",
            "POST /gestures": "Detect gestures in video",
            "GET /docs": "Interactive API documentation"
        }
    }


@app.get("/health")
async def health():
    """
    Health check endpoint for the unified API.
    
    Returns
    -------
    dict
        Health status of all service components
    """
    return {
        "status": "healthy",
        "service": "unified-api",
        "version": "1.0.0",
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "components": {
            "gesture_detection": "available",
            "identity_validation": "available",
            "facial_validator": "initialized",
            "gesture_validator": "initialized"
        }
    }


@app.post("/gestures", response_model=GestureResponse)
async def detect_gestures(video: UploadFile = File(...), frame_skip: int = Form(1)):
    """
    Detect gestures in an uploaded video file.
    
    Parameters
    ----------
    video : UploadFile
        The video file to process
    frame_skip : int
        Number of frames to skip between processing (1 = process every frame, 3 = process every 3rd frame)
        
    Returns
    -------
    GestureResponse
        Response containing detected gestures with duration and confidence
    """
    logger.info(f"Gesture detection request received: {video.filename}")
    
    # Validate file type
    if not video.content_type or not video.content_type.startswith('video/'):
        raise HTTPException(status_code=400, detail="File must be a video")
    
    # Create temporary file to save uploaded video
    with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as temp_file:
        try:
            # Write uploaded content to temporary file
            content = await video.read()
            temp_file.write(content)
            temp_file.flush()
            
            logger.info(f"Processing video: {temp_file.name} ({len(content)} bytes)")
            
            # Process the video with frame skip parameter
            gestures = process_video_for_gestures(temp_file.name, frame_skip=frame_skip)
            
            logger.info(f"Gesture detection completed: {len(gestures)} gestures detected")
            
            return GestureResponse(gestures=gestures)
            
        except Exception as e:
            logger.error(f"Error processing video: {str(e)}", exc_info=True)
            raise HTTPException(status_code=500, detail=f"Error processing video: {str(e)}")
        
        finally:
            # Clean up temporary file
            if os.path.exists(temp_file.name):
                os.unlink(temp_file.name)
                logger.debug(f"Cleaned up temporary file: {temp_file.name}")


@app.post("/validate", response_model=ValidationResponse)
async def validate_identity(
    photo: UploadFile = File(...),
    video: UploadFile = File(...),
    request: ValidationRequest = Depends(get_validation_request)
):
    """
    Validate user identity using facial recognition and gesture validation.

    This endpoint accepts an ID document photo, a user video containing
    the person's face and required gestures, and a list of gestures that
    must be performed. It returns validation results for both facial
    recognition and gesture compliance.

    Parameters
    ----------
    photo : UploadFile
        ID document photo file (image format)
    video : UploadFile
        User video file containing face and gestures (video format)
    request : ValidationRequest
        Validation configuration and gesture requirements

    Returns
    -------
    ValidationResponse
        Validation results with success indicators and optional details

    Raises
    ------
    HTTPException
        If validation fails or processing errors occur
    """
    start_time = time.time()
    logger.info(f"Identity validation request received for {request.asked_gestures}")

    # Validate file types
    if not photo.content_type or not photo.content_type.startswith(('image/', 'application/')):
        raise HTTPException(
            status_code=400,
            detail="Photo file must be an image"
        )

    if not video.content_type or not video.content_type.startswith('video/'):
        raise HTTPException(
            status_code=400,
            detail="Video file must be a video"
        )

    # Validate file sizes (basic check)
    MAX_FILE_SIZE = 100 * 1024 * 1024  # 100MB
    if photo.size and photo.size > MAX_FILE_SIZE:
        raise HTTPException(
            status_code=413,
            detail="Photo file too large (max 100MB)"
        )

    if video.size and video.size > MAX_FILE_SIZE:
        raise HTTPException(
            status_code=413,
            detail="Video file too large (max 100MB)"
        )

    # Create temporary files for processing
    temp_photo = None
    temp_video = None

    try:
        # Save uploaded files to temporary location
        with tempfile.NamedTemporaryFile(delete=False, suffix=f"_photo.{photo.filename.split('.')[-1] if '.' in photo.filename else 'jpg'}") as temp_photo_file:
            temp_photo = temp_photo_file.name
            photo_content = await photo.read()
            temp_photo_file.write(photo_content)

        with tempfile.NamedTemporaryFile(delete=False, suffix=f"_video.{video.filename.split('.')[-1] if '.' in video.filename else 'mp4'}") as temp_video_file:
            temp_video = temp_video_file.name
            video_content = await video.read()
            temp_video_file.write(video_content)

        logger.info(f"Files saved: photo={temp_photo}, video={temp_video}")

        # Perform facial validation
        logger.info("Starting facial validation")

        # Update facial validator with request-specific parameters if provided
        if request.similarity_threshold is not None:
            facial_validator.similarity_threshold = request.similarity_threshold
        if request.frame_sample_rate is not None:
            facial_validator.frame_sample_rate = request.frame_sample_rate

        face_result = facial_validator.validate_facial_match(temp_photo, temp_video)

        # Perform gesture validation
        logger.info("Starting gesture validation")
        
        # Update gesture validator with request-specific parameters if provided
        if request.confidence_threshold is not None:
            gesture_validator.confidence_threshold = request.confidence_threshold
        if request.min_gesture_duration is not None:
            gesture_validator.min_gesture_duration = request.min_gesture_duration
            
        gesture_result = gesture_validator.validate_gestures(
            temp_video,
            request.asked_gestures,
            error_margin=request.error_margin,
            require_all=request.require_all_gestures
        )

        # Determine overall result
        overall_success = face_result.success and gesture_result.success
        overall_status = ValidationStatus.SUCCESS if overall_success else ValidationStatus.PARTIAL

        # Calculate processing time
        processing_time_ms = int((time.time() - start_time) * 1000)

        # Build response
        response = ValidationResponse(
            face=face_result.success,
            gestures=gesture_result.success,
            overall=overall_success,
            status=overall_status,
            face_result=face_result if request.include_details else None,
            gesture_result=gesture_result if request.include_details else None,
            processing_time_ms=processing_time_ms,
            timestamp=datetime.now(timezone.utc).isoformat()
        )

        # Log results
        logger.info(
            "Identity validation completed",
            extra={
                "face_success": face_result.success,
                "gesture_success": gesture_result.success,
                "overall_success": overall_success,
                "processing_time_ms": processing_time_ms,
                "requested_gestures": request.asked_gestures
            }
        )

        return response

    except Exception as e:
        logger.error(f"Error during identity validation: {str(e)}", exc_info=True)
        raise HTTPException(
            status_code=500,
            detail=f"Internal server error during validation: {str(e)}"
        )

    finally:
        # Clean up temporary files
        for temp_file in [temp_photo, temp_video]:
            if temp_file and os.path.exists(temp_file):
                try:
                    os.unlink(temp_file)
                    logger.debug(f"Cleaned up temporary file: {temp_file}")
                except Exception as e:
                    logger.warning(f"Failed to clean up temporary file {temp_file}: {e}")


def main():
    """Start the unified API server."""
    # Get port from environment variable, default to 7860 for HF Spaces compatibility
    port = int(os.getenv("PORT", 7860))

    print("πŸš€ Starting Unified Gesture Detection & Identity Validation API")
    print(f"πŸ“ API will be available at: http://localhost:{port}")
    print(f"πŸ“š API documentation at: http://localhost:{port}/docs")
    print(f"❀️  Health check at: http://localhost:{port}/health")
    print(f"πŸ” Identity validation at: POST http://localhost:{port}/validate")
    print(f"πŸ‘‹ Gesture detection at: POST http://localhost:{port}/gestures")
    print("\nPress Ctrl+C to stop the server")

    uvicorn.run(
        app,
        host="0.0.0.0",
        port=port,
        reload=False,  # Disable reload in production/Docker
        log_level="info"
    )


if __name__ == "__main__":
    main()