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

Skip to content

Commit 71a760e

Browse files
committed
Shorten _ImageBase._make_image.
_ImageBase._make_image is a big, 267-line (+docstring) beast. Shorten it by ~20% by factoring out a helper for _image.resample (that takes care of allocating the output array), and with various small cleanups.
1 parent 8d487d8 commit 71a760e

File tree

1 file changed

+57
-90
lines changed

1 file changed

+57
-90
lines changed

lib/matplotlib/image.py

Lines changed: 57 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,25 @@ 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` that takes care of allocating
167+
the output array and fetching the relevant properties from the Image
168+
object.
169+
"""
170+
out = np.zeros(out_shape + data.shape[2:], data.dtype) # 2D->2D, 3D->3D.
171+
if resample is None:
172+
resample = image_obj.get_resample()
173+
_image.resample(data, out, transform,
174+
_interpd_[image_obj.get_interpolation()],
175+
resample,
176+
alpha,
177+
image_obj.get_filternorm(),
178+
image_obj.get_filterrad())
179+
return out
180+
181+
163182
def _rgb_to_rgba(A):
164183
"""
165184
Convert an RGB image to RGBA, as required by the image resample C++
@@ -320,32 +339,30 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
320339
-clipped_bbox.y0)
321340
.scale(magnification, magnification))
322341

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.
342+
# So that the image is aligned with the edge of the axes, we want to
343+
# round up the output width to the next integer. This also means
344+
# scaling the transform slightly to account for the extra subpixel.
327345
if (t.is_affine and round_to_pixel_border and
328346
(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))
347+
out_width = math.ceil(out_width_base)
348+
out_height = math.ceil(out_height_base)
331349
extra_width = (out_width - out_width_base) / out_width_base
332350
extra_height = (out_height - out_height_base) / out_height_base
333351
t += Affine2D().scale(1.0 + extra_width, 1.0 + extra_height)
334352
else:
335353
out_width = int(out_width_base)
336354
out_height = int(out_height_base)
355+
out_shape = (out_height, out_width)
337356

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

343361
if A.ndim == 2:
344362
# if we are a 2D array, then we are running through the
345363
# norm + colormap transformation. However, in general the
346364
# input data is not going to match the size on the screen so we
347365
# have to resample to the correct number of pixels
348-
# need to
349366

350367
# TODO slice input array first
351368
inp_dtype = A.dtype
@@ -363,18 +380,15 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
363380
# Cast to float64
364381
if A.dtype not in (np.float32, np.float16):
365382
if A.dtype != np.float64:
366-
cbook._warn_external("Casting input data from "
367-
"'{0}' to 'float64' for "
368-
"imshow".format(A.dtype))
383+
cbook._warn_external(
384+
f"Casting input data from '{A.dtype}' to "
385+
f"'float64' for imshow")
369386
scaled_dtype = np.float64
370387
else:
371388
# probably an integer of some type.
372389
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
390+
# give more breathing room if a big dynamic range
391+
scaled_dtype = np.float64 if da > 1e8 else np.float32
378392

379393
# scale the input data to [.1, .9]. The Agg
380394
# interpolators clip to [0, 1] internally, use a
@@ -383,16 +397,15 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
383397
# over / under.
384398
# This may introduce numeric instabilities in very broadly
385399
# scaled data
386-
A_scaled = np.empty(A.shape, dtype=scaled_dtype)
387-
A_scaled[:] = A
400+
# Always copy, and don't allow array subtypes.
401+
A_scaled = np.array(A, dtype=scaled_dtype)
388402
# clip scaled data around norm if necessary.
389403
# This is necessary for big numbers at the edge of
390404
# float64's ability to represent changes. Applying
391405
# a norm first would be good, but ruins the interpolation
392406
# of over numbers.
393407
self.norm.autoscale_None(A)
394-
dv = (np.float64(self.norm.vmax) -
395-
np.float64(self.norm.vmin))
408+
dv = np.float64(self.norm.vmax) - np.float64(self.norm.vmin)
396409
vmid = self.norm.vmin + dv / 2
397410
fact = 1e7 if scaled_dtype == np.float64 else 1e4
398411
newmin = vmid - dv * fact
@@ -406,7 +419,7 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
406419
else:
407420
a_max = np.float64(newmax)
408421
if newmax is not None or newmin is not None:
409-
A_scaled = np.clip(A_scaled, newmin, newmax)
422+
np.clip(A_scaled, newmin, newmax, out=A_scaled)
410423

411424
A_scaled -= a_min
412425
# a_min and a_max might be ndarray subclasses so use
@@ -417,18 +430,10 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
417430
if a_min != a_max:
418431
A_scaled /= ((a_max - a_min) / 0.8)
419432
A_scaled += 0.1
420-
A_resampled = np.zeros((out_height, out_width),
421-
dtype=A_scaled.dtype)
422433
# 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!
434+
A_resampled = _resample(self, A_scaled, out_shape, t)
435+
436+
# done with A_scaled now, remove from namespace to be sure!
432437
del A_scaled
433438
# un-scale the resampled data to approximately the
434439
# original range things that interpolated to above /
@@ -443,73 +448,35 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
443448
if isinstance(self.norm, mcolors.NoNorm):
444449
A_resampled = A_resampled.astype(A.dtype)
445450

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-
451+
mask = (np.where(A.mask, np.float32(np.nan), np.float32(1))
452+
if A.mask.shape == A.shape # nontrivial mask
453+
else np.ones_like(A, np.float32))
454454
# we always have to interpolate the mask to account for
455455
# 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!
456+
out_alpha = _resample(self, mask, out_shape, t, resample=True)
457+
# done with the mask now, delete from namespace to be sure!
465458
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)
459+
# Agg updates out_alpha in place. If the pixel has no image
460+
# data it will not be updated (and still be 0 as we initialized
461+
# it), if input data that would go into that output pixel than
462+
# it will be `nan`, if all the input data for a pixel is good
463+
# it will be 1, and if there is _some_ good data in that output
464+
# pixel it will be between [0, 1] (such as a rotated image).
465+
out_mask = np.isnan(out_alpha)
476466
out_alpha[out_mask] = 1
477-
478467
# mask and run through the norm
479468
output = self.norm(np.ma.masked_array(A_resampled, out_mask))
480469
else:
481-
# Always convert to RGBA, even if only RGB input
482470
if A.shape[2] == 3:
483471
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-
491472
alpha = self.get_alpha()
492473
if alpha is None:
493474
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
475+
output_a = _resample( # resample alpha channel
476+
self, A[..., 3], out_shape, t, alpha=alpha)
477+
output = _resample( # resample rgb channels
478+
self, _rgb_to_rgba(A[..., :3]), out_shape, t, alpha=alpha)
479+
output[..., 3] = output_a # recombine rgb and alpha channels
513480

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

0 commit comments

Comments
 (0)