Spaces:
Sleeping
Sleeping
Update skin_analysis.py
Browse files- skin_analysis.py +284 -22
skin_analysis.py
CHANGED
|
@@ -89,7 +89,7 @@ def analyze_skin_complete(
|
|
| 89 |
max_retries: int = 3
|
| 90 |
) -> Optional[Dict[str, Any]]:
|
| 91 |
"""
|
| 92 |
-
Complete skin analysis with ONE API call
|
| 93 |
|
| 94 |
Args:
|
| 95 |
image_path: Path to the facial image
|
|
@@ -101,7 +101,7 @@ def analyze_skin_complete(
|
|
| 101 |
"""
|
| 102 |
|
| 103 |
# Check cache first
|
| 104 |
-
cache_key = f"
|
| 105 |
if use_cache and cache_key in _analysis_cache:
|
| 106 |
print("✓ Using cached analysis results")
|
| 107 |
return _analysis_cache[cache_key]
|
|
@@ -115,7 +115,7 @@ def analyze_skin_complete(
|
|
| 115 |
|
| 116 |
image_part = Part.from_bytes(data=image_bytes, mime_type="image/jpeg")
|
| 117 |
|
| 118 |
-
# Combined comprehensive prompt
|
| 119 |
prompt = """
|
| 120 |
You are an advanced AI skin analysis system. Analyze the face in this image comprehensively.
|
| 121 |
|
|
@@ -147,6 +147,29 @@ Return STRICT JSON with ALL these fields (use exact field names):
|
|
| 147 |
"scarring": float (0.0-1.0, acne scarring),
|
| 148 |
"congestion": float (0.0-1.0, pore congestion)
|
| 149 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
"age_analysis": {
|
| 151 |
"fitzpatrick_type": integer (1-6, skin type based on melanin),
|
| 152 |
"eye_age": integer (estimated age of eye area),
|
|
@@ -154,6 +177,25 @@ Return STRICT JSON with ALL these fields (use exact field names):
|
|
| 154 |
}
|
| 155 |
}
|
| 156 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
CRITICAL RULES:
|
| 158 |
- Return ONLY raw JSON, no markdown formatting
|
| 159 |
- No explanations, no text outside JSON
|
|
@@ -162,6 +204,7 @@ CRITICAL RULES:
|
|
| 162 |
- Base analysis ONLY on visible features in the image
|
| 163 |
- Do NOT guess or infer anything not visible
|
| 164 |
- Ensure all fields are present in the response
|
|
|
|
| 165 |
"""
|
| 166 |
|
| 167 |
# Make API call
|
|
@@ -234,6 +277,7 @@ def compute_hydration_score(hydration_factors: Dict[str, float]) -> Optional[flo
|
|
| 234 |
def compute_pigmentation_score(pigmentation_factors: Dict[str, float]) -> Optional[float]:
|
| 235 |
"""
|
| 236 |
Calculate pigmentation score (0-100) from factors
|
|
|
|
| 237 |
"""
|
| 238 |
if not pigmentation_factors:
|
| 239 |
return None
|
|
@@ -255,6 +299,7 @@ def compute_pigmentation_score(pigmentation_factors: Dict[str, float]) -> Option
|
|
| 255 |
def compute_acne_score(acne_factors: Dict[str, float]) -> Optional[float]:
|
| 256 |
"""
|
| 257 |
Calculate acne score (0-100) from factors
|
|
|
|
| 258 |
"""
|
| 259 |
if not acne_factors:
|
| 260 |
return None
|
|
@@ -272,9 +317,54 @@ def compute_acne_score(acne_factors: Dict[str, float]) -> Optional[float]:
|
|
| 272 |
return None
|
| 273 |
|
| 274 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
def get_comprehensive_analysis(image_path: str) -> Optional[Dict[str, Any]]:
|
| 276 |
"""
|
| 277 |
-
Get complete skin analysis with computed scores.
|
| 278 |
|
| 279 |
Returns a dictionary with all raw factors plus computed scores.
|
| 280 |
"""
|
|
@@ -289,7 +379,9 @@ def get_comprehensive_analysis(image_path: str) -> Optional[Dict[str, Any]]:
|
|
| 289 |
"scores": {
|
| 290 |
"hydration": compute_hydration_score(raw_analysis.get("hydration")),
|
| 291 |
"pigmentation": compute_pigmentation_score(raw_analysis.get("pigmentation")),
|
| 292 |
-
"acne": compute_acne_score(raw_analysis.get("acne"))
|
|
|
|
|
|
|
| 293 |
},
|
| 294 |
"age_analysis": raw_analysis.get("age_analysis"),
|
| 295 |
"metadata": {
|
|
@@ -302,7 +394,6 @@ def get_comprehensive_analysis(image_path: str) -> Optional[Dict[str, Any]]:
|
|
| 302 |
|
| 303 |
|
| 304 |
# ==================== LEGACY COMPATIBILITY FUNCTIONS ====================
|
| 305 |
-
# These functions maintain backward compatibility with your existing code
|
| 306 |
|
| 307 |
def get_hydration_factors(image_path: str) -> Optional[Dict[str, float]]:
|
| 308 |
"""Legacy function - extracts hydration from complete analysis"""
|
|
@@ -322,6 +413,18 @@ def get_acne_factors(image_path: str) -> Optional[Dict[str, float]]:
|
|
| 322 |
return result.get("acne") if result else None
|
| 323 |
|
| 324 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 325 |
def get_fitzpatrick_type(image_path: str) -> Optional[int]:
|
| 326 |
"""Legacy function - extracts Fitzpatrick type from complete analysis"""
|
| 327 |
result = analyze_skin_complete(image_path)
|
|
@@ -346,6 +449,111 @@ def get_skin_age(image_path: str) -> Optional[int]:
|
|
| 346 |
return None
|
| 347 |
|
| 348 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
# ==================== USAGE STATISTICS ====================
|
| 350 |
|
| 351 |
def get_usage_stats() -> Dict[str, Any]:
|
|
@@ -379,10 +587,10 @@ def clear_cache():
|
|
| 379 |
# ==================== EXAMPLE USAGE ====================
|
| 380 |
|
| 381 |
if __name__ == "__main__":
|
| 382 |
-
# Example 1: Get complete analysis
|
| 383 |
-
print("=" *
|
| 384 |
-
print("COMPLETE SKIN ANALYSIS")
|
| 385 |
-
print("=" *
|
| 386 |
|
| 387 |
image_path = "path/to/face_image.jpg"
|
| 388 |
|
|
@@ -391,12 +599,21 @@ if __name__ == "__main__":
|
|
| 391 |
|
| 392 |
if analysis:
|
| 393 |
print("\n✓ Analysis successful!")
|
| 394 |
-
print(f"\
|
| 395 |
-
print(
|
| 396 |
-
print(f"
|
| 397 |
-
print(f"
|
| 398 |
-
print(f"
|
| 399 |
-
print(f"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 400 |
|
| 401 |
# Save to file
|
| 402 |
with open("analysis_result.json", "w") as f:
|
|
@@ -405,10 +622,49 @@ if __name__ == "__main__":
|
|
| 405 |
else:
|
| 406 |
print("\n❌ Analysis failed")
|
| 407 |
|
| 408 |
-
# Example 2:
|
| 409 |
-
print("\n" + "=" *
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 410 |
print("USING LEGACY FUNCTIONS")
|
| 411 |
-
print("=" *
|
| 412 |
|
| 413 |
hydration = get_hydration_factors(image_path)
|
| 414 |
print(f"\nHydration factors: {hydration}")
|
|
@@ -416,9 +672,15 @@ if __name__ == "__main__":
|
|
| 416 |
pigmentation = get_pigmentation_factors(image_path)
|
| 417 |
print(f"Pigmentation factors: {pigmentation}")
|
| 418 |
|
| 419 |
-
|
| 420 |
-
print("
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 421 |
print("API USAGE STATISTICS")
|
| 422 |
-
print("=" *
|
| 423 |
stats = get_usage_stats()
|
| 424 |
print(json.dumps(stats, indent=2))
|
|
|
|
| 89 |
max_retries: int = 3
|
| 90 |
) -> Optional[Dict[str, Any]]:
|
| 91 |
"""
|
| 92 |
+
Complete skin analysis with ONE API call including pores and wrinkles.
|
| 93 |
|
| 94 |
Args:
|
| 95 |
image_path: Path to the facial image
|
|
|
|
| 101 |
"""
|
| 102 |
|
| 103 |
# Check cache first
|
| 104 |
+
cache_key = f"complete_v2_{get_image_hash(image_path)}"
|
| 105 |
if use_cache and cache_key in _analysis_cache:
|
| 106 |
print("✓ Using cached analysis results")
|
| 107 |
return _analysis_cache[cache_key]
|
|
|
|
| 115 |
|
| 116 |
image_part = Part.from_bytes(data=image_bytes, mime_type="image/jpeg")
|
| 117 |
|
| 118 |
+
# Combined comprehensive prompt with pores and wrinkles
|
| 119 |
prompt = """
|
| 120 |
You are an advanced AI skin analysis system. Analyze the face in this image comprehensively.
|
| 121 |
|
|
|
|
| 147 |
"scarring": float (0.0-1.0, acne scarring),
|
| 148 |
"congestion": float (0.0-1.0, pore congestion)
|
| 149 |
},
|
| 150 |
+
"pores": {
|
| 151 |
+
"visibility": float (0.0-1.0, how visible/prominent pores are),
|
| 152 |
+
"size": float (0.0-1.0, average pore size - larger is worse),
|
| 153 |
+
"enlarged_pores": float (0.0-1.0, percentage of enlarged pores),
|
| 154 |
+
"clogged_pores": float (0.0-1.0, degree of pore clogging),
|
| 155 |
+
"texture_roughness": float (0.0-1.0, roughness due to pores),
|
| 156 |
+
"t_zone_prominence": float (0.0-1.0, pore visibility in T-zone),
|
| 157 |
+
"cheek_prominence": float (0.0-1.0, pore visibility on cheeks)
|
| 158 |
+
},
|
| 159 |
+
"wrinkles": {
|
| 160 |
+
"forehead_lines": float (0.0-1.0, horizontal forehead wrinkles),
|
| 161 |
+
"frown_lines": float (0.0-1.0, glabellar lines between eyebrows),
|
| 162 |
+
"crows_feet": float (0.0-1.0, eye corner wrinkles),
|
| 163 |
+
"nasolabial_folds": float (0.0-1.0, nose-to-mouth lines),
|
| 164 |
+
"marionette_lines": float (0.0-1.0, mouth-to-chin lines),
|
| 165 |
+
"under_eye_wrinkles": float (0.0-1.0, fine lines under eyes),
|
| 166 |
+
"lip_lines": float (0.0-1.0, perioral wrinkles around mouth),
|
| 167 |
+
"neck_lines": float (0.0-1.0, horizontal neck wrinkles if visible),
|
| 168 |
+
"overall_severity": float (0.0-1.0, overall wrinkle severity),
|
| 169 |
+
"depth": float (0.0-1.0, average depth of wrinkles),
|
| 170 |
+
"dynamic_wrinkles": float (0.0-1.0, expression-related wrinkles),
|
| 171 |
+
"static_wrinkles": float (0.0-1.0, wrinkles at rest)
|
| 172 |
+
},
|
| 173 |
"age_analysis": {
|
| 174 |
"fitzpatrick_type": integer (1-6, skin type based on melanin),
|
| 175 |
"eye_age": integer (estimated age of eye area),
|
|
|
|
| 177 |
}
|
| 178 |
}
|
| 179 |
|
| 180 |
+
DETAILED ANALYSIS GUIDELINES:
|
| 181 |
+
|
| 182 |
+
PORES:
|
| 183 |
+
- Assess pore visibility across different facial zones
|
| 184 |
+
- Consider pore size relative to skin type
|
| 185 |
+
- Note if pores appear stretched, enlarged, or clogged
|
| 186 |
+
- T-zone (forehead, nose, chin) typically has more prominent pores
|
| 187 |
+
- Cheeks may show different pore characteristics
|
| 188 |
+
|
| 189 |
+
WRINKLES:
|
| 190 |
+
- Distinguish between dynamic (expression) and static (at rest) wrinkles
|
| 191 |
+
- Forehead lines: horizontal lines across forehead
|
| 192 |
+
- Frown lines: vertical lines between eyebrows (11 lines)
|
| 193 |
+
- Crow's feet: radiating lines from outer eye corners
|
| 194 |
+
- Nasolabial folds: lines from nose to mouth corners
|
| 195 |
+
- Marionette lines: lines from mouth corners downward
|
| 196 |
+
- Assess depth (superficial vs deep wrinkles)
|
| 197 |
+
- Consider fine lines vs established wrinkles
|
| 198 |
+
|
| 199 |
CRITICAL RULES:
|
| 200 |
- Return ONLY raw JSON, no markdown formatting
|
| 201 |
- No explanations, no text outside JSON
|
|
|
|
| 204 |
- Base analysis ONLY on visible features in the image
|
| 205 |
- Do NOT guess or infer anything not visible
|
| 206 |
- Ensure all fields are present in the response
|
| 207 |
+
- If a feature is not visible or applicable, use 0.0
|
| 208 |
"""
|
| 209 |
|
| 210 |
# Make API call
|
|
|
|
| 277 |
def compute_pigmentation_score(pigmentation_factors: Dict[str, float]) -> Optional[float]:
|
| 278 |
"""
|
| 279 |
Calculate pigmentation score (0-100) from factors
|
| 280 |
+
Higher score = more pigmentation issues
|
| 281 |
"""
|
| 282 |
if not pigmentation_factors:
|
| 283 |
return None
|
|
|
|
| 299 |
def compute_acne_score(acne_factors: Dict[str, float]) -> Optional[float]:
|
| 300 |
"""
|
| 301 |
Calculate acne score (0-100) from factors
|
| 302 |
+
Higher score = more acne issues
|
| 303 |
"""
|
| 304 |
if not acne_factors:
|
| 305 |
return None
|
|
|
|
| 317 |
return None
|
| 318 |
|
| 319 |
|
| 320 |
+
def compute_pores_score(pores_factors: Dict[str, float]) -> Optional[float]:
|
| 321 |
+
"""
|
| 322 |
+
Calculate pores score (0-100) from factors
|
| 323 |
+
Higher score = more visible/problematic pores
|
| 324 |
+
"""
|
| 325 |
+
if not pores_factors:
|
| 326 |
+
return None
|
| 327 |
+
|
| 328 |
+
try:
|
| 329 |
+
score = (
|
| 330 |
+
pores_factors["visibility"] * 25 +
|
| 331 |
+
pores_factors["size"] * 25 +
|
| 332 |
+
pores_factors["enlarged_pores"] * 20 +
|
| 333 |
+
pores_factors["clogged_pores"] * 15 +
|
| 334 |
+
pores_factors["texture_roughness"] * 15
|
| 335 |
+
)
|
| 336 |
+
return round(score, 1)
|
| 337 |
+
except (KeyError, TypeError):
|
| 338 |
+
return None
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
def compute_wrinkles_score(wrinkles_factors: Dict[str, float]) -> Optional[float]:
|
| 342 |
+
"""
|
| 343 |
+
Calculate wrinkles score (0-100) from factors
|
| 344 |
+
Higher score = more severe wrinkling
|
| 345 |
+
"""
|
| 346 |
+
if not wrinkles_factors:
|
| 347 |
+
return None
|
| 348 |
+
|
| 349 |
+
try:
|
| 350 |
+
score = (
|
| 351 |
+
wrinkles_factors["overall_severity"] * 30 +
|
| 352 |
+
wrinkles_factors["depth"] * 20 +
|
| 353 |
+
wrinkles_factors["forehead_lines"] * 10 +
|
| 354 |
+
wrinkles_factors["crows_feet"] * 10 +
|
| 355 |
+
wrinkles_factors["nasolabial_folds"] * 10 +
|
| 356 |
+
wrinkles_factors["frown_lines"] * 8 +
|
| 357 |
+
wrinkles_factors["static_wrinkles"] * 7 +
|
| 358 |
+
wrinkles_factors["under_eye_wrinkles"] * 5
|
| 359 |
+
)
|
| 360 |
+
return round(score, 1)
|
| 361 |
+
except (KeyError, TypeError):
|
| 362 |
+
return None
|
| 363 |
+
|
| 364 |
+
|
| 365 |
def get_comprehensive_analysis(image_path: str) -> Optional[Dict[str, Any]]:
|
| 366 |
"""
|
| 367 |
+
Get complete skin analysis with computed scores including pores and wrinkles.
|
| 368 |
|
| 369 |
Returns a dictionary with all raw factors plus computed scores.
|
| 370 |
"""
|
|
|
|
| 379 |
"scores": {
|
| 380 |
"hydration": compute_hydration_score(raw_analysis.get("hydration")),
|
| 381 |
"pigmentation": compute_pigmentation_score(raw_analysis.get("pigmentation")),
|
| 382 |
+
"acne": compute_acne_score(raw_analysis.get("acne")),
|
| 383 |
+
"pores": compute_pores_score(raw_analysis.get("pores")),
|
| 384 |
+
"wrinkles": compute_wrinkles_score(raw_analysis.get("wrinkles"))
|
| 385 |
},
|
| 386 |
"age_analysis": raw_analysis.get("age_analysis"),
|
| 387 |
"metadata": {
|
|
|
|
| 394 |
|
| 395 |
|
| 396 |
# ==================== LEGACY COMPATIBILITY FUNCTIONS ====================
|
|
|
|
| 397 |
|
| 398 |
def get_hydration_factors(image_path: str) -> Optional[Dict[str, float]]:
|
| 399 |
"""Legacy function - extracts hydration from complete analysis"""
|
|
|
|
| 413 |
return result.get("acne") if result else None
|
| 414 |
|
| 415 |
|
| 416 |
+
def get_pores_factors(image_path: str) -> Optional[Dict[str, float]]:
|
| 417 |
+
"""Get pores analysis from complete analysis"""
|
| 418 |
+
result = analyze_skin_complete(image_path)
|
| 419 |
+
return result.get("pores") if result else None
|
| 420 |
+
|
| 421 |
+
|
| 422 |
+
def get_wrinkles_factors(image_path: str) -> Optional[Dict[str, float]]:
|
| 423 |
+
"""Get wrinkles analysis from complete analysis"""
|
| 424 |
+
result = analyze_skin_complete(image_path)
|
| 425 |
+
return result.get("wrinkles") if result else None
|
| 426 |
+
|
| 427 |
+
|
| 428 |
def get_fitzpatrick_type(image_path: str) -> Optional[int]:
|
| 429 |
"""Legacy function - extracts Fitzpatrick type from complete analysis"""
|
| 430 |
result = analyze_skin_complete(image_path)
|
|
|
|
| 449 |
return None
|
| 450 |
|
| 451 |
|
| 452 |
+
# ==================== DETAILED ANALYSIS FUNCTIONS ====================
|
| 453 |
+
|
| 454 |
+
def get_wrinkle_breakdown(image_path: str) -> Optional[Dict[str, Any]]:
|
| 455 |
+
"""
|
| 456 |
+
Get detailed breakdown of wrinkles by type and severity
|
| 457 |
+
"""
|
| 458 |
+
wrinkles = get_wrinkles_factors(image_path)
|
| 459 |
+
if not wrinkles:
|
| 460 |
+
return None
|
| 461 |
+
|
| 462 |
+
# Categorize wrinkles by location
|
| 463 |
+
upper_face = {
|
| 464 |
+
"forehead_lines": wrinkles.get("forehead_lines", 0),
|
| 465 |
+
"frown_lines": wrinkles.get("frown_lines", 0),
|
| 466 |
+
"average": (wrinkles.get("forehead_lines", 0) + wrinkles.get("frown_lines", 0)) / 2
|
| 467 |
+
}
|
| 468 |
+
|
| 469 |
+
eye_area = {
|
| 470 |
+
"crows_feet": wrinkles.get("crows_feet", 0),
|
| 471 |
+
"under_eye_wrinkles": wrinkles.get("under_eye_wrinkles", 0),
|
| 472 |
+
"average": (wrinkles.get("crows_feet", 0) + wrinkles.get("under_eye_wrinkles", 0)) / 2
|
| 473 |
+
}
|
| 474 |
+
|
| 475 |
+
lower_face = {
|
| 476 |
+
"nasolabial_folds": wrinkles.get("nasolabial_folds", 0),
|
| 477 |
+
"marionette_lines": wrinkles.get("marionette_lines", 0),
|
| 478 |
+
"lip_lines": wrinkles.get("lip_lines", 0),
|
| 479 |
+
"average": (wrinkles.get("nasolabial_folds", 0) +
|
| 480 |
+
wrinkles.get("marionette_lines", 0) +
|
| 481 |
+
wrinkles.get("lip_lines", 0)) / 3
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
# Categorize by type
|
| 485 |
+
wrinkle_types = {
|
| 486 |
+
"dynamic": wrinkles.get("dynamic_wrinkles", 0),
|
| 487 |
+
"static": wrinkles.get("static_wrinkles", 0),
|
| 488 |
+
"predominant_type": "static" if wrinkles.get("static_wrinkles", 0) > wrinkles.get("dynamic_wrinkles", 0) else "dynamic"
|
| 489 |
+
}
|
| 490 |
+
|
| 491 |
+
return {
|
| 492 |
+
"by_location": {
|
| 493 |
+
"upper_face": upper_face,
|
| 494 |
+
"eye_area": eye_area,
|
| 495 |
+
"lower_face": lower_face
|
| 496 |
+
},
|
| 497 |
+
"by_type": wrinkle_types,
|
| 498 |
+
"severity": {
|
| 499 |
+
"depth": wrinkles.get("depth", 0),
|
| 500 |
+
"overall": wrinkles.get("overall_severity", 0)
|
| 501 |
+
},
|
| 502 |
+
"most_affected_area": max(
|
| 503 |
+
[("upper_face", upper_face["average"]),
|
| 504 |
+
("eye_area", eye_area["average"]),
|
| 505 |
+
("lower_face", lower_face["average"])],
|
| 506 |
+
key=lambda x: x[1]
|
| 507 |
+
)[0]
|
| 508 |
+
}
|
| 509 |
+
|
| 510 |
+
|
| 511 |
+
def get_pores_breakdown(image_path: str) -> Optional[Dict[str, Any]]:
|
| 512 |
+
"""
|
| 513 |
+
Get detailed breakdown of pores by zone and characteristics
|
| 514 |
+
"""
|
| 515 |
+
pores = get_pores_factors(image_path)
|
| 516 |
+
if not pores:
|
| 517 |
+
return None
|
| 518 |
+
|
| 519 |
+
# Analyze by facial zone
|
| 520 |
+
zones = {
|
| 521 |
+
"t_zone": {
|
| 522 |
+
"prominence": pores.get("t_zone_prominence", 0),
|
| 523 |
+
"severity": "severe" if pores.get("t_zone_prominence", 0) > 0.7
|
| 524 |
+
else "moderate" if pores.get("t_zone_prominence", 0) > 0.4
|
| 525 |
+
else "mild"
|
| 526 |
+
},
|
| 527 |
+
"cheeks": {
|
| 528 |
+
"prominence": pores.get("cheek_prominence", 0),
|
| 529 |
+
"severity": "severe" if pores.get("cheek_prominence", 0) > 0.7
|
| 530 |
+
else "moderate" if pores.get("cheek_prominence", 0) > 0.4
|
| 531 |
+
else "mild"
|
| 532 |
+
}
|
| 533 |
+
}
|
| 534 |
+
|
| 535 |
+
# Overall pore characteristics
|
| 536 |
+
characteristics = {
|
| 537 |
+
"size": pores.get("size", 0),
|
| 538 |
+
"visibility": pores.get("visibility", 0),
|
| 539 |
+
"enlarged_percentage": pores.get("enlarged_pores", 0) * 100,
|
| 540 |
+
"clogging_level": pores.get("clogged_pores", 0)
|
| 541 |
+
}
|
| 542 |
+
|
| 543 |
+
# Recommendations based on severity
|
| 544 |
+
severity_score = (pores.get("visibility", 0) + pores.get("size", 0)) / 2
|
| 545 |
+
|
| 546 |
+
return {
|
| 547 |
+
"by_zone": zones,
|
| 548 |
+
"characteristics": characteristics,
|
| 549 |
+
"texture_impact": pores.get("texture_roughness", 0),
|
| 550 |
+
"overall_severity": "severe" if severity_score > 0.7
|
| 551 |
+
else "moderate" if severity_score > 0.4
|
| 552 |
+
else "mild",
|
| 553 |
+
"most_affected_zone": "t_zone" if zones["t_zone"]["prominence"] > zones["cheeks"]["prominence"] else "cheeks"
|
| 554 |
+
}
|
| 555 |
+
|
| 556 |
+
|
| 557 |
# ==================== USAGE STATISTICS ====================
|
| 558 |
|
| 559 |
def get_usage_stats() -> Dict[str, Any]:
|
|
|
|
| 587 |
# ==================== EXAMPLE USAGE ====================
|
| 588 |
|
| 589 |
if __name__ == "__main__":
|
| 590 |
+
# Example 1: Get complete analysis with pores and wrinkles
|
| 591 |
+
print("=" * 70)
|
| 592 |
+
print("COMPLETE SKIN ANALYSIS (INCLUDING PORES & WRINKLES)")
|
| 593 |
+
print("=" * 70)
|
| 594 |
|
| 595 |
image_path = "path/to/face_image.jpg"
|
| 596 |
|
|
|
|
| 599 |
|
| 600 |
if analysis:
|
| 601 |
print("\n✓ Analysis successful!")
|
| 602 |
+
print(f"\n{'='*70}")
|
| 603 |
+
print("SCORES (0-100, higher = more concern)")
|
| 604 |
+
print(f"{'='*70}")
|
| 605 |
+
print(f"Hydration Score: {analysis['scores']['hydration']:.1f}/100")
|
| 606 |
+
print(f"Pigmentation Score: {analysis['scores']['pigmentation']:.1f}/100")
|
| 607 |
+
print(f"Acne Score: {analysis['scores']['acne']:.1f}/100")
|
| 608 |
+
print(f"Pores Score: {analysis['scores']['pores']:.1f}/100")
|
| 609 |
+
print(f"Wrinkles Score: {analysis['scores']['wrinkles']:.1f}/100")
|
| 610 |
+
|
| 611 |
+
print(f"\n{'='*70}")
|
| 612 |
+
print("AGE ANALYSIS")
|
| 613 |
+
print(f"{'='*70}")
|
| 614 |
+
print(f"Fitzpatrick Type: {analysis['age_analysis']['fitzpatrick_type']}")
|
| 615 |
+
print(f"Eye Age: {analysis['age_analysis']['eye_age']} years")
|
| 616 |
+
print(f"Skin Age: {analysis['age_analysis']['skin_age']} years")
|
| 617 |
|
| 618 |
# Save to file
|
| 619 |
with open("analysis_result.json", "w") as f:
|
|
|
|
| 622 |
else:
|
| 623 |
print("\n❌ Analysis failed")
|
| 624 |
|
| 625 |
+
# Example 2: Get detailed pores breakdown
|
| 626 |
+
print("\n" + "=" * 70)
|
| 627 |
+
print("DETAILED PORES ANALYSIS")
|
| 628 |
+
print("=" * 70)
|
| 629 |
+
|
| 630 |
+
pores_detail = get_pores_breakdown(image_path)
|
| 631 |
+
if pores_detail:
|
| 632 |
+
print(f"\nMost Affected Zone: {pores_detail['most_affected_zone'].upper()}")
|
| 633 |
+
print(f"Overall Severity: {pores_detail['overall_severity'].upper()}")
|
| 634 |
+
print(f"\nT-Zone Prominence: {pores_detail['by_zone']['t_zone']['prominence']:.2f}")
|
| 635 |
+
print(f"Cheek Prominence: {pores_detail['by_zone']['cheeks']['prominence']:.2f}")
|
| 636 |
+
print(f"Enlarged Pores: {pores_detail['characteristics']['enlarged_percentage']:.1f}%")
|
| 637 |
+
print(f"Pore Size: {pores_detail['characteristics']['size']:.2f}")
|
| 638 |
+
print(f"Visibility: {pores_detail['characteristics']['visibility']:.2f}")
|
| 639 |
+
|
| 640 |
+
# Example 3: Get detailed wrinkles breakdown
|
| 641 |
+
print("\n" + "=" * 70)
|
| 642 |
+
print("DETAILED WRINKLES ANALYSIS")
|
| 643 |
+
print("=" * 70)
|
| 644 |
+
|
| 645 |
+
wrinkles_detail = get_wrinkle_breakdown(image_path)
|
| 646 |
+
if wrinkles_detail:
|
| 647 |
+
print(f"\nMost Affected Area: {wrinkles_detail['most_affected_area'].upper()}")
|
| 648 |
+
print(f"Predominant Type: {wrinkles_detail['by_type']['predominant_type'].upper()}")
|
| 649 |
+
print(f"Overall Severity: {wrinkles_detail['severity']['overall']:.2f}")
|
| 650 |
+
print(f"Average Depth: {wrinkles_detail['severity']['depth']:.2f}")
|
| 651 |
+
|
| 652 |
+
print(f"\nUpper Face (avg): {wrinkles_detail['by_location']['upper_face']['average']:.2f}")
|
| 653 |
+
print(f" - Forehead: {wrinkles_detail['by_location']['upper_face']['forehead_lines']:.2f}")
|
| 654 |
+
print(f" - Frown Lines: {wrinkles_detail['by_location']['upper_face']['frown_lines']:.2f}")
|
| 655 |
+
|
| 656 |
+
print(f"\nEye Area (avg): {wrinkles_detail['by_location']['eye_area']['average']:.2f}")
|
| 657 |
+
print(f" - Crow's Feet: {wrinkles_detail['by_location']['eye_area']['crows_feet']:.2f}")
|
| 658 |
+
print(f" - Under Eye: {wrinkles_detail['by_location']['eye_area']['under_eye_wrinkles']:.2f}")
|
| 659 |
+
|
| 660 |
+
print(f"\nLower Face (avg): {wrinkles_detail['by_location']['lower_face']['average']:.2f}")
|
| 661 |
+
print(f" - Nasolabial: {wrinkles_detail['by_location']['lower_face']['nasolabial_folds']:.2f}")
|
| 662 |
+
print(f" - Marionette: {wrinkles_detail['by_location']['lower_face']['marionette_lines']:.2f}")
|
| 663 |
+
|
| 664 |
+
# Example 4: Using legacy functions (still works, uses same cached data)
|
| 665 |
+
print("\n" + "=" * 70)
|
| 666 |
print("USING LEGACY FUNCTIONS")
|
| 667 |
+
print("=" * 70)
|
| 668 |
|
| 669 |
hydration = get_hydration_factors(image_path)
|
| 670 |
print(f"\nHydration factors: {hydration}")
|
|
|
|
| 672 |
pigmentation = get_pigmentation_factors(image_path)
|
| 673 |
print(f"Pigmentation factors: {pigmentation}")
|
| 674 |
|
| 675 |
+
pores = get_pores_factors(image_path)
|
| 676 |
+
print(f"Pores factors: {pores}")
|
| 677 |
+
|
| 678 |
+
wrinkles = get_wrinkles_factors(image_path)
|
| 679 |
+
print(f"Wrinkles factors: {wrinkles}")
|
| 680 |
+
|
| 681 |
+
# Example 5: Check usage statistics
|
| 682 |
+
print("\n" + "=" * 70)
|
| 683 |
print("API USAGE STATISTICS")
|
| 684 |
+
print("=" * 70)
|
| 685 |
stats = get_usage_stats()
|
| 686 |
print(json.dumps(stats, indent=2))
|