colormap cleverness

I would like to create a colormap that plots a particular

    > value in a specific color but is otherwise normal. So far
    > I haven't been smart enough to figure out how to do
    > this. For example, imagine an image with missing data. The
    > missing data has some sentinel value e.g. -1, which I can
    > set to be outside the range of the rest of the image. I
    > want the missing data to plot is a specific color unrelated
    > to the colormap for the rest of the image. Like I said, I
    > haven't been clever enough to figure it out. This is sort
    > of similar behavior to masked arrays in pcolor, but I would
    > like to use imshow if possible. Perhaps a custom
    > normalization to compress a given colormap and map the
    > sentinel to a boundary value which has discontinuous
    > segment? Any ideas would be appreciated.

This is a nice example. At first I thought it would be easier since
all one needs to do is define a custom norm and cmap instance to
imshow. All these really need to be is callable, but, but because of
my &$*%*!#@ C++ background, I introduced typechecking in the imshow
code

        if norm is not None: assert(isinstance(norm, normalize))
        if cmap is not None: assert(isinstance(cmap, Colormap))

So there is a little extra overhead to make them the right "type". It
would have been preferable to use ducktyping here.

The norm instance takes your data and returns a same shape float array
ranging normalized to [0,1]. We want to define our own norm instance
that preserves the sentinel

import pylab
import matplotlib.numerix as nx
import matplotlib.colors

class DannysNorm(matplotlib.colors.normalize):
    """
    Leave the sentinel unchanged
    """
    def __init__(self, sentinel):
        matplotlib.colors.normalize.__init__(self)
        self.sentinel = sentinel
        
    def __call__(self, value):
        vnorm = matplotlib.colors.normalize.__call__(self, value)
        return nx.where(value==self.sentinel, self.sentinel, vnorm)

Next you have to define a custom colormap. The cmap instance takes
normalized data and returns RGBA data, and the call method is

    def __call__(self, X, alpha=1):

So we need to create a class which takes a default cmap and returns it
except at the sentinel value

class DannysMap(matplotlib.colors.Colormap):
    def __init__(self, cmap, sentinel, rgb):
        self.N = cmap.N
        self.name = 'DannysMap'
        self.cmap = cmap
        self.sentinel = sentinel

        if len(rgb)!=3:
            raise ValueError('sentinel color must be RGB')
        
        self.rgb = rgb
        
    def __call__(self, X, alpha=1):

        m,n = X.shape

        r,g,b = self.rgb
        Xm = self.cmap(X)

        ret = nx.zeros( (m,n,4), typecode=nx.Float)
        ret[:,:,0] = nx.where(X==self.sentinel, r, Xm[:,:,0])
        ret[:,:,1] = nx.where(X==self.sentinel, g, Xm[:,:,1])
        ret[:,:,2] = nx.where(X==self.sentinel, b, Xm[:,:,2])
        ret[:,:,3] = nx.where(X==self.sentinel, alpha, Xm[:,:,3])
        return ret

That's it. Now you just have to pass these on to imshow

X = nx.mlab.rand(100,50)
X[20:30, 5:10] = -1
cmap = DannysMap(pylab.cm.jet, -1, (1,0,0))
norm = DannysNorm(-1)

pylab.imshow(X, cmap = cmap, norm=norm)
pylab.show()

JDH