somoncef/glamflow-model
0
1from flask import Flask, request, jsonify2from flask_cors import CORS3import numpy as np4import tensorflow.lite as tflite # REMARQUE: tf.lite.Interpreter sera supprimé dans TF 2.20, voir migration: https://ai.google.dev/edge/litert/migration5import cv26from PIL import Image7import io8import os9from functools import wraps10import requests11from dotenv import load_dotenv12 13app = Flask(__name__)14# Configuration CORS spécifique pour le frontend déployé15CORS(app, resources={r"/*": {"origins": ["https://app.glamflow.ma", "https://analysepeaux.vercel.app"]}})16 17# Middleware pour vérifier l'origine des requêtes18def check_origin(f):19 @wraps(f)20 def decorated_function(*args, **kwargs):21 # Ignorer toutes les vérifications en mode debug22 if app.debug:23 return f(*args, **kwargs)24 25 origin = request.headers.get('Origin', '')26 referer = request.headers.get('Referer', '')27 28 # Autoriser les requêtes provenant des domaines autorisés29 if any(domain in origin or domain in referer for domain in ['https://app.glamflow.ma', 'https://analysepeaux.vercel.app']):30 return f(*args, **kwargs)31 else:32 return jsonify({'error': 'Unauthorized origin'}), 40333 return decorated_function34 35# Charger les variables d'environnement36load_dotenv()37GROQ_API_KEY = os.getenv('GROQ_API_KEY')38 39# Image Size40IMG_SIZE = (150, 150)41 42# Load TFLite models globally43models_dir = os.path.join(os.path.dirname(__file__), "saved_models")44interpreter_type = tflite.Interpreter(model_path=os.path.join(models_dir, "skin_type_model.tflite"))45interpreter_disease = tflite.Interpreter(model_path=os.path.join(models_dir, "skin_disease_model.tflite"))46interpreter_type.allocate_tensors()47interpreter_disease.allocate_tensors()48 49# Label mappings50skin_type_labels = {0: 'Sèche', 1: 'Normale', 2: 'Grasse'}51skin_disease_labels = {0: 'Acné', 1: 'Mélanome', 2: 'Psoriasis', 3: 'Rosacée', 4: 'Vitiligo'}52 53# Recommendations pour les différents types de peau54skin_type_recommendations = {55 "Sèche": "💧 Hydratez bien, évitez les douches chaudes et utilisez une crème hydratante épaisse.",56 "Grasse": "🧴 Utilisez des soins légers et sans huile. Nettoyez deux fois par jour.",57 "Normale": "🌿 Maintenez une routine équilibrée, utilisez un écran solaire et hydratez bien."58}59 60# Recommendations pour les différentes maladies de peau61disease_treatment = {62 "Acné": "🔬 Utilisez de l'acide salicylique, évitez de toucher votre visage et nettoyez bien.",63 "Mélanome": "🛀 Utilisez des crèmes sans parfum, évitez les allergènes et gérez votre stress.",64 "Psoriasis": "🩺 Hydratez souvent, utilisez des traitements médicamenteux et évitez les déclencheurs.",65 "Rosacée": "☀️ Évitez les aliments épicés, utilisez des soins doux et portez un écran solaire.",66 "Vitiligo": "🌞 Utilisez des sérums éclaircissants, évitez l'exposition au soleil et appliquez un SPF 50+."67}68 69# Charger le détecteur de visage Haar Cascade70face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')71 72def detect_face(image_array):73 """Détecte si l'image contient un visage"""74 gray = cv2.cvtColor(image_array, cv2.COLOR_RGB2GRAY)75 faces = face_cascade.detectMultiScale(gray, 1.1, 4)76 return len(faces) > 077 78def predict_skin_condition(image_array):79 img = cv2.resize(image_array, IMG_SIZE)80 img = img / 255.081 img = np.expand_dims(img, axis=0).astype(np.float32)82 83 # Skin Type Prediction84 input_tensor_index = interpreter_type.get_input_details()[0]['index']85 interpreter_type.set_tensor(input_tensor_index, img)86 interpreter_type.invoke()87 output_tensor_index = interpreter_type.get_output_details()[0]['index']88 type_pred = interpreter_type.get_tensor(output_tensor_index)89 90 # Skin Disease Prediction91 input_tensor_index = interpreter_disease.get_input_details()[0]['index']92 interpreter_disease.set_tensor(input_tensor_index, img)93 interpreter_disease.invoke()94 output_tensor_index = interpreter_disease.get_output_details()[0]['index']95 disease_pred = interpreter_disease.get_tensor(output_tensor_index)96 97 # Results processing98 skin_type = skin_type_labels[np.argmax(type_pred)]99 skin_disease = skin_disease_labels[np.argmax(disease_pred)]100 101 type_confidence = float(np.max(type_pred) * 100)102 disease_confidence = float(np.max(disease_pred) * 100)103 104 type_probabilities = {skin_type_labels[i]: float(prob * 100) for i, prob in enumerate(type_pred[0])}105 disease_probabilities = {skin_disease_labels[i]: float(prob * 100) for i, prob in enumerate(disease_pred[0])}106 107 return {108 'skin_type': {109 'prediction': skin_type,110 'confidence': round(type_confidence, 2),111 'probabilities': {k: round(v, 2) for k, v in type_probabilities.items()}112 },113 'skin_disease': {114 'prediction': skin_disease,115 'confidence': round(disease_confidence, 2),116 'probabilities': {k: round(v, 2) for k, v in disease_probabilities.items()}117 },118 'recommendations': {119 'skin_type': skin_type_recommendations[skin_type],120 'skin_disease': disease_treatment[skin_disease]121 }122 }123 124def get_ai_analysis(analysis_results):125 """Obtenir une analyse détaillée via Groq LLM"""126 headers = {127 "Content-Type": "application/json",128 "Authorization": f"Bearer {GROQ_API_KEY}"129 }130 131 prompt = f"""Analyse ces résultats dermatologiques et fournis une réponse structurée avec exactement les 3 sections suivantes, séparées par [SECTION]:132 133Type de peau: {analysis_results['skin_type']['prediction']} (confiance: {analysis_results['skin_type']['confidence']}%)134Condition détectée: {analysis_results['skin_disease']['prediction']} (confiance: {analysis_results['skin_disease']['confidence']}%)135 136Distributions des probabilités:137Types de peau: {analysis_results['skin_type']['probabilities']}138Conditions: {analysis_results['skin_disease']['probabilities']}139 140Réponds en utilisant exactement ce format:141[SECTION]142Interprétation détaillée des résultats dermatologiques143[SECTION]144Conseils supplémentaires personnalisés145[SECTION]146Recommandations pour une routine quotidienne"""147 148 try:149 response = requests.post(150 "https://api.groq.com/openai/v1/chat/completions",151 headers=headers,152 json={153 "model": "llama-3.3-70b-versatile",154 "messages": [{155 "role": "user",156 "content": prompt157 }]158 }159 )160 161 if response.status_code == 200:162 content = response.json()["choices"][0]["message"]["content"]163 164 # Diviser la réponse en sections165 sections = content.split("[SECTION]")166 sections = [s.strip() for s in sections if s.strip()] # Nettoyer les sections167 168 # Structurer la réponse169 if len(sections) >= 3:170 return {171 "interpretation": sections[0],172 "conseils": sections[1],173 "routine": sections[2]174 }175 else:176 return {177 "interpretation": "Analyse non disponible",178 "conseils": "Conseils non disponibles",179 "routine": "Routine non disponible"180 }181 182 else:183 return {184 "interpretation": "Analyse non disponible",185 "conseils": "Conseils non disponibles",186 "routine": "Routine non disponible"187 }188 189 except Exception as e:190 print(f"Erreur lors de l'appel à Groq: {str(e)}")191 return {192 "interpretation": "Erreur lors de l'analyse",193 "conseils": "Conseils non disponibles",194 "routine": "Routine non disponible"195 }196 197@app.route('/')198@check_origin199def home():200 return jsonify({201 "message": "GlamFlow AI API - Services disponibles:",202 "endpoints": [203 "/predict - Analyse complète de la peau (type et maladie)",204 "/health - Vérification de l'état des modèles",205 "/preload - Préchargement des modèles"206 ]207 }), 200208 209@app.route('/predict', methods=['POST'])210@check_origin211def predict():212 if 'file' not in request.files:213 return jsonify({'error': 'Aucun fichier fourni'}), 400214 215 file = request.files['file']216 if file.filename == '':217 return jsonify({'error': 'Aucun fichier sélectionné'}), 400218 219 if file:220 try:221 # Lire l'image et la convertir en format compatible222 image_bytes = file.read()223 image = Image.open(io.BytesIO(image_bytes)).convert("RGB")224 image_array = np.array(image)225 226 # Vérifier la présence d'un visage227 if not detect_face(image_array):228 return jsonify({229 'error': 'Aucun visage n\'est détecté dans l\'image. Veuillez choisir une photo claire de votre visage pour effectuer l\'analyse.'230 }), 400231 232 # Analyser l'image avec les deux modèles233 results = predict_skin_condition(image_array)234 235 # Obtenir l'analyse IA supplémentaire236 ai_analysis = get_ai_analysis(results)237 results['ai_analysis'] = ai_analysis238 239 return jsonify(results), 200240 except Exception as e:241 return jsonify({'error': str(e)}), 500242 243@app.route('/preload', methods=['GET'])244@check_origin245def preload_models():246 """Précharge les modèles pour éviter les délais lors de la première prédiction"""247 return jsonify({'status': 'Models preloaded successfully'}), 200248 249@app.route('/health', methods=['GET'])250@check_origin251def health_check():252 models_loaded = {253 'skin_type_model': True,254 'skin_disease_model': True255 }256 257 return jsonify({258 'status': 'healthy', 259 'models': models_loaded260 }), 200261 262if __name__ == '__main__':263 app.run(host='0.0.0.0', port=5000, debug=True)