@@ -192,14 +192,6 @@ def _interpdr(self):
192192def iterpnames (self ):
193193return interpolations_names
194194
195- def set_cmap (self ,cmap ):
196- super (_ImageBase ,self ).set_cmap (cmap )
197- self .stale = True
198-
199- def set_norm (self ,norm ):
200- super (_ImageBase ,self ).set_norm (norm )
201- self .stale = True
202-
203195def __str__ (self ):
204196return "AxesImage(%g,%g;%gx%g)" % tuple (self .axes .bbox .bounds )
205197
@@ -357,58 +349,100 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
357349out_height = int (out_height_base )
358350
359351if not unsampled :
360- created_rgba_mask = False
361-
362352if A .ndim not in (2 ,3 ):
363353raise ValueError ("Invalid dimensions, got {}" .format (A .shape ))
364354
365355if A .ndim == 2 :
366- A = self .norm (A )
367- if A .dtype .kind == 'f' :
368- # If the image is greyscale, convert to RGBA and
369- # use the extra channels for resizing the over,
370- # under, and bad pixels. This is needed because
371- # Agg's resampler is very aggressive about
372- # clipping to [0, 1] and we use out-of-bounds
373- # values to carry the over/under/bad information
374- rgba = np .empty ((A .shape [0 ],A .shape [1 ],4 ),dtype = A .dtype )
375- rgba [...,0 ]= A # normalized data
376- # this is to work around spurious warnings coming
377- # out of masked arrays.
378- with np .errstate (invalid = 'ignore' ):
379- rgba [...,1 ]= np .where (A < 0 ,np .nan ,1 )# under data
380- rgba [...,2 ]= np .where (A > 1 ,np .nan ,1 )# over data
381- # Have to invert mask, Agg knows what alpha means
382- # so if you put this in as 0 for 'good' points, they
383- # all get zeroed out
384- rgba [...,3 ]= 1
385- if A .mask .shape == A .shape :
386- # this is the case of a nontrivial mask
387- mask = np .where (A .mask ,np .nan ,1 )
388- else :
389- # this is the case that the mask is a
390- # numpy.bool_ of False
391- mask = A .mask
392- # ~A.mask # masked data
393- A = rgba
394- output = np .zeros ((out_height ,out_width ,4 ),
395- dtype = A .dtype )
396- alpha = 1.0
397- created_rgba_mask = True
356+ # if we are a 2D array, then we are running through the
357+ # norm + colormap transformation. However, in general the
358+ # input data is not going to match the size on the screen so we
359+ # have to resample to the correct number of pixels
360+ # need to
361+
362+ # TODO slice input array first
363+ inp_dtype = A .dtype
364+ if inp_dtype .kind == 'f' :
365+ scaled_dtype = A .dtype
366+ else :
367+ scaled_dtype = np .float32
368+ # old versions of numpy do not work with `np.nammin`
369+ # and `np.nanmax` as inputs
370+ a_min = np .ma .min (A )
371+ a_max = np .ma .max (A )
372+ # scale the input data to [.1, .9]. The Agg
373+ # interpolators clip to [0, 1] internally, use a
374+ # smaller input scale to identify which of the
375+ # interpolated points need to be should be flagged as
376+ # over / under.
377+ # This may introduce numeric instabilities in very broadly
378+ # scaled data
379+ A_scaled = np .empty (A .shape ,dtype = scaled_dtype )
380+ A_scaled [:]= A
381+ A_scaled -= a_min
382+ if a_min != a_max :
383+ A_scaled /= ((a_max - a_min )/ 0.8 )
384+ A_scaled += 0.1
385+ A_resampled = np .zeros ((out_height ,out_width ),
386+ dtype = A_scaled .dtype )
387+ # resample the input data to the correct resolution and shape
388+ _image .resample (A_scaled ,A_resampled ,
389+ t ,
390+ _interpd_ [self .get_interpolation ()],
391+ self .get_resample (),1.0 ,
392+ self .get_filternorm ()or 0.0 ,
393+ self .get_filterrad ()or 0.0 )
394+
395+ # we are done with A_scaled now, remove from namespace
396+ # to be sure!
397+ del A_scaled
398+ # un-scale the resampled data to approximately the
399+ # original range things that interpolated to above /
400+ # below the original min/max will still be above /
401+ # below, but possibly clipped in the case of higher order
402+ # interpolation + drastically changing data.
403+ A_resampled -= 0.1
404+ if a_min != a_max :
405+ A_resampled *= ((a_max - a_min )/ 0.8 )
406+ A_resampled += a_min
407+ # if using NoNorm, cast back to the original datatype
408+ if isinstance (self .norm ,mcolors .NoNorm ):
409+ A_resampled = A_resampled .astype (A .dtype )
410+
411+ mask = np .empty (A .shape ,dtype = np .float32 )
412+ if A .mask .shape == A .shape :
413+ # this is the case of a nontrivial mask
414+ mask [:]= np .where (A .mask ,np .float32 (np .nan ),
415+ np .float32 (1 ))
398416else :
399- # colormap norms that output integers (ex NoNorm
400- # and BoundaryNorm) to RGBA space before
401- # interpolating. This is needed due to the
402- # Agg resampler only working on floats in the
403- # range [0, 1] and because interpolating indexes
404- # into an arbitrary LUT may be problematic.
405- #
406- # This falls back to interpolating in RGBA space which
407- # can produce it's own artifacts of colors not in the map
408- # showing up in the final image.
409- A = self .cmap (A ,alpha = self .get_alpha (),bytes = True )
410-
411- if not created_rgba_mask :
417+ mask [:]= 1
418+
419+ # we always have to interpolate the mask to account for
420+ # non-affine transformations
421+ out_mask = np .zeros ((out_height ,out_width ),
422+ dtype = mask .dtype )
423+ _image .resample (mask ,out_mask ,
424+ t ,
425+ _interpd_ [self .get_interpolation ()],
426+ True ,1 ,
427+ self .get_filternorm ()or 0.0 ,
428+ self .get_filterrad ()or 0.0 )
429+ # we are done with the mask, delete from namespace to be sure!
430+ del mask
431+ # Agg updates the out_mask in place. If the pixel has
432+ # no image data it will not be updated (and still be 0
433+ # as we initialized it), if input data that would go
434+ # into that output pixel than it will be `nan`, if all
435+ # the input data for a pixel is good it will be 1, and
436+ # if there is _some_ good data in that output pixel it
437+ # will be between [0, 1] (such as a rotated image).
438+
439+ out_alpha = np .array (out_mask )
440+ out_mask = np .isnan (out_mask )
441+ out_alpha [out_mask ]= 1
442+
443+ # mask and run through the norm
444+ output = self .norm (np .ma .masked_array (A_resampled ,out_mask ))
445+ else :
412446# Always convert to RGBA, even if only RGB input
413447if A .shape [2 ]== 3 :
414448A = _rgb_to_rgba (A )
@@ -421,57 +455,27 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
421455if alpha is None :
422456alpha = 1.0
423457
424- _image .resample (
425- A ,output ,t ,_interpd_ [self .get_interpolation ()],
426- self .get_resample (),alpha ,
427- self .get_filternorm ()or 0.0 ,self .get_filterrad ()or 0.0 )
428-
429- if created_rgba_mask :
430- # Convert back to a masked greyscale array so
431- # colormapping works correctly
432- hid_output = output
433- # any pixel where the a masked pixel is included
434- # in the kernel (pulling this down from 1) needs to
435- # be masked in the output
436- if len (mask .shape )== 2 :
437- out_mask = np .empty ((out_height ,out_width ),
438- dtype = mask .dtype )
439- _image .resample (mask ,out_mask ,t ,
440- _interpd_ [self .get_interpolation ()],
441- True ,1 ,
442- self .get_filternorm ()or 0.0 ,
443- self .get_filterrad ()or 0.0 )
444- out_mask = np .isnan (out_mask )
445- else :
446- out_mask = mask
447- # we need to mask both pixels which came in as masked
448- # and the pixels that Agg is telling us to ignore (relavent
449- # to non-affine transforms)
450- # Use half alpha as the threshold for pixels to mask.
451- out_mask = out_mask | (hid_output [...,3 ]< .5 )
452- output = np .ma .masked_array (
453- hid_output [...,0 ],
454- out_mask )
455- # 'unshare' the mask array to
456- # needed to suppress numpy warning
457- del out_mask
458- invalid_mask = ~ output .mask * ~ np .isnan (output .data )
459- # relabel under data. If any of the input data for
460- # the pixel has input out of the norm bounds,
461- output [np .isnan (hid_output [...,1 ])* invalid_mask ]= - 1
462- # relabel over data
463- output [np .isnan (hid_output [...,2 ])* invalid_mask ]= 2
458+ _image .resample (
459+ A ,output ,t ,_interpd_ [self .get_interpolation ()],
460+ self .get_resample (),alpha ,
461+ self .get_filternorm ()or 0.0 ,self .get_filterrad ()or 0.0 )
464462
463+ # at this point output is either a 2D array of normed data
464+ # (of int or float)
465+ # or an RGBA array of re-sampled input
465466output = self .to_rgba (output ,bytes = True ,norm = False )
467+ # output is now a correctly sized RGBA array of uint8
466468
467469# Apply alpha *after* if the input was greyscale without a mask
468- if A .ndim == 2 or created_rgba_mask :
470+ if A .ndim == 2 :
469471alpha = self .get_alpha ()
470- if alpha is not None and alpha != 1.0 :
471- alpha_channel = output [:, :,3 ]
472- alpha_channel [:]= np .asarray (
473- np .asarray (alpha_channel ,np .float32 )* alpha ,
474- np .uint8 )
472+ if alpha is None :
473+ alpha = 1
474+ alpha_channel = output [:, :,3 ]
475+ alpha_channel [:]= np .asarray (
476+ np .asarray (alpha_channel ,np .float32 )* out_alpha * alpha ,
477+ np .uint8 )
478+
475479else :
476480if self ._imcache is None :
477481self ._imcache = self .to_rgba (A ,bytes = True ,norm = (A .ndim == 2 ))