I hesitated for some time today about where to go next in my discussion about the new default colormap in MATLAB. I finally decided to spend a little time on the reactions so far. Those reactions have ranged from "Great, what took you so long?" to "I hate it; give me back jet!" In the Twitterverse, one scientist proclaimed "End of an era!" Another simply said "yuck." (Jet is still in MATLAB, by the way.)
Today I want to dig into some of those reactions a little bit. Along the way I'll show some experiments with a test pattern to explain some of my comments about jet.
To remind you, last time I summarized 15 years or so of criticisms about rainbow colormaps:
- Rainbow colormaps confuse viewers because there is no natural perceptual ordering of the spectral colors.
- Rainbow colormaps obscure small details in the data.
- Rainbow colormaps mislead viewers by suggesting data features that are not really there. These "phantom features" often take the form of false boundaries. This effect can falsely segment the data.
- Rainbow colormaps lose critical information about high and low data values when displayed or printed on a gray-scale device.
- Rainbow colormaps can be difficult to interpret for some color-impaired viewers.
I have seen a claim that that rainbow incorporates a universally understood order based on associating blue with cold and red with hot. There's no doubt that this order makes sense for some people (although blue flame is hotter than red flame?). I quibble with the "universally understood" part, though. The dominant evidence is that individuals do not universally order the spectral hues the same way unless they refer to spectral order mnemonic, such as "Roy G. Biv." It is well-established, however, that individuals can easily and consistent order colors by relative brightness.
Another commenter said that the rainbow colormap has higher contrast than parula. Yes, this is true. Rainbow goes all the way from dark to bright in the bottom third of the data range, and it goes all the way from bright to dark in the top third of the data range. For some parts of the data range, therefore, there is higher visual contrast than with parula, which uses the entire data range to go from dark to bright. A price paid for this increased contrast, however, is the visual illusions and confusion caused by the reversed roles of dark and bright in different parts of the colormap.
Matteo Niccoli gives the following nice example of a rainbow-based visual illusion. The arrow in the right-hand image below points to something that gives the strong but false impression of being a depression in the bone structure.
Image credit: Matteo Niccoli, "The Rainbow Is Dead ? Long Live the Rainbow!" Copyright 2014 MYCARTA. Used with permission.
And I showed another visual confusion example last time with these visualizations of two different sinusoidal patterns:
These visualizations give the impression that the two sinusoidal patterns have opposite phase, but in reality they have exactly the same phase.
Another commenter said that, with sufficiently familiarity, the bright "stripes" that are characteristic of the rainbow colormap can be an advantage for certain kinds of data. That might be true in some particular cases, but I don't think it's true in general. Let me show an example, using a test pattern, that illustrates how much difficulty the cyan and yellow stripes can cause in interpreting what should be easily recognizable data.
url = 'https://blogs.mathworks.com/images/steve/2014/eia-2000-cropped.png'; I = imread(url); imshow(I) colormap(jet)
Let me label some portions of the visualization and then ask some quetions about it.
text(35,90,'A','FontSize',25,'HorizontalAlignment','center',... 'BackgroundColor','white') text(125,90,'A','FontSize',25,'HorizontalAlignment','center',... 'BackgroundColor','white') text(215,90,'A','FontSize',25,'HorizontalAlignment','center',... 'BackgroundColor','white') text(190,320,'B','FontSize',25,'HorizontalAlignment','center') line([77 160],[225 225],'Color','white','LineWidth',5) text(65,225,'C','FontSize',25,'HorizontalAlignment','right',... 'BackgroundColor','white')
At periodic intervals, the dark blue vertical bars have a bright outline (see the points marked "A"). What is the data significance of these bright outlines? Especially when compared with the dark blue vertical bars that do not have a bright outline?
The vertical bars in between the "A" marks don't have bright outlines. Those bars alternate between dark blue and dark red, a low-contrast combination that is a little difficult to focus clearly on (a red-blue visual illusion called Chromostereopsis).
Above the "B", I see very thin vertical dark bars. When the bars are this thin, it's difficult to distinguish between the dark red and dark blue, which is unfortunate because those colors represent opposite ends of the data range. The bright outlines between the bars is broken up by a diagonal pattern of blue stripes.
The numbers around the border are oddly broken up and hard to decipher.
Let's investigate why some vertical bars are outlined and some not by looking at the data cross-section along the white path marked "C".
c = improfile(I,[77 160],[225 225]); figure plot(c,'*-')
I'll superimpose two lines that show the data values corresponding to the bright cyan and bright yellow in the jet colormap.
hold on plot([1 84],[1 1]*0.375*255,'c','LineWidth',5) plot([1 84],[1 1]*0.625*255,'y','LineWidth',5) hold off
It is now apparent why some vertical bars are outlined but not others. In each transition from low to high and from high to low, there is one data value that is in between. When that one transitional data value happens to be close the data values mapping to cyan or yellow, the vertical bar is shown with a bright outline. Otherwise, there is no outline. Although the effect is visually dramatic and makes some regions of the data look quite different from others, the visual difference has no real data significance.
OK, now let's show this data using the parula colormap.
This visualization is trivially easy to interpret. The vertical bars in the middle are all the same. The digits are easy to read. The visual difference between low data values and high data values has high contrast and is easy on the eyes. And the data above the point marked "B" is seen to have a completely different form than the way it appeared with jet. The stripes have a lower spatial frequency and are seen to be slightly fanning out in direction instead of appearing vertical.
A valid complaint about this example is that it is completely artificial, with data rather unlike what people are likely to be looking at with jet or parula. I'd like to suggesting thinking about it this way, though: If jet can take familiar, easily interpretable data and render it almost unrecognizable, what can jet do to your own data?
Finally, I'll mention that a blog reader commented that using jet made it easy to distinguish positive and negative values from each other in a data set whose values are centered around zero. And yes, jet can be better than parula for that. Jet is bright and relatively unsaturated in the middle, and it is dark and saturated at both ends. These are characteristics often shared by colormaps designed specifically for highlighting a central, specific value and distinguishing the values above and below that. These are called divergent colormaps. In a future post I'll show where to find some good divergent colormaps and how to use them in MATLAB.
Get the MATLAB code
Published with MATLAB® R2014b
Comments are closed.
15 CommentsOldest to Newest
Great post Steve: I think that after all, everyone must think about the data at hand and select the best colormap based in what he is trying to accomplish, instead of just stick with the default, be it parula or jet. A post like this encourages critical thinking.
Hi Steve, nice post as usual :) I’ve got an unrelated question for you; is there a possibility to enhance CONTOUR to handle matrices with jump discontinuties & saddle lines (not sure of the right term), such as you’d get when your source matrix is discrete-valued like a binary or uintX image? Right now it kind of makes a mess of them, and specifying the number or value of contours to use doesn’t help… I know why this is, and that there are problems inherent in asking for a contour plot of this type of data, but it seems to me like there must be some way to get a better (more intuitive-looking?) result, even if it requires a new CONTOURx function that comes with some built-in limitations. Naively I would try putting the contour lines in the centroid of the saddles/planes (what you get via bwmorph(‘thin’)), and ignore the jump discontinuities, and see what that looks like. Am I making any sense? Thanks for your thoughts!
Eric—I’ll mention your use case to our contouring experts. You could try resizing the matrix larger using bilinear interpolation and then contouring that.
Thanks Steve, appreciate that :)
Nice post Steve!
I really like the test pattern you used, and your explanation for the yellow and cyan bands. I wonder if the resulting regions of high lightness juxtaposed to regions of low lightness could be chromatic Mach bands?
Thanks for using my hand x-ray example. As I mentioned on Twitter, that chipped scaphoid bone still troubles me from time to time, especially when I use mouse a lot, but at least I got a lot of miles out of the image….
I just published a new post today with a new colormap for the hardcore fans of Jet and rainbows in general, with high, but controlled lightness contras. It is a rainbow with a saw-tooth shaped lightness profile: http://mycarta.wordpress.com/2014/11/13/new-rainbow-colormap-sawthoot-shaped-lightness-profile/
Matteo—Regarding chromatic mach bands – this is not something I’m familiar with, so I’m not certain.
I like your new saw-tooth rainbow colormap. Thanks for posting it!
Is it possible to make a brightness-ordered colormap with less of a ‘pond-scum’ style appearance? Even though it is deceptive, jet often resembles a rainbow for real data while parula, for most of what I’ve tried, looks like algae.
Steve, not to be a stick in the mud but I keep reading your problem case examples expecting to be somehow fooled by the jet colormap and each time I end up unconvinced because many of these characteristics could be considered features rather than handicaps. Consider the test pattern – it is precisely because the jet map is being used that the non-binary nature of the pattern is immediately visible. In fact I regularly use this property of jet to get intuitive yet precise estimations of how smoothing operations are affecting transitions when applied to image masks. In contrast, when using Parula, this information is almost completely invisible. I would have assumed it was binary if I had not seen the jet version. And the bone structure case study: I’m still scratching my head about that one. Not to say Parula doesn’t have domains in which it excels – for example, when we already have an intuitive concept of how something should appear, such as a photograph. If you use Parula to display a photograph the results are intuitive vs. jet, which can give visually disturbing results. But if you already have a strong sense of how color maps to value (critical) and small amplitude variations are essential image features I think Parula is a pretty tough sell vs. jet.
Ted—When jet is applied to this test pattern, the gross spatial characteristics of the data are completely distorted and obscured. Other gross spatial patterns are strongly suggested that are illusory. For what kind of data is this visual distortion really OK? If the visualization result for familiar data is so confusing, is this helping or hurting for looking at a data set for the first time? Also, I would argue that jet isn’t even that good at highlighting in-between values of low-to-high transitions. It only highlights some of them, those that are pretty close to the bright yellow and cyan points. Some of the vertical bars appear in the visualization to not have in-between values in the transitions, but they do.
Regarding small amplitude variations – these are only highlighted in particular parts of the range for jet. In other parts of the range, jet tends to obscure small variations. In response to a preview of MATLAB R2014b, a user complained that the parula colormap was “noisy.” It turned out that parula had revealed amplitude variation in the user’s data that the jet colormap had obscured.
If you do have a need to highlight data that is specifically 37% and 62% through the range, and getting a good visual understanding of the gross characteristics of data in the rest of the range is not so important, then jet is great.
Fred—I’ll send you a note to ask for a sample or three.
Ted—How would you feel about an argument that the advantages of jet for some situations don’t generically apply, whereas the disadvantages do affect most uses? And that therefore jet isn’t a good choice as the default for MATLAB graphics?
Complete agreement with your statement about Parula being more generically applicable, and it is why I support the switch. With that in mind, regarding the test pattern: if the goal is to make the basic patterns most visible then yeah, Parula is better. No argument there. But, this is because Parula is essentially hiding the defects. Applications abound in which you want to do just the opposite. As a hypothetical example: if I were working on an algorithm for rotating or resizing binary images and these colormaps were my only options I would use jet during algorithm development, but I would use Parula when showing results to my sponsors :). Jet makes the defects obvious: it is trivial to locate/identify them and estimate their severity. In the given test pattern, immediately you see that the problems are in the transition regions and that closely spaced, narrow lines are highly affected by whatever process was applied to the originally binary image. Furthermore, it is entirely because you used the jet map that I know for certain that the degradations are due to a process applied in Matlab, vs. resizing before posting to your blog, or some compression being applied at a later date.
Ted—Thanks for the additional insight! Maybe we weren’t disagreeing as much as I thought.
I like the new colormap. I have for a long time had my own default colormap defined in startup.m because jet is not good for colour blind.
Inspired by this and #endrainbow on Twitter then I have put a new colormap generating tool on the file exchange
It does not guarantee that the colormap works well in b/w or for colour blind, but hsl colours make it easier to avoid some of the common pitfalls.
Aslak—Thanks for contributing to the File Exchange!