Your colour map is bad, here’s how to fix it
By: Thomas Bury
Tags: Visualization, Colors, Data Science, Matplotlib, Python

Your colour map is bad, here’s how to fix it

Different scientific colour maps applied to the result of GRHD simulation (see https://arxiv.org/ftp/arxiv/papers/1906/1906.11241.pdf)

Can you remember those papers with those blurry excel charts and unreadable colours? No? It’s pretty normal, no one does. Even if the content is king, the presentation provides the incentive to read the story you’re telling.

An important factor is how well the information are colour-encoded. The accurate representation of data is essential. Many common colour maps distort data through uneven colour gradients and are often unreadable to those with colour-vision deficiency. An infamous example is the jet colour map. These colour maps do not render all the information you want to illustrate or even worse render false information through artefacts. Scientist or not, your goal is to communicate visual information in the most accurate and appealing fashion. Moreover, do not overlook colour-vision deficiency, which represents 8% of the (Caucasian) male population.

What colour map would you choose for your presentation? The ugly Jet or the smoother Thermal one? See the https://github.com/ThomasBury/scicomap

🗺 Colour spaces

A colour space is a specific organization of colour, insuring reproducible representations of colour. The main principles are the following.

Perceptual uniformity is the idea that Euclidean distance between colours in colour space should match human colour perception distance judgements. For example, a blue and red that are at a distance d apart should look as discriminable as green and purple that are at a distance d apart.

The CAM02-UCS colour space (Uniform Colour Space) is a good basis for accurate information representation. Its three coordinates are usually denoted by J’, a’, and b’. And its cylindrical coordinates are J’, c’, and h’. The perceptual colour space Jab is similar to Lab. However, Jab uses an updated colour appearance model that in theory provides greater precision for discriminability measurements.

Lightness: also known as value or tone, is a representation of a colour’s brightness

also known as value or tone, is a representation of a colour’s brightness Chroma: the intrinsic difference between a colour and grey of an object

the intrinsic difference between a colour and grey of an object Hue: the degree to which a stimulus can be described as similar to or different from stimuli that are described as red, green, blue, and yellow

The CAM02-UCS is the space used by Matplotlib’s Viridis, Plasma, Magma colour maps that you may already know.

🥽 Visual encoding of information

We can use those degrees of freedom to encode information. The best practices being:

Lightness J’: for a scalar value, intensity. It must vary linearly with the physical quantity

hue h’ can encode an additional physical quantity, the change of hue should be linearly proportional to the quantity. The hue h’ is also ideal in making an image more attractive without interfering with the representation of pixel values.

chroma is less recognizable and should not be used to encode physical information

🤔 What makes a colour map a good colour map?

Following the references and the theories, a good colour map is a colour map that does not introduces artefact and appear uniform to the human eyes. The uniformization is performed by

Making the colour map linear in J’

Lifting the colour map (making it lighter, i.e. increasing the minimal value of J’)

Symmetrizing the chroma to avoid further artefacts

Avoid kinks and edges in the chroma curve

Bitonic symmetrisation or not

🛠 How to fix a bad colour map?

Using the scicomap package (scientific colour maps), it is easy to fix a large variety of colour maps (sequential, diverging, converging, multi-sequential, circular, qualitative). You can either choose a built-in colour map or use a Matplotlib colour map of the previous kinds.

Let’s take an example of a common colour map which is a bad choice: Matplotlib hot cmap:

import scicomap as sc # Import the colour map

sc_map = sc.ScicoSequential(cmap="hot") # Fix the colour map

sc_map.unif_sym_cmap(lift=40, bitonic=False, diffuse=True) # Visualize the rendering of information

fig_examples=sc_map.draw_example(cblind=False, figsize=(22, 4))

fig_cmap=sc_map.assess_cmap(figsize=(22,10))

Left panel: the original colour map. Too dark, the lightness is not linear and has edges, the chroma is asymmetric and not smooth. Right panel: the same colour map but fixed: brighter, linear, smooth and symmetric. The visualization is built upon the EHTplot library

The top row illustrate the raw hot cmap and the bottom row the fixed colour map. The raw colour map introduces a lot of artefacts, stepwise rendering of the pyramid (smooth function excepted the edges). The artefacts are almost absent after the fix and is much more readable.

How to use with Matplotlib

import matplotlib.pyplot as plt

import matplotlib as mpl

import scicomap as sc

from scicomap.utils import _fn_with_roots # load the color map (built-in or any mpl compatible cmap)

div_map = sc.ScicoDiverging(cmap='watermelon')

# mpl_cmap_obj = plt.get_cmap("PRGn")

# div_map = sc.ScicoDiverging(cmap=mpl_cmap_obj) # correct the colormap

div_map.unif_sym_cmap(lift=15,

bitonic=False,

diffuse=True) # get the fixed color map

fixed_cmap = div_map.get_mpl_color_map()

print(type(fixed_cmap)) # use it as you like

im = _fn_with_roots()

norm = mpl.colors.CenteredNorm()

divnorm = mpl.colors.TwoSlopeNorm(vmin=-1, vcenter=0, vmax=1.25)

fig = plt.figure(figsize=(3,3), facecolor="white")

ax = fig.add_subplot(1, 1, 1, facecolor="white")

pos = ax.imshow(im, cmap=fixed_cmap, aspect="auto", norm=divnorm)

fig.colorbar(pos, ax=ax);

Plotting with a corrected cmap

🌈 Do not overlook the colour-deficient rendering

Up to 8% of (Caucasian) males are colour-deficient users, do not overlook that when crafting your chart. Using the Scicomap library, you can easily put yourself in the shoes of someone who cannot clearly distinguish colours.

In the examples you can draw using scicomap, you can visualize what a colour-deficient would see and if the colour map is appropriate or not. Especially a red-green colour map is not the best choice, even if corrected to appear uniform for non colour-deficient users

div_map = sc.ScicoDiverging(cmap='watermelon')

div_map.unif_sym_cmap(lift=10,

bitonic=False,

diffuse=True)

f = div_map.draw_example()

How Red-greenish colour maps will render to different type of colour-deficient users

You can even compare quickly different colour maps

c_l = ["cividis", "inferno"]

f = sc.plot_colorblind_vision(ctype='sequential', cmap_list=c_l, figsize=(30, 4), n_colors=11, facecolor="black")

🧰 More features

The scicomap package provides more features (diverging, diverging, multi-sequential, circular and qualitative colour maps) that I’ll let you explore. No excuses for using the jet colour map anymore ✌

📚 References