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) @app.post("/recommend") 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)) @app.get("/health") 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)