Spaces:
Sleeping
Sleeping
| """ | |
| AI-Powered Style Analyzer using Google Gemini | |
| This module analyzes user-uploaded images for: | |
| - Body type and features | |
| - Body alignment and posture | |
| - Skin tone and undertones | |
| - Face shape | |
| - Style recommendations based on analysis | |
| Uses Google's Gemini AI for comprehensive image analysis. | |
| """ | |
| import os | |
| import io | |
| import base64 | |
| from typing import Dict, Any, Optional, Union | |
| from pathlib import Path | |
| from PIL import Image | |
| from dotenv import load_dotenv | |
| import traceback | |
| try: | |
| from google import genai | |
| from google.genai import types | |
| GOOGLE_GENAI_AVAILABLE = True | |
| except ImportError: | |
| GOOGLE_GENAI_AVAILABLE = False | |
| genai = None | |
| types = None | |
| class StyleAnalyzer: | |
| """ | |
| AI-powered style analyzer using Google Gemini. | |
| Analyzes user photos to determine: | |
| - 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 | |
| Example: | |
| >>> analyzer = StyleAnalyzer() | |
| >>> result = analyzer.analyze_image("user_photo.jpg") | |
| >>> print(result['body_type']) | |
| 'hourglass' | |
| >>> print(result['skin_tone']) | |
| 'medium with warm undertones' | |
| """ | |
| def __init__(self, api_key: Optional[str] = None): | |
| """ | |
| Initialize the StyleAnalyzer. | |
| Args: | |
| api_key: Gemini API key. If None, reads from GEMINI_API_KEY environment variable. | |
| Raises: | |
| ImportError: If google-genai package is not installed | |
| ValueError: If API key is not provided or found in environment | |
| """ | |
| if not GOOGLE_GENAI_AVAILABLE: | |
| raise ImportError( | |
| "google-genai package is required. Install it with: pip install google-genai" | |
| ) | |
| # Load environment variables | |
| load_dotenv() | |
| # Get API key | |
| self.api_key = api_key or os.getenv("GEMINI_API_KEY") | |
| if not self.api_key: | |
| raise ValueError( | |
| "Gemini API key is required. Provide it via constructor parameter " | |
| "or set GEMINI_API_KEY environment variable." | |
| ) | |
| # Configure Gemini client | |
| self.client = genai.Client(api_key=self.api_key) | |
| # Use Gemini 2.5 Flash for analysis (fast and efficient) | |
| self.model_name = "gemini-2.5-flash" | |
| def _load_image(self, image: Union[str, Path, Image.Image, bytes]) -> Image.Image: | |
| """ | |
| Load image from various input formats. | |
| Args: | |
| image: Image as file path, PIL Image, or bytes | |
| Returns: | |
| PIL Image object | |
| """ | |
| if isinstance(image, (str, Path)): | |
| return Image.open(image) | |
| elif isinstance(image, Image.Image): | |
| return image | |
| elif isinstance(image, bytes): | |
| return Image.open(io.BytesIO(image)) | |
| else: | |
| raise ValueError(f"Unsupported image type: {type(image)}") | |
| def _image_to_base64(self, image: Image.Image) -> str: | |
| """ | |
| Convert PIL Image to base64 string. | |
| Args: | |
| image: PIL Image object | |
| Returns: | |
| Base64-encoded image string | |
| """ | |
| buffer = io.BytesIO() | |
| image.save(buffer, format="PNG") | |
| buffer.seek(0) | |
| return base64.b64encode(buffer.read()).decode('utf-8') | |
| def analyze_image( | |
| self, | |
| image: Union[str, Path, Image.Image, bytes], | |
| detailed: bool = True | |
| ) -> Dict[str, Any]: | |
| """ | |
| Analyze user image for style characteristics. | |
| Args: | |
| image: User photo (file path, PIL Image, or bytes) | |
| detailed: If True, provides detailed analysis and recommendations | |
| Returns: | |
| Dictionary containing: | |
| - body_type: Body shape classification | |
| - body_alignment: Posture and alignment assessment | |
| - skin_tone: Skin color classification | |
| - skin_undertone: Cool, warm, or neutral undertone | |
| - face_shape: Face shape classification | |
| - height_estimate: Estimated height category (if full body visible) | |
| - style_recommendations: Personalized fashion suggestions | |
| - color_palette: Recommended colors based on skin tone | |
| - analysis_details: Detailed explanation (if detailed=True) | |
| """ | |
| # Load image | |
| pil_image = self._load_image(image) | |
| # Prepare detailed analysis prompt | |
| prompt = """ | |
| Analyze this person's image and provide a comprehensive style analysis based on PAKISTANI FASHION and cultural context. | |
| Focus on the following aspects: | |
| 1. **BODY TYPE ANALYSIS**: | |
| - Classify the body shape (rectangle, triangle/pear, inverted triangle, hourglass, oval/apple) | |
| - Note the shoulder-to-hip ratio | |
| - Identify the body frame (petite, average, tall) | |
| - Assess body proportions | |
| 2. **BODY ALIGNMENT & POSTURE**: | |
| - Evaluate posture (upright, slouched, forward head, etc.) | |
| - Check shoulder alignment (level, tilted) | |
| - Assess hip alignment | |
| - Note any visible asymmetries | |
| - Comment on overall body balance | |
| 3. **SKIN TONE ANALYSIS**: | |
| - Identify skin tone (fair, light, medium, olive, tan, brown, deep) | |
| - Determine skin undertone (cool/pink, warm/yellow-golden, neutral) | |
| - Note any variations in skin tone across visible areas | |
| - Assess skin texture and clarity (if visible) | |
| 4. **FACE SHAPE**: | |
| - Classify face shape (oval, round, square, heart, diamond, oblong/rectangle) | |
| - Note prominent features (cheekbones, jawline, forehead) | |
| 5. **HEIGHT ESTIMATION** (if full body is visible): | |
| - Estimate height category (petite: <5'4", average: 5'4"-5'7", tall: >5'7") | |
| - Base this on body proportions and limb-to-torso ratio | |
| 6. **PAKISTANI FASHION STYLE RECOMMENDATIONS**: | |
| - Suggest PAKISTANI clothing styles that complement the body type (Kurta, Shalwar Kameez, Lehenga, Sharara, Gharara, Anarkali, etc.) | |
| - Recommend cuts, silhouettes, and fits popular in Pakistani fashion | |
| - Suggest traditional and contemporary fusion styles | |
| - Provide neckline recommendations (boat neck, round neck, V-neck, collar neck, etc.) based on face shape | |
| - Include dupatta/scarf styling suggestions | |
| - Recommend embellishments and embroidery styles (Zari, Gota, Mirror work, Thread work, etc.) | |
| 7. **COLOR PALETTE** (Based on Pakistani Fashion Preferences): | |
| - Recommend colors based on skin tone and undertone, considering popular Pakistani color palettes | |
| - Suggest traditional Pakistani colors (pastels, jewel tones, earthy tones, bright festive colors) | |
| - Include seasonal preferences (lawn season colors, winter wear, festive colors) | |
| - Suggest colors to wear and colors to avoid | |
| 8. **PAKISTANI OUTFIT SUGGESTIONS**: | |
| - Suggest 3-5 complete PAKISTANI outfit ideas that would suit this person | |
| - Include both traditional and modern Pakistani styles | |
| - For each outfit, provide: | |
| * Name of the outfit style (e.g., "Elegant Anarkali Suit", "Modern Kurta Set", "Festive Sharara") | |
| * Specific items (Kurta/Kameez, Shalwar/Trouser/Palazzo, Dupatta, accessories) | |
| * Fabric suggestions (Lawn, Chiffon, Silk, Cotton, Karandi, Linen, etc.) | |
| * Embellishment details | |
| * Occasion suitability (casual, formal, festive, wedding, party) | |
| - Make suggestions practical and culturally appropriate | |
| Provide your analysis in the following JSON-like structure (but in natural language): | |
| **Body Type**: [classification] | |
| **Body Alignment**: [posture assessment] | |
| **Skin Tone**: [tone] with [undertone] undertones | |
| **Face Shape**: [shape] | |
| **Height Estimate**: [category if visible, or "Cannot determine - upper body only"] | |
| **Style Recommendations**: | |
| - [Pakistani fashion recommendation 1 - with specific garment types] | |
| - [Pakistani fashion recommendation 2 - with specific garment types] | |
| - [Pakistani fashion recommendation 3 - with specific garment types] | |
| - [Pakistani fashion recommendation 4 - styling tips] | |
| **Color Palette**: | |
| - Best colors: [list of colors popular in Pakistani fashion] | |
| - Colors to avoid: [list] | |
| **Outfit Suggestions**: | |
| - [Pakistani Outfit 1 with name]: [detailed outfit description with fabrics and embellishments] | |
| - [Pakistani Outfit 2 with name]: [detailed outfit description with fabrics and embellishments] | |
| - [Pakistani Outfit 3 with name]: [detailed outfit description with fabrics and embellishments] | |
| - [Pakistani Outfit 4 with name]: [detailed outfit description with fabrics and embellishments] | |
| - [Pakistani Outfit 5 with name]: [detailed outfit description with fabrics and embellishments] | |
| **Detailed Analysis**: | |
| [Provide a comprehensive paragraph explaining the analysis and how the PAKISTANI fashion recommendations | |
| will enhance the person's natural features, considering cultural preferences and modern trends] | |
| Be professional, positive, and constructive. Focus on enhancing natural features | |
| rather than criticizing. Provide actionable Pakistani fashion advice that considers both traditional | |
| and contemporary styles. Consider the rich heritage of Pakistani fashion including regional variations. | |
| """ | |
| if not detailed: | |
| prompt = """ | |
| Analyze this person's image briefly: | |
| 1. Body type | |
| 2. Posture/alignment | |
| 3. Skin tone with undertone | |
| 4. Face shape | |
| 5. Quick style suggestions | |
| Be concise and provide just the key findings in a few sentences. | |
| """ | |
| try: | |
| # Convert PIL image to bytes for Gemini | |
| img_byte_arr = io.BytesIO() | |
| pil_image.save(img_byte_arr, format='PNG') | |
| img_byte_arr.seek(0) | |
| # Generate analysis using Gemini | |
| response = self.client.models.generate_content( | |
| model=self.model_name, | |
| contents=[prompt, pil_image] | |
| ) | |
| # Parse response text | |
| analysis_text = response.text | |
| # Parse the structured response into a dictionary | |
| result = self._parse_analysis(analysis_text) | |
| # Add raw analysis text | |
| result['raw_analysis'] = analysis_text | |
| result['success'] = True | |
| return result | |
| except Exception as e: | |
| print(f"[ERROR] Gemini API error: {str(e)}") | |
| print(f"[ERROR] Full traceback: {traceback.format_exc()}") | |
| return { | |
| 'success': False, | |
| 'error': str(e), | |
| 'message': 'Failed to analyze image' | |
| } | |
| def _parse_analysis(self, text: str) -> Dict[str, Any]: | |
| """ | |
| Parse the AI response text into structured data. | |
| Args: | |
| text: Raw text response from Gemini | |
| Returns: | |
| Structured dictionary with analysis results | |
| """ | |
| result = { | |
| 'body_type': '', | |
| 'body_alignment': '', | |
| 'skin_tone': '', | |
| 'skin_undertone': '', | |
| 'face_shape': '', | |
| 'height_estimate': '', | |
| 'style_recommendations': [], | |
| 'outfit_suggestions': [], | |
| 'color_palette': { | |
| 'best_colors': [], | |
| 'colors_to_avoid': [] | |
| }, | |
| 'detailed_analysis': '' | |
| } | |
| lines = text.split('\n') | |
| current_section = None | |
| for line in lines: | |
| line = line.strip() | |
| # Skip empty lines | |
| if not line: | |
| continue | |
| # Detect sections | |
| if line.startswith('**Body Type**'): | |
| current_section = 'body_type' | |
| result['body_type'] = line.split(':', 1)[1].strip() if ':' in line else '' | |
| elif line.startswith('**Body Alignment**'): | |
| current_section = 'body_alignment' | |
| result['body_alignment'] = line.split(':', 1)[1].strip() if ':' in line else '' | |
| elif line.startswith('**Skin Tone**'): | |
| current_section = 'skin_tone' | |
| tone_text = line.split(':', 1)[1].strip() if ':' in line else '' | |
| result['skin_tone'] = tone_text | |
| # Extract undertone if present | |
| if 'undertone' in tone_text.lower(): | |
| if 'warm' in tone_text.lower(): | |
| result['skin_undertone'] = 'warm' | |
| elif 'cool' in tone_text.lower(): | |
| result['skin_undertone'] = 'cool' | |
| elif 'neutral' in tone_text.lower(): | |
| result['skin_undertone'] = 'neutral' | |
| elif line.startswith('**Face Shape**'): | |
| current_section = 'face_shape' | |
| result['face_shape'] = line.split(':', 1)[1].strip() if ':' in line else '' | |
| elif line.startswith('**Height Estimate**'): | |
| current_section = 'height_estimate' | |
| result['height_estimate'] = line.split(':', 1)[1].strip() if ':' in line else '' | |
| elif line.startswith('**Style Recommendations**'): | |
| current_section = 'style_recommendations' | |
| elif line.startswith('**Outfit Suggestions**'): | |
| current_section = 'outfit_suggestions' | |
| elif line.startswith('**Color Palette**'): | |
| current_section = 'color_palette' | |
| elif line.startswith('**Detailed Analysis**'): | |
| current_section = 'detailed_analysis' | |
| # Parse list items | |
| elif line.startswith('-') or line.startswith('•'): | |
| item = line[1:].strip() | |
| # Clean up markdown formatting (* and ** for bold/italic) | |
| item = item.replace('**', '').replace('*', '') | |
| if current_section == 'style_recommendations': | |
| result['style_recommendations'].append(item) | |
| elif current_section == 'outfit_suggestions': | |
| result['outfit_suggestions'].append(item) | |
| elif current_section == 'color_palette': | |
| if 'best' in item.lower() or 'wear' in item.lower(): | |
| # Extract colors from the line | |
| colors = item.split(':', 1)[1].strip() if ':' in item else item | |
| result['color_palette']['best_colors'] = [c.strip() for c in colors.split(',')] | |
| elif 'avoid' in item.lower(): | |
| colors = item.split(':', 1)[1].strip() if ':' in item else item | |
| result['color_palette']['colors_to_avoid'] = [c.strip() for c in colors.split(',')] | |
| # Accumulate detailed analysis | |
| elif current_section == 'detailed_analysis' and line: | |
| if result['detailed_analysis']: | |
| result['detailed_analysis'] += ' ' + line | |
| else: | |
| result['detailed_analysis'] = line | |
| # If parsing didn't capture everything, store the whole text | |
| if not result['body_type'] and not result['skin_tone']: | |
| result['detailed_analysis'] = text | |
| return result | |
| def analyze_for_virtual_tryon( | |
| self, | |
| image: Union[str, Path, Image.Image, bytes] | |
| ) -> Dict[str, Any]: | |
| """ | |
| Specialized analysis for virtual try-on preparation. | |
| Provides specific information needed for optimal virtual try-on results: | |
| - Body measurements and proportions | |
| - Best garment fits for body type | |
| - Pose quality assessment | |
| - Image quality check | |
| Args: | |
| image: User photo (file path, PIL Image, or bytes) | |
| Returns: | |
| Dictionary with virtual try-on specific analysis | |
| """ | |
| pil_image = self._load_image(image) | |
| prompt = """ | |
| Analyze this image for virtual try-on readiness with PAKISTANI FASHION context. Provide: | |
| 1. **Image Quality**: Rate the image quality (excellent, good, fair, poor) | |
| - Check lighting, clarity, resolution | |
| - Assess if the person is clearly visible | |
| 2. **Pose Assessment**: Evaluate the pose for try-on | |
| - Is the person facing forward? | |
| - Are arms positioned well for clothing visualization? | |
| - Is the full body or upper body visible? | |
| - Rate pose quality: excellent, good, fair, poor | |
| 3. **Body Type for Try-On**: Quick classification for Pakistani garment fitting | |
| - Body shape | |
| - Frame size | |
| - Suitability for different Pakistani outfit styles | |
| 4. **Pakistani Garment Fit Recommendations**: | |
| - What types of Pakistani clothing would fit best (Kurta, Shalwar Kameez, Anarkali, Sharara, Gharara, etc.) | |
| - Sizes to consider (Small, Medium, Large, XL for Pakistani wear) | |
| - Style suggestions for this body type in Pakistani fashion context | |
| - Dupatta and accessories recommendations | |
| 5. **Try-On Readiness Score**: Overall score from 1-10 | |
| - 10 = Perfect for virtual try-on of Pakistani outfits | |
| - 1 = Poor quality, may not work well | |
| 6. **Improvement Suggestions**: If score < 8, suggest how to improve the photo for trying on Pakistani fashion | |
| Be concise and practical. Focus on what matters for virtual try-on success with Pakistani clothing styles. | |
| Consider the draping nature of Pakistani garments and cultural styling preferences. | |
| """ | |
| try: | |
| # Convert PIL image to bytes for Gemini | |
| img_byte_arr = io.BytesIO() | |
| pil_image.save(img_byte_arr, format='PNG') | |
| img_byte_arr.seek(0) | |
| response = self.client.models.generate_content( | |
| model=self.model_name, | |
| contents=[prompt, pil_image] | |
| ) | |
| analysis_text = response.text | |
| return { | |
| 'success': True, | |
| 'analysis': analysis_text, | |
| 'raw_response': analysis_text, | |
| 'image_dimensions': { | |
| 'width': pil_image.width, | |
| 'height': pil_image.height | |
| } | |
| } | |
| except Exception as e: | |
| return { | |
| 'success': False, | |
| 'error': str(e), | |
| 'message': 'Failed to analyze image for virtual try-on' | |
| } | |
| # Example usage | |
| if __name__ == "__main__": | |
| import sys | |
| if len(sys.argv) < 2: | |
| print("Usage: python analyzer.py <image_path>") | |
| sys.exit(1) | |
| image_path = sys.argv[1] | |
| if not os.path.exists(image_path): | |
| print(f"Error: Image file not found: {image_path}") | |
| sys.exit(1) | |
| # Initialize analyzer | |
| analyzer = StyleAnalyzer() | |
| # Perform analysis | |
| print("Analyzing image...") | |
| result = analyzer.analyze_image(image_path, detailed=True) | |
| if result['success']: | |
| print("\n" + "="*60) | |
| print("STYLE ANALYSIS REPORT") | |
| print("="*60 + "\n") | |
| print(f"Body Type: {result['body_type']}") | |
| print(f"Body Alignment: {result['body_alignment']}") | |
| print(f"Skin Tone: {result['skin_tone']}") | |
| print(f"Face Shape: {result['face_shape']}") | |
| print(f"Height Estimate: {result['height_estimate']}") | |
| print("\nStyle Recommendations:") | |
| for rec in result['style_recommendations']: | |
| print(f" • {rec}") | |
| if result['color_palette']['best_colors']: | |
| print(f"\nBest Colors: {', '.join(result['color_palette']['best_colors'])}") | |
| if result['color_palette']['colors_to_avoid']: | |
| print(f"Colors to Avoid: {', '.join(result['color_palette']['colors_to_avoid'])}") | |
| print(f"\nDetailed Analysis:\n{result['detailed_analysis']}") | |
| print("\n" + "="*60) | |
| else: | |
| print(f"Analysis failed: {result.get('error', 'Unknown error')}") | |