@@ -356,58 +356,87 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
356356 out_height = int (out_height_base )
357357
358358 if not unsampled :
359- created_rgba_mask = False
360-
361359 if A .ndim not in (2 , 3 ):
362360 raise ValueError ("Invalid dimensions, got {}" .format (A .shape ))
363361
364362 if 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 ))
397417 else :
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
412441 if A .shape [2 ] == 3 :
413442 A = _rgb_to_rgba (A )
@@ -420,57 +449,25 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
420449 if alpha is None :
421450 alpha = 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
464459 output = 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 :
468464 alpha = self .get_alpha ()
469465 if alpha is not None and alpha != 1.0 :
470466 alpha_channel = output [:, :, 3 ]
471467 alpha_channel [:] = np .asarray (
472468 np .asarray (alpha_channel , np .float32 ) * alpha ,
473469 np .uint8 )
470+
474471 else :
475472 if self ._imcache is None :
476473 self ._imcache = self .to_rgba (A , bytes = True , norm = (A .ndim == 2 ))
0 commit comments