From: <ef...@us...> - 2011-02-05 20:56:08
|
Revision: 8946 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=8946&view=rev Author: efiring Date: 2011-02-05 20:56:02 +0000 (Sat, 05 Feb 2011) Log Message: ----------- speed up Normalize, LogNorm, colormapping; modified patch by C. Gohlke Modified Paths: -------------- trunk/matplotlib/CHANGELOG trunk/matplotlib/examples/pylab_examples/image_slices_viewer.py trunk/matplotlib/lib/matplotlib/colors.py Modified: trunk/matplotlib/CHANGELOG =================================================================== --- trunk/matplotlib/CHANGELOG 2011-02-04 19:18:57 UTC (rev 8945) +++ trunk/matplotlib/CHANGELOG 2011-02-05 20:56:02 UTC (rev 8946) @@ -1,3 +1,8 @@ +2011-02-05 Speed up Normalize and LogNorm by using in-place + operations and by using float32 for float32 inputs + and for ints of 2 bytes or shorter; based on + patch by Christoph Gohlke. - EF + 2011-02-04 Changed imshow to use rgba as uint8 from start to finish, instead of going through an intermediate step as double precision; thanks to Christoph Gohlke. - EF Modified: trunk/matplotlib/examples/pylab_examples/image_slices_viewer.py =================================================================== --- trunk/matplotlib/examples/pylab_examples/image_slices_viewer.py 2011-02-04 19:18:57 UTC (rev 8945) +++ trunk/matplotlib/examples/pylab_examples/image_slices_viewer.py 2011-02-05 20:56:02 UTC (rev 8946) @@ -1,5 +1,5 @@ import numpy -from pylab import figure, show +from matplotlib.pyplot import figure, show @@ -7,7 +7,7 @@ class IndexTracker: def __init__(self, ax, X): self.ax = ax - ax.set_title('use scroll wheen to navigate images') + ax.set_title('use scroll wheel to navigate images') self.X = X rows,cols,self.slices = X.shape @@ -28,7 +28,7 @@ def update(self): self.im.set_data(self.X[:,:,self.ind]) - ax.set_ylabel('slice %s'%self.ind) + ax.set_ylabel('slice %s'%self.ind) self.im.axes.figure.canvas.draw() Modified: trunk/matplotlib/lib/matplotlib/colors.py =================================================================== --- trunk/matplotlib/lib/matplotlib/colors.py 2011-02-04 19:18:57 UTC (rev 8945) +++ trunk/matplotlib/lib/matplotlib/colors.py 2011-02-05 20:56:02 UTC (rev 8946) @@ -509,20 +509,22 @@ xa = np.array([X]) else: vtype = 'array' - # force a copy here -- the ma.array and filled functions - # do force a cop of the data by default - JDH - xma = ma.array(X, copy=True) - xa = xma.filled(0) - mask_bad = ma.getmask(xma) + xma = ma.array(X, copy=False) + mask_bad = xma.mask + xa = xma.data.copy() # Copy here to avoid side effects. + del xma + # masked values are substituted below; no need to fill them here + if xa.dtype.char in np.typecodes['Float']: np.putmask(xa, xa==1.0, 0.9999999) #Treat 1.0 as slightly less than 1. # The following clip is fast, and prevents possible # conversion of large positive values to negative integers. + xa *= self.N if NP_CLIP_OUT: - np.clip(xa * self.N, -1, self.N, out=xa) + np.clip(xa, -1, self.N, out=xa) else: - xa = np.clip(xa * self.N, -1, self.N) + xa = np.clip(xa, -1, self.N) # ensure that all 'under' values will still have negative # value after casting to int @@ -532,8 +534,11 @@ # otherwise the under-range values get converted to over-range. np.putmask(xa, xa>self.N-1, self._i_over) np.putmask(xa, xa<0, self._i_under) - if mask_bad is not None and mask_bad.shape == xa.shape: - np.putmask(xa, mask_bad, self._i_bad) + if mask_bad is not None: + if mask_bad.shape == xa.shape: + np.putmask(xa, mask_bad, self._i_bad) + elif mask_bad: + xa.fill(self._i_bad) if bytes: lut = (self._lut * 255).astype(np.uint8) else: @@ -797,32 +802,60 @@ self.vmax = vmax self.clip = clip + @staticmethod + def process_value(value): + """ + Homogenize the input *value* for easy and efficient normalization. + + *value* can be a scalar or sequence. + + Returns *result*, *is_scalar*, where *result* is a + masked array matching *value*. Float dtypes are preserved; + integer types with two bytes or smaller are converted to + np.float32, and larger types are converted to np.float. + Preserving float32 when possible, and using in-place operations, + can greatly improve speed for large arrays. + + Experimental; we may want to add an option to force the + use of float32. + """ + if cbook.iterable(value): + is_scalar = False + result = ma.asarray(value) + if result.dtype.kind == 'f': + if isinstance(value, np.ndarray): + result = result.copy() + elif result.dtype.itemsize > 2: + result = result.astype(np.float) + else: + result = result.astype(np.float32) + else: + is_scalar = True + result = ma.array([value]).astype(np.float) + return result, is_scalar + def __call__(self, value, clip=None): if clip is None: clip = self.clip - if cbook.iterable(value): - vtype = 'array' - val = ma.asarray(value).astype(np.float) - else: - vtype = 'scalar' - val = ma.array([value]).astype(np.float) + result, is_scalar = self.process_value(value) - self.autoscale_None(val) + self.autoscale_None(result) vmin, vmax = self.vmin, self.vmax if vmin > vmax: raise ValueError("minvalue must be less than or equal to maxvalue") - elif vmin==vmax: - result = 0.0 * val + elif vmin == vmax: + result.fill(0) # Or should it be all masked? Or 0.5? else: vmin = float(vmin) vmax = float(vmax) if clip: - mask = ma.getmask(val) - val = ma.array(np.clip(val.filled(vmax), vmin, vmax), - mask=mask) - result = (val-vmin) / (vmax-vmin) - if vtype == 'scalar': + mask = ma.getmask(result) + result = ma.array(np.clip(result.filled(vmax), vmin, vmax), + mask=mask) + result -= vmin + result /= vmax - vmin + if is_scalar: result = result[0] return result @@ -847,8 +880,10 @@ def autoscale_None(self, A): ' autoscale only None-valued vmin or vmax' - if self.vmin is None: self.vmin = ma.min(A) - if self.vmax is None: self.vmax = ma.max(A) + if self.vmin is None: + self.vmin = ma.min(A) + if self.vmax is None: + self.vmax = ma.max(A) def scaled(self): 'return true if vmin and vmax set' @@ -862,30 +897,29 @@ if clip is None: clip = self.clip - if cbook.iterable(value): - vtype = 'array' - val = ma.asarray(value).astype(np.float) - else: - vtype = 'scalar' - val = ma.array([value]).astype(np.float) + result, is_scalar = self.process_value(value) - val = ma.masked_less_equal(val, 0, copy=False) + result = ma.masked_less_equal(result, 0, copy=False) - self.autoscale_None(val) + self.autoscale_None(result) vmin, vmax = self.vmin, self.vmax if vmin > vmax: raise ValueError("minvalue must be less than or equal to maxvalue") elif vmin<=0: raise ValueError("values must all be positive") elif vmin==vmax: - result = 0.0 * val + result.fill(0) else: if clip: - mask = ma.getmask(val) - val = ma.array(np.clip(val.filled(vmax), vmin, vmax), + mask = ma.getmask(result) + val = ma.array(np.clip(result.filled(vmax), vmin, vmax), mask=mask) - result = (ma.log(val)-np.log(vmin))/(np.log(vmax)-np.log(vmin)) - if vtype == 'scalar': + #result = (ma.log(result)-np.log(vmin))/(np.log(vmax)-np.log(vmin)) + # in-place equivalent of above can be much faster + np.ma.log(result, result) + result -= np.log(vmin) + result /= (np.log(vmax) - np.log(vmin)) + if is_scalar: result = result[0] return result This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |