image.py 65 KB

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