User Guide

Perceptually uniform colormaps from CET

Peter Kovesi at the Center for Exploration Targeting created a very useful set of perceptually uniform colormaps , many of which can replace the highly non-uniform colormaps provided with Python plotting programs. Here we will show how to use them via the new colorcet Python package, listing all the ones available and allowing you to evaluate how perceptually uniform they are for you, your particular monitor, etc. Download and installation instructions are at the github site .

We will plot them using matplotlib via holoviews , but identical bokeh palettes are also provided, and both Bokeh palettes and Matplotlib colormaps are usable in datashader . Thus these colormaps can be used in any of those Python packages, as well as any other package that accepts a Python list of normalized RGB tuples or hex colors. See Peter's site to download versions for other non-Python packages.

We first make some utilities for sorting and visualizing colormaps

In [1]:
import numpy as np
import holoviews as hv
import colorcet as cc

In [2]:
xs, ys = np.meshgrid(np.linspace(0, 1, 80), np.linspace(0, 1, 10))

def colormap(name, cmap=None, array=xs):
    if cmap is None:
        cmap =[name]
    options = hv.opts.Image(xaxis=None, yaxis=None, aspect=3, fig_size=200, cmap=cmap)
    return hv.Image(array, group=name).opts(options)

def colormaps(named_cms, array=xs, cols=3, skip_r=True, size=100):
    if skip_r:
        named_cms = list(filter(lambda x: not x[0].endswith('_r'), named_cms))
    options = hv.opts.Layout(sublabel_format=None, fig_size=size)
    return hv.Layout([colormap(name, cm, array) for name, cm in named_cms]).opts(options).cols(cols)

Accessing the colormaps

After importing colorcet as cc , all the colormaps shown in this notebook will be available for use in different forms. It's a bit difficult to describe, but the idea is that colorcet should have at least one such form convenient for any particular application. There are two different basic versions for each colormap, each of which is fundamentally a list of 256 distinct colors:

  1. A Bokeh-style palette, i.e., a Python list of RGB colors as hex strings, like ['#000000', ..., '#ffffff']
  2. If matplotlib is installed and importable, a Matplotlib LinearSegmentedColormap using normalized magnitudes, like LinearSegmentedColormap.from_list("fire",[ [0.0,0.0,0.0], ..., [1.0,1.0,1.0] ], 256)

The Bokeh-compatible palettes are provided as attributes in the colorcet namespace, with long names prefixed with b_ or a short name (if any) with no prefix. E.g. linear_kryw_0_100_c71 can be accessed as cc.b_linear_kryw_0_100_c71 or by the short name , which both return the same Bokeh palette. These names should tab complete once cc has been imported. Because Bokeh palettes are just Python lists, you can always reverse them using normal Python syntax, e.g. list(reversed( , or use subsets of them with slice notation, e.g.[25:] . If you want to access the palettes by string name, they are also collected into a dictionary named palette , so you can use cc.palette["linear_kryw_0_100_c71"] or cc.palette["fire"] or ; whichever is more convenient. Finally, the subset of colormaps that have short, readable names are available separately, accessible as or cc.palette_n["fire"] , e.g. for use in GUI widgets selecting a colormap by readable name.

The Matplotlib colormaps are also provided as tab-completable attributes, but consistently with a prefix m_ , e.g. cc.m_linear_kryw_0_100_c71 or cc.m_fire . Already reversed versions are also available, as cc.m_linear_kryw_0_100_c71_r or cc.m_fire_r . The same colormaps are also registered with matplotlib's string-based dictionary with the prefix cet_ , making them available by name within various matplotlib functions (e.g. cet_linear_kryw_0_100_c71 , cet_linear_kryw_0_100_c71_r , cet_fire , or cet_fire_r ). Finally, if you want to access the colormaps by string name without using Matplotlib's registry, they are also stored in the dictionary, e.g.["linear_kryw_0_100_c71"] ,["linear_kryw_0_100_c71_r"] ,["fire"] , ,["fire_r"] , or .

In each case, the colormap names used are the shortest ones that are expected to be unique in that context, and in practice you are only likely to need one of these forms for any particular application.

Named colormaps

The full list of colormaps included will be shown below, but a useful subset of these maps that cover the various types have been given more convenient names, and we will focus on those first.

In [3]:

Linear (sequential) colormaps, for plotting magnitudes:

In [4]:
colormaps([(name, cm) for name, cm in if "linear" in name and "diverging" not in name])

Diverging colormaps, for plotting magnitudes increasing or decreasing from a central point:

In [5]:
colormaps([(name, cm) for name, cm in if "diverging" in name])

Misc colormaps:

  • cyclic: for cyclic quantities like orientation or phase (where the highest and lowest values need the same color)
  • isoluminant: to highlight low spatial-frequency information
  • rainbow: to highlight local differences in sequential data
In [6]:
colormaps([(name, cm) for name, cm in if "cyclic" in name or "isoluminant" in name or "rainbow" in name])

All colormaps

Each colormap has a name in the form:


Some also have a shorter name.

In [7]:

Testing perceptual uniformity

Peter Kovesi created a test image with a sine grating modulation of intensity , where modulation gain decreases from top to bottom, which helps evaluate perceptual uniformity of a colormap at a glance. The matplotlib maintainers use different definitions of perceptually uniform (uniformity in a different color space), but the new matplotlib perceptually uniform colormaps do well at Peter's test image:

In [8]:
sine = np.load("../assets/colourmaptest.npy")
In [9]:
colormaps([("viridis", "viridis"), ("inferno", "inferno")], cols=1, array=sine, size=200)

Here the sine grating for a uniform colormap should be visible as a fine-toothed comb with teeth that gradually become less visible from top to bottom. The further down the comb these teeth are visible, the higher the discriminability of magnitudes at that location in the colormap. Thus a perceptually uniform colormap, like the two above, should have teeth that visible at the same length for all colors.

You can also use these images to evaluate the overall level of discriminability provided by a given colormap -- the longer the visible area of teeth, the better this colormap allows you to discriminate fine differences in magnitude. Here the inferno map seems to have better discriminability than the viridis map, despite both being perceptually uniform.

The default colormaps that have traditionally been used with Matlab, Matplotlib, and HoloViews are clearly not perceptually uniform -- all the green and yellow areas are nearly indistinguishable:

In [10]:
colormaps([("hot", "hot"),("jet", "jet")], cols=1, array=sine, size=200)

Thus those colormaps should be avoided if at all possible, to avoid generating misleading visualizations. Compare these two to the perceptually uniform versions provided by this package:

In [11]:
colormaps([("fire", cc.m_fire), ("rainbow", cc.m_rainbow)], cols=1, array=sine, size=200)

We can see the results for all the colorcet colormaps below, which can be summarized as:

  • "linear" colormaps all work well by this criterion
  • "diverging" colormaps typically have discontinuities in perceptual discriminability around the central value
  • "cyclic" colormaps with repeating colors tend to have discontinuities at 1/4 and 3/4 of the way through the cycle, or at other locations if shifted.
  • "isoluminant" colormaps typically show no visible modulation, because without luminance cues humans can only discriminate low spatial-frequency modulations (i.e., much wider teeth would be needed for evaluating such colormaps)
  • Some of the "rainbow" colormaps appear to have a perceptual discontinuity around the color red, the reasons for which are not yet clear.
In [12]:
colormaps(, cols=2, array=sine, size=200)