Spaces:
Build error
Build error
import matplotlib.pyplot as plt | |
import matplotlib.cm as mpl_color_map | |
import copy | |
import numpy as np | |
import torch | |
import torch.nn.functional as F | |
from PIL import Image | |
from modules.guided_backprop import GuidedBackprop | |
import sys,os | |
sys.path.append(os.getcwd()) | |
def apply_colormap_on_image(org_im, activation, colormap_name): | |
""" | |
Apply heatmap on image | |
Args: | |
org_img (PIL img): Original image | |
activation_map (numpy arr): Activation map (grayscale) 0-255 | |
colormap_name (str): Name of the colormap | |
""" | |
# Get colormap | |
color_map = mpl_color_map.get_cmap(colormap_name) | |
no_trans_heatmap = color_map(activation) | |
# Change alpha channel in colormap to make sure original image is displayed | |
heatmap = copy.copy(no_trans_heatmap) | |
heatmap[:, :, 3] = 0.4 | |
heatmap = Image.fromarray((heatmap*255).astype(np.uint8)) | |
no_trans_heatmap = Image.fromarray((no_trans_heatmap*255).astype(np.uint8)) | |
# Apply heatmap on iamge | |
org_im = np.uint8(org_im.detach().to("cpu").numpy()[0][0]*255) | |
org_im = Image.fromarray(org_im) | |
heatmap_on_image = Image.new("RGBA", org_im.size) | |
heatmap_on_image = Image.alpha_composite(heatmap_on_image, org_im.convert('RGBA')) | |
heatmap_on_image = Image.alpha_composite(heatmap_on_image, heatmap) | |
return no_trans_heatmap, heatmap_on_image | |
def save_gradient_images(gradient, file_name): | |
""" | |
Exports the original gradient image | |
Args: | |
gradient (np arr): Numpy array of the gradient with shape (3, 224, 224) | |
file_name (str): Full filename including directory and png | |
""" | |
if not os.path.exists('../results'): | |
os.makedirs('../results') | |
# Normalize | |
gradient = gradient - gradient.min() | |
gradient /= gradient.max() | |
# Save image | |
path_to_file = file_name | |
# print("gradient save shape: ", gradient.shape) | |
save_image(gradient, path_to_file) | |
def format_np_output(np_arr): | |
""" | |
This is a (kind of) bandaid fix to streamline saving procedure. | |
It converts all the outputs to the same format which is 3xWxH | |
with using sucecssive if clauses. | |
Args: | |
im_as_arr (Numpy array): Matrix of shape 1xWxH or WxH or 3xWxH | |
""" | |
# Phase/Case 1: The np arr only has 2 dimensions | |
# Result: Add a dimension at the beginning | |
if len(np_arr.shape) == 2: | |
np_arr = np.expand_dims(np_arr, axis=0) | |
# Phase/Case 2: Np arr has only 1 channel (assuming first dim is channel) | |
# Result: Repeat first channel and convert 1xWxH to 3xWxH | |
if np_arr.shape[0] == 1: | |
np_arr = np.repeat(np_arr, 3, axis=0) | |
# Phase/Case 3: Np arr is of shape 3xWxH | |
# Result: Convert it to WxHx3 in order to make it saveable by PIL | |
if np_arr.shape[0] == 3: | |
np_arr = np_arr.transpose(1, 2, 0) | |
# Phase/Case 4: NP arr is normalized between 0-1 | |
# Result: Multiply with 255 and change type to make it saveable by PIL | |
if np.max(np_arr) <= 1: | |
np_arr = (np_arr*255).astype(np.uint8) | |
return np_arr | |
def save_image(im, path): | |
""" | |
Saves a numpy matrix or PIL image as an image | |
Args: | |
im_as_arr (Numpy array): Matrix of shape DxWxH | |
path (str): Path to the image | |
""" | |
if isinstance(im, (np.ndarray, np.generic)): | |
im = format_np_output(im) | |
im = Image.fromarray(im) | |
im.save(path) | |
def module_output_to_numpy(tensor): | |
return tensor.data.to('cpu').numpy() | |
def convert_to_grayscale(im_as_arr): | |
""" | |
Converts 3d image to grayscale | |
Args: | |
im_as_arr (numpy arr): RGB image with shape (D,W,H) | |
returns: | |
grayscale_im (numpy_arr): Grayscale image with shape (1,W,D) | |
""" | |
grayscale_im = np.sum(np.abs(im_as_arr), axis=0) | |
im_max = np.percentile(grayscale_im, 99) | |
im_min = np.min(grayscale_im) | |
grayscale_im = (np.clip((grayscale_im - im_min) / (im_max - im_min), 0, 1)) | |
grayscale_im = np.expand_dims(grayscale_im, axis=0) | |
return grayscale_im | |
class SaveOutput: | |
def __init__(self, totalFeatMaps): | |
self.layer_outputs = [] | |
self.grad_outputs = [] | |
self.first_grads = [] | |
self.totalFeatMaps = totalFeatMaps | |
self.feature_ext = None | |
### Used on register_forward_hook | |
### Output up to totalFeatMaps | |
def append_layer_out(self, module, input, output): | |
self.layer_outputs.append(output[0]) ### Appending with earlier index pertaining to earlier layers | |
### Used on register_backward_hook | |
### Output up to totalFeatMaps | |
def append_grad_out(self, module, grad_input, grad_output): | |
self.grad_outputs.append(grad_output[0][0]) ### Appending with last-to-first index pertaining to first-to-last layers | |
### Used as guided backprop mask | |
def append_first_grads(self, module, grad_in, grad_out): | |
self.first_grads.append(grad_in[0]) | |
def clear(self): | |
self.layer_outputs = [] | |
self.grad_outputs = [] | |
self.first_grads = [] | |
def set_feature_ext(self, feature_ext): | |
self.feature_ext = feature_ext | |
def getGuidedGradImg(self, layerNum, input_img): | |
# print("layer outputs shape: ", self.layer_outputs[0].shape) | |
# print("layer grad_outputs shape: ", self.grad_outputs[0].shape) | |
conv_output_img = module_output_to_numpy(self.layer_outputs[layerNum]) | |
grad_output_img = module_output_to_numpy(self.grad_outputs[len(self.grad_outputs)-layerNum-1]) | |
first_grad_output = self.first_grads[0].data.to('cpu').numpy()[0] | |
print("conv_output_img output shape: ", conv_output_img.shape) | |
print("grad_output_img output shape: ", grad_output_img.shape) | |
print("first_grad_output output shape: ", first_grad_output.shape) | |
print("target min max: {}, {}".format(conv_output_img.min(), conv_output_img.max())) | |
print("guided_gradients min max: {}, {}".format(grad_output_img.min(), grad_output_img.max())) | |
weights = np.mean(grad_output_img, axis=(1, 2)) # Take averages for each gradient | |
print("weights shape: ", weights.shape) | |
print("weights min max1: {}, {}".format(weights.min(), weights.max())) | |
# Create empty numpy array for cam | |
# conv_output_img = np.clip(conv_output_img, 0, conv_output_img.max()) | |
cam = np.ones(conv_output_img.shape[1:], dtype=np.float32) | |
print("cam min max1: {}, {}".format(cam.min(), cam.max())) | |
# Multiply each weight with its conv output and then, sum | |
for i, w in enumerate(weights): | |
cam += w * conv_output_img[i, :, :] | |
# cam = np.maximum(cam, 0) | |
print("cam min max2: {}, {}".format(cam.min(), cam.max())) | |
cam = (cam - np.min(cam)) / (np.max(cam) - np.min(cam)) # Normalize between 0-1 | |
cam = np.uint8(cam * 255) # Scale between 0-255 to visualize | |
cam = np.uint8(Image.fromarray(cam).resize((input_img.shape[3], | |
input_img.shape[2]), Image.ANTIALIAS))/255 | |
# cam_gb = np.multiply(cam, first_grad_output) | |
# grayscale_cam_gb = convert_to_grayscale(cam) | |
return cam | |
def getGuidedGradTimesImg(self, layerNum, input_img): | |
grad_output_img = module_output_to_numpy(self.grad_outputs[len(self.grad_outputs)-layerNum-1]) | |
print("grad_output_img output shape: ", grad_output_img.shape) | |
grad_times_image = grad_output_img[0]*input_img.detach().to("cpu").numpy()[0] | |
return grad_times_image | |
### target_output -- pass a created output tensor with one hot (1s) already in placed, used for guided gradients (first layer) | |
def output_feature_maps(self, targetDir, input_img): | |
# GBP = GuidedBackprop(self.feature_ext, 'resnet34') | |
# guided_grads = GBP.generate_gradients(input_img, one_hot_output_guided, text_for_pred) | |
# print("guided_grads shape: ", guided_grads.shape) | |
for layerNum in range(self.totalFeatMaps): | |
grad_times_image = self.getGuidedGradTimesImg(layerNum, input_img) | |
# save_gradient_images(cam_gb, targetDir + 'GGrad_Cam_Layer{}.jpg'.format(layerNum)) | |
# save_gradient_images(grayscale_cam_gb, targetDir + 'GGrad_Cam_Gray_Layer{}.jpg'.format(layerNum)) | |
### Output heatmaps | |
grayscale_vanilla_grads = convert_to_grayscale(grad_times_image) | |
save_gradient_images(grayscale_vanilla_grads, targetDir + 'Vanilla_grad_times_image_gray{}.jpg'.format(layerNum)) | |