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 cetcolors python package, listing all the ones available and allowing you to evaluate how perceptually uniform they are for you, your particular monitor, etc. 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:
import numpy as np
import holoviews as hv
import cetcolors as cc
hv.notebook_extension()
import param
with param.logging_level('CRITICAL'):
hv.plotting.mpl.LayoutPlot.v17_layout_format = True
%opts Image [xaxis='bare' yaxis='bare' sublabel_format=None aspect=3 fontsize=13]
# Collapse all colormap aliases into a single string, and then sort by it
inverse = {}
for k,v in cc.cm.items():
if not k[-2:] == "_r":
inverse[v] = inverse.get(v, [])
inverse[v].insert(0,k)
all_cms = {', '.join(reversed(v)):k for (k,v) in inverse.items()}.items()
all_cms.sort()
aliased_cms=[(k,v) for (k,v) in all_cms if "," in k]
def lay_out_in_columns(_list, cols, spacer=None):
"""Given a list, reorder it into the number of columns specified, filling missing items with the given spacer."""
rows = int(np.ceil(len(_list)*1.0/cols))
padded = _list + [spacer] * (rows*cols-len(_list))
return list(np.array(padded).reshape(cols,rows).T.flat)
xs,ys = np.meshgrid(np.linspace(0,1,80), np.linspace(0,1,10))
def colormaps(named_cms,cols=3,array=xs):
images = [hv.Image(array, group=name)(style=dict(cmap=cm))
for name,cm in named_cms]
return hv.Layout(lay_out_in_columns(images,cols,hv.Empty())).display('all').cols(cols)
def colormaps_by_name(names,cols=3,array=xs):
return colormaps({(k,v) for (k,v) in cc.cm.items() if k in names},cols,array)
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.
For Matplotlib users: After importing cetcolors
, all the colormaps shown here will be available by string name prefixed with cet_
, along with reversed versions suffixed with _r
(not shown below). For instance, the cetcolors
version of "hot" is available as "cet_hot" and "cet_hot_r" (as well as the more verbose names "cet_linear_kryw_0_100_c71" and "cet_linear_kryw_0_100_c71_r" as described below).
colormaps([i for i in aliased_cms if "linear" in i[0] and "diverging" not in i[0]])
colormaps([i for i in aliased_cms if "diverging" in i[0]])
colormaps([i for i in aliased_cms if "cyclic" in i[0] or "rainbow" in i[0] or "isoluminant" in i[0]])
This list includes all those above, plus other variants and alternatives. Each colormap has a name in the form:
category_
huesequence_
lightnessrange_c
meanchroma[_
scolorshift_[
rifreversed]]
along with any shorter alias that may be defined above.
colormaps(all_cms, cols=4)
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:
%output size=200
%opts Image [xaxis='bare' yaxis='bare' sublabel_format=None aspect=4 fontsize=10]
sine=np.load("colourmaptest.npy")
colormaps([["viridis"]*2,["inferno"]*2],cols=1,array=sine)
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:
colormaps([["hot"]*2,["jet"]*2],cols=1,array=sine)
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:
colormaps([("fire",cc.m_fire),("rainbow",cc.m_rainbow)],cols=1,array=sine)
We can see the results for all the cetcolors
colormaps below, which can be summarized as:
colormaps(all_cms,cols=1,array=sine)