Spaces:
Sleeping
Sleeping
| """ | |
| 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 | |
| async def root(): | |
| """Root endpoint for health checks""" | |
| return {"status": "running", "service": "style-analysis"} | |
| async def health(): | |
| """Health check endpoint""" | |
| return { | |
| "status": "healthy", | |
| "analyzer_available": analyzer_available | |
| } | |
| 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)}" | |
| ) | |
| 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)}" | |
| ) | |
| 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) | |