import numpy as np import torch import torch.nn.functional as F import cv2 from PIL import Image import tensorflow as tf class PyTorchGradCAM: def __init__(self, model, target_layer): self.model = model self.feature_maps = None self.gradients = None self.forward_hook = target_layer.register_forward_hook(self._save_feature_maps) self.backward_hook = target_layer.register_full_backward_hook(self._save_gradients) def _save_feature_maps(self, module, input, output): self.feature_maps = output.detach() def _save_gradients(self, module, grad_input, grad_output): self.gradients = grad_output[0].detach() def compute(self, tensor): tensor = tensor.clone().requires_grad_(True) output = self.model(tensor) predicted_class = output.argmax(dim=1).item() self.model.zero_grad() output[0, predicted_class].backward() weights = self.gradients.mean(dim=(2, 3), keepdim=True) cam = (weights * self.feature_maps).sum(dim=1, keepdim=True) cam = F.relu(cam) cam = cam.squeeze().cpu().numpy() cam = (cam - cam.min()) / (cam.max() - cam.min() + 1e-8) return cam def remove_hooks(self): self.forward_hook.remove() self.backward_hook.remove() def _overlay_heatmap(original_image, cam): img_resized = np.array(original_image.convert("RGB").resize((224, 224))) cam_resized = cv2.resize(cam, (224, 224)) heatmap = cv2.applyColorMap(np.uint8(255 * cam_resized), cv2.COLORMAP_JET) heatmap = cv2.cvtColor(heatmap, cv2.COLOR_BGR2RGB) overlay = cv2.addWeighted(img_resized, 0.6, heatmap, 0.4, 0) return Image.fromarray(overlay) def gradcam_pytorch(original_image, model, tensor, target_layer): gc = PyTorchGradCAM(model, target_layer) try: cam = gc.compute(tensor) finally: gc.remove_hooks() return _overlay_heatmap(original_image, cam) def gradcam_keras_manual(original_image, model, array, predicted_class): img_tensor = tf.cast(array, tf.float32) # Base model (MobileNetV2) nikalo base_model = model.layers[0] # Last conv layer dhundo last_conv_layer = None for layer in base_model.layers: if isinstance(layer, tf.keras.layers.Conv2D): last_conv_layer = layer if last_conv_layer is None: return original_image.resize((224, 224)) # Sirf ek output — last_conv_layer.output ONLY grad_model = tf.keras.models.Model( inputs=base_model.input, outputs=last_conv_layer.output ) with tf.GradientTape() as tape: conv_outputs = grad_model(img_tensor, training=False) tape.watch(conv_outputs) x = conv_outputs # base_model baaki layers manually found = False for layer in base_model.layers: if found: x = layer(x, training=False) if layer == last_conv_layer: found = True # Sequential baaki layers for layer in model.layers[1:]: x = layer(x, training=False) loss = x[:, 0] grads = tape.gradient(loss, conv_outputs) pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2)) conv_out = conv_outputs[0] cam = conv_out @ pooled_grads[..., tf.newaxis] cam = tf.squeeze(cam) cam = tf.maximum(cam, 0) / (tf.math.reduce_max(cam) + 1e-8) cam = cam.numpy() return _overlay_heatmap(original_image, cam) def generate_gradcam_pytorch_resnet(original_image, model, tensor): return gradcam_pytorch(original_image, model, tensor, model.layer4) def generate_gradcam_pytorch_mobilenet(original_image, model, tensor): return gradcam_pytorch(original_image, model, tensor, model.features[-1]) def generate_gradcam_keras(original_image, model, array, predicted_class): return gradcam_keras_manual(original_image, model, array, predicted_class)