Spaces:
Paused
Paused
| import os | |
| import json | |
| import time | |
| import hashlib | |
| from typing import Optional, Dict, Any | |
| from datetime import datetime | |
| from google import genai | |
| from google.genai.types import Part | |
| # ==================== CONFIGURATION ==================== | |
| API_KEY = os.getenv("GEMINI_API_KEY") | |
| # Use Gemini 2.0 Flash for cost efficiency (or 1.5 Pro for higher quality) | |
| MODEL_COMBINED = "models/gemini-2.0-flash-exp" # or "models/gemini-1.5-pro" | |
| # Cache for avoiding duplicate analyses | |
| _analysis_cache = {} | |
| # Usage tracking | |
| _usage_log = [] | |
| # ==================== HELPER FUNCTIONS ==================== | |
| def load_client(): | |
| """Initialize Gemini client""" | |
| return genai.Client(api_key=API_KEY) | |
| def get_image_hash(image_path: str) -> str: | |
| """Generate hash of image for caching""" | |
| with open(image_path, "rb") as f: | |
| return hashlib.md5(f.read()).hexdigest() | |
| def log_api_usage(tokens_used: int, cost: float, success: bool = True): | |
| """Track API usage for monitoring""" | |
| _usage_log.append({ | |
| "timestamp": datetime.now().isoformat(), | |
| "tokens": tokens_used, | |
| "cost": cost, | |
| "success": success | |
| }) | |
| # Optional: Write to file | |
| with open("api_usage.log", "a") as f: | |
| f.write(f"{datetime.now()},{tokens_used},{cost},{success}\n") | |
| def retry_with_backoff(func, max_retries: int = 3, initial_delay: float = 2.0): | |
| """ | |
| Retry API calls with exponential backoff | |
| """ | |
| delay = initial_delay | |
| for attempt in range(max_retries): | |
| try: | |
| return func() | |
| except Exception as e: | |
| error_msg = str(e).lower() | |
| # Check if it's a retryable error | |
| is_retryable = any(keyword in error_msg for keyword in [ | |
| "500", "503", "502", "504", | |
| "timeout", "overload", "unavailable", | |
| "internal error", "service unavailable" | |
| ]) | |
| if is_retryable and attempt < max_retries - 1: | |
| wait_time = delay * (2 ** attempt) | |
| print(f"⚠️ Attempt {attempt + 1}/{max_retries} failed: {e}") | |
| print(f" Retrying in {wait_time:.1f}s...") | |
| time.sleep(wait_time) | |
| elif attempt == max_retries - 1: | |
| print(f"❌ All {max_retries} attempts failed: {e}") | |
| raise | |
| else: | |
| # Non-retryable error, raise immediately | |
| raise | |
| return None | |
| # ==================== MAIN ANALYSIS FUNCTION ==================== | |
| def analyze_skin_complete( | |
| image_path: str, | |
| use_cache: bool = True, | |
| max_retries: int = 3 | |
| ) -> Optional[Dict[str, Any]]: | |
| """ | |
| Complete skin analysis with ONE API call including pores and wrinkles. | |
| Args: | |
| image_path: Path to the facial image | |
| use_cache: Whether to use cached results if available | |
| max_retries: Number of retry attempts on failure | |
| Returns: | |
| Dictionary containing all analysis results, or None on failure | |
| """ | |
| # Check cache first | |
| cache_key = f"complete_v2_{get_image_hash(image_path)}" | |
| if use_cache and cache_key in _analysis_cache: | |
| print("✓ Using cached analysis results") | |
| return _analysis_cache[cache_key] | |
| def _call(): | |
| client = load_client() | |
| # Load image | |
| with open(image_path, "rb") as f: | |
| image_bytes = f.read() | |
| image_part = Part.from_bytes(data=image_bytes, mime_type="image/jpeg") | |
| # Combined comprehensive prompt with pores and wrinkles | |
| prompt = """ | |
| You are an advanced AI skin analysis system. Analyze the face in this image comprehensively. | |
| Return STRICT JSON with ALL these fields (use exact field names): | |
| { | |
| "hydration": { | |
| "texture": float (0.0-1.0, smoothness level), | |
| "radiance": float (0.0-1.0, natural glow), | |
| "flakiness": float (0.0-1.0, visible dry flakes - higher is worse), | |
| "oil_balance": float (0.0-1.0, healthy surface moisture), | |
| "fine_lines": float (0.0-1.0, dryness lines - higher is worse) | |
| }, | |
| "pigmentation": { | |
| "dark_spots": float (0.0-1.0, severity of dark spots), | |
| "hyperpigmentation": float (0.0-1.0, overall hyperpigmentation), | |
| "under_eye_pigmentation": float (0.0-1.0, dark circles), | |
| "redness": float (0.0-1.0, skin redness), | |
| "melanin_unevenness": float (0.0-1.0, uneven melanin distribution), | |
| "uv_damage": float (0.0-1.0, visible UV damage), | |
| "overall_evenness": float (0.0-1.0, overall skin tone evenness) | |
| }, | |
| "acne": { | |
| "active_acne": float (0.0-1.0, active breakouts), | |
| "comedones": float (0.0-1.0, blackheads/whiteheads), | |
| "cystic_acne": float (0.0-1.0, deep cystic acne), | |
| "inflammation": float (0.0-1.0, inflammatory response), | |
| "oiliness": float (0.0-1.0, excess sebum production), | |
| "scarring": float (0.0-1.0, acne scarring), | |
| "congestion": float (0.0-1.0, pore congestion) | |
| }, | |
| "pores": { | |
| "visibility": float (0.0-1.0, how visible/prominent pores are), | |
| "size": float (0.0-1.0, average pore size - larger is worse), | |
| "enlarged_pores": float (0.0-1.0, percentage of enlarged pores), | |
| "clogged_pores": float (0.0-1.0, degree of pore clogging), | |
| "texture_roughness": float (0.0-1.0, roughness due to pores), | |
| "t_zone_prominence": float (0.0-1.0, pore visibility in T-zone), | |
| "cheek_prominence": float (0.0-1.0, pore visibility on cheeks) | |
| }, | |
| "wrinkles": { | |
| "forehead_lines": float (0.0-1.0, horizontal forehead wrinkles), | |
| "frown_lines": float (0.0-1.0, glabellar lines between eyebrows), | |
| "crows_feet": float (0.0-1.0, eye corner wrinkles), | |
| "nasolabial_folds": float (0.0-1.0, nose-to-mouth lines), | |
| "marionette_lines": float (0.0-1.0, mouth-to-chin lines), | |
| "under_eye_wrinkles": float (0.0-1.0, fine lines under eyes), | |
| "lip_lines": float (0.0-1.0, perioral wrinkles around mouth), | |
| "neck_lines": float (0.0-1.0, horizontal neck wrinkles if visible), | |
| "overall_severity": float (0.0-1.0, overall wrinkle severity), | |
| "depth": float (0.0-1.0, average depth of wrinkles), | |
| "dynamic_wrinkles": float (0.0-1.0, expression-related wrinkles), | |
| "static_wrinkles": float (0.0-1.0, wrinkles at rest) | |
| }, | |
| "age_analysis": { | |
| "fitzpatrick_type": integer (1-6, skin type based on melanin), | |
| "eye_age": integer (estimated age of eye area), | |
| "skin_age": integer (estimated overall skin age) | |
| } | |
| } | |
| DETAILED ANALYSIS GUIDELINES: | |
| PORES: | |
| - Assess pore visibility across different facial zones | |
| - Consider pore size relative to skin type | |
| - Note if pores appear stretched, enlarged, or clogged | |
| - T-zone (forehead, nose, chin) typically has more prominent pores | |
| - Cheeks may show different pore characteristics | |
| WRINKLES: | |
| - Distinguish between dynamic (expression) and static (at rest) wrinkles | |
| - Forehead lines: horizontal lines across forehead | |
| - Frown lines: vertical lines between eyebrows (11 lines) | |
| - Crow's feet: radiating lines from outer eye corners | |
| - Nasolabial folds: lines from nose to mouth corners | |
| - Marionette lines: lines from mouth corners downward | |
| - Assess depth (superficial vs deep wrinkles) | |
| - Consider fine lines vs established wrinkles | |
| CRITICAL RULES: | |
| - Return ONLY raw JSON, no markdown formatting | |
| - No explanations, no text outside JSON | |
| - All float values must be between 0.0 and 1.0 | |
| - All integer values must be positive integers | |
| - Base analysis ONLY on visible features in the image | |
| - Do NOT guess or infer anything not visible | |
| - Ensure all fields are present in the response | |
| - If a feature is not visible or applicable, use 0.0 | |
| """ | |
| # Make API call | |
| start_time = time.time() | |
| response = client.models.generate_content( | |
| model=MODEL_COMBINED, | |
| contents=[prompt, image_part], | |
| config={ | |
| "temperature": 0, | |
| "top_p": 1, | |
| "top_k": 1 | |
| } | |
| ) | |
| elapsed = time.time() - start_time | |
| # Parse response | |
| clean_text = response.text.strip() | |
| clean_text = clean_text.replace("```json", "").replace("```", "").strip() | |
| result = json.loads(clean_text) | |
| # Estimate tokens (rough estimate) | |
| estimated_tokens = len(prompt) / 4 + len(clean_text) / 4 + 1000 # +1000 for image | |
| cost = (estimated_tokens / 1_000_000) * 0.075 # Gemini 2.0 Flash pricing | |
| # Log usage | |
| log_api_usage(int(estimated_tokens), cost, success=True) | |
| print(f"✓ Analysis completed in {elapsed:.2f}s (est. cost: ${cost:.6f})") | |
| return result | |
| try: | |
| # Execute with retry logic | |
| result = retry_with_backoff(_call, max_retries=max_retries) | |
| if result and use_cache: | |
| _analysis_cache[cache_key] = result | |
| return result | |
| except Exception as e: | |
| print(f"❌ Failed to analyze skin after {max_retries} attempts: {e}") | |
| log_api_usage(0, 0, success=False) | |
| return None | |
| # ==================== SCORE CALCULATION FUNCTIONS ==================== | |
| def compute_hydration_score(hydration_factors: Dict[str, float]) -> Optional[float]: | |
| """ | |
| Calculate hydration score (0-100) from factors | |
| """ | |
| if not hydration_factors: | |
| return None | |
| try: | |
| score = ( | |
| hydration_factors["radiance"] * 30 + | |
| (1 - hydration_factors["flakiness"]) * 25 + | |
| (1 - hydration_factors["fine_lines"]) * 20 + | |
| hydration_factors["oil_balance"] * 15 + | |
| hydration_factors["texture"] * 10 | |
| ) | |
| return round(score, 1) | |
| except (KeyError, TypeError): | |
| return None | |
| def compute_pigmentation_score(pigmentation_factors: Dict[str, float]) -> Optional[float]: | |
| """ | |
| Calculate pigmentation score (0-100) from factors | |
| Higher score = more pigmentation issues | |
| """ | |
| if not pigmentation_factors: | |
| return None | |
| try: | |
| score = ( | |
| pigmentation_factors["hyperpigmentation"] * 30 + | |
| pigmentation_factors["dark_spots"] * 25 + | |
| pigmentation_factors["melanin_unevenness"] * 20 + | |
| pigmentation_factors["under_eye_pigmentation"] * 10 + | |
| pigmentation_factors["uv_damage"] * 10 + | |
| pigmentation_factors["redness"] * 5 | |
| ) | |
| return round(score, 1) | |
| except (KeyError, TypeError): | |
| return None | |
| def compute_acne_score(acne_factors: Dict[str, float]) -> Optional[float]: | |
| """ | |
| Calculate acne score (0-100) from factors | |
| Higher score = more acne issues | |
| """ | |
| if not acne_factors: | |
| return None | |
| try: | |
| score = ( | |
| acne_factors["active_acne"] * 40 + | |
| acne_factors["comedones"] * 20 + | |
| acne_factors["inflammation"] * 15 + | |
| acne_factors["cystic_acne"] * 15 + | |
| acne_factors["scarring"] * 10 | |
| ) | |
| return round(score, 1) | |
| except (KeyError, TypeError): | |
| return None | |
| def compute_pores_score(pores_factors: Dict[str, float]) -> Optional[float]: | |
| """ | |
| Calculate pores score (0-100) from factors | |
| Higher score = more visible/problematic pores | |
| """ | |
| if not pores_factors: | |
| return None | |
| try: | |
| score = ( | |
| pores_factors["visibility"] * 25 + | |
| pores_factors["size"] * 25 + | |
| pores_factors["enlarged_pores"] * 20 + | |
| pores_factors["clogged_pores"] * 15 + | |
| pores_factors["texture_roughness"] * 15 | |
| ) | |
| return round(score, 1) | |
| except (KeyError, TypeError): | |
| return None | |
| def compute_wrinkles_score(wrinkles_factors: Dict[str, float]) -> Optional[float]: | |
| """ | |
| Calculate wrinkles score (0-100) from factors | |
| Higher score = more severe wrinkling | |
| """ | |
| if not wrinkles_factors: | |
| return None | |
| try: | |
| score = ( | |
| wrinkles_factors["overall_severity"] * 30 + | |
| wrinkles_factors["depth"] * 20 + | |
| wrinkles_factors["forehead_lines"] * 10 + | |
| wrinkles_factors["crows_feet"] * 10 + | |
| wrinkles_factors["nasolabial_folds"] * 10 + | |
| wrinkles_factors["frown_lines"] * 8 + | |
| wrinkles_factors["static_wrinkles"] * 7 + | |
| wrinkles_factors["under_eye_wrinkles"] * 5 | |
| ) | |
| return round(score, 1) | |
| except (KeyError, TypeError): | |
| return None | |
| def get_comprehensive_analysis(image_path: str) -> Optional[Dict[str, Any]]: | |
| """ | |
| Get complete skin analysis with computed scores including pores and wrinkles. | |
| Returns a dictionary with all raw factors plus computed scores. | |
| """ | |
| raw_analysis = analyze_skin_complete(image_path) | |
| if not raw_analysis: | |
| return None | |
| # Add computed scores | |
| result = { | |
| "raw_data": raw_analysis, | |
| "scores": { | |
| "hydration": compute_hydration_score(raw_analysis.get("hydration")), | |
| "pigmentation": compute_pigmentation_score(raw_analysis.get("pigmentation")), | |
| "acne": compute_acne_score(raw_analysis.get("acne")), | |
| "pores": compute_pores_score(raw_analysis.get("pores")), | |
| "wrinkles": compute_wrinkles_score(raw_analysis.get("wrinkles")) | |
| }, | |
| "age_analysis": raw_analysis.get("age_analysis"), | |
| "metadata": { | |
| "analyzed_at": datetime.now().isoformat(), | |
| "model_used": MODEL_COMBINED | |
| } | |
| } | |
| return result | |
| # ==================== LEGACY COMPATIBILITY FUNCTIONS ==================== | |
| def get_hydration_factors(image_path: str) -> Optional[Dict[str, float]]: | |
| """Legacy function - extracts hydration from complete analysis""" | |
| result = analyze_skin_complete(image_path) | |
| return result.get("hydration") if result else None | |
| def get_pigmentation_factors(image_path: str) -> Optional[Dict[str, float]]: | |
| """Legacy function - extracts pigmentation from complete analysis""" | |
| result = analyze_skin_complete(image_path) | |
| return result.get("pigmentation") if result else None | |
| def get_acne_factors(image_path: str) -> Optional[Dict[str, float]]: | |
| """Legacy function - extracts acne from complete analysis""" | |
| result = analyze_skin_complete(image_path) | |
| return result.get("acne") if result else None | |
| def get_pores_factors(image_path: str) -> Optional[Dict[str, float]]: | |
| """Get pores analysis from complete analysis""" | |
| result = analyze_skin_complete(image_path) | |
| return result.get("pores") if result else None | |
| def get_wrinkles_factors(image_path: str) -> Optional[Dict[str, float]]: | |
| """Get wrinkles analysis from complete analysis""" | |
| result = analyze_skin_complete(image_path) | |
| return result.get("wrinkles") if result else None | |
| def get_fitzpatrick_type(image_path: str) -> Optional[int]: | |
| """Legacy function - extracts Fitzpatrick type from complete analysis""" | |
| result = analyze_skin_complete(image_path) | |
| if result and "age_analysis" in result: | |
| return result["age_analysis"].get("fitzpatrick_type") | |
| return None | |
| def get_eye_age(image_path: str) -> Optional[int]: | |
| """Legacy function - extracts eye age from complete analysis""" | |
| result = analyze_skin_complete(image_path) | |
| if result and "age_analysis" in result: | |
| return result["age_analysis"].get("eye_age") | |
| return None | |
| def get_skin_age(image_path: str) -> Optional[int]: | |
| """Legacy function - extracts skin age from complete analysis""" | |
| result = analyze_skin_complete(image_path) | |
| if result and "age_analysis" in result: | |
| return result["age_analysis"].get("skin_age") | |
| return None | |
| # ==================== DETAILED ANALYSIS FUNCTIONS ==================== | |
| def get_wrinkle_breakdown(image_path: str) -> Optional[Dict[str, Any]]: | |
| """ | |
| Get detailed breakdown of wrinkles by type and severity | |
| """ | |
| wrinkles = get_wrinkles_factors(image_path) | |
| if not wrinkles: | |
| return None | |
| # Categorize wrinkles by location | |
| upper_face = { | |
| "forehead_lines": wrinkles.get("forehead_lines", 0), | |
| "frown_lines": wrinkles.get("frown_lines", 0), | |
| "average": (wrinkles.get("forehead_lines", 0) + wrinkles.get("frown_lines", 0)) / 2 | |
| } | |
| eye_area = { | |
| "crows_feet": wrinkles.get("crows_feet", 0), | |
| "under_eye_wrinkles": wrinkles.get("under_eye_wrinkles", 0), | |
| "average": (wrinkles.get("crows_feet", 0) + wrinkles.get("under_eye_wrinkles", 0)) / 2 | |
| } | |
| lower_face = { | |
| "nasolabial_folds": wrinkles.get("nasolabial_folds", 0), | |
| "marionette_lines": wrinkles.get("marionette_lines", 0), | |
| "lip_lines": wrinkles.get("lip_lines", 0), | |
| "average": (wrinkles.get("nasolabial_folds", 0) + | |
| wrinkles.get("marionette_lines", 0) + | |
| wrinkles.get("lip_lines", 0)) / 3 | |
| } | |
| # Categorize by type | |
| wrinkle_types = { | |
| "dynamic": wrinkles.get("dynamic_wrinkles", 0), | |
| "static": wrinkles.get("static_wrinkles", 0), | |
| "predominant_type": "static" if wrinkles.get("static_wrinkles", 0) > wrinkles.get("dynamic_wrinkles", 0) else "dynamic" | |
| } | |
| return { | |
| "by_location": { | |
| "upper_face": upper_face, | |
| "eye_area": eye_area, | |
| "lower_face": lower_face | |
| }, | |
| "by_type": wrinkle_types, | |
| "severity": { | |
| "depth": wrinkles.get("depth", 0), | |
| "overall": wrinkles.get("overall_severity", 0) | |
| }, | |
| "most_affected_area": max( | |
| [("upper_face", upper_face["average"]), | |
| ("eye_area", eye_area["average"]), | |
| ("lower_face", lower_face["average"])], | |
| key=lambda x: x[1] | |
| )[0] | |
| } | |
| def get_pores_breakdown(image_path: str) -> Optional[Dict[str, Any]]: | |
| """ | |
| Get detailed breakdown of pores by zone and characteristics | |
| """ | |
| pores = get_pores_factors(image_path) | |
| if not pores: | |
| return None | |
| # Analyze by facial zone | |
| zones = { | |
| "t_zone": { | |
| "prominence": pores.get("t_zone_prominence", 0), | |
| "severity": "severe" if pores.get("t_zone_prominence", 0) > 0.7 | |
| else "moderate" if pores.get("t_zone_prominence", 0) > 0.4 | |
| else "mild" | |
| }, | |
| "cheeks": { | |
| "prominence": pores.get("cheek_prominence", 0), | |
| "severity": "severe" if pores.get("cheek_prominence", 0) > 0.7 | |
| else "moderate" if pores.get("cheek_prominence", 0) > 0.4 | |
| else "mild" | |
| } | |
| } | |
| # Overall pore characteristics | |
| characteristics = { | |
| "size": pores.get("size", 0), | |
| "visibility": pores.get("visibility", 0), | |
| "enlarged_percentage": pores.get("enlarged_pores", 0) * 100, | |
| "clogging_level": pores.get("clogged_pores", 0) | |
| } | |
| # Recommendations based on severity | |
| severity_score = (pores.get("visibility", 0) + pores.get("size", 0)) / 2 | |
| return { | |
| "by_zone": zones, | |
| "characteristics": characteristics, | |
| "texture_impact": pores.get("texture_roughness", 0), | |
| "overall_severity": "severe" if severity_score > 0.7 | |
| else "moderate" if severity_score > 0.4 | |
| else "mild", | |
| "most_affected_zone": "t_zone" if zones["t_zone"]["prominence"] > zones["cheeks"]["prominence"] else "cheeks" | |
| } | |
| # ==================== USAGE STATISTICS ==================== | |
| def get_usage_stats() -> Dict[str, Any]: | |
| """Get API usage statistics""" | |
| if not _usage_log: | |
| return {"message": "No usage data available"} | |
| total_calls = len(_usage_log) | |
| successful_calls = sum(1 for log in _usage_log if log["success"]) | |
| total_cost = sum(log["cost"] for log in _usage_log) | |
| total_tokens = sum(log["tokens"] for log in _usage_log) | |
| return { | |
| "total_calls": total_calls, | |
| "successful_calls": successful_calls, | |
| "failed_calls": total_calls - successful_calls, | |
| "success_rate": f"{(successful_calls/total_calls*100):.1f}%", | |
| "total_cost": f"${total_cost:.6f}", | |
| "total_tokens": total_tokens, | |
| "avg_cost_per_call": f"${(total_cost/total_calls):.6f}" if total_calls > 0 else "$0" | |
| } | |
| def clear_cache(): | |
| """Clear the analysis cache""" | |
| global _analysis_cache | |
| _analysis_cache = {} | |
| print("✓ Cache cleared") | |
| # ==================== EXAMPLE USAGE ==================== | |
| if __name__ == "__main__": | |
| # Example 1: Get complete analysis with pores and wrinkles | |
| print("=" * 70) | |
| print("COMPLETE SKIN ANALYSIS (INCLUDING PORES & WRINKLES)") | |
| print("=" * 70) | |
| image_path = "path/to/face_image.jpg" | |
| # Single API call for everything | |
| analysis = get_comprehensive_analysis(image_path) | |
| if analysis: | |
| print("\n✓ Analysis successful!") | |
| print(f"\n{'='*70}") | |
| print("SCORES (0-100, higher = more concern)") | |
| print(f"{'='*70}") | |
| print(f"Hydration Score: {analysis['scores']['hydration']:.1f}/100") | |
| print(f"Pigmentation Score: {analysis['scores']['pigmentation']:.1f}/100") | |
| print(f"Acne Score: {analysis['scores']['acne']:.1f}/100") | |
| print(f"Pores Score: {analysis['scores']['pores']:.1f}/100") | |
| print(f"Wrinkles Score: {analysis['scores']['wrinkles']:.1f}/100") | |
| print(f"\n{'='*70}") | |
| print("AGE ANALYSIS") | |
| print(f"{'='*70}") | |
| print(f"Fitzpatrick Type: {analysis['age_analysis']['fitzpatrick_type']}") | |
| print(f"Eye Age: {analysis['age_analysis']['eye_age']} years") | |
| print(f"Skin Age: {analysis['age_analysis']['skin_age']} years") | |
| # Save to file | |
| with open("analysis_result.json", "w") as f: | |
| json.dump(analysis, f, indent=2) | |
| print("\n✓ Results saved to analysis_result.json") | |
| else: | |
| print("\n❌ Analysis failed") | |
| # Example 2: Get detailed pores breakdown | |
| print("\n" + "=" * 70) | |
| print("DETAILED PORES ANALYSIS") | |
| print("=" * 70) | |
| pores_detail = get_pores_breakdown(image_path) | |
| if pores_detail: | |
| print(f"\nMost Affected Zone: {pores_detail['most_affected_zone'].upper()}") | |
| print(f"Overall Severity: {pores_detail['overall_severity'].upper()}") | |
| print(f"\nT-Zone Prominence: {pores_detail['by_zone']['t_zone']['prominence']:.2f}") | |
| print(f"Cheek Prominence: {pores_detail['by_zone']['cheeks']['prominence']:.2f}") | |
| print(f"Enlarged Pores: {pores_detail['characteristics']['enlarged_percentage']:.1f}%") | |
| print(f"Pore Size: {pores_detail['characteristics']['size']:.2f}") | |
| print(f"Visibility: {pores_detail['characteristics']['visibility']:.2f}") | |
| # Example 3: Get detailed wrinkles breakdown | |
| print("\n" + "=" * 70) | |
| print("DETAILED WRINKLES ANALYSIS") | |
| print("=" * 70) | |
| wrinkles_detail = get_wrinkle_breakdown(image_path) | |
| if wrinkles_detail: | |
| print(f"\nMost Affected Area: {wrinkles_detail['most_affected_area'].upper()}") | |
| print(f"Predominant Type: {wrinkles_detail['by_type']['predominant_type'].upper()}") | |
| print(f"Overall Severity: {wrinkles_detail['severity']['overall']:.2f}") | |
| print(f"Average Depth: {wrinkles_detail['severity']['depth']:.2f}") | |
| print(f"\nUpper Face (avg): {wrinkles_detail['by_location']['upper_face']['average']:.2f}") | |
| print(f" - Forehead: {wrinkles_detail['by_location']['upper_face']['forehead_lines']:.2f}") | |
| print(f" - Frown Lines: {wrinkles_detail['by_location']['upper_face']['frown_lines']:.2f}") | |
| print(f"\nEye Area (avg): {wrinkles_detail['by_location']['eye_area']['average']:.2f}") | |
| print(f" - Crow's Feet: {wrinkles_detail['by_location']['eye_area']['crows_feet']:.2f}") | |
| print(f" - Under Eye: {wrinkles_detail['by_location']['eye_area']['under_eye_wrinkles']:.2f}") | |
| print(f"\nLower Face (avg): {wrinkles_detail['by_location']['lower_face']['average']:.2f}") | |
| print(f" - Nasolabial: {wrinkles_detail['by_location']['lower_face']['nasolabial_folds']:.2f}") | |
| print(f" - Marionette: {wrinkles_detail['by_location']['lower_face']['marionette_lines']:.2f}") | |
| # Example 4: Using legacy functions (still works, uses same cached data) | |
| print("\n" + "=" * 70) | |
| print("USING LEGACY FUNCTIONS") | |
| print("=" * 70) | |
| hydration = get_hydration_factors(image_path) | |
| print(f"\nHydration factors: {hydration}") | |
| pigmentation = get_pigmentation_factors(image_path) | |
| print(f"Pigmentation factors: {pigmentation}") | |
| pores = get_pores_factors(image_path) | |
| print(f"Pores factors: {pores}") | |
| wrinkles = get_wrinkles_factors(image_path) | |
| print(f"Wrinkles factors: {wrinkles}") | |
| # Example 5: Check usage statistics | |
| print("\n" + "=" * 70) | |
| print("API USAGE STATISTICS") | |
| print("=" * 70) | |
| stats = get_usage_stats() | |
| print(json.dumps(stats, indent=2)) |