colormaps - combine linear segmented with discrete sections?

Hello,

I have a 2D numpy masked array of geo-located data -- with some data
missing -- that I wish to plot on a map. Basemap provides a nice tool
to do this, but I am stumped trying to get the colorscheme I want.

My data are only physically meaningful on land, so I am using
Basemap.maskoceans() to mask out "wet" locations (oceans, lakes, etc.)
Trouble is, now both water as well as actual missing data show up in
the missing data color.

I want to have blue water, some other (bright) color for missing data,
and a nice-looking color transition (matplotlib.cm.Blues or something
similar) for the valid data over land (values from 0 to 50). The
Cookbook example at
<http://www.scipy.org/Cookbook/Matplotlib/Plotting_Images_with_Special_Values>
addresses my problem, but I cannot get it to work. After changing
instances of matplotlib.numerix to numpy, I get a long list of
exceptions, the last of which is
TypeError: __call__() got an unexpected keyword argument 'bytes'.
This has to do with sentinelNorm, I think, but I'm not sure how to fix it.

Eventually I would like to sub-classify missing data by the type of
missing input that caused a missing value, but for now a single
missing data color is enough.

The code below does almost what I want- I just need to figure out how
to make the water blue. I have also messed around with
matplotlib.cm.BoundaryNorm to create a colormap/normalization to
handle my data, but I am getting hung up initializing the cmap,
counting bin edges, etc. I also tacked my test code for that... Any
help greatly appreciated!

Thanks,
Tim

···

--

Timothy W. Hilton
PhD Candidate, Department of Meteorology
The Pennsylvania State University
503 Walker Building, University Park, PA 16802
hilton@...3085...

#--------------------------------------------------
# amost right...

import numpy as np
import numpy.ma as ma
from mpl_toolkits.basemap import Basemap, maskoceans
import matplotlib.pyplot as plt
import matplotlib.colors

if __name__=="__main__":
    
    # setup a basemap instance & draw a map
    m_aeqd = Basemap(width=9e5,height=9e5,projection='aeqd',
                     lat_0=28.46,lon_0=360-80.67, resolution='i', area_thresh=1000,
                     rsphere=6371007.181000)
    col_water='#B9D3EE' #SlateGray2
    col_land ='#1C1C1C'
    m_aeqd.drawmapboundary(fill_color=col_water)
    # draw coasts and fill continents.
    m_aeqd.drawcoastlines(linewidth=0.5)
    m_aeqd.fillcontinents(color=col_land,lake_color=col_water, zorder=0)

    # create a 100 x 100 pseudodata array with valid data between 0
      and 50 and some "missing" data (less than zero)
    n=100
    X = ma.masked_less(np.random.random_integers(-1, 50, (n,n)), 0)
    plot_mid = np.mean((m_aeqd.llcrnrx, m_aeqd.urcrnrx))
    Xu, Xv = np.meshgrid(np.arange(stop=plot_mid + 500*n,
                                   start=plot_mid - 500*n, step=1000),
                         np.arange(stop=m_aeqd.urcrnry,
                                   start=m_aeqd.urcrnry - 1000*n, step=1000))
    Xlon, Xlat = m_aeqd(Xu, Xv, inverse=True)

    #setup a colormap and plot the data
    cmap = matplotlib.cm.get_cmap("Blues", 25)
    #mask oceans
    ocean_mask = maskoceans(Xlon, Xlat, X)
    #now I'm stumped how to incorporate oceans_mask into the color map
    oceans_cmap = matplotlib.colors.ListedColormap((col_land, "#000000"),
                                                   name="oceans", N=2)
    cmap.set_bad(color="#FF0000") #show missing vals in bright red
    m_aeqd.pcolormesh(Xu, Xv, ocean_mask, cmap=cmap)
    plt.colorbar()

    #it seems like I could do another pcolormesh call with
    #ocean_mask.mask and oceans_cmap; I'd need to set the
    #transparency. It seems like the more elegant solution is to
    #devise cmap to account for ocean_mask.mask (water) separately
    #from X.mask (data that are actually missing). I'm not sure how
    #do that though.

2010/11/1 Timothy W. Hilton <hilton@...3085...>:

[...]

I want to have blue water, some other (bright) color for missing data,
and a nice-looking color transition (matplotlib.cm.Blues or something
similar) for the valid data over land (values from 0 to 50). The
Cookbook example at
<http://www.scipy.org/Cookbook/Matplotlib/Plotting_Images_with_Special_Values&gt;
addresses my problem, but I cannot get it to work. After changing
instances of matplotlib.numerix to numpy, I get a long list of
exceptions, the last of which is
TypeError: __call__() got an unexpected keyword argument 'bytes'.
This has to do with sentinelNorm, I think, but I'm not sure how to fix it.

I think I would tackle this by writing a Norm that doesn't change
negative values, and you might mask then the oceans by -0.5, and the
nans by -1.5. Then, you might create a colormap comprised of the
ocean color for [-1, 0] and the nan color for [-2, -1], and for the
normal normed range [0, 1] the normal Blues cm. Have a look at cm.py
and _cm.py how it works. Basically, you can specify for all sections
of the colormap the left and right color. So you can mix discrete
maps with continuous ones, because the continuous ones are just
linearly interpolated with matching colors for left/right at each
boundary position. Looking at the code will clarify things a lot I
believe.

I don't know what went wrong with the cb example you said. From a
quick look, it seems to have "sentinel rgb values", but this is not
what we want, right?

Eventually I would like to sub-classify missing data by the type of
missing input that caused a missing value, but for now a single
missing data color is enough.

That would be possible with the approach above, by just adding
sections below zero.

_cm.py: Definitions of colormaps, like Blues.
cm.py: among other things, how to load such specifications.
colors.py: Defines Colormaps, and Norms. Have a look at both of them,
esp. at :class:`Normalize`. I would subclass the Norm mentioned above
from Normalize.

I hope this helps you,
Friedrich

Hi Friedrich,

Many thanks for your detailed response. I've had to turn my attention
to other things in the past few weeks, but I am back to this task now.

I've implemented the Norm that you suggested by subclassing Normalize;
that was a great suggestion. Now I have a two dimensional array where
water has value -1.5, missing data have -0.5, and valid data over land
have values in [0, 1].

After poking around in cm.py, _cm.py, and colors.py, I understand better
how to define a colormap, but I am not sure how to map negative values
like -0.5 or -1.5 to a color -- I believe the x values in the color
dictionary must be positive?

I can imagine various schemes to sidestep this by assigning water and
missing values positive discrete values (say, 0.1 and 0.2 or something)
and then putting valid data in the remaining portion of [0,1] -- maybe
[0.3, 1] or something. But there's an elegance to valid data going into
[0, 1] and other invalid data getting values outside of that range... I
think I'd lose a little readability in giving that up.

Anyway, thanks a whole lot for your helpful suggestions.

Best,
Tim

···

On Sat, Nov 2010, 06 at 10:52:48PM +0100, Friedrich Romstedt wrote:

2010/11/1 Timothy W. Hilton <hilton@...3085...>:
> [...]
>
> I want to have blue water, some other (bright) color for missing data,
> and a nice-looking color transition (matplotlib.cm.Blues or something
> similar) for the valid data over land (values from 0 to 50). The
> Cookbook example at
> <http://www.scipy.org/Cookbook/Matplotlib/Plotting_Images_with_Special_Values&gt;
> addresses my problem, but I cannot get it to work. After changing
> instances of matplotlib.numerix to numpy, I get a long list of
> exceptions, the last of which is
> TypeError: __call__() got an unexpected keyword argument 'bytes'.
> This has to do with sentinelNorm, I think, but I'm not sure how to fix it.

I think I would tackle this by writing a Norm that doesn't change
negative values, and you might mask then the oceans by -0.5, and the
nans by -1.5. Then, you might create a colormap comprised of the
ocean color for [-1, 0] and the nan color for [-2, -1], and for the
normal normed range [0, 1] the normal Blues cm. Have a look at cm.py
and _cm.py how it works. Basically, you can specify for all sections
of the colormap the left and right color. So you can mix discrete
maps with continuous ones, because the continuous ones are just
linearly interpolated with matching colors for left/right at each
boundary position. Looking at the code will clarify things a lot I
believe.

I don't know what went wrong with the cb example you said. From a
quick look, it seems to have "sentinel rgb values", but this is not
what we want, right?

> Eventually I would like to sub-classify missing data by the type of
> missing input that caused a missing value, but for now a single
> missing data color is enough.

That would be possible with the approach above, by just adding
sections below zero.

_cm.py: Definitions of colormaps, like Blues.
cm.py: among other things, how to load such specifications.
colors.py: Defines Colormaps, and Norms. Have a look at both of them,
esp. at :class:`Normalize`. I would subclass the Norm mentioned above
from Normalize.

I hope this helps you,
Friedrich