From: John H. <jdh...@ac...> - 2005-07-12 21:20:34
|
>>>>> "Danny" == Danny Shevitz <sh...@la...> writes: Danny> I would like to create a colormap that plots a particular Danny> value in a specific color but is otherwise normal. So far Danny> I haven't been smart enough to figure out how to do Danny> this. For example, imagine an image with missing data. The Danny> missing data has some sentinel value e.g. -1, which I can Danny> set to be outside the range of the rest of the image. I Danny> want the missing data to plot is a specific color unrelated Danny> to the colormap for the rest of the image. Like I said, I Danny> haven't been clever enough to figure it out. This is sort Danny> of similar behavior to masked arrays in pcolor, but I would Danny> like to use imshow if possible. Perhaps a custom Danny> normalization to compress a given colormap and map the Danny> sentinel to a boundary value which has discontinuous Danny> 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 |