Update app.py
Browse files
app.py
CHANGED
@@ -62,47 +62,94 @@ def predict_single(image_pil, weights=(0.45, 0.25, 0.30)):
|
|
62 |
}
|
63 |
|
64 |
# ---- Grad-CAM ----
|
|
|
65 |
def make_gradcam(image_pil, model, last_conv_layer_name, class_index):
|
66 |
input_size = model.input_shape[1:3]
|
67 |
img_np = np.array(image_pil)
|
68 |
img_resized = cv2.resize(img_np, input_size)
|
69 |
|
70 |
-
if 'xception' in model.name:
|
71 |
-
|
72 |
-
|
|
|
|
|
|
|
73 |
|
74 |
img_array_preprocessed = preprocessor(np.expand_dims(img_resized, axis=0))
|
75 |
|
76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
input_name = get_primary_input_name(model)
|
78 |
input_for_model = {input_name: img_array_preprocessed}
|
79 |
|
80 |
with tf.GradientTape() as tape:
|
81 |
last_conv_layer_output, preds = grad_model(input_for_model, training=False)
|
82 |
-
if isinstance(preds, list):
|
|
|
83 |
class_channel = preds[:, class_index]
|
84 |
|
85 |
grads = tape.gradient(class_channel, last_conv_layer_output)
|
86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
87 |
|
88 |
-
pooled_grads = tf.reduce_mean(grads, axis=(0, 1))
|
89 |
last_conv_layer_output = last_conv_layer_output[0]
|
90 |
|
|
|
91 |
heatmap = last_conv_layer_output @ pooled_grads[..., tf.newaxis]
|
92 |
heatmap = tf.squeeze(heatmap)
|
93 |
-
heatmap = tf.maximum(heatmap, 0) / (tf.math.reduce_max(heatmap) + 1e-8)
|
94 |
-
heatmap = heatmap.numpy()
|
95 |
-
|
96 |
-
# ===== CORRECTION FINALE ET CRUCIALE =====
|
97 |
-
# On redimensionne la heatmap (ex: 7x7) à la taille de l'image (ex: 224x224)
|
98 |
-
heatmap = cv2.resize(heatmap, (img_resized.shape[1], img_resized.shape[0]))
|
99 |
-
# =======================================
|
100 |
-
|
101 |
-
heatmap = np.uint8(255 * heatmap)
|
102 |
-
heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)
|
103 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
104 |
img_bgr = cv2.cvtColor(img_resized, cv2.COLOR_RGB2BGR)
|
105 |
-
superimposed_img = cv2.addWeighted(img_bgr, 0.6,
|
106 |
|
107 |
return cv2.cvtColor(superimposed_img, cv2.COLOR_BGR2RGB)
|
108 |
|
|
|
62 |
}
|
63 |
|
64 |
# ---- Grad-CAM ----
|
65 |
+
# ---- Grad-CAM CORRIGÉ ----
|
66 |
def make_gradcam(image_pil, model, last_conv_layer_name, class_index):
|
67 |
input_size = model.input_shape[1:3]
|
68 |
img_np = np.array(image_pil)
|
69 |
img_resized = cv2.resize(img_np, input_size)
|
70 |
|
71 |
+
if 'xception' in model.name:
|
72 |
+
preprocessor = preprocess_xception
|
73 |
+
elif 'resnet50' in model.name:
|
74 |
+
preprocessor = preprocess_resnet
|
75 |
+
else:
|
76 |
+
preprocessor = preprocess_densenet
|
77 |
|
78 |
img_array_preprocessed = preprocessor(np.expand_dims(img_resized, axis=0))
|
79 |
|
80 |
+
# Vérification que la couche existe
|
81 |
+
try:
|
82 |
+
conv_layer = model.get_layer(last_conv_layer_name)
|
83 |
+
except ValueError:
|
84 |
+
print(f"Couche '{last_conv_layer_name}' non trouvée dans le modèle")
|
85 |
+
return img_resized
|
86 |
+
|
87 |
+
grad_model = Model(model.inputs, [conv_layer.output, model.output])
|
88 |
input_name = get_primary_input_name(model)
|
89 |
input_for_model = {input_name: img_array_preprocessed}
|
90 |
|
91 |
with tf.GradientTape() as tape:
|
92 |
last_conv_layer_output, preds = grad_model(input_for_model, training=False)
|
93 |
+
if isinstance(preds, list):
|
94 |
+
preds = preds[0]
|
95 |
class_channel = preds[:, class_index]
|
96 |
|
97 |
grads = tape.gradient(class_channel, last_conv_layer_output)
|
98 |
+
|
99 |
+
# Vérifications de sécurité
|
100 |
+
if grads is None:
|
101 |
+
print("Gradients sont None - retour de l'image originale")
|
102 |
+
return img_resized
|
103 |
+
|
104 |
+
# Vérifier les valeurs NaN ou inf
|
105 |
+
if tf.reduce_any(tf.math.is_nan(grads)) or tf.reduce_any(tf.math.is_inf(grads)):
|
106 |
+
print("Gradients contiennent des NaN/inf - retour de l'image originale")
|
107 |
+
return img_resized
|
108 |
|
109 |
+
pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))
|
110 |
last_conv_layer_output = last_conv_layer_output[0]
|
111 |
|
112 |
+
# Calcul de la heatmap
|
113 |
heatmap = last_conv_layer_output @ pooled_grads[..., tf.newaxis]
|
114 |
heatmap = tf.squeeze(heatmap)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
115 |
|
116 |
+
# Normalisation sécurisée
|
117 |
+
heatmap = tf.maximum(heatmap, 0)
|
118 |
+
max_val = tf.math.reduce_max(heatmap)
|
119 |
+
|
120 |
+
if max_val == 0:
|
121 |
+
print("Heatmap max est 0 - création d'une heatmap neutre")
|
122 |
+
heatmap = tf.ones_like(heatmap) * 0.5
|
123 |
+
else:
|
124 |
+
heatmap = heatmap / max_val
|
125 |
+
|
126 |
+
heatmap_np = heatmap.numpy()
|
127 |
+
|
128 |
+
# Vérifications finales avant resize
|
129 |
+
if heatmap_np.size == 0:
|
130 |
+
print("Heatmap vide - retour de l'image originale")
|
131 |
+
return img_resized
|
132 |
+
|
133 |
+
if np.any(np.isnan(heatmap_np)) or np.any(np.isinf(heatmap_np)):
|
134 |
+
print("Heatmap contient des NaN/inf après conversion - retour de l'image originale")
|
135 |
+
return img_resized
|
136 |
+
|
137 |
+
# Redimensionnement sécurisé
|
138 |
+
try:
|
139 |
+
# S'assurer que heatmap_np est en float32 et dans [0,1]
|
140 |
+
heatmap_np = np.clip(heatmap_np.astype(np.float32), 0, 1)
|
141 |
+
heatmap_resized = cv2.resize(heatmap_np, (img_resized.shape[1], img_resized.shape[0]))
|
142 |
+
except cv2.error as e:
|
143 |
+
print(f"Erreur OpenCV resize: {e}")
|
144 |
+
return img_resized
|
145 |
+
|
146 |
+
# Conversion finale
|
147 |
+
heatmap_uint8 = np.uint8(255 * heatmap_resized)
|
148 |
+
heatmap_colored = cv2.applyColorMap(heatmap_uint8, cv2.COLORMAP_JET)
|
149 |
+
|
150 |
+
# Superposition
|
151 |
img_bgr = cv2.cvtColor(img_resized, cv2.COLOR_RGB2BGR)
|
152 |
+
superimposed_img = cv2.addWeighted(img_bgr, 0.6, heatmap_colored, 0.4, 0)
|
153 |
|
154 |
return cv2.cvtColor(superimposed_img, cv2.COLOR_BGR2RGB)
|
155 |
|