Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f899d04

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

File tree

Expand file treeCollapse file tree

1 file changed

+58
-90
lines changed
Filter options
Expand file treeCollapse file tree

1 file changed

+58
-90
lines changed

‎lib/matplotlib/image.py

Copy file name to clipboardExpand all lines: lib/matplotlib/image.py
+58-90Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.