persee-tech commited on
Commit
0a5c8ec
·
verified ·
1 Parent(s): 6728a2e

Update backend/main.py

Browse files
Files changed (1) hide show
  1. backend/main.py +99 -109
backend/main.py CHANGED
@@ -1,7 +1,7 @@
1
  import os
2
  import socketio
3
  import uvicorn
4
- from fastapi import FastAPI
5
  from fastapi.middleware.cors import CORSMiddleware
6
  import asyncio
7
  import base64
@@ -10,166 +10,156 @@ import numpy as np
10
  import random
11
  from deepface import DeepFace
12
  from supabase import create_client, Client
 
13
 
14
- # --- 1. CONFIGURATION SUPABASE ---
15
  SUPABASE_URL = os.getenv("SUPABASE_URL", "https://gwjrwejdjpctizolfkcz.supabase.co")
16
  SUPABASE_KEY = os.getenv("SUPABASE_KEY")
17
 
18
  try:
19
  if SUPABASE_KEY:
20
  supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
21
- print("☁️ Connecté à Supabase")
22
- else:
23
- print("⚠️ ATTENTION: SUPABASE_KEY manquante !")
24
- except:
25
- pass
26
-
27
- # --- 2. SERVER SETUP ---
28
- def force_allow_origins(origin, environ, **kwargs):
29
- return True
30
-
31
- sio = socketio.AsyncServer(
32
- async_mode='asgi',
33
- cors_allowed_origins=force_allow_origins,
34
- logger=False, # On réduit les logs pour la performance
35
- engineio_logger=False,
36
- always_connect=True
37
- )
38
 
 
 
39
  app = FastAPI()
40
- app.add_middleware(
41
- CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"],
42
- )
43
  socket_app = socketio.ASGIApp(sio, app)
44
 
45
- # --- 3. VARIABLES GLOBALES ---
46
- # On stocke l'état pour chaque client connecté (sid)
47
- sessions_data = {}
 
 
 
48
 
49
  def get_default_kpis():
50
  return {
51
  "engagement": 0, "satisfaction": 50, "trust": 50, "loyalty": 50, "opinion": 50,
52
- "lbl_eng": "En attente...", "lbl_sat": "Analyse..."
53
  }
54
 
55
- # --- 4. LOGIQUE MÉTIER ---
56
- def analyze_image_task(frame):
57
- # Cette fonction sera exécutée dans un thread séparé pour ne pas bloquer
58
  try:
59
- result = DeepFace.analyze(frame, actions=['emotion'], enforce_detection=False, silent=True)
60
- data = result[0] if isinstance(result, list) else result
61
- emotion = data['dominant_emotion']
62
- region = data['region']
63
- face_coords = {'x': region['x'], 'y': region['y'], 'w': region['w'], 'h': region['h']} if region['w'] > 0 else None
 
 
64
 
65
- # Calcul KPI (Algorithme)
66
- valence = 0.0; arousal = 0.0
67
- if emotion == "happy": valence = 0.8; arousal = 0.6
68
- elif emotion == "sad": valence = -0.6; arousal = 0.2
69
- elif emotion == "angry": valence = -0.7; arousal = 0.8
70
- elif emotion == "fear": valence = -0.7; arousal = 0.8
71
- elif emotion == "surprise": valence = 0.2; arousal = 0.9
72
- else: valence = 0.0; arousal = 0.3 # neutral
73
-
74
  def clamp(n): return max(0, min(100, int(n)))
75
- kpis = {
76
- "engagement": clamp((arousal * 100) + random.uniform(0, 10)),
77
- "satisfaction": clamp(((valence + 1) / 2) * 100),
78
- "trust": clamp(50 + (valence * 20)),
79
- "loyalty": clamp(50 + (valence * 10)),
80
- "opinion": clamp(((valence + 1) / 2) * 100),
81
- "lbl_eng": "Fort 🔥" if arousal > 0.6 else "Moyen",
82
- "lbl_sat": "Positif 😃" if valence > 0.2 else ("Négatif 😡" if valence < -0.2 else "Neutre 😐")
 
 
 
83
  }
84
- return {"emotion": emotion, "face_coords": face_coords, "kpis": kpis}
85
- except Exception as e:
86
- print(f"DeepFace Error: {e}")
87
- return None
88
 
89
- # --- 5. SOCKET EVENTS ---
90
  @sio.event
91
  async def connect(sid, environ):
92
- print(f"✅ Client CONNECTÉ: {sid}")
93
- sessions_data[sid] = {
94
- "is_recording": False, "session_time": 0, "db_id": None,
95
- "last_data": {"emotion": "neutral", "face_coords": None, "kpis": get_default_kpis()},
96
- "is_processing": False # Verrou pour éviter la surcharge
97
  }
98
 
99
  @sio.event
100
  async def disconnect(sid):
101
- if sid in sessions_data: del sessions_data[sid]
102
 
103
  @sio.event
104
  async def start_session(sid, data):
105
- if sid in sessions_data:
106
- sessions_data[sid]["is_recording"] = True
107
- sessions_data[sid]["session_time"] = 0
108
- print(f"▶️ SESSION START pour {sid}")
109
- # Optionnel: Création DB ici
 
 
110
 
111
  @sio.event
112
  async def stop_session(sid):
113
- if sid in sessions_data:
114
- sessions_data[sid]["is_recording"] = False
115
- print(f"⏹️ SESSION STOP pour {sid}")
116
 
117
  @sio.event
118
  async def process_frame(sid, data_uri):
119
- # Si le client n'existe pas ou si le serveur est déjà occupé à traiter une image pour ce client : ON IGNORE
120
- if sid not in sessions_data: return
121
- if sessions_data[sid]["is_processing"]: return
122
-
123
- sessions_data[sid]["is_processing"] = True # On verrouille
124
-
125
  try:
126
- # 1. Décodage Image
127
  encoded_data = data_uri.split(',')[1]
128
  nparr = np.frombuffer(base64.b64decode(encoded_data), np.uint8)
129
  frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
130
-
131
- # 2. Analyse dans un THREAD SÉPARÉ (Magie Asyncio)
132
- # Cela empêche le serveur de se figer
133
- result = await asyncio.to_thread(analyze_image_task, frame)
134
-
135
- # 3. Mise à jour des données si succès
136
- if result:
137
- sessions_data[sid]["last_data"]["emotion"] = result["emotion"]
138
- sessions_data[sid]["last_data"]["face_coords"] = result["face_coords"]
139
- sessions_data[sid]["last_data"]["kpis"] = result["kpis"]
 
 
 
 
 
 
 
140
 
141
  except Exception as e:
142
  print(f"Erreur Frame: {e}")
143
- finally:
144
- sessions_data[sid]["is_processing"] = False # On déverrouille
145
 
146
- # --- 6. BOUCLE D'ENVOI (Le Coeur) ---
 
 
 
 
 
 
 
 
 
147
  async def broadcast_loop():
148
  while True:
149
- await asyncio.sleep(1) # 1 Tick par seconde pour l'horloge
150
 
151
- for sid, session in list(sessions_data.items()):
152
- # Gestion du chrono
153
- if session["is_recording"]:
154
- session["session_time"] += 1
 
 
 
155
 
156
- # Préparation du paquet
157
  payload = {
158
- "emotion": session["last_data"]["emotion"],
159
- "face_coords": session["last_data"]["face_coords"],
160
- "metrics": session["last_data"]["kpis"],
161
- "session_time": session["session_time"],
162
- "is_recording": session["is_recording"]
163
  }
164
-
165
- # Envoi au Frontend
166
- try:
167
- await sio.emit('metrics_update', payload, room=sid)
168
- except:
169
- pass
170
 
171
  if __name__ == "__main__":
172
  @app.on_event("startup")
173
- async def startup():
174
- asyncio.create_task(broadcast_loop())
175
  uvicorn.run(socket_app, host="0.0.0.0", port=7860)
 
1
  import os
2
  import socketio
3
  import uvicorn
4
+ from fastapi import FastAPI, HTTPException
5
  from fastapi.middleware.cors import CORSMiddleware
6
  import asyncio
7
  import base64
 
10
  import random
11
  from deepface import DeepFace
12
  from supabase import create_client, Client
13
+ import time
14
 
15
+ # --- 1. CONFIGURATION ---
16
  SUPABASE_URL = os.getenv("SUPABASE_URL", "https://gwjrwejdjpctizolfkcz.supabase.co")
17
  SUPABASE_KEY = os.getenv("SUPABASE_KEY")
18
 
19
  try:
20
  if SUPABASE_KEY:
21
  supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
22
+ print("☁️ Supabase Connecté")
23
+ except: pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
+ # --- 2. SOCKET IO (Optimisé) ---
26
+ sio = socketio.AsyncServer(async_mode='asgi', cors_allowed_origins='*', logger=False, engineio_logger=False, always_connect=True)
27
  app = FastAPI()
28
+ app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
 
 
29
  socket_app = socketio.ASGIApp(sio, app)
30
 
31
+ # --- 3. MOTEUR LÉGER (Pour le Carré Vert) ---
32
+ # On charge le détecteur de visage ultra-rapide d'OpenCV
33
+ face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
34
+
35
+ # --- 4. ETAT GLOBAL ---
36
+ sessions = {}
37
 
38
  def get_default_kpis():
39
  return {
40
  "engagement": 0, "satisfaction": 50, "trust": 50, "loyalty": 50, "opinion": 50,
41
+ "lbl_eng": "En attente...", "lbl_sat": "Calibration..."
42
  }
43
 
44
+ # --- 5. TÂCHE DE FOND (LOURDE) ---
45
+ def deepface_task(frame):
46
+ # C'est ici que l'IA réfléchit (ça peut prendre 1 à 2 secondes)
47
  try:
48
+ objs = DeepFace.analyze(frame, actions=['emotion'], enforce_detection=False, silent=True)
49
+ data = objs[0] if isinstance(objs, list) else objs
50
+
51
+ # Calculs KPIs simulés basés sur l'émotion réelle
52
+ emo = data['dominant_emotion']
53
+ val = 0.8 if emo == "happy" else (-0.6 if emo in ["sad", "angry", "fear"] else 0.0)
54
+ aro = 0.8 if emo in ["angry", "fear", "surprise"] else 0.3
55
 
 
 
 
 
 
 
 
 
 
56
  def clamp(n): return max(0, min(100, int(n)))
57
+ return {
58
+ "emotion": emo,
59
+ "metrics": {
60
+ "engagement": clamp((aro * 100) + random.uniform(0, 10)),
61
+ "satisfaction": clamp(((val + 1) / 2) * 100),
62
+ "trust": clamp(50 + (val * 20)),
63
+ "loyalty": clamp(50 + (val * 10)),
64
+ "opinion": clamp(((val + 1) / 2) * 100),
65
+ "lbl_eng": "Fort 🔥" if aro > 0.6 else "Moyen",
66
+ "lbl_sat": "Positif 😃" if val > 0.2 else "Neutre 😐"
67
+ }
68
  }
69
+ except: return None
 
 
 
70
 
71
+ # --- 6. ÉVÉNEMENTS ---
72
  @sio.event
73
  async def connect(sid, environ):
74
+ print(f"✅ CONNECTÉ: {sid}")
75
+ sessions[sid] = {
76
+ "is_recording": False, "session_time": 0, "db_id": None,
77
+ "face_coords": None, "emotion": "neutral", "metrics": get_default_kpis(),
78
+ "last_deepface_time": 0
79
  }
80
 
81
  @sio.event
82
  async def disconnect(sid):
83
+ if sid in sessions: del sessions[sid]
84
 
85
  @sio.event
86
  async def start_session(sid, data):
87
+ if sid in sessions:
88
+ sessions[sid]["is_recording"] = True
89
+ sessions[sid]["session_time"] = 0
90
+ try:
91
+ res = supabase.table('sessions').insert({"first_name": data.get('firstName', ''), "last_name": data.get('lastName', ''), "client_id": data.get('clientId', '')}).execute()
92
+ sessions[sid]["db_id"] = res.data[0]['id']
93
+ except: pass
94
 
95
  @sio.event
96
  async def stop_session(sid):
97
+ if sid in sessions: sessions[sid]["is_recording"] = False
 
 
98
 
99
  @sio.event
100
  async def process_frame(sid, data_uri):
101
+ if sid not in sessions: return
102
+
 
 
 
 
103
  try:
104
+ # 1. Décodage Rapide
105
  encoded_data = data_uri.split(',')[1]
106
  nparr = np.frombuffer(base64.b64decode(encoded_data), np.uint8)
107
  frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
108
+
109
+ # 2. DÉTECTION RAPIDE (CARRÉ VERT) - Ça prend 0.01s
110
+ gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
111
+ faces = face_cascade.detectMultiScale(gray, 1.1, 4)
112
+
113
+ if len(faces) > 0:
114
+ (x, y, w, h) = faces[0] # On prend le premier visage
115
+ sessions[sid]["face_coords"] = {'x': int(x), 'y': int(y), 'w': int(w), 'h': int(h)}
116
+ else:
117
+ sessions[sid]["face_coords"] = None # Pas de visage = Pas de carré
118
+
119
+ # 3. ANALYSE LENTE (ÉMOTIONS) - Seulement toutes les 1.5 secondes
120
+ now = time.time()
121
+ if now - sessions[sid]["last_deepface_time"] > 1.5:
122
+ sessions[sid]["last_deepface_time"] = now
123
+ # On lance l'IA en arrière-plan sans bloquer
124
+ asyncio.create_task(run_deepface_background(sid, frame))
125
 
126
  except Exception as e:
127
  print(f"Erreur Frame: {e}")
 
 
128
 
129
+ async def run_deepface_background(sid, frame):
130
+ # Wrapper pour exécuter DeepFace sans bloquer le serveur
131
+ try:
132
+ result = await asyncio.to_thread(deepface_task, frame)
133
+ if result and sid in sessions:
134
+ sessions[sid]["emotion"] = result["emotion"]
135
+ sessions[sid]["metrics"] = result["metrics"]
136
+ except: pass
137
+
138
+ # --- 7. BOUCLE DE D'ENVOI (BROADCAST) ---
139
  async def broadcast_loop():
140
  while True:
141
+ await asyncio.sleep(0.5) # Mise à jour fluide (2 fois par seconde)
142
 
143
+ for sid in list(sessions.keys()):
144
+ if sid not in sessions: continue
145
+ sess = sessions[sid]
146
+
147
+ if sess["is_recording"]:
148
+ sess["session_time"] += 0.5 # On ajoute 0.5s au chrono
149
+ # Sauvegarde DB allégée ici si besoin
150
 
151
+ # On envoie TOUT ce qu'on a (le carré vert est à jour, l'émotion peut dater d'il y a 1s)
152
  payload = {
153
+ "emotion": sess["emotion"],
154
+ "face_coords": sess["face_coords"], # C'est ça qui affiche le carré vert !
155
+ "metrics": sess["metrics"],
156
+ "session_time": int(sess["session_time"]),
157
+ "is_recording": sess["is_recording"]
158
  }
159
+ try: await sio.emit('metrics_update', payload, room=sid)
160
+ except: pass
 
 
 
 
161
 
162
  if __name__ == "__main__":
163
  @app.on_event("startup")
164
+ async def startup(): asyncio.create_task(broadcast_loop())
 
165
  uvicorn.run(socket_app, host="0.0.0.0", port=7860)