Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/matplotlib/image.py : 14%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2The image module supports basic image loading, rescaling and display
3operations.
4"""
6from io import BytesIO
7import math
8import os
9import logging
10from numbers import Number
11from pathlib import Path
12import urllib.parse
14import numpy as np
16from matplotlib import rcParams
17import matplotlib.artist as martist
18from matplotlib.backend_bases import FigureCanvasBase
19import matplotlib.colors as mcolors
20import matplotlib.cm as cm
21import matplotlib.cbook as cbook
23# For clarity, names from _image are given explicitly in this module:
24import matplotlib._image as _image
26# For user convenience, the names from _image are also imported into
27# the image namespace:
28from matplotlib._image import *
30from matplotlib.transforms import (Affine2D, BboxBase, Bbox, BboxTransform,
31 IdentityTransform, TransformedBbox)
33_log = logging.getLogger(__name__)
35# map interpolation strings to module constants
36_interpd_ = {
37 'antialiased': _image.NEAREST, # this will use nearest or Hanning...
38 'none': _image.NEAREST, # fall back to nearest when not supported
39 'nearest': _image.NEAREST,
40 'bilinear': _image.BILINEAR,
41 'bicubic': _image.BICUBIC,
42 'spline16': _image.SPLINE16,
43 'spline36': _image.SPLINE36,
44 'hanning': _image.HANNING,
45 'hamming': _image.HAMMING,
46 'hermite': _image.HERMITE,
47 'kaiser': _image.KAISER,
48 'quadric': _image.QUADRIC,
49 'catrom': _image.CATROM,
50 'gaussian': _image.GAUSSIAN,
51 'bessel': _image.BESSEL,
52 'mitchell': _image.MITCHELL,
53 'sinc': _image.SINC,
54 'lanczos': _image.LANCZOS,
55 'blackman': _image.BLACKMAN,
56}
58interpolations_names = set(_interpd_)
61def composite_images(images, renderer, magnification=1.0):
62 """
63 Composite a number of RGBA images into one. The images are
64 composited in the order in which they appear in the `images` list.
66 Parameters
67 ----------
68 images : list of Images
69 Each must have a `make_image` method. For each image,
70 `can_composite` should return `True`, though this is not
71 enforced by this function. Each image must have a purely
72 affine transformation with no shear.
74 renderer : RendererBase instance
76 magnification : float
77 The additional magnification to apply for the renderer in use.
79 Returns
80 -------
81 tuple : image, offset_x, offset_y
82 Returns the tuple:
84 - image: A numpy array of the same type as the input images.
86 - offset_x, offset_y: The offset of the image (left, bottom)
87 in the output figure.
88 """
89 if len(images) == 0:
90 return np.empty((0, 0, 4), dtype=np.uint8), 0, 0
92 parts = []
93 bboxes = []
94 for image in images:
95 data, x, y, trans = image.make_image(renderer, magnification)
96 if data is not None:
97 x *= magnification
98 y *= magnification
99 parts.append((data, x, y, image._get_scalar_alpha()))
100 bboxes.append(
101 Bbox([[x, y], [x + data.shape[1], y + data.shape[0]]]))
103 if len(parts) == 0:
104 return np.empty((0, 0, 4), dtype=np.uint8), 0, 0
106 bbox = Bbox.union(bboxes)
108 output = np.zeros(
109 (int(bbox.height), int(bbox.width), 4), dtype=np.uint8)
111 for data, x, y, alpha in parts:
112 trans = Affine2D().translate(x - bbox.x0, y - bbox.y0)
113 _image.resample(data, output, trans, _image.NEAREST,
114 resample=False, alpha=alpha)
116 return output, bbox.x0 / magnification, bbox.y0 / magnification
119def _draw_list_compositing_images(
120 renderer, parent, artists, suppress_composite=None):
121 """
122 Draw a sorted list of artists, compositing images into a single
123 image where possible.
125 For internal matplotlib use only: It is here to reduce duplication
126 between `Figure.draw` and `Axes.draw`, but otherwise should not be
127 generally useful.
128 """
129 has_images = any(isinstance(x, _ImageBase) for x in artists)
131 # override the renderer default if suppressComposite is not None
132 not_composite = (suppress_composite if suppress_composite is not None
133 else renderer.option_image_nocomposite())
135 if not_composite or not has_images:
136 for a in artists:
137 a.draw(renderer)
138 else:
139 # Composite any adjacent images together
140 image_group = []
141 mag = renderer.get_image_magnification()
143 def flush_images():
144 if len(image_group) == 1:
145 image_group[0].draw(renderer)
146 elif len(image_group) > 1:
147 data, l, b = composite_images(image_group, renderer, mag)
148 if data.size != 0:
149 gc = renderer.new_gc()
150 gc.set_clip_rectangle(parent.bbox)
151 gc.set_clip_path(parent.get_clip_path())
152 renderer.draw_image(gc, round(l), round(b), data)
153 gc.restore()
154 del image_group[:]
156 for a in artists:
157 if isinstance(a, _ImageBase) and a.can_composite():
158 image_group.append(a)
159 else:
160 flush_images()
161 a.draw(renderer)
162 flush_images()
165def _resample(
166 image_obj, data, out_shape, transform, *, resample=None, alpha=1):
167 """
168 Convenience wrapper around `._image.resample` to resample *data* to
169 *out_shape* (with a third dimension if *data* is RGBA) that takes care of
170 allocating the output array and fetching the relevant properties from the
171 Image object *image_obj*.
172 """
174 # decide if we need to apply anti-aliasing if the data is upsampled:
175 # compare the number of displayed pixels to the number of
176 # the data pixels.
177 interpolation = image_obj.get_interpolation()
178 if interpolation == 'antialiased':
179 # don't antialias if upsampling by an integer number or
180 # if zooming in more than a factor of 3
181 pos = np.array([[0, 0], [data.shape[1], data.shape[0]]])
182 disp = transform.transform(pos)
183 dispx = np.abs(np.diff(disp[:, 0]))
184 dispy = np.abs(np.diff(disp[:, 1]))
185 if ((dispx > 3 * data.shape[1] or
186 dispx == data.shape[1] or
187 dispx == 2 * data.shape[1]) and
188 (dispy > 3 * data.shape[0] or
189 dispy == data.shape[0] or
190 dispy == 2 * data.shape[0])):
191 interpolation = 'nearest'
192 else:
193 interpolation = 'hanning'
194 out = np.zeros(out_shape + data.shape[2:], data.dtype) # 2D->2D, 3D->3D.
195 if resample is None:
196 resample = image_obj.get_resample()
197 _image.resample(data, out, transform,
198 _interpd_[interpolation],
199 resample,
200 alpha,
201 image_obj.get_filternorm(),
202 image_obj.get_filterrad())
203 return out
206def _rgb_to_rgba(A):
207 """
208 Convert an RGB image to RGBA, as required by the image resample C++
209 extension.
210 """
211 rgba = np.zeros((A.shape[0], A.shape[1], 4), dtype=A.dtype)
212 rgba[:, :, :3] = A
213 if rgba.dtype == np.uint8:
214 rgba[:, :, 3] = 255
215 else:
216 rgba[:, :, 3] = 1.0
217 return rgba
220class _ImageBase(martist.Artist, cm.ScalarMappable):
221 """
222 Base class for images.
224 interpolation and cmap default to their rc settings
226 cmap is a colors.Colormap instance
227 norm is a colors.Normalize instance to map luminance to 0-1
229 extent is data axes (left, right, bottom, top) for making image plots
230 registered with data plots. Default is to label the pixel
231 centers with the zero-based row and column indices.
233 Additional kwargs are matplotlib.artist properties
234 """
235 zorder = 0
237 def __init__(self, ax,
238 cmap=None,
239 norm=None,
240 interpolation=None,
241 origin=None,
242 filternorm=True,
243 filterrad=4.0,
244 resample=False,
245 **kwargs
246 ):
247 martist.Artist.__init__(self)
248 cm.ScalarMappable.__init__(self, norm, cmap)
249 self._mouseover = True
250 if origin is None:
251 origin = rcParams['image.origin']
252 self.origin = origin
253 self.set_filternorm(filternorm)
254 self.set_filterrad(filterrad)
255 self.set_interpolation(interpolation)
256 self.set_resample(resample)
257 self.axes = ax
259 self._imcache = None
261 self.update(kwargs)
263 def __getstate__(self):
264 state = super().__getstate__()
265 # We can't pickle the C Image cached object.
266 state['_imcache'] = None
267 return state
269 def get_size(self):
270 """Return the size of the image as tuple (numrows, numcols)."""
271 if self._A is None:
272 raise RuntimeError('You must first set the image array')
274 return self._A.shape[:2]
276 def set_alpha(self, alpha):
277 """
278 Set the alpha value used for blending - not supported on all backends.
280 Parameters
281 ----------
282 alpha : float
283 """
284 if alpha is not None and not isinstance(alpha, Number):
285 alpha = np.asarray(alpha)
286 if alpha.ndim != 2:
287 raise TypeError('alpha must be a float, two-dimensional '
288 'array, or None')
289 self._alpha = alpha
290 self.pchanged()
291 self.stale = True
292 self._imcache = None
294 def _get_scalar_alpha(self):
295 """
296 Get a scalar alpha value to be applied to the artist as a whole.
298 If the alpha value is a matrix, the method returns 1.0 because pixels
299 have individual alpha values (see `~._ImageBase._make_image` for
300 details). If the alpha value is a scalar, the method returns said value
301 to be applied to the artist as a whole because pixels do not have
302 individual alpha values.
303 """
304 return 1.0 if self._alpha is None or np.ndim(self._alpha) > 0 \
305 else self._alpha
307 def changed(self):
308 """
309 Call this whenever the mappable is changed so observers can
310 update state
311 """
312 self._imcache = None
313 self._rgbacache = None
314 cm.ScalarMappable.changed(self)
316 def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
317 unsampled=False, round_to_pixel_border=True):
318 """
319 Normalize, rescale, and colormap the image *A* from the given *in_bbox*
320 (in data space), to the given *out_bbox* (in pixel space) clipped to
321 the given *clip_bbox* (also in pixel space), and magnified by the
322 *magnification* factor.
324 *A* may be a greyscale image (M, N) with a dtype of float32, float64,
325 float128, uint16 or uint8, or an (M, N, 4) RGBA image with a dtype of
326 float32, float64, float128, or uint8.
328 If *unsampled* is True, the image will not be scaled, but an
329 appropriate affine transformation will be returned instead.
331 If *round_to_pixel_border* is True, the output image size will be
332 rounded to the nearest pixel boundary. This makes the images align
333 correctly with the axes. It should not be used if exact scaling is
334 needed, such as for `FigureImage`.
336 Returns
337 -------
338 image : (M, N, 4) uint8 array
339 The RGBA image, resampled unless *unsampled* is True.
340 x, y : float
341 The upper left corner where the image should be drawn, in pixel
342 space.
343 trans : Affine2D
344 The affine transformation from image to pixel space.
345 """
346 if A is None:
347 raise RuntimeError('You must first set the image '
348 'array or the image attribute')
349 if A.size == 0:
350 raise RuntimeError("_make_image must get a non-empty image. "
351 "Your Artist's draw method must filter before "
352 "this method is called.")
354 clipped_bbox = Bbox.intersection(out_bbox, clip_bbox)
356 if clipped_bbox is None:
357 return None, 0, 0, None
359 out_width_base = clipped_bbox.width * magnification
360 out_height_base = clipped_bbox.height * magnification
362 if out_width_base == 0 or out_height_base == 0:
363 return None, 0, 0, None
365 if self.origin == 'upper':
366 # Flip the input image using a transform. This avoids the
367 # problem with flipping the array, which results in a copy
368 # when it is converted to contiguous in the C wrapper
369 t0 = Affine2D().translate(0, -A.shape[0]).scale(1, -1)
370 else:
371 t0 = IdentityTransform()
373 t0 += (
374 Affine2D()
375 .scale(
376 in_bbox.width / A.shape[1],
377 in_bbox.height / A.shape[0])
378 .translate(in_bbox.x0, in_bbox.y0)
379 + self.get_transform())
381 t = (t0
382 + (Affine2D()
383 .translate(-clipped_bbox.x0, -clipped_bbox.y0)
384 .scale(magnification)))
386 # So that the image is aligned with the edge of the axes, we want to
387 # round up the output width to the next integer. This also means
388 # scaling the transform slightly to account for the extra subpixel.
389 if (t.is_affine and round_to_pixel_border and
390 (out_width_base % 1.0 != 0.0 or out_height_base % 1.0 != 0.0)):
391 out_width = math.ceil(out_width_base)
392 out_height = math.ceil(out_height_base)
393 extra_width = (out_width - out_width_base) / out_width_base
394 extra_height = (out_height - out_height_base) / out_height_base
395 t += Affine2D().scale(1.0 + extra_width, 1.0 + extra_height)
396 else:
397 out_width = int(out_width_base)
398 out_height = int(out_height_base)
399 out_shape = (out_height, out_width)
401 if not unsampled:
402 if not (A.ndim == 2 or A.ndim == 3 and A.shape[-1] in (3, 4)):
403 raise ValueError(f"Invalid shape {A.shape} for image data")
405 if A.ndim == 2:
406 # if we are a 2D array, then we are running through the
407 # norm + colormap transformation. However, in general the
408 # input data is not going to match the size on the screen so we
409 # have to resample to the correct number of pixels
411 # TODO slice input array first
412 inp_dtype = A.dtype
413 a_min = A.min()
414 a_max = A.max()
415 # figure out the type we should scale to. For floats,
416 # leave as is. For integers cast to an appropriate-sized
417 # float. Small integers get smaller floats in an attempt
418 # to keep the memory footprint reasonable.
419 if a_min is np.ma.masked:
420 # all masked, so values don't matter
421 a_min, a_max = np.int32(0), np.int32(1)
422 if inp_dtype.kind == 'f':
423 scaled_dtype = A.dtype
424 # Cast to float64
425 if A.dtype not in (np.float32, np.float16):
426 if A.dtype != np.float64:
427 cbook._warn_external(
428 f"Casting input data from '{A.dtype}' to "
429 f"'float64' for imshow")
430 scaled_dtype = np.float64
431 else:
432 # probably an integer of some type.
433 da = a_max.astype(np.float64) - a_min.astype(np.float64)
434 # give more breathing room if a big dynamic range
435 scaled_dtype = np.float64 if da > 1e8 else np.float32
437 # scale the input data to [.1, .9]. The Agg
438 # interpolators clip to [0, 1] internally, use a
439 # smaller input scale to identify which of the
440 # interpolated points need to be should be flagged as
441 # over / under.
442 # This may introduce numeric instabilities in very broadly
443 # scaled data
444 # Always copy, and don't allow array subtypes.
445 A_scaled = np.array(A, dtype=scaled_dtype)
446 # clip scaled data around norm if necessary.
447 # This is necessary for big numbers at the edge of
448 # float64's ability to represent changes. Applying
449 # a norm first would be good, but ruins the interpolation
450 # of over numbers.
451 self.norm.autoscale_None(A)
452 dv = np.float64(self.norm.vmax) - np.float64(self.norm.vmin)
453 vmid = self.norm.vmin + dv / 2
454 fact = 1e7 if scaled_dtype == np.float64 else 1e4
455 newmin = vmid - dv * fact
456 if newmin < a_min:
457 newmin = None
458 else:
459 a_min = np.float64(newmin)
460 newmax = vmid + dv * fact
461 if newmax > a_max:
462 newmax = None
463 else:
464 a_max = np.float64(newmax)
465 if newmax is not None or newmin is not None:
466 np.clip(A_scaled, newmin, newmax, out=A_scaled)
468 A_scaled -= a_min
469 # a_min and a_max might be ndarray subclasses so use
470 # item to avoid errors
471 a_min = a_min.astype(scaled_dtype).item()
472 a_max = a_max.astype(scaled_dtype).item()
474 if a_min != a_max:
475 A_scaled /= ((a_max - a_min) / 0.8)
476 A_scaled += 0.1
477 # resample the input data to the correct resolution and shape
478 A_resampled = _resample(self, A_scaled, out_shape, t)
479 # done with A_scaled now, remove from namespace to be sure!
480 del A_scaled
481 # un-scale the resampled data to approximately the
482 # original range things that interpolated to above /
483 # below the original min/max will still be above /
484 # below, but possibly clipped in the case of higher order
485 # interpolation + drastically changing data.
486 A_resampled -= 0.1
487 if a_min != a_max:
488 A_resampled *= ((a_max - a_min) / 0.8)
489 A_resampled += a_min
490 # if using NoNorm, cast back to the original datatype
491 if isinstance(self.norm, mcolors.NoNorm):
492 A_resampled = A_resampled.astype(A.dtype)
494 mask = (np.where(A.mask, np.float32(np.nan), np.float32(1))
495 if A.mask.shape == A.shape # nontrivial mask
496 else np.ones_like(A, np.float32))
497 # we always have to interpolate the mask to account for
498 # non-affine transformations
499 out_alpha = _resample(self, mask, out_shape, t, resample=True)
500 # done with the mask now, delete from namespace to be sure!
501 del mask
502 # Agg updates out_alpha in place. If the pixel has no image
503 # data it will not be updated (and still be 0 as we initialized
504 # it), if input data that would go into that output pixel than
505 # it will be `nan`, if all the input data for a pixel is good
506 # it will be 1, and if there is _some_ good data in that output
507 # pixel it will be between [0, 1] (such as a rotated image).
508 out_mask = np.isnan(out_alpha)
509 out_alpha[out_mask] = 1
510 # Apply the pixel-by-pixel alpha values if present
511 alpha = self.get_alpha()
512 if alpha is not None and np.ndim(alpha) > 0:
513 out_alpha *= _resample(self, alpha, out_shape,
514 t, resample=True)
515 # mask and run through the norm
516 output = self.norm(np.ma.masked_array(A_resampled, out_mask))
517 else:
518 if A.shape[2] == 3:
519 A = _rgb_to_rgba(A)
520 alpha = self._get_scalar_alpha()
521 output_alpha = _resample( # resample alpha channel
522 self, A[..., 3], out_shape, t, alpha=alpha)
523 output = _resample( # resample rgb channels
524 self, _rgb_to_rgba(A[..., :3]), out_shape, t, alpha=alpha)
525 output[..., 3] = output_alpha # recombine rgb and alpha
527 # at this point output is either a 2D array of normed data
528 # (of int or float)
529 # or an RGBA array of re-sampled input
530 output = self.to_rgba(output, bytes=True, norm=False)
531 # output is now a correctly sized RGBA array of uint8
533 # Apply alpha *after* if the input was greyscale without a mask
534 if A.ndim == 2:
535 alpha = self._get_scalar_alpha()
536 alpha_channel = output[:, :, 3]
537 alpha_channel[:] = np.asarray(
538 np.asarray(alpha_channel, np.float32) * out_alpha * alpha,
539 np.uint8)
541 else:
542 if self._imcache is None:
543 self._imcache = self.to_rgba(A, bytes=True, norm=(A.ndim == 2))
544 output = self._imcache
546 # Subset the input image to only the part that will be
547 # displayed
548 subset = TransformedBbox(clip_bbox, t0.inverted()).frozen()
549 output = output[
550 int(max(subset.ymin, 0)):
551 int(min(subset.ymax + 1, output.shape[0])),
552 int(max(subset.xmin, 0)):
553 int(min(subset.xmax + 1, output.shape[1]))]
555 t = Affine2D().translate(
556 int(max(subset.xmin, 0)), int(max(subset.ymin, 0))) + t
558 return output, clipped_bbox.x0, clipped_bbox.y0, t
560 def make_image(self, renderer, magnification=1.0, unsampled=False):
561 """
562 Normalize, rescale, and colormap this image's data for rendering using
563 *renderer*, with the given *magnification*.
565 If *unsampled* is True, the image will not be scaled, but an
566 appropriate affine transformation will be returned instead.
568 Returns
569 -------
570 image : (M, N, 4) uint8 array
571 The RGBA image, resampled unless *unsampled* is True.
572 x, y : float
573 The upper left corner where the image should be drawn, in pixel
574 space.
575 trans : Affine2D
576 The affine transformation from image to pixel space.
577 """
578 raise NotImplementedError('The make_image method must be overridden')
580 def _draw_unsampled_image(self, renderer, gc):
581 """
582 Draw unsampled image. The renderer should support a draw_image method
583 with scale parameter.
584 """
585 im, l, b, trans = self.make_image(renderer, unsampled=True)
587 if im is None:
588 return
590 trans = Affine2D().scale(im.shape[1], im.shape[0]) + trans
592 renderer.draw_image(gc, l, b, im, trans)
594 def _check_unsampled_image(self, renderer):
595 """
596 Return whether the image is better to be drawn unsampled.
598 The derived class needs to override it.
599 """
600 return False
602 @martist.allow_rasterization
603 def draw(self, renderer, *args, **kwargs):
604 # if not visible, declare victory and return
605 if not self.get_visible():
606 self.stale = False
607 return
609 # for empty images, there is nothing to draw!
610 if self.get_array().size == 0:
611 self.stale = False
612 return
614 # actually render the image.
615 gc = renderer.new_gc()
616 self._set_gc_clip(gc)
617 gc.set_alpha(self._get_scalar_alpha())
618 gc.set_url(self.get_url())
619 gc.set_gid(self.get_gid())
621 if (self._check_unsampled_image(renderer) and
622 self.get_transform().is_affine):
623 self._draw_unsampled_image(renderer, gc)
624 else:
625 im, l, b, trans = self.make_image(
626 renderer, renderer.get_image_magnification())
627 if im is not None:
628 renderer.draw_image(gc, l, b, im)
629 gc.restore()
630 self.stale = False
632 def contains(self, mouseevent):
633 """
634 Test whether the mouse event occurred within the image.
635 """
636 inside, info = self._default_contains(mouseevent)
637 if inside is not None:
638 return inside, info
639 # 1) This doesn't work for figimage; but figimage also needs a fix
640 # below (as the check cannot use x/ydata and extents).
641 # 2) As long as the check below uses x/ydata, we need to test axes
642 # identity instead of `self.axes.contains(event)` because even if
643 # axes overlap, x/ydata is only valid for event.inaxes anyways.
644 if self.axes is not mouseevent.inaxes:
645 return False, {}
646 # TODO: make sure this is consistent with patch and patch
647 # collection on nonlinear transformed coordinates.
648 # TODO: consider returning image coordinates (shouldn't
649 # be too difficult given that the image is rectilinear
650 x, y = mouseevent.xdata, mouseevent.ydata
651 xmin, xmax, ymin, ymax = self.get_extent()
652 if xmin > xmax:
653 xmin, xmax = xmax, xmin
654 if ymin > ymax:
655 ymin, ymax = ymax, ymin
657 if x is not None and y is not None:
658 inside = (xmin <= x <= xmax) and (ymin <= y <= ymax)
659 else:
660 inside = False
662 return inside, {}
664 def write_png(self, fname):
665 """Write the image to png file with fname"""
666 from matplotlib import _png
667 im = self.to_rgba(self._A[::-1] if self.origin == 'lower' else self._A,
668 bytes=True, norm=True)
669 with cbook.open_file_cm(fname, "wb") as file:
670 _png.write_png(im, file)
672 def set_data(self, A):
673 """
674 Set the image array.
676 Note that this function does *not* update the normalization used.
678 Parameters
679 ----------
680 A : array-like or `PIL.Image.Image`
681 """
682 try:
683 from PIL import Image
684 except ImportError:
685 pass
686 else:
687 if isinstance(A, Image.Image):
688 A = pil_to_array(A) # Needed e.g. to apply png palette.
689 self._A = cbook.safe_masked_invalid(A, copy=True)
691 if (self._A.dtype != np.uint8 and
692 not np.can_cast(self._A.dtype, float, "same_kind")):
693 raise TypeError("Image data of dtype {} cannot be converted to "
694 "float".format(self._A.dtype))
696 if not (self._A.ndim == 2
697 or self._A.ndim == 3 and self._A.shape[-1] in [3, 4]):
698 raise TypeError("Invalid shape {} for image data"
699 .format(self._A.shape))
701 if self._A.ndim == 3:
702 # If the input data has values outside the valid range (after
703 # normalisation), we issue a warning and then clip X to the bounds
704 # - otherwise casting wraps extreme values, hiding outliers and
705 # making reliable interpretation impossible.
706 high = 255 if np.issubdtype(self._A.dtype, np.integer) else 1
707 if self._A.min() < 0 or high < self._A.max():
708 _log.warning(
709 'Clipping input data to the valid range for imshow with '
710 'RGB data ([0..1] for floats or [0..255] for integers).'
711 )
712 self._A = np.clip(self._A, 0, high)
713 # Cast unsupported integer types to uint8
714 if self._A.dtype != np.uint8 and np.issubdtype(self._A.dtype,
715 np.integer):
716 self._A = self._A.astype(np.uint8)
718 self._imcache = None
719 self._rgbacache = None
720 self.stale = True
722 def set_array(self, A):
723 """
724 Retained for backwards compatibility - use set_data instead.
726 Parameters
727 ----------
728 A : array-like
729 """
730 # This also needs to be here to override the inherited
731 # cm.ScalarMappable.set_array method so it is not invoked by mistake.
732 self.set_data(A)
734 def get_interpolation(self):
735 """
736 Return the interpolation method the image uses when resizing.
738 One of 'antialiased', 'nearest', 'bilinear', 'bicubic', 'spline16',
739 'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric',
740 'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos',
741 or 'none'.
743 """
744 return self._interpolation
746 def set_interpolation(self, s):
747 """
748 Set the interpolation method the image uses when resizing.
750 if None, use a value from rc setting. If 'none', the image is
751 shown as is without interpolating. 'none' is only supported in
752 agg, ps and pdf backends and will fall back to 'nearest' mode
753 for other backends.
755 Parameters
756 ----------
757 s : {'antialiased', 'nearest', 'bilinear', 'bicubic', 'spline16',
758'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'catrom', \
759'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos', 'none'}
761 """
762 if s is None:
763 s = rcParams['image.interpolation']
764 s = s.lower()
765 cbook._check_in_list(_interpd_, interpolation=s)
766 self._interpolation = s
767 self.stale = True
769 def can_composite(self):
770 """Return whether the image can be composited with its neighbors."""
771 trans = self.get_transform()
772 return (
773 self._interpolation != 'none' and
774 trans.is_affine and
775 trans.is_separable)
777 def set_resample(self, v):
778 """
779 Set whether image resampling is used.
781 Parameters
782 ----------
783 v : bool or None
784 If None, use :rc:`image.resample` = True.
785 """
786 if v is None:
787 v = rcParams['image.resample']
788 self._resample = v
789 self.stale = True
791 def get_resample(self):
792 """Return whether image resampling is used."""
793 return self._resample
795 def set_filternorm(self, filternorm):
796 """
797 Set whether the resize filter normalizes the weights.
799 See help for `~.Axes.imshow`.
801 Parameters
802 ----------
803 filternorm : bool
804 """
805 self._filternorm = bool(filternorm)
806 self.stale = True
808 def get_filternorm(self):
809 """Return whether the resize filter normalizes the weights."""
810 return self._filternorm
812 def set_filterrad(self, filterrad):
813 """
814 Set the resize filter radius only applicable to some
815 interpolation schemes -- see help for imshow
817 Parameters
818 ----------
819 filterrad : positive float
820 """
821 r = float(filterrad)
822 if r <= 0:
823 raise ValueError("The filter radius must be a positive number")
824 self._filterrad = r
825 self.stale = True
827 def get_filterrad(self):
828 """Return the filterrad setting."""
829 return self._filterrad
832class AxesImage(_ImageBase):
833 """
834 Parameters
835 ----------
836 ax : `~.axes.Axes`
837 The axes the image will belong to.
838 cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
839 The Colormap instance or registered colormap name used to map scalar
840 data to colors.
841 norm : `~matplotlib.colors.Normalize`
842 Maps luminance to 0-1.
843 interpolation : str, default: :rc:`image.interpolation`
844 Supported values are 'none', 'antialiased', 'nearest', 'bilinear',
845 'bicubic', 'spline16', 'spline36', 'hanning', 'hamming', 'hermite',
846 'kaiser', 'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell',
847 'sinc', 'lanczos'.
848 origin : {'upper', 'lower'}, default: :rc:`image.origin`
849 Place the [0, 0] index of the array in the upper left or lower left
850 corner of the axes. The convention 'upper' is typically used for
851 matrices and images.
852 extent : tuple, optional
853 The data axes (left, right, bottom, top) for making image plots
854 registered with data plots. Default is to label the pixel
855 centers with the zero-based row and column indices.
856 filternorm : bool, default: True
857 A parameter for the antigrain image resize filter
858 (see the antigrain documentation).
859 If filternorm is set, the filter normalizes integer values and corrects
860 the rounding errors. It doesn't do anything with the source floating
861 point values, it corrects only integers according to the rule of 1.0
862 which means that any sum of pixel weights must be equal to 1.0. So,
863 the filter function must produce a graph of the proper shape.
864 filterrad : float > 0, default: 4
865 The filter radius for filters that have a radius parameter, i.e. when
866 interpolation is one of: 'sinc', 'lanczos' or 'blackman'.
867 resample : bool, default: False
868 When True, use a full resampling method. When False, only resample when
869 the output image is larger than the input image.
870 **kwargs : `.Artist` properties
872 """
873 def __str__(self):
874 return "AxesImage(%g,%g;%gx%g)" % tuple(self.axes.bbox.bounds)
876 def __init__(self, ax,
877 cmap=None,
878 norm=None,
879 interpolation=None,
880 origin=None,
881 extent=None,
882 filternorm=1,
883 filterrad=4.0,
884 resample=False,
885 **kwargs
886 ):
888 self._extent = extent
890 super().__init__(
891 ax,
892 cmap=cmap,
893 norm=norm,
894 interpolation=interpolation,
895 origin=origin,
896 filternorm=filternorm,
897 filterrad=filterrad,
898 resample=resample,
899 **kwargs
900 )
902 def get_window_extent(self, renderer=None):
903 x0, x1, y0, y1 = self._extent
904 bbox = Bbox.from_extents([x0, y0, x1, y1])
905 return bbox.transformed(self.axes.transData)
907 def make_image(self, renderer, magnification=1.0, unsampled=False):
908 # docstring inherited
909 trans = self.get_transform()
910 # image is created in the canvas coordinate.
911 x1, x2, y1, y2 = self.get_extent()
912 bbox = Bbox(np.array([[x1, y1], [x2, y2]]))
913 transformed_bbox = TransformedBbox(bbox, trans)
914 return self._make_image(
915 self._A, bbox, transformed_bbox,
916 self.get_clip_box() or self.axes.bbox,
917 magnification, unsampled=unsampled)
919 def _check_unsampled_image(self, renderer):
920 """
921 Return whether the image would be better drawn unsampled.
922 """
923 return (self.get_interpolation() == "none"
924 and renderer.option_scale_image())
926 def set_extent(self, extent):
927 """
928 Set the image extent.
930 Parameters
931 ----------
932 extent : 4-tuple of float
933 The position and size of the image as tuple
934 ``(left, right, bottom, top)`` in data coordinates.
936 Notes
937 -----
938 This updates ``ax.dataLim``, and, if autoscaling, sets ``ax.viewLim``
939 to tightly fit the image, regardless of ``dataLim``. Autoscaling
940 state is not changed, so following this with ``ax.autoscale_view()``
941 will redo the autoscaling in accord with ``dataLim``.
942 """
943 self._extent = xmin, xmax, ymin, ymax = extent
944 corners = (xmin, ymin), (xmax, ymax)
945 self.axes.update_datalim(corners)
946 self.sticky_edges.x[:] = [xmin, xmax]
947 self.sticky_edges.y[:] = [ymin, ymax]
948 if self.axes._autoscaleXon:
949 self.axes.set_xlim((xmin, xmax), auto=None)
950 if self.axes._autoscaleYon:
951 self.axes.set_ylim((ymin, ymax), auto=None)
952 self.stale = True
954 def get_extent(self):
955 """Return the image extent as tuple (left, right, bottom, top)."""
956 if self._extent is not None:
957 return self._extent
958 else:
959 sz = self.get_size()
960 numrows, numcols = sz
961 if self.origin == 'upper':
962 return (-0.5, numcols-0.5, numrows-0.5, -0.5)
963 else:
964 return (-0.5, numcols-0.5, -0.5, numrows-0.5)
966 def get_cursor_data(self, event):
967 """
968 Return the image value at the event position or *None* if the event is
969 outside the image.
971 See Also
972 --------
973 matplotlib.artist.Artist.get_cursor_data
974 """
975 xmin, xmax, ymin, ymax = self.get_extent()
976 if self.origin == 'upper':
977 ymin, ymax = ymax, ymin
978 arr = self.get_array()
979 data_extent = Bbox([[ymin, xmin], [ymax, xmax]])
980 array_extent = Bbox([[0, 0], arr.shape[:2]])
981 trans = BboxTransform(boxin=data_extent, boxout=array_extent)
982 point = trans.transform([event.ydata, event.xdata])
983 if any(np.isnan(point)):
984 return None
985 i, j = point.astype(int)
986 # Clip the coordinates at array bounds
987 if not (0 <= i < arr.shape[0]) or not (0 <= j < arr.shape[1]):
988 return None
989 else:
990 return arr[i, j]
992 def format_cursor_data(self, data):
993 if np.ndim(data) == 0 and self.colorbar:
994 return (
995 "["
996 + cbook.strip_math(
997 self.colorbar.formatter.format_data_short(data)).strip()
998 + "]")
999 else:
1000 return super().format_cursor_data(data)
1003class NonUniformImage(AxesImage):
1004 def __init__(self, ax, *, interpolation='nearest', **kwargs):
1005 """
1006 Parameters
1007 ----------
1008 interpolation : {'nearest', 'bilinear'}
1010 **kwargs
1011 All other keyword arguments are identical to those of `.AxesImage`.
1012 """
1013 super().__init__(ax, **kwargs)
1014 self.set_interpolation(interpolation)
1016 def _check_unsampled_image(self, renderer):
1017 """Return False. Do not use unsampled image."""
1018 return False
1020 def make_image(self, renderer, magnification=1.0, unsampled=False):
1021 # docstring inherited
1022 if self._A is None:
1023 raise RuntimeError('You must first set the image array')
1024 if unsampled:
1025 raise ValueError('unsampled not supported on NonUniformImage')
1026 A = self._A
1027 if A.ndim == 2:
1028 if A.dtype != np.uint8:
1029 A = self.to_rgba(A, bytes=True)
1030 self.is_grayscale = self.cmap.is_gray()
1031 else:
1032 A = np.repeat(A[:, :, np.newaxis], 4, 2)
1033 A[:, :, 3] = 255
1034 self.is_grayscale = True
1035 else:
1036 if A.dtype != np.uint8:
1037 A = (255*A).astype(np.uint8)
1038 if A.shape[2] == 3:
1039 B = np.zeros(tuple([*A.shape[0:2], 4]), np.uint8)
1040 B[:, :, 0:3] = A
1041 B[:, :, 3] = 255
1042 A = B
1043 self.is_grayscale = False
1044 x0, y0, v_width, v_height = self.axes.viewLim.bounds
1045 l, b, r, t = self.axes.bbox.extents
1046 width = (round(r) + 0.5) - (round(l) - 0.5)
1047 height = (round(t) + 0.5) - (round(b) - 0.5)
1048 width *= magnification
1049 height *= magnification
1050 im = _image.pcolor(self._Ax, self._Ay, A,
1051 int(height), int(width),
1052 (x0, x0+v_width, y0, y0+v_height),
1053 _interpd_[self._interpolation])
1054 return im, l, b, IdentityTransform()
1056 def set_data(self, x, y, A):
1057 """
1058 Set the grid for the pixel centers, and the pixel values.
1060 Parameters
1061 ----------
1062 x, y : 1D array-likes
1063 Monotonic arrays of shapes (N,) and (M,), respectively, specifying
1064 pixel centers.
1065 A : array-like
1066 (M, N) ndarray or masked array of values to be colormapped, or
1067 (M, N, 3) RGB array, or (M, N, 4) RGBA array.
1068 """
1069 x = np.array(x, np.float32)
1070 y = np.array(y, np.float32)
1071 A = cbook.safe_masked_invalid(A, copy=True)
1072 if not (x.ndim == y.ndim == 1 and A.shape[0:2] == y.shape + x.shape):
1073 raise TypeError("Axes don't match array shape")
1074 if A.ndim not in [2, 3]:
1075 raise TypeError("Can only plot 2D or 3D data")
1076 if A.ndim == 3 and A.shape[2] not in [1, 3, 4]:
1077 raise TypeError("3D arrays must have three (RGB) "
1078 "or four (RGBA) color components")
1079 if A.ndim == 3 and A.shape[2] == 1:
1080 A.shape = A.shape[0:2]
1081 self._A = A
1082 self._Ax = x
1083 self._Ay = y
1084 self._imcache = None
1086 self.stale = True
1088 def set_array(self, *args):
1089 raise NotImplementedError('Method not supported')
1091 def set_interpolation(self, s):
1092 """
1093 Parameters
1094 ----------
1095 s : str, None
1096 Either 'nearest', 'bilinear', or ``None``.
1097 """
1098 if s is not None and s not in ('nearest', 'bilinear'):
1099 raise NotImplementedError('Only nearest neighbor and '
1100 'bilinear interpolations are supported')
1101 AxesImage.set_interpolation(self, s)
1103 def get_extent(self):
1104 if self._A is None:
1105 raise RuntimeError('Must set data first')
1106 return self._Ax[0], self._Ax[-1], self._Ay[0], self._Ay[-1]
1108 def set_filternorm(self, s):
1109 pass
1111 def set_filterrad(self, s):
1112 pass
1114 def set_norm(self, norm):
1115 if self._A is not None:
1116 raise RuntimeError('Cannot change colors after loading data')
1117 super().set_norm(norm)
1119 def set_cmap(self, cmap):
1120 if self._A is not None:
1121 raise RuntimeError('Cannot change colors after loading data')
1122 super().set_cmap(cmap)
1125class PcolorImage(AxesImage):
1126 """
1127 Make a pcolor-style plot with an irregular rectangular grid.
1129 This uses a variation of the original irregular image code,
1130 and it is used by pcolorfast for the corresponding grid type.
1131 """
1132 def __init__(self, ax,
1133 x=None,
1134 y=None,
1135 A=None,
1136 cmap=None,
1137 norm=None,
1138 **kwargs
1139 ):
1140 """
1141 cmap defaults to its rc setting
1143 cmap is a colors.Colormap instance
1144 norm is a colors.Normalize instance to map luminance to 0-1
1146 Additional kwargs are matplotlib.artist properties
1147 """
1148 super().__init__(ax, norm=norm, cmap=cmap)
1149 self.update(kwargs)
1150 if A is not None:
1151 self.set_data(x, y, A)
1153 def make_image(self, renderer, magnification=1.0, unsampled=False):
1154 # docstring inherited
1155 if self._A is None:
1156 raise RuntimeError('You must first set the image array')
1157 if unsampled:
1158 raise ValueError('unsampled not supported on PColorImage')
1159 fc = self.axes.patch.get_facecolor()
1160 bg = mcolors.to_rgba(fc, 0)
1161 bg = (np.array(bg)*255).astype(np.uint8)
1162 l, b, r, t = self.axes.bbox.extents
1163 width = (round(r) + 0.5) - (round(l) - 0.5)
1164 height = (round(t) + 0.5) - (round(b) - 0.5)
1165 # The extra cast-to-int is only needed for python2
1166 width = int(round(width * magnification))
1167 height = int(round(height * magnification))
1168 if self._rgbacache is None:
1169 A = self.to_rgba(self._A, bytes=True)
1170 self._rgbacache = A
1171 if self._A.ndim == 2:
1172 self.is_grayscale = self.cmap.is_gray()
1173 else:
1174 A = self._rgbacache
1175 vl = self.axes.viewLim
1176 im = _image.pcolor2(self._Ax, self._Ay, A,
1177 height,
1178 width,
1179 (vl.x0, vl.x1, vl.y0, vl.y1),
1180 bg)
1181 return im, l, b, IdentityTransform()
1183 def _check_unsampled_image(self, renderer):
1184 return False
1186 def set_data(self, x, y, A):
1187 """
1188 Set the grid for the rectangle boundaries, and the data values.
1190 Parameters
1191 ----------
1192 x, y : 1D array-likes or None
1193 Monotonic arrays of shapes (N + 1,) and (M + 1,), respectively,
1194 specifying rectangle boundaries. If None, will default to
1195 ``range(N + 1)`` and ``range(M + 1)``, respectively.
1196 A : array-like
1197 (M, N) ndarray or masked array of values to be colormapped, or
1198 (M, N, 3) RGB array, or (M, N, 4) RGBA array.
1199 """
1200 A = cbook.safe_masked_invalid(A, copy=True)
1201 if x is None:
1202 x = np.arange(0, A.shape[1]+1, dtype=np.float64)
1203 else:
1204 x = np.array(x, np.float64).ravel()
1205 if y is None:
1206 y = np.arange(0, A.shape[0]+1, dtype=np.float64)
1207 else:
1208 y = np.array(y, np.float64).ravel()
1210 if A.shape[:2] != (y.size-1, x.size-1):
1211 raise ValueError(
1212 "Axes don't match array shape. Got %s, expected %s." %
1213 (A.shape[:2], (y.size - 1, x.size - 1)))
1214 if A.ndim not in [2, 3]:
1215 raise ValueError("A must be 2D or 3D")
1216 if A.ndim == 3 and A.shape[2] == 1:
1217 A.shape = A.shape[:2]
1218 self.is_grayscale = False
1219 if A.ndim == 3:
1220 if A.shape[2] in [3, 4]:
1221 if ((A[:, :, 0] == A[:, :, 1]).all() and
1222 (A[:, :, 0] == A[:, :, 2]).all()):
1223 self.is_grayscale = True
1224 else:
1225 raise ValueError("3D arrays must have RGB or RGBA as last dim")
1227 # For efficient cursor readout, ensure x and y are increasing.
1228 if x[-1] < x[0]:
1229 x = x[::-1]
1230 A = A[:, ::-1]
1231 if y[-1] < y[0]:
1232 y = y[::-1]
1233 A = A[::-1]
1235 self._A = A
1236 self._Ax = x
1237 self._Ay = y
1238 self._rgbacache = None
1239 self.stale = True
1241 def set_array(self, *args):
1242 raise NotImplementedError('Method not supported')
1244 def get_cursor_data(self, event):
1245 # docstring inherited
1246 x, y = event.xdata, event.ydata
1247 if (x < self._Ax[0] or x > self._Ax[-1] or
1248 y < self._Ay[0] or y > self._Ay[-1]):
1249 return None
1250 j = np.searchsorted(self._Ax, x) - 1
1251 i = np.searchsorted(self._Ay, y) - 1
1252 try:
1253 return self._A[i, j]
1254 except IndexError:
1255 return None
1258class FigureImage(_ImageBase):
1259 zorder = 0
1261 _interpolation = 'nearest'
1263 def __init__(self, fig,
1264 cmap=None,
1265 norm=None,
1266 offsetx=0,
1267 offsety=0,
1268 origin=None,
1269 **kwargs
1270 ):
1271 """
1272 cmap is a colors.Colormap instance
1273 norm is a colors.Normalize instance to map luminance to 0-1
1275 kwargs are an optional list of Artist keyword args
1276 """
1277 super().__init__(
1278 None,
1279 norm=norm,
1280 cmap=cmap,
1281 origin=origin
1282 )
1283 self.figure = fig
1284 self.ox = offsetx
1285 self.oy = offsety
1286 self.update(kwargs)
1287 self.magnification = 1.0
1289 def get_extent(self):
1290 """Return the image extent as tuple (left, right, bottom, top)."""
1291 numrows, numcols = self.get_size()
1292 return (-0.5 + self.ox, numcols-0.5 + self.ox,
1293 -0.5 + self.oy, numrows-0.5 + self.oy)
1295 def make_image(self, renderer, magnification=1.0, unsampled=False):
1296 # docstring inherited
1297 fac = renderer.dpi/self.figure.dpi
1298 # fac here is to account for pdf, eps, svg backends where
1299 # figure.dpi is set to 72. This means we need to scale the
1300 # image (using magnification) and offset it appropriately.
1301 bbox = Bbox([[self.ox/fac, self.oy/fac],
1302 [(self.ox/fac + self._A.shape[1]),
1303 (self.oy/fac + self._A.shape[0])]])
1304 width, height = self.figure.get_size_inches()
1305 width *= renderer.dpi
1306 height *= renderer.dpi
1307 clip = Bbox([[0, 0], [width, height]])
1308 return self._make_image(
1309 self._A, bbox, bbox, clip, magnification=magnification / fac,
1310 unsampled=unsampled, round_to_pixel_border=False)
1312 def set_data(self, A):
1313 """Set the image array."""
1314 cm.ScalarMappable.set_array(self,
1315 cbook.safe_masked_invalid(A, copy=True))
1316 self.stale = True
1319class BboxImage(_ImageBase):
1320 """The Image class whose size is determined by the given bbox."""
1322 @cbook._delete_parameter("3.1", "interp_at_native")
1323 def __init__(self, bbox,
1324 cmap=None,
1325 norm=None,
1326 interpolation=None,
1327 origin=None,
1328 filternorm=1,
1329 filterrad=4.0,
1330 resample=False,
1331 interp_at_native=True,
1332 **kwargs
1333 ):
1334 """
1335 cmap is a colors.Colormap instance
1336 norm is a colors.Normalize instance to map luminance to 0-1
1338 kwargs are an optional list of Artist keyword args
1339 """
1340 super().__init__(
1341 None,
1342 cmap=cmap,
1343 norm=norm,
1344 interpolation=interpolation,
1345 origin=origin,
1346 filternorm=filternorm,
1347 filterrad=filterrad,
1348 resample=resample,
1349 **kwargs
1350 )
1352 self.bbox = bbox
1353 self._interp_at_native = interp_at_native
1354 self._transform = IdentityTransform()
1356 @cbook.deprecated("3.1")
1357 @property
1358 def interp_at_native(self):
1359 return self._interp_at_native
1361 def get_transform(self):
1362 return self._transform
1364 def get_window_extent(self, renderer=None):
1365 if renderer is None:
1366 renderer = self.get_figure()._cachedRenderer
1368 if isinstance(self.bbox, BboxBase):
1369 return self.bbox
1370 elif callable(self.bbox):
1371 return self.bbox(renderer)
1372 else:
1373 raise ValueError("unknown type of bbox")
1375 def contains(self, mouseevent):
1376 """Test whether the mouse event occurred within the image."""
1377 inside, info = self._default_contains(mouseevent)
1378 if inside is not None:
1379 return inside, info
1381 if not self.get_visible(): # or self.get_figure()._renderer is None:
1382 return False, {}
1384 x, y = mouseevent.x, mouseevent.y
1385 inside = self.get_window_extent().contains(x, y)
1387 return inside, {}
1389 def make_image(self, renderer, magnification=1.0, unsampled=False):
1390 # docstring inherited
1391 width, height = renderer.get_canvas_width_height()
1392 bbox_in = self.get_window_extent(renderer).frozen()
1393 bbox_in._points /= [width, height]
1394 bbox_out = self.get_window_extent(renderer)
1395 clip = Bbox([[0, 0], [width, height]])
1396 self._transform = BboxTransform(Bbox([[0, 0], [1, 1]]), clip)
1397 return self._make_image(
1398 self._A,
1399 bbox_in, bbox_out, clip, magnification, unsampled=unsampled)
1402def imread(fname, format=None):
1403 """
1404 Read an image from a file into an array.
1406 Parameters
1407 ----------
1408 fname : str or file-like
1409 The image file to read: a filename, a URL or a file-like object opened
1410 in read-binary mode.
1411 format : str, optional
1412 The image file format assumed for reading the data. If not
1413 given, the format is deduced from the filename. If nothing can
1414 be deduced, PNG is tried.
1416 Returns
1417 -------
1418 imagedata : :class:`numpy.array`
1419 The image data. The returned array has shape
1421 - (M, N) for grayscale images.
1422 - (M, N, 3) for RGB images.
1423 - (M, N, 4) for RGBA images.
1425 Notes
1426 -----
1427 Matplotlib can only read PNGs natively. Further image formats are
1428 supported via the optional dependency on Pillow. Note, URL strings
1429 are not compatible with Pillow. Check the `Pillow documentation`_
1430 for more information.
1432 .. _Pillow documentation: http://pillow.readthedocs.io/en/latest/
1433 """
1434 if format is None:
1435 if isinstance(fname, str):
1436 parsed = urllib.parse.urlparse(fname)
1437 # If the string is a URL (Windows paths appear as if they have a
1438 # length-1 scheme), assume png.
1439 if len(parsed.scheme) > 1:
1440 ext = 'png'
1441 else:
1442 basename, ext = os.path.splitext(fname)
1443 ext = ext.lower()[1:]
1444 elif hasattr(fname, 'geturl'): # Returned by urlopen().
1445 # We could try to parse the url's path and use the extension, but
1446 # returning png is consistent with the block above. Note that this
1447 # if clause has to come before checking for fname.name as
1448 # urlopen("file:///...") also has a name attribute (with the fixed
1449 # value "<urllib response>").
1450 ext = 'png'
1451 elif hasattr(fname, 'name'):
1452 basename, ext = os.path.splitext(fname.name)
1453 ext = ext.lower()[1:]
1454 else:
1455 ext = 'png'
1456 else:
1457 ext = format
1458 if ext != 'png':
1459 try: # Try to load the image with PIL.
1460 from PIL import Image
1461 except ImportError:
1462 raise ValueError('Only know how to handle PNG; with Pillow '
1463 'installed, Matplotlib can handle more images')
1464 with Image.open(fname) as image:
1465 return pil_to_array(image)
1466 from matplotlib import _png
1467 if isinstance(fname, str):
1468 parsed = urllib.parse.urlparse(fname)
1469 # If fname is a URL, download the data
1470 if len(parsed.scheme) > 1:
1471 from urllib import request
1472 fd = BytesIO(request.urlopen(fname).read())
1473 return _png.read_png(fd)
1474 with cbook.open_file_cm(fname, "rb") as file:
1475 return _png.read_png(file)
1478def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None,
1479 origin=None, dpi=100, *, metadata=None, pil_kwargs=None):
1480 """
1481 Save an array as an image file.
1483 Parameters
1484 ----------
1485 fname : str or PathLike or file-like
1486 A path or a file-like object to store the image in.
1487 If *format* is not set, then the output format is inferred from the
1488 extension of *fname*, if any, and from :rc:`savefig.format` otherwise.
1489 If *format* is set, it determines the output format.
1490 arr : array-like
1491 The image data. The shape can be one of
1492 MxN (luminance), MxNx3 (RGB) or MxNx4 (RGBA).
1493 vmin, vmax : scalar, optional
1494 *vmin* and *vmax* set the color scaling for the image by fixing the
1495 values that map to the colormap color limits. If either *vmin*
1496 or *vmax* is None, that limit is determined from the *arr*
1497 min/max value.
1498 cmap : str or `~matplotlib.colors.Colormap`, optional
1499 A Colormap instance or registered colormap name. The colormap
1500 maps scalar data to colors. It is ignored for RGB(A) data.
1501 Defaults to :rc:`image.cmap` ('viridis').
1502 format : str, optional
1503 The file format, e.g. 'png', 'pdf', 'svg', ... The behavior when this
1504 is unset is documented under *fname*.
1505 origin : {'upper', 'lower'}, optional
1506 Indicates whether the ``(0, 0)`` index of the array is in the upper
1507 left or lower left corner of the axes. Defaults to :rc:`image.origin`
1508 ('upper').
1509 dpi : int
1510 The DPI to store in the metadata of the file. This does not affect the
1511 resolution of the output image.
1512 metadata : dict, optional
1513 Metadata in the image file. The supported keys depend on the output
1514 format, see the documentation of the respective backends for more
1515 information.
1516 pil_kwargs : dict, optional
1517 If set to a non-None value, always use Pillow to save the figure
1518 (regardless of the output format), and pass these keyword arguments to
1519 `PIL.Image.save`.
1521 If the 'pnginfo' key is present, it completely overrides
1522 *metadata*, including the default 'Software' key.
1523 """
1524 from matplotlib.figure import Figure
1525 from matplotlib import _png
1526 if isinstance(fname, os.PathLike):
1527 fname = os.fspath(fname)
1528 if format is None:
1529 format = (Path(fname).suffix[1:] if isinstance(fname, str)
1530 else rcParams["savefig.format"]).lower()
1531 if format in ["pdf", "ps", "eps", "svg"]:
1532 # Vector formats that are not handled by PIL.
1533 if pil_kwargs is not None:
1534 raise ValueError(
1535 f"Cannot use 'pil_kwargs' when saving to {format}")
1536 fig = Figure(dpi=dpi, frameon=False)
1537 fig.figimage(arr, cmap=cmap, vmin=vmin, vmax=vmax, origin=origin,
1538 resize=True)
1539 fig.savefig(fname, dpi=dpi, format=format, transparent=True,
1540 metadata=metadata)
1541 else:
1542 # Don't bother creating an image; this avoids rounding errors on the
1543 # size when dividing and then multiplying by dpi.
1544 sm = cm.ScalarMappable(cmap=cmap)
1545 sm.set_clim(vmin, vmax)
1546 if origin is None:
1547 origin = rcParams["image.origin"]
1548 if origin == "lower":
1549 arr = arr[::-1]
1550 rgba = sm.to_rgba(arr, bytes=True)
1551 if format == "png" and pil_kwargs is None:
1552 with cbook.open_file_cm(fname, "wb") as file:
1553 _png.write_png(rgba, file, dpi=dpi, metadata=metadata)
1554 else:
1555 try:
1556 from PIL import Image
1557 from PIL.PngImagePlugin import PngInfo
1558 except ImportError as exc:
1559 if pil_kwargs is not None:
1560 raise ImportError("Setting 'pil_kwargs' requires Pillow")
1561 else:
1562 raise ImportError(f"Saving to {format} requires Pillow")
1563 if pil_kwargs is None:
1564 pil_kwargs = {}
1565 pil_shape = (rgba.shape[1], rgba.shape[0])
1566 image = Image.frombuffer(
1567 "RGBA", pil_shape, rgba, "raw", "RGBA", 0, 1)
1568 if format == "png" and metadata:
1569 # cf. backend_agg's print_png.
1570 if "pnginfo" in pil_kwargs:
1571 cbook._warn_external("'metadata' is overridden by the "
1572 "'pnginfo' entry in 'pil_kwargs'.")
1573 else:
1574 pnginfo = PngInfo()
1575 for k, v in metadata.items():
1576 pnginfo.add_text(k, v)
1577 pil_kwargs["pnginfo"] = pnginfo
1578 if format in ["jpg", "jpeg"]:
1579 format = "jpeg" # Pillow doesn't recognize "jpg".
1580 color = tuple(
1581 int(x * 255)
1582 for x in mcolors.to_rgb(rcParams["savefig.facecolor"]))
1583 background = Image.new("RGB", pil_shape, color)
1584 background.paste(image, image)
1585 image = background
1586 pil_kwargs.setdefault("format", format)
1587 pil_kwargs.setdefault("dpi", (dpi, dpi))
1588 image.save(fname, **pil_kwargs)
1591def pil_to_array(pilImage):
1592 """Load a `PIL image`_ and return it as a numpy array.
1594 .. _PIL image: https://pillow.readthedocs.io/en/latest/reference/Image.html
1596 Returns
1597 -------
1598 numpy.array
1600 The array shape depends on the image type:
1602 - (M, N) for grayscale images.
1603 - (M, N, 3) for RGB images.
1604 - (M, N, 4) for RGBA images.
1606 """
1607 if pilImage.mode in ['RGBA', 'RGBX', 'RGB', 'L']:
1608 # return MxNx4 RGBA, MxNx3 RBA, or MxN luminance array
1609 return np.asarray(pilImage)
1610 elif pilImage.mode.startswith('I;16'):
1611 # return MxN luminance array of uint16
1612 raw = pilImage.tobytes('raw', pilImage.mode)
1613 if pilImage.mode.endswith('B'):
1614 x = np.frombuffer(raw, '>u2')
1615 else:
1616 x = np.frombuffer(raw, '<u2')
1617 return x.reshape(pilImage.size[::-1]).astype('=u2')
1618 else: # try to convert to an rgba image
1619 try:
1620 pilImage = pilImage.convert('RGBA')
1621 except ValueError:
1622 raise RuntimeError('Unknown image mode')
1623 return np.asarray(pilImage) # return MxNx4 RGBA array
1626def thumbnail(infile, thumbfile, scale=0.1, interpolation='bilinear',
1627 preview=False):
1628 """
1629 Make a thumbnail of image in *infile* with output filename *thumbfile*.
1631 See :doc:`/gallery/misc/image_thumbnail_sgskip`.
1633 Parameters
1634 ----------
1635 infile : str or file-like
1636 The image file -- must be PNG, or Pillow-readable if you have Pillow_
1637 installed.
1639 .. _Pillow: http://python-pillow.org/
1641 thumbfile : str or file-like
1642 The thumbnail filename.
1644 scale : float, optional
1645 The scale factor for the thumbnail.
1647 interpolation : str, optional
1648 The interpolation scheme used in the resampling. See the
1649 *interpolation* parameter of `~.Axes.imshow` for possible values.
1651 preview : bool, optional
1652 If True, the default backend (presumably a user interface
1653 backend) will be used which will cause a figure to be raised if
1654 `~matplotlib.pyplot.show` is called. If it is False, the figure is
1655 created using `FigureCanvasBase` and the drawing backend is selected
1656 as `~matplotlib.figure.savefig` would normally do.
1658 Returns
1659 -------
1660 figure : `~.figure.Figure`
1661 The figure instance containing the thumbnail.
1662 """
1664 im = imread(infile)
1665 rows, cols, depth = im.shape
1667 # This doesn't really matter (it cancels in the end) but the API needs it.
1668 dpi = 100
1670 height = rows / dpi * scale
1671 width = cols / dpi * scale
1673 if preview:
1674 # Let the UI backend do everything.
1675 import matplotlib.pyplot as plt
1676 fig = plt.figure(figsize=(width, height), dpi=dpi)
1677 else:
1678 from matplotlib.figure import Figure
1679 fig = Figure(figsize=(width, height), dpi=dpi)
1680 FigureCanvasBase(fig)
1682 ax = fig.add_axes([0, 0, 1, 1], aspect='auto',
1683 frameon=False, xticks=[], yticks=[])
1684 ax.imshow(im, aspect='auto', resample=True, interpolation=interpolation)
1685 fig.savefig(thumbfile, dpi=dpi)
1686 return fig