March 7, 2012

The 'jet' colormap must die! Or: how to improve your map plots and create your own nice colormaps.


Introduction.

We all like the colorful map plots of data, produced by the imagesc function in octave (and matlab). Unfortunately, the standard colormap 'jet' is far from beeing optimal. 
Octave and Matlab give other colormaps for some purposes, but the great thing is that you are able to freely create your own maps!

some remarks:

  • i'm sorry for my english
  • the functions shown here should work in GNU octave, a great free and open source matlab clone. With some little changes, this should also be applicable to Matlab.
  • I made quick'n'dirty plots here, which means I disregarded some rules of scaling and size of axis, labels and ticks. I mostly concentrated on colormaps here.

General structure of colormaps.


A Octave colormap basically is just a  N x 3  matrix, which you can freely manipulate. The columns of the matrix represent the three channels Red, Green and Blue. N is the number of intermediate values for each color.
As an example, jet(10) gives us 

octave-3.4.0:1> m = jet(10)
m =
   0.00000   0.00000   0.50000
   0.00000   0.00000   0.94444
   0.00000   0.38889   1.00000
   0.00000   0.83333   1.00000
   0.27778   1.00000   0.72222
   0.72222   1.00000   0.27778
   1.00000   0.83333   0.00000
   1.00000   0.38889   0.00000
   0.94444   0.00000   0.00000
   0.50000   0.00000   0.00000


Each color has values between 0 and 1, representing the intensity of that channel.
I wrote a little function that visualizes the channels of a given map. 






function showRGBchannels(Fignr,Map); 

  x = linspace(0,1, size(Map)(1));
  figure(Fignr)
  lw = 4;
  plot( x, Map(:,1),'color',[1,0,0],'linewidth',lw,
        x, Map(:,2),'color',[0,1,0],'linewidth',lw,
        x, Map(:,3),'color',[0,0,1],'linewidth',lw,
        x, mean(Map,2),'color',[0.7,0.7,0.7],'o')
  xlabel 'fraction'
  ylabel 'intensity'
end


 Let's have a look at the colormap 'summer'.


   showRGBchannels(1,summer(500))

gives us the following plot:




color channels of the 'summer' map
For every map,  'fraction' ranges from 0 to 1 and is mapped to the lowest or highest value of your 3d data. For 'summer', we see that a constant blue value is present. Red and Green increase while fraction increases. 
The gray line shows the mean intensity value. It's an indication of the 'brightness' of the map, if you want so. Here one disadvantage of the summer map becomes visible: it has a very low contrast. The 'brightness' only changes between 0.3 and 0.8, half of the possible value.


Comparing colormaps

To compare the pros and cons of some colormaps, we consider some data to plot. The figure below shows intensity distribution for the Airy Disk , which maybe some of you know from their physics lectures.
A test function: The Airy disk
Basically, the Airy distribution has the typical shape of some peak structure, but there are also little features in the wings.


Why the jet colormap is no good choice here.


This is the standard plot you would get using the imagesc(data) function
Airy Disk, plotted with the jet colormap
When we look at the channels of the jet colormap, we see that the colors appear and also disappear again. The grey curve again gives the mean brightness value. The different regions of the picture are mainly distinguished by the colors.
Color channels of the jet colormap
Besides the fact that not the full contrast is used: the average brightness of the colormap is not a monotonously growing or falling curve. After a first rise it drops again! This is a bad thing. Let me explain you why:

  • There are about 5-10 percent of people out there, who have some form of colorblindness. They only will see some reduced projection of the color space. It is no good idea to pronounce different regions of your plot only by using a certain color.
  • Depending on the form of presentation, you will loose color contrast. Consider a projector with poor color quality or some black-and-white printer.
The two figures below show what happens, when the jet colormap is converted to grayscale. This was done using the freely available program gimp ('colors / desaturate')  with two different algorithms.
Jet map converted to grayscale I

Jet map converted to grayscale, II

The main issue here is that in grayscale representation, information about the 'height' of map regions gets lost. The center of the plot (where the highest intensity is found) has the same gray value as the border regions.


Using other colormaps (that can be printed in grayscale)
One of these maps is already included in octave, it is the 'hot' map. Below is the Channelplot of this map:

RGB channels of the standard hot colormap


As you can see on the gray curve, the brightness of the map increases monotonously. Now let's see how the Airy disk looks like when plotted using the hot map:
Airy Disk, plotted using the hot map.

This already is an improvement, the plot clearly and intuitively points out where the most power of the Airy function is. Unfortunately, the fact that there are more bright rings besides the maximum gets lost. Let's look how we can improve that (see below).

A simple way to create your own, specialized colormaps
As you saw above, there often are reasons to differ from the standard map. I already mentioned, that colormap creation basically is easy, as it is only a Nx3 matrix. You can think of all kinds of curves for your different color channels.
Nevertheless, in most cases it is sufficient to use piecewise linear gradients for the channel function. To make things catchier, i wrote a little function that makes the creation of a colormap simple:


function mymap = colormapRGBmatrices( N, rm, gm, bm)
  x = linspace(0,1, N);
  rv = interp1( rm(:,1), rm(:,2), x);
  gv = interp1( gm(:,1), gm(:,2), x);
  mv = interp1( bm(:,1), bm(:,2), x);
  mymap = [ rv', gv', mv'];
  %exclude invalid values that could appear
  mymap( isnan(mymap) ) = 0;
  mymap( (mymap>1) ) = 1;
  mymap( (mymap<0) ) = 0;
end


It expects 4 input parameters: N is the number of intermediate points that your colormap should have. The other three are matrices that contain the transitions for each channel. Such a matrix should have the following form:

M = [ 0, b1;
      x1,b2;
      ...
      x_n, b_(n+1);
      1, b_(n+1);
    ];

the first column give the fractions, where a brightness value should be defined. The second column should contain the brightness levels. Make sure to start the first column at 0 and end it with 1!

A simple, linear grayscale map can be created with


M = [0,0;1,1;];
simplegray = colormapRGBmatrices( 256, M, M, M);

where we use the same matrix three times (which always results in gray). Now the trick is to figure out, which region of your data you want to emphasize. Knowing the fraction in which the magic happens, you can set accents using sharp color or brightness transitions. This mostly can be done by setting two or three extra points per matrix, which is no big effort.

Simple example: improve the hot colormap.
Let's go back to the airy disk. As we saw in the figure above, the hot colormap gives some natural feeling about intensities. Now we want to pronounce the second intensity ring a little bit more. This can be done by adding one extra node point in the red channel, which creates a strong gradient in color and brightness. We set the matrices as follows


MR=[0,0; 
    0.02,0.3; %this is the important extra point
    0.3,1;
    1,1];
MG=[0,0;
    0.3,0; 
    0.7,1;
    1,1];
MB=[0,0; 
    0.7,0;
    1,1];
hot2 = colormapRGBmatrices(500,MR,MG,MB);

showRGBchannels(2,hot2);

The resulting channelplot looks like this:
color channels of 'hot2'
You can see, that on the first some percent of the scale, the red channel is dramatically increased, and so does the overall brightness. This results in a better visibility of the second intensity ring of the airy function.

Airy disk, plotted using 'hot2'
There are more easy modifications. Assume, you make a presentation and are bound to some given corporate design (e.g. blue-white or so). You can switch the order of the matrices to generate colormaps exhibiting the overall same brightness gradient but with different colors. Lets make a blueish 'hot2' map:

bluehot = colormapRGBmatrices(mcolors,MB,MG,MR);


some blueish 'hot2' colormap
That was easy, wasn't it? 
So I hope I could  make clear that thinking about colormaps indeed is worth the time.  Using user-defined colormaps gives you the ability to create plots that fit to your data and pronounce the remarkable features. Besides, it's nice to play around with such things.

[edit: code for plotting the Airy disk field / intensity]


  clear all;
  more off;
  x = linspace(-20,20,500);


%compose a grid
  [xx,yy] = meshgrid(x,x);


% calculate the radius r for xx and yy pairs
% as the airy disc has a  circular symmetry
  r = sqrt( xx.^2 + yy.^2 );


%calculate the electrical field
  AiryField = besselj(1,r)./r;
% intensity = abs( field ) ^2
  AiryIntensity = abs(AiryField).^2;



% plot them
  figure(1)
  imagesc(x,x,AiryField)


  figure(2)
  imagesc(x,x,AiryIntensity);


EDIT 2013/13/04: I stumbled upon a paper from a research group that discusses the choice of colormaps with regard to people that exhibit red-green perception deficiencies:


EDIT 2013/23/04: There's another nice page which has a dedicated view on the perception of plots/presentations by colorblind people:

9 comments:

  1. Thanks for this code! It allowed me to really make my color plots look the way I needed them to

    ReplyDelete
  2. Thanks a lot! Often thought about this problem, but never tried to solve it myself..

    ReplyDelete
  3. Thanks for this! Do you know how to get a nice looking color palette for visualizing plotted curves (say under 10 curves). One can use jet(numberOfcurves), but how to you get better "matching" colors for optimal visualization, a little bit like explained here:

    http://devmag.org.za/2012/07/29/how-to-choose-colours-procedurally-algorithms/

    Thanks!

    ReplyDelete
  4. Very useful.

    @Anonymous from February 7, 2013 at 9:11 AM: have you looked into this: http://colorbrewer2.org/

    ReplyDelete
  5. Really nice article. Thumbs up!

    ReplyDelete
  6. What general-purpose colormap should be used as the default, then?

    ReplyDelete
    Replies
    1. i guess my point was that there is *no* good general purpose colormap. I would say for about 80% of the cases, in which the z-range is from zero to some number, something like the 'hot' colormap (increasing overall luminosity) would be a good choice.
      the jet colormap is good, when positive and negative values shall be distinguished (e.g. the amplitude of electrical field distribution)

      Delete