Getting started
Let's assume you took the following image img
, reshaped it to WHCN format (width, height, color channels, batch dimension) and ran it through a vision model:
using Images
img = load(joinpath(asset_dir, "img1.png")) # load image file

You might use an input space attribution method (for example from ExplainableAI.jl) to determine which parts of the input contributed most to the "saxophone" class.
Let's load such an attribution x
in WHCN format:
x = load(data_heatmap, "x") # load precomputed array from file
typeof(x)
Array{Float32, 4}
size(x)
(224, 224, 3, 1)
To make this attribution more interpretable, we can visualize it as a heatmap:
using VisionHeatmaps
heatmap(x)
By default, to support batched explanations, a vector of heatmaps is returned. Since our following examples don't use batches, we will use the only
function to unpack singleton heatmaps:
using VisionHeatmaps
heatmap(x) |> only

Custom heatmapping pipelines
VisionHeatmaps internally applies a sequence of image transformations in what we call a Pipeline
. The default pipeline corresponds to:
pipe = NormReduction() |> ExtremaColormap() |> FlipImage()
Pipeline(
NormReduction(),
ExtremaColormap(:batlow),
FlipImage(),
)
We can apply this pipeline by passing it to heatmap
:
heatmap(x, pipe) |> only

In the following subsection, we will explain and modify this pipeline step by step.
Color channel reduction
For arrays with multiple color channels, the channels need to be reduced to a single scalar value for each pixel, which is later mapped onto a color scheme.
Several transformats are available for this purpose. Let's compare the two most commonly used ones. NormReduction
reduces color channels in the array by taking their norm, whereas SumReduction
takes the sum:
pipe = NormReduction() |> ExtremaColormap() |> FlipImage()
heatmap(x, pipe) |> only

pipe = SumReduction() |> ExtremaColormap() |> FlipImage()
heatmap(x, pipe) |> only

Colormaps
To map the now color-channel-reduced array onto a color scheme, we first need to normalize all values to the range $[0, 1]$.
For this purpose, two colormapping transforms are available:
ExtremaColormap
: normalizes colorscheme to the minimum and maximum value in the array.CenteredColormap
: normalizes colorscheme to the maximum absolute value of the array. Values of zero will be mapped to the center of the color scheme.
Since NormReduction
only yields positive values, it is well suited for ExtremaColormap
. SumReduction
on the other hand can yield positive and negative values. If zero-values are meaningful, using a divergent color scheme with CenteredColormap
can be the right choice:
pipe = NormReduction() |> ExtremaColormap() |> FlipImage()
heatmap(x, pipe) |> only

pipe = SumReduction() |> CenteredColormap() |> FlipImage()
heatmap(x, pipe) |> only

Outlier removal
While this isn't part of the default heatmapping pipelines, previous heatmaps visibly emphasized three "dots" on the neck of the saxophone. Very high values in explanations tend to desaturate colors. For this purpose, we provide the adaptive PercentileClip
. By default, it clips the 0.1-th and 99.9-th percentiles of values.
pipe = SumReduction() |> PercentileClip() |> CenteredColormap() |> FlipImage()
heatmap(x, pipe) |> only

Custom color schemes
We can use a custom color scheme from ColorSchemes.jl in our colormap:
using ColorSchemes
pipe = NormReduction() |> ExtremaColormap(:jet) |> FlipImage()
heatmap(x, pipe) |> only

pipe = NormReduction() |> ExtremaColormap(:viridis) |> FlipImage()
heatmap(x, pipe) |> only

We strongly suggest to only use sequential color schemes with ExtremaColormap
and divergent color schemes with CenteredColormap
.
Refer to the ColorSchemes.jl catalogue for a gallery of available color schemes.
Overlays
Singleton heatmaps can be overlaid on top of the original image. This can be used to recreate CAM-like heatmaps (usually in combination with ResizeToImage
):
pipe = NormReduction() |> PercentileClip() |> ExtremaColormap(:jet) |> FlipImage() |> AlphaOverlay()
heatmap(x, img, pipe) |> only

Heatmapping batches
heatmap
can also be used to visualize input batches. Let's assume we computed an input space attribution batch
for the following images.
imgs = [load(joinpath(asset_dir, f)) for f in ("img1.png", "img2.png", "img3.png", "img4.png", "img5.png")] # load image files
Once again, we assume that batch
is in WHCN format:
batch = load(data_heatmaps, "x") # load precomputed array from file
typeof(batch)
Array{Float32, 4}
size(batch)
(224, 224, 3, 5)
Calling heatmap
will automatically return an vector of images:
heatmap(batch)
These heatmaps can be customized as usual:
pipe = SumReduction() |> CenteredColormap() |> FlipImage()
heatmap(batch, pipe)