@@ -460,15 +460,36 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
460460 if newmax is not None or newmin is not None :
461461 np .clip (A_scaled , newmin , newmax , out = A_scaled )
462462
463+ # used to rescale the raw data to [offset, 1-offset]
464+ # so that the resampling code will run cleanly. Using
465+ # dyadic numbers here could reduce the error, but
466+ # would not full eliminate it and breaks a number of
467+ # tests (due to the slightly different error bouncing
468+ # some pixels across a boundary in the (very
469+ # quantized) color mapping step).
470+ offset = .1
471+ frac = .8
472+ # we need to run the vmin/vmax through the same rescaling
473+ # that we run the raw data through because there are small
474+ # errors in the round-trip due to float precision. If we
475+ # do not run the vmin/vmax through the same pipeline we can
476+ # have values close or equal to the boundaries end up on the
477+ # wrong side.
478+ vrange = np .array ([self .norm .vmin , self .norm .vmax ],
479+ dtype = scaled_dtype )
480+
463481 A_scaled -= a_min
482+ vrange -= a_min
464483 # a_min and a_max might be ndarray subclasses so use
465484 # item to avoid errors
466485 a_min = a_min .astype (scaled_dtype ).item ()
467486 a_max = a_max .astype (scaled_dtype ).item ()
468487
469488 if a_min != a_max :
470- A_scaled /= ((a_max - a_min ) / 0.8 )
471- A_scaled += 0.1
489+ A_scaled /= ((a_max - a_min ) / frac )
490+ vrange /= ((a_max - a_min ) / frac )
491+ A_scaled += offset
492+ vrange += offset
472493 # resample the input data to the correct resolution and shape
473494 A_resampled = _resample (self , A_scaled , out_shape , t )
474495 # done with A_scaled now, remove from namespace to be sure!
@@ -478,10 +499,13 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
478499 # below the original min/max will still be above /
479500 # below, but possibly clipped in the case of higher order
480501 # interpolation + drastically changing data.
481- A_resampled -= 0.1
502+ A_resampled -= offset
503+ vrange -= offset
482504 if a_min != a_max :
483- A_resampled *= ((a_max - a_min ) / 0.8 )
505+ A_resampled *= ((a_max - a_min ) / frac )
506+ vrange *= ((a_max - a_min ) / frac )
484507 A_resampled += a_min
508+ vrange += a_min
485509 # if using NoNorm, cast back to the original datatype
486510 if isinstance (self .norm , mcolors .NoNorm ):
487511 A_resampled = A_resampled .astype (A .dtype )
@@ -508,7 +532,14 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
508532 out_alpha *= _resample (self , alpha , out_shape ,
509533 t , resample = True )
510534 # mask and run through the norm
511- output = self .norm (np .ma .masked_array (A_resampled , out_mask ))
535+ resampled_masked = np .ma .masked_array (A_resampled , out_mask )
536+ # we have re-set the vmin/vmax to account for small errors
537+ # that may have moved input values in/out of range
538+ with cbook ._setattr_cm (self .norm ,
539+ vmin = vrange [0 ],
540+ vmax = vrange [1 ],
541+ ):
542+ output = self .norm (resampled_masked )
512543 else :
513544 if A .shape [2 ] == 3 :
514545 A = _rgb_to_rgba (A )
0 commit comments