@@ -357,58 +357,98 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
357357 out_height = int (out_height_base )
358358
359359 if not unsampled :
360- created_rgba_mask = False
361-
362360 if A .ndim not in (2 , 3 ):
363361 raise ValueError ("Invalid dimensions, got {}" .format (A .shape ))
364362
365363 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
364+ # if we are a 2D array, then we are running through the
365+ # norm + colormap transformation. However, in general the
366+ # input data is not going to match the size on the screen so we
367+ # have to resample to the correct number of pixels
368+ # need to
369+
370+ # TODO slice input array first
371+ inp_dtype = A .dtype
372+ if inp_dtype .kind == 'f' :
373+ scaled_dtype = A .dtype
398374 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 :
375+ scaled_dtype = np .float32
376+ # old versions of numpy do not work with `np.nammin`
377+ # and `np.nanmax` as inputs
378+ a_min = np .ma .min (A )
379+ a_max = np .ma .max (A )
380+ # scale the input data to [.1, .9]. The Agg
381+ # interpolators clip to [0, 1] internally, use a
382+ # smaller input scale to identify which of the
383+ # interpolated points need to be should be flagged as
384+ # over / under.
385+ # This may introduce numeric instabilities in very broadly
386+ # scaled data
387+ A_scaled = np .empty (A .shape , dtype = scaled_dtype )
388+ A_scaled [:] = A
389+ A_scaled -= a_min
390+ A_scaled /= ((a_max - a_min ) / 0.8 )
391+ A_scaled += 0.1
392+ A_resampled = np .zeros ((out_height , out_width ),
393+ dtype = A_scaled .dtype )
394+ # resample the input data to the correct resolution and shape
395+ _image .resample (A_scaled , A_resampled ,
396+ t ,
397+ _interpd_ [self .get_interpolation ()],
398+ self .get_resample (), 1.0 ,
399+ self .get_filternorm () or 0.0 ,
400+ self .get_filterrad () or 0.0 )
401+
402+ # we are done with A_scaled now, remove from namespace
403+ # to be sure!
404+ del A_scaled
405+ # un-scale the resampled data to approximately the
406+ # original range things that interpolated to above /
407+ # below the original min/max will still be above /
408+ # below, but possibly clipped in the case of higher order
409+ # interpolation + drastically changing data.
410+ A_resampled -= 0.1
411+ A_resampled *= ((a_max - a_min ) / 0.8 )
412+ A_resampled += a_min
413+ # if using NoNorm, cast back to the original datatype
414+ if isinstance (self .norm , mcolors .NoNorm ):
415+ A_resampled = A_resampled .astype (A .dtype )
416+
417+ mask = np .empty (A .shape , dtype = np .float32 )
418+ if A .mask .shape == A .shape :
419+ # this is the case of a nontrivial mask
420+ mask [:] = np .where (A .mask , np .float32 (np .nan ),
421+ np .float32 (1 ))
422+ else :
423+ mask [:] = 1
424+
425+ # we always have to interpolate the mask to account for
426+ # non-affine transformations
427+ out_mask = np .zeros ((out_height , out_width ),
428+ dtype = mask .dtype )
429+ _image .resample (mask , out_mask ,
430+ t ,
431+ _interpd_ [self .get_interpolation ()],
432+ True , 1 ,
433+ self .get_filternorm () or 0.0 ,
434+ self .get_filterrad () or 0.0 )
435+ # we are done with the mask, delete from namespace to be sure!
436+ del mask
437+ # Agg updates the out_mask in place. If the pixel has
438+ # no image data it will not be updated (and still be 0
439+ # as we initialized it), if input data that would go
440+ # into that output pixel than it will be `nan`, if all
441+ # the input data for a pixel is good it will be 1, and
442+ # if there is _some_ good data in that output pixel it
443+ # will be between [0, 1] (such as a rotated image).
444+
445+ out_alpha = np .array (out_mask )
446+ out_mask = np .isnan (out_mask )
447+ out_alpha [out_mask ] = 1
448+
449+ # mask and run through the norm
450+ output = self .norm (np .ma .masked_array (A_resampled , out_mask ))
451+ else :
412452 # Always convert to RGBA, even if only RGB input
413453 if A .shape [2 ] == 3 :
414454 A = _rgb_to_rgba (A )
@@ -421,57 +461,27 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
421461 if alpha is None :
422462 alpha = 1.0
423463
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
464+ _image .resample (
465+ A , output , t , _interpd_ [self .get_interpolation ()],
466+ self .get_resample (), alpha ,
467+ self .get_filternorm () or 0.0 , self .get_filterrad () or 0.0 )
464468
469+ # at this point output is either a 2D array of normed data
470+ # (of int or float)
471+ # or an RGBA array of re-sampled input
465472 output = self .to_rgba (output , bytes = True , norm = False )
473+ # output is now a correctly sized RGBA array of uint8
466474
467475 # Apply alpha *after* if the input was greyscale without a mask
468- if A .ndim == 2 or created_rgba_mask :
476+ if A .ndim == 2 :
469477 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 )
478+ if alpha is None :
479+ alpha = 1
480+ alpha_channel = output [:, :, 3 ]
481+ alpha_channel [:] = np .asarray (
482+ np .asarray (alpha_channel , np .float32 ) * out_alpha * alpha ,
483+ np .uint8 )
484+
475485 else :
476486 if self ._imcache is None :
477487 self ._imcache = self .to_rgba (A , bytes = True , norm = (A .ndim == 2 ))
0 commit comments