@@ -356,58 +356,87 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
356356out_height = int (out_height_base )
357357
358358if not unsampled :
359- created_rgba_mask = False
360-
361359if A .ndim not in (2 ,3 ):
362360raise ValueError ("Invalid dimensions, got {}" .format (A .shape ))
363361
364362if A .ndim == 2 :
365- A = self .norm (A )
366- if A .dtype .kind == 'f' :
367- # If the image is greyscale, convert to RGBA and
368- # use the extra channels for resizing the over,
369- # under, and bad pixels. This is needed because
370- # Agg's resampler is very aggressive about
371- # clipping to [0, 1] and we use out-of-bounds
372- # values to carry the over/under/bad information
373- rgba = np .empty ((A .shape [0 ],A .shape [1 ],4 ),dtype = A .dtype )
374- rgba [...,0 ]= A # normalized data
375- # this is to work around spurious warnings coming
376- # out of masked arrays.
377- with np .errstate (invalid = 'ignore' ):
378- rgba [...,1 ]= np .where (A < 0 ,np .nan ,1 )# under data
379- rgba [...,2 ]= np .where (A > 1 ,np .nan ,1 )# over data
380- # Have to invert mask, Agg knows what alpha means
381- # so if you put this in as 0 for 'good' points, they
382- # all get zeroed out
383- rgba [...,3 ]= 1
384- if A .mask .shape == A .shape :
385- # this is the case of a nontrivial mask
386- mask = np .where (A .mask ,np .nan ,1 )
387- else :
388- # this is the case that the mask is a
389- # numpy.bool_ of False
390- mask = A .mask
391- # ~A.mask # masked data
392- A = rgba
393- output = np .zeros ((out_height ,out_width ,4 ),
394- dtype = A .dtype )
395- alpha = 1.0
396- created_rgba_mask = True
363+ # if we are a 2D array, then we are running through the
364+ # norm + colormap transformation. However, in general the
365+ # input data is not going to match the size on the screen so we
366+ # have to resample to the correct number of pixels
367+ # need to
368+
369+ # TODO slice input array first
370+
371+ # make a working array up here, re-use twice to save memory
372+ working_array = np .empty (A .shape ,dtype = np .float32 )
373+
374+ a_min = np .nanmin (A )
375+ a_max = np .nanmax (A )
376+ # scale the input data to [.1, .9]. The Agg
377+ # interpolators clip to [0, 1] internally, use a
378+ # smaller input scale to identify which of the
379+ # interpolated points need to be should be flagged as
380+ # over / under.
381+ # This may introduce numeric instabilities in very broadly
382+ # scaled data
383+ A_scaled = working_array
384+ A_scaled [:]= A
385+ A_scaled -= a_min
386+ A_scaled /= ((a_max - a_min )/ 0.8 )
387+ A_scaled += 0.1
388+ A_resampled = np .empty ((out_height ,out_width ),dtype = A_scaled .dtype )
389+ A_resampled [:]= np .nan
390+ # resample the input data to the correct resolution and shape
391+ _image .resample (A_scaled ,A_resampled ,
392+ t ,
393+ _interpd_ [self .get_interpolation ()],
394+ self .get_resample (),1.0 ,
395+ self .get_filternorm ()or 0.0 ,
396+ self .get_filterrad ()or 0.0 )
397+
398+ # we are done with A_scaled now, remove from namespace to be sure!
399+ del A_scaled
400+ # un-scale the resampled data to approximatly the
401+ # original range things that interpolated to above /
402+ # below the original min/max will still be above /
403+ # below, but possibly clipped in the case of higher order
404+ # interpolation + drastically changing data.
405+ A_resampled -= 0.1
406+ A_resampled *= ((a_max - a_min )/ 0.8 )
407+ A_resampled += a_min
408+ # if using NoNorm, cast back to the original datatype
409+ if isinstance (self .norm ,mcolors .NoNorm ):
410+ A_resampled = A_resampled .astype (A .dtype )
411+
412+ mask = working_array
413+ if A .mask .shape == A .shape :
414+ # this is the case of a nontrivial mask
415+ mask [:]= np .where (A .mask ,np .float32 (np .nan ),
416+ np .float32 (1 ))
397417else :
398- # colormap norms that output integers (ex NoNorm
399- # and BoundaryNorm) to RGBA space before
400- # interpolating. This is needed due to the
401- # Agg resampler only working on floats in the
402- # range [0, 1] and because interpolating indexes
403- # into an arbitrary LUT may be problematic.
404- #
405- # This falls back to interpolating in RGBA space which
406- # can produce it's own artifacts of colors not in the map
407- # showing up in the final image.
408- A = self .cmap (A ,alpha = self .get_alpha (),bytes = True )
409-
410- if not created_rgba_mask :
418+ mask [:]= 1
419+
420+ # we always have to interpolate the mask to account for
421+ # non-affine transformations
422+ out_mask = np .empty ((out_height ,out_width ),
423+ dtype = mask .dtype )
424+ out_mask [:]= np .nan
425+ _image .resample (mask ,out_mask ,
426+ t ,
427+ _interpd_ [self .get_interpolation ()],
428+ True ,1 ,
429+ self .get_filternorm ()or 0.0 ,
430+ self .get_filterrad ()or 0.0 )
431+ # we are done with the mask, delete from namespace to be sure!
432+ del mask
433+ # Agg tells us a pixel has no value not setting a value into it
434+ # thus, if we didn't set it, should still be nan.
435+ out_mask = np .isnan (out_mask )
436+
437+ # mask and run through the norm
438+ output = self .norm (np .ma .masked_array (A_resampled ,out_mask ))
439+ else :
411440# Always convert to RGBA, even if only RGB input
412441if A .shape [2 ]== 3 :
413442A = _rgb_to_rgba (A )
@@ -420,57 +449,25 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
420449if alpha is None :
421450alpha = 1.0
422451
423- _image .resample (
424- A ,output ,t ,_interpd_ [self .get_interpolation ()],
425- self .get_resample (),alpha ,
426- self .get_filternorm ()or 0.0 ,self .get_filterrad ()or 0.0 )
427-
428- if created_rgba_mask :
429- # Convert back to a masked greyscale array so
430- # colormapping works correctly
431- hid_output = output
432- # any pixel where the a masked pixel is included
433- # in the kernel (pulling this down from 1) needs to
434- # be masked in the output
435- if len (mask .shape )== 2 :
436- out_mask = np .empty ((out_height ,out_width ),
437- dtype = mask .dtype )
438- _image .resample (mask ,out_mask ,t ,
439- _interpd_ [self .get_interpolation ()],
440- True ,1 ,
441- self .get_filternorm ()or 0.0 ,
442- self .get_filterrad ()or 0.0 )
443- out_mask = np .isnan (out_mask )
444- else :
445- out_mask = mask
446- # we need to mask both pixels which came in as masked
447- # and the pixels that Agg is telling us to ignore (relavent
448- # to non-affine transforms)
449- # Use half alpha as the threshold for pixels to mask.
450- out_mask = out_mask | (hid_output [...,3 ]< .5 )
451- output = np .ma .masked_array (
452- hid_output [...,0 ],
453- out_mask )
454- # 'unshare' the mask array to
455- # needed to suppress numpy warning
456- del out_mask
457- invalid_mask = ~ output .mask * ~ np .isnan (output .data )
458- # relabel under data. If any of the input data for
459- # the pixel has input out of the norm bounds,
460- output [np .isnan (hid_output [...,1 ])* invalid_mask ]= - 1
461- # relabel over data
462- output [np .isnan (hid_output [...,2 ])* invalid_mask ]= 2
452+ _image .resample (
453+ A ,output ,t ,_interpd_ [self .get_interpolation ()],
454+ self .get_resample (),alpha ,
455+ self .get_filternorm ()or 0.0 ,self .get_filterrad ()or 0.0 )
463456
457+ # at this point output is either a 2D array of normed data (of int or float)
458+ # or an RGBA array of re-sampled input
464459output = self .to_rgba (output ,bytes = True ,norm = False )
460+ # output is now a correctly sized RGBA array of uint8
465461
466462# Apply alpha *after* if the input was greyscale without a mask
467- if A .ndim == 2 or created_rgba_mask :
463+ if A .ndim == 2 :
468464alpha = self .get_alpha ()
469465if alpha is not None and alpha != 1.0 :
470466alpha_channel = output [:, :,3 ]
471467alpha_channel [:]= np .asarray (
472468np .asarray (alpha_channel ,np .float32 )* alpha ,
473469np .uint8 )
470+
474471else :
475472if self ._imcache is None :
476473self ._imcache = self .to_rgba (A ,bytes = True ,norm = (A .ndim == 2 ))