Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/matplotlib/image.py: 14%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x 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"""
6import math
7import os
8import logging
9from pathlib import Path
10import warnings
12import numpy as np
13import PIL.Image
14import PIL.PngImagePlugin
16import matplotlib as mpl
17from matplotlib import _api, cbook, cm
18# For clarity, names from _image are given explicitly in this module
19from matplotlib import _image
20# For user convenience, the names from _image are also imported into
21# the image namespace
22from matplotlib._image import * # noqa: F401, F403
23import matplotlib.artist as martist
24from matplotlib.backend_bases import FigureCanvasBase
25import matplotlib.colors as mcolors
26from matplotlib.transforms import (
27 Affine2D, BboxBase, Bbox, BboxTransform, BboxTransformTo,
28 IdentityTransform, TransformedBbox)
30_log = logging.getLogger(__name__)
32# map interpolation strings to module constants
33_interpd_ = {
34 'antialiased': _image.NEAREST, # this will use nearest or Hanning...
35 'none': _image.NEAREST, # fall back to nearest when not supported
36 'nearest': _image.NEAREST,
37 'bilinear': _image.BILINEAR,
38 'bicubic': _image.BICUBIC,
39 'spline16': _image.SPLINE16,
40 'spline36': _image.SPLINE36,
41 'hanning': _image.HANNING,
42 'hamming': _image.HAMMING,
43 'hermite': _image.HERMITE,
44 'kaiser': _image.KAISER,
45 'quadric': _image.QUADRIC,
46 'catrom': _image.CATROM,
47 'gaussian': _image.GAUSSIAN,
48 'bessel': _image.BESSEL,
49 'mitchell': _image.MITCHELL,
50 'sinc': _image.SINC,
51 'lanczos': _image.LANCZOS,
52 'blackman': _image.BLACKMAN,
53}
55interpolations_names = set(_interpd_)
58def composite_images(images, renderer, magnification=1.0):
59 """
60 Composite a number of RGBA images into one. The images are
61 composited in the order in which they appear in the *images* list.
63 Parameters
64 ----------
65 images : list of Images
66 Each must have a `make_image` method. For each image,
67 `can_composite` should return `True`, though this is not
68 enforced by this function. Each image must have a purely
69 affine transformation with no shear.
71 renderer : `.RendererBase`
73 magnification : float, default: 1
74 The additional magnification to apply for the renderer in use.
76 Returns
77 -------
78 image : (M, N, 4) `numpy.uint8` array
79 The composited RGBA image.
80 offset_x, offset_y : float
81 The (left, bottom) offset where the composited image should be placed
82 in the output figure.
83 """
84 if len(images) == 0:
85 return np.empty((0, 0, 4), dtype=np.uint8), 0, 0
87 parts = []
88 bboxes = []
89 for image in images:
90 data, x, y, trans = image.make_image(renderer, magnification)
91 if data is not None:
92 x *= magnification
93 y *= magnification
94 parts.append((data, x, y, image._get_scalar_alpha()))
95 bboxes.append(
96 Bbox([[x, y], [x + data.shape[1], y + data.shape[0]]]))
98 if len(parts) == 0:
99 return np.empty((0, 0, 4), dtype=np.uint8), 0, 0
101 bbox = Bbox.union(bboxes)
103 output = np.zeros(
104 (int(bbox.height), int(bbox.width), 4), dtype=np.uint8)
106 for data, x, y, alpha in parts:
107 trans = Affine2D().translate(x - bbox.x0, y - bbox.y0)
108 _image.resample(data, output, trans, _image.NEAREST,
109 resample=False, alpha=alpha)
111 return output, bbox.x0 / magnification, bbox.y0 / magnification
114def _draw_list_compositing_images(
115 renderer, parent, artists, suppress_composite=None):
116 """
117 Draw a sorted list of artists, compositing images into a single
118 image where possible.
120 For internal Matplotlib use only: It is here to reduce duplication
121 between `Figure.draw` and `Axes.draw`, but otherwise should not be
122 generally useful.
123 """
124 has_images = any(isinstance(x, _ImageBase) for x in artists)
126 # override the renderer default if suppressComposite is not None
127 not_composite = (suppress_composite if suppress_composite is not None
128 else renderer.option_image_nocomposite())
130 if not_composite or not has_images:
131 for a in artists:
132 a.draw(renderer)
133 else:
134 # Composite any adjacent images together
135 image_group = []
136 mag = renderer.get_image_magnification()
138 def flush_images():
139 if len(image_group) == 1:
140 image_group[0].draw(renderer)
141 elif len(image_group) > 1:
142 data, l, b = composite_images(image_group, renderer, mag)
143 if data.size != 0:
144 gc = renderer.new_gc()
145 gc.set_clip_rectangle(parent.bbox)
146 gc.set_clip_path(parent.get_clip_path())
147 renderer.draw_image(gc, round(l), round(b), data)
148 gc.restore()
149 del image_group[:]
151 for a in artists:
152 if (isinstance(a, _ImageBase) and a.can_composite() and
153 a.get_clip_on() and not a.get_clip_path()):
154 image_group.append(a)
155 else:
156 flush_images()
157 a.draw(renderer)
158 flush_images()
161def _resample(
162 image_obj, data, out_shape, transform, *, resample=None, alpha=1):
163 """
164 Convenience wrapper around `._image.resample` to resample *data* to
165 *out_shape* (with a third dimension if *data* is RGBA) that takes care of
166 allocating the output array and fetching the relevant properties from the
167 Image object *image_obj*.
168 """
169 # AGG can only handle coordinates smaller than 24-bit signed integers,
170 # so raise errors if the input data is larger than _image.resample can
171 # handle.
172 msg = ('Data with more than {n} cannot be accurately displayed. '
173 'Downsampling to less than {n} before displaying. '
174 'To remove this warning, manually downsample your data.')
175 if data.shape[1] > 2**23:
176 warnings.warn(msg.format(n='2**23 columns'))
177 step = int(np.ceil(data.shape[1] / 2**23))
178 data = data[:, ::step]
179 transform = Affine2D().scale(step, 1) + transform
180 if data.shape[0] > 2**24:
181 warnings.warn(msg.format(n='2**24 rows'))
182 step = int(np.ceil(data.shape[0] / 2**24))
183 data = data[::step, :]
184 transform = Affine2D().scale(1, step) + transform
185 # decide if we need to apply anti-aliasing if the data is upsampled:
186 # compare the number of displayed pixels to the number of
187 # the data pixels.
188 interpolation = image_obj.get_interpolation()
189 if interpolation == 'antialiased':
190 # don't antialias if upsampling by an integer number or
191 # if zooming in more than a factor of 3
192 pos = np.array([[0, 0], [data.shape[1], data.shape[0]]])
193 disp = transform.transform(pos)
194 dispx = np.abs(np.diff(disp[:, 0]))
195 dispy = np.abs(np.diff(disp[:, 1]))
196 if ((dispx > 3 * data.shape[1] or
197 dispx == data.shape[1] or
198 dispx == 2 * data.shape[1]) and
199 (dispy > 3 * data.shape[0] or
200 dispy == data.shape[0] or
201 dispy == 2 * data.shape[0])):
202 interpolation = 'nearest'
203 else:
204 interpolation = 'hanning'
205 out = np.zeros(out_shape + data.shape[2:], data.dtype) # 2D->2D, 3D->3D.
206 if resample is None:
207 resample = image_obj.get_resample()
208 _image.resample(data, out, transform,
209 _interpd_[interpolation],
210 resample,
211 alpha,
212 image_obj.get_filternorm(),
213 image_obj.get_filterrad())
214 return out
217def _rgb_to_rgba(A):
218 """
219 Convert an RGB image to RGBA, as required by the image resample C++
220 extension.
221 """
222 rgba = np.zeros((A.shape[0], A.shape[1], 4), dtype=A.dtype)
223 rgba[:, :, :3] = A
224 if rgba.dtype == np.uint8:
225 rgba[:, :, 3] = 255
226 else:
227 rgba[:, :, 3] = 1.0
228 return rgba
231class _ImageBase(martist.Artist, cm.ScalarMappable):
232 """
233 Base class for images.
235 interpolation and cmap default to their rc settings
237 cmap is a colors.Colormap instance
238 norm is a colors.Normalize instance to map luminance to 0-1
240 extent is data axes (left, right, bottom, top) for making image plots
241 registered with data plots. Default is to label the pixel
242 centers with the zero-based row and column indices.
244 Additional kwargs are matplotlib.artist properties
245 """
246 zorder = 0
248 def __init__(self, ax,
249 cmap=None,
250 norm=None,
251 interpolation=None,
252 origin=None,
253 filternorm=True,
254 filterrad=4.0,
255 resample=False,
256 *,
257 interpolation_stage=None,
258 **kwargs
259 ):
260 martist.Artist.__init__(self)
261 cm.ScalarMappable.__init__(self, norm, cmap)
262 if origin is None:
263 origin = mpl.rcParams['image.origin']
264 _api.check_in_list(["upper", "lower"], origin=origin)
265 self.origin = origin
266 self.set_filternorm(filternorm)
267 self.set_filterrad(filterrad)
268 self.set_interpolation(interpolation)
269 self.set_interpolation_stage(interpolation_stage)
270 self.set_resample(resample)
271 self.axes = ax
273 self._imcache = None
275 self._internal_update(kwargs)
277 def __str__(self):
278 try:
279 shape = self.get_shape()
280 return f"{type(self).__name__}(shape={shape!r})"
281 except RuntimeError:
282 return type(self).__name__
284 def __getstate__(self):
285 # Save some space on the pickle by not saving the cache.
286 return {**super().__getstate__(), "_imcache": None}
288 def get_size(self):
289 """Return the size of the image as tuple (numrows, numcols)."""
290 return self.get_shape()[:2]
292 def get_shape(self):
293 """
294 Return the shape of the image as tuple (numrows, numcols, channels).
295 """
296 if self._A is None:
297 raise RuntimeError('You must first set the image array')
299 return self._A.shape
301 def set_alpha(self, alpha):
302 """
303 Set the alpha value used for blending - not supported on all backends.
305 Parameters
306 ----------
307 alpha : float or 2D array-like or None
308 """
309 martist.Artist._set_alpha_for_array(self, alpha)
310 if np.ndim(alpha) not in (0, 2):
311 raise TypeError('alpha must be a float, two-dimensional '
312 'array, or None')
313 self._imcache = None
315 def _get_scalar_alpha(self):
316 """
317 Get a scalar alpha value to be applied to the artist as a whole.
319 If the alpha value is a matrix, the method returns 1.0 because pixels
320 have individual alpha values (see `~._ImageBase._make_image` for
321 details). If the alpha value is a scalar, the method returns said value
322 to be applied to the artist as a whole because pixels do not have
323 individual alpha values.
324 """
325 return 1.0 if self._alpha is None or np.ndim(self._alpha) > 0 \
326 else self._alpha
328 def changed(self):
329 """
330 Call this whenever the mappable is changed so observers can update.
331 """
332 self._imcache = None
333 cm.ScalarMappable.changed(self)
335 def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
336 unsampled=False, round_to_pixel_border=True):
337 """
338 Normalize, rescale, and colormap the image *A* from the given *in_bbox*
339 (in data space), to the given *out_bbox* (in pixel space) clipped to
340 the given *clip_bbox* (also in pixel space), and magnified by the
341 *magnification* factor.
343 *A* may be a greyscale image (M, N) with a dtype of `~numpy.float32`,
344 `~numpy.float64`, `~numpy.float128`, `~numpy.uint16` or `~numpy.uint8`,
345 or an (M, N, 4) RGBA image with a dtype of `~numpy.float32`,
346 `~numpy.float64`, `~numpy.float128`, or `~numpy.uint8`.
348 If *unsampled* is True, the image will not be scaled, but an
349 appropriate affine transformation will be returned instead.
351 If *round_to_pixel_border* is True, the output image size will be
352 rounded to the nearest pixel boundary. This makes the images align
353 correctly with the Axes. It should not be used if exact scaling is
354 needed, such as for `FigureImage`.
356 Returns
357 -------
358 image : (M, N, 4) `numpy.uint8` array
359 The RGBA image, resampled unless *unsampled* is True.
360 x, y : float
361 The upper left corner where the image should be drawn, in pixel
362 space.
363 trans : `~matplotlib.transforms.Affine2D`
364 The affine transformation from image to pixel space.
365 """
366 if A is None:
367 raise RuntimeError('You must first set the image '
368 'array or the image attribute')
369 if A.size == 0:
370 raise RuntimeError("_make_image must get a non-empty image. "
371 "Your Artist's draw method must filter before "
372 "this method is called.")
374 clipped_bbox = Bbox.intersection(out_bbox, clip_bbox)
376 if clipped_bbox is None:
377 return None, 0, 0, None
379 out_width_base = clipped_bbox.width * magnification
380 out_height_base = clipped_bbox.height * magnification
382 if out_width_base == 0 or out_height_base == 0:
383 return None, 0, 0, None
385 if self.origin == 'upper':
386 # Flip the input image using a transform. This avoids the
387 # problem with flipping the array, which results in a copy
388 # when it is converted to contiguous in the C wrapper
389 t0 = Affine2D().translate(0, -A.shape[0]).scale(1, -1)
390 else:
391 t0 = IdentityTransform()
393 t0 += (
394 Affine2D()
395 .scale(
396 in_bbox.width / A.shape[1],
397 in_bbox.height / A.shape[0])
398 .translate(in_bbox.x0, in_bbox.y0)
399 + self.get_transform())
401 t = (t0
402 + (Affine2D()
403 .translate(-clipped_bbox.x0, -clipped_bbox.y0)
404 .scale(magnification)))
406 # So that the image is aligned with the edge of the Axes, we want to
407 # round up the output width to the next integer. This also means
408 # scaling the transform slightly to account for the extra subpixel.
409 if ((not unsampled) and t.is_affine and round_to_pixel_border and
410 (out_width_base % 1.0 != 0.0 or out_height_base % 1.0 != 0.0)):
411 out_width = math.ceil(out_width_base)
412 out_height = math.ceil(out_height_base)
413 extra_width = (out_width - out_width_base) / out_width_base
414 extra_height = (out_height - out_height_base) / out_height_base
415 t += Affine2D().scale(1.0 + extra_width, 1.0 + extra_height)
416 else:
417 out_width = int(out_width_base)
418 out_height = int(out_height_base)
419 out_shape = (out_height, out_width)
421 if not unsampled:
422 if not (A.ndim == 2 or A.ndim == 3 and A.shape[-1] in (3, 4)):
423 raise ValueError(f"Invalid shape {A.shape} for image data")
424 if A.ndim == 2 and self._interpolation_stage != 'rgba':
425 # if we are a 2D array, then we are running through the
426 # norm + colormap transformation. However, in general the
427 # input data is not going to match the size on the screen so we
428 # have to resample to the correct number of pixels
430 # TODO slice input array first
431 a_min = A.min()
432 a_max = A.max()
433 if a_min is np.ma.masked: # All masked; values don't matter.
434 a_min, a_max = np.int32(0), np.int32(1)
435 if A.dtype.kind == 'f': # Float dtype: scale to same dtype.
436 scaled_dtype = np.dtype(
437 np.float64 if A.dtype.itemsize > 4 else np.float32)
438 if scaled_dtype.itemsize < A.dtype.itemsize:
439 _api.warn_external(f"Casting input data from {A.dtype}"
440 f" to {scaled_dtype} for imshow.")
441 else: # Int dtype, likely.
442 # Scale to appropriately sized float: use float32 if the
443 # dynamic range is small, to limit the memory footprint.
444 da = a_max.astype(np.float64) - a_min.astype(np.float64)
445 scaled_dtype = np.float64 if da > 1e8 else np.float32
447 # Scale the input data to [.1, .9]. The Agg interpolators clip
448 # to [0, 1] internally, and we use a smaller input scale to
449 # identify the interpolated points that need to be flagged as
450 # over/under. This may introduce numeric instabilities in very
451 # broadly scaled data.
453 # Always copy, and don't allow array subtypes.
454 A_scaled = np.array(A, dtype=scaled_dtype)
455 # Clip scaled data around norm if necessary. This is necessary
456 # for big numbers at the edge of float64's ability to represent
457 # changes. Applying a norm first would be good, but ruins the
458 # interpolation of over numbers.
459 self.norm.autoscale_None(A)
460 dv = np.float64(self.norm.vmax) - np.float64(self.norm.vmin)
461 vmid = np.float64(self.norm.vmin) + dv / 2
462 fact = 1e7 if scaled_dtype == np.float64 else 1e4
463 newmin = vmid - dv * fact
464 if newmin < a_min:
465 newmin = None
466 else:
467 a_min = np.float64(newmin)
468 newmax = vmid + dv * fact
469 if newmax > a_max:
470 newmax = None
471 else:
472 a_max = np.float64(newmax)
473 if newmax is not None or newmin is not None:
474 np.clip(A_scaled, newmin, newmax, out=A_scaled)
476 # Rescale the raw data to [offset, 1-offset] so that the
477 # resampling code will run cleanly. Using dyadic numbers here
478 # could reduce the error, but would not fully eliminate it and
479 # breaks a number of tests (due to the slightly different
480 # error bouncing some pixels across a boundary in the (very
481 # quantized) colormapping step).
482 offset = .1
483 frac = .8
484 # Run vmin/vmax through the same rescaling as the raw data;
485 # otherwise, data values close or equal to the boundaries can
486 # end up on the wrong side due to floating point error.
487 vmin, vmax = self.norm.vmin, self.norm.vmax
488 if vmin is np.ma.masked:
489 vmin, vmax = a_min, a_max
490 vrange = np.array([vmin, vmax], dtype=scaled_dtype)
492 A_scaled -= a_min
493 vrange -= a_min
494 # .item() handles a_min/a_max being ndarray subclasses.
495 a_min = a_min.astype(scaled_dtype).item()
496 a_max = a_max.astype(scaled_dtype).item()
498 if a_min != a_max:
499 A_scaled /= ((a_max - a_min) / frac)
500 vrange /= ((a_max - a_min) / frac)
501 A_scaled += offset
502 vrange += offset
503 # resample the input data to the correct resolution and shape
504 A_resampled = _resample(self, A_scaled, out_shape, t)
505 del A_scaled # Make sure we don't use A_scaled anymore!
506 # Un-scale the resampled data to approximately the original
507 # range. Things that interpolated to outside the original range
508 # will still be outside, but possibly clipped in the case of
509 # higher order interpolation + drastically changing data.
510 A_resampled -= offset
511 vrange -= offset
512 if a_min != a_max:
513 A_resampled *= ((a_max - a_min) / frac)
514 vrange *= ((a_max - a_min) / frac)
515 A_resampled += a_min
516 vrange += a_min
517 # if using NoNorm, cast back to the original datatype
518 if isinstance(self.norm, mcolors.NoNorm):
519 A_resampled = A_resampled.astype(A.dtype)
521 mask = (np.where(A.mask, np.float32(np.nan), np.float32(1))
522 if A.mask.shape == A.shape # nontrivial mask
523 else np.ones_like(A, np.float32))
524 # we always have to interpolate the mask to account for
525 # non-affine transformations
526 out_alpha = _resample(self, mask, out_shape, t, resample=True)
527 del mask # Make sure we don't use mask anymore!
528 # Agg updates out_alpha in place. If the pixel has no image
529 # data it will not be updated (and still be 0 as we initialized
530 # it), if input data that would go into that output pixel than
531 # it will be `nan`, if all the input data for a pixel is good
532 # it will be 1, and if there is _some_ good data in that output
533 # pixel it will be between [0, 1] (such as a rotated image).
534 out_mask = np.isnan(out_alpha)
535 out_alpha[out_mask] = 1
536 # Apply the pixel-by-pixel alpha values if present
537 alpha = self.get_alpha()
538 if alpha is not None and np.ndim(alpha) > 0:
539 out_alpha *= _resample(self, alpha, out_shape,
540 t, resample=True)
541 # mask and run through the norm
542 resampled_masked = np.ma.masked_array(A_resampled, out_mask)
543 # we have re-set the vmin/vmax to account for small errors
544 # that may have moved input values in/out of range
545 s_vmin, s_vmax = vrange
546 if isinstance(self.norm, mcolors.LogNorm) and s_vmin <= 0:
547 # Don't give 0 or negative values to LogNorm
548 s_vmin = np.finfo(scaled_dtype).eps
549 # Block the norm from sending an update signal during the
550 # temporary vmin/vmax change
551 with self.norm.callbacks.blocked(), \
552 cbook._setattr_cm(self.norm, vmin=s_vmin, vmax=s_vmax):
553 output = self.norm(resampled_masked)
554 else:
555 if A.ndim == 2: # _interpolation_stage == 'rgba'
556 self.norm.autoscale_None(A)
557 A = self.to_rgba(A)
558 alpha = self._get_scalar_alpha()
559 if A.shape[2] == 3:
560 # No need to resample alpha or make a full array; NumPy will expand
561 # this out and cast to uint8 if necessary when it's assigned to the
562 # alpha channel below.
563 output_alpha = (255 * alpha) if A.dtype == np.uint8 else alpha
564 else:
565 output_alpha = _resample( # resample alpha channel
566 self, A[..., 3], out_shape, t, alpha=alpha)
567 output = _resample( # resample rgb channels
568 self, _rgb_to_rgba(A[..., :3]), out_shape, t, alpha=alpha)
569 output[..., 3] = output_alpha # recombine rgb and alpha
571 # output is now either a 2D array of normed (int or float) data
572 # or an RGBA array of re-sampled input
573 output = self.to_rgba(output, bytes=True, norm=False)
574 # output is now a correctly sized RGBA array of uint8
576 # Apply alpha *after* if the input was greyscale without a mask
577 if A.ndim == 2:
578 alpha = self._get_scalar_alpha()
579 alpha_channel = output[:, :, 3]
580 alpha_channel[:] = ( # Assignment will cast to uint8.
581 alpha_channel.astype(np.float32) * out_alpha * alpha)
583 else:
584 if self._imcache is None:
585 self._imcache = self.to_rgba(A, bytes=True, norm=(A.ndim == 2))
586 output = self._imcache
588 # Subset the input image to only the part that will be displayed.
589 subset = TransformedBbox(clip_bbox, t0.inverted()).frozen()
590 output = output[
591 int(max(subset.ymin, 0)):
592 int(min(subset.ymax + 1, output.shape[0])),
593 int(max(subset.xmin, 0)):
594 int(min(subset.xmax + 1, output.shape[1]))]
596 t = Affine2D().translate(
597 int(max(subset.xmin, 0)), int(max(subset.ymin, 0))) + t
599 return output, clipped_bbox.x0, clipped_bbox.y0, t
601 def make_image(self, renderer, magnification=1.0, unsampled=False):
602 """
603 Normalize, rescale, and colormap this image's data for rendering using
604 *renderer*, with the given *magnification*.
606 If *unsampled* is True, the image will not be scaled, but an
607 appropriate affine transformation will be returned instead.
609 Returns
610 -------
611 image : (M, N, 4) `numpy.uint8` array
612 The RGBA image, resampled unless *unsampled* is True.
613 x, y : float
614 The upper left corner where the image should be drawn, in pixel
615 space.
616 trans : `~matplotlib.transforms.Affine2D`
617 The affine transformation from image to pixel space.
618 """
619 raise NotImplementedError('The make_image method must be overridden')
621 def _check_unsampled_image(self):
622 """
623 Return whether the image is better to be drawn unsampled.
625 The derived class needs to override it.
626 """
627 return False
629 @martist.allow_rasterization
630 def draw(self, renderer):
631 # if not visible, declare victory and return
632 if not self.get_visible():
633 self.stale = False
634 return
635 # for empty images, there is nothing to draw!
636 if self.get_array().size == 0:
637 self.stale = False
638 return
639 # actually render the image.
640 gc = renderer.new_gc()
641 self._set_gc_clip(gc)
642 gc.set_alpha(self._get_scalar_alpha())
643 gc.set_url(self.get_url())
644 gc.set_gid(self.get_gid())
645 if (renderer.option_scale_image() # Renderer supports transform kwarg.
646 and self._check_unsampled_image()
647 and self.get_transform().is_affine):
648 im, l, b, trans = self.make_image(renderer, unsampled=True)
649 if im is not None:
650 trans = Affine2D().scale(im.shape[1], im.shape[0]) + trans
651 renderer.draw_image(gc, l, b, im, trans)
652 else:
653 im, l, b, trans = self.make_image(
654 renderer, renderer.get_image_magnification())
655 if im is not None:
656 renderer.draw_image(gc, l, b, im)
657 gc.restore()
658 self.stale = False
660 def contains(self, mouseevent):
661 """Test whether the mouse event occurred within the image."""
662 if (self._different_canvas(mouseevent)
663 # This doesn't work for figimage.
664 or not self.axes.contains(mouseevent)[0]):
665 return False, {}
666 # TODO: make sure this is consistent with patch and patch
667 # collection on nonlinear transformed coordinates.
668 # TODO: consider returning image coordinates (shouldn't
669 # be too difficult given that the image is rectilinear
670 trans = self.get_transform().inverted()
671 x, y = trans.transform([mouseevent.x, mouseevent.y])
672 xmin, xmax, ymin, ymax = self.get_extent()
673 # This checks xmin <= x <= xmax *or* xmax <= x <= xmin.
674 inside = (x is not None and (x - xmin) * (x - xmax) <= 0
675 and y is not None and (y - ymin) * (y - ymax) <= 0)
676 return inside, {}
678 def write_png(self, fname):
679 """Write the image to png file *fname*."""
680 im = self.to_rgba(self._A[::-1] if self.origin == 'lower' else self._A,
681 bytes=True, norm=True)
682 PIL.Image.fromarray(im).save(fname, format="png")
684 @staticmethod
685 def _normalize_image_array(A):
686 """
687 Check validity of image-like input *A* and normalize it to a format suitable for
688 Image subclasses.
689 """
690 A = cbook.safe_masked_invalid(A, copy=True)
691 if A.dtype != np.uint8 and not np.can_cast(A.dtype, float, "same_kind"):
692 raise TypeError(f"Image data of dtype {A.dtype} cannot be "
693 f"converted to float")
694 if A.ndim == 3 and A.shape[-1] == 1:
695 A = A.squeeze(-1) # If just (M, N, 1), assume scalar and apply colormap.
696 if not (A.ndim == 2 or A.ndim == 3 and A.shape[-1] in [3, 4]):
697 raise TypeError(f"Invalid shape {A.shape} for image data")
698 if A.ndim == 3:
699 # If the input data has values outside the valid range (after
700 # normalisation), we issue a warning and then clip X to the bounds
701 # - otherwise casting wraps extreme values, hiding outliers and
702 # making reliable interpretation impossible.
703 high = 255 if np.issubdtype(A.dtype, np.integer) else 1
704 if A.min() < 0 or high < A.max():
705 _log.warning(
706 'Clipping input data to the valid range for imshow with '
707 'RGB data ([0..1] for floats or [0..255] for integers). '
708 'Got range [%s..%s].',
709 A.min(), A.max()
710 )
711 A = np.clip(A, 0, high)
712 # Cast unsupported integer types to uint8
713 if A.dtype != np.uint8 and np.issubdtype(A.dtype, np.integer):
714 A = A.astype(np.uint8)
715 return A
717 def set_data(self, A):
718 """
719 Set the image array.
721 Note that this function does *not* update the normalization used.
723 Parameters
724 ----------
725 A : array-like or `PIL.Image.Image`
726 """
727 if isinstance(A, PIL.Image.Image):
728 A = pil_to_array(A) # Needed e.g. to apply png palette.
729 self._A = self._normalize_image_array(A)
730 self._imcache = None
731 self.stale = True
733 def set_array(self, A):
734 """
735 Retained for backwards compatibility - use set_data instead.
737 Parameters
738 ----------
739 A : array-like
740 """
741 # This also needs to be here to override the inherited
742 # cm.ScalarMappable.set_array method so it is not invoked by mistake.
743 self.set_data(A)
745 def get_interpolation(self):
746 """
747 Return the interpolation method the image uses when resizing.
749 One of 'antialiased', 'nearest', 'bilinear', 'bicubic', 'spline16',
750 'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric',
751 'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos',
752 or 'none'.
753 """
754 return self._interpolation
756 def set_interpolation(self, s):
757 """
758 Set the interpolation method the image uses when resizing.
760 If None, use :rc:`image.interpolation`. If 'none', the image is
761 shown as is without interpolating. 'none' is only supported in
762 agg, ps and pdf backends and will fall back to 'nearest' mode
763 for other backends.
765 Parameters
766 ----------
767 s : {'antialiased', 'nearest', 'bilinear', 'bicubic', 'spline16', \
768'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'catrom', \
769'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos', 'none'} or None
770 """
771 s = mpl._val_or_rc(s, 'image.interpolation').lower()
772 _api.check_in_list(interpolations_names, interpolation=s)
773 self._interpolation = s
774 self.stale = True
776 def get_interpolation_stage(self):
777 """
778 Return when interpolation happens during the transform to RGBA.
780 One of 'data', 'rgba'.
781 """
782 return self._interpolation_stage
784 def set_interpolation_stage(self, s):
785 """
786 Set when interpolation happens during the transform to RGBA.
788 Parameters
789 ----------
790 s : {'data', 'rgba'} or None
791 Whether to apply up/downsampling interpolation in data or RGBA
792 space. If None, use :rc:`image.interpolation_stage`.
793 """
794 s = mpl._val_or_rc(s, 'image.interpolation_stage')
795 _api.check_in_list(['data', 'rgba'], s=s)
796 self._interpolation_stage = s
797 self.stale = True
799 def can_composite(self):
800 """Return whether the image can be composited with its neighbors."""
801 trans = self.get_transform()
802 return (
803 self._interpolation != 'none' and
804 trans.is_affine and
805 trans.is_separable)
807 def set_resample(self, v):
808 """
809 Set whether image resampling is used.
811 Parameters
812 ----------
813 v : bool or None
814 If None, use :rc:`image.resample`.
815 """
816 v = mpl._val_or_rc(v, 'image.resample')
817 self._resample = v
818 self.stale = True
820 def get_resample(self):
821 """Return whether image resampling is used."""
822 return self._resample
824 def set_filternorm(self, filternorm):
825 """
826 Set whether the resize filter normalizes the weights.
828 See help for `~.Axes.imshow`.
830 Parameters
831 ----------
832 filternorm : bool
833 """
834 self._filternorm = bool(filternorm)
835 self.stale = True
837 def get_filternorm(self):
838 """Return whether the resize filter normalizes the weights."""
839 return self._filternorm
841 def set_filterrad(self, filterrad):
842 """
843 Set the resize filter radius only applicable to some
844 interpolation schemes -- see help for imshow
846 Parameters
847 ----------
848 filterrad : positive float
849 """
850 r = float(filterrad)
851 if r <= 0:
852 raise ValueError("The filter radius must be a positive number")
853 self._filterrad = r
854 self.stale = True
856 def get_filterrad(self):
857 """Return the filterrad setting."""
858 return self._filterrad
861class AxesImage(_ImageBase):
862 """
863 An image attached to an Axes.
865 Parameters
866 ----------
867 ax : `~matplotlib.axes.Axes`
868 The Axes the image will belong to.
869 cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
870 The Colormap instance or registered colormap name used to map scalar
871 data to colors.
872 norm : str or `~matplotlib.colors.Normalize`
873 Maps luminance to 0-1.
874 interpolation : str, default: :rc:`image.interpolation`
875 Supported values are 'none', 'antialiased', 'nearest', 'bilinear',
876 'bicubic', 'spline16', 'spline36', 'hanning', 'hamming', 'hermite',
877 'kaiser', 'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell',
878 'sinc', 'lanczos', 'blackman'.
879 interpolation_stage : {'data', 'rgba'}, default: 'data'
880 If 'data', interpolation
881 is carried out on the data provided by the user. If 'rgba', the
882 interpolation is carried out after the colormapping has been
883 applied (visual interpolation).
884 origin : {'upper', 'lower'}, default: :rc:`image.origin`
885 Place the [0, 0] index of the array in the upper left or lower left
886 corner of the Axes. The convention 'upper' is typically used for
887 matrices and images.
888 extent : tuple, optional
889 The data axes (left, right, bottom, top) for making image plots
890 registered with data plots. Default is to label the pixel
891 centers with the zero-based row and column indices.
892 filternorm : bool, default: True
893 A parameter for the antigrain image resize filter
894 (see the antigrain documentation).
895 If filternorm is set, the filter normalizes integer values and corrects
896 the rounding errors. It doesn't do anything with the source floating
897 point values, it corrects only integers according to the rule of 1.0
898 which means that any sum of pixel weights must be equal to 1.0. So,
899 the filter function must produce a graph of the proper shape.
900 filterrad : float > 0, default: 4
901 The filter radius for filters that have a radius parameter, i.e. when
902 interpolation is one of: 'sinc', 'lanczos' or 'blackman'.
903 resample : bool, default: False
904 When True, use a full resampling method. When False, only resample when
905 the output image is larger than the input image.
906 **kwargs : `~matplotlib.artist.Artist` properties
907 """
909 def __init__(self, ax,
910 *,
911 cmap=None,
912 norm=None,
913 interpolation=None,
914 origin=None,
915 extent=None,
916 filternorm=True,
917 filterrad=4.0,
918 resample=False,
919 interpolation_stage=None,
920 **kwargs
921 ):
923 self._extent = extent
925 super().__init__(
926 ax,
927 cmap=cmap,
928 norm=norm,
929 interpolation=interpolation,
930 origin=origin,
931 filternorm=filternorm,
932 filterrad=filterrad,
933 resample=resample,
934 interpolation_stage=interpolation_stage,
935 **kwargs
936 )
938 def get_window_extent(self, renderer=None):
939 x0, x1, y0, y1 = self._extent
940 bbox = Bbox.from_extents([x0, y0, x1, y1])
941 return bbox.transformed(self.get_transform())
943 def make_image(self, renderer, magnification=1.0, unsampled=False):
944 # docstring inherited
945 trans = self.get_transform()
946 # image is created in the canvas coordinate.
947 x1, x2, y1, y2 = self.get_extent()
948 bbox = Bbox(np.array([[x1, y1], [x2, y2]]))
949 transformed_bbox = TransformedBbox(bbox, trans)
950 clip = ((self.get_clip_box() or self.axes.bbox) if self.get_clip_on()
951 else self.figure.bbox)
952 return self._make_image(self._A, bbox, transformed_bbox, clip,
953 magnification, unsampled=unsampled)
955 def _check_unsampled_image(self):
956 """Return whether the image would be better drawn unsampled."""
957 return self.get_interpolation() == "none"
959 def set_extent(self, extent, **kwargs):
960 """
961 Set the image extent.
963 Parameters
964 ----------
965 extent : 4-tuple of float
966 The position and size of the image as tuple
967 ``(left, right, bottom, top)`` in data coordinates.
968 **kwargs
969 Other parameters from which unit info (i.e., the *xunits*,
970 *yunits*, *zunits* (for 3D Axes), *runits* and *thetaunits* (for
971 polar Axes) entries are applied, if present.
973 Notes
974 -----
975 This updates ``ax.dataLim``, and, if autoscaling, sets ``ax.viewLim``
976 to tightly fit the image, regardless of ``dataLim``. Autoscaling
977 state is not changed, so following this with ``ax.autoscale_view()``
978 will redo the autoscaling in accord with ``dataLim``.
979 """
980 (xmin, xmax), (ymin, ymax) = self.axes._process_unit_info(
981 [("x", [extent[0], extent[1]]),
982 ("y", [extent[2], extent[3]])],
983 kwargs)
984 if kwargs:
985 raise _api.kwarg_error("set_extent", kwargs)
986 xmin = self.axes._validate_converted_limits(
987 xmin, self.convert_xunits)
988 xmax = self.axes._validate_converted_limits(
989 xmax, self.convert_xunits)
990 ymin = self.axes._validate_converted_limits(
991 ymin, self.convert_yunits)
992 ymax = self.axes._validate_converted_limits(
993 ymax, self.convert_yunits)
994 extent = [xmin, xmax, ymin, ymax]
996 self._extent = extent
997 corners = (xmin, ymin), (xmax, ymax)
998 self.axes.update_datalim(corners)
999 self.sticky_edges.x[:] = [xmin, xmax]
1000 self.sticky_edges.y[:] = [ymin, ymax]
1001 if self.axes.get_autoscalex_on():
1002 self.axes.set_xlim((xmin, xmax), auto=None)
1003 if self.axes.get_autoscaley_on():
1004 self.axes.set_ylim((ymin, ymax), auto=None)
1005 self.stale = True
1007 def get_extent(self):
1008 """Return the image extent as tuple (left, right, bottom, top)."""
1009 if self._extent is not None:
1010 return self._extent
1011 else:
1012 sz = self.get_size()
1013 numrows, numcols = sz
1014 if self.origin == 'upper':
1015 return (-0.5, numcols-0.5, numrows-0.5, -0.5)
1016 else:
1017 return (-0.5, numcols-0.5, -0.5, numrows-0.5)
1019 def get_cursor_data(self, event):
1020 """
1021 Return the image value at the event position or *None* if the event is
1022 outside the image.
1024 See Also
1025 --------
1026 matplotlib.artist.Artist.get_cursor_data
1027 """
1028 xmin, xmax, ymin, ymax = self.get_extent()
1029 if self.origin == 'upper':
1030 ymin, ymax = ymax, ymin
1031 arr = self.get_array()
1032 data_extent = Bbox([[xmin, ymin], [xmax, ymax]])
1033 array_extent = Bbox([[0, 0], [arr.shape[1], arr.shape[0]]])
1034 trans = self.get_transform().inverted()
1035 trans += BboxTransform(boxin=data_extent, boxout=array_extent)
1036 point = trans.transform([event.x, event.y])
1037 if any(np.isnan(point)):
1038 return None
1039 j, i = point.astype(int)
1040 # Clip the coordinates at array bounds
1041 if not (0 <= i < arr.shape[0]) or not (0 <= j < arr.shape[1]):
1042 return None
1043 else:
1044 return arr[i, j]
1047class NonUniformImage(AxesImage):
1049 def __init__(self, ax, *, interpolation='nearest', **kwargs):
1050 """
1051 Parameters
1052 ----------
1053 ax : `~matplotlib.axes.Axes`
1054 The Axes the image will belong to.
1055 interpolation : {'nearest', 'bilinear'}, default: 'nearest'
1056 The interpolation scheme used in the resampling.
1057 **kwargs
1058 All other keyword arguments are identical to those of `.AxesImage`.
1059 """
1060 super().__init__(ax, **kwargs)
1061 self.set_interpolation(interpolation)
1063 def _check_unsampled_image(self):
1064 """Return False. Do not use unsampled image."""
1065 return False
1067 def make_image(self, renderer, magnification=1.0, unsampled=False):
1068 # docstring inherited
1069 if self._A is None:
1070 raise RuntimeError('You must first set the image array')
1071 if unsampled:
1072 raise ValueError('unsampled not supported on NonUniformImage')
1073 A = self._A
1074 if A.ndim == 2:
1075 if A.dtype != np.uint8:
1076 A = self.to_rgba(A, bytes=True)
1077 else:
1078 A = np.repeat(A[:, :, np.newaxis], 4, 2)
1079 A[:, :, 3] = 255
1080 else:
1081 if A.dtype != np.uint8:
1082 A = (255*A).astype(np.uint8)
1083 if A.shape[2] == 3:
1084 B = np.zeros(tuple([*A.shape[0:2], 4]), np.uint8)
1085 B[:, :, 0:3] = A
1086 B[:, :, 3] = 255
1087 A = B
1088 vl = self.axes.viewLim
1089 l, b, r, t = self.axes.bbox.extents
1090 width = int(((round(r) + 0.5) - (round(l) - 0.5)) * magnification)
1091 height = int(((round(t) + 0.5) - (round(b) - 0.5)) * magnification)
1092 x_pix = np.linspace(vl.x0, vl.x1, width)
1093 y_pix = np.linspace(vl.y0, vl.y1, height)
1094 if self._interpolation == "nearest":
1095 x_mid = (self._Ax[:-1] + self._Ax[1:]) / 2
1096 y_mid = (self._Ay[:-1] + self._Ay[1:]) / 2
1097 x_int = x_mid.searchsorted(x_pix)
1098 y_int = y_mid.searchsorted(y_pix)
1099 # The following is equal to `A[y_int[:, None], x_int[None, :]]`,
1100 # but many times faster. Both casting to uint32 (to have an
1101 # effectively 1D array) and manual index flattening matter.
1102 im = (
1103 np.ascontiguousarray(A).view(np.uint32).ravel()[
1104 np.add.outer(y_int * A.shape[1], x_int)]
1105 .view(np.uint8).reshape((height, width, 4)))
1106 else: # self._interpolation == "bilinear"
1107 # Use np.interp to compute x_int/x_float has similar speed.
1108 x_int = np.clip(
1109 self._Ax.searchsorted(x_pix) - 1, 0, len(self._Ax) - 2)
1110 y_int = np.clip(
1111 self._Ay.searchsorted(y_pix) - 1, 0, len(self._Ay) - 2)
1112 idx_int = np.add.outer(y_int * A.shape[1], x_int)
1113 x_frac = np.clip(
1114 np.divide(x_pix - self._Ax[x_int], np.diff(self._Ax)[x_int],
1115 dtype=np.float32), # Downcasting helps with speed.
1116 0, 1)
1117 y_frac = np.clip(
1118 np.divide(y_pix - self._Ay[y_int], np.diff(self._Ay)[y_int],
1119 dtype=np.float32),
1120 0, 1)
1121 f00 = np.outer(1 - y_frac, 1 - x_frac)
1122 f10 = np.outer(y_frac, 1 - x_frac)
1123 f01 = np.outer(1 - y_frac, x_frac)
1124 f11 = np.outer(y_frac, x_frac)
1125 im = np.empty((height, width, 4), np.uint8)
1126 for chan in range(4):
1127 ac = A[:, :, chan].reshape(-1) # reshape(-1) avoids a copy.
1128 # Shifting the buffer start (`ac[offset:]`) avoids an array
1129 # addition (`ac[idx_int + offset]`).
1130 buf = f00 * ac[idx_int]
1131 buf += f10 * ac[A.shape[1]:][idx_int]
1132 buf += f01 * ac[1:][idx_int]
1133 buf += f11 * ac[A.shape[1] + 1:][idx_int]
1134 im[:, :, chan] = buf # Implicitly casts to uint8.
1135 return im, l, b, IdentityTransform()
1137 def set_data(self, x, y, A):
1138 """
1139 Set the grid for the pixel centers, and the pixel values.
1141 Parameters
1142 ----------
1143 x, y : 1D array-like
1144 Monotonic arrays of shapes (N,) and (M,), respectively, specifying
1145 pixel centers.
1146 A : array-like
1147 (M, N) `~numpy.ndarray` or masked array of values to be
1148 colormapped, or (M, N, 3) RGB array, or (M, N, 4) RGBA array.
1149 """
1150 A = self._normalize_image_array(A)
1151 x = np.array(x, np.float32)
1152 y = np.array(y, np.float32)
1153 if not (x.ndim == y.ndim == 1 and A.shape[:2] == y.shape + x.shape):
1154 raise TypeError("Axes don't match array shape")
1155 self._A = A
1156 self._Ax = x
1157 self._Ay = y
1158 self._imcache = None
1159 self.stale = True
1161 def set_array(self, *args):
1162 raise NotImplementedError('Method not supported')
1164 def set_interpolation(self, s):
1165 """
1166 Parameters
1167 ----------
1168 s : {'nearest', 'bilinear'} or None
1169 If None, use :rc:`image.interpolation`.
1170 """
1171 if s is not None and s not in ('nearest', 'bilinear'):
1172 raise NotImplementedError('Only nearest neighbor and '
1173 'bilinear interpolations are supported')
1174 super().set_interpolation(s)
1176 def get_extent(self):
1177 if self._A is None:
1178 raise RuntimeError('Must set data first')
1179 return self._Ax[0], self._Ax[-1], self._Ay[0], self._Ay[-1]
1181 @_api.rename_parameter("3.8", "s", "filternorm")
1182 def set_filternorm(self, filternorm):
1183 pass
1185 @_api.rename_parameter("3.8", "s", "filterrad")
1186 def set_filterrad(self, filterrad):
1187 pass
1189 def set_norm(self, norm):
1190 if self._A is not None:
1191 raise RuntimeError('Cannot change colors after loading data')
1192 super().set_norm(norm)
1194 def set_cmap(self, cmap):
1195 if self._A is not None:
1196 raise RuntimeError('Cannot change colors after loading data')
1197 super().set_cmap(cmap)
1199 def get_cursor_data(self, event):
1200 # docstring inherited
1201 x, y = event.xdata, event.ydata
1202 if (x < self._Ax[0] or x > self._Ax[-1] or
1203 y < self._Ay[0] or y > self._Ay[-1]):
1204 return None
1205 j = np.searchsorted(self._Ax, x) - 1
1206 i = np.searchsorted(self._Ay, y) - 1
1207 return self._A[i, j]
1210class PcolorImage(AxesImage):
1211 """
1212 Make a pcolor-style plot with an irregular rectangular grid.
1214 This uses a variation of the original irregular image code,
1215 and it is used by pcolorfast for the corresponding grid type.
1216 """
1218 def __init__(self, ax,
1219 x=None,
1220 y=None,
1221 A=None,
1222 *,
1223 cmap=None,
1224 norm=None,
1225 **kwargs
1226 ):
1227 """
1228 Parameters
1229 ----------
1230 ax : `~matplotlib.axes.Axes`
1231 The Axes the image will belong to.
1232 x, y : 1D array-like, optional
1233 Monotonic arrays of length N+1 and M+1, respectively, specifying
1234 rectangle boundaries. If not given, will default to
1235 ``range(N + 1)`` and ``range(M + 1)``, respectively.
1236 A : array-like
1237 The data to be color-coded. The interpretation depends on the
1238 shape:
1240 - (M, N) `~numpy.ndarray` or masked array: values to be colormapped
1241 - (M, N, 3): RGB array
1242 - (M, N, 4): RGBA array
1244 cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
1245 The Colormap instance or registered colormap name used to map
1246 scalar data to colors.
1247 norm : str or `~matplotlib.colors.Normalize`
1248 Maps luminance to 0-1.
1249 **kwargs : `~matplotlib.artist.Artist` properties
1250 """
1251 super().__init__(ax, norm=norm, cmap=cmap)
1252 self._internal_update(kwargs)
1253 if A is not None:
1254 self.set_data(x, y, A)
1256 def make_image(self, renderer, magnification=1.0, unsampled=False):
1257 # docstring inherited
1258 if self._A is None:
1259 raise RuntimeError('You must first set the image array')
1260 if unsampled:
1261 raise ValueError('unsampled not supported on PColorImage')
1263 if self._imcache is None:
1264 A = self.to_rgba(self._A, bytes=True)
1265 self._imcache = np.pad(A, [(1, 1), (1, 1), (0, 0)], "constant")
1266 padded_A = self._imcache
1267 bg = mcolors.to_rgba(self.axes.patch.get_facecolor(), 0)
1268 bg = (np.array(bg) * 255).astype(np.uint8)
1269 if (padded_A[0, 0] != bg).all():
1270 padded_A[[0, -1], :] = padded_A[:, [0, -1]] = bg
1272 l, b, r, t = self.axes.bbox.extents
1273 width = (round(r) + 0.5) - (round(l) - 0.5)
1274 height = (round(t) + 0.5) - (round(b) - 0.5)
1275 width = round(width * magnification)
1276 height = round(height * magnification)
1277 vl = self.axes.viewLim
1279 x_pix = np.linspace(vl.x0, vl.x1, width)
1280 y_pix = np.linspace(vl.y0, vl.y1, height)
1281 x_int = self._Ax.searchsorted(x_pix)
1282 y_int = self._Ay.searchsorted(y_pix)
1283 im = ( # See comment in NonUniformImage.make_image re: performance.
1284 padded_A.view(np.uint32).ravel()[
1285 np.add.outer(y_int * padded_A.shape[1], x_int)]
1286 .view(np.uint8).reshape((height, width, 4)))
1287 return im, l, b, IdentityTransform()
1289 def _check_unsampled_image(self):
1290 return False
1292 def set_data(self, x, y, A):
1293 """
1294 Set the grid for the rectangle boundaries, and the data values.
1296 Parameters
1297 ----------
1298 x, y : 1D array-like, optional
1299 Monotonic arrays of length N+1 and M+1, respectively, specifying
1300 rectangle boundaries. If not given, will default to
1301 ``range(N + 1)`` and ``range(M + 1)``, respectively.
1302 A : array-like
1303 The data to be color-coded. The interpretation depends on the
1304 shape:
1306 - (M, N) `~numpy.ndarray` or masked array: values to be colormapped
1307 - (M, N, 3): RGB array
1308 - (M, N, 4): RGBA array
1309 """
1310 A = self._normalize_image_array(A)
1311 x = np.arange(0., A.shape[1] + 1) if x is None else np.array(x, float).ravel()
1312 y = np.arange(0., A.shape[0] + 1) if y is None else np.array(y, float).ravel()
1313 if A.shape[:2] != (y.size - 1, x.size - 1):
1314 raise ValueError(
1315 "Axes don't match array shape. Got %s, expected %s." %
1316 (A.shape[:2], (y.size - 1, x.size - 1)))
1317 # For efficient cursor readout, ensure x and y are increasing.
1318 if x[-1] < x[0]:
1319 x = x[::-1]
1320 A = A[:, ::-1]
1321 if y[-1] < y[0]:
1322 y = y[::-1]
1323 A = A[::-1]
1324 self._A = A
1325 self._Ax = x
1326 self._Ay = y
1327 self._imcache = None
1328 self.stale = True
1330 def set_array(self, *args):
1331 raise NotImplementedError('Method not supported')
1333 def get_cursor_data(self, event):
1334 # docstring inherited
1335 x, y = event.xdata, event.ydata
1336 if (x < self._Ax[0] or x > self._Ax[-1] or
1337 y < self._Ay[0] or y > self._Ay[-1]):
1338 return None
1339 j = np.searchsorted(self._Ax, x) - 1
1340 i = np.searchsorted(self._Ay, y) - 1
1341 return self._A[i, j]
1344class FigureImage(_ImageBase):
1345 """An image attached to a figure."""
1347 zorder = 0
1349 _interpolation = 'nearest'
1351 def __init__(self, fig,
1352 *,
1353 cmap=None,
1354 norm=None,
1355 offsetx=0,
1356 offsety=0,
1357 origin=None,
1358 **kwargs
1359 ):
1360 """
1361 cmap is a colors.Colormap instance
1362 norm is a colors.Normalize instance to map luminance to 0-1
1364 kwargs are an optional list of Artist keyword args
1365 """
1366 super().__init__(
1367 None,
1368 norm=norm,
1369 cmap=cmap,
1370 origin=origin
1371 )
1372 self.figure = fig
1373 self.ox = offsetx
1374 self.oy = offsety
1375 self._internal_update(kwargs)
1376 self.magnification = 1.0
1378 def get_extent(self):
1379 """Return the image extent as tuple (left, right, bottom, top)."""
1380 numrows, numcols = self.get_size()
1381 return (-0.5 + self.ox, numcols-0.5 + self.ox,
1382 -0.5 + self.oy, numrows-0.5 + self.oy)
1384 def make_image(self, renderer, magnification=1.0, unsampled=False):
1385 # docstring inherited
1386 fac = renderer.dpi/self.figure.dpi
1387 # fac here is to account for pdf, eps, svg backends where
1388 # figure.dpi is set to 72. This means we need to scale the
1389 # image (using magnification) and offset it appropriately.
1390 bbox = Bbox([[self.ox/fac, self.oy/fac],
1391 [(self.ox/fac + self._A.shape[1]),
1392 (self.oy/fac + self._A.shape[0])]])
1393 width, height = self.figure.get_size_inches()
1394 width *= renderer.dpi
1395 height *= renderer.dpi
1396 clip = Bbox([[0, 0], [width, height]])
1397 return self._make_image(
1398 self._A, bbox, bbox, clip, magnification=magnification / fac,
1399 unsampled=unsampled, round_to_pixel_border=False)
1401 def set_data(self, A):
1402 """Set the image array."""
1403 cm.ScalarMappable.set_array(self, A)
1404 self.stale = True
1407class BboxImage(_ImageBase):
1408 """The Image class whose size is determined by the given bbox."""
1410 def __init__(self, bbox,
1411 *,
1412 cmap=None,
1413 norm=None,
1414 interpolation=None,
1415 origin=None,
1416 filternorm=True,
1417 filterrad=4.0,
1418 resample=False,
1419 **kwargs
1420 ):
1421 """
1422 cmap is a colors.Colormap instance
1423 norm is a colors.Normalize instance to map luminance to 0-1
1425 kwargs are an optional list of Artist keyword args
1426 """
1427 super().__init__(
1428 None,
1429 cmap=cmap,
1430 norm=norm,
1431 interpolation=interpolation,
1432 origin=origin,
1433 filternorm=filternorm,
1434 filterrad=filterrad,
1435 resample=resample,
1436 **kwargs
1437 )
1438 self.bbox = bbox
1440 def get_window_extent(self, renderer=None):
1441 if renderer is None:
1442 renderer = self.get_figure()._get_renderer()
1444 if isinstance(self.bbox, BboxBase):
1445 return self.bbox
1446 elif callable(self.bbox):
1447 return self.bbox(renderer)
1448 else:
1449 raise ValueError("Unknown type of bbox")
1451 def contains(self, mouseevent):
1452 """Test whether the mouse event occurred within the image."""
1453 if self._different_canvas(mouseevent) or not self.get_visible():
1454 return False, {}
1455 x, y = mouseevent.x, mouseevent.y
1456 inside = self.get_window_extent().contains(x, y)
1457 return inside, {}
1459 def make_image(self, renderer, magnification=1.0, unsampled=False):
1460 # docstring inherited
1461 width, height = renderer.get_canvas_width_height()
1462 bbox_in = self.get_window_extent(renderer).frozen()
1463 bbox_in._points /= [width, height]
1464 bbox_out = self.get_window_extent(renderer)
1465 clip = Bbox([[0, 0], [width, height]])
1466 self._transform = BboxTransformTo(clip)
1467 return self._make_image(
1468 self._A,
1469 bbox_in, bbox_out, clip, magnification, unsampled=unsampled)
1472def imread(fname, format=None):
1473 """
1474 Read an image from a file into an array.
1476 .. note::
1478 This function exists for historical reasons. It is recommended to
1479 use `PIL.Image.open` instead for loading images.
1481 Parameters
1482 ----------
1483 fname : str or file-like
1484 The image file to read: a filename, a URL or a file-like object opened
1485 in read-binary mode.
1487 Passing a URL is deprecated. Please open the URL
1488 for reading and pass the result to Pillow, e.g. with
1489 ``np.array(PIL.Image.open(urllib.request.urlopen(url)))``.
1490 format : str, optional
1491 The image file format assumed for reading the data. The image is
1492 loaded as a PNG file if *format* is set to "png", if *fname* is a path
1493 or opened file with a ".png" extension, or if it is a URL. In all
1494 other cases, *format* is ignored and the format is auto-detected by
1495 `PIL.Image.open`.
1497 Returns
1498 -------
1499 `numpy.array`
1500 The image data. The returned array has shape
1502 - (M, N) for grayscale images.
1503 - (M, N, 3) for RGB images.
1504 - (M, N, 4) for RGBA images.
1506 PNG images are returned as float arrays (0-1). All other formats are
1507 returned as int arrays, with a bit depth determined by the file's
1508 contents.
1509 """
1510 # hide imports to speed initial import on systems with slow linkers
1511 from urllib import parse
1513 if format is None:
1514 if isinstance(fname, str):
1515 parsed = parse.urlparse(fname)
1516 # If the string is a URL (Windows paths appear as if they have a
1517 # length-1 scheme), assume png.
1518 if len(parsed.scheme) > 1:
1519 ext = 'png'
1520 else:
1521 ext = Path(fname).suffix.lower()[1:]
1522 elif hasattr(fname, 'geturl'): # Returned by urlopen().
1523 # We could try to parse the url's path and use the extension, but
1524 # returning png is consistent with the block above. Note that this
1525 # if clause has to come before checking for fname.name as
1526 # urlopen("file:///...") also has a name attribute (with the fixed
1527 # value "<urllib response>").
1528 ext = 'png'
1529 elif hasattr(fname, 'name'):
1530 ext = Path(fname.name).suffix.lower()[1:]
1531 else:
1532 ext = 'png'
1533 else:
1534 ext = format
1535 img_open = (
1536 PIL.PngImagePlugin.PngImageFile if ext == 'png' else PIL.Image.open)
1537 if isinstance(fname, str) and len(parse.urlparse(fname).scheme) > 1:
1538 # Pillow doesn't handle URLs directly.
1539 raise ValueError(
1540 "Please open the URL for reading and pass the "
1541 "result to Pillow, e.g. with "
1542 "``np.array(PIL.Image.open(urllib.request.urlopen(url)))``."
1543 )
1544 with img_open(fname) as image:
1545 return (_pil_png_to_float_array(image)
1546 if isinstance(image, PIL.PngImagePlugin.PngImageFile) else
1547 pil_to_array(image))
1550def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None,
1551 origin=None, dpi=100, *, metadata=None, pil_kwargs=None):
1552 """
1553 Colormap and save an array as an image file.
1555 RGB(A) images are passed through. Single channel images will be
1556 colormapped according to *cmap* and *norm*.
1558 .. note::
1560 If you want to save a single channel image as gray scale please use an
1561 image I/O library (such as pillow, tifffile, or imageio) directly.
1563 Parameters
1564 ----------
1565 fname : str or path-like or file-like
1566 A path or a file-like object to store the image in.
1567 If *format* is not set, then the output format is inferred from the
1568 extension of *fname*, if any, and from :rc:`savefig.format` otherwise.
1569 If *format* is set, it determines the output format.
1570 arr : array-like
1571 The image data. The shape can be one of
1572 MxN (luminance), MxNx3 (RGB) or MxNx4 (RGBA).
1573 vmin, vmax : float, optional
1574 *vmin* and *vmax* set the color scaling for the image by fixing the
1575 values that map to the colormap color limits. If either *vmin*
1576 or *vmax* is None, that limit is determined from the *arr*
1577 min/max value.
1578 cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
1579 A Colormap instance or registered colormap name. The colormap
1580 maps scalar data to colors. It is ignored for RGB(A) data.
1581 format : str, optional
1582 The file format, e.g. 'png', 'pdf', 'svg', ... The behavior when this
1583 is unset is documented under *fname*.
1584 origin : {'upper', 'lower'}, default: :rc:`image.origin`
1585 Indicates whether the ``(0, 0)`` index of the array is in the upper
1586 left or lower left corner of the Axes.
1587 dpi : float
1588 The DPI to store in the metadata of the file. This does not affect the
1589 resolution of the output image. Depending on file format, this may be
1590 rounded to the nearest integer.
1591 metadata : dict, optional
1592 Metadata in the image file. The supported keys depend on the output
1593 format, see the documentation of the respective backends for more
1594 information.
1595 Currently only supported for "png", "pdf", "ps", "eps", and "svg".
1596 pil_kwargs : dict, optional
1597 Keyword arguments passed to `PIL.Image.Image.save`. If the 'pnginfo'
1598 key is present, it completely overrides *metadata*, including the
1599 default 'Software' key.
1600 """
1601 from matplotlib.figure import Figure
1602 if isinstance(fname, os.PathLike):
1603 fname = os.fspath(fname)
1604 if format is None:
1605 format = (Path(fname).suffix[1:] if isinstance(fname, str)
1606 else mpl.rcParams["savefig.format"]).lower()
1607 if format in ["pdf", "ps", "eps", "svg"]:
1608 # Vector formats that are not handled by PIL.
1609 if pil_kwargs is not None:
1610 raise ValueError(
1611 f"Cannot use 'pil_kwargs' when saving to {format}")
1612 fig = Figure(dpi=dpi, frameon=False)
1613 fig.figimage(arr, cmap=cmap, vmin=vmin, vmax=vmax, origin=origin,
1614 resize=True)
1615 fig.savefig(fname, dpi=dpi, format=format, transparent=True,
1616 metadata=metadata)
1617 else:
1618 # Don't bother creating an image; this avoids rounding errors on the
1619 # size when dividing and then multiplying by dpi.
1620 if origin is None:
1621 origin = mpl.rcParams["image.origin"]
1622 else:
1623 _api.check_in_list(('upper', 'lower'), origin=origin)
1624 if origin == "lower":
1625 arr = arr[::-1]
1626 if (isinstance(arr, memoryview) and arr.format == "B"
1627 and arr.ndim == 3 and arr.shape[-1] == 4):
1628 # Such an ``arr`` would also be handled fine by sm.to_rgba below
1629 # (after casting with asarray), but it is useful to special-case it
1630 # because that's what backend_agg passes, and can be in fact used
1631 # as is, saving a few operations.
1632 rgba = arr
1633 else:
1634 sm = cm.ScalarMappable(cmap=cmap)
1635 sm.set_clim(vmin, vmax)
1636 rgba = sm.to_rgba(arr, bytes=True)
1637 if pil_kwargs is None:
1638 pil_kwargs = {}
1639 else:
1640 # we modify this below, so make a copy (don't modify caller's dict)
1641 pil_kwargs = pil_kwargs.copy()
1642 pil_shape = (rgba.shape[1], rgba.shape[0])
1643 rgba = np.require(rgba, requirements='C')
1644 image = PIL.Image.frombuffer(
1645 "RGBA", pil_shape, rgba, "raw", "RGBA", 0, 1)
1646 if format == "png":
1647 # Only use the metadata kwarg if pnginfo is not set, because the
1648 # semantics of duplicate keys in pnginfo is unclear.
1649 if "pnginfo" in pil_kwargs:
1650 if metadata:
1651 _api.warn_external("'metadata' is overridden by the "
1652 "'pnginfo' entry in 'pil_kwargs'.")
1653 else:
1654 metadata = {
1655 "Software": (f"Matplotlib version{mpl.__version__}, "
1656 f"https://matplotlib.org/"),
1657 **(metadata if metadata is not None else {}),
1658 }
1659 pil_kwargs["pnginfo"] = pnginfo = PIL.PngImagePlugin.PngInfo()
1660 for k, v in metadata.items():
1661 if v is not None:
1662 pnginfo.add_text(k, v)
1663 elif metadata is not None:
1664 raise ValueError(f"metadata not supported for format {format!r}")
1665 if format in ["jpg", "jpeg"]:
1666 format = "jpeg" # Pillow doesn't recognize "jpg".
1667 facecolor = mpl.rcParams["savefig.facecolor"]
1668 if cbook._str_equal(facecolor, "auto"):
1669 facecolor = mpl.rcParams["figure.facecolor"]
1670 color = tuple(int(x * 255) for x in mcolors.to_rgb(facecolor))
1671 background = PIL.Image.new("RGB", pil_shape, color)
1672 background.paste(image, image)
1673 image = background
1674 pil_kwargs.setdefault("format", format)
1675 pil_kwargs.setdefault("dpi", (dpi, dpi))
1676 image.save(fname, **pil_kwargs)
1679def pil_to_array(pilImage):
1680 """
1681 Load a `PIL image`_ and return it as a numpy int array.
1683 .. _PIL image: https://pillow.readthedocs.io/en/latest/reference/Image.html
1685 Returns
1686 -------
1687 numpy.array
1689 The array shape depends on the image type:
1691 - (M, N) for grayscale images.
1692 - (M, N, 3) for RGB images.
1693 - (M, N, 4) for RGBA images.
1694 """
1695 if pilImage.mode in ['RGBA', 'RGBX', 'RGB', 'L']:
1696 # return MxNx4 RGBA, MxNx3 RBA, or MxN luminance array
1697 return np.asarray(pilImage)
1698 elif pilImage.mode.startswith('I;16'):
1699 # return MxN luminance array of uint16
1700 raw = pilImage.tobytes('raw', pilImage.mode)
1701 if pilImage.mode.endswith('B'):
1702 x = np.frombuffer(raw, '>u2')
1703 else:
1704 x = np.frombuffer(raw, '<u2')
1705 return x.reshape(pilImage.size[::-1]).astype('=u2')
1706 else: # try to convert to an rgba image
1707 try:
1708 pilImage = pilImage.convert('RGBA')
1709 except ValueError as err:
1710 raise RuntimeError('Unknown image mode') from err
1711 return np.asarray(pilImage) # return MxNx4 RGBA array
1714def _pil_png_to_float_array(pil_png):
1715 """Convert a PIL `PNGImageFile` to a 0-1 float array."""
1716 # Unlike pil_to_array this converts to 0-1 float32s for backcompat with the
1717 # old libpng-based loader.
1718 # The supported rawmodes are from PIL.PngImagePlugin._MODES. When
1719 # mode == "RGB(A)", the 16-bit raw data has already been coarsened to 8-bit
1720 # by Pillow.
1721 mode = pil_png.mode
1722 rawmode = pil_png.png.im_rawmode
1723 if rawmode == "1": # Grayscale.
1724 return np.asarray(pil_png, np.float32)
1725 if rawmode == "L;2": # Grayscale.
1726 return np.divide(pil_png, 2**2 - 1, dtype=np.float32)
1727 if rawmode == "L;4": # Grayscale.
1728 return np.divide(pil_png, 2**4 - 1, dtype=np.float32)
1729 if rawmode == "L": # Grayscale.
1730 return np.divide(pil_png, 2**8 - 1, dtype=np.float32)
1731 if rawmode == "I;16B": # Grayscale.
1732 return np.divide(pil_png, 2**16 - 1, dtype=np.float32)
1733 if mode == "RGB": # RGB.
1734 return np.divide(pil_png, 2**8 - 1, dtype=np.float32)
1735 if mode == "P": # Palette.
1736 return np.divide(pil_png.convert("RGBA"), 2**8 - 1, dtype=np.float32)
1737 if mode == "LA": # Grayscale + alpha.
1738 return np.divide(pil_png.convert("RGBA"), 2**8 - 1, dtype=np.float32)
1739 if mode == "RGBA": # RGBA.
1740 return np.divide(pil_png, 2**8 - 1, dtype=np.float32)
1741 raise ValueError(f"Unknown PIL rawmode: {rawmode}")
1744def thumbnail(infile, thumbfile, scale=0.1, interpolation='bilinear',
1745 preview=False):
1746 """
1747 Make a thumbnail of image in *infile* with output filename *thumbfile*.
1749 See :doc:`/gallery/misc/image_thumbnail_sgskip`.
1751 Parameters
1752 ----------
1753 infile : str or file-like
1754 The image file. Matplotlib relies on Pillow_ for image reading, and
1755 thus supports a wide range of file formats, including PNG, JPG, TIFF
1756 and others.
1758 .. _Pillow: https://python-pillow.org/
1760 thumbfile : str or file-like
1761 The thumbnail filename.
1763 scale : float, default: 0.1
1764 The scale factor for the thumbnail.
1766 interpolation : str, default: 'bilinear'
1767 The interpolation scheme used in the resampling. See the
1768 *interpolation* parameter of `~.Axes.imshow` for possible values.
1770 preview : bool, default: False
1771 If True, the default backend (presumably a user interface
1772 backend) will be used which will cause a figure to be raised if
1773 `~matplotlib.pyplot.show` is called. If it is False, the figure is
1774 created using `.FigureCanvasBase` and the drawing backend is selected
1775 as `.Figure.savefig` would normally do.
1777 Returns
1778 -------
1779 `.Figure`
1780 The figure instance containing the thumbnail.
1781 """
1783 im = imread(infile)
1784 rows, cols, depth = im.shape
1786 # This doesn't really matter (it cancels in the end) but the API needs it.
1787 dpi = 100
1789 height = rows / dpi * scale
1790 width = cols / dpi * scale
1792 if preview:
1793 # Let the UI backend do everything.
1794 import matplotlib.pyplot as plt
1795 fig = plt.figure(figsize=(width, height), dpi=dpi)
1796 else:
1797 from matplotlib.figure import Figure
1798 fig = Figure(figsize=(width, height), dpi=dpi)
1799 FigureCanvasBase(fig)
1801 ax = fig.add_axes([0, 0, 1, 1], aspect='auto',
1802 frameon=False, xticks=[], yticks=[])
1803 ax.imshow(im, aspect='auto', resample=True, interpolation=interpolation)
1804 fig.savefig(thumbfile, dpi=dpi)
1805 return fig