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

Skip to content

Commit f899d04

Browse files
authored
Merge pull request #14101 from anntzer/make_image
Shorten _ImageBase._make_image.
2 parents e551980 + 0906271 commit f899d04

File tree

1 file changed

+58
-90
lines changed

1 file changed

+58
-90
lines changed

lib/matplotlib/image.py

Lines changed: 58 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"""
55

66
from io import BytesIO
7-
from math import ceil
7+
import math
88
import os
99
import logging
1010
from pathlib import Path
@@ -160,6 +160,26 @@ def flush_images():
160160
flush_images()
161161

162162

163+
def _resample(
164+
image_obj, data, out_shape, transform, *, resample=None, alpha=1):
165+
"""
166+
Convenience wrapper around `._image.resample` to resample *data* to
167+
*out_shape* (with a third dimension if *data* is RGBA) that takes care of
168+
allocating the output array and fetching the relevant properties from the
169+
Image object *image_obj*.
170+
"""
171+
out = np.zeros(out_shape + data.shape[2:], data.dtype) # 2D->2D, 3D->3D.
172+
if resample is None:
173+
resample = image_obj.get_resample()
174+
_image.resample(data, out, transform,
175+
_interpd_[image_obj.get_interpolation()],
176+
resample,
177+
alpha,
178+
image_obj.get_filternorm(),
179+
image_obj.get_filterrad())
180+
return out
181+
182+
163183
def _rgb_to_rgba(A):
164184
"""
165185
Convert an RGB image to RGBA, as required by the image resample C++
@@ -320,32 +340,30 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
320340
-clipped_bbox.y0)
321341
.scale(magnification, magnification))
322342

323-
# So that the image is aligned with the edge of the axes, we want
324-
# to round up the output width to the next integer. This also
325-
# means scaling the transform just slightly to account for the
326-
# extra subpixel.
343+
# So that the image is aligned with the edge of the axes, we want to
344+
# round up the output width to the next integer. This also means
345+
# scaling the transform slightly to account for the extra subpixel.
327346
if (t.is_affine and round_to_pixel_border and
328347
(out_width_base % 1.0 != 0.0 or out_height_base % 1.0 != 0.0)):
329-
out_width = int(ceil(out_width_base))
330-
out_height = int(ceil(out_height_base))
348+
out_width = math.ceil(out_width_base)
349+
out_height = math.ceil(out_height_base)
331350
extra_width = (out_width - out_width_base) / out_width_base
332351
extra_height = (out_height - out_height_base) / out_height_base
333352
t += Affine2D().scale(1.0 + extra_width, 1.0 + extra_height)
334353
else:
335354
out_width = int(out_width_base)
336355
out_height = int(out_height_base)
356+
out_shape = (out_height, out_width)
337357

338358
if not unsampled:
339-
if A.ndim not in (2, 3):
340-
raise ValueError("Invalid shape {} for image data"
341-
.format(A.shape))
359+
if not (A.ndim == 2 or A.ndim == 3 and A.shape[-1] in (3, 4)):
360+
raise ValueError(f"Invalid shape {A.shape} for image data")
342361

343362
if A.ndim == 2:
344363
# if we are a 2D array, then we are running through the
345364
# norm + colormap transformation. However, in general the
346365
# input data is not going to match the size on the screen so we
347366
# have to resample to the correct number of pixels
348-
# need to
349367

350368
# TODO slice input array first
351369
inp_dtype = A.dtype
@@ -363,18 +381,15 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
363381
# Cast to float64
364382
if A.dtype not in (np.float32, np.float16):
365383
if A.dtype != np.float64:
366-
cbook._warn_external("Casting input data from "
367-
"'{0}' to 'float64' for "
368-
"imshow".format(A.dtype))
384+
cbook._warn_external(
385+
f"Casting input data from '{A.dtype}' to "
386+
f"'float64' for imshow")
369387
scaled_dtype = np.float64
370388
else:
371389
# probably an integer of some type.
372390
da = a_max.astype(np.float64) - a_min.astype(np.float64)
373-
if da > 1e8:
374-
# give more breathing room if a big dynamic range
375-
scaled_dtype = np.float64
376-
else:
377-
scaled_dtype = np.float32
391+
# give more breathing room if a big dynamic range
392+
scaled_dtype = np.float64 if da > 1e8 else np.float32
378393

379394
# scale the input data to [.1, .9]. The Agg
380395
# interpolators clip to [0, 1] internally, use a
@@ -383,16 +398,15 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
383398
# over / under.
384399
# This may introduce numeric instabilities in very broadly
385400
# scaled data
386-
A_scaled = np.empty(A.shape, dtype=scaled_dtype)
387-
A_scaled[:] = A
401+
# Always copy, and don't allow array subtypes.
402+
A_scaled = np.array(A, dtype=scaled_dtype)
388403
# clip scaled data around norm if necessary.
389404
# This is necessary for big numbers at the edge of
390405
# float64's ability to represent changes. Applying
391406
# a norm first would be good, but ruins the interpolation
392407
# of over numbers.
393408
self.norm.autoscale_None(A)
394-
dv = (np.float64(self.norm.vmax) -
395-
np.float64(self.norm.vmin))
409+
dv = np.float64(self.norm.vmax) - np.float64(self.norm.vmin)
396410
vmid = self.norm.vmin + dv / 2
397411
fact = 1e7 if scaled_dtype == np.float64 else 1e4
398412
newmin = vmid - dv * fact
@@ -406,7 +420,7 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
406420
else:
407421
a_max = np.float64(newmax)
408422
if newmax is not None or newmin is not None:
409-
A_scaled = np.clip(A_scaled, newmin, newmax)
423+
np.clip(A_scaled, newmin, newmax, out=A_scaled)
410424

411425
A_scaled -= a_min
412426
# a_min and a_max might be ndarray subclasses so use
@@ -417,18 +431,10 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
417431
if a_min != a_max:
418432
A_scaled /= ((a_max - a_min) / 0.8)
419433
A_scaled += 0.1
420-
A_resampled = np.zeros((out_height, out_width),
421-
dtype=A_scaled.dtype)
422434
# resample the input data to the correct resolution and shape
423-
_image.resample(A_scaled, A_resampled,
424-
t,
425-
_interpd_[self.get_interpolation()],
426-
self.get_resample(), 1.0,
427-
self.get_filternorm(),
428-
self.get_filterrad())
429-
430-
# we are done with A_scaled now, remove from namespace
431-
# to be sure!
435+
A_resampled = _resample(self, A_scaled, out_shape, t)
436+
437+
# done with A_scaled now, remove from namespace to be sure!
432438
del A_scaled
433439
# un-scale the resampled data to approximately the
434440
# original range things that interpolated to above /
@@ -443,73 +449,35 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
443449
if isinstance(self.norm, mcolors.NoNorm):
444450
A_resampled = A_resampled.astype(A.dtype)
445451

446-
mask = np.empty(A.shape, dtype=np.float32)
447-
if A.mask.shape == A.shape:
448-
# this is the case of a nontrivial mask
449-
mask[:] = np.where(A.mask, np.float32(np.nan),
450-
np.float32(1))
451-
else:
452-
mask[:] = 1
453-
452+
mask = (np.where(A.mask, np.float32(np.nan), np.float32(1))
453+
if A.mask.shape == A.shape # nontrivial mask
454+
else np.ones_like(A, np.float32))
454455
# we always have to interpolate the mask to account for
455456
# non-affine transformations
456-
out_mask = np.zeros((out_height, out_width),
457-
dtype=mask.dtype)
458-
_image.resample(mask, out_mask,
459-
t,
460-
_interpd_[self.get_interpolation()],
461-
True, 1,
462-
self.get_filternorm(),
463-
self.get_filterrad())
464-
# we are done with the mask, delete from namespace to be sure!
457+
out_alpha = _resample(self, mask, out_shape, t, resample=True)
458+
# done with the mask now, delete from namespace to be sure!
465459
del mask
466-
# Agg updates the out_mask in place. If the pixel has
467-
# no image data it will not be updated (and still be 0
468-
# as we initialized it), if input data that would go
469-
# into that output pixel than it will be `nan`, if all
470-
# the input data for a pixel is good it will be 1, and
471-
# if there is _some_ good data in that output pixel it
472-
# will be between [0, 1] (such as a rotated image).
473-
474-
out_alpha = np.array(out_mask)
475-
out_mask = np.isnan(out_mask)
460+
# Agg updates out_alpha in place. If the pixel has no image
461+
# data it will not be updated (and still be 0 as we initialized
462+
# it), if input data that would go into that output pixel than
463+
# it will be `nan`, if all the input data for a pixel is good
464+
# it will be 1, and if there is _some_ good data in that output
465+
# pixel it will be between [0, 1] (such as a rotated image).
466+
out_mask = np.isnan(out_alpha)
476467
out_alpha[out_mask] = 1
477-
478468
# mask and run through the norm
479469
output = self.norm(np.ma.masked_array(A_resampled, out_mask))
480470
else:
481-
# Always convert to RGBA, even if only RGB input
482471
if A.shape[2] == 3:
483472
A = _rgb_to_rgba(A)
484-
elif A.shape[2] != 4:
485-
raise ValueError("Invalid shape {} for image data"
486-
.format(A.shape))
487-
488-
output = np.zeros((out_height, out_width, 4), dtype=A.dtype)
489-
output_a = np.zeros((out_height, out_width), dtype=A.dtype)
490-
491473
alpha = self.get_alpha()
492474
if alpha is None:
493475
alpha = 1
494-
495-
#resample alpha channel
496-
alpha_channel = A[..., 3]
497-
_image.resample(
498-
alpha_channel, output_a, t,
499-
_interpd_[self.get_interpolation()],
500-
self.get_resample(), alpha,
501-
self.get_filternorm(), self.get_filterrad())
502-
503-
#resample rgb channels
504-
A = _rgb_to_rgba(A[..., :3])
505-
_image.resample(
506-
A, output, t,
507-
_interpd_[self.get_interpolation()],
508-
self.get_resample(), alpha,
509-
self.get_filternorm(), self.get_filterrad())
510-
511-
#recombine rgb and alpha channels
512-
output[..., 3] = output_a
476+
output_alpha = _resample( # resample alpha channel
477+
self, A[..., 3], out_shape, t, alpha=alpha)
478+
output = _resample( # resample rgb channels
479+
self, _rgb_to_rgba(A[..., :3]), out_shape, t, alpha=alpha)
480+
output[..., 3] = output_alpha # recombine rgb and alpha
513481

514482
# at this point output is either a 2D array of normed data
515483
# (of int or float)

0 commit comments

Comments
 (0)