|
|
|
|
|
|
| import os
|
| import numpy as np
|
| from PIL import Image
|
| import tensorflow as tf
|
| import cv2
|
| from tensorflow.keras.models import Model
|
| from tensorflow.keras.layers import Conv2D
|
|
|
|
|
|
|
| gpus = tf.config.list_physical_devices("GPU")
|
| if gpus:
|
| try:
|
| for g in gpus:
|
| tf.config.experimental.set_memory_growth(g, True)
|
| except Exception as e:
|
|
|
| print("Warning: Could not set memory growth:", e)
|
|
|
| print("Num GPUs Available:", len(gpus))
|
| print("TensorFlow version:", tf.__version__)
|
|
|
|
|
| MODEL_PATH = os.getenv("MODEL_PATH", "saved_model/InceptionV3_Brain_Tumor_MRI.h5")
|
| print("Loading model from:", MODEL_PATH)
|
| model = tf.keras.models.load_model(MODEL_PATH)
|
| model.trainable = False
|
|
|
|
|
|
|
| last_conv_layer = None
|
| for layer in reversed(model.layers):
|
| if isinstance(layer, Conv2D):
|
| last_conv_layer = layer
|
| break
|
| if last_conv_layer is None:
|
| raise RuntimeError("No Conv2D layer found in the model; cannot build Grad-CAM.")
|
|
|
| target_layer = model.get_layer(last_conv_layer.name)
|
| grad_model = Model(inputs=model.inputs, outputs=[target_layer.output, model.output])
|
| print("Built grad_model with target layer:", target_layer.name)
|
|
|
|
|
| CLASS_NAMES = ["glioma", "meningioma", "notumor", "pituitary"]
|
|
|
|
|
| def preprocess_image_pil(img: Image.Image, target_size=(512, 512)):
|
| """
|
| Accepts PIL.Image, returns float32 numpy array shaped (1,H,W,3) with values in [0,1].
|
| """
|
| img = img.convert("RGB")
|
| img = img.resize(target_size, resample=Image.BILINEAR)
|
| arr = np.asarray(img).astype("float32") / 255.0
|
| arr = np.expand_dims(arr, axis=0)
|
| return arr
|
|
|
| def pil_to_tf_tensor(img: Image.Image, target_size=(512, 512)):
|
| """
|
| Convert PIL image to a TF tensor float32 (1,H,W,3) scaled to [0,1].
|
| Uses TF ops to allow better GPU pipeline.
|
| """
|
| arr = preprocess_image_pil(img, target_size=target_size)
|
| return tf.convert_to_tensor(arr, dtype=tf.float32)
|
|
|
|
|
| def predict(img: Image.Image):
|
| """
|
| Returns (label, confidence, prob_dict)
|
| """
|
| input_tensor = preprocess_image_pil(img)
|
|
|
| preds = model(input_tensor, training=False)
|
| probs = preds.numpy()[0]
|
| class_idx = int(np.argmax(probs))
|
| confidence = float(np.max(probs))
|
| prob_dict = {CLASS_NAMES[i]: float(probs[i]) for i in range(len(CLASS_NAMES))}
|
| return CLASS_NAMES[class_idx], confidence, prob_dict
|
|
|
|
|
|
|
| @tf.function
|
| def _compute_conv_and_grads(img_input, class_index):
|
| with tf.GradientTape() as tape:
|
| conv_outputs, preds = grad_model(img_input)
|
|
|
|
|
| if isinstance(preds, (list, tuple)):
|
| preds = preds[0]
|
|
|
| class_logits = preds[:, class_index]
|
|
|
| grads = tape.gradient(class_logits, conv_outputs)
|
| return conv_outputs, grads, preds
|
|
|
| def compute_gradcam_overlay(img: Image.Image, interpolant=0.5, target_size=(512,512)):
|
| """
|
| High-level wrapper:
|
| - builds input tensor
|
| - obtains predicted class index (fast forward)
|
| - calls compiled grad function to get conv features + grads
|
| - computes heatmap and overlay efficiently
|
| Returns: overlay as uint8 HxWx3 numpy array
|
| """
|
|
|
| input_tf = pil_to_tf_tensor(img, target_size=target_size)
|
|
|
|
|
| preds = model(input_tf, training=False)
|
| pred_np = preds.numpy()[0]
|
| class_idx = int(np.argmax(pred_np))
|
|
|
|
|
| conv_out, grads, _ = _compute_conv_and_grads(input_tf, tf.constant(class_idx, dtype=tf.int64))
|
|
|
|
|
| conv_out_np = conv_out.numpy()
|
| grads_np = grads.numpy() if grads is not None else None
|
|
|
| if grads_np is None:
|
|
|
| H = input_tf.shape[1]
|
| W = input_tf.shape[2]
|
| original_img = np.array(img.resize((W, H))).astype("uint8")
|
| if original_img.ndim == 2:
|
| original_img = np.stack([original_img]*3, axis=-1)
|
| return original_img
|
|
|
|
|
| if conv_out_np.ndim == 4 and conv_out_np.shape[0] == 1:
|
| conv_out_np = conv_out_np[0]
|
|
|
| if grads_np.ndim == 4 and grads_np.shape[0] == 1:
|
| grads_np = grads_np[0]
|
|
|
|
|
| pooled_grads = np.mean(grads_np, axis=(0,1))
|
|
|
|
|
| heatmap = np.sum(conv_out_np * pooled_grads[np.newaxis, np.newaxis, :], axis=-1)
|
| heatmap = np.maximum(heatmap, 0.0)
|
| max_val = np.max(heatmap) if heatmap.size else 0.0
|
| if max_val > 0:
|
| heatmap = heatmap / (max_val + 1e-9)
|
| else:
|
| heatmap = np.zeros_like(heatmap, dtype=np.float32)
|
|
|
|
|
| H = input_tf.shape[1]
|
| W = input_tf.shape[2]
|
| original_img = np.array(img.resize((W, H))).astype("float32")
|
| if original_img.ndim == 2:
|
| original_img = np.stack([original_img]*3, axis=-1)
|
|
|
| heatmap_resized = cv2.resize((heatmap * 255.0).astype("uint8"), (W, H))
|
| heatmap_color = cv2.applyColorMap(heatmap_resized, cv2.COLORMAP_JET)
|
| heatmap_color = cv2.cvtColor(heatmap_color, cv2.COLOR_BGR2RGB).astype("float32")
|
|
|
|
|
| orig_uint8 = np.clip(original_img, 0, 255).astype("uint8")
|
|
|
|
|
| overlay = np.clip(orig_uint8.astype("float32") * interpolant + heatmap_color * (1.0 - interpolant), 0, 255).astype("uint8")
|
| return overlay
|
|
|
|
|
| __all__ = ["model", "grad_model", "predict", "compute_gradcam_overlay", "CLASS_NAMES"]
|
|
|
| def gradcam(img: Image.Image, interpolant=0.5):
|
| return compute_gradcam_overlay(img, interpolant=interpolant)
|
|
|
|
|
|
|
| |