Today I want to tell you how and why I made these images:
Sometime later, I came across some material by Peter Kovesi about designing perceptually uniform colormaps (or colourmaps, as Peter writes it).
I was particularly intrigued by a test image that Peter designed for the purpose of visually evaluating the perceptual characteristics of a colormap. Here is the test image:
The image is constructed by superimposing a sinusoid on a linear ramp, with the amplitude of the sinusoid getting smaller as you move away from the top row. Here are three cross-sections of the image: row 1, row 64, and row 128.
url = 'http://peterkovesi.com/projects/colourmaps/cm_MATLAB_gray256.png'; I = im2double(imread(url)); subplot(3,1,1) plot(I(1,:)) axis([1 512 0 1]) title('Row 1') subplot(3,1,2) plot(I(64,:)) axis([1 512 0 1]) title('Row 64') subplot(3,1,3) plot(I(128,:)) axis([1 512 0 1]) title('Row 128')
Here is the basic code for making this test image. I'm going to vary Kovesi's image slightly. I'll add an extra half-cycle of the sinusoid so that it reaches a peak at the right side of the image, and I'll add a full-range linear ramp section at the bottom. (If you look closely at the cross-section curves above, you'll see that the linear ramp goes from 5% to 95% of the range.)
First, compute the ramp.
num_cycles = 64.5; pixels_per_cycle = 8; A = 0.05; width = pixels_per_cycle * num_cycles + 1; height = round((width - 1) / 4); ramp = linspace(A, 1-A, width);
Next, compute the sinusoid.
k = 0:(width-1); x = -A*cos((2*pi/pixels_per_cycle) * k);
Now, vary the amplitude of the sinusoid with the square of the distance from the bottom of the image.
q = 0:(height-1); y = ((height - q) / (height - 1)).^2; I1 = (y') .* x;
Superimpose the sinusoid on the ramp.
I = I1 + ramp;
Finally, add a full-range linear ramp section to the bottom of the image.
I = [I ; repmat(linspace(0,1,width), round(height/4), 1)]; clf imshow(I) title('Colormap test image')
Last week, I posted Colormap Test Image to the File Exchange. It contains the function colormapTestImage, which does all this for you.
I = colormapTestImage;
The function has another syntax, too. If you pass it the name of a colormap, it will display the test image using that colormap. For example, here is the test image with the old MATLAB default colormap, jet.
This test image illustrates why we replaced jet as the default MATLAB colormap. I have annotated the image below to show some of the issues.
Now compare with the new default colormap, parula.
I think that illustrates what we were trying to achieve with parula: perceptual fidelity to the data.
Since I'm talking about parula, I'll finish by mentioning that we need some very subtle tweaks to parula in the R2017b release. So you can compare, I'll show you the original version that shipped with R2014b.
Readers, can you tell what is different? Let us know in the comments.
Get the MATLAB code
Published with MATLAB® R2017a
8 CommentsOldest to Newest
This is another excellent review in the colormap series. All those little—but important—details make the appearance of Matlab figures much better and more professional. I have definitely learned a lot. Keep up the good work!
Regarding your question about parula. It went almost unnoticed in the Matlab release notes. The new version looks better, but I could tell the difference only after reading this blog: it has vivider and better contrasted colors. To me, this is a positive change.
Yaroslav—Yes. One complaint about the original parula was that the region about 60-70% up the scale was relatively dull. We bumped up the “colorfulness” of that region, as well as the bottom region. We also subtly tweaked the overall perceptual uniformity of the scale.
This demonstrates well the advantages of parula vs jet.
But i still tend to use jet more often, for 2 reasons:
1. Parula highs are to bright, and it is somtimes annoying to see them on a white background.
2. Jet has more colors (thre reds), and while parula might be better for fine diffrerences, you can say that jet is more efficient in the sense that it is not “wasting” good colors.
Is it not possible to create a colormap that takes the advantages of each?
Tom—I have seen various attempts to create a perceptually improved rainbow (jet) colormap, and I did not think any of them were very good. Using all available hues is possible and useful for some purposes, such as visualizing data with periodicity. However, using a dark-to-bright scheme for improved perceptual cues is fundamentally incompatible with using all hues.
It isn’t possible to have a one general-purpose colormap that works well for every data visualization scenario. I hope that at some point we will provide a better family of colormaps so that you have multiple choices available for your own needs.
I think there is more saturation in the new version. This is especially noticeable in the greens, which look brighter
@Steve and Tom: The cubehelix colormaps (https://www.mathworks.com/matlabcentral/fileexchange/43700) are perceptually monotonic (albeit not exactly perceptually linear) and provide more dynamic range than parula. Stephen Cobeldick has cleverly designed his cubehelix function to allow you to enter maximum lightness if you’re concerned about the lighter end blending in with a white background. Or you can clip the high end of any colormap a la:
col = parula(300);
It’s worth noting that parula’s yellow can be tough to see on a white background on a computer monitor, but it’s often much more distinct when printed. Unfortunately, if you optimize for one, the other suffers.
Chad—Thanks for the link.