Spaces:
Runtime error
Runtime error
| import asyncio | |
| from fastapi import FastAPI, UploadFile, File, HTTPException | |
| from ocr.gemini_service import parse_pan | |
| from ocr.gemini_aadhar_service import parse_aadhar | |
| from ocr.gemini_face_extractor import detect_face | |
| from utils.crop import crop_face | |
| from utils.pdf import pdf_to_image_bytes | |
| app = FastAPI() | |
| SUPPORTED_TYPES = {"image/jpeg", "image/png", "image/webp", "application/pdf"} | |
| def _resolve_image(raw_bytes: bytes, content_type: str) -> bytes: | |
| if content_type == "application/pdf": | |
| return pdf_to_image_bytes(raw_bytes) | |
| return raw_bytes | |
| async def process_pan(file: UploadFile = File(...)): | |
| if file.content_type not in SUPPORTED_TYPES: | |
| raise HTTPException( | |
| status_code=415, | |
| detail=f"Unsupported file type '{file.content_type}'. Upload a JPEG, PNG, WebP, or PDF.", | |
| ) | |
| raw_bytes = await file.read() | |
| try: | |
| image_bytes = _resolve_image(raw_bytes, file.content_type) | |
| except ValueError as e: | |
| raise HTTPException(status_code=422, detail=str(e)) | |
| try: | |
| pan_result, face_result = await asyncio.gather( | |
| asyncio.to_thread(parse_pan, image_bytes), | |
| asyncio.to_thread(detect_face, image_bytes), | |
| ) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| face_bbox = face_result.get("box_2d") | |
| if face_bbox is None: | |
| raise HTTPException(status_code=422, detail="Could not detect face on the PAN card.") | |
| face_image = crop_face(image_bytes, face_bbox) | |
| return { | |
| "success": True, | |
| "parsed_data": pan_result.get("data"), | |
| "face_image": face_image, | |
| "engine_used": pan_result.get("engine_used"), | |
| } | |
| async def process_aadhar(file: UploadFile = File(...)): | |
| if file.content_type not in SUPPORTED_TYPES: | |
| raise HTTPException( | |
| status_code=415, | |
| detail=f"Unsupported file type '{file.content_type}'. Upload a JPEG, PNG, WebP, or PDF.", | |
| ) | |
| raw_bytes = await file.read() | |
| try: | |
| image_bytes = _resolve_image(raw_bytes, file.content_type) | |
| except ValueError as e: | |
| raise HTTPException(status_code=422, detail=str(e)) | |
| # Face detection is best-effort — back side of Aadhaar has no photo. | |
| # return_exceptions=True prevents one failure from cancelling the other. | |
| ocr_result, face_result = await asyncio.gather( | |
| asyncio.to_thread(parse_aadhar, image_bytes), | |
| asyncio.to_thread(detect_face, image_bytes), | |
| return_exceptions=True, | |
| ) | |
| if isinstance(ocr_result, Exception): | |
| raise HTTPException(status_code=500, detail=str(ocr_result)) | |
| data = ocr_result.get("data", {}) | |
| address_found = data.get("address_found", False) | |
| # Attempt to crop face; silently skip if detection failed (e.g. back side) | |
| face_image = None | |
| if not isinstance(face_result, Exception): | |
| face_bbox = face_result.get("box_2d") | |
| if face_bbox: | |
| try: | |
| face_image = crop_face(image_bytes, face_bbox) | |
| except Exception: | |
| pass | |
| return { | |
| "success": True, | |
| "parsed_data": data, | |
| "face_image": face_image, | |
| "face_found": face_image is not None, | |
| "address_found": address_found, | |
| "engine_used": ocr_result.get("engine_used"), | |
| } | |