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.