ericjedha commited on
Commit
ac184a4
·
verified ·
1 Parent(s): 9a7a962

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +79 -89
app.py CHANGED
@@ -160,105 +160,95 @@ def find_last_dense_layer(model):
160
  raise ValueError("Aucune couche Dense trouvée dans le modèle.")
161
 
162
  # ---- GRAD-CAM ----
163
- # ---- GESTION ASYNCHRONE / ÉTAT ----
164
- current_image = None
165
- current_predictions = None
166
-
167
- # ---- Fonctions pour l'UI Gradio ----
168
- def quick_predict_ui(image_pil):
169
- global current_image, current_predictions
170
- if image_pil is None:
171
- return "Veuillez uploader une image.", None, None
172
  try:
173
- current_image = image_pil
174
- all_preds = predict_single(image_pil)
175
- current_predictions = all_preds
176
- ensemble_probs = all_preds["ensemble"]
177
-
178
- # Débogage : Afficher les probabilités brutes
179
- print("Probabilités brutes (ensemble):", ensemble_probs)
180
- print("Somme des probabilités brutes:", ensemble_probs.sum())
181
-
182
- # Vérification et normalisation explicite si nécessaire
183
- if ensemble_probs.sum() > 1.0: # Si les probabilités dépassent 1, renormaliser
184
- ensemble_probs = _renorm_safe(ensemble_probs)
185
- print("Probabilités renormées:", ensemble_probs)
186
- print("Somme après renorm:", ensemble_probs.sum())
187
-
188
- top_class_idx = int(np.argmax(ensemble_probs))
189
- top_class_name = CLASS_NAMES[top_class_idx]
190
- global_diag = diagnosis_map[top_class_name]
191
-
192
- # Conversion en pourcentages pour le DataFrame
193
- confidences = {CLASS_NAMES[i]: float(ensemble_probs[i] * 100) for i in range(len(CLASS_NAMES))} # En pourcentages
194
- df = pd.DataFrame.from_dict(confidences, orient='index', columns=['Probabilité']).reset_index().rename(columns={'index': 'Classe'})
195
- df = df.sort_values(by='Probabilité', ascending=False)
196
-
197
- # Ajout d'une colonne pour les étiquettes avec pourcentages
198
- df['Pourcentage'] = df['Probabilité'].apply(lambda x: f"{x:.1f}%")
199
-
200
- # Génération du graphique avec texte dynamique
201
- fig = px.bar(df,
202
- x="Classe",
203
- y="Probabilité",
204
- color="Probabilité",
205
- color_continuous_scale=px.colors.sequential.Viridis,
206
- title="Probabilités par classe",
207
- text="Pourcentage") # Utilise la colonne Pourcentage pour les étiquettes
208
-
209
- # Définir textposition comme une liste personnalisée
210
- text_positions = []
211
- for val in df['Probabilité']:
212
- if val <= 10: # Si la probabilité est ≤ 10%, texte à l'extérieur
213
- text_positions.append("outside")
214
- else: # Sinon, texte à l'intérieur
215
- text_positions.append("inside")
216
- fig.update_traces(textposition=text_positions) # Appliquer les positions personnalisées
217
 
218
- fig.update_layout(xaxis_title="", yaxis_title="Probabilité (%)", height=400)
 
 
219
 
220
- return f"{global_diag} ({top_class_name.upper()})", fig, None
221
- except Exception as e:
222
- print(f"Erreur dans quick_predict_ui: {e}")
223
- return f"Erreur: {e}", None, None
 
224
 
225
- def generate_gradcam_ui():
226
- global current_image, current_predictions
227
- if current_image is None or current_predictions is None:
228
- return None, 0
229
- try:
230
- ensemble_probs = current_predictions["ensemble"]
231
- top_class_idx = int(np.argmax(ensemble_probs))
232
 
233
- candidates = []
234
- if model_xcept is not None: candidates.append(("xception", model_xcept, current_predictions["xception"][top_class_idx]))
235
- if model_resnet50 is not None: candidates.append(("resnet50", model_resnet50, current_predictions["resnet50"][top_class_idx]))
236
- if model_densenet is not None: candidates.append(("densenet201", model_densenet, current_predictions["densenet201"][top_class_idx]))
237
 
238
- if not candidates:
239
- return None, 0
 
 
 
240
 
241
- # Génération asynchrone avec progression
242
- progress = 0
243
- yield None, progress # Initialisation
244
- progress = 10
245
- yield None, progress # Sélection du modèle
246
- explainer_model_name, explainer_model, conf = max(candidates, key=lambda t: t[2])
247
- explainer_layer = LAST_CONV_LAYERS.get(explainer_model_name)
248
 
249
- status = f"🎯 Génération Grad-CAM avec {explainer_model_name} (confiance: {conf:.1%})..."
250
- print(status)
251
- progress = 30
252
- yield None, progress # Début du calcul
253
- gradcam_img = make_gradcam(current_image, explainer_model, explainer_layer, class_index=top_class_idx)
254
 
255
- progress = 100
256
- yield gradcam_img, progress # Fin
 
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  except Exception as e:
259
- print(f"❌ Erreur dans generate_gradcam_ui: {e}")
260
  import traceback; traceback.print_exc()
261
- yield None, 0
262
 
263
  # ---- GESTION ASYNCHRONE / ÉTAT ----
264
  current_image = None
@@ -338,7 +328,7 @@ def generate_gradcam_ui():
338
  if not candidates:
339
  return None, 0
340
 
341
- # Simulation de progression avec des étapes
342
  progress = 0
343
  yield None, progress # Initialisation
344
  progress = 10
@@ -358,7 +348,7 @@ def generate_gradcam_ui():
358
  except Exception as e:
359
  print(f"❌ Erreur dans generate_gradcam_ui: {e}")
360
  import traceback; traceback.print_exc()
361
- yield None, 0"
362
 
363
  # ---- INTERFACE GRADIO ----
364
  example_paths = ["ISIC_0024627.jpg", "ISIC_0025539.jpg", "ISIC_0031410.jpg"]
 
160
  raise ValueError("Aucune couche Dense trouvée dans le modèle.")
161
 
162
  # ---- GRAD-CAM ----
163
+ def make_gradcam(image_pil, model, last_conv_layer_name, class_index):
164
+ if model is None: return np.array(image_pil)
 
 
 
 
 
 
 
165
  try:
166
+ input_size = model.input_shape[1:3]
167
+ bb_name = _guess_backbone_name(model)
168
+ print(f"Backbone détecté: {bb_name}")
169
+
170
+ if 'xception' in model.name.lower():
171
+ preprocessor = preprocess_xception
172
+ elif 'resnet50' in model.name.lower():
173
+ preprocessor = preprocess_resnet
174
+ elif 'densenet' in model.name.lower():
175
+ preprocessor = preprocess_densenet
176
+ else:
177
+ print("Préprocesseur non reconnu - utilisation de preprocess_densenet par défaut")
178
+ preprocessor = preprocess_densenet
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
+ img_np = np.array(image_pil.convert("RGB"))
181
+ img_resized = cv2.resize(img_np, input_size)
182
+ img_array_preprocessed = preprocessor(np.expand_dims(img_resized, axis=0))
183
 
184
+ try:
185
+ conv_layer = model.get_layer(last_conv_layer_name)
186
+ except ValueError:
187
+ print(f"Couche '{last_conv_layer_name}' non trouvée dans le modèle")
188
+ return img_resized
189
 
190
+ dense_layer = find_last_dense_layer(model)
191
+
192
+ grad_model = Model(model.inputs, [conv_layer.output, model.output])
 
 
 
 
193
 
194
+ input_name = get_primary_input_name(model)
195
+ input_for_model = {input_name: img_array_preprocessed}
 
 
196
 
197
+ with tf.GradientTape() as tape:
198
+ last_conv_layer_output, preds = grad_model(input_for_model, training=False)
199
+ if isinstance(preds, list):
200
+ preds = preds[0]
201
+ class_channel = preds[:, int(class_index)]
202
 
203
+ grads = tape.gradient(class_channel, last_conv_layer_output)
 
 
 
 
 
 
204
 
205
+ if grads is None:
206
+ print("⚠️ Gradients sont None - retour de l'image originale.")
207
+ return img_resized
 
 
208
 
209
+ if tf.reduce_any(tf.math.is_nan(grads)) or tf.reduce_any(tf.math.is_inf(grads)):
210
+ print("⚠️ Gradients contiennent des NaN/inf - retour de l'image originale.")
211
+ return img_resized
212
 
213
+ pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))
214
+ last_conv_layer_output = last_conv_layer_output[0]
215
+
216
+ heatmap = last_conv_layer_output @ pooled_grads[..., tf.newaxis]
217
+ heatmap = tf.squeeze(heatmap)
218
+
219
+ heatmap = tf.maximum(heatmap, 0)
220
+ max_val = tf.math.reduce_max(heatmap)
221
+
222
+ if max_val == 0:
223
+ print("Heatmap max est 0 - création d'une heatmap neutre")
224
+ heatmap = tf.ones_like(heatmap) * 0.5
225
+ else:
226
+ heatmap = heatmap / max_val
227
+
228
+ heatmap_np = heatmap.numpy()
229
+
230
+ if heatmap_np.size == 0:
231
+ print("Heatmap vide - retour de l'image originale")
232
+ return img_resized
233
+
234
+ if np.any(np.isnan(heatmap_np)) or np.any(np.isinf(heatmap_np)):
235
+ print("Heatmap contient des NaN/inf après conversion - retour de l'image originale")
236
+ return img_resized
237
+
238
+ heatmap_np = np.clip(heatmap_np.astype(np.float32), 0, 1)
239
+
240
+ heatmap_resized = cv2.resize(heatmap_np, (img_resized.shape[1], img_resized.shape[0]))
241
+ heatmap_uint8 = np.uint8(255 * heatmap_resized)
242
+ heatmap_colored = cv2.applyColorMap(heatmap_uint8, cv2.COLORMAP_JET)
243
+
244
+ img_bgr = cv2.cvtColor(img_resized, cv2.COLOR_RGB2BGR)
245
+ superimposed_img = cv2.addWeighted(img_bgr, 0.6, heatmap_colored, 0.4, 0)
246
+
247
+ return cv2.cvtColor(superimposed_img, cv2.COLOR_BGR2RGB)
248
  except Exception as e:
249
+ print(f"❌ Erreur inattendue dans make_gradcam: {e}")
250
  import traceback; traceback.print_exc()
251
+ return np.array(image_pil)
252
 
253
  # ---- GESTION ASYNCHRONE / ÉTAT ----
254
  current_image = None
 
328
  if not candidates:
329
  return None, 0
330
 
331
+ # Génération asynchrone avec progression
332
  progress = 0
333
  yield None, progress # Initialisation
334
  progress = 10
 
348
  except Exception as e:
349
  print(f"❌ Erreur dans generate_gradcam_ui: {e}")
350
  import traceback; traceback.print_exc()
351
+ yield None, 0
352
 
353
  # ---- INTERFACE GRADIO ----
354
  example_paths = ["ISIC_0024627.jpg", "ISIC_0025539.jpg", "ISIC_0031410.jpg"]