ejazanik's picture
Upload app.py
b7bdef3 verified
Raw
History Blame Contribute Delete
11.7 kB
import gradio as gr
import numpy as np
import tensorflow as tf
from tensorflow.keras.preprocessing.image import array_to_img
import matplotlib.cm as cm
import matplotlib.pyplot as plt
import os
from PIL import Image # Ensure PIL is imported
# --- Global Variables ---
# Define model path assuming it's in the same directory or a 'models' subdirectory
# For simplicity, we'll assume 'best_model.h5' is uploaded alongside app.py
MODEL_PATH = 'best_model.h5'
# Ensure these variables are correctly defined and not defaulted
INPUT_SHAPE = (224, 224, 3)
NUM_CLASSES = 4
class_labels = ['Glioma', 'Meningioma', 'Normal', 'Pituitary'] # Use the updated class labels
last_conv_layer_name = 'conv5_block16_2_conv' # From previous cell's finding
# --- Load Model (outside predict function for efficiency) ---
try:
best_model = tf.keras.models.load_model(MODEL_PATH)
print(f"Model loaded successfully from: {MODEL_PATH}")
except Exception as e:
print(f"Error loading model from {MODEL_PATH}: {e}")
best_model = None # Set to None if loading fails
# --- Explainability Helper Functions ---
# Helper function to display Grad-CAM
def display_gradcam(img_pil, heatmap, alpha=0.4):
img_array = tf.keras.preprocessing.image.img_to_array(img_pil)
# Rescale heatmap to a range 0-255
heatmap = np.uint8(255 * heatmap)
# Use jet colormap to colorize heatmap
jet = plt.colormaps["jet"]
jet_colors = jet(np.arange(256))
jet_heatmap = jet_colors[heatmap]
# Create an image with RGB colorized heatmap
jet_heatmap_pil = Image.fromarray(np.uint8(jet_heatmap * 255))
jet_heatmap_pil = jet_heatmap_pil.resize((img_pil.width, img_pil.height))
jet_heatmap_array = tf.keras.preprocessing.image.img_to_array(jet_heatmap_pil)
jet_heatmap_array = jet_heatmap_array[:, :, :3] # Convert RGBA to RGB
# Superimpose the heatmap on original image
superimposed_img_array = jet_heatmap_array * alpha + img_array
superimposed_img_array = np.clip(superimposed_img_array, 0, 255).astype(np.uint8)
superimposed_img_pil = Image.fromarray(superimposed_img_array)
return superimposed_img_pil, Image.fromarray(np.uint8(jet_heatmap_array))
# Helper function to get Grad-CAM heatmap
def make_gradcam_heatmap(img_array, model, last_conv_layer_name, pred_index=None):
grad_model = tf.keras.models.Model(
[model.inputs], [model.get_layer(last_conv_layer_name).output, model.output]
)
with tf.GradientTape() as tape:
last_conv_layer_output, preds = grad_model([img_array])
if pred_index is None:
pred_index = tf.argmax(preds[0])
class_channel = preds[:, pred_index]
grads = tape.gradient(class_channel, last_conv_layer_output)
if grads is None:
return np.zeros(last_conv_layer_output.shape[1:-1])
pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))
last_conv_layer_output = last_conv_layer_output[0]
heatmap = last_conv_layer_output @ pooled_grads[..., tf.newaxis]
heatmap = tf.squeeze(heatmap)
heatmap = tf.maximum(heatmap, 0) / (tf.reduce_max(heatmap) + 1e-7)
return heatmap.numpy()
# Grad-CAM++
def make_gradcam_plus_plus_heatmap(img_array, model, last_conv_layer_name, pred_index=None):
grad_model = tf.keras.models.Model(
model.inputs, [model.get_layer(last_conv_layer_name).output, model.output]
)
with tf.GradientTape(persistent=True) as tape:
last_conv_layer_output, preds = grad_model([img_array])
if pred_index is None:
pred_index = tf.argmax(preds[0])
class_channel = preds[:, pred_index]
first_grad_tensor = tape.gradient(class_channel, last_conv_layer_output)
if first_grad_tensor is None:
del tape
return np.zeros(last_conv_layer_output.shape[1:-1])
first_grad_tensor = first_grad_tensor[0]
second_grad_tensor = tape.gradient(first_grad_tensor, last_conv_layer_output)[0]
third_grad_tensor = tape.gradient(second_grad_tensor, last_conv_layer_output)[0]
del tape
last_conv_layer_output_nobatch = last_conv_layer_output[0]
first_grad_nobatch = first_grad_tensor[0]
second_grad_nobatch = second_grad_tensor[0]
third_grad_nobatch = third_grad_tensor[0]
pooled_second_grad = tf.reduce_mean(second_grad_nobatch, axis=(0, 1))
pooled_third_grad = tf.reduce_mean(third_grad_nobatch, axis=(0, 1))
sum_activations_per_channel = tf.reduce_sum(last_conv_layer_output_nobatch, axis=(0, 1))
eps = 1e-7
alpha_num = pooled_second_grad
alpha_den = pooled_second_grad * 2 + pooled_third_grad * sum_activations_per_channel
alpha_den = tf.where(tf.equal(alpha_den, 0.0), eps, alpha_den) # Handle division by zero for tensors
alphas = alpha_num / alpha_den
alphas = alphas[tf.newaxis, tf.newaxis, :] # (1, 1, C)
weights = tf.maximum(first_grad_nobatch, 0.0)
deep_insights = alphas * weights * last_conv_layer_output_nobatch
heatmap = tf.reduce_sum(deep_insights, axis=-1) # (H, W)
heatmap = tf.maximum(heatmap, 0) / (tf.reduce_max(heatmap) + 1e-7)
return heatmap.numpy()
# LayerCAM
def make_layercam_heatmap(img_array, model, last_conv_layer_name, pred_index=None):
grad_model = tf.keras.models.Model(
model.inputs, [model.get_layer(last_conv_layer_name).output, model.output]
)
with tf.GradientTape() as tape:
last_conv_layer_output, preds = grad_model([img_array])
if pred_index is None:
pred_index = tf.argmax(preds[0])
class_channel = preds[:, pred_index]
grads = tape.gradient(class_channel, last_conv_layer_output)
if grads is None:
return np.zeros(last_conv_layer_output.shape[1:-1])
heatmap = tf.reduce_sum(tf.abs(grads[0][0]) * last_conv_layer_output[0], axis=-1)
heatmap = tf.maximum(heatmap, 0) / (tf.reduce_max(heatmap) + 1e-7)
return heatmap.numpy()
# ScoreCAM
def make_scorecam_heatmap(img_array, model, last_conv_layer_name, pred_index=None):
intermediate_model = tf.keras.models.Model(inputs=model.inputs, outputs=model.get_layer(last_conv_layer_name).output)
activations_batch = intermediate_model.predict(img_array, verbose=0)
activations = activations_batch[0]
original_h, original_w = img_array.shape[1:3]
upsampled_activations = []
for i in range(activations.shape[-1]):
channel_activation = activations[:, :, i]
upsampled_channel = tf.image.resize(tf.expand_dims(channel_activation, -1),
(original_h, original_w),
method='bilinear').numpy()[:, :, 0]
upsampled_activations.append(upsampled_channel)
upsampled_activations_stacked = np.stack(upsampled_activations, axis=-1)
if pred_index is None:
preds = model.predict(img_array, verbose=0)
pred_index = tf.argmax(preds[0])
masked_images_list = []
for i in range(activations.shape[-1]):
mask = upsampled_activations_stacked[:, :, i]
normalized_mask = mask / (np.max(mask) + 1e-7) if np.max(mask) > 0 else mask
masked_img_unprocessed = img_array[0] * normalized_mask[:, :, np.newaxis]
masked_img_processed = np.expand_dims(masked_img_unprocessed, axis=0)
masked_images_list.append(masked_img_processed)
if not masked_images_list:
return np.zeros((original_h, original_w))
batched_masked_images = np.vstack(masked_images_list)
preds_masked = model.predict(batched_masked_images, verbose=0)
scores_for_target_class = preds_masked[:, pred_index]
min_score = np.min(scores_for_target_class)
max_score = np.max(scores_for_target_class)
if (max_score - min_score) == 0:
weights = np.zeros_like(scores_for_target_class)
else:
weights = (scores_for_target_class - min_score) / (max_score - min_score + 1e-7)
heatmap_scorecam = np.sum(upsampled_activations_stacked * weights[np.newaxis, np.newaxis, :], axis=-1)
heatmap_scorecam = np.maximum(heatmap_scorecam, 0)
if np.max(heatmap_scorecam) > 0:
heatmap_scorecam /= (np.max(heatmap_scorecam) + 1e-7)
else:
heatmap_scorecam = np.zeros_like(heatmap_scorecam)
return heatmap_scorecam
# --- Prediction and Explainability Function for Gradio ---
def predict_and_explain(image_pil):
if best_model is None:
return "Error: Model not loaded.", "N/A", None, None, None, None, None, None, None, None, None
# Preprocess the image
img_resized = image_pil.resize((INPUT_SHAPE[0], INPUT_SHAPE[1]))
img_array = tf.keras.preprocessing.image.img_to_array(img_resized)
img_array = np.expand_dims(img_array, axis=0) # Add batch dimension
processed_img = tf.keras.applications.densenet.preprocess_input(img_array)
# Make prediction
preds = best_model.predict(processed_img, verbose=0)
predicted_class_idx = np.argmax(preds[0])
predicted_class_name = class_labels[predicted_class_idx]
confidence = preds[0][predicted_class_idx] * 100
# Generate heatmaps
grad_cam_heatmap = make_gradcam_heatmap(processed_img, best_model, last_conv_layer_name, pred_index=predicted_class_idx)
grad_cam_plus_plus_heatmap = make_gradcam_plus_plus_heatmap(processed_img, best_model, last_conv_layer_name, pred_index=predicted_class_idx)
layercam_heatmap = make_layercam_heatmap(processed_img, best_model, last_conv_layer_name, pred_index=predicted_class_idx)
scorecam_heatmap = make_scorecam_heatmap(processed_img, best_model, last_conv_layer_name, pred_index=predicted_class_idx)
# Superimpose heatmaps
original_img_display = img_resized # Use the resized PIL image
superimposed_grad_cam, grad_cam_heatmap_img = display_gradcam(img_resized, grad_cam_heatmap)
superimposed_grad_cam_plus_plus, grad_cam_plus_plus_heatmap_img = display_gradcam(img_resized, grad_cam_plus_plus_heatmap)
superimposed_layercam, layercam_heatmap_img = display_gradcam(img_resized, layercam_heatmap)
superimposed_scorecam, scorecam_heatmap_img = display_gradcam(img_resized, scorecam_heatmap)
return (
predicted_class_name,
f"{confidence:.2f}%",
original_img_display,
grad_cam_heatmap_img,
superimposed_grad_cam,
grad_cam_plus_plus_heatmap_img,
superimposed_grad_cam_plus_plus,
layercam_heatmap_img,
superimposed_layercam,
scorecam_heatmap_img,
superimposed_scorecam
)
# --- Set up Gradio Interface ---
if best_model is not None:
interface = gr.Interface(
fn=predict_and_explain,
inputs=gr.Image(type="pil", label="Upload MRI Scan"),
outputs=[
gr.Label(label="Predicted Class"),
gr.Textbox(label="Confidence"),
gr.Image(label="Original Image"),
gr.Image(label="Grad-CAM Heatmap"),
gr.Image(label="Grad-CAM Superimposed"),
gr.Image(label="Grad-CAM++ Heatmap"),
gr.Image(label="Grad-CAM++ Superimposed"),
gr.Image(label="LayerCAM Heatmap"),
gr.Image(label="LayerCAM Superimposed"),
gr.Image(label="ScoreCAM Heatmap"),
gr.Image(label="ScoreCAM Superimposed")
],
title="Brain Tumor Classification with Explainability",
description="Upload an MRI image to classify brain tumor types and visualize model's focus using Grad-CAM, Grad-CAM++, LayerCAM, and ScoreCAM."
)
# To run on Hugging Face Spaces, do not use debug=True or share=True
# interface.launch(debug=True, share=True)
interface.launch()
else:
print("Gradio interface could not be launched because the model failed to load.")