TerenceG commited on
Commit
11f49be
·
verified ·
1 Parent(s): 77fb0e6

Update handler.py

Browse files
Files changed (1) hide show
  1. handler.py +122 -326
handler.py CHANGED
@@ -1,282 +1,89 @@
1
- from typing import Any, Dict
2
  import torch
3
- from torchvision import transforms
4
- from PIL import Image, ImageDraw, ImageOps
5
  import base64
6
  import io
7
  import numpy as np
8
- from transformers import AutoModelForImageClassification, AutoImageProcessor, AutoConfig
9
  import torch.nn.functional as F
10
  import json
11
  import re
12
  import gc
13
- import sys
14
  import traceback
 
 
15
 
16
- # Gestion des dépendances optionnelles
17
- HAS_MATPLOTLIB = False
18
- try:
19
- import matplotlib.pyplot as plt
20
- import matplotlib.cm as cm
21
- HAS_MATPLOTLIB = True
22
- print("✅ Matplotlib disponible - Grad-CAM avancé activé")
23
- except ImportError:
24
- print("⚠️ Matplotlib non disponible - Utilisation de PIL pour Grad-CAM")
25
 
26
- class OptimizedGradCAM:
27
- """Version optimisée de Grad-CAM avec nettoyage automatique"""
28
-
29
- def __init__(self, model, target_layer):
30
- self.model = model
31
- self.target_layer = target_layer
32
- self.gradients = None
33
- self.activations = None
34
- self.hooks = []
35
-
36
- # Enregistrer les hooks avec nettoyage automatique
37
- if target_layer is not None:
38
- hook1 = self.target_layer.register_backward_hook(self.save_gradients)
39
- hook2 = self.target_layer.register_forward_hook(self.save_activations)
40
- self.hooks = [hook1, hook2]
41
- else:
42
- print("⚠️ Aucune couche cible trouvée - Grad-CAM désactivé")
43
-
44
- def save_gradients(self, module, grad_input, grad_output):
45
- if grad_output[0] is not None:
46
- self.gradients = grad_output[0].detach()
47
-
48
- def save_activations(self, module, input, output):
49
- self.activations = output.detach()
50
-
51
- def generate_cam(self, input_tensor, class_idx=None):
52
- """Génère la carte de saillance Grad-CAM"""
53
- if self.target_layer is None:
54
- return None
55
-
56
- try:
57
- # Forward pass
58
- output = self.model(input_tensor)
59
-
60
- if class_idx is None:
61
- class_idx = output.logits.argmax(dim=1).item()
62
-
63
- # Backward pass
64
- self.model.zero_grad()
65
- output.logits[0, class_idx].backward(retain_graph=False)
66
-
67
- if self.gradients is None or self.activations is None:
68
- print("⚠️ Gradients ou activations manquants")
69
- return None
70
-
71
- # Generate CAM
72
- gradients = self.gradients[0] # (C, H, W)
73
- activations = self.activations[0] # (C, H, W)
74
-
75
- # Moyenne globale des gradients
76
- weights = torch.mean(gradients, dim=(1, 2)) # (C,)
77
-
78
- # CAM = somme pondérée des activations
79
- cam = torch.zeros(activations.shape[1:], device=activations.device) # (H, W)
80
- for i, w in enumerate(weights):
81
- cam += w * activations[i, :, :]
82
-
83
- # ReLU et normalisation
84
- cam = F.relu(cam)
85
- if cam.max() > 0:
86
- cam = cam / cam.max()
87
-
88
- return cam.detach().cpu().numpy()
89
-
90
- except Exception as e:
91
- print(f"⚠️ Erreur lors de la génération CAM: {e}")
92
- return None
93
- finally:
94
- # Nettoyage explicite
95
- if self.gradients is not None:
96
- self.gradients = None
97
- if self.activations is not None:
98
- self.activations = None
99
-
100
- def cleanup(self):
101
- """Nettoie les hooks et libère la mémoire"""
102
- for hook in self.hooks:
103
- try:
104
- hook.remove()
105
- except:
106
- pass
107
- self.hooks = []
108
- self.gradients = None
109
- self.activations = None
110
-
111
- def __del__(self):
112
- """Nettoyage automatique lors de la destruction"""
113
- self.cleanup()
114
-
115
- def get_last_conv_layer_safe(model):
116
- """Trouve la dernière couche de convolution de manière sécurisée"""
117
- try:
118
- last_conv = None
119
- conv_layers = []
120
-
121
- for name, module in model.named_modules():
122
- if isinstance(module, (torch.nn.Conv2d, torch.nn.AdaptiveAvgPool2d)):
123
- conv_layers.append((name, module))
124
-
125
- if conv_layers:
126
- last_conv = conv_layers[-1][1]
127
- print(f"✅ Couche cible trouvée: {conv_layers[-1][0]}")
128
- else:
129
- print("⚠️ Aucune couche de convolution trouvée")
130
-
131
- return last_conv
132
- except Exception as e:
133
- print(f"⚠️ Erreur lors de la recherche de couche: {e}")
134
- return None
135
-
136
- def create_gradcam_overlay_pil(original_image, cam_array):
137
- """Crée une superposition Grad-CAM en utilisant PIL (sans matplotlib)"""
138
- try:
139
- if cam_array is None:
140
- return None
141
-
142
- # Convertir CAM en image
143
- cam_normalized = (cam_array * 255).astype(np.uint8)
144
- cam_img = Image.fromarray(cam_normalized, mode='L')
145
-
146
- # Redimensionner au format de l'image originale
147
- cam_resized = cam_img.resize(original_image.size, Image.Resampling.LANCZOS)
148
-
149
- # Créer une heatmap colorée (rouge pour les zones importantes)
150
- # Convertir en RGB et appliquer une colormap simple
151
- cam_array_resized = np.array(cam_resized)
152
-
153
- # Créer une colormap simple (bleu -> rouge)
154
- heatmap = np.zeros((cam_array_resized.shape[0], cam_array_resized.shape[1], 3), dtype=np.uint8)
155
- heatmap[:, :, 0] = cam_array_resized # Rouge
156
- heatmap[:, :, 2] = 255 - cam_array_resized # Bleu inversé
157
-
158
- heatmap_img = Image.fromarray(heatmap, 'RGB')
159
-
160
- # Mélanger avec l'image originale
161
- blended = Image.blend(original_image.convert('RGB'), heatmap_img, alpha=0.4)
162
-
163
- # Convertir en base64
164
- buffer = io.BytesIO()
165
- blended.save(buffer, format='PNG', optimize=True)
166
- buffer.seek(0)
167
-
168
- return base64.b64encode(buffer.getvalue()).decode('utf-8')
169
-
170
- except Exception as e:
171
- print(f"⚠️ Erreur lors de la création de l'overlay PIL: {e}")
172
- return None
173
-
174
- def create_gradcam_overlay_matplotlib(original_image, cam_array):
175
- """Crée une superposition Grad-CAM en utilisant matplotlib (si disponible)"""
176
- try:
177
- if not HAS_MATPLOTLIB or cam_array is None:
178
- return None
179
-
180
- # Redimensionner CAM
181
- cam_resized = np.array(Image.fromarray((cam_array * 255).astype(np.uint8)).resize(
182
- original_image.size, Image.Resampling.LANCZOS
183
- )) / 255.0
184
-
185
- # Créer la figure
186
- fig, ax = plt.subplots(figsize=(8, 8), dpi=100)
187
- ax.imshow(original_image)
188
- ax.imshow(cam_resized, cmap='jet', alpha=0.5)
189
- ax.axis('off')
190
-
191
- # Sauvegarder en base64
192
- buffer = io.BytesIO()
193
- plt.savefig(buffer, format='png', bbox_inches='tight', pad_inches=0, dpi=100)
194
- plt.close(fig) # Important: fermer la figure
195
- buffer.seek(0)
196
-
197
- return base64.b64encode(buffer.getvalue()).decode('utf-8')
198
-
199
- except Exception as e:
200
- print(f"⚠️ Erreur lors de la création de l'overlay matplotlib: {e}")
201
- if 'fig' in locals():
202
- plt.close(fig)
203
- return None
204
 
205
  class EndpointHandler:
206
  def __init__(self, path=""):
207
- print("🚀 VerifAI Handler V2 FIXED - Initialisation")
208
- print("📋 Modèle: haywoodsloan/ai-image-detector-deploy (Version Corrigée)")
209
 
210
  self.model = None
211
  self.processor = None
212
- self.grad_cam = None
213
  self.model_labels = {}
 
 
 
 
 
214
 
215
  try:
216
- # Vérification de la disponibilité du modèle
217
- self.model_name = "haywoodsloan/ai-image-detector-deploy"
218
 
219
- if not self._verify_model_exists():
220
- raise Exception(f"Modèle {self.model_name} non accessible")
 
 
 
221
 
222
- # Chargement du modèle avec gestion d'erreurs
223
- print("🔄 Chargement du modèle...")
224
- self.processor = AutoImageProcessor.from_pretrained(self.model_name)
225
  self.model = AutoModelForImageClassification.from_pretrained(
226
  self.model_name,
227
- torch_dtype=torch.float32 # Force float32 pour la compatibilité
 
 
228
  )
229
  self.model.eval()
230
 
231
- # Configuration Grad-CAM sécurisée
232
- target_layer = get_last_conv_layer_safe(self.model)
233
- if target_layer is not None:
234
- self.grad_cam = OptimizedGradCAM(self.model, target_layer)
235
- print("✅ Grad-CAM activé")
236
- else:
237
- print("⚠️ Grad-CAM désactivé (aucune couche compatible)")
238
-
239
- # Récupérer les labels
240
  if hasattr(self.model.config, 'id2label'):
241
  self.model_labels = self.model.config.id2label
242
  else:
243
- self.model_labels = {0: "Real", 1: "Fake"} # Fallback
244
 
245
- print("✅ Modèle chargé avec succès")
246
- print(f"📋 Étiquettes du modèle: {self.model_labels}")
247
- print("🎯 VerifAI Handler V2 FIXED prêt!")
248
 
249
- except Exception as e:
250
- print(f"❌ Erreur lors de l'initialisation: {e}")
251
- print(f"🔍 Traceback: {traceback.format_exc()}")
252
- # Ne pas faire échouer l'initialisation, mais signaler l'erreur
253
  self.model = None
254
  self.processor = None
255
-
256
- def _verify_model_exists(self):
257
- """Vérifie que le modèle existe avant de le charger"""
258
- try:
259
- config = AutoConfig.from_pretrained(self.model_name)
260
- print(f"✅ Modèle {self.model_name} vérifié")
261
- return True
262
  except Exception as e:
263
- print(f"❌ Modèle {self.model_name} non accessible: {e}")
264
- return False
 
 
 
265
 
266
  def _normalize_label(self, label: str) -> str:
267
- """Normalise les étiquettes pour qu'elles soient cohérentes."""
268
  if not isinstance(label, str):
269
  label = str(label)
270
-
271
  label_lower = label.lower()
272
- if re.search(r'real|human|authentic', label_lower):
273
  return "Human"
274
- if re.search(r'fake|generated|ai|artificial', label_lower):
275
  return "AI Generated"
276
  return "Unknown"
277
 
278
  def _cleanup_memory(self):
279
- """Nettoie la mémoire explicitement"""
280
  try:
281
  if torch.cuda.is_available():
282
  torch.cuda.empty_cache()
@@ -285,49 +92,60 @@ class EndpointHandler:
285
  pass
286
 
287
  def __call__(self, data):
288
- # Vérification de l'état du handler
289
  if self.model is None or self.processor is None:
290
  return {
291
  "status": "error",
292
- "error": "Handler non initialisé correctement",
293
  "prediction": 0,
294
  "predicted_class_name": "Error",
295
  "confidence": 0.0,
296
  "class_probabilities": {"Human": 0.0, "AI Generated": 0.0},
297
  "cam_image": None,
298
- "version": "2.0-fixed",
299
- "handler_name": "VerifAI Handler V2 FIXED"
300
  }
301
 
 
 
 
 
302
  try:
303
- # Traitement de l'image avec validation
 
 
304
  image_data = data.get("inputs") or data
305
  if not image_data:
306
- raise ValueError("Aucune donnée d'image fournie")
307
 
308
- # Décodage sécurisé de l'image
309
  try:
 
 
 
 
310
  image_bytes = base64.b64decode(image_data)
311
  image = Image.open(io.BytesIO(image_bytes))
312
 
313
- # Validation et conversion
314
  if image.mode != 'RGB':
315
  image = image.convert('RGB')
316
-
317
- # Validation de la taille
318
- if image.size[0] * image.size[1] > 4096 * 4096:
319
- image = image.resize((1024, 1024), Image.Resampling.LANCZOS)
320
- print("⚠️ Image redimensionnée pour éviter les problèmes de mémoire")
321
 
322
  except Exception as e:
323
- raise ValueError(f"Erreur lors du décodage de l'image: {e}")
324
 
325
- # Prédiction avec gestion d'erreurs
326
- print("🔄 VerifAI V2 FIXED - Analyse en cours...")
327
 
 
328
  try:
329
  inputs = self.processor(image, return_tensors="pt")
330
 
 
331
  with torch.no_grad():
332
  outputs = self.model(**inputs)
333
  logits = outputs.logits
@@ -335,90 +153,75 @@ class EndpointHandler:
335
  predicted_class_id = logits.argmax().item()
336
 
337
  except Exception as e:
338
- raise RuntimeError(f"Erreur lors de l'inférence: {e}")
339
 
340
- # Traitement des résultats
341
  class_probs = {}
342
  for class_id, prob in enumerate(probabilities):
343
- label_str = self.model_labels.get(class_id, f"Class {class_id}")
344
- normalized_label = self._normalize_label(label_str)
345
- if normalized_label != "Unknown":
346
- class_probs[normalized_label] = float(prob)
 
347
 
348
- # S'assurer que les deux classes existent
349
  class_probs.setdefault("Human", 0.0)
350
  class_probs.setdefault("AI Generated", 0.0)
351
 
352
  prediction_label = self._normalize_label(self.model_labels.get(predicted_class_id, "Unknown"))
353
  confidence = class_probs.get(prediction_label, 0.0)
354
-
355
- # Déterminer l'ID de prédiction pour la compatibilité
356
  prediction_id = 1 if prediction_label == "AI Generated" else 0
357
 
358
- print(f"🔍 VerifAI V2 FIXED Résultat: {prediction_label} (confiance: {confidence:.3f})")
359
-
360
- # Génération du Grad-CAM avec fallback
361
- cam_image_b64 = None
362
- if self.grad_cam is not None:
363
- try:
364
- print("🎨 Génération du Grad-CAM...")
365
- cam = self.grad_cam.generate_cam(inputs['pixel_values'], predicted_class_id)
366
-
367
- if cam is not None:
368
- # Essayer matplotlib d'abord, puis PIL
369
- if HAS_MATPLOTLIB:
370
- cam_image_b64 = create_gradcam_overlay_matplotlib(image, cam)
371
-
372
- if cam_image_b64 is None:
373
- cam_image_b64 = create_gradcam_overlay_pil(image, cam)
374
-
375
- if cam_image_b64:
376
- print("✅ Grad-CAM généré avec succès")
377
- else:
378
- print("⚠️ Échec de la génération Grad-CAM")
379
-
380
- except Exception as e:
381
- print(f"⚠️ Erreur Grad-CAM: {e}")
382
- cam_image_b64 = None
383
 
384
- # Nettoyage mémoire
385
  self._cleanup_memory()
386
 
387
- # Construction de la réponse compatible
388
  return {
389
  "status": "success",
390
  "prediction": prediction_id,
391
  "predicted_class_name": prediction_label,
392
  "confidence": confidence,
393
  "class_probabilities": class_probs,
394
- "cam_image": cam_image_b64,
395
  "model_info": {
396
  "model_name": self.model_name,
397
- "handler_version": "verifai-v2-fixed",
398
- "precision_mode": "high",
399
  "raw_prediction_id": predicted_class_id,
400
  "raw_labels": self.model_labels,
401
- "grad_cam_method": "matplotlib" if HAS_MATPLOTLIB else "pil"
402
  },
403
- "reliability": "TRÈS ÉLEVÉE",
404
- "version": "2.0-fixed",
405
- "handler_name": "VerifAI Handler V2 FIXED",
406
- "deployment_note": "VERIFAI HANDLER V2 FIXED - PRODUCTION READY",
407
- "fixes_applied": [
408
- "Gestion d'erreurs robuste",
409
- "Fallback PIL pour Grad-CAM",
410
- "Nettoyage mémoire automatique",
411
- "Validation d'entrée renforcée"
412
- ]
413
  }
414
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
  except Exception as e:
416
- print(f"❌ Erreur dans VerifAI Handler V2 FIXED: {e}")
417
  print(f"🔍 Traceback: {traceback.format_exc()}")
418
 
419
- # Nettoyage en cas d'erreur
420
- self._cleanup_memory()
421
-
422
  return {
423
  "status": "error",
424
  "error": str(e),
@@ -427,52 +230,45 @@ class EndpointHandler:
427
  "confidence": 0.0,
428
  "class_probabilities": {"Human": 0.0, "AI Generated": 0.0},
429
  "cam_image": None,
430
- "version": "2.0-fixed",
431
- "handler_name": "VerifAI Handler V2 FIXED",
432
  "error_details": {
433
  "error_type": type(e).__name__,
434
- "traceback": traceback.format_exc()[-500:] # Dernières 500 chars
435
  }
436
  }
437
-
438
- def __del__(self):
439
- """Nettoyage lors de la destruction de l'instance"""
440
- try:
441
- if hasattr(self, 'grad_cam') and self.grad_cam is not None:
442
- self.grad_cam.cleanup()
443
  self._cleanup_memory()
444
- except:
445
- pass
446
 
447
- # Test de fonctionnement si exécuté directement
448
  if __name__ == "__main__":
449
- print("🧪 TEST DU HANDLER VERIFAI V2 FIXED")
450
  print("=" * 50)
451
 
452
  try:
453
- # Initialisation
454
  handler = EndpointHandler()
455
 
456
  if handler.model is not None:
457
- print("✅ Initialisation réussie")
458
 
459
- # Test avec une image simple
460
- print("🔄 Test avec image de base...")
461
  test_img = Image.new('RGB', (224, 224), color='red')
462
  buffer = io.BytesIO()
463
  test_img.save(buffer, format='JPEG')
464
  test_data = base64.b64encode(buffer.getvalue()).decode('utf-8')
465
 
 
466
  result = handler({"inputs": test_data})
467
- print(f"📊 Résultat: {result['status']}")
 
468
  if result['status'] == 'success':
469
  print(f"🎯 Prédiction: {result['predicted_class_name']} ({result['confidence']:.3f})")
470
- print("✅ Handler fonctionnel!")
471
  else:
472
  print(f"❌ Erreur: {result.get('error', 'Inconnue')}")
473
  else:
474
- print("❌ Échec de l'initialisation")
475
 
476
  except Exception as e:
477
- print(f"❌ Erreur de test: {e}")
478
- print(f"🔍 Traceback: {traceback.format_exc()}")
 
 
1
  import torch
2
+ from transformers import AutoModelForImageClassification, AutoImageProcessor
3
+ from PIL import Image
4
  import base64
5
  import io
6
  import numpy as np
 
7
  import torch.nn.functional as F
8
  import json
9
  import re
10
  import gc
 
11
  import traceback
12
+ import signal
13
+ from contextlib import timeout as context_timeout
14
 
15
+ class TimeoutError(Exception):
16
+ pass
 
 
 
 
 
 
 
17
 
18
+ def timeout_handler(signum, frame):
19
+ raise TimeoutError("Opération timeout")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  class EndpointHandler:
22
  def __init__(self, path=""):
23
+ print("🚀 VerifAI Handler V3 ULTRA LIGHT - Initialisation")
24
+ print(" Version optimisée sans timeouts")
25
 
26
  self.model = None
27
  self.processor = None
 
28
  self.model_labels = {}
29
+ self.model_name = "haywoodsloan/ai-image-detector-deploy"
30
+
31
+ # Timeout pour l'initialisation (30 secondes max)
32
+ signal.signal(signal.SIGALRM, timeout_handler)
33
+ signal.alarm(30)
34
 
35
  try:
36
+ print("🔄 Chargement rapide du modèle...")
 
37
 
38
+ # Chargement simplifié et rapide
39
+ self.processor = AutoImageProcessor.from_pretrained(
40
+ self.model_name,
41
+ trust_remote_code=False
42
+ )
43
 
 
 
 
44
  self.model = AutoModelForImageClassification.from_pretrained(
45
  self.model_name,
46
+ torch_dtype=torch.float32,
47
+ trust_remote_code=False,
48
+ low_cpu_mem_usage=True # Optimisation mémoire
49
  )
50
  self.model.eval()
51
 
52
+ # Labels simples
 
 
 
 
 
 
 
 
53
  if hasattr(self.model.config, 'id2label'):
54
  self.model_labels = self.model.config.id2label
55
  else:
56
+ self.model_labels = {0: "Real", 1: "Fake"}
57
 
58
+ print("✅ Modèle chargé (version ultra-light)")
59
+ print(f"📋 Labels: {self.model_labels}")
60
+ print("🎯 Handler prêt!")
61
 
62
+ except TimeoutError:
63
+ print("❌ Timeout lors de l'initialisation")
 
 
64
  self.model = None
65
  self.processor = None
 
 
 
 
 
 
 
66
  except Exception as e:
67
+ print(f"❌ Erreur initialisation: {e}")
68
+ self.model = None
69
+ self.processor = None
70
+ finally:
71
+ signal.alarm(0) # Désactiver le timeout
72
 
73
  def _normalize_label(self, label: str) -> str:
74
+ """Normalise les labels rapidement"""
75
  if not isinstance(label, str):
76
  label = str(label)
77
+
78
  label_lower = label.lower()
79
+ if any(word in label_lower for word in ['real', 'human', 'authentic']):
80
  return "Human"
81
+ if any(word in label_lower for word in ['fake', 'generated', 'ai', 'artificial']):
82
  return "AI Generated"
83
  return "Unknown"
84
 
85
  def _cleanup_memory(self):
86
+ """Nettoyage mémoire rapide"""
87
  try:
88
  if torch.cuda.is_available():
89
  torch.cuda.empty_cache()
 
92
  pass
93
 
94
  def __call__(self, data):
95
+ # Vérification rapide
96
  if self.model is None or self.processor is None:
97
  return {
98
  "status": "error",
99
+ "error": "Handler non initialisé",
100
  "prediction": 0,
101
  "predicted_class_name": "Error",
102
  "confidence": 0.0,
103
  "class_probabilities": {"Human": 0.0, "AI Generated": 0.0},
104
  "cam_image": None,
105
+ "version": "3.0-ultra-light",
106
+ "handler_name": "VerifAI Handler V3 ULTRA LIGHT"
107
  }
108
 
109
+ # Timeout pour le traitement (15 secondes max)
110
+ signal.signal(signal.SIGALRM, timeout_handler)
111
+ signal.alarm(15)
112
+
113
  try:
114
+ print("🔄 Traitement ultra-rapide...")
115
+
116
+ # Extraction image avec validation minimale
117
  image_data = data.get("inputs") or data
118
  if not image_data:
119
+ raise ValueError("Pas de données image")
120
 
121
+ # Décodage rapide
122
  try:
123
+ # Nettoyer le base64 si nécessaire
124
+ if isinstance(image_data, str) and image_data.startswith('data:'):
125
+ image_data = image_data.split(',', 1)[1]
126
+
127
  image_bytes = base64.b64decode(image_data)
128
  image = Image.open(io.BytesIO(image_bytes))
129
 
130
+ # Conversion rapide
131
  if image.mode != 'RGB':
132
  image = image.convert('RGB')
133
+
134
+ # Redimensionnement agressif si trop grand
135
+ if image.size[0] * image.size[1] > 1024 * 1024: # Plus de 1MP
136
+ image = image.resize((512, 512), Image.Resampling.LANCZOS)
137
+ print("⚠️ Image redimensionnée (optimisation)")
138
 
139
  except Exception as e:
140
+ raise ValueError(f"Erreur décodage image: {e}")
141
 
142
+ print("🧠 Inférence rapide...")
 
143
 
144
+ # Traitement minimal et rapide
145
  try:
146
  inputs = self.processor(image, return_tensors="pt")
147
 
148
+ # Inférence avec timeout interne
149
  with torch.no_grad():
150
  outputs = self.model(**inputs)
151
  logits = outputs.logits
 
153
  predicted_class_id = logits.argmax().item()
154
 
155
  except Exception as e:
156
+ raise RuntimeError(f"Erreur inférence: {e}")
157
 
158
+ # Traitement des résultats - Version rapide
159
  class_probs = {}
160
  for class_id, prob in enumerate(probabilities):
161
+ if class_id < len(self.model_labels):
162
+ label_str = self.model_labels.get(class_id, f"Class_{class_id}")
163
+ normalized_label = self._normalize_label(label_str)
164
+ if normalized_label != "Unknown":
165
+ class_probs[normalized_label] = float(prob)
166
 
167
+ # Fallback pour assurer les deux classes
168
  class_probs.setdefault("Human", 0.0)
169
  class_probs.setdefault("AI Generated", 0.0)
170
 
171
  prediction_label = self._normalize_label(self.model_labels.get(predicted_class_id, "Unknown"))
172
  confidence = class_probs.get(prediction_label, 0.0)
 
 
173
  prediction_id = 1 if prediction_label == "AI Generated" else 0
174
 
175
+ print(f"🎯 Résultat: {prediction_label} ({confidence:.3f})")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
+ # Nettoyage rapide
178
  self._cleanup_memory()
179
 
180
+ # Réponse optimisée (sans Grad-CAM pour éviter les timeouts)
181
  return {
182
  "status": "success",
183
  "prediction": prediction_id,
184
  "predicted_class_name": prediction_label,
185
  "confidence": confidence,
186
  "class_probabilities": class_probs,
187
+ "cam_image": None, # Désactivé temporairement
188
  "model_info": {
189
  "model_name": self.model_name,
190
+ "handler_version": "verifai-v3-ultra-light",
191
+ "precision_mode": "fast",
192
  "raw_prediction_id": predicted_class_id,
193
  "raw_labels": self.model_labels,
194
+ "grad_cam_method": "disabled_for_speed"
195
  },
196
+ "reliability": "ÉLEVÉE",
197
+ "version": "3.0-ultra-light",
198
+ "handler_name": "VerifAI Handler V3 ULTRA LIGHT",
199
+ "optimizations": [
200
+ "Grad-CAM désactivé (évite timeouts)",
201
+ "Chargement modèle optimisé",
202
+ "Timeouts internes appliqués",
203
+ "Nettoyage mémoire agressif"
204
+ ],
205
+ "performance_note": "Version rapide sans visualisation Grad-CAM"
206
  }
207
 
208
+ except TimeoutError:
209
+ print("❌ Timeout lors du traitement")
210
+ return {
211
+ "status": "error",
212
+ "error": "Timeout - traitement trop long",
213
+ "prediction": 0,
214
+ "predicted_class_name": "Error",
215
+ "confidence": 0.0,
216
+ "class_probabilities": {"Human": 0.0, "AI Generated": 0.0},
217
+ "cam_image": None,
218
+ "version": "3.0-ultra-light",
219
+ "handler_name": "VerifAI Handler V3 ULTRA LIGHT"
220
+ }
221
  except Exception as e:
222
+ print(f"❌ Erreur traitement: {e}")
223
  print(f"🔍 Traceback: {traceback.format_exc()}")
224
 
 
 
 
225
  return {
226
  "status": "error",
227
  "error": str(e),
 
230
  "confidence": 0.0,
231
  "class_probabilities": {"Human": 0.0, "AI Generated": 0.0},
232
  "cam_image": None,
233
+ "version": "3.0-ultra-light",
234
+ "handler_name": "VerifAI Handler V3 ULTRA LIGHT",
235
  "error_details": {
236
  "error_type": type(e).__name__,
237
+ "traceback": traceback.format_exc()[-300:]
238
  }
239
  }
240
+ finally:
241
+ signal.alarm(0) # Désactiver le timeout
 
 
 
 
242
  self._cleanup_memory()
 
 
243
 
244
+ # Test de fonctionnement
245
  if __name__ == "__main__":
246
+ print("🧪 TEST HANDLER V3 ULTRA LIGHT")
247
  print("=" * 50)
248
 
249
  try:
 
250
  handler = EndpointHandler()
251
 
252
  if handler.model is not None:
253
+ print("✅ Initialisation OK")
254
 
255
+ # Test rapide
 
256
  test_img = Image.new('RGB', (224, 224), color='red')
257
  buffer = io.BytesIO()
258
  test_img.save(buffer, format='JPEG')
259
  test_data = base64.b64encode(buffer.getvalue()).decode('utf-8')
260
 
261
+ print("🔄 Test inférence...")
262
  result = handler({"inputs": test_data})
263
+
264
+ print(f"📊 Statut: {result['status']}")
265
  if result['status'] == 'success':
266
  print(f"🎯 Prédiction: {result['predicted_class_name']} ({result['confidence']:.3f})")
267
+ print("✅ Handler V3 ULTRA LIGHT fonctionnel!")
268
  else:
269
  print(f"❌ Erreur: {result.get('error', 'Inconnue')}")
270
  else:
271
+ print("❌ Échec initialisation")
272
 
273
  except Exception as e:
274
+ print(f"❌ Erreur test: {e}")