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}") @app.route("/") 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)" } }) @app.route("/analyze", methods=["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) @app.route("/analyze/detailed", methods=["POST"]) 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) @app.route("/stats", methods=["GET"]) 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 @app.route("/cache/clear", methods=["POST"]) 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 @app.route("/health", methods=["GET"]) def health_check(): """ Health check endpoint for monitoring """ return jsonify({ "status": "healthy", "service": "Skin Analysis API", "version": "2.0" }) @app.errorhandler(413) 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 @app.errorhandler(500) 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 )