Update app.py
Browse files
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 |
-
|
164 |
-
|
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 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
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 |
-
|
|
|
|
|
219 |
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
|
|
224 |
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
return None, 0
|
229 |
-
try:
|
230 |
-
ensemble_probs = current_predictions["ensemble"]
|
231 |
-
top_class_idx = int(np.argmax(ensemble_probs))
|
232 |
|
233 |
-
|
234 |
-
|
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 |
-
|
239 |
-
|
|
|
|
|
|
|
240 |
|
241 |
-
|
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 |
-
|
250 |
-
|
251 |
-
|
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 |
-
|
256 |
-
|
|
|
257 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
258 |
except Exception as e:
|
259 |
-
print(f"❌ Erreur dans
|
260 |
import traceback; traceback.print_exc()
|
261 |
-
|
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 |
-
#
|
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"]
|