@@ -192,14 +192,6 @@ def _interpdr(self):
192192 def iterpnames (self ):
193193 return 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-
203195 def __str__ (self ):
204196 return "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,
357349 out_height = int (out_height_base )
358350
359351 if not unsampled :
360- created_rgba_mask = False
361-
362352 if A .ndim not in (2 , 3 ):
363353 raise ValueError ("Invalid dimensions, got {}" .format (A .shape ))
364354
365355 if 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 ))
398416 else :
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
413447 if A .shape [2 ] == 3 :
414448 A = _rgb_to_rgba (A )
@@ -421,57 +455,27 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
421455 if alpha is None :
422456 alpha = 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
465466 output = 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 :
469471 alpha = 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+
475479 else :
476480 if self ._imcache is None :
477481 self ._imcache = self .to_rgba (A , bytes = True , norm = (A .ndim == 2 ))
0 commit comments