Spaces:
Sleeping
Sleeping
| from fastapi import FastAPI, UploadFile, File, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import JSONResponse | |
| import uvicorn | |
| import shutil | |
| import os | |
| import uuid | |
| from pathlib import Path | |
| import logging | |
| from detect import detect_face_shape | |
| from recommend import get_recommendations | |
| # Configure logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| app = FastAPI(title="AI Frame Recommendation Service") | |
| # Configure CORS | |
| # For Hugging Face Spaces, we allow all origins or you can add specific patterns later | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| UPLOAD_DIR = Path("uploads") | |
| UPLOAD_DIR.mkdir(exist_ok=True) | |
| async def recommend(file: UploadFile = File(...), source: str = "upload", consent: bool = False): | |
| try: | |
| # Save uploaded file temporarily | |
| file_ext = file.filename.split(".")[-1] if file.filename else "jpg" | |
| filename = f"{uuid.uuid4()}.{file_ext}" | |
| file_path = UPLOAD_DIR / filename | |
| with open(file_path, "wb") as buffer: | |
| shutil.copyfileobj(file.file, buffer) | |
| logger.info(f"Processing image: {file_path}") | |
| # Analyze face shape | |
| try: | |
| # Returns dict: {'Oval': 0.6, 'Square': 0.4, ...} | |
| face_shape_probs = detect_face_shape(str(file_path)) | |
| # Get top result (keys are sorted descending in detect.py) | |
| top_shape = list(face_shape_probs.keys())[0] | |
| top_confidence = face_shape_probs[top_shape] | |
| logger.info(f"Detected face shape: {top_shape} ({top_confidence})") | |
| except Exception as e: | |
| logger.error(f"Error analyzing face: {str(e)}") | |
| # Clean up file if analysis fails | |
| if not consent: | |
| try: | |
| os.remove(file_path) | |
| except: pass | |
| raise HTTPException(status_code=400, detail=f"Could not analyze face: {str(e)}") | |
| # Get recommendations using top shape | |
| recommendations = get_recommendations(top_shape) | |
| # Format response | |
| response = { | |
| "face_shape": top_shape, | |
| "confidence": top_confidence, | |
| "shape_confidence": top_confidence, | |
| "shape_probabilities": face_shape_probs, | |
| "frame_types": [ | |
| {"label": rec, "confidence": 0.9} for rec in recommendations.get("glasses", []) | |
| ], | |
| "frame_size": {"label": "Medium", "confidence": 0.8}, # Placeholder | |
| "bridge_type": {"label": "Regular", "confidence": 0.8}, # Placeholder | |
| "materials": [{"label": "Acetate", "confidence": 0.7}], # Placeholder | |
| "measurements": { | |
| "face_width": 140, # Placeholder | |
| "temple_length": 145 # Placeholder | |
| }, | |
| "ml_predicted_frame": recommendations.get("glasses", [])[0] if recommendations.get("glasses") else None, | |
| "ml_confidence": 0.85, | |
| "image_id": str(filename) if consent else None | |
| } | |
| # Clean up if no consent to save | |
| if not consent: | |
| os.remove(file_path) | |
| return JSONResponse(content=response, headers={"Cache-Control": "no-store"}) | |
| except Exception as e: | |
| logger.error(f"Server error: {str(e)}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| def health_check(): | |
| return {"status": "ok"} | |
| if __name__ == "__main__": | |
| # Use PORT environment variable if available (for Railway/Spaces) | |
| port = int(os.environ.get("PORT", 7860)) | |
| uvicorn.run("app:app", host="0.0.0.0", port=port, reload=True) | |