| import matplotlib.pyplot as plt |
| import numpy as np |
| import cv2 |
|
|
| def plot_greenery_overlay(image_np, mask_classifications, output_path=None): |
| """ |
| Plots the original image and an overlay where greenery is highlighted. |
| """ |
| plt.figure(figsize=(10, 10)) |
| plt.imshow(image_np) |
|
|
| ax = plt.gca() |
| ax.set_autoscale_on(False) |
|
|
| |
| h, w = image_np.shape[:2] |
| greenery_overlay = np.zeros((h, w, 4)) |
|
|
| green_color = np.array([0, 1, 0, 0.5]) |
|
|
| for item in mask_classifications: |
| if item['is_green']: |
| seg = item['segmentation'] |
| greenery_overlay[seg] = green_color |
| |
| ax.imshow(greenery_overlay) |
| plt.axis('off') |
| |
| if output_path: |
| plt.savefig(output_path, bbox_inches='tight', pad_inches=0) |
| plt.close() |
| else: |
| plt.show() |
|
|
|
|
| def create_greenery_overlay(image_np, mask_classifications): |
| """ |
| Creates a semi-transparent greenery overlay composite image. |
| |
| Returns: |
| tuple: (composite_rgba, green_masks) where composite_rgba is the |
| overlay image and green_masks is the list of greenery items. |
| """ |
| h, w = image_np.shape[:2] |
| greenery_overlay = np.zeros((h, w, 4)) |
| green_color = np.array([0, 1, 0, 0.5]) |
|
|
| green_masks = [] |
| for item in mask_classifications: |
| if item['is_green']: |
| green_masks.append(item) |
| seg = item['segmentation'] |
| greenery_overlay[seg] = green_color |
|
|
| base_img_rgba = cv2.cvtColor(image_np, cv2.COLOR_RGB2RGBA) |
| alpha_mask = greenery_overlay[:, :, 3] > 0 |
| composite = base_img_rgba.copy() |
| composite[alpha_mask] = ( |
| base_img_rgba[alpha_mask] * 0.5 + |
| greenery_overlay[alpha_mask] * 255 * 0.5 |
| ).astype(np.uint8) |
|
|
| return composite, green_masks |
|
|
|
|
| def apply_grad_cam(model, input_tensor, target_class=None): |
| """ |
| Generates a Grad-CAM heatmap for the given input tensor using the model's |
| final convolutional layer (layer4 for ResNet-50). |
| |
| Args: |
| model: A GreeneryClassifier instance (wraps ResNet-50 via model.model). |
| input_tensor: A preprocessed image tensor of shape (1, 3, 64, 64). |
| target_class: The class index to generate the heatmap for. |
| If None, uses the model's predicted class. |
| |
| Returns: |
| heatmap: A numpy array (H, W) with values in [0, 1] representing |
| the class activation map. |
| predicted_class: The class index the model predicted. |
| """ |
| import torch |
| import torch.nn.functional as F |
|
|
| model.eval() |
| device = next(model.parameters()).device |
| input_tensor = input_tensor.to(device) |
|
|
| |
| activations = [] |
| gradients = [] |
|
|
| |
| target_layer = model.model.layer4 |
|
|
| def forward_hook(module, input, output): |
| activations.append(output.detach()) |
|
|
| def backward_hook(module, grad_input, grad_output): |
| gradients.append(grad_output[0].detach()) |
|
|
| fwd_handle = target_layer.register_forward_hook(forward_hook) |
| bwd_handle = target_layer.register_full_backward_hook(backward_hook) |
|
|
| try: |
| |
| output = model(input_tensor) |
| predicted_class = output.argmax(dim=1).item() |
|
|
| if target_class is None: |
| target_class = predicted_class |
|
|
| |
| model.zero_grad() |
| class_score = output[0, target_class] |
| class_score.backward() |
|
|
| |
| act = activations[0] |
| grad = gradients[0] |
|
|
| |
| weights = grad.mean(dim=[2, 3], keepdim=True) |
|
|
| |
| cam = (weights * act).sum(dim=1, keepdim=True) |
| cam = F.relu(cam) |
|
|
| |
| cam = F.interpolate(cam, size=input_tensor.shape[2:], mode='bilinear', align_corners=False) |
| cam = cam.squeeze().cpu().numpy() |
|
|
| |
| if cam.max() > 0: |
| cam = (cam - cam.min()) / (cam.max() - cam.min()) |
|
|
| return cam, predicted_class |
| finally: |
| |
| fwd_handle.remove() |
| bwd_handle.remove() |
|
|
|
|
| def save_grad_cam_overlay(image_np, heatmap, output_path, alpha=0.4): |
| """ |
| Overlays a Grad-CAM heatmap on the original image and saves to disk. |
| |
| Args: |
| image_np: Original RGB image as numpy array (H, W, 3), values 0-255. |
| heatmap: Grad-CAM heatmap as numpy array (H, W), values in [0, 1]. |
| output_path: File path to save the result. |
| alpha: Transparency of the heatmap overlay. |
| """ |
| |
| heatmap_resized = cv2.resize(heatmap, (image_np.shape[1], image_np.shape[0])) |
| heatmap_colored = cv2.applyColorMap(np.uint8(255 * heatmap_resized), cv2.COLORMAP_JET) |
| heatmap_colored = cv2.cvtColor(heatmap_colored, cv2.COLOR_BGR2RGB) |
|
|
| overlay = np.uint8(image_np * (1 - alpha) + heatmap_colored * alpha) |
|
|
| plt.figure(figsize=(10, 10)) |
| plt.imshow(overlay) |
| plt.axis('off') |
| plt.title('Grad-CAM Heatmap Overlay') |
| plt.savefig(output_path, bbox_inches='tight', pad_inches=0) |
| plt.close() |
| print(f"Grad-CAM overlay saved to {output_path}") |
|
|