@@ -509,20 +509,22 @@ def __call__(self, X, alpha=None, bytes=False):
509509 xa = np .array ([X ])
510510 else :
511511 vtype = 'array'
512- # force a copy here -- the ma.array and filled functions
513- # do force a cop of the data by default - JDH
514- xma = ma .array (X , copy = True )
515- xa = xma .filled (0 )
516- mask_bad = ma .getmask (xma )
512+ xma = ma .array (X , copy = False )
513+ mask_bad = xma .mask
514+ xa = xma .data .copy () # Copy here to avoid side effects.
515+ del xma
516+ # masked values are substituted below; no need to fill them here
517+
517518 if xa .dtype .char in np .typecodes ['Float' ]:
518519 np .putmask (xa , xa == 1.0 , 0.9999999 ) #Treat 1.0 as slightly less than 1.
519520 # The following clip is fast, and prevents possible
520521 # conversion of large positive values to negative integers.
521522
523+ xa *= self .N
522524 if NP_CLIP_OUT :
523- np .clip (xa * self . N , - 1 , self .N , out = xa )
525+ np .clip (xa , - 1 , self .N , out = xa )
524526 else :
525- xa = np .clip (xa * self . N , - 1 , self .N )
527+ xa = np .clip (xa , - 1 , self .N )
526528
527529 # ensure that all 'under' values will still have negative
528530 # value after casting to int
@@ -532,8 +534,11 @@ def __call__(self, X, alpha=None, bytes=False):
532534 # otherwise the under-range values get converted to over-range.
533535 np .putmask (xa , xa > self .N - 1 , self ._i_over )
534536 np .putmask (xa , xa < 0 , self ._i_under )
535- if mask_bad is not None and mask_bad .shape == xa .shape :
536- np .putmask (xa , mask_bad , self ._i_bad )
537+ if mask_bad is not None :
538+ if mask_bad .shape == xa .shape :
539+ np .putmask (xa , mask_bad , self ._i_bad )
540+ elif mask_bad :
541+ xa .fill (self ._i_bad )
537542 if bytes :
538543 lut = (self ._lut * 255 ).astype (np .uint8 )
539544 else :
@@ -797,32 +802,60 @@ def __init__(self, vmin=None, vmax=None, clip=False):
797802 self .vmax = vmax
798803 self .clip = clip
799804
805+ @staticmethod
806+ def process_value (value ):
807+ """
808+ Homogenize the input *value* for easy and efficient normalization.
809+
810+ *value* can be a scalar or sequence.
811+
812+ Returns *result*, *is_scalar*, where *result* is a
813+ masked array matching *value*. Float dtypes are preserved;
814+ integer types with two bytes or smaller are converted to
815+ np.float32, and larger types are converted to np.float.
816+ Preserving float32 when possible, and using in-place operations,
817+ can greatly improve speed for large arrays.
818+
819+ Experimental; we may want to add an option to force the
820+ use of float32.
821+ """
822+ if cbook .iterable (value ):
823+ is_scalar = False
824+ result = ma .asarray (value )
825+ if result .dtype .kind == 'f' :
826+ if isinstance (value , np .ndarray ):
827+ result = result .copy ()
828+ elif result .dtype .itemsize > 2 :
829+ result = result .astype (np .float )
830+ else :
831+ result = result .astype (np .float32 )
832+ else :
833+ is_scalar = True
834+ result = ma .array ([value ]).astype (np .float )
835+ return result , is_scalar
836+
800837 def __call__ (self , value , clip = None ):
801838 if clip is None :
802839 clip = self .clip
803840
804- if cbook .iterable (value ):
805- vtype = 'array'
806- val = ma .asarray (value ).astype (np .float )
807- else :
808- vtype = 'scalar'
809- val = ma .array ([value ]).astype (np .float )
841+ result , is_scalar = self .process_value (value )
810842
811- self .autoscale_None (val )
843+ self .autoscale_None (result )
812844 vmin , vmax = self .vmin , self .vmax
813845 if vmin > vmax :
814846 raise ValueError ("minvalue must be less than or equal to maxvalue" )
815- elif vmin == vmax :
816- result = 0.0 * val
847+ elif vmin == vmax :
848+ result . fill ( 0 ) # Or should it be all masked? Or 0.5?
817849 else :
818850 vmin = float (vmin )
819851 vmax = float (vmax )
820852 if clip :
821- mask = ma .getmask (val )
822- val = ma .array (np .clip (val .filled (vmax ), vmin , vmax ),
823- mask = mask )
824- result = (val - vmin ) / (vmax - vmin )
825- if vtype == 'scalar' :
853+ mask = ma .getmask (result )
854+ result = ma .array (np .clip (result .filled (vmax ), vmin , vmax ),
855+ mask = mask )
856+ result -= vmin
857+ result /= vmax - vmin
858+ if is_scalar :
826859 result = result [0 ]
827860 return result
828861
@@ -847,8 +880,10 @@ def autoscale(self, A):
847880
848881 def autoscale_None (self , A ):
849882 ' autoscale only None-valued vmin or vmax'
850- if self .vmin is None : self .vmin = ma .min (A )
851- if self .vmax is None : self .vmax = ma .max (A )
883+ if self .vmin is None :
884+ self .vmin = ma .min (A )
885+ if self .vmax is None :
886+ self .vmax = ma .max (A )
852887
853888 def scaled (self ):
854889 'return true if vmin and vmax set'
@@ -862,30 +897,29 @@ def __call__(self, value, clip=None):
862897 if clip is None :
863898 clip = self .clip
864899
865- if cbook .iterable (value ):
866- vtype = 'array'
867- val = ma .asarray (value ).astype (np .float )
868- else :
869- vtype = 'scalar'
870- val = ma .array ([value ]).astype (np .float )
900+ result , is_scalar = self .process_value (value )
871901
872- val = ma .masked_less_equal (val , 0 , copy = False )
902+ result = ma .masked_less_equal (result , 0 , copy = False )
873903
874- self .autoscale_None (val )
904+ self .autoscale_None (result )
875905 vmin , vmax = self .vmin , self .vmax
876906 if vmin > vmax :
877907 raise ValueError ("minvalue must be less than or equal to maxvalue" )
878908 elif vmin <= 0 :
879909 raise ValueError ("values must all be positive" )
880910 elif vmin == vmax :
881- result = 0.0 * val
911+ result . fill ( 0 )
882912 else :
883913 if clip :
884- mask = ma .getmask (val )
885- val = ma .array (np .clip (val .filled (vmax ), vmin , vmax ),
914+ mask = ma .getmask (result )
915+ val = ma .array (np .clip (result .filled (vmax ), vmin , vmax ),
886916 mask = mask )
887- result = (ma .log (val )- np .log (vmin ))/ (np .log (vmax )- np .log (vmin ))
888- if vtype == 'scalar' :
917+ #result = (ma.log(result)-np.log(vmin))/(np.log(vmax)-np.log(vmin))
918+ # in-place equivalent of above can be much faster
919+ np .ma .log (result , result )
920+ result -= np .log (vmin )
921+ result /= (np .log (vmax ) - np .log (vmin ))
922+ if is_scalar :
889923 result = result [0 ]
890924 return result
891925
0 commit comments