Matplotlib:使用特殊值绘制图像

日期2006-01-22(最后修改),2006-01-22(创建)

图像绘制需要数据、颜色映射和归一化。一个常见的需求是使用指定颜色显示缺失数据或其他值。以下代码展示了如何实现这一点。

代码创建了一个新的 Colormap 子类和一个 norm 子类。

初始化接受一个值、颜色对的字典。数据被假定为已归一化(除了保留的哨兵值)。哨兵值处的 RGB 值被替换为指定的颜色。

该类以标准方式对数据进行归一化,但有一个细微差别。接受一个“ignore”参数。忽略的值需要从归一化中排除,这样它们就不会扭曲结果。

我使用了一个不太好的算法,明确地对数据进行排序,并使用第一个非哨兵值来定义最小值和最大值。这可能可以改进,但对于我的目的来说,它简单且足够。然后对数据进行归一化,包括哨兵值。最后,哨兵值被替换。

在 [ ]
from matplotlib.colors import Colormap, normalize
import matplotlib.numerix as nx
from types import IntType, FloatType, ListType

class SentinelMap(Colormap):
        def __init__(self, cmap, sentinels={}):
                # boilerplate stuff
                self.N = cmap.N
                self.name = 'SentinelMap'
                self.cmap = cmap
                self.sentinels = sentinels
                for rgb in sentinels.values():
                        if len(rgb)!=3:
                                raise ValueError('sentinel color must be RGB')


        def __call__(self, scaledImageData, alpha=1):
                # assumes the data is already normalized (ignoring sentinels)
                # clip to be on the safe side
                rgbaValues = self.cmap(nx.clip(scaledImageData, 0.,1.))

                #replace sentinel data with sentinel colors
                for sentinel,rgb in self.sentinels.items():
                        r,g,b = rgb
                        rgbaValues[:,:,0] =  nx.where(scaledImageData==sentinel, r, rgbaValues[:,:,0])
                        rgbaValues[:,:,1] =  nx.where(scaledImageData==sentinel, g, rgbaValues[:,:,1])
                        rgbaValues[:,:,2] =  nx.where(scaledImageData==sentinel, b, rgbaValues[:,:,2])
                        rgbaValues[:,:,3] =  nx.where(scaledImageData==sentinel, alpha, rgbaValues[:,:,3])

                return rgbaValues

class SentinelNorm(normalize):
        """
        Leave the sentinel unchanged
        """
        def __init__(self, ignore=[], vmin=None, vmax=None):
                self.vmin=vmin
                self.vmax=vmax

                if type(ignore) in [IntType, FloatType]:
                        self.ignore = [ignore]
                else:
                        self.ignore = list(ignore)


        def __call__(self, value):

                vmin = self.vmin
                vmax = self.vmax

                if type(value) in [IntType, FloatType]:
                        vtype = 'scalar'
                        val = array([value])
                else:
                        vtype = 'array'
                        val = nx.asarray(value)

                # if both vmin is None and vmax is None, we'll automatically
                # norm the data to vmin/vmax of the actual data, so the
                # clipping step won't be needed.
                if vmin is None and vmax is None:
                        needs_clipping = False
                else:
                        needs_clipping = True

                if vmin is None or vmax is None:
                        rval = nx.ravel(val)
                        #do this if sentinels (values to ignore in data)
                        if self.ignore:
                                sortValues=nx.sort(rval)
                                if vmin is None:
                                        # find the lowest non-sentinel value
                                        for thisVal in sortValues:
                                                if thisVal not in self.ignore:
                                                        vmin=thisVal #vmin is the lowest non-sentinel value
                                                        break
                                        else:
                                                vmin=0.
                                if vmax is None:
                                        for thisVal in sortValues[::-1]:
                                                if thisVal not in self.ignore:
                                                        vmax=thisVal #vmax is the greatest non-sentinel value
                                                        break
                                        else:
                                                vmax=0.
                        else:
                                if vmin is None: vmin = min(rval)
                                if vmax is None: vmax = max(rval)
                if vmin > vmax:
                        raise ValueError("minvalue must be less than or equal to maxvalue")
                elif vmin==vmax:
                        return 0.*value
                else:
                        if needs_clipping:
                                val = nx.clip(val,vmin, vmax)
                        result = (1.0/(vmax-vmin))*(val-vmin)

                # replace sentinels with original (non-normalized) values
                for thisIgnore in self.ignore:
                        result = nx.where(val==thisIgnore,thisIgnore,result)

                if vtype == 'scalar':
                        result = result[0]
                return result


if __name__=="__main__":
        import pylab
        import matplotlib.colors
        n=100

        # create a random array
        X = nx.mlab.rand(n,n)
        cmBase = pylab.cm.jet

        # plot it array as an image
        pylab.figure(1)
        pylab.imshow(X, cmap=cmBase, interpolation='nearest')

        # define the sentinels
        sentinel1 = -10
        sentinel2 = 10

        # replace some data with sentinels
        X[int(.1*n):int(.2*n), int(.5*n):int(.7*n)]  = sentinel1
        X[int(.6*n):int(.8*n), int(.2*n):int(.3*n)]  = sentinel2

        # define the colormap and norm
        rgb1 = (0.,0.,0.)
        rgb2 = (1.,0.,0.)
        cmap = SentinelMap(cmBase, sentinels={sentinel1:rgb1,sentinel2:rgb2,})
        norm = SentinelNorm(ignore=[sentinel1,sentinel2])

        # plot with the modified colormap and norm
        pylab.figure(2)
        pylab.imshow(X, cmap = cmap, norm=norm, interpolation='nearest')

        pylab.show()

如果前面的代码从提示符运行,则会生成两个图像。第一个是随机数据的原始图像。第二个图像是通过将某些块设置为哨兵值,然后以特定颜色绘制哨兵值而修改的数据。下面显示了一个示例结果。

章节作者:AndrewStraw

附件