Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

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

3operations. 

4""" 

5 

6from io import BytesIO 

7import math 

8import os 

9import logging 

10from numbers import Number 

11from pathlib import Path 

12import urllib.parse 

13 

14import numpy as np 

15 

16from matplotlib import rcParams 

17import matplotlib.artist as martist 

18from matplotlib.backend_bases import FigureCanvasBase 

19import matplotlib.colors as mcolors 

20import matplotlib.cm as cm 

21import matplotlib.cbook as cbook 

22 

23# For clarity, names from _image are given explicitly in this module: 

24import matplotlib._image as _image 

25 

26# For user convenience, the names from _image are also imported into 

27# the image namespace: 

28from matplotlib._image import * 

29 

30from matplotlib.transforms import (Affine2D, BboxBase, Bbox, BboxTransform, 

31 IdentityTransform, TransformedBbox) 

32 

33_log = logging.getLogger(__name__) 

34 

35# map interpolation strings to module constants 

36_interpd_ = { 

37 'antialiased': _image.NEAREST, # this will use nearest or Hanning... 

38 'none': _image.NEAREST, # fall back to nearest when not supported 

39 'nearest': _image.NEAREST, 

40 'bilinear': _image.BILINEAR, 

41 'bicubic': _image.BICUBIC, 

42 'spline16': _image.SPLINE16, 

43 'spline36': _image.SPLINE36, 

44 'hanning': _image.HANNING, 

45 'hamming': _image.HAMMING, 

46 'hermite': _image.HERMITE, 

47 'kaiser': _image.KAISER, 

48 'quadric': _image.QUADRIC, 

49 'catrom': _image.CATROM, 

50 'gaussian': _image.GAUSSIAN, 

51 'bessel': _image.BESSEL, 

52 'mitchell': _image.MITCHELL, 

53 'sinc': _image.SINC, 

54 'lanczos': _image.LANCZOS, 

55 'blackman': _image.BLACKMAN, 

56} 

57 

58interpolations_names = set(_interpd_) 

59 

60 

61def composite_images(images, renderer, magnification=1.0): 

62 """ 

63 Composite a number of RGBA images into one. The images are 

64 composited in the order in which they appear in the `images` list. 

65 

66 Parameters 

67 ---------- 

68 images : list of Images 

69 Each must have a `make_image` method. For each image, 

70 `can_composite` should return `True`, though this is not 

71 enforced by this function. Each image must have a purely 

72 affine transformation with no shear. 

73 

74 renderer : RendererBase instance 

75 

76 magnification : float 

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

78 

79 Returns 

80 ------- 

81 tuple : image, offset_x, offset_y 

82 Returns the tuple: 

83 

84 - image: A numpy array of the same type as the input images. 

85 

86 - offset_x, offset_y: The offset of the image (left, bottom) 

87 in the output figure. 

88 """ 

89 if len(images) == 0: 

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

91 

92 parts = [] 

93 bboxes = [] 

94 for image in images: 

95 data, x, y, trans = image.make_image(renderer, magnification) 

96 if data is not None: 

97 x *= magnification 

98 y *= magnification 

99 parts.append((data, x, y, image._get_scalar_alpha())) 

100 bboxes.append( 

101 Bbox([[x, y], [x + data.shape[1], y + data.shape[0]]])) 

102 

103 if len(parts) == 0: 

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

105 

106 bbox = Bbox.union(bboxes) 

107 

108 output = np.zeros( 

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

110 

111 for data, x, y, alpha in parts: 

112 trans = Affine2D().translate(x - bbox.x0, y - bbox.y0) 

113 _image.resample(data, output, trans, _image.NEAREST, 

114 resample=False, alpha=alpha) 

115 

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

117 

118 

119def _draw_list_compositing_images( 

120 renderer, parent, artists, suppress_composite=None): 

121 """ 

122 Draw a sorted list of artists, compositing images into a single 

123 image where possible. 

124 

125 For internal matplotlib use only: It is here to reduce duplication 

126 between `Figure.draw` and `Axes.draw`, but otherwise should not be 

127 generally useful. 

128 """ 

129 has_images = any(isinstance(x, _ImageBase) for x in artists) 

130 

131 # override the renderer default if suppressComposite is not None 

132 not_composite = (suppress_composite if suppress_composite is not None 

133 else renderer.option_image_nocomposite()) 

134 

135 if not_composite or not has_images: 

136 for a in artists: 

137 a.draw(renderer) 

138 else: 

139 # Composite any adjacent images together 

140 image_group = [] 

141 mag = renderer.get_image_magnification() 

142 

143 def flush_images(): 

144 if len(image_group) == 1: 

145 image_group[0].draw(renderer) 

146 elif len(image_group) > 1: 

147 data, l, b = composite_images(image_group, renderer, mag) 

148 if data.size != 0: 

149 gc = renderer.new_gc() 

150 gc.set_clip_rectangle(parent.bbox) 

151 gc.set_clip_path(parent.get_clip_path()) 

152 renderer.draw_image(gc, round(l), round(b), data) 

153 gc.restore() 

154 del image_group[:] 

155 

156 for a in artists: 

157 if isinstance(a, _ImageBase) and a.can_composite(): 

158 image_group.append(a) 

159 else: 

160 flush_images() 

161 a.draw(renderer) 

162 flush_images() 

163 

164 

165def _resample( 

166 image_obj, data, out_shape, transform, *, resample=None, alpha=1): 

167 """ 

168 Convenience wrapper around `._image.resample` to resample *data* to 

169 *out_shape* (with a third dimension if *data* is RGBA) that takes care of 

170 allocating the output array and fetching the relevant properties from the 

171 Image object *image_obj*. 

172 """ 

173 

174 # decide if we need to apply anti-aliasing if the data is upsampled: 

175 # compare the number of displayed pixels to the number of 

176 # the data pixels. 

177 interpolation = image_obj.get_interpolation() 

178 if interpolation == 'antialiased': 

179 # don't antialias if upsampling by an integer number or 

180 # if zooming in more than a factor of 3 

181 pos = np.array([[0, 0], [data.shape[1], data.shape[0]]]) 

182 disp = transform.transform(pos) 

183 dispx = np.abs(np.diff(disp[:, 0])) 

184 dispy = np.abs(np.diff(disp[:, 1])) 

185 if ((dispx > 3 * data.shape[1] or 

186 dispx == data.shape[1] or 

187 dispx == 2 * data.shape[1]) and 

188 (dispy > 3 * data.shape[0] or 

189 dispy == data.shape[0] or 

190 dispy == 2 * data.shape[0])): 

191 interpolation = 'nearest' 

192 else: 

193 interpolation = 'hanning' 

194 out = np.zeros(out_shape + data.shape[2:], data.dtype) # 2D->2D, 3D->3D. 

195 if resample is None: 

196 resample = image_obj.get_resample() 

197 _image.resample(data, out, transform, 

198 _interpd_[interpolation], 

199 resample, 

200 alpha, 

201 image_obj.get_filternorm(), 

202 image_obj.get_filterrad()) 

203 return out 

204 

205 

206def _rgb_to_rgba(A): 

207 """ 

208 Convert an RGB image to RGBA, as required by the image resample C++ 

209 extension. 

210 """ 

211 rgba = np.zeros((A.shape[0], A.shape[1], 4), dtype=A.dtype) 

212 rgba[:, :, :3] = A 

213 if rgba.dtype == np.uint8: 

214 rgba[:, :, 3] = 255 

215 else: 

216 rgba[:, :, 3] = 1.0 

217 return rgba 

218 

219 

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

221 """ 

222 Base class for images. 

223 

224 interpolation and cmap default to their rc settings 

225 

226 cmap is a colors.Colormap instance 

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

228 

229 extent is data axes (left, right, bottom, top) for making image plots 

230 registered with data plots. Default is to label the pixel 

231 centers with the zero-based row and column indices. 

232 

233 Additional kwargs are matplotlib.artist properties 

234 """ 

235 zorder = 0 

236 

237 def __init__(self, ax, 

238 cmap=None, 

239 norm=None, 

240 interpolation=None, 

241 origin=None, 

242 filternorm=True, 

243 filterrad=4.0, 

244 resample=False, 

245 **kwargs 

246 ): 

247 martist.Artist.__init__(self) 

248 cm.ScalarMappable.__init__(self, norm, cmap) 

249 self._mouseover = True 

250 if origin is None: 

251 origin = rcParams['image.origin'] 

252 self.origin = origin 

253 self.set_filternorm(filternorm) 

254 self.set_filterrad(filterrad) 

255 self.set_interpolation(interpolation) 

256 self.set_resample(resample) 

257 self.axes = ax 

258 

259 self._imcache = None 

260 

261 self.update(kwargs) 

262 

263 def __getstate__(self): 

264 state = super().__getstate__() 

265 # We can't pickle the C Image cached object. 

266 state['_imcache'] = None 

267 return state 

268 

269 def get_size(self): 

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

271 if self._A is None: 

272 raise RuntimeError('You must first set the image array') 

273 

274 return self._A.shape[:2] 

275 

276 def set_alpha(self, alpha): 

277 """ 

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

279 

280 Parameters 

281 ---------- 

282 alpha : float 

283 """ 

284 if alpha is not None and not isinstance(alpha, Number): 

285 alpha = np.asarray(alpha) 

286 if alpha.ndim != 2: 

287 raise TypeError('alpha must be a float, two-dimensional ' 

288 'array, or None') 

289 self._alpha = alpha 

290 self.pchanged() 

291 self.stale = True 

292 self._imcache = None 

293 

294 def _get_scalar_alpha(self): 

295 """ 

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

297 

298 If the alpha value is a matrix, the method returns 1.0 because pixels 

299 have individual alpha values (see `~._ImageBase._make_image` for 

300 details). If the alpha value is a scalar, the method returns said value 

301 to be applied to the artist as a whole because pixels do not have 

302 individual alpha values. 

303 """ 

304 return 1.0 if self._alpha is None or np.ndim(self._alpha) > 0 \ 

305 else self._alpha 

306 

307 def changed(self): 

308 """ 

309 Call this whenever the mappable is changed so observers can 

310 update state 

311 """ 

312 self._imcache = None 

313 self._rgbacache = None 

314 cm.ScalarMappable.changed(self) 

315 

316 def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, 

317 unsampled=False, round_to_pixel_border=True): 

318 """ 

319 Normalize, rescale, and colormap the image *A* from the given *in_bbox* 

320 (in data space), to the given *out_bbox* (in pixel space) clipped to 

321 the given *clip_bbox* (also in pixel space), and magnified by the 

322 *magnification* factor. 

323 

324 *A* may be a greyscale image (M, N) with a dtype of float32, float64, 

325 float128, uint16 or uint8, or an (M, N, 4) RGBA image with a dtype of 

326 float32, float64, float128, or uint8. 

327 

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

329 appropriate affine transformation will be returned instead. 

330 

331 If *round_to_pixel_border* is True, the output image size will be 

332 rounded to the nearest pixel boundary. This makes the images align 

333 correctly with the axes. It should not be used if exact scaling is 

334 needed, such as for `FigureImage`. 

335 

336 Returns 

337 ------- 

338 image : (M, N, 4) uint8 array 

339 The RGBA image, resampled unless *unsampled* is True. 

340 x, y : float 

341 The upper left corner where the image should be drawn, in pixel 

342 space. 

343 trans : Affine2D 

344 The affine transformation from image to pixel space. 

345 """ 

346 if A is None: 

347 raise RuntimeError('You must first set the image ' 

348 'array or the image attribute') 

349 if A.size == 0: 

350 raise RuntimeError("_make_image must get a non-empty image. " 

351 "Your Artist's draw method must filter before " 

352 "this method is called.") 

353 

354 clipped_bbox = Bbox.intersection(out_bbox, clip_bbox) 

355 

356 if clipped_bbox is None: 

357 return None, 0, 0, None 

358 

359 out_width_base = clipped_bbox.width * magnification 

360 out_height_base = clipped_bbox.height * magnification 

361 

362 if out_width_base == 0 or out_height_base == 0: 

363 return None, 0, 0, None 

364 

365 if self.origin == 'upper': 

366 # Flip the input image using a transform. This avoids the 

367 # problem with flipping the array, which results in a copy 

368 # when it is converted to contiguous in the C wrapper 

369 t0 = Affine2D().translate(0, -A.shape[0]).scale(1, -1) 

370 else: 

371 t0 = IdentityTransform() 

372 

373 t0 += ( 

374 Affine2D() 

375 .scale( 

376 in_bbox.width / A.shape[1], 

377 in_bbox.height / A.shape[0]) 

378 .translate(in_bbox.x0, in_bbox.y0) 

379 + self.get_transform()) 

380 

381 t = (t0 

382 + (Affine2D() 

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

384 .scale(magnification))) 

385 

386 # So that the image is aligned with the edge of the axes, we want to 

387 # round up the output width to the next integer. This also means 

388 # scaling the transform slightly to account for the extra subpixel. 

389 if (t.is_affine and round_to_pixel_border and 

390 (out_width_base % 1.0 != 0.0 or out_height_base % 1.0 != 0.0)): 

391 out_width = math.ceil(out_width_base) 

392 out_height = math.ceil(out_height_base) 

393 extra_width = (out_width - out_width_base) / out_width_base 

394 extra_height = (out_height - out_height_base) / out_height_base 

395 t += Affine2D().scale(1.0 + extra_width, 1.0 + extra_height) 

396 else: 

397 out_width = int(out_width_base) 

398 out_height = int(out_height_base) 

399 out_shape = (out_height, out_width) 

400 

401 if not unsampled: 

402 if not (A.ndim == 2 or A.ndim == 3 and A.shape[-1] in (3, 4)): 

403 raise ValueError(f"Invalid shape {A.shape} for image data") 

404 

405 if A.ndim == 2: 

406 # if we are a 2D array, then we are running through the 

407 # norm + colormap transformation. However, in general the 

408 # input data is not going to match the size on the screen so we 

409 # have to resample to the correct number of pixels 

410 

411 # TODO slice input array first 

412 inp_dtype = A.dtype 

413 a_min = A.min() 

414 a_max = A.max() 

415 # figure out the type we should scale to. For floats, 

416 # leave as is. For integers cast to an appropriate-sized 

417 # float. Small integers get smaller floats in an attempt 

418 # to keep the memory footprint reasonable. 

419 if a_min is np.ma.masked: 

420 # all masked, so values don't matter 

421 a_min, a_max = np.int32(0), np.int32(1) 

422 if inp_dtype.kind == 'f': 

423 scaled_dtype = A.dtype 

424 # Cast to float64 

425 if A.dtype not in (np.float32, np.float16): 

426 if A.dtype != np.float64: 

427 cbook._warn_external( 

428 f"Casting input data from '{A.dtype}' to " 

429 f"'float64' for imshow") 

430 scaled_dtype = np.float64 

431 else: 

432 # probably an integer of some type. 

433 da = a_max.astype(np.float64) - a_min.astype(np.float64) 

434 # give more breathing room if a big dynamic range 

435 scaled_dtype = np.float64 if da > 1e8 else np.float32 

436 

437 # scale the input data to [.1, .9]. The Agg 

438 # interpolators clip to [0, 1] internally, use a 

439 # smaller input scale to identify which of the 

440 # interpolated points need to be should be flagged as 

441 # over / under. 

442 # This may introduce numeric instabilities in very broadly 

443 # scaled data 

444 # Always copy, and don't allow array subtypes. 

445 A_scaled = np.array(A, dtype=scaled_dtype) 

446 # clip scaled data around norm if necessary. 

447 # This is necessary for big numbers at the edge of 

448 # float64's ability to represent changes. Applying 

449 # a norm first would be good, but ruins the interpolation 

450 # of over numbers. 

451 self.norm.autoscale_None(A) 

452 dv = np.float64(self.norm.vmax) - np.float64(self.norm.vmin) 

453 vmid = self.norm.vmin + dv / 2 

454 fact = 1e7 if scaled_dtype == np.float64 else 1e4 

455 newmin = vmid - dv * fact 

456 if newmin < a_min: 

457 newmin = None 

458 else: 

459 a_min = np.float64(newmin) 

460 newmax = vmid + dv * fact 

461 if newmax > a_max: 

462 newmax = None 

463 else: 

464 a_max = np.float64(newmax) 

465 if newmax is not None or newmin is not None: 

466 np.clip(A_scaled, newmin, newmax, out=A_scaled) 

467 

468 A_scaled -= a_min 

469 # a_min and a_max might be ndarray subclasses so use 

470 # item to avoid errors 

471 a_min = a_min.astype(scaled_dtype).item() 

472 a_max = a_max.astype(scaled_dtype).item() 

473 

474 if a_min != a_max: 

475 A_scaled /= ((a_max - a_min) / 0.8) 

476 A_scaled += 0.1 

477 # resample the input data to the correct resolution and shape 

478 A_resampled = _resample(self, A_scaled, out_shape, t) 

479 # done with A_scaled now, remove from namespace to be sure! 

480 del A_scaled 

481 # un-scale the resampled data to approximately the 

482 # original range things that interpolated to above / 

483 # below the original min/max will still be above / 

484 # below, but possibly clipped in the case of higher order 

485 # interpolation + drastically changing data. 

486 A_resampled -= 0.1 

487 if a_min != a_max: 

488 A_resampled *= ((a_max - a_min) / 0.8) 

489 A_resampled += a_min 

490 # if using NoNorm, cast back to the original datatype 

491 if isinstance(self.norm, mcolors.NoNorm): 

492 A_resampled = A_resampled.astype(A.dtype) 

493 

494 mask = (np.where(A.mask, np.float32(np.nan), np.float32(1)) 

495 if A.mask.shape == A.shape # nontrivial mask 

496 else np.ones_like(A, np.float32)) 

497 # we always have to interpolate the mask to account for 

498 # non-affine transformations 

499 out_alpha = _resample(self, mask, out_shape, t, resample=True) 

500 # done with the mask now, delete from namespace to be sure! 

501 del mask 

502 # Agg updates out_alpha in place. If the pixel has no image 

503 # data it will not be updated (and still be 0 as we initialized 

504 # it), if input data that would go into that output pixel than 

505 # it will be `nan`, if all the input data for a pixel is good 

506 # it will be 1, and if there is _some_ good data in that output 

507 # pixel it will be between [0, 1] (such as a rotated image). 

508 out_mask = np.isnan(out_alpha) 

509 out_alpha[out_mask] = 1 

510 # Apply the pixel-by-pixel alpha values if present 

511 alpha = self.get_alpha() 

512 if alpha is not None and np.ndim(alpha) > 0: 

513 out_alpha *= _resample(self, alpha, out_shape, 

514 t, resample=True) 

515 # mask and run through the norm 

516 output = self.norm(np.ma.masked_array(A_resampled, out_mask)) 

517 else: 

518 if A.shape[2] == 3: 

519 A = _rgb_to_rgba(A) 

520 alpha = self._get_scalar_alpha() 

521 output_alpha = _resample( # resample alpha channel 

522 self, A[..., 3], out_shape, t, alpha=alpha) 

523 output = _resample( # resample rgb channels 

524 self, _rgb_to_rgba(A[..., :3]), out_shape, t, alpha=alpha) 

525 output[..., 3] = output_alpha # recombine rgb and alpha 

526 

527 # at this point output is either a 2D array of normed data 

528 # (of int or float) 

529 # or an RGBA array of re-sampled input 

530 output = self.to_rgba(output, bytes=True, norm=False) 

531 # output is now a correctly sized RGBA array of uint8 

532 

533 # Apply alpha *after* if the input was greyscale without a mask 

534 if A.ndim == 2: 

535 alpha = self._get_scalar_alpha() 

536 alpha_channel = output[:, :, 3] 

537 alpha_channel[:] = np.asarray( 

538 np.asarray(alpha_channel, np.float32) * out_alpha * alpha, 

539 np.uint8) 

540 

541 else: 

542 if self._imcache is None: 

543 self._imcache = self.to_rgba(A, bytes=True, norm=(A.ndim == 2)) 

544 output = self._imcache 

545 

546 # Subset the input image to only the part that will be 

547 # displayed 

548 subset = TransformedBbox(clip_bbox, t0.inverted()).frozen() 

549 output = output[ 

550 int(max(subset.ymin, 0)): 

551 int(min(subset.ymax + 1, output.shape[0])), 

552 int(max(subset.xmin, 0)): 

553 int(min(subset.xmax + 1, output.shape[1]))] 

554 

555 t = Affine2D().translate( 

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

557 

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

559 

560 def make_image(self, renderer, magnification=1.0, unsampled=False): 

561 """ 

562 Normalize, rescale, and colormap this image's data for rendering using 

563 *renderer*, with the given *magnification*. 

564 

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

566 appropriate affine transformation will be returned instead. 

567 

568 Returns 

569 ------- 

570 image : (M, N, 4) uint8 array 

571 The RGBA image, resampled unless *unsampled* is True. 

572 x, y : float 

573 The upper left corner where the image should be drawn, in pixel 

574 space. 

575 trans : Affine2D 

576 The affine transformation from image to pixel space. 

577 """ 

578 raise NotImplementedError('The make_image method must be overridden') 

579 

580 def _draw_unsampled_image(self, renderer, gc): 

581 """ 

582 Draw unsampled image. The renderer should support a draw_image method 

583 with scale parameter. 

584 """ 

585 im, l, b, trans = self.make_image(renderer, unsampled=True) 

586 

587 if im is None: 

588 return 

589 

590 trans = Affine2D().scale(im.shape[1], im.shape[0]) + trans 

591 

592 renderer.draw_image(gc, l, b, im, trans) 

593 

594 def _check_unsampled_image(self, renderer): 

595 """ 

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

597 

598 The derived class needs to override it. 

599 """ 

600 return False 

601 

602 @martist.allow_rasterization 

603 def draw(self, renderer, *args, **kwargs): 

604 # if not visible, declare victory and return 

605 if not self.get_visible(): 

606 self.stale = False 

607 return 

608 

609 # for empty images, there is nothing to draw! 

610 if self.get_array().size == 0: 

611 self.stale = False 

612 return 

613 

614 # actually render the image. 

615 gc = renderer.new_gc() 

616 self._set_gc_clip(gc) 

617 gc.set_alpha(self._get_scalar_alpha()) 

618 gc.set_url(self.get_url()) 

619 gc.set_gid(self.get_gid()) 

620 

621 if (self._check_unsampled_image(renderer) and 

622 self.get_transform().is_affine): 

623 self._draw_unsampled_image(renderer, gc) 

624 else: 

625 im, l, b, trans = self.make_image( 

626 renderer, renderer.get_image_magnification()) 

627 if im is not None: 

628 renderer.draw_image(gc, l, b, im) 

629 gc.restore() 

630 self.stale = False 

631 

632 def contains(self, mouseevent): 

633 """ 

634 Test whether the mouse event occurred within the image. 

635 """ 

636 inside, info = self._default_contains(mouseevent) 

637 if inside is not None: 

638 return inside, info 

639 # 1) This doesn't work for figimage; but figimage also needs a fix 

640 # below (as the check cannot use x/ydata and extents). 

641 # 2) As long as the check below uses x/ydata, we need to test axes 

642 # identity instead of `self.axes.contains(event)` because even if 

643 # axes overlap, x/ydata is only valid for event.inaxes anyways. 

644 if self.axes is not mouseevent.inaxes: 

645 return False, {} 

646 # TODO: make sure this is consistent with patch and patch 

647 # collection on nonlinear transformed coordinates. 

648 # TODO: consider returning image coordinates (shouldn't 

649 # be too difficult given that the image is rectilinear 

650 x, y = mouseevent.xdata, mouseevent.ydata 

651 xmin, xmax, ymin, ymax = self.get_extent() 

652 if xmin > xmax: 

653 xmin, xmax = xmax, xmin 

654 if ymin > ymax: 

655 ymin, ymax = ymax, ymin 

656 

657 if x is not None and y is not None: 

658 inside = (xmin <= x <= xmax) and (ymin <= y <= ymax) 

659 else: 

660 inside = False 

661 

662 return inside, {} 

663 

664 def write_png(self, fname): 

665 """Write the image to png file with fname""" 

666 from matplotlib import _png 

667 im = self.to_rgba(self._A[::-1] if self.origin == 'lower' else self._A, 

668 bytes=True, norm=True) 

669 with cbook.open_file_cm(fname, "wb") as file: 

670 _png.write_png(im, file) 

671 

672 def set_data(self, A): 

673 """ 

674 Set the image array. 

675 

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

677 

678 Parameters 

679 ---------- 

680 A : array-like or `PIL.Image.Image` 

681 """ 

682 try: 

683 from PIL import Image 

684 except ImportError: 

685 pass 

686 else: 

687 if isinstance(A, Image.Image): 

688 A = pil_to_array(A) # Needed e.g. to apply png palette. 

689 self._A = cbook.safe_masked_invalid(A, copy=True) 

690 

691 if (self._A.dtype != np.uint8 and 

692 not np.can_cast(self._A.dtype, float, "same_kind")): 

693 raise TypeError("Image data of dtype {} cannot be converted to " 

694 "float".format(self._A.dtype)) 

695 

696 if not (self._A.ndim == 2 

697 or self._A.ndim == 3 and self._A.shape[-1] in [3, 4]): 

698 raise TypeError("Invalid shape {} for image data" 

699 .format(self._A.shape)) 

700 

701 if self._A.ndim == 3: 

702 # If the input data has values outside the valid range (after 

703 # normalisation), we issue a warning and then clip X to the bounds 

704 # - otherwise casting wraps extreme values, hiding outliers and 

705 # making reliable interpretation impossible. 

706 high = 255 if np.issubdtype(self._A.dtype, np.integer) else 1 

707 if self._A.min() < 0 or high < self._A.max(): 

708 _log.warning( 

709 'Clipping input data to the valid range for imshow with ' 

710 'RGB data ([0..1] for floats or [0..255] for integers).' 

711 ) 

712 self._A = np.clip(self._A, 0, high) 

713 # Cast unsupported integer types to uint8 

714 if self._A.dtype != np.uint8 and np.issubdtype(self._A.dtype, 

715 np.integer): 

716 self._A = self._A.astype(np.uint8) 

717 

718 self._imcache = None 

719 self._rgbacache = None 

720 self.stale = True 

721 

722 def set_array(self, A): 

723 """ 

724 Retained for backwards compatibility - use set_data instead. 

725 

726 Parameters 

727 ---------- 

728 A : array-like 

729 """ 

730 # This also needs to be here to override the inherited 

731 # cm.ScalarMappable.set_array method so it is not invoked by mistake. 

732 self.set_data(A) 

733 

734 def get_interpolation(self): 

735 """ 

736 Return the interpolation method the image uses when resizing. 

737 

738 One of 'antialiased', 'nearest', 'bilinear', 'bicubic', 'spline16', 

739 'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 

740 'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos', 

741 or 'none'. 

742 

743 """ 

744 return self._interpolation 

745 

746 def set_interpolation(self, s): 

747 """ 

748 Set the interpolation method the image uses when resizing. 

749 

750 if None, use a value from rc setting. If 'none', the image is 

751 shown as is without interpolating. 'none' is only supported in 

752 agg, ps and pdf backends and will fall back to 'nearest' mode 

753 for other backends. 

754 

755 Parameters 

756 ---------- 

757 s : {'antialiased', 'nearest', 'bilinear', 'bicubic', 'spline16', 

758'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'catrom', \ 

759'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos', 'none'} 

760 

761 """ 

762 if s is None: 

763 s = rcParams['image.interpolation'] 

764 s = s.lower() 

765 cbook._check_in_list(_interpd_, interpolation=s) 

766 self._interpolation = s 

767 self.stale = True 

768 

769 def can_composite(self): 

770 """Return whether the image can be composited with its neighbors.""" 

771 trans = self.get_transform() 

772 return ( 

773 self._interpolation != 'none' and 

774 trans.is_affine and 

775 trans.is_separable) 

776 

777 def set_resample(self, v): 

778 """ 

779 Set whether image resampling is used. 

780 

781 Parameters 

782 ---------- 

783 v : bool or None 

784 If None, use :rc:`image.resample` = True. 

785 """ 

786 if v is None: 

787 v = rcParams['image.resample'] 

788 self._resample = v 

789 self.stale = True 

790 

791 def get_resample(self): 

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

793 return self._resample 

794 

795 def set_filternorm(self, filternorm): 

796 """ 

797 Set whether the resize filter normalizes the weights. 

798 

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

800 

801 Parameters 

802 ---------- 

803 filternorm : bool 

804 """ 

805 self._filternorm = bool(filternorm) 

806 self.stale = True 

807 

808 def get_filternorm(self): 

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

810 return self._filternorm 

811 

812 def set_filterrad(self, filterrad): 

813 """ 

814 Set the resize filter radius only applicable to some 

815 interpolation schemes -- see help for imshow 

816 

817 Parameters 

818 ---------- 

819 filterrad : positive float 

820 """ 

821 r = float(filterrad) 

822 if r <= 0: 

823 raise ValueError("The filter radius must be a positive number") 

824 self._filterrad = r 

825 self.stale = True 

826 

827 def get_filterrad(self): 

828 """Return the filterrad setting.""" 

829 return self._filterrad 

830 

831 

832class AxesImage(_ImageBase): 

833 """ 

834 Parameters 

835 ---------- 

836 ax : `~.axes.Axes` 

837 The axes the image will belong to. 

838 cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap` 

839 The Colormap instance or registered colormap name used to map scalar 

840 data to colors. 

841 norm : `~matplotlib.colors.Normalize` 

842 Maps luminance to 0-1. 

843 interpolation : str, default: :rc:`image.interpolation` 

844 Supported values are 'none', 'antialiased', 'nearest', 'bilinear', 

845 'bicubic', 'spline16', 'spline36', 'hanning', 'hamming', 'hermite', 

846 'kaiser', 'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell', 

847 'sinc', 'lanczos'. 

848 origin : {'upper', 'lower'}, default: :rc:`image.origin` 

849 Place the [0, 0] index of the array in the upper left or lower left 

850 corner of the axes. The convention 'upper' is typically used for 

851 matrices and images. 

852 extent : tuple, optional 

853 The data axes (left, right, bottom, top) for making image plots 

854 registered with data plots. Default is to label the pixel 

855 centers with the zero-based row and column indices. 

856 filternorm : bool, default: True 

857 A parameter for the antigrain image resize filter 

858 (see the antigrain documentation). 

859 If filternorm is set, the filter normalizes integer values and corrects 

860 the rounding errors. It doesn't do anything with the source floating 

861 point values, it corrects only integers according to the rule of 1.0 

862 which means that any sum of pixel weights must be equal to 1.0. So, 

863 the filter function must produce a graph of the proper shape. 

864 filterrad : float > 0, default: 4 

865 The filter radius for filters that have a radius parameter, i.e. when 

866 interpolation is one of: 'sinc', 'lanczos' or 'blackman'. 

867 resample : bool, default: False 

868 When True, use a full resampling method. When False, only resample when 

869 the output image is larger than the input image. 

870 **kwargs : `.Artist` properties 

871 

872 """ 

873 def __str__(self): 

874 return "AxesImage(%g,%g;%gx%g)" % tuple(self.axes.bbox.bounds) 

875 

876 def __init__(self, ax, 

877 cmap=None, 

878 norm=None, 

879 interpolation=None, 

880 origin=None, 

881 extent=None, 

882 filternorm=1, 

883 filterrad=4.0, 

884 resample=False, 

885 **kwargs 

886 ): 

887 

888 self._extent = extent 

889 

890 super().__init__( 

891 ax, 

892 cmap=cmap, 

893 norm=norm, 

894 interpolation=interpolation, 

895 origin=origin, 

896 filternorm=filternorm, 

897 filterrad=filterrad, 

898 resample=resample, 

899 **kwargs 

900 ) 

901 

902 def get_window_extent(self, renderer=None): 

903 x0, x1, y0, y1 = self._extent 

904 bbox = Bbox.from_extents([x0, y0, x1, y1]) 

905 return bbox.transformed(self.axes.transData) 

906 

907 def make_image(self, renderer, magnification=1.0, unsampled=False): 

908 # docstring inherited 

909 trans = self.get_transform() 

910 # image is created in the canvas coordinate. 

911 x1, x2, y1, y2 = self.get_extent() 

912 bbox = Bbox(np.array([[x1, y1], [x2, y2]])) 

913 transformed_bbox = TransformedBbox(bbox, trans) 

914 return self._make_image( 

915 self._A, bbox, transformed_bbox, 

916 self.get_clip_box() or self.axes.bbox, 

917 magnification, unsampled=unsampled) 

918 

919 def _check_unsampled_image(self, renderer): 

920 """ 

921 Return whether the image would be better drawn unsampled. 

922 """ 

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

924 and renderer.option_scale_image()) 

925 

926 def set_extent(self, extent): 

927 """ 

928 Set the image extent. 

929 

930 Parameters 

931 ---------- 

932 extent : 4-tuple of float 

933 The position and size of the image as tuple 

934 ``(left, right, bottom, top)`` in data coordinates. 

935 

936 Notes 

937 ----- 

938 This updates ``ax.dataLim``, and, if autoscaling, sets ``ax.viewLim`` 

939 to tightly fit the image, regardless of ``dataLim``. Autoscaling 

940 state is not changed, so following this with ``ax.autoscale_view()`` 

941 will redo the autoscaling in accord with ``dataLim``. 

942 """ 

943 self._extent = xmin, xmax, ymin, ymax = extent 

944 corners = (xmin, ymin), (xmax, ymax) 

945 self.axes.update_datalim(corners) 

946 self.sticky_edges.x[:] = [xmin, xmax] 

947 self.sticky_edges.y[:] = [ymin, ymax] 

948 if self.axes._autoscaleXon: 

949 self.axes.set_xlim((xmin, xmax), auto=None) 

950 if self.axes._autoscaleYon: 

951 self.axes.set_ylim((ymin, ymax), auto=None) 

952 self.stale = True 

953 

954 def get_extent(self): 

955 """Return the image extent as tuple (left, right, bottom, top).""" 

956 if self._extent is not None: 

957 return self._extent 

958 else: 

959 sz = self.get_size() 

960 numrows, numcols = sz 

961 if self.origin == 'upper': 

962 return (-0.5, numcols-0.5, numrows-0.5, -0.5) 

963 else: 

964 return (-0.5, numcols-0.5, -0.5, numrows-0.5) 

965 

966 def get_cursor_data(self, event): 

967 """ 

968 Return the image value at the event position or *None* if the event is 

969 outside the image. 

970 

971 See Also 

972 -------- 

973 matplotlib.artist.Artist.get_cursor_data 

974 """ 

975 xmin, xmax, ymin, ymax = self.get_extent() 

976 if self.origin == 'upper': 

977 ymin, ymax = ymax, ymin 

978 arr = self.get_array() 

979 data_extent = Bbox([[ymin, xmin], [ymax, xmax]]) 

980 array_extent = Bbox([[0, 0], arr.shape[:2]]) 

981 trans = BboxTransform(boxin=data_extent, boxout=array_extent) 

982 point = trans.transform([event.ydata, event.xdata]) 

983 if any(np.isnan(point)): 

984 return None 

985 i, j = point.astype(int) 

986 # Clip the coordinates at array bounds 

987 if not (0 <= i < arr.shape[0]) or not (0 <= j < arr.shape[1]): 

988 return None 

989 else: 

990 return arr[i, j] 

991 

992 def format_cursor_data(self, data): 

993 if np.ndim(data) == 0 and self.colorbar: 

994 return ( 

995 "[" 

996 + cbook.strip_math( 

997 self.colorbar.formatter.format_data_short(data)).strip() 

998 + "]") 

999 else: 

1000 return super().format_cursor_data(data) 

1001 

1002 

1003class NonUniformImage(AxesImage): 

1004 def __init__(self, ax, *, interpolation='nearest', **kwargs): 

1005 """ 

1006 Parameters 

1007 ---------- 

1008 interpolation : {'nearest', 'bilinear'} 

1009 

1010 **kwargs 

1011 All other keyword arguments are identical to those of `.AxesImage`. 

1012 """ 

1013 super().__init__(ax, **kwargs) 

1014 self.set_interpolation(interpolation) 

1015 

1016 def _check_unsampled_image(self, renderer): 

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

1018 return False 

1019 

1020 def make_image(self, renderer, magnification=1.0, unsampled=False): 

1021 # docstring inherited 

1022 if self._A is None: 

1023 raise RuntimeError('You must first set the image array') 

1024 if unsampled: 

1025 raise ValueError('unsampled not supported on NonUniformImage') 

1026 A = self._A 

1027 if A.ndim == 2: 

1028 if A.dtype != np.uint8: 

1029 A = self.to_rgba(A, bytes=True) 

1030 self.is_grayscale = self.cmap.is_gray() 

1031 else: 

1032 A = np.repeat(A[:, :, np.newaxis], 4, 2) 

1033 A[:, :, 3] = 255 

1034 self.is_grayscale = True 

1035 else: 

1036 if A.dtype != np.uint8: 

1037 A = (255*A).astype(np.uint8) 

1038 if A.shape[2] == 3: 

1039 B = np.zeros(tuple([*A.shape[0:2], 4]), np.uint8) 

1040 B[:, :, 0:3] = A 

1041 B[:, :, 3] = 255 

1042 A = B 

1043 self.is_grayscale = False 

1044 x0, y0, v_width, v_height = self.axes.viewLim.bounds 

1045 l, b, r, t = self.axes.bbox.extents 

1046 width = (round(r) + 0.5) - (round(l) - 0.5) 

1047 height = (round(t) + 0.5) - (round(b) - 0.5) 

1048 width *= magnification 

1049 height *= magnification 

1050 im = _image.pcolor(self._Ax, self._Ay, A, 

1051 int(height), int(width), 

1052 (x0, x0+v_width, y0, y0+v_height), 

1053 _interpd_[self._interpolation]) 

1054 return im, l, b, IdentityTransform() 

1055 

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

1057 """ 

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

1059 

1060 Parameters 

1061 ---------- 

1062 x, y : 1D array-likes 

1063 Monotonic arrays of shapes (N,) and (M,), respectively, specifying 

1064 pixel centers. 

1065 A : array-like 

1066 (M, N) ndarray or masked array of values to be colormapped, or 

1067 (M, N, 3) RGB array, or (M, N, 4) RGBA array. 

1068 """ 

1069 x = np.array(x, np.float32) 

1070 y = np.array(y, np.float32) 

1071 A = cbook.safe_masked_invalid(A, copy=True) 

1072 if not (x.ndim == y.ndim == 1 and A.shape[0:2] == y.shape + x.shape): 

1073 raise TypeError("Axes don't match array shape") 

1074 if A.ndim not in [2, 3]: 

1075 raise TypeError("Can only plot 2D or 3D data") 

1076 if A.ndim == 3 and A.shape[2] not in [1, 3, 4]: 

1077 raise TypeError("3D arrays must have three (RGB) " 

1078 "or four (RGBA) color components") 

1079 if A.ndim == 3 and A.shape[2] == 1: 

1080 A.shape = A.shape[0:2] 

1081 self._A = A 

1082 self._Ax = x 

1083 self._Ay = y 

1084 self._imcache = None 

1085 

1086 self.stale = True 

1087 

1088 def set_array(self, *args): 

1089 raise NotImplementedError('Method not supported') 

1090 

1091 def set_interpolation(self, s): 

1092 """ 

1093 Parameters 

1094 ---------- 

1095 s : str, None 

1096 Either 'nearest', 'bilinear', or ``None``. 

1097 """ 

1098 if s is not None and s not in ('nearest', 'bilinear'): 

1099 raise NotImplementedError('Only nearest neighbor and ' 

1100 'bilinear interpolations are supported') 

1101 AxesImage.set_interpolation(self, s) 

1102 

1103 def get_extent(self): 

1104 if self._A is None: 

1105 raise RuntimeError('Must set data first') 

1106 return self._Ax[0], self._Ax[-1], self._Ay[0], self._Ay[-1] 

1107 

1108 def set_filternorm(self, s): 

1109 pass 

1110 

1111 def set_filterrad(self, s): 

1112 pass 

1113 

1114 def set_norm(self, norm): 

1115 if self._A is not None: 

1116 raise RuntimeError('Cannot change colors after loading data') 

1117 super().set_norm(norm) 

1118 

1119 def set_cmap(self, cmap): 

1120 if self._A is not None: 

1121 raise RuntimeError('Cannot change colors after loading data') 

1122 super().set_cmap(cmap) 

1123 

1124 

1125class PcolorImage(AxesImage): 

1126 """ 

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

1128 

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

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

1131 """ 

1132 def __init__(self, ax, 

1133 x=None, 

1134 y=None, 

1135 A=None, 

1136 cmap=None, 

1137 norm=None, 

1138 **kwargs 

1139 ): 

1140 """ 

1141 cmap defaults to its rc setting 

1142 

1143 cmap is a colors.Colormap instance 

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

1145 

1146 Additional kwargs are matplotlib.artist properties 

1147 """ 

1148 super().__init__(ax, norm=norm, cmap=cmap) 

1149 self.update(kwargs) 

1150 if A is not None: 

1151 self.set_data(x, y, A) 

1152 

1153 def make_image(self, renderer, magnification=1.0, unsampled=False): 

1154 # docstring inherited 

1155 if self._A is None: 

1156 raise RuntimeError('You must first set the image array') 

1157 if unsampled: 

1158 raise ValueError('unsampled not supported on PColorImage') 

1159 fc = self.axes.patch.get_facecolor() 

1160 bg = mcolors.to_rgba(fc, 0) 

1161 bg = (np.array(bg)*255).astype(np.uint8) 

1162 l, b, r, t = self.axes.bbox.extents 

1163 width = (round(r) + 0.5) - (round(l) - 0.5) 

1164 height = (round(t) + 0.5) - (round(b) - 0.5) 

1165 # The extra cast-to-int is only needed for python2 

1166 width = int(round(width * magnification)) 

1167 height = int(round(height * magnification)) 

1168 if self._rgbacache is None: 

1169 A = self.to_rgba(self._A, bytes=True) 

1170 self._rgbacache = A 

1171 if self._A.ndim == 2: 

1172 self.is_grayscale = self.cmap.is_gray() 

1173 else: 

1174 A = self._rgbacache 

1175 vl = self.axes.viewLim 

1176 im = _image.pcolor2(self._Ax, self._Ay, A, 

1177 height, 

1178 width, 

1179 (vl.x0, vl.x1, vl.y0, vl.y1), 

1180 bg) 

1181 return im, l, b, IdentityTransform() 

1182 

1183 def _check_unsampled_image(self, renderer): 

1184 return False 

1185 

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

1187 """ 

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

1189 

1190 Parameters 

1191 ---------- 

1192 x, y : 1D array-likes or None 

1193 Monotonic arrays of shapes (N + 1,) and (M + 1,), respectively, 

1194 specifying rectangle boundaries. If None, will default to 

1195 ``range(N + 1)`` and ``range(M + 1)``, respectively. 

1196 A : array-like 

1197 (M, N) ndarray or masked array of values to be colormapped, or 

1198 (M, N, 3) RGB array, or (M, N, 4) RGBA array. 

1199 """ 

1200 A = cbook.safe_masked_invalid(A, copy=True) 

1201 if x is None: 

1202 x = np.arange(0, A.shape[1]+1, dtype=np.float64) 

1203 else: 

1204 x = np.array(x, np.float64).ravel() 

1205 if y is None: 

1206 y = np.arange(0, A.shape[0]+1, dtype=np.float64) 

1207 else: 

1208 y = np.array(y, np.float64).ravel() 

1209 

1210 if A.shape[:2] != (y.size-1, x.size-1): 

1211 raise ValueError( 

1212 "Axes don't match array shape. Got %s, expected %s." % 

1213 (A.shape[:2], (y.size - 1, x.size - 1))) 

1214 if A.ndim not in [2, 3]: 

1215 raise ValueError("A must be 2D or 3D") 

1216 if A.ndim == 3 and A.shape[2] == 1: 

1217 A.shape = A.shape[:2] 

1218 self.is_grayscale = False 

1219 if A.ndim == 3: 

1220 if A.shape[2] in [3, 4]: 

1221 if ((A[:, :, 0] == A[:, :, 1]).all() and 

1222 (A[:, :, 0] == A[:, :, 2]).all()): 

1223 self.is_grayscale = True 

1224 else: 

1225 raise ValueError("3D arrays must have RGB or RGBA as last dim") 

1226 

1227 # For efficient cursor readout, ensure x and y are increasing. 

1228 if x[-1] < x[0]: 

1229 x = x[::-1] 

1230 A = A[:, ::-1] 

1231 if y[-1] < y[0]: 

1232 y = y[::-1] 

1233 A = A[::-1] 

1234 

1235 self._A = A 

1236 self._Ax = x 

1237 self._Ay = y 

1238 self._rgbacache = None 

1239 self.stale = True 

1240 

1241 def set_array(self, *args): 

1242 raise NotImplementedError('Method not supported') 

1243 

1244 def get_cursor_data(self, event): 

1245 # docstring inherited 

1246 x, y = event.xdata, event.ydata 

1247 if (x < self._Ax[0] or x > self._Ax[-1] or 

1248 y < self._Ay[0] or y > self._Ay[-1]): 

1249 return None 

1250 j = np.searchsorted(self._Ax, x) - 1 

1251 i = np.searchsorted(self._Ay, y) - 1 

1252 try: 

1253 return self._A[i, j] 

1254 except IndexError: 

1255 return None 

1256 

1257 

1258class FigureImage(_ImageBase): 

1259 zorder = 0 

1260 

1261 _interpolation = 'nearest' 

1262 

1263 def __init__(self, fig, 

1264 cmap=None, 

1265 norm=None, 

1266 offsetx=0, 

1267 offsety=0, 

1268 origin=None, 

1269 **kwargs 

1270 ): 

1271 """ 

1272 cmap is a colors.Colormap instance 

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

1274 

1275 kwargs are an optional list of Artist keyword args 

1276 """ 

1277 super().__init__( 

1278 None, 

1279 norm=norm, 

1280 cmap=cmap, 

1281 origin=origin 

1282 ) 

1283 self.figure = fig 

1284 self.ox = offsetx 

1285 self.oy = offsety 

1286 self.update(kwargs) 

1287 self.magnification = 1.0 

1288 

1289 def get_extent(self): 

1290 """Return the image extent as tuple (left, right, bottom, top).""" 

1291 numrows, numcols = self.get_size() 

1292 return (-0.5 + self.ox, numcols-0.5 + self.ox, 

1293 -0.5 + self.oy, numrows-0.5 + self.oy) 

1294 

1295 def make_image(self, renderer, magnification=1.0, unsampled=False): 

1296 # docstring inherited 

1297 fac = renderer.dpi/self.figure.dpi 

1298 # fac here is to account for pdf, eps, svg backends where 

1299 # figure.dpi is set to 72. This means we need to scale the 

1300 # image (using magnification) and offset it appropriately. 

1301 bbox = Bbox([[self.ox/fac, self.oy/fac], 

1302 [(self.ox/fac + self._A.shape[1]), 

1303 (self.oy/fac + self._A.shape[0])]]) 

1304 width, height = self.figure.get_size_inches() 

1305 width *= renderer.dpi 

1306 height *= renderer.dpi 

1307 clip = Bbox([[0, 0], [width, height]]) 

1308 return self._make_image( 

1309 self._A, bbox, bbox, clip, magnification=magnification / fac, 

1310 unsampled=unsampled, round_to_pixel_border=False) 

1311 

1312 def set_data(self, A): 

1313 """Set the image array.""" 

1314 cm.ScalarMappable.set_array(self, 

1315 cbook.safe_masked_invalid(A, copy=True)) 

1316 self.stale = True 

1317 

1318 

1319class BboxImage(_ImageBase): 

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

1321 

1322 @cbook._delete_parameter("3.1", "interp_at_native") 

1323 def __init__(self, bbox, 

1324 cmap=None, 

1325 norm=None, 

1326 interpolation=None, 

1327 origin=None, 

1328 filternorm=1, 

1329 filterrad=4.0, 

1330 resample=False, 

1331 interp_at_native=True, 

1332 **kwargs 

1333 ): 

1334 """ 

1335 cmap is a colors.Colormap instance 

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

1337 

1338 kwargs are an optional list of Artist keyword args 

1339 """ 

1340 super().__init__( 

1341 None, 

1342 cmap=cmap, 

1343 norm=norm, 

1344 interpolation=interpolation, 

1345 origin=origin, 

1346 filternorm=filternorm, 

1347 filterrad=filterrad, 

1348 resample=resample, 

1349 **kwargs 

1350 ) 

1351 

1352 self.bbox = bbox 

1353 self._interp_at_native = interp_at_native 

1354 self._transform = IdentityTransform() 

1355 

1356 @cbook.deprecated("3.1") 

1357 @property 

1358 def interp_at_native(self): 

1359 return self._interp_at_native 

1360 

1361 def get_transform(self): 

1362 return self._transform 

1363 

1364 def get_window_extent(self, renderer=None): 

1365 if renderer is None: 

1366 renderer = self.get_figure()._cachedRenderer 

1367 

1368 if isinstance(self.bbox, BboxBase): 

1369 return self.bbox 

1370 elif callable(self.bbox): 

1371 return self.bbox(renderer) 

1372 else: 

1373 raise ValueError("unknown type of bbox") 

1374 

1375 def contains(self, mouseevent): 

1376 """Test whether the mouse event occurred within the image.""" 

1377 inside, info = self._default_contains(mouseevent) 

1378 if inside is not None: 

1379 return inside, info 

1380 

1381 if not self.get_visible(): # or self.get_figure()._renderer is None: 

1382 return False, {} 

1383 

1384 x, y = mouseevent.x, mouseevent.y 

1385 inside = self.get_window_extent().contains(x, y) 

1386 

1387 return inside, {} 

1388 

1389 def make_image(self, renderer, magnification=1.0, unsampled=False): 

1390 # docstring inherited 

1391 width, height = renderer.get_canvas_width_height() 

1392 bbox_in = self.get_window_extent(renderer).frozen() 

1393 bbox_in._points /= [width, height] 

1394 bbox_out = self.get_window_extent(renderer) 

1395 clip = Bbox([[0, 0], [width, height]]) 

1396 self._transform = BboxTransform(Bbox([[0, 0], [1, 1]]), clip) 

1397 return self._make_image( 

1398 self._A, 

1399 bbox_in, bbox_out, clip, magnification, unsampled=unsampled) 

1400 

1401 

1402def imread(fname, format=None): 

1403 """ 

1404 Read an image from a file into an array. 

1405 

1406 Parameters 

1407 ---------- 

1408 fname : str or file-like 

1409 The image file to read: a filename, a URL or a file-like object opened 

1410 in read-binary mode. 

1411 format : str, optional 

1412 The image file format assumed for reading the data. If not 

1413 given, the format is deduced from the filename. If nothing can 

1414 be deduced, PNG is tried. 

1415 

1416 Returns 

1417 ------- 

1418 imagedata : :class:`numpy.array` 

1419 The image data. The returned array has shape 

1420 

1421 - (M, N) for grayscale images. 

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

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

1424 

1425 Notes 

1426 ----- 

1427 Matplotlib can only read PNGs natively. Further image formats are 

1428 supported via the optional dependency on Pillow. Note, URL strings 

1429 are not compatible with Pillow. Check the `Pillow documentation`_ 

1430 for more information. 

1431 

1432 .. _Pillow documentation: http://pillow.readthedocs.io/en/latest/ 

1433 """ 

1434 if format is None: 

1435 if isinstance(fname, str): 

1436 parsed = urllib.parse.urlparse(fname) 

1437 # If the string is a URL (Windows paths appear as if they have a 

1438 # length-1 scheme), assume png. 

1439 if len(parsed.scheme) > 1: 

1440 ext = 'png' 

1441 else: 

1442 basename, ext = os.path.splitext(fname) 

1443 ext = ext.lower()[1:] 

1444 elif hasattr(fname, 'geturl'): # Returned by urlopen(). 

1445 # We could try to parse the url's path and use the extension, but 

1446 # returning png is consistent with the block above. Note that this 

1447 # if clause has to come before checking for fname.name as 

1448 # urlopen("file:///...") also has a name attribute (with the fixed 

1449 # value "<urllib response>"). 

1450 ext = 'png' 

1451 elif hasattr(fname, 'name'): 

1452 basename, ext = os.path.splitext(fname.name) 

1453 ext = ext.lower()[1:] 

1454 else: 

1455 ext = 'png' 

1456 else: 

1457 ext = format 

1458 if ext != 'png': 

1459 try: # Try to load the image with PIL. 

1460 from PIL import Image 

1461 except ImportError: 

1462 raise ValueError('Only know how to handle PNG; with Pillow ' 

1463 'installed, Matplotlib can handle more images') 

1464 with Image.open(fname) as image: 

1465 return pil_to_array(image) 

1466 from matplotlib import _png 

1467 if isinstance(fname, str): 

1468 parsed = urllib.parse.urlparse(fname) 

1469 # If fname is a URL, download the data 

1470 if len(parsed.scheme) > 1: 

1471 from urllib import request 

1472 fd = BytesIO(request.urlopen(fname).read()) 

1473 return _png.read_png(fd) 

1474 with cbook.open_file_cm(fname, "rb") as file: 

1475 return _png.read_png(file) 

1476 

1477 

1478def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, 

1479 origin=None, dpi=100, *, metadata=None, pil_kwargs=None): 

1480 """ 

1481 Save an array as an image file. 

1482 

1483 Parameters 

1484 ---------- 

1485 fname : str or PathLike or file-like 

1486 A path or a file-like object to store the image in. 

1487 If *format* is not set, then the output format is inferred from the 

1488 extension of *fname*, if any, and from :rc:`savefig.format` otherwise. 

1489 If *format* is set, it determines the output format. 

1490 arr : array-like 

1491 The image data. The shape can be one of 

1492 MxN (luminance), MxNx3 (RGB) or MxNx4 (RGBA). 

1493 vmin, vmax : scalar, optional 

1494 *vmin* and *vmax* set the color scaling for the image by fixing the 

1495 values that map to the colormap color limits. If either *vmin* 

1496 or *vmax* is None, that limit is determined from the *arr* 

1497 min/max value. 

1498 cmap : str or `~matplotlib.colors.Colormap`, optional 

1499 A Colormap instance or registered colormap name. The colormap 

1500 maps scalar data to colors. It is ignored for RGB(A) data. 

1501 Defaults to :rc:`image.cmap` ('viridis'). 

1502 format : str, optional 

1503 The file format, e.g. 'png', 'pdf', 'svg', ... The behavior when this 

1504 is unset is documented under *fname*. 

1505 origin : {'upper', 'lower'}, optional 

1506 Indicates whether the ``(0, 0)`` index of the array is in the upper 

1507 left or lower left corner of the axes. Defaults to :rc:`image.origin` 

1508 ('upper'). 

1509 dpi : int 

1510 The DPI to store in the metadata of the file. This does not affect the 

1511 resolution of the output image. 

1512 metadata : dict, optional 

1513 Metadata in the image file. The supported keys depend on the output 

1514 format, see the documentation of the respective backends for more 

1515 information. 

1516 pil_kwargs : dict, optional 

1517 If set to a non-None value, always use Pillow to save the figure 

1518 (regardless of the output format), and pass these keyword arguments to 

1519 `PIL.Image.save`. 

1520 

1521 If the 'pnginfo' key is present, it completely overrides 

1522 *metadata*, including the default 'Software' key. 

1523 """ 

1524 from matplotlib.figure import Figure 

1525 from matplotlib import _png 

1526 if isinstance(fname, os.PathLike): 

1527 fname = os.fspath(fname) 

1528 if format is None: 

1529 format = (Path(fname).suffix[1:] if isinstance(fname, str) 

1530 else rcParams["savefig.format"]).lower() 

1531 if format in ["pdf", "ps", "eps", "svg"]: 

1532 # Vector formats that are not handled by PIL. 

1533 if pil_kwargs is not None: 

1534 raise ValueError( 

1535 f"Cannot use 'pil_kwargs' when saving to {format}") 

1536 fig = Figure(dpi=dpi, frameon=False) 

1537 fig.figimage(arr, cmap=cmap, vmin=vmin, vmax=vmax, origin=origin, 

1538 resize=True) 

1539 fig.savefig(fname, dpi=dpi, format=format, transparent=True, 

1540 metadata=metadata) 

1541 else: 

1542 # Don't bother creating an image; this avoids rounding errors on the 

1543 # size when dividing and then multiplying by dpi. 

1544 sm = cm.ScalarMappable(cmap=cmap) 

1545 sm.set_clim(vmin, vmax) 

1546 if origin is None: 

1547 origin = rcParams["image.origin"] 

1548 if origin == "lower": 

1549 arr = arr[::-1] 

1550 rgba = sm.to_rgba(arr, bytes=True) 

1551 if format == "png" and pil_kwargs is None: 

1552 with cbook.open_file_cm(fname, "wb") as file: 

1553 _png.write_png(rgba, file, dpi=dpi, metadata=metadata) 

1554 else: 

1555 try: 

1556 from PIL import Image 

1557 from PIL.PngImagePlugin import PngInfo 

1558 except ImportError as exc: 

1559 if pil_kwargs is not None: 

1560 raise ImportError("Setting 'pil_kwargs' requires Pillow") 

1561 else: 

1562 raise ImportError(f"Saving to {format} requires Pillow") 

1563 if pil_kwargs is None: 

1564 pil_kwargs = {} 

1565 pil_shape = (rgba.shape[1], rgba.shape[0]) 

1566 image = Image.frombuffer( 

1567 "RGBA", pil_shape, rgba, "raw", "RGBA", 0, 1) 

1568 if format == "png" and metadata: 

1569 # cf. backend_agg's print_png. 

1570 if "pnginfo" in pil_kwargs: 

1571 cbook._warn_external("'metadata' is overridden by the " 

1572 "'pnginfo' entry in 'pil_kwargs'.") 

1573 else: 

1574 pnginfo = PngInfo() 

1575 for k, v in metadata.items(): 

1576 pnginfo.add_text(k, v) 

1577 pil_kwargs["pnginfo"] = pnginfo 

1578 if format in ["jpg", "jpeg"]: 

1579 format = "jpeg" # Pillow doesn't recognize "jpg". 

1580 color = tuple( 

1581 int(x * 255) 

1582 for x in mcolors.to_rgb(rcParams["savefig.facecolor"])) 

1583 background = Image.new("RGB", pil_shape, color) 

1584 background.paste(image, image) 

1585 image = background 

1586 pil_kwargs.setdefault("format", format) 

1587 pil_kwargs.setdefault("dpi", (dpi, dpi)) 

1588 image.save(fname, **pil_kwargs) 

1589 

1590 

1591def pil_to_array(pilImage): 

1592 """Load a `PIL image`_ and return it as a numpy array. 

1593 

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

1595 

1596 Returns 

1597 ------- 

1598 numpy.array 

1599 

1600 The array shape depends on the image type: 

1601 

1602 - (M, N) for grayscale images. 

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

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

1605 

1606 """ 

1607 if pilImage.mode in ['RGBA', 'RGBX', 'RGB', 'L']: 

1608 # return MxNx4 RGBA, MxNx3 RBA, or MxN luminance array 

1609 return np.asarray(pilImage) 

1610 elif pilImage.mode.startswith('I;16'): 

1611 # return MxN luminance array of uint16 

1612 raw = pilImage.tobytes('raw', pilImage.mode) 

1613 if pilImage.mode.endswith('B'): 

1614 x = np.frombuffer(raw, '>u2') 

1615 else: 

1616 x = np.frombuffer(raw, '<u2') 

1617 return x.reshape(pilImage.size[::-1]).astype('=u2') 

1618 else: # try to convert to an rgba image 

1619 try: 

1620 pilImage = pilImage.convert('RGBA') 

1621 except ValueError: 

1622 raise RuntimeError('Unknown image mode') 

1623 return np.asarray(pilImage) # return MxNx4 RGBA array 

1624 

1625 

1626def thumbnail(infile, thumbfile, scale=0.1, interpolation='bilinear', 

1627 preview=False): 

1628 """ 

1629 Make a thumbnail of image in *infile* with output filename *thumbfile*. 

1630 

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

1632 

1633 Parameters 

1634 ---------- 

1635 infile : str or file-like 

1636 The image file -- must be PNG, or Pillow-readable if you have Pillow_ 

1637 installed. 

1638 

1639 .. _Pillow: http://python-pillow.org/ 

1640 

1641 thumbfile : str or file-like 

1642 The thumbnail filename. 

1643 

1644 scale : float, optional 

1645 The scale factor for the thumbnail. 

1646 

1647 interpolation : str, optional 

1648 The interpolation scheme used in the resampling. See the 

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

1650 

1651 preview : bool, optional 

1652 If True, the default backend (presumably a user interface 

1653 backend) will be used which will cause a figure to be raised if 

1654 `~matplotlib.pyplot.show` is called. If it is False, the figure is 

1655 created using `FigureCanvasBase` and the drawing backend is selected 

1656 as `~matplotlib.figure.savefig` would normally do. 

1657 

1658 Returns 

1659 ------- 

1660 figure : `~.figure.Figure` 

1661 The figure instance containing the thumbnail. 

1662 """ 

1663 

1664 im = imread(infile) 

1665 rows, cols, depth = im.shape 

1666 

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

1668 dpi = 100 

1669 

1670 height = rows / dpi * scale 

1671 width = cols / dpi * scale 

1672 

1673 if preview: 

1674 # Let the UI backend do everything. 

1675 import matplotlib.pyplot as plt 

1676 fig = plt.figure(figsize=(width, height), dpi=dpi) 

1677 else: 

1678 from matplotlib.figure import Figure 

1679 fig = Figure(figsize=(width, height), dpi=dpi) 

1680 FigureCanvasBase(fig) 

1681 

1682 ax = fig.add_axes([0, 0, 1, 1], aspect='auto', 

1683 frameon=False, xticks=[], yticks=[]) 

1684 ax.imshow(im, aspect='auto', resample=True, interpolation=interpolation) 

1685 fig.savefig(thumbfile, dpi=dpi) 

1686 return fig