image.py 61 KB

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