Image plotting requires data, a colormap, and a normalization. A common desire is to show missing data or other values in a specified color. The following code shows an example of how to do this.

The code creates a new SentinelMap Colormap subclass and a SentinelNorm norm subclass.

The SentinelMap initialization takes a dictionary of value, color pairs. The data is already assumed to be normalized (except for the sentinels which are preserved). The RGB values at the sentinel values are replaced by the specified colors.

The SentinelNorm class normalizes the data in the standard way except for one sublety. SentinelNorm takes an "ignore" argument. The ignored values need to be excluded from the normalization so that they do not skew the results.

I use a not particularly wonderful algorithm of explicitly sorting the data and using the first non-sentinel values to define the min and max. This can probably be improved, but for my purposes was easy and sufficient. The data is then normalized including the sentinels. Finally, the sentinels are replaced.

   1 from matplotlib.colors import Colormap, normalize
   2 import matplotlib.numerix as nx
   3 from types import IntType, FloatType, ListType
   4 
   5 class SentinelMap(Colormap):
   6         def __init__(self, cmap, sentinels={}):
   7                 # boilerplate stuff
   8                 self.N = cmap.N
   9                 self.name = 'SentinelMap'
  10                 self.cmap = cmap
  11                 self.sentinels = sentinels
  12                 for rgb in sentinels.values():
  13                         if len(rgb)!=3:
  14                                 raise ValueError('sentinel color must be RGB')
  15 
  16 
  17         def __call__(self, scaledImageData, alpha=1):
  18                 # assumes the data is already normalized (ignoring sentinels)
  19                 # clip to be on the safe side
  20                 rgbaValues = self.cmap(nx.clip(scaledImageData, 0.,1.))
  21 
  22                 #replace sentinel data with sentinel colors
  23                 for sentinel,rgb in self.sentinels.items():
  24                         r,g,b = rgb
  25                         rgbaValues[:,:,0] =  nx.where(scaledImageData==sentinel, r, rgbaValues[:,:,0])
  26                         rgbaValues[:,:,1] =  nx.where(scaledImageData==sentinel, g, rgbaValues[:,:,1])
  27                         rgbaValues[:,:,2] =  nx.where(scaledImageData==sentinel, b, rgbaValues[:,:,2])
  28                         rgbaValues[:,:,3] =  nx.where(scaledImageData==sentinel, alpha, rgbaValues[:,:,3])
  29 
  30                 return rgbaValues
  31 
  32 class SentinelNorm(normalize):
  33         """
  34         Leave the sentinel unchanged
  35         """
  36         def __init__(self, ignore=[], vmin=None, vmax=None):
  37                 self.vmin=vmin
  38                 self.vmax=vmax
  39 
  40                 if type(ignore) in [IntType, FloatType]:
  41                         self.ignore = [ignore]
  42                 else:
  43                         self.ignore = list(ignore)
  44 
  45 
  46         def __call__(self, value):
  47 
  48                 vmin = self.vmin
  49                 vmax = self.vmax
  50 
  51                 if type(value) in [IntType, FloatType]:
  52                         vtype = 'scalar'
  53                         val = array([value])
  54                 else:
  55                         vtype = 'array'
  56                         val = nx.asarray(value)
  57 
  58                 # if both vmin is None and vmax is None, we'll automatically
  59                 # norm the data to vmin/vmax of the actual data, so the
  60                 # clipping step won't be needed.
  61                 if vmin is None and vmax is None:
  62                         needs_clipping = False
  63                 else:
  64                         needs_clipping = True
  65 
  66                 if vmin is None or vmax is None:
  67                         rval = nx.ravel(val)
  68                         #do this if sentinels (values to ignore in data)
  69                         if self.ignore:
  70                                 sortValues=nx.sort(rval)
  71                                 if vmin is None:
  72                                         # find the lowest non-sentinel value
  73                                         for thisVal in sortValues:
  74                                                 if thisVal not in self.ignore:
  75                                                         vmin=thisVal #vmin is the lowest non-sentinel value
  76                                                         break
  77                                         else:
  78                                                 vmin=0.
  79                                 if vmax is None:
  80                                         for thisVal in sortValues[::-1]:
  81                                                 if thisVal not in self.ignore:
  82                                                         vmax=thisVal #vmax is the greatest non-sentinel value
  83                                                         break
  84                                         else:
  85                                                 vmax=0.
  86                         else:
  87                                 if vmin is None: vmin = min(rval)
  88                                 if vmax is None: vmax = max(rval)
  89                 if vmin > vmax:
  90                         raise ValueError("minvalue must be less than or equal to maxvalue")
  91                 elif vmin==vmax:
  92                         return 0.*value
  93                 else:
  94                         if needs_clipping:
  95                                 val = nx.clip(val,vmin, vmax)
  96                         result = (1.0/(vmax-vmin))*(val-vmin)
  97 
  98                 # replace sentinels with original (non-normalized) values
  99                 for thisIgnore in self.ignore:
 100                         result = nx.where(val==thisIgnore,thisIgnore,result)
 101 
 102                 if vtype == 'scalar':
 103                         result = result[0]
 104                 return result
 105 
 106 
 107 if __name__=="__main__":
 108         import pylab
 109         import matplotlib.colors
 110         n=100
 111 
 112         # create a random array
 113         X = nx.mlab.rand(n,n)
 114         cmBase = pylab.cm.jet
 115 
 116         # plot it array as an image
 117         pylab.figure(1)
 118         pylab.imshow(X, cmap=cmBase, interpolation='nearest')
 119 
 120         # define the sentinels
 121         sentinel1 = -10
 122         sentinel2 = 10
 123 
 124         # replace some data with sentinels
 125         X[int(.1*n):int(.2*n), int(.5*n):int(.7*n)]  = sentinel1
 126         X[int(.6*n):int(.8*n), int(.2*n):int(.3*n)]  = sentinel2
 127 
 128         # define the colormap and norm
 129         rgb1 = (0.,0.,0.)
 130         rgb2 = (1.,0.,0.)
 131         cmap = SentinelMap(cmBase, sentinels={sentinel1:rgb1,sentinel2:rgb2,})
 132         norm = SentinelNorm(ignore=[sentinel1,sentinel2])
 133 
 134         # plot with the modified colormap and norm
 135         pylab.figure(2)
 136         pylab.imshow(X, cmap = cmap, norm=norm, interpolation='nearest')
 137 
 138         pylab.show()

If the preceeding code is run from a prompt, two images are generated. The first is a pristine image of random data. The second image is the data modified by setting some blocks to sentinel values and then plotting the sentinels in specific colors. A sample result is shown below.


CategoryCookbookMatplotlib

Cookbook/Matplotlib/Plotting Images with Special Values (last edited 2006-01-22 21:06:09 by AndrewStraw)