Diverging colormaps for scalp plots: Always have "white" as zero
See original GitHub issueProblem description
I want to compare scalp maps of ERPs across three different tasks. To allow for comparability between the subplots, they all need to share the same colorbar, which I can achieve by the following:
- find the maximum value across all tasks and the minimum value across all tasks
- pass max and min from step 1. into the
vmin
andvmax
arguments when plotting
The result is as follows (note the colorbar):
The problem is, that the white color of the “RdBu_r” colormap should be centered on zero, but this is not the case, as soon as vmin != vmax
.
A workaround is to perform an intermediate step between 1. and 2. from above:
- find the maximum value across all tasks and the minimum value across all tasks
- find the absolute maximum of the max and min identified in step 1.
- pass abs_max from step 2. into both the
vmin
andvmax
arguments when plotting
The result is as follows (note the colorbar … vmin == vmax
):
While it is great to have the white color centered on the zero value, here the problem is that we lose some of the color range into one direction of the scale. In this example, this is visible in the red colors of the positive scale … dark red is at 6 … but the maximum is actually 4, so dark red is never reached.
Potential solutions
One solution to get the full color range and still the zero value centered on white color is described here (also provides a minimal working example to play around with):
http://chris35wills.github.io/matplotlib_diverging_colorbar/
Basically, it is possible by subclassing mpl.color.Normalize
# set the colormap and centre the colorbar
class MidpointNormalize(mpl.colors.Normalize):
"""Normalise the colorbar."""
def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
self.midpoint = midpoint
mpl.colors.Normalize.__init__(self, vmin, vmax, clip)
def __call__(self, value, clip=None):
x, y = [self.vmin, self.midpoint, self.vmax], [0, 0.5, 1]
return np.ma.masked_array(np.interp(value, x, y), np.isnan(value))
… and then passing this class as an argument to plt.imshow
’s parameter called norm
:
im = ax.imshow(mydata, vmin=mymin, vmax=mymax, norm=MidpointNormalize(mymin, mymax, 0.))
What are your thoughts on implementing something like this in MNE-Python? How have other people solved this problem?
Issue Analytics
- State:
- Created 5 years ago
- Reactions:1
- Comments:11 (11 by maintainers)
Top GitHub Comments
I also prefer the second image. But preferences aside, I think that it’s fine for advanced users to be able to do this sort of tweaking when they know what they are doing – but that we should not promote it because a) it’s likely to be abused more often than used properly and b) complicates the code base.
What I we could consider doing is adding the asymmetric cmap conversion function in https://github.com/mne-tools/mne-python/blob/master/examples/time_frequency/plot_time_frequency_erds.py#L54 to
mne.viz.utils
with tests, and then use it in the example. That’s a sort of in-between that allows us not to pollute ourviz
functions with more arguments, but also facilitates code reuse and simplifies things for advanced users.Yes, I need that all the time, and I have included a function in my ERDS example, which doesn’t subclass and is pretty simple. If it is needed in more places, we could make it available somewhere in the
viz
package.