Spaces:
Sleeping
Sleeping
| import os | |
| from flask import Flask, request, jsonify | |
| from werkzeug.utils import secure_filename | |
| import uuid | |
| from pathlib import Path | |
| from skin_analysis import ( | |
| get_comprehensive_analysis, | |
| get_pores_breakdown, | |
| get_wrinkle_breakdown, | |
| get_usage_stats, | |
| clear_cache | |
| ) | |
| app = Flask(__name__) | |
| # Configuration | |
| UPLOAD_FOLDER = "temp_uploads" | |
| ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'webp'} | |
| MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB | |
| # Create upload folder if it doesn't exist | |
| Path(UPLOAD_FOLDER).mkdir(parents=True, exist_ok=True) | |
| def allowed_file(filename): | |
| """Check if file extension is allowed""" | |
| return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS | |
| def cleanup_temp_file(filepath): | |
| """Delete temporary file""" | |
| try: | |
| if os.path.exists(filepath): | |
| os.remove(filepath) | |
| except Exception as e: | |
| print(f"Warning: Could not delete temp file {filepath}: {e}") | |
| def home(): | |
| """Health check endpoint""" | |
| return jsonify({ | |
| "status": "Skin Analysis API Running", | |
| "version": "2.0", | |
| "features": [ | |
| "hydration", | |
| "pigmentation", | |
| "acne", | |
| "pores", | |
| "wrinkles", | |
| "age_analysis" | |
| ], | |
| "endpoints": { | |
| "analyze": "/analyze (POST)", | |
| "analyze_detailed": "/analyze/detailed (POST)", | |
| "stats": "/stats (GET)", | |
| "clear_cache": "/cache/clear (POST)" | |
| } | |
| }) | |
| def analyze(): | |
| """ | |
| Main analysis endpoint - returns scores and basic data | |
| """ | |
| # Validate request | |
| if "image" not in request.files: | |
| return jsonify({"error": "No image uploaded"}), 400 | |
| img = request.files["image"] | |
| if img.filename == '': | |
| return jsonify({"error": "No image selected"}), 400 | |
| if not allowed_file(img.filename): | |
| return jsonify({ | |
| "error": f"Invalid file type. Allowed types: {', '.join(ALLOWED_EXTENSIONS)}" | |
| }), 400 | |
| # Check file size | |
| img.seek(0, os.SEEK_END) | |
| file_size = img.tell() | |
| img.seek(0) | |
| if file_size > MAX_FILE_SIZE: | |
| return jsonify({ | |
| "error": f"File too large. Maximum size: {MAX_FILE_SIZE / (1024*1024):.1f}MB" | |
| }), 400 | |
| # Save image with unique filename | |
| filename = secure_filename(img.filename) | |
| unique_filename = f"{uuid.uuid4()}_{filename}" | |
| temp_path = os.path.join(UPLOAD_FOLDER, unique_filename) | |
| try: | |
| img.save(temp_path) | |
| # Perform comprehensive analysis (single API call) | |
| analysis = get_comprehensive_analysis(temp_path) | |
| if not analysis: | |
| return jsonify({ | |
| "error": "Analysis failed. Please try again or contact support." | |
| }), 500 | |
| # Build response | |
| result = { | |
| "success": True, | |
| "scores": { | |
| "hydration": analysis['scores']['hydration'], | |
| "pigmentation": 100 - analysis['scores']['pigmentation'], # Invert (higher = better) | |
| "acne": 100 - analysis['scores']['acne'], # Invert (higher = better) | |
| "pores": 100 - analysis['scores']['pores'], # Invert (higher = better) | |
| "wrinkles": 100 - analysis['scores']['wrinkles'] # Invert (higher = better) | |
| }, | |
| "raw_factors": { | |
| "hydration": analysis['raw_data']['hydration'], | |
| "pigmentation": analysis['raw_data']['pigmentation'], | |
| "acne": analysis['raw_data']['acne'], | |
| "pores": analysis['raw_data']['pores'], | |
| "wrinkles": analysis['raw_data']['wrinkles'] | |
| }, | |
| "age_analysis": { | |
| "fitzpatrick_type": analysis['age_analysis']['fitzpatrick_type'], | |
| "eye_age": analysis['age_analysis']['eye_age'], | |
| "skin_age": analysis['age_analysis']['skin_age'] | |
| }, | |
| "metadata": analysis['metadata'] | |
| } | |
| return jsonify(result) | |
| except Exception as e: | |
| return jsonify({ | |
| "error": f"Server error: {str(e)}" | |
| }), 500 | |
| finally: | |
| # Cleanup temp file | |
| cleanup_temp_file(temp_path) | |
| def analyze_detailed(): | |
| """ | |
| Detailed analysis endpoint - includes breakdowns for pores and wrinkles | |
| """ | |
| # Validate request | |
| if "image" not in request.files: | |
| return jsonify({"error": "No image uploaded"}), 400 | |
| img = request.files["image"] | |
| if img.filename == '': | |
| return jsonify({"error": "No image selected"}), 400 | |
| if not allowed_file(img.filename): | |
| return jsonify({ | |
| "error": f"Invalid file type. Allowed types: {', '.join(ALLOWED_EXTENSIONS)}" | |
| }), 400 | |
| # Save image | |
| filename = secure_filename(img.filename) | |
| unique_filename = f"{uuid.uuid4()}_{filename}" | |
| temp_path = os.path.join(UPLOAD_FOLDER, unique_filename) | |
| try: | |
| img.save(temp_path) | |
| # Perform comprehensive analysis | |
| analysis = get_comprehensive_analysis(temp_path) | |
| if not analysis: | |
| return jsonify({ | |
| "error": "Analysis failed. Please try again." | |
| }), 500 | |
| # Get detailed breakdowns | |
| pores_detail = get_pores_breakdown(temp_path) | |
| wrinkles_detail = get_wrinkle_breakdown(temp_path) | |
| # Build detailed response | |
| result = { | |
| "success": True, | |
| "scores": { | |
| "hydration": analysis['scores']['hydration'], | |
| "pigmentation": 100 - analysis['scores']['pigmentation'], | |
| "acne": 100 - analysis['scores']['acne'], | |
| "pores": 100 - analysis['scores']['pores'], | |
| "wrinkles": 100 - analysis['scores']['wrinkles'] | |
| }, | |
| "raw_factors": { | |
| "hydration": analysis['raw_data']['hydration'], | |
| "pigmentation": analysis['raw_data']['pigmentation'], | |
| "acne": analysis['raw_data']['acne'], | |
| "pores": analysis['raw_data']['pores'], | |
| "wrinkles": analysis['raw_data']['wrinkles'] | |
| }, | |
| "age_analysis": analysis['age_analysis'], | |
| "detailed_analysis": { | |
| "pores": pores_detail, | |
| "wrinkles": wrinkles_detail | |
| }, | |
| "metadata": analysis['metadata'] | |
| } | |
| return jsonify(result) | |
| except Exception as e: | |
| return jsonify({ | |
| "error": f"Server error: {str(e)}" | |
| }), 500 | |
| finally: | |
| cleanup_temp_file(temp_path) | |
| def stats(): | |
| """ | |
| Get API usage statistics | |
| """ | |
| try: | |
| usage_stats = get_usage_stats() | |
| return jsonify({ | |
| "success": True, | |
| "usage": usage_stats | |
| }) | |
| except Exception as e: | |
| return jsonify({ | |
| "error": f"Could not retrieve stats: {str(e)}" | |
| }), 500 | |
| def clear_analysis_cache(): | |
| """ | |
| Clear the analysis cache (admin endpoint) | |
| """ | |
| try: | |
| clear_cache() | |
| return jsonify({ | |
| "success": True, | |
| "message": "Cache cleared successfully" | |
| }) | |
| except Exception as e: | |
| return jsonify({ | |
| "error": f"Could not clear cache: {str(e)}" | |
| }), 500 | |
| def health_check(): | |
| """ | |
| Health check endpoint for monitoring | |
| """ | |
| return jsonify({ | |
| "status": "healthy", | |
| "service": "Skin Analysis API", | |
| "version": "2.0" | |
| }) | |
| def request_entity_too_large(error): | |
| """Handle file too large error""" | |
| return jsonify({ | |
| "error": f"File too large. Maximum size: {MAX_FILE_SIZE / (1024*1024):.1f}MB" | |
| }), 413 | |
| def internal_server_error(error): | |
| """Handle internal server errors""" | |
| return jsonify({ | |
| "error": "Internal server error. Please try again later." | |
| }), 500 | |
| if __name__ == "__main__": | |
| # For production, use a proper WSGI server like gunicorn | |
| # gunicorn -w 4 -b 0.0.0.0:7860 app:app | |
| # Development server | |
| app.run( | |
| host="0.0.0.0", | |
| port=7860, | |
| debug=False # Set to False in production | |
| ) |