2
votes

Is it possible to define a matplitlib colormap which logarithmically interpolates between two given colors?

Background: When plotting arrays in matplotlib via the imshow(...) command, colormaps are used to assign a color to each data value. Often, predefined colormaps are used from matplotlib.cm but also more enhanced colormaps can be created using matplotlib.colors.LinearSegmentedColormap. Typically, in these colormaps the color value linearly changes with the data value.

Hint: My question is not, how to plot an array logarithmically. This is usually done, by keeping the linear colormap and some tricks (either by plotting log(array) and replacing the labels x on the colorbar by 10^x, or by explicitly changing the normalization behavior of the plot command). Here, I explicitly need the colormap instance.

1
So imshow(data,norm=matplotlib.colors.LogNorm()) is not what you are looking for? That does essentially what you are describing in your hint. Is it that you want to plot the array logarithmically, but have the colorbar linearly plotted? - burnpanck
@burnpanck no, that's what I do not want, I need an autonomous colormap instance, that works logarithmically. The reason is that I use a different software which produces plots using matplotlib colormaps. - flonk
Ok, what kind of software, in what way do you pass the colormap to it? The reason is, one could quite easily write a custom Colormap subclass that just wraps another colormap, applying the linear-to-log transformation. However, it's not clear if such a custom class can be passed. - burnpanck
@burnpanck Well, it is a private plotting library of our research group. I think your suggestion would solve my problem, as the plotting software just applies the colormap to compute colors and thus should not care if it is a LinearSegementedColormap or any other instance derived from Colormap. - flonk

1 Answers

2
votes

You can provide a fake Colormap that applies your desired normalisation, before passing it to the real colormap. Here is such a colormap implementation:

import matplotlib as mpl

class ReNormColormapAdaptor(mpl.colors.Colormap):
    """ Colormap adaptor that uses another Normalize instance
    for the colormap than applied to the mappable. """
    def __init__(self,base,cmap_norm,orig_norm=None):
        if orig_norm is None:
            if isinstance(base,mpl.cm.ScalarMappable):
                orig_norm = base.norm
                base = base.cmap
            else:
                orig_norm = mpl.colors.Normalize(0,1)
        self._base = base
        if (
            isinstance(cmap_norm,type(mpl.colors.Normalize))
            and issubclass(cmap_norm,mpl.colors.Normalize)
        ):
            # a class was provided instead of an instance. create an instance
            # with the same limits.
            cmap_norm = cmap_norm(orig_norm.vmin,orig_norm.vmax)
        self._cmap_norm = cmap_norm
        self._orig_norm = orig_norm

    def __call__(self, X, **kwargs):
        """ Re-normalise the values before applying the colormap. """
        return self._base(self._cmap_norm(self._orig_norm.inverse(X)),**kwargs)

    def __getattr__(self,attr):
        """ Any other attribute, we simply dispatch to the underlying cmap. """
        return getattr(self._base,attr)

Since the values it gets will already be normalised to [0,1), it needs to know that previous normalisation to undo it (given as orig_norm). Leave it empty if you want to apply the colormap to unnormalised values:

cmap = ReNormColormapAdaptor(mpl.cm.jet,mpl.colors.LogNorm(vmin,vmax))

If you already have a ScalarMappable, then you can pass it in stead of the colormap, from where both the colormap, the previous normalisation and the new normalisation limits will be taken:

import matplotlib.pyplot as plt

scalar_mappable = plt.imshow(C);
scalar_mappable.set_cmap(ReNormColormapAdaptor(
    scalar_mappable,
    mpl.colors.LogNorm
))