Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a883cd2

Browse files
committed
FIX: first pass at re-working image interpolation
- interpolate raw, not normed, data - should reduce memory footprint, only 1 or 2 copies of input data - this changes many tests in small ways
1 parent 5f534ad commit a883cd2

File tree

1 file changed

+85
-88
lines changed

1 file changed

+85
-88
lines changed

lib/matplotlib/image.py

Lines changed: 85 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)