<jupyter_start><jupyter_text>Preparations---  *Requirements* *In order to run this notebook we rely on specifc package-versions; here we will downgrade these nessecarry packages.**After it's completion it is **required to restart** current running runtime-enviroment, this can be done by clicking 'Runtime' > 'Reset runtime' in the menubar.*<jupyter_code><jupyter_output><empty_output><jupyter_text>*Versioning*<jupyter_code>import torch
import PIL
import numpy as np

print('Installed Packegesversions:\n - PyTroch: {}\n - Pillow:  {}\n - Numpy:   {}'.format(torch.__version__, PIL.__version__, np.__version__))<jupyter_output>Installed Packegesversions:
 - PyTroch: 1.10.0+cu111
 - Pillow:  7.1.2
 - Numpy:   1.19.5<jupyter_text>*Imports*<jupyter_code>import sys
import os
import shutil
from glob import glob

import torch
from torch.nn.functional import softmax
from torchvision import models
from PIL import Image, ImageDraw

import math

from IPython.display import HTML, display
import ipywidgets
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.image as mpimg<jupyter_output><empty_output><jupyter_text>*CNN-Visualization-Framework** *Author: <NAME>** *Year: 2019** *Github: [utkuozbulak/pytorch-cnn-visualizations](https://github.com/utkuozbulak/pytorch-cnn-visualizations)* We start by cloning this repository and checkout at a specifc commit:<jupyter_code>%%capture
!git clone https://github.com/utkuozbulak/pytorch-cnn-visualizations.git
%cd /content/pytorch-cnn-visualizations
!git checkout 66af4935d76cb0b597c33068026ca2a8e1c562dc
%cd /content<jupyter_output><empty_output><jupyter_text>Now will make the delcared classes, variables and functions importable:<jupyter_code>%%capture
%cd /content/pytorch-cnn-visualizations
with open('__init__.py', 'w') as f:
  f.write('')
%cd /content
sys.path.append('/content/pytorch-cnn-visualizations/src')<jupyter_output><empty_output><jupyter_text>And will finally import used classes, variables and functions into this python-enviorment:<jupyter_code>from misc_functions import preprocess_image, save_image, apply_colormap_on_image<jupyter_output><empty_output><jupyter_text>*ImageNet-Labels*<jupyter_code>class ImageNetLabels:
  """
  A Helper-class to resolve  imagenet-labels
  """

  def __init__(self, p: str = '/content/imagenet_classes.txt'):
    """
    Init

    @param p: the path to the text-file to load labels from
    """
    if not os.path.isfile(p):
      !wget https://raw.githubusercontent.com/pytorch/hub/master/imagenet_classes.txt

    self._labels = []
    with open(p, 'r') as f:
      for s in f.readlines():
        self._labels.append(s.strip())

  def decode_predictions(self, tensor, top_n) -> ([int], [str], [float]): 
    top_prob, top_catid = torch.topk(tensor, top_n)
    class_ids = []
    class_names = []
    class_probabilities = []
    for i in range(top_prob.size(0)):
      id = top_catid[i].item()
      class_ids.append(id)
      class_names.append(self._labels[id])
      class_probabilities.append(top_prob[i].item())
    return (class_ids, class_names, class_probabilities)

# the instance
ImageNet_Labels = ImageNetLabels()<jupyter_output>--2021-11-12 17:40:10--  https://raw.githubusercontent.com/pytorch/hub/master/imagenet_classes.txt
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 192.168.3.11, 172.16.17.32, 192.168.3.11, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|192.168.3.11|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10472 (10K) [text/plain]
Saving to: ‘imagenet_classes.txt’

imagenet_classes.tx 100%[===================>]  10.23K  --.-KB/s    in 0s      

2021-11-12 17:40:10 (68.7 MB/s) - ‘imagenet_classes.txt’ saved [10472/10472]<jupyter_text>*Progressbar*To show a HTML-progress bar when doing some intesive tasks:<jupyter_code>PROGRESS_BAR = None
PROGRESS_BAR_VALUE = 0
PROGRESS_BAR_MAX_VALUE = 100

def progressbar(value, max_value):
  """
  Creates a HTML-Progressbar

  @param value: the value to set the prograssbar to
  @param max_value: the max-value to set the prograssbar to
  """
  return HTML(f'<progress value=\'{value/max_value}\' max=\'{max}\' style=\'width: 100%\'>\'{value}%\'</progress><label>Finished {value} / {max_value}</label>')   

def display_progress_bar(init_value, max_value):
  """
  Initializes and displays a HTML-Progressbar

  @param init_value: the initial value to set the prograssbar to
  @param max_value: the max-value to set the prograssbar to
  """
  global PROGRESS_BAR
  global PROGRESS_BAR_VALUE
  global PROGRESS_BAR_MAX_VALUE
  PROGRESS_BAR_VALUE = init_value
  PROGRESS_BAR_MAX_VALUE = max_value
  PROGRESS_BAR = display(progressbar(PROGRESS_BAR_VALUE, PROGRESS_BAR_MAX_VALUE), display_id=True)

def update_progress_bar():
  """
  Will incrementaly update the progress-value and update the displayed HTML-Progressbar
  """
  global PROGRESS_BAR
  global PROGRESS_BAR_VALUE
  global PROGRESS_BAR_MAX_VALUE
  PROGRESS_BAR_VALUE = PROGRESS_BAR_VALUE + 1
  PROGRESS_BAR.update(progressbar(PROGRESS_BAR_VALUE, PROGRESS_BAR_MAX_VALUE))<jupyter_output><empty_output><jupyter_text>Some functionality to display multiple images:  *Matplotlib*<jupyter_code>%matplotlib inline

def extract_first_numbers(txt: str) -> str:
  """
  Will extract all numbers out of given string

  @param txt: the text to extract from

  @return: the extracted numbers 
  """
  return ''.join([s for s in txt if s.isdigit()])

def plot_images(image_paths: [str], rows: int, row_titles: [str], columns: int, column_titles: [str], figsize: (int, int), hide_column_title: bool=False):
  """
  Will plot given images

  @param image_paths: the paths of the images to display
  @param rows: number of rows to use
  @param columns: number of colums to use
  @param figsize: the sizes used for the figure
  @param tfunc: function called to transform a filename into a more meaningfull str
  """
  # smaller figures so they can get rendered correctly in colab:
  max_rows_per_figure = 10
  if rows > max_rows_per_figure:
    unscaled_figsize = (figsize[0], int(figsize[1]/rows))
    sub_figure_count = math.ceil(rows/max_rows_per_figure)
    split_image_paths = []
    split_row_titles = []
    i1, j1 = 0, max_rows_per_figure * columns
    i2, j2 = 0, max_rows_per_figure
    left_over = rows
    for _ in range(0, sub_figure_count-1):
      split_image_paths.append(image_paths[i1:j1])
      split_row_titles.append(row_titles[i2:j2])
      i1 = j1
      j1 += (max_rows_per_figure * columns)
      i2 = j2
      j2 += max_rows_per_figure
    split_image_paths.append(image_paths[i1:])
    split_row_titles.append(row_titles[i2:])
    
    for i in range(0, sub_figure_count):
      sub_rows = len(split_row_titles[i])
      plot_images(split_image_paths[i], sub_rows, split_row_titles[i], columns, column_titles, (unscaled_figsize[0], unscaled_figsize[1] * sub_rows), bool(i))
    return


  fig, axes = plt.subplots(nrows=rows, ncols=columns, figsize=figsize)
  
  # set column-title
  if not hide_column_title:
    if rows > 1:
      for ax, ct in zip(axes[0], column_titles):
        ax.set_title(ct, fontsize='16')
    else:
      axes[0].set_title(column_titles[0], fontsize='16')
  
  # set row-title
  if rows > 1:
    for ax, rt in zip(axes[:,0], row_titles):
      ax.set_ylabel(rt, rotation=90, fontsize='16')
  else:
    axes[0].set_ylabel(row_titles[0], rotation=90, fontsize='16')

  # image
  for i, cell in enumerate(axes.flat):
    cell.imshow(mpimg.imread(image_paths[i]))

  # styling
  #fig.text(0.5, 1.004, title, fontsize='20', horizontalalignment='center', verticalalignment='top')
  fig.tight_layout()
  fig.patch.set_facecolor('xkcd:white')
  plt.show()<jupyter_output><empty_output><jupyter_text>*Utilities*Other utility-functions<jupyter_code>def chunks(lst, n):
  """ 
  Yield successive n-sized chunks from lst.

  @param lst: the list to split
  @param n: the chunks to split the given list into
  """
  for i in range(0, len(lst), n):
      yield lst[i:i + n]<jupyter_output><empty_output><jupyter_text>GifUsed i.e. to transform images into a GIF.<jupyter_code>def make_gif_from_images(image_paths: [str], description: [], export_dir: str, gif_filename: str, duration: int, loop: int, gif_filetype: str = '.gif', gif_fromat: str = 'GIF', margin: int = 3):
  assert len(image_paths) > 1, 'Can not make a gif out of a single path.'
  assert len(image_paths) == len(description), 'Length of images does not match the length of available names.'

  if not gif_filetype[0] == '.':
    gif_filetype = '.' + gif_filetype
  if not export_dir[-1] == '/':
    export_dir += '/'

  # the images
  images = [Image.open(p) for p in image_paths]
  # draw the texts
  for i, image in enumerate(images):
    if type(description[i]) == int:
      description[i] = f'conv_{description[i]}'
    iw, ih = image.size
    draw = ImageDraw.Draw(image)
    tw, th = draw.textsize(description[i])

    x = margin
    y = ih - th - margin
    draw.rectangle((x,y,x+tw,y+th), fill='black')
    draw.text((x,y), description[i])

  # finaly 
  filename = f'{export_dir}{gif_filename}{gif_filetype}'
  images[0].save(
      fp=filename,
      format=gif_fromat, append_images=images[1:],
      save_all=True, duration=duration, loop=loop
  )
  [image.close() for image in images]

  return filename

def show_gifs(gif_paths: [str], format: str = 'gif', width: int = 325, height: int = 325, padding: int = 20):
  images = []
  for path in gif_paths:
    images.append(ipywidgets.Image(value=open(path, 'rb').read(), format=format, width=width, height=height, layout=ipywidgets.Layout(padding=f'{padding}px')))
  hbox = ipywidgets.HBox(images)
  display(hbox)<jupyter_output><empty_output><jupyter_text>Image-Samples---Here we declare a helper-class for our sample-images, which manages indices and offered some useful access methods to oure sample data:<jupyter_code>class ImageSamples:
  """
  A helper-class for Image-Sampls
  """
  
  def __init__(self, image_paths: [], targeted_class: [] = None, start_index: int = -1):
    """
    Init

    @param image_paths: list of paths to images
    @param targeted_class: list of targeted classes, if None the most likely detected class will be targeted
    @param start_index: internal inital index
    """
    self.__samples = image_paths
    assert len(self.__samples) >= 1, 'There must be at least one given sample'
    if targeted_class is None:
      self.__classes = [None] * len(self.__samples)
    else:
      self.__classes = targeted_class
      assert len(self.__samples) == len(self.__classes), 'Targeted classes must be of same length as given samples'
    self.__i = start_index
  
  def next(self) -> bool:
    """
    Will incrementality increase the index internaly used.

    @return: whether there is a element left
    """
    i = 1 + self.__i
    if i >= len(self.__samples) or i < 0:
      return False
    else:
      self.__i = i
      return True

  def reset_index(self):
    """
    Will reset the index, tho the next-function can be called again
    """
    self.__i = -1
  
  def get_preprocessed_image(self, colorcodec: str = 'RGB') -> Image:
    """
    Will return preprocess the current image for current index

    @param colorcodec: the colorcodec used to transform the image

    @return: the preprocessed image
    """
    return preprocess_image(Image.open(self.__samples[self.__i]).convert(colorcodec), resize_im=True)

  def get_original_image(self, colorcodec: str = 'RGB') -> Image:
    """
    Will return the original image for current index

    @param colorcodec: the colorcodec used to transform the image

    @return: the original image
    """
    return Image.open(self.__samples[self.__i]).convert(colorcodec)

  def get_file_name(self) -> str:
    """
    Will return the filename for current index

    @return: the filename (without file-extension)
    """
    return self.__samples[self.__i].split('/')[-1].split('.')[0]

  def get_sample(self) -> str:
    """
    Will return the set sample for current index

    @return: the sample 
    """
    return self.__samples[self.__i]

  def get_targeted_class(self) -> int:
    """
    Will return targeted class for current index

    @return: the number of targeted class
    """
    return self.__classes[self.__i]


  def __len__(self):
    return len(self.__samples)
  
def default_image_samples() -> ImageSamples:
  """
  Will return a 'default' ImageSamples-object

  @return: a ImageSamples-object with some predefined values
  """
  return ImageSamples(image_paths = [
    '/content/pytorch-cnn-visualizations/input_images/snake.jpg',   
    '/content/pytorch-cnn-visualizations/input_images/cat_dog.png', 
  ])<jupyter_output><empty_output><jupyter_text>XAI-Functionality---  *CamExtractor*<jupyter_code>class CamExtractor():
  """
  Extracts cam features from the model
  """
  def __init__(self, model, target_layer):
    self.model = model
    self.target_layer = target_layer
    self.gradients = None

  def save_gradient(self, grad):
    self.gradients = grad

  def forward_pass_on_convolutions(self, x):
    """
    Does a forward pass on convolutions, hooks the function at given layer

    @param x: input-tensor

    @return target-layer-output, output-tensor
    """
    conv_output = None

    # vgg
    if type(self.model) == models.vgg.VGG:
      for module_pos, module in self.model.features._modules.items():
        x = module(x)  # Forward
        if int(module_pos) == self.target_layer:
          x.register_hook(self.save_gradient)
          conv_output = x  # Save the convolution output on that layer

    # resnet
    elif type(self.model) == models.resnet.ResNet:
      # header
      x = self.model.conv1(x)
      if self.target_layer == 'conv1':
        x.register_hook(self.save_gradient)
        conv_output = x
      x = self.model.bn1(x)
      x = self.model.relu(x)
      x = self.model.maxpool(x)

      # layers
      for l, layer in enumerate([self.model.layer1, self.model.layer2, self.model.layer3, self.model.layer4]):
        if self.target_layer.startswith(f'layer{l+1}_'):
          for i, module in enumerate(layer):
            identity = x

            x = module.conv1(x)
            if self.target_layer == f'layer{l+1}_{i}_conv1':
              x.register_hook(self.save_gradient)
              conv_output = x
            x = module.bn1(x)
            x = module.relu(x)

            x = module.conv2(x)
            if self.target_layer == f'layer{l+1}_{i}_conv2':
              x.register_hook(self.save_gradient)
              conv_output = x
            x = module.bn2(x)
            x = module.relu(x)

            x = module.conv3(x)
            if self.target_layer == f'layer{l+1}_{i}_conv3':
              x.register_hook(self.save_gradient)
              conv_output = x
            x = module.bn3(x)

            if module.downsample is not None:
              if self.target_layer.startswith(f'layer{l+1}_{i}_downsample'):
                for j, (_, i_module) in enumerate(module.downsample._modules.items()):
                    identity = i_module(identity)
                    if self.target_layer == f'layer{l+1}_{i}_downsample_{j}':
                      identity.register_hook(self.save_gradient)
                      conv_output = identity
              else:
                  identity = module.downsample(identity)

            x += identity
            x = module.relu(x)

        else:
          x = layer(x)

      # tail
      x = self.model.avgpool(x)
    
    else:
      assert False, 'Unsupported model.'
      
    # output
    return conv_output, x

  def forward_pass(self, x):
    """
    Does a full forward pass on the model

    @param x: input-tensor

    @return target-layer-output, output-tensor
    """
    # Forward pass on the convolutions
    conv_output, x = self.forward_pass_on_convolutions(x)
    
    # Forward pass on the classifier
    if type(self.model) == models.vgg.VGG:
      x = x.view(x.size(0), -1)
      x = self.model.classifier(x)
    elif type(self.model) == models.resnet.ResNet:
      x = torch.flatten(x, 1)
      x = self.model.fc(x)
    else:
      assert False, 'Unsupported model.'
    
    return conv_output, x<jupyter_output><empty_output><jupyter_text>*GradientCam*<jupyter_code>class GradientCam:
  """
  A Helper-Class wrapping functions to build a gradient-cam
  """
  
  def __init__(self, gradient_cam_export_path: str = 'grad_cam', cam_heatmap_file_desc: str = '_cam_heatmap', cam_on_image_heatmap_file_desc: str = '_cam_on_image', cam_grayscale_file_desc: str = '_cam_grayscale', save_file_type: str = '.png', filter_n_predicitons: int = 15, prediction_desc: str = 'predictions', save_prediction_file_type: str = '.txt'):
    """
    Init

    @param gradient_cam_export_path: the directory name/path to use when exporrting gradientcams
    @param cam_heatmap_file_desc: description for a exported heatmap gradientcam
    @param cam_on_image_heatmap_file_desc: description for a heatmap on the original image gradientcam
    @param cam_grayscale_file_desc: description for a exported grayscale gradientcam
    @param save_file_type: file-extension to use when exporting gradientcams
    @param filter_n_predicitons: the amount of most-likely classes to filter out from the predictions
    @param prediction_desc
    @param save_prediction_file_type
    """
    self.grad_cam = None
    self.cam = None
    self.model = None
    self.predictions = None
    self.gradient_cam_export_path = gradient_cam_export_path
    self.cam_heatmap_file_desc = cam_heatmap_file_desc
    self.cam_on_image_heatmap_file_desc = cam_on_image_heatmap_file_desc
    self.cam_grayscale_file_desc = cam_grayscale_file_desc
    if not save_file_type[0] == '.':
      self.save_file_type = '.' + save_file_type
    else:
      self.save_file_type = save_file_type
    self.filter_n_predicitons = filter_n_predicitons
    self.prediction_desc = prediction_desc
    self.save_prediction_file_type = save_prediction_file_type
  
  def run(self, model_wrapper, sample: ImageSamples, target_layer):
    """
    Will calculate gradientcams

    @param model_wrapper: the model-wrapper object to use
    @param sample: the samples-object to use
    @param target_layer: the targeted layer
    """
    #print(f'Calculating GradientCam for Image \'{sample.get_file_name()}\' ...')
    self.model = model_wrapper.model
    self.model.eval()
    self.extractor = CamExtractor(model_wrapper.model, target_layer)
    self.cam = self._generate_cam(sample.get_preprocessed_image(), sample.get_targeted_class())

  def _generate_cam(self, input_image, target_class=None):
    """
    Modified version from GradCam.generate_cam
     -> see https://github.com/utkuozbulak/pytorch-cnn-visualizations/blob/master/src/gradcam.py
    """
    # Full forward pass
    # conv_output is the output of convolutions at specified layer
    # model_output is the final output of the model (1, 1000)
    conv_output, model_output = self.extractor.forward_pass(input_image)
    model_data_output = model_output.data.numpy()
    # detrmine target_class
    if target_class is None:
      target_class = np.argmax(model_data_output)
    self.predictions = ImageNet_Labels.decode_predictions(softmax(model_output[0], dim=0), self.filter_n_predicitons)
    # Target for backprop
    one_hot_output = torch.FloatTensor(1, model_output.size()[-1]).zero_()
    one_hot_output[0][target_class] = 1

    # Zero grads
    if type(self.model) == models.vgg.VGG:
      self.model.features.zero_grad()
      self.model.classifier.zero_grad()
    elif type(self.model) == models.resnet.ResNet:
      #[v.zero_grad() for k,v in self.model._modules.items()]
      self.model.fc.zero_grad()

    # Backward pass with specified target
    model_output.backward(gradient=one_hot_output, retain_graph=True)
    # Get hooked gradients
    guided_gradients = self.extractor.gradients.data.numpy()[0]
    # Get convolution outputs
    target = conv_output.data.numpy()[0]
    # Get weights from gradients
    weights = np.mean(guided_gradients, axis=(1, 2))  # Take averages for each gradient
    # Create empty numpy array for cam
    cam = np.ones(target.shape[1:], dtype=np.float32)
    # Multiply each weight with its conv output and then, sum
    for i, w in enumerate(weights):
        cam += w * target[i, :, :]
    cam = np.maximum(cam, 0)
    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_image.shape[2], input_image.shape[3]), Image.ANTIALIAS))/255
    return cam
    
  def __generate_export_dir_path(self, model_export_path) -> str:
    """
    Will generate a full export-path to use when exporting gradientcams

    @param model_export_path: the export-path declared in the model-wrapper
    """
    export_dir = model_export_path
    if not export_dir[-1] == '/':
      export_dir += '/'
    if self.gradient_cam_export_path[0] == '/':
      self.gradient_cam_export_path = self.gradient_cam_export_path[1:]
    export_dir += self.gradient_cam_export_path
    return export_dir

  def save_class_activation_images(self, model_wrapper, sample: ImageSamples, file_start: str = '', colormap_name: str = 'hsv'):
    """
    Will export the gradientcams calculated

    @param model_wrapper: the model model_wrapper used
    @param sample: the samples used
    @param file_start: a str added to the exported filename
    @param colormap_name: the name of the colormap to use
    """
    # Generate export path
    export_dir = self.__generate_export_dir_path(model_wrapper.model_export_path)

    # Create export-directory
    if not os.path.exists(export_dir):
        os.makedirs(export_dir)

    # Grayscale activation map
    heatmap, heatmap_on_image = apply_colormap_on_image(samples.get_original_image(), self.cam, colormap_name)

    # Save images
    file_name = samples.get_file_name()    
    save_image(heatmap, os.path.join(export_dir, file_start + file_name + self.cam_heatmap_file_desc + self.save_file_type))
    save_image(heatmap_on_image, os.path.join(export_dir, file_start + file_name + self.cam_on_image_heatmap_file_desc + self.save_file_type))
    save_image(self.cam, os.path.join(export_dir, file_start + file_name + self.cam_grayscale_file_desc + self.save_file_type))
    #print(f'Saved GradientCam Image \'{file_start+file_name}_*\' to \'{export_dir}\' ...')

    # Save predictions
    with open(os.path.join(export_dir, file_start + file_name + self.prediction_desc + self.save_prediction_file_type), 'w') as f:
      for id, cls, pred in zip(self.predictions[0], self.predictions[1], self.predictions[2]):
        f.write(f'{str(id)},{str(cls)},{str(pred)}\n')

  def __get_prediction_info(self, prediction_path: str) -> str:
    pred_info = []
    with open(prediction_path, 'r') as f:
      for line in f:
        sl = line.rstrip().split(',')
        pred_info.append(f'  {(sl[0]):>4s}: {sl[1]:30} with a Probability of {(float(sl[2]) * 100):6.2f} %')
    return '\n'.join(pred_info)

  def show_class_activation_images(self, model_wrapper, sample: ImageSamples, images_per_row: int = 3, figsize: (int, int) = (200, 200)):
    """
    Will display the calculated and exported images with matplot

    @param model_wrapper: the model model_wrapper used
    @param sample: the samples used
    @param images_per_row: amount of images to display per row
    @param figsize: the figure-sizes to use
    """
    # Generate export path
    export_dir = self.__generate_export_dir_path(model_wrapper.model_export_path)
    file_names = []
    samples.reset_index()
    while samples.next():
      file_names.append(samples.get_file_name())
  
    image_paths = []
    prediction_paths = []
    for file_name in file_names:
      impaths = glob(f'{export_dir}/*{file_name}*{self.save_file_type}')
      impaths.sort()
      image_paths.append(impaths)
      predpaths = glob(f'{export_dir}/*{file_name}*{self.save_prediction_file_type}')
      predpaths.sort()
      prediction_paths.append(predpaths)
    
    assert len(image_paths) == len(prediction_paths), 'Cant find image-paths with matching prediction files'

    # column titles
    column_titles = []
    for path in image_paths[0:3]:
      if self.cam_heatmap_file_desc in path:
        column_titles.append('CAM (Heatmap)')
      elif self.cam_on_image_heatmap_file_desc in path:
        column_titles.append('CAM (Heatmap) on Image')
      elif self.cam_grayscale_file_desc in path:
        column_titles.append('CAM (Grayscale)')
      else:
        column_titles.append('')
      
    # row titles
    row_titles = model_wrapper.get_targeted_layers()

    for i, paths in enumerate(image_paths):
      rows = math.ceil(len(paths)/images_per_row)
      print(f'Image: {file_names[i]}\nPredictions:\n{self.__get_prediction_info(prediction_paths[i][0])}\n')
      plot_images(paths, rows, row_titles, images_per_row, column_titles, (figsize[0], figsize[1] * rows))
      print('\n')

  def save_animated_gif(self, model_wrapper, sample: ImageSamples, duration: int = 1000, loop: int = 0):
    # Generate export path
    export_dir = self.__generate_export_dir_path(model_wrapper.model_export_path)
    file_names = []
    samples.reset_index()
    while samples.next():
      file_names.append(samples.get_file_name())

    image_paths = []

    for file_name in file_names:
      _image_paths = glob(f'{export_dir}/*{file_name}*{self.save_file_type}')
      _image_paths.sort()
      image_paths.append(_image_paths)

    out = []
    for i, file_specific_image_paths in enumerate(image_paths):
      heatmap = []
      heatmap_on_image = []
      grayscale = []
      tmp = []
      for image_path in file_specific_image_paths:
        if self.cam_heatmap_file_desc in image_path:
          heatmap.append(image_path)
        elif self.cam_on_image_heatmap_file_desc in image_path:
          heatmap_on_image.append(image_path)
        elif self.cam_grayscale_file_desc in image_path:
          grayscale.append(image_path)

      tmp.append(make_gif_from_images(grayscale, model_wrapper.get_targeted_layers(), export_dir, f'_{file_names[i]}{self.cam_grayscale_file_desc}', duration, loop))
      tmp.append(make_gif_from_images(heatmap, model_wrapper.get_targeted_layers(), export_dir, f'_{file_names[i]}{self.cam_heatmap_file_desc}', duration, loop))
      tmp.append(make_gif_from_images(heatmap_on_image, model_wrapper.get_targeted_layers(), export_dir, f'_{file_names[i]}{self.cam_on_image_heatmap_file_desc}', duration, loop))
      out.append(tmp)
    
    return out, file_names

  def show_animated_gifs(self, model_wrapper, sample: ImageSamples, gif_paths: [[str]], filenames: [str]):
    
    # Generate export path
    export_dir = self.__generate_export_dir_path(model_wrapper.model_export_path)
    file_names = []
    samples.reset_index()
    while samples.next():
      file_names.append(samples.get_file_name())
  
    prediction_paths = []
    for file_name in file_names:
      prediction_paths.append(glob(f'{export_dir}/*{file_name}*{self.save_prediction_file_type}')[0])

    for filename, gif_path, prediction_path in zip(filenames, gif_paths, prediction_paths):
      print('\nPredictions:\n{}'.format(self.__get_prediction_info(prediction_path)))
      show_gifs(gif_path)<jupyter_output><empty_output><jupyter_text>Neuronal-Networks---Here we declare our Neuralnetwork-Wrappers, which is mainly responsible to give access to specified model:  *VGG19*The VGG-neural-network is a often used backbone for detection; it makes almost as less error as humans would do (et al. [Comparison of Backbones for SemanticSegmentation Network](https://iopscience.iop.org/article/10.1088/1742-6596/1544/1/012196/pdf)).Source: <NAME> - Transfer learning with VGG16 and VGG19, the simpler way!<jupyter_code>class Wrapper_VGG19:
  """
  A wrapper-class for the torch-model of vgg
  """

  def __init__(self, pretrained: bool = True, model_export_path: str = '/content/results/vgg19'):
    """
    Init

    @param pretrained: whether to load pretrained weights
    @param model_export_path: the export directory specific to the wrapped model
    """
    self.pretrained = pretrained
    self.model_export_path = model_export_path
    self.model = None
  
  def build(self):
    """
    Will build the model

    @return self
    """
    self.model = models.vgg19(pretrained=self.pretrained)
    return self
  
  def summary(self):
    """
    Will print layer-informationen about the model
    """
    print(f'\n{self.model}')

  def get_targeted_layers(self) -> [int]:
    """
    Returns a list of layer-indicies to consider for xai-methods

    @return the requested list of layer-indicies
    """
    #return [0,7,14,21,28,34]
    return [0,2,5,7,10,12,14,16,19,21,23,25,28,30,32,34]<jupyter_output><empty_output><jupyter_text>*ResNet152*The ResNet-neural-network is a powerfull and often used backbone for detection; it makes less error than a humans would do (et al. [Comparison of Backbones for SemanticSegmentation Network](https://iopscience.iop.org/article/10.1088/1742-6596/1544/1/012196/pdf)).Source: Region-Based CNN Method with Deformable Modules for Visually Classifying Concrete Cracks<jupyter_code>class Wrapper_ResNet152:
  """
  A wrapper-class for the torch-model of resnet
  """

  def __init__(self, pretrained: bool = True, model_export_path: str = '/content/results/resnet152'):
    """
    Init

    @param pretrained: whether to load pretrained weights
    @param model_export_path: the export directory specific to the wrapped model
    """
    self.pretrained = pretrained
    self.model_export_path = model_export_path
    self.model = None

  def build(self):
    """
    Will build the model

    @return self
    """
    self.model = models.resnet152(pretrained=self.pretrained)
    return self
  
  def summary(self):
    """
    Will print layer-informationen about the model
    """
    print(f'\n{self.model}')

  def get_targeted_layers(self) -> [str]:
    """
    Returns a list of layer-indicies to consider for xai-methods

    @return the requested list of layer-indicies
    """
    #return ['conv1', 'layer1_0_conv1', 'layer1_2_conv1', 'layer2_0_conv1',  'layer2_2_conv1', 'layer2_4_conv1', 'layer2_7_conv1', 'layer3_0_conv1', 'layer3_5_conv1', 'layer3_10_conv1', 'layer3_15_conv1', 'layer3_20_conv1', 'layer3_25_conv1', 'layer3_30_conv1', 'layer3_35_conv1', 'layer4_0_conv1', 'layer4_2_conv1']
    return ['conv1',
      # layer1
      'layer1_0_conv1', 'layer1_0_conv2', 'layer1_0_conv3', 'layer1_0_downsample_0',
      'layer1_1_conv1', 'layer1_1_conv2', 'layer1_1_conv3',
      'layer1_2_conv1', 'layer1_2_conv2', 'layer1_2_conv3',
      # layer2
      'layer2_0_conv1', 'layer2_0_conv2', 'layer2_0_conv3', 'layer2_0_downsample_0',
      'layer2_1_conv1', 'layer2_1_conv2', 'layer2_1_conv3',
      'layer2_2_conv1', 'layer2_2_conv2', 'layer2_2_conv3',
      'layer2_3_conv1', 'layer2_3_conv2', 'layer2_3_conv3',
      'layer2_4_conv1', 'layer2_4_conv2', 'layer2_4_conv3',
      'layer2_5_conv1', 'layer2_5_conv2', 'layer2_5_conv3',
      'layer2_6_conv1', 'layer2_6_conv2', 'layer2_6_conv3',
      'layer2_7_conv1', 'layer2_7_conv2', 'layer2_7_conv3',
      # layer3
      'layer3_0_conv1', 'layer3_0_conv2', 'layer3_0_conv3', 'layer3_0_downsample_0',
      'layer3_1_conv1', 'layer3_1_conv2', 'layer3_1_conv3',
      'layer3_2_conv1', 'layer3_2_conv2', 'layer3_2_conv3',
      'layer3_3_conv1', 'layer3_3_conv2', 'layer3_3_conv3',
      'layer3_4_conv1', 'layer3_4_conv2', 'layer3_4_conv3',
      'layer3_5_conv1', 'layer3_5_conv2', 'layer3_5_conv3',
      'layer3_6_conv1', 'layer3_6_conv2', 'layer3_6_conv3',
      'layer3_7_conv1', 'layer3_7_conv2', 'layer3_7_conv3',
      'layer3_8_conv1', 'layer3_8_conv2', 'layer3_8_conv3',
      'layer3_9_conv1', 'layer3_9_conv2', 'layer3_9_conv3',
      'layer3_10_conv1', 'layer3_10_conv2', 'layer3_10_conv3',
      'layer3_11_conv1', 'layer3_11_conv2', 'layer3_11_conv3',
      'layer3_12_conv1', 'layer3_12_conv2', 'layer3_12_conv3',
      'layer3_13_conv1', 'layer3_13_conv2', 'layer3_13_conv3',
      'layer3_14_conv1', 'layer3_14_conv2', 'layer3_14_conv3',
      'layer3_15_conv1', 'layer3_15_conv2', 'layer3_15_conv3',
      'layer3_16_conv1', 'layer3_16_conv2', 'layer3_16_conv3',
      'layer3_17_conv1', 'layer3_17_conv2', 'layer3_17_conv3',
      'layer3_18_conv1', 'layer3_18_conv2', 'layer3_18_conv3',
      'layer3_19_conv1', 'layer3_19_conv2', 'layer3_19_conv3',
      'layer3_20_conv1', 'layer3_20_conv2', 'layer3_20_conv3',
      'layer3_21_conv1', 'layer3_21_conv2', 'layer3_21_conv3',
      'layer3_22_conv1', 'layer3_22_conv2', 'layer3_22_conv3',
      'layer3_23_conv1', 'layer3_23_conv2', 'layer3_23_conv3',
      'layer3_24_conv1', 'layer3_24_conv2', 'layer3_24_conv3',
      'layer3_25_conv1', 'layer3_25_conv2', 'layer3_25_conv3',
      'layer3_26_conv1', 'layer3_26_conv2', 'layer3_26_conv3',
      'layer3_27_conv1', 'layer3_27_conv2', 'layer3_27_conv3',
      'layer3_28_conv1', 'layer3_28_conv2', 'layer3_28_conv3',
      'layer3_29_conv1', 'layer3_29_conv2', 'layer3_29_conv3',
      'layer3_30_conv1', 'layer3_30_conv2', 'layer3_30_conv3',
      'layer3_31_conv1', 'layer3_31_conv2', 'layer3_31_conv3',
      'layer3_32_conv1', 'layer3_32_conv2', 'layer3_32_conv3',
      'layer3_33_conv1', 'layer3_33_conv2', 'layer3_33_conv3',
      'layer3_34_conv1', 'layer3_34_conv2', 'layer3_34_conv3',
      'layer3_35_conv1', 'layer3_35_conv2', 'layer3_35_conv3',
      # layer4
      'layer4_0_conv1', 'layer4_0_conv2', 'layer4_0_conv3', 'layer4_0_downsample_0',
      'layer4_1_conv1', 'layer4_1_conv2', 'layer4_1_conv3',
      'layer4_2_conv1', 'layer4_2_conv2', 'layer4_2_conv3']<jupyter_output><empty_output><jupyter_text>GradientCam--- To view the result's of the Gradient am, we start with initializing the Samples-Objekt and the GradientCam-Objekt:<jupyter_code>samples = default_image_samples()
gradcam = GradientCam()<jupyter_output><empty_output><jupyter_text>*VGG19*Next up we will initialize the neural Networks and run the GradientCam with them.<jupyter_code>vgg19 = Wrapper_VGG19(pretrained=True).build()
vgg19.summary()<jupyter_output>VGG(
  (features): Sequential(
    (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU(inplace=True)
    (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU(inplace=True)
    (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (6): ReLU(inplace=True)
    (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (8): ReLU(inplace=True)
    (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU(inplace=True)
    (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (13): ReLU(inplace=True)
    (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (15): ReLU(inplace=True)
    (16): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), pad[...]<jupyter_text>Now we can run the gradcam with specified parameters.<jupyter_code># show displaybar
display_progress_bar(0, len(vgg19.get_targeted_layers()) * len(samples))

for targeted_layer in vgg19.get_targeted_layers():
  # reset samples
  samples.reset_index()
  # loop all samples:
  while samples.next():
    # calculate
    gradcam.run(vgg19, samples, targeted_layer)
    gradcam.save_class_activation_images(vgg19, samples, file_start=f'{targeted_layer:02d}_', colormap_name='hsv')
    # update progress
    update_progress_bar()<jupyter_output><empty_output><jupyter_text>Let's have a look at generated images:<jupyter_code># Rendering all images may cause problems with jupyternotebook; uncomment at your own risk
#gradcam.show_class_activation_images(vgg19, samples, figsize=(20, 6))
gradcam.show_animated_gifs(vgg19, samples, *gradcam.save_animated_gif(vgg19, samples, duration=1100))<jupyter_output>Predictions:
    56: king snake                     with a Probability of  60.62 %
    60: night snake                    with a Probability of  30.63 %
    68: sidewinder                     with a Probability of   3.38 %
    54: hognose snake                  with a Probability of   1.52 %
    66: horned viper                   with a Probability of   1.51 %
    53: ringneck snake                 with a Probability of   0.53 %
    58: water snake                    with a Probability of   0.44 %
    67: diamondback                    with a Probability of   0.32 %
    62: rock python                    with a Probability of   0.28 %
    65: sea snake                      with a Probability of   0.26 %
    52: thunder snake                  with a Probability of   0.20 %
    61: boa constrictor                with a Probability of   0.18 %
    63: Indian cobra                   with a Probability of   0.04 %
    38: banded gecko                   with a Probability of   0.03 %
    59[...]<jupyter_text>Let's clean up.<jupyter_code>del vgg19<jupyter_output><empty_output><jupyter_text>*ResNet152*<jupyter_code>resnet152 = Wrapper_ResNet152(pretrained=True).build()
resnet152.summary()
# show displaybar
display_progress_bar(0, len(resnet152.get_targeted_layers()) * len(samples))

for targeted_layer in resnet152.get_targeted_layers():
  # reset samples
  samples.reset_index()
  # loop all samples:
  while samples.next():
    # calculate
    gradcam.run(resnet152, samples, targeted_layer)
    gradcam.save_class_activation_images(resnet152, samples, file_start=f'{targeted_layer}_', colormap_name='hsv')
    # update progress
    update_progress_bar()<jupyter_output><empty_output><jupyter_text>Let's have a look at generated images:<jupyter_code># Rendering all images may cause problems with jupyternotebook; uncomment at your own risk
#gradcam.show_class_activation_images(resnet152, samples, figsize=(20, 6))
gradcam.show_animated_gifs(resnet152, samples, *gradcam.save_animated_gif(resnet152, samples, duration=1100))<jupyter_output>Predictions:
    60: night snake                    with a Probability of  48.22 %
    56: king snake                     with a Probability of  38.90 %
    68: sidewinder                     with a Probability of   5.01 %
    66: horned viper                   with a Probability of   1.46 %
    53: ringneck snake                 with a Probability of   1.19 %
    65: sea snake                      with a Probability of   1.01 %
    58: water snake                    with a Probability of   0.84 %
    54: hognose snake                  with a Probability of   0.63 %
    63: Indian cobra                   with a Probability of   0.60 %
    38: banded gecko                   with a Probability of   0.50 %
    52: thunder snake                  with a Probability of   0.41 %
    67: diamondback                    with a Probability of   0.37 %
    62: rock python                    with a Probability of   0.32 %
    59: vine snake                     with a Probability of   0.18 %
    61[...]<jupyter_text>Let's clean up.<jupyter_code>del resnet152<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11445354218583126, 'fraction_numerical': 0.03544728678228144, 'mean_word_length': 3.7188624910007197, 'pattern_counts': {'":': 0, '<': 99, '<?xml version=': 0, '>': 121, 'https://': 7, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 4, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '24', 'dir': 'jupyter-structured-clean-dedup', 'id': '246162', 'license': 'MIT', 'n_tokens_mistral': 14253, 'n_tokens_neox': 13435, 'n_words': 2987, 'path': 'foo.ipynb', 'repo_name': 'JanGaida/xai-with-object-detection', 'size': '39351'}