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