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

741 statements  

1""" 

2The image module supports basic image loading, rescaling and display 

3operations. 

4""" 

5 

6import math 

7import os 

8import logging 

9from pathlib import Path 

10import warnings 

11 

12import numpy as np 

13import PIL.Image 

14import PIL.PngImagePlugin 

15 

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) 

29 

30_log = logging.getLogger(__name__) 

31 

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} 

54 

55interpolations_names = set(_interpd_) 

56 

57 

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. 

62 

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. 

70 

71 renderer : `.RendererBase` 

72 

73 magnification : float, default: 1 

74 The additional magnification to apply for the renderer in use. 

75 

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 

86 

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]]])) 

97 

98 if len(parts) == 0: 

99 return np.empty((0, 0, 4), dtype=np.uint8), 0, 0 

100 

101 bbox = Bbox.union(bboxes) 

102 

103 output = np.zeros( 

104 (int(bbox.height), int(bbox.width), 4), dtype=np.uint8) 

105 

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) 

110 

111 return output, bbox.x0 / magnification, bbox.y0 / magnification 

112 

113 

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. 

119 

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) 

125 

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()) 

129 

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() 

137 

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[:] 

150 

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() 

159 

160 

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 

215 

216 

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 

229 

230 

231class _ImageBase(martist.Artist, cm.ScalarMappable): 

232 """ 

233 Base class for images. 

234 

235 interpolation and cmap default to their rc settings 

236 

237 cmap is a colors.Colormap instance 

238 norm is a colors.Normalize instance to map luminance to 0-1 

239 

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. 

243 

244 Additional kwargs are matplotlib.artist properties 

245 """ 

246 zorder = 0 

247 

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 

272 

273 self._imcache = None 

274 

275 self._internal_update(kwargs) 

276 

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__ 

283 

284 def __getstate__(self): 

285 # Save some space on the pickle by not saving the cache. 

286 return {**super().__getstate__(), "_imcache": None} 

287 

288 def get_size(self): 

289 """Return the size of the image as tuple (numrows, numcols).""" 

290 return self.get_shape()[:2] 

291 

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') 

298 

299 return self._A.shape 

300 

301 def set_alpha(self, alpha): 

302 """ 

303 Set the alpha value used for blending - not supported on all backends. 

304 

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 

314 

315 def _get_scalar_alpha(self): 

316 """ 

317 Get a scalar alpha value to be applied to the artist as a whole. 

318 

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 

327 

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) 

334 

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. 

342 

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`. 

347 

348 If *unsampled* is True, the image will not be scaled, but an 

349 appropriate affine transformation will be returned instead. 

350 

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`. 

355 

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.") 

373 

374 clipped_bbox = Bbox.intersection(out_bbox, clip_bbox) 

375 

376 if clipped_bbox is None: 

377 return None, 0, 0, None 

378 

379 out_width_base = clipped_bbox.width * magnification 

380 out_height_base = clipped_bbox.height * magnification 

381 

382 if out_width_base == 0 or out_height_base == 0: 

383 return None, 0, 0, None 

384 

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() 

392 

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()) 

400 

401 t = (t0 

402 + (Affine2D() 

403 .translate(-clipped_bbox.x0, -clipped_bbox.y0) 

404 .scale(magnification))) 

405 

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) 

420 

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 

429 

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 

446 

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. 

452 

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) 

475 

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) 

491 

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() 

497 

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) 

520 

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 

570 

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 

575 

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) 

582 

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 

587 

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]))] 

595 

596 t = Affine2D().translate( 

597 int(max(subset.xmin, 0)), int(max(subset.ymin, 0))) + t 

598 

599 return output, clipped_bbox.x0, clipped_bbox.y0, t 

600 

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*. 

605 

606 If *unsampled* is True, the image will not be scaled, but an 

607 appropriate affine transformation will be returned instead. 

608 

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') 

620 

621 def _check_unsampled_image(self): 

622 """ 

623 Return whether the image is better to be drawn unsampled. 

624 

625 The derived class needs to override it. 

626 """ 

627 return False 

628 

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 

659 

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, {} 

677 

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") 

683 

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 

716 

717 def set_data(self, A): 

718 """ 

719 Set the image array. 

720 

721 Note that this function does *not* update the normalization used. 

722 

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 

732 

733 def set_array(self, A): 

734 """ 

735 Retained for backwards compatibility - use set_data instead. 

736 

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) 

744 

745 def get_interpolation(self): 

746 """ 

747 Return the interpolation method the image uses when resizing. 

748 

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 

755 

756 def set_interpolation(self, s): 

757 """ 

758 Set the interpolation method the image uses when resizing. 

759 

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. 

764 

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 

775 

776 def get_interpolation_stage(self): 

777 """ 

778 Return when interpolation happens during the transform to RGBA. 

779 

780 One of 'data', 'rgba'. 

781 """ 

782 return self._interpolation_stage 

783 

784 def set_interpolation_stage(self, s): 

785 """ 

786 Set when interpolation happens during the transform to RGBA. 

787 

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 

798 

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) 

806 

807 def set_resample(self, v): 

808 """ 

809 Set whether image resampling is used. 

810 

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 

819 

820 def get_resample(self): 

821 """Return whether image resampling is used.""" 

822 return self._resample 

823 

824 def set_filternorm(self, filternorm): 

825 """ 

826 Set whether the resize filter normalizes the weights. 

827 

828 See help for `~.Axes.imshow`. 

829 

830 Parameters 

831 ---------- 

832 filternorm : bool 

833 """ 

834 self._filternorm = bool(filternorm) 

835 self.stale = True 

836 

837 def get_filternorm(self): 

838 """Return whether the resize filter normalizes the weights.""" 

839 return self._filternorm 

840 

841 def set_filterrad(self, filterrad): 

842 """ 

843 Set the resize filter radius only applicable to some 

844 interpolation schemes -- see help for imshow 

845 

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 

855 

856 def get_filterrad(self): 

857 """Return the filterrad setting.""" 

858 return self._filterrad 

859 

860 

861class AxesImage(_ImageBase): 

862 """ 

863 An image attached to an Axes. 

864 

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 """ 

908 

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 ): 

922 

923 self._extent = extent 

924 

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 ) 

937 

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()) 

942 

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) 

954 

955 def _check_unsampled_image(self): 

956 """Return whether the image would be better drawn unsampled.""" 

957 return self.get_interpolation() == "none" 

958 

959 def set_extent(self, extent, **kwargs): 

960 """ 

961 Set the image extent. 

962 

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. 

972 

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] 

995 

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 

1006 

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) 

1018 

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. 

1023 

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] 

1045 

1046 

1047class NonUniformImage(AxesImage): 

1048 

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) 

1062 

1063 def _check_unsampled_image(self): 

1064 """Return False. Do not use unsampled image.""" 

1065 return False 

1066 

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() 

1136 

1137 def set_data(self, x, y, A): 

1138 """ 

1139 Set the grid for the pixel centers, and the pixel values. 

1140 

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 

1160 

1161 def set_array(self, *args): 

1162 raise NotImplementedError('Method not supported') 

1163 

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) 

1175 

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] 

1180 

1181 @_api.rename_parameter("3.8", "s", "filternorm") 

1182 def set_filternorm(self, filternorm): 

1183 pass 

1184 

1185 @_api.rename_parameter("3.8", "s", "filterrad") 

1186 def set_filterrad(self, filterrad): 

1187 pass 

1188 

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) 

1193 

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) 

1198 

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] 

1208 

1209 

1210class PcolorImage(AxesImage): 

1211 """ 

1212 Make a pcolor-style plot with an irregular rectangular grid. 

1213 

1214 This uses a variation of the original irregular image code, 

1215 and it is used by pcolorfast for the corresponding grid type. 

1216 """ 

1217 

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: 

1239 

1240 - (M, N) `~numpy.ndarray` or masked array: values to be colormapped 

1241 - (M, N, 3): RGB array 

1242 - (M, N, 4): RGBA array 

1243 

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) 

1255 

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') 

1262 

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 

1271 

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 

1278 

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() 

1288 

1289 def _check_unsampled_image(self): 

1290 return False 

1291 

1292 def set_data(self, x, y, A): 

1293 """ 

1294 Set the grid for the rectangle boundaries, and the data values. 

1295 

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: 

1305 

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 

1329 

1330 def set_array(self, *args): 

1331 raise NotImplementedError('Method not supported') 

1332 

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] 

1342 

1343 

1344class FigureImage(_ImageBase): 

1345 """An image attached to a figure.""" 

1346 

1347 zorder = 0 

1348 

1349 _interpolation = 'nearest' 

1350 

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 

1363 

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 

1377 

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) 

1383 

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) 

1400 

1401 def set_data(self, A): 

1402 """Set the image array.""" 

1403 cm.ScalarMappable.set_array(self, A) 

1404 self.stale = True 

1405 

1406 

1407class BboxImage(_ImageBase): 

1408 """The Image class whose size is determined by the given bbox.""" 

1409 

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 

1424 

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 

1439 

1440 def get_window_extent(self, renderer=None): 

1441 if renderer is None: 

1442 renderer = self.get_figure()._get_renderer() 

1443 

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") 

1450 

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, {} 

1458 

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) 

1470 

1471 

1472def imread(fname, format=None): 

1473 """ 

1474 Read an image from a file into an array. 

1475 

1476 .. note:: 

1477 

1478 This function exists for historical reasons. It is recommended to 

1479 use `PIL.Image.open` instead for loading images. 

1480 

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. 

1486 

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`. 

1496 

1497 Returns 

1498 ------- 

1499 `numpy.array` 

1500 The image data. The returned array has shape 

1501 

1502 - (M, N) for grayscale images. 

1503 - (M, N, 3) for RGB images. 

1504 - (M, N, 4) for RGBA images. 

1505 

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 

1512 

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)) 

1548 

1549 

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. 

1554 

1555 RGB(A) images are passed through. Single channel images will be 

1556 colormapped according to *cmap* and *norm*. 

1557 

1558 .. note:: 

1559 

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. 

1562 

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) 

1677 

1678 

1679def pil_to_array(pilImage): 

1680 """ 

1681 Load a `PIL image`_ and return it as a numpy int array. 

1682 

1683 .. _PIL image: https://pillow.readthedocs.io/en/latest/reference/Image.html 

1684 

1685 Returns 

1686 ------- 

1687 numpy.array 

1688 

1689 The array shape depends on the image type: 

1690 

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 

1712 

1713 

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}") 

1742 

1743 

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*. 

1748 

1749 See :doc:`/gallery/misc/image_thumbnail_sgskip`. 

1750 

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. 

1757 

1758 .. _Pillow: https://python-pillow.org/ 

1759 

1760 thumbfile : str or file-like 

1761 The thumbnail filename. 

1762 

1763 scale : float, default: 0.1 

1764 The scale factor for the thumbnail. 

1765 

1766 interpolation : str, default: 'bilinear' 

1767 The interpolation scheme used in the resampling. See the 

1768 *interpolation* parameter of `~.Axes.imshow` for possible values. 

1769 

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. 

1776 

1777 Returns 

1778 ------- 

1779 `.Figure` 

1780 The figure instance containing the thumbnail. 

1781 """ 

1782 

1783 im = imread(infile) 

1784 rows, cols, depth = im.shape 

1785 

1786 # This doesn't really matter (it cancels in the end) but the API needs it. 

1787 dpi = 100 

1788 

1789 height = rows / dpi * scale 

1790 width = cols / dpi * scale 

1791 

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) 

1800 

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