""" FastAPI Server for Style Analysis This server provides REST API endpoints for AI-powered style analysis using Google's Gemini AI. It analyzes user-uploaded images for: - Body type and features - Body alignment and posture - Skin tone and undertones - Face shape - Personalized style recommendations """ import os import io import base64 from datetime import datetime from pathlib import Path from typing import Optional from fastapi import FastAPI, File, UploadFile, HTTPException, Form from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from PIL import Image from dotenv import load_dotenv from analyzer import StyleAnalyzer # Load environment variables env_path = Path(__file__).parent.parent / "opentryon" / ".env" load_dotenv(env_path) # Create output directory for saving analyzed images (optional) OUTPUT_DIR = Path("outputs/style_analysis") OUTPUT_DIR.mkdir(parents=True, exist_ok=True) app = FastAPI( title="AI Style Analysis API", description="AI-powered style analysis using Google Gemini for body type, skin tone, and fashion recommendations", version="1.0.0" ) # CORS middleware to allow requests from frontend app.add_middleware( CORSMiddleware, allow_origins=[ "http://localhost:3000", "http://127.0.0.1:3000", "http://localhost:5173", "http://127.0.0.1:5173", "https://style-ai-virutal-stylish-and-trends.vercel.app" ], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Initialize analyzer try: analyzer = StyleAnalyzer() analyzer_available = True except Exception as e: print(f"Warning: StyleAnalyzer initialization failed: {e}") analyzer_available = False @app.get("/") async def root(): """Root endpoint for health checks""" return {"status": "running", "service": "style-analysis"} @app.get("/health") async def health(): """Health check endpoint""" return { "status": "healthy", "analyzer_available": analyzer_available } @app.post("/api/v1/analyze") async def analyze_style( image: UploadFile = File(..., description="User photo for style analysis"), save_image: bool = Form(default=False, description="Save uploaded image to disk"), detailed: bool = Form(default=True, description="Provide detailed analysis") ): """ Comprehensive style analysis of user image. Analyzes: - Body type (rectangle, triangle, inverted triangle, hourglass, oval) - Body alignment and posture - Skin tone (fair, light, medium, olive, tan, brown, deep) - Skin undertones (cool, warm, neutral) - Face shape (oval, round, square, heart, diamond, oblong) - Personalized style recommendations - Color palette suggestions Args: image: User photo file save_image: Whether to save the uploaded image detailed: Whether to provide detailed analysis and recommendations Returns: JSON response with comprehensive style analysis """ if not analyzer_available: raise HTTPException( status_code=503, detail="Style analyzer is not available. Check Gemini API configuration." ) try: # Read image image_bytes = await image.read() pil_image = Image.open(io.BytesIO(image_bytes)) print(f"[DEBUG] Image loaded: {pil_image.size}, mode: {pil_image.mode}") # Optionally save image saved_path = None if save_image: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f") filename = f"analysis_{timestamp}.png" filepath = OUTPUT_DIR / filename # Save in RGB mode if pil_image.mode != 'RGB': pil_image = pil_image.convert('RGB') pil_image.save(str(filepath), 'PNG') saved_path = str(filepath) print("[DEBUG] Starting analysis...") # Perform analysis result = analyzer.analyze_image(pil_image, detailed=detailed) print(f"[DEBUG] Analysis result: success={result.get('success')}") print(f"[DEBUG] Result keys: {list(result.keys())}") if not result.get('success', False): print(f"[ERROR] Analysis failed: {result.get('message', 'Unknown error')}") raise HTTPException( status_code=500, detail=result.get('message', 'Analysis failed') ) # Build response response_data = { "success": True, "analysis": { "body_type": result.get('body_type', ''), "body_alignment": result.get('body_alignment', ''), "skin_tone": result.get('skin_tone', ''), "skin_undertone": result.get('skin_undertone', ''), "face_shape": result.get('face_shape', ''), "height_estimate": result.get('height_estimate', ''), "style_recommendations": result.get('style_recommendations', []), "outfit_suggestions": result.get('outfit_suggestions', []), "color_palette": result.get('color_palette', {}), "detailed_analysis": result.get('detailed_analysis', '') }, "raw_analysis": result.get('raw_analysis', ''), "image_info": { "width": pil_image.width, "height": pil_image.height, "mode": pil_image.mode, "format": pil_image.format or "Unknown" } } if saved_path: response_data["saved_path"] = saved_path return JSONResponse(response_data) except HTTPException: raise except Exception as e: import traceback error_details = f"Error analyzing image: {str(e)}\n{traceback.format_exc()}" print(error_details) raise HTTPException( status_code=500, detail=f"Error analyzing image: {str(e)}" ) @app.post("/api/v1/analyze/tryon") async def analyze_for_tryon( image: UploadFile = File(..., description="User photo for virtual try-on analysis"), save_image: bool = Form(default=False, description="Save uploaded image to disk") ): """ Analyze image for virtual try-on readiness. Provides: - Image quality assessment - Pose quality for try-on - Body type for garment fitting - Garment fit recommendations - Try-on readiness score (1-10) - Improvement suggestions Args: image: User photo file save_image: Whether to save the uploaded image Returns: JSON response with virtual try-on analysis """ if not analyzer_available: raise HTTPException( status_code=503, detail="Style analyzer is not available. Check Gemini API configuration." ) try: # Read image image_bytes = await image.read() pil_image = Image.open(io.BytesIO(image_bytes)) # Optionally save image saved_path = None if save_image: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f") filename = f"tryon_analysis_{timestamp}.png" filepath = OUTPUT_DIR / filename if pil_image.mode != 'RGB': pil_image = pil_image.convert('RGB') pil_image.save(str(filepath), 'PNG') saved_path = str(filepath) # Perform virtual try-on analysis result = analyzer.analyze_for_virtual_tryon(pil_image) if not result.get('success', False): raise HTTPException( status_code=500, detail=result.get('message', 'Analysis failed') ) # Build response response_data = { "success": True, "tryon_analysis": result.get('analysis', ''), "raw_response": result.get('raw_response', ''), "image_dimensions": result.get('image_dimensions', {}) } if saved_path: response_data["saved_path"] = saved_path return JSONResponse(response_data) except HTTPException: raise except Exception as e: import traceback error_details = f"Error analyzing image: {str(e)}\n{traceback.format_exc()}" print(error_details) raise HTTPException( status_code=500, detail=f"Error analyzing image: {str(e)}" ) @app.post("/api/v1/analyze/quick") async def quick_analyze( image: UploadFile = File(..., description="User photo for quick analysis") ): """ Quick style analysis (less detailed, faster response). Provides: - Body type - Posture/alignment - Skin tone with undertone - Face shape - Quick style suggestions Args: image: User photo file Returns: JSON response with quick style analysis """ if not analyzer_available: raise HTTPException( status_code=503, detail="Style analyzer is not available. Check Gemini API configuration." ) try: # Read image image_bytes = await image.read() pil_image = Image.open(io.BytesIO(image_bytes)) # Perform quick analysis (detailed=False) result = analyzer.analyze_image(pil_image, detailed=False) if not result.get('success', False): raise HTTPException( status_code=500, detail=result.get('message', 'Analysis failed') ) # Build response response_data = { "success": True, "quick_analysis": result.get('raw_analysis', ''), "image_info": { "width": pil_image.width, "height": pil_image.height } } return JSONResponse(response_data) except HTTPException: raise except Exception as e: import traceback error_details = f"Error analyzing image: {str(e)}\n{traceback.format_exc()}" print(error_details) raise HTTPException( status_code=500, detail=f"Error analyzing image: {str(e)}" ) if __name__ == "__main__": uvicorn.run("api_server:app", host="0.0.0.0", port=7860, reload=True)