@@ -356,21 +356,42 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
356356 if not unsampled :
357357 created_rgba_mask = False
358358
359+ if A .ndim not in (2 , 3 ):
360+ raise ValueError ("Invalid dimensions, got %s" % (A .shape ,))
361+
359362 if A .ndim == 2 :
360363 A = self .norm (A )
361- # If the image is greyscale, convert to RGBA with the
362- # correct alpha channel for resizing
363- rgba = np .empty ((A .shape [0 ], A .shape [1 ], 4 ), dtype = A .dtype )
364- rgba [..., 0 :3 ] = np .expand_dims (A , 2 )
365364 if A .dtype .kind == 'f' :
366- rgba [..., 3 ] = ~ A .mask
365+ # If the image is greyscale, convert to RGBA and
366+ # use the extra channels for resizing the over,
367+ # under, and bad pixels. This is needed because
368+ # Agg's resampler is very aggressive about
369+ # clipping to [0, 1] and we use out-of-bounds
370+ # values to carry the over/under/bad information
371+ rgba = np .empty ((A .shape [0 ], A .shape [1 ], 4 ), dtype = A .dtype )
372+ rgba [..., 0 ] = A # normalized data
373+ rgba [..., 1 ] = A < 0 # under data
374+ rgba [..., 2 ] = A > 1 # over data
375+ rgba [..., 3 ] = ~ A .mask # bad data
376+ A = rgba
377+ output = np .zeros ((out_height , out_width , 4 ),
378+ dtype = A .dtype )
379+ alpha = 1.0
380+ created_rgba_mask = True
367381 else :
368- rgba [..., 3 ] = np .where (A .mask , 0 , np .iinfo (A .dtype ).max )
369- A = rgba
370- output = np .zeros ((out_height , out_width , 4 ), dtype = A .dtype )
371- alpha = 1.0
372- created_rgba_mask = True
373- elif A .ndim == 3 :
382+ # colormap norms that output integers (ex NoNorm
383+ # and BoundaryNorm) to RGBA space before
384+ # interpolating. This is needed due to the
385+ # Agg resampler only working on floats in the
386+ # range [0, 1] and because interpolating indexes
387+ # into an arbitrary LUT may be problematic.
388+ #
389+ # This falls back to interpolating in RGBA space which
390+ # can produce it's own artifacts of colors not in the map
391+ # showing up in the final image.
392+ A = self .cmap (A , alpha = self .get_alpha (), bytes = True )
393+
394+ if not created_rgba_mask :
374395 # Always convert to RGBA, even if only RGB input
375396 if A .shape [2 ] == 3 :
376397 A = _rgb_to_rgba (A )
@@ -382,8 +403,6 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
382403 alpha = self .get_alpha ()
383404 if alpha is None :
384405 alpha = 1.0
385- else :
386- raise ValueError ("Invalid dimensions, got %s" % (A .shape ,))
387406
388407 _image .resample (
389408 A , output , t , _interpd_ [self .get_interpolation ()],
@@ -393,8 +412,13 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
393412 if created_rgba_mask :
394413 # Convert back to a masked greyscale array so
395414 # colormapping works correctly
415+ hid_output = output
396416 output = np .ma .masked_array (
397- output [..., 0 ], output [..., 3 ] < 0.5 )
417+ hid_output [..., 0 ], hid_output [..., 3 ] < 0.5 )
418+ # relabel under data
419+ output [hid_output [..., 1 ] > .5 ] = - 1
420+ # relabel over data
421+ output [hid_output [..., 2 ] > .5 ] = 2
398422
399423 output = self .to_rgba (output , bytes = True , norm = False )
400424
0 commit comments