create ListedColormap with different alpha values

Hi all,

I'd like to create a ListedColormap with different alpha values, like

...
        [ 1. , 1. , 1. , 0.65],
        [ 1. , 1. , 1. , 0.66],
        [ 1. , 1. , 1. , 0.67],
        [ 1. , 1. , 1. , 0.68],
        [ 1. , 1. , 1. , 0.69],
        [ 1. , 1. , 1. , 0.7 ],
        [ 1. , 1. , 1. , 0.71],
        [ 1. , 1. , 1. , 0.72],
        [ 1. , 1. , 1. , 0.73],
        [ 1. , 1. , 1. , 0.74],
        [ 1. , 1. , 1. , 0.75],
        [ 1. , 1. , 1. , 0.76],
        [ 1. , 1. , 1. , 0.77],
        [ 1. , 1. , 1. , 0.78],
        [ 1. , 1. , 1. , 0.79],
...

meaning "map all my values to white, but with different alpha".

When I do something like this:

myColormap = matplotlib.colors.ListedColormap(my_rgba_array)
contourf(x,y,data,arange(0.1,1.05,0.05), cmap=myColormap)

it seems like my alpha values get changed to "1" deep inside matplotlib.colors.Colormap/LinearSegmentedColormap/ListedColormap.

After looking at the source of matplotlib.colors, it seems to me that different alpha values are something Colormap is not designed for.

Something like:
myColormap._lut = my_rgba_array
doesn't work.

What would be the best approach to create a Colormap with different alphas? ListedColormap would be convinient, but I can also go with LinearSegmentedColormap.

Regards
Simon

Yes, it looks like the colormap only holds the RGB channels, but it
also looks fairly straightforward to patch the code to support the
fourth channel. Is this something you'd like to tackle?

JDH

···

On Fri, Nov 21, 2008 at 2:45 AM, Simon Kammerer <kontakt@...2402...> wrote:

After looking at the source of matplotlib.colors, it seems to me that
different alpha values are something Colormap is not designed for.

I took a stab at it, how does this look?

I also took the liberty of adding alpha to LinearSegmentedColormap and
updated its docstring changing two somewhat ambiguous uses of the word
'entry' with 'key' and 'value'.

tested with

In [1]: import matplotlib; import numpy as np
In [2]: my_rgba_array= np.array( [[ 1., 1., 1., 0.65],
        [ 1., 0., 0., 0.79]])
In [3]: myColormap = matplotlib.colors.ListedColormap(my_rgba_array)

In [4]: myColormap.__call__(.1)
Out[4]: (1.0, 1.0, 1.0, 0.65000000000000002)

In [5]: myColormap.__call__(.9)
Out[5]: (1.0, 0.0, 0.0, 0.790000000000000

In [6]: my_rgba_array= np.array( [ [ 1. , 1. , 1. ],
       [ 1. , 0. , 0. ]])

In [7]: myColormap = matplotlib.colors.ListedColormap(my_rgba_array)

In [8]: myColormap.__call__(.1)
Out[8]: (1.0, 1.0, 1.0, 1.0)

In [9]: myColormap.__call__(.9)
Out[9]: (1.0, 0.0, 0.0, 1.0)

cheers,
Paul Ivanov

John Hunter, on 2008-11-21 05:52, wrote:

rgba_colormap.diff (2.45 KB)

···

On Fri, Nov 21, 2008 at 2:45 AM, Simon Kammerer <kontakt@...2402...> wrote:

After looking at the source of matplotlib.colors, it seems to me that
different alpha values are something Colormap is not designed for.

Yes, it looks like the colormap only holds the RGB channels, but it
also looks fairly straightforward to patch the code to support the
fourth channel. Is this something you'd like to tackle?

JDH

-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-users

Hey Paul,

Thanks for taking this on. I haven't tested this but I read the patch
and have some inline comments below. Some additional comments:

  * the patch should include a section in the CHANGELOG and
API_CHANGES letting people know what is different.

  * you should run examples/tests/backend_driver.py and make sure all
the examples still run, checking the output of some of the mappable
types (images, scaltter, pcolor...)

  * it would be nice to have an example in the examples dir which
exercises the new capabilities.

See also, in case you haven't,
http://matplotlib.sourceforge.net/devel/coding_guide.html, which
covers some of this in more detail.

Thanks again! Comments below:

    Index: lib/matplotlib/colors.py

···

On Sun, Nov 23, 2008 at 2:01 AM, Paul Ivanov <pivanov314@...287...> wrote:

I took a stab at it, how does this look?

I also took the liberty of adding alpha to LinearSegmentedColormap and
updated its docstring changing two somewhat ambiguous uses of the word
'entry' with 'key' and 'value'.

    ===================================================================
    --- lib/matplotlib/colors.py (revision 6431)
    +++ lib/matplotlib/colors.py (working copy)
    @@ -452,7 +452,7 @@
             self._isinit = False

    - def __call__(self, X, alpha=1.0, bytes=False):
    + def __call__(self, X, alpha=None, bytes=False):
             """
             *X* is either a scalar or an array (of any dimension).
             If scalar, a tuple of rgba values is returned, otherwise
    @@ -466,9 +466,10 @@
             """
You need to document what alpha can be here: what does None mean, can
it be an array, scalar, etc...

             if not self._isinit: self._init()
    - alpha = min(alpha, 1.0) # alpha must be between 0 and 1
    - alpha = max(alpha, 0.0)
    - self._lut[:-3, -1] = alpha
    + if alpha:

I prefer to explicitly use "if alpha is None", since there are other
things that would test False (0, [], '') that you probably don't mean.

    + alpha = min(alpha, 1.0) # alpha must be between 0 and 1
    + alpha = max(alpha, 0.0)

You should be able to use np.clip(alpha, 0, 1) here, but we should
consider instead raising for illegal alpha values since this will be
more helpful to the user. I realize some of this is inherited code
from before your changes, but we can improve it while making this
patch.

    + self._lut[:-3, -1] = alpha
             mask_bad = None
             if not cbook.iterable(X):
                 vtype = 'scalar'
    @@ -558,9 +559,10 @@
         def __init__(self, name, segmentdata, N=256):
             """Create color map from linear mapping segments

    - segmentdata argument is a dictionary with a red, green and blue
    - entries. Each entry should be a list of *x*, *y0*, *y1* tuples,
    - forming rows in a table.
    + segmentdata argument is a dictionary with red, green and blue
    + keys. An optional alpha key is also supported. Each value
    + should be a list of *x*, *y0*, *y1* tuples, forming rows in a
    + table.

             Example: suppose you want red to increase from 0 to 1 over
             the bottom half, green to do the same over the middle half,
    @@ -606,6 +608,8 @@
             self._lut[:-3, 0] = makeMappingArray(self.N,
self._segmentdata['red'])
             self._lut[:-3, 1] = makeMappingArray(self.N,
self._segmentdata['green'])
             self._lut[:-3, 2] = makeMappingArray(self.N,
self._segmentdata['blue'])
    + if self._segmentdata.has_key('alpha'):
    + self._lut[:-3, 3] = makeMappingArray(self.N,
self._segmentdata['blue'])

Is this what you meant? I think you would use 'alpha' rather than
'blue' here, no?

             self._isinit = True
             self._set_extremes()

    @@ -664,11 +668,10 @@

         def _init(self):
    - rgb = np.array([colorConverter.to_rgb(c)
    + rgba = np.array([colorConverter.to_rgba(c)
                         for c in self.colors], np.float)
             self._lut = np.zeros((self.N + 3, 4), np.float)
    - self._lut[:-3, :-1] = rgb
    - self._lut[:-3, -1] = 1
    + self._lut[:-3] = rgba
             self._isinit = True
             self._set_extremes()

Hey John and the rest of the MPL gang:

I've made the changes you suggested, but the problem is looking to be
deeper than it seemed. I'm also moving this conversation to
matplotlib-devel, since that's probably the more appropriate place for
it.

This updated patch allows for the creation of colormaps with various
alphas, but there is likely more work to be done so that mpl can
consistently make use of it (because it seems like all built-in cmaps
are RGB, not RGBA).

In trying to come up with an example that exercises the new
capabilities, I found out that methods like scatter and countourf modify
the colormap you give them and reset all of the alpha values to 1.

I think this is because inside collections, we pass self._alpha, which
is the Artist._alpha, and 1.0 by default, when making calls such
as:
    _colors.colorConverter.to_rgba_array(c, self._alpha)

...Thus resetting all of alpha values.

I was able to get around this by allowing collections to take on an
alpha value of None, and then passing alpha=None to scatter and
countourf, for example. There are probably other places where such a
change should be done, unless someone has a better idea for how do do
this. I updated examples/pylab/plot_scatter.py to show off the new
capability.

Another thing that I was unable to get around is that if you now make a
plot using the same colormap but omit the alpha=None parameter, or set
it to something other than None, it will reset the alpha values on the
previous plot:

    figure(2)
    c = scatter(theta, r, c=colors, s=area,cmap=myColormap,alpha=None)

will do the right thing, but calling scatter without alpha=None

    figure(3)
    d = scatter(theta, r, c=colors, s=area,cmap=myColormap)
or
    d = scatter(theta, r, c=colors, s=area,cmap=myColormap, alpha=.5)

will reset all of the alpha values in myColormap to 1 or .5.
You can do c.cmap._init() to reset its original alpha values, and if you
force a redraw on figure(2) (by panning or zooming on it, for example),
it will look right again. However, if you go and fiddle with figure(3)
(pan/zoom), and come back to figure(2), panning or zooming will
cause all of the alpha values will be reset again.

I'm not sure if it would be worth it to make a copy of the colormap to
prevent this from happening. Anyone have thoughts on this?

(the full example of this is commented with FIXME: in polar_scatter.py)

best,
    Paul Ivanov

John Hunter, on 2008-11-23 07:36, wrote:

rgba_colormap_v2.diff (6.89 KB)

···

On Sun, Nov 23, 2008 at 2:01 AM, Paul Ivanov <pivanov314@...287...> wrote:

I took a stab at it, how does this look?

I also took the liberty of adding alpha to LinearSegmentedColormap and
updated its docstring changing two somewhat ambiguous uses of the word
'entry' with 'key' and 'value'.

Hey Paul,

Thanks for taking this on. I haven't tested this but I read the patch
and have some inline comments below. Some additional comments:

  * the patch should include a section in the CHANGELOG and
API_CHANGES letting people know what is different.

  * you should run examples/tests/backend_driver.py and make sure all
the examples still run, checking the output of some of the mappable
types (images, scaltter, pcolor...)

  * it would be nice to have an example in the examples dir which
exercises the new capabilities.

See also, in case you haven't,
http://matplotlib.sourceforge.net/devel/coding_guide.html, which
covers some of this in more detail.

Thanks again! Comments below:

    Index: lib/matplotlib/colors.py
    ===================================================================
    --- lib/matplotlib/colors.py (revision 6431)
    +++ lib/matplotlib/colors.py (working copy)
    @@ -452,7 +452,7 @@
             self._isinit = False

    - def __call__(self, X, alpha=1.0, bytes=False):
    + def __call__(self, X, alpha=None, bytes=False):
             """
             *X* is either a scalar or an array (of any dimension).
             If scalar, a tuple of rgba values is returned, otherwise
    @@ -466,9 +466,10 @@
             """
You need to document what alpha can be here: what does None mean, can
it be an array, scalar, etc...

             if not self._isinit: self._init()
    - alpha = min(alpha, 1.0) # alpha must be between 0 and 1
    - alpha = max(alpha, 0.0)
    - self._lut[:-3, -1] = alpha
    + if alpha:

I prefer to explicitly use "if alpha is None", since there are other
things that would test False (0, [], '') that you probably don't mean.

    + alpha = min(alpha, 1.0) # alpha must be between 0 and 1
    + alpha = max(alpha, 0.0)

You should be able to use np.clip(alpha, 0, 1) here, but we should
consider instead raising for illegal alpha values since this will be
more helpful to the user. I realize some of this is inherited code
from before your changes, but we can improve it while making this
patch.

    + self._lut[:-3, -1] = alpha
             mask_bad = None
             if not cbook.iterable(X):
                 vtype = 'scalar'
    @@ -558,9 +559,10 @@
         def __init__(self, name, segmentdata, N=256):
             """Create color map from linear mapping segments

    - segmentdata argument is a dictionary with a red, green and blue
    - entries. Each entry should be a list of *x*, *y0*, *y1* tuples,
    - forming rows in a table.
    + segmentdata argument is a dictionary with red, green and blue
    + keys. An optional alpha key is also supported. Each value
    + should be a list of *x*, *y0*, *y1* tuples, forming rows in a
    + table.

             Example: suppose you want red to increase from 0 to 1 over
             the bottom half, green to do the same over the middle half,
    @@ -606,6 +608,8 @@
             self._lut[:-3, 0] = makeMappingArray(self.N,
self._segmentdata['red'])
             self._lut[:-3, 1] = makeMappingArray(self.N,
self._segmentdata['green'])
             self._lut[:-3, 2] = makeMappingArray(self.N,
self._segmentdata['blue'])
    + if self._segmentdata.has_key('alpha'):
    + self._lut[:-3, 3] = makeMappingArray(self.N,
self._segmentdata['blue'])

Is this what you meant? I think you would use 'alpha' rather than
'blue' here, no?

             self._isinit = True
             self._set_extremes()

    @@ -664,11 +668,10 @@

         def _init(self):
    - rgb = np.array([colorConverter.to_rgb(c)
    + rgba = np.array([colorConverter.to_rgba(c)
                         for c in self.colors], np.float)
             self._lut = np.zeros((self.N + 3, 4), np.float)
    - self._lut[:-3, :-1] = rgb
    - self._lut[:-3, -1] = 1
    + self._lut[:-3] = rgba
             self._isinit = True
             self._set_extremes()