test_image.py 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505
  1. from contextlib import ExitStack
  2. from copy import copy
  3. import functools
  4. import io
  5. import os
  6. from pathlib import Path
  7. import platform
  8. import sys
  9. import urllib.request
  10. import numpy as np
  11. from numpy.testing import assert_array_equal
  12. from PIL import Image
  13. import matplotlib as mpl
  14. from matplotlib import (
  15. colors, image as mimage, patches, pyplot as plt, style, rcParams)
  16. from matplotlib.image import (AxesImage, BboxImage, FigureImage,
  17. NonUniformImage, PcolorImage)
  18. from matplotlib.testing.decorators import check_figures_equal, image_comparison
  19. from matplotlib.transforms import Bbox, Affine2D, TransformedBbox
  20. import matplotlib.ticker as mticker
  21. import pytest
  22. @image_comparison(['image_interps'], style='mpl20')
  23. def test_image_interps():
  24. """Make the basic nearest, bilinear and bicubic interps."""
  25. # Remove texts when this image is regenerated.
  26. # Remove this line when this test image is regenerated.
  27. plt.rcParams['text.kerning_factor'] = 6
  28. X = np.arange(100).reshape(5, 20)
  29. fig, (ax1, ax2, ax3) = plt.subplots(3)
  30. ax1.imshow(X, interpolation='nearest')
  31. ax1.set_title('three interpolations')
  32. ax1.set_ylabel('nearest')
  33. ax2.imshow(X, interpolation='bilinear')
  34. ax2.set_ylabel('bilinear')
  35. ax3.imshow(X, interpolation='bicubic')
  36. ax3.set_ylabel('bicubic')
  37. @image_comparison(['interp_alpha.png'], remove_text=True)
  38. def test_alpha_interp():
  39. """Test the interpolation of the alpha channel on RGBA images"""
  40. fig, (axl, axr) = plt.subplots(1, 2)
  41. # full green image
  42. img = np.zeros((5, 5, 4))
  43. img[..., 1] = np.ones((5, 5))
  44. # transparent under main diagonal
  45. img[..., 3] = np.tril(np.ones((5, 5), dtype=np.uint8))
  46. axl.imshow(img, interpolation="none")
  47. axr.imshow(img, interpolation="bilinear")
  48. @image_comparison(['interp_nearest_vs_none'],
  49. extensions=['pdf', 'svg'], remove_text=True)
  50. def test_interp_nearest_vs_none():
  51. """Test the effect of "nearest" and "none" interpolation"""
  52. # Setting dpi to something really small makes the difference very
  53. # visible. This works fine with pdf, since the dpi setting doesn't
  54. # affect anything but images, but the agg output becomes unusably
  55. # small.
  56. rcParams['savefig.dpi'] = 3
  57. X = np.array([[[218, 165, 32], [122, 103, 238]],
  58. [[127, 255, 0], [255, 99, 71]]], dtype=np.uint8)
  59. fig, (ax1, ax2) = plt.subplots(1, 2)
  60. ax1.imshow(X, interpolation='none')
  61. ax1.set_title('interpolation none')
  62. ax2.imshow(X, interpolation='nearest')
  63. ax2.set_title('interpolation nearest')
  64. @pytest.mark.parametrize('suppressComposite', [False, True])
  65. @image_comparison(['figimage'], extensions=['png', 'pdf'])
  66. def test_figimage(suppressComposite):
  67. fig = plt.figure(figsize=(2, 2), dpi=100)
  68. fig.suppressComposite = suppressComposite
  69. x, y = np.ix_(np.arange(100) / 100.0, np.arange(100) / 100)
  70. z = np.sin(x**2 + y**2 - x*y)
  71. c = np.sin(20*x**2 + 50*y**2)
  72. img = z + c/5
  73. fig.figimage(img, xo=0, yo=0, origin='lower')
  74. fig.figimage(img[::-1, :], xo=0, yo=100, origin='lower')
  75. fig.figimage(img[:, ::-1], xo=100, yo=0, origin='lower')
  76. fig.figimage(img[::-1, ::-1], xo=100, yo=100, origin='lower')
  77. def test_image_python_io():
  78. fig, ax = plt.subplots()
  79. ax.plot([1, 2, 3])
  80. buffer = io.BytesIO()
  81. fig.savefig(buffer)
  82. buffer.seek(0)
  83. plt.imread(buffer)
  84. @pytest.mark.parametrize(
  85. "img_size, fig_size, interpolation",
  86. [(5, 2, "hanning"), # data larger than figure.
  87. (5, 5, "nearest"), # exact resample.
  88. (5, 10, "nearest"), # double sample.
  89. (3, 2.9, "hanning"), # <3 upsample.
  90. (3, 9.1, "nearest"), # >3 upsample.
  91. ])
  92. @check_figures_equal(extensions=['png'])
  93. def test_imshow_antialiased(fig_test, fig_ref,
  94. img_size, fig_size, interpolation):
  95. np.random.seed(19680801)
  96. dpi = plt.rcParams["savefig.dpi"]
  97. A = np.random.rand(int(dpi * img_size), int(dpi * img_size))
  98. for fig in [fig_test, fig_ref]:
  99. fig.set_size_inches(fig_size, fig_size)
  100. ax = fig_test.subplots()
  101. ax.set_position([0, 0, 1, 1])
  102. ax.imshow(A, interpolation='antialiased')
  103. ax = fig_ref.subplots()
  104. ax.set_position([0, 0, 1, 1])
  105. ax.imshow(A, interpolation=interpolation)
  106. @check_figures_equal(extensions=['png'])
  107. def test_imshow_zoom(fig_test, fig_ref):
  108. # should be less than 3 upsample, so should be nearest...
  109. np.random.seed(19680801)
  110. dpi = plt.rcParams["savefig.dpi"]
  111. A = np.random.rand(int(dpi * 3), int(dpi * 3))
  112. for fig in [fig_test, fig_ref]:
  113. fig.set_size_inches(2.9, 2.9)
  114. ax = fig_test.subplots()
  115. ax.imshow(A, interpolation='antialiased')
  116. ax.set_xlim([10, 20])
  117. ax.set_ylim([10, 20])
  118. ax = fig_ref.subplots()
  119. ax.imshow(A, interpolation='nearest')
  120. ax.set_xlim([10, 20])
  121. ax.set_ylim([10, 20])
  122. @check_figures_equal()
  123. def test_imshow_pil(fig_test, fig_ref):
  124. style.use("default")
  125. png_path = Path(__file__).parent / "baseline_images/pngsuite/basn3p04.png"
  126. tiff_path = Path(__file__).parent / "baseline_images/test_image/uint16.tif"
  127. axs = fig_test.subplots(2)
  128. axs[0].imshow(Image.open(png_path))
  129. axs[1].imshow(Image.open(tiff_path))
  130. axs = fig_ref.subplots(2)
  131. axs[0].imshow(plt.imread(png_path))
  132. axs[1].imshow(plt.imread(tiff_path))
  133. def test_imread_pil_uint16():
  134. img = plt.imread(os.path.join(os.path.dirname(__file__),
  135. 'baseline_images', 'test_image', 'uint16.tif'))
  136. assert img.dtype == np.uint16
  137. assert np.sum(img) == 134184960
  138. def test_imread_fspath():
  139. img = plt.imread(
  140. Path(__file__).parent / 'baseline_images/test_image/uint16.tif')
  141. assert img.dtype == np.uint16
  142. assert np.sum(img) == 134184960
  143. @pytest.mark.parametrize("fmt", ["png", "jpg", "jpeg", "tiff"])
  144. def test_imsave(fmt):
  145. has_alpha = fmt not in ["jpg", "jpeg"]
  146. # The goal here is that the user can specify an output logical DPI
  147. # for the image, but this will not actually add any extra pixels
  148. # to the image, it will merely be used for metadata purposes.
  149. # So we do the traditional case (dpi == 1), and the new case (dpi
  150. # == 100) and read the resulting PNG files back in and make sure
  151. # the data is 100% identical.
  152. np.random.seed(1)
  153. # The height of 1856 pixels was selected because going through creating an
  154. # actual dpi=100 figure to save the image to a Pillow-provided format would
  155. # cause a rounding error resulting in a final image of shape 1855.
  156. data = np.random.rand(1856, 2)
  157. buff_dpi1 = io.BytesIO()
  158. plt.imsave(buff_dpi1, data, format=fmt, dpi=1)
  159. buff_dpi100 = io.BytesIO()
  160. plt.imsave(buff_dpi100, data, format=fmt, dpi=100)
  161. buff_dpi1.seek(0)
  162. arr_dpi1 = plt.imread(buff_dpi1, format=fmt)
  163. buff_dpi100.seek(0)
  164. arr_dpi100 = plt.imread(buff_dpi100, format=fmt)
  165. assert arr_dpi1.shape == (1856, 2, 3 + has_alpha)
  166. assert arr_dpi100.shape == (1856, 2, 3 + has_alpha)
  167. assert_array_equal(arr_dpi1, arr_dpi100)
  168. @pytest.mark.parametrize("fmt", ["png", "pdf", "ps", "eps", "svg"])
  169. def test_imsave_fspath(fmt):
  170. plt.imsave(Path(os.devnull), np.array([[0, 1]]), format=fmt)
  171. def test_imsave_color_alpha():
  172. # Test that imsave accept arrays with ndim=3 where the third dimension is
  173. # color and alpha without raising any exceptions, and that the data is
  174. # acceptably preserved through a save/read roundtrip.
  175. np.random.seed(1)
  176. for origin in ['lower', 'upper']:
  177. data = np.random.rand(16, 16, 4)
  178. buff = io.BytesIO()
  179. plt.imsave(buff, data, origin=origin, format="png")
  180. buff.seek(0)
  181. arr_buf = plt.imread(buff)
  182. # Recreate the float -> uint8 conversion of the data
  183. # We can only expect to be the same with 8 bits of precision,
  184. # since that's what the PNG file used.
  185. data = (255*data).astype('uint8')
  186. if origin == 'lower':
  187. data = data[::-1]
  188. arr_buf = (255*arr_buf).astype('uint8')
  189. assert_array_equal(data, arr_buf)
  190. def test_imsave_pil_kwargs_png():
  191. from PIL.PngImagePlugin import PngInfo
  192. buf = io.BytesIO()
  193. pnginfo = PngInfo()
  194. pnginfo.add_text("Software", "test")
  195. plt.imsave(buf, [[0, 1], [2, 3]],
  196. format="png", pil_kwargs={"pnginfo": pnginfo})
  197. im = Image.open(buf)
  198. assert im.info["Software"] == "test"
  199. def test_imsave_pil_kwargs_tiff():
  200. from PIL.TiffTags import TAGS_V2 as TAGS
  201. buf = io.BytesIO()
  202. pil_kwargs = {"description": "test image"}
  203. plt.imsave(buf, [[0, 1], [2, 3]], format="tiff", pil_kwargs=pil_kwargs)
  204. assert len(pil_kwargs) == 1
  205. im = Image.open(buf)
  206. tags = {TAGS[k].name: v for k, v in im.tag_v2.items()}
  207. assert tags["ImageDescription"] == "test image"
  208. @image_comparison(['image_alpha'], remove_text=True)
  209. def test_image_alpha():
  210. np.random.seed(0)
  211. Z = np.random.rand(6, 6)
  212. fig, (ax1, ax2, ax3) = plt.subplots(1, 3)
  213. ax1.imshow(Z, alpha=1.0, interpolation='none')
  214. ax2.imshow(Z, alpha=0.5, interpolation='none')
  215. ax3.imshow(Z, alpha=0.5, interpolation='nearest')
  216. def test_cursor_data():
  217. from matplotlib.backend_bases import MouseEvent
  218. fig, ax = plt.subplots()
  219. im = ax.imshow(np.arange(100).reshape(10, 10), origin='upper')
  220. x, y = 4, 4
  221. xdisp, ydisp = ax.transData.transform([x, y])
  222. event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
  223. assert im.get_cursor_data(event) == 44
  224. # Now try for a point outside the image
  225. # Tests issue #4957
  226. x, y = 10.1, 4
  227. xdisp, ydisp = ax.transData.transform([x, y])
  228. event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
  229. assert im.get_cursor_data(event) is None
  230. # Hmm, something is wrong here... I get 0, not None...
  231. # But, this works further down in the tests with extents flipped
  232. # x, y = 0.1, -0.1
  233. # xdisp, ydisp = ax.transData.transform([x, y])
  234. # event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
  235. # z = im.get_cursor_data(event)
  236. # assert z is None, "Did not get None, got %d" % z
  237. ax.clear()
  238. # Now try with the extents flipped.
  239. im = ax.imshow(np.arange(100).reshape(10, 10), origin='lower')
  240. x, y = 4, 4
  241. xdisp, ydisp = ax.transData.transform([x, y])
  242. event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
  243. assert im.get_cursor_data(event) == 44
  244. fig, ax = plt.subplots()
  245. im = ax.imshow(np.arange(100).reshape(10, 10), extent=[0, 0.5, 0, 0.5])
  246. x, y = 0.25, 0.25
  247. xdisp, ydisp = ax.transData.transform([x, y])
  248. event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
  249. assert im.get_cursor_data(event) == 55
  250. # Now try for a point outside the image
  251. # Tests issue #4957
  252. x, y = 0.75, 0.25
  253. xdisp, ydisp = ax.transData.transform([x, y])
  254. event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
  255. assert im.get_cursor_data(event) is None
  256. x, y = 0.01, -0.01
  257. xdisp, ydisp = ax.transData.transform([x, y])
  258. event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
  259. assert im.get_cursor_data(event) is None
  260. # Now try with additional transform applied to the image artist
  261. trans = Affine2D().scale(2).rotate(0.5)
  262. im = ax.imshow(np.arange(100).reshape(10, 10),
  263. transform=trans + ax.transData)
  264. x, y = 3, 10
  265. xdisp, ydisp = ax.transData.transform([x, y])
  266. event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
  267. assert im.get_cursor_data(event) == 44
  268. @pytest.mark.parametrize(
  269. "data, text", [
  270. ([[10001, 10000]], "[10001.000]"),
  271. ([[.123, .987]], "[0.123]"),
  272. ([[np.nan, 1, 2]], "[]"),
  273. ([[1, 1+1e-15]], "[1.0000000000000000]"),
  274. ([[-1, -1]], "[-1.0000000000000000]"),
  275. ])
  276. def test_format_cursor_data(data, text):
  277. from matplotlib.backend_bases import MouseEvent
  278. fig, ax = plt.subplots()
  279. im = ax.imshow(data)
  280. xdisp, ydisp = ax.transData.transform([0, 0])
  281. event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
  282. assert im.format_cursor_data(im.get_cursor_data(event)) == text
  283. @image_comparison(['image_clip'], style='mpl20')
  284. def test_image_clip():
  285. d = [[1, 2], [3, 4]]
  286. fig, ax = plt.subplots()
  287. im = ax.imshow(d)
  288. patch = patches.Circle((0, 0), radius=1, transform=ax.transData)
  289. im.set_clip_path(patch)
  290. @image_comparison(['image_cliprect'], style='mpl20')
  291. def test_image_cliprect():
  292. fig, ax = plt.subplots()
  293. d = [[1, 2], [3, 4]]
  294. im = ax.imshow(d, extent=(0, 5, 0, 5))
  295. rect = patches.Rectangle(
  296. xy=(1, 1), width=2, height=2, transform=im.axes.transData)
  297. im.set_clip_path(rect)
  298. @image_comparison(['imshow'], remove_text=True, style='mpl20')
  299. def test_imshow():
  300. fig, ax = plt.subplots()
  301. arr = np.arange(100).reshape((10, 10))
  302. ax.imshow(arr, interpolation="bilinear", extent=(1, 2, 1, 2))
  303. ax.set_xlim(0, 3)
  304. ax.set_ylim(0, 3)
  305. @check_figures_equal(extensions=['png'])
  306. def test_imshow_10_10_1(fig_test, fig_ref):
  307. # 10x10x1 should be the same as 10x10
  308. arr = np.arange(100).reshape((10, 10, 1))
  309. ax = fig_ref.subplots()
  310. ax.imshow(arr[:, :, 0], interpolation="bilinear", extent=(1, 2, 1, 2))
  311. ax.set_xlim(0, 3)
  312. ax.set_ylim(0, 3)
  313. ax = fig_test.subplots()
  314. ax.imshow(arr, interpolation="bilinear", extent=(1, 2, 1, 2))
  315. ax.set_xlim(0, 3)
  316. ax.set_ylim(0, 3)
  317. def test_imshow_10_10_2():
  318. fig, ax = plt.subplots()
  319. arr = np.arange(200).reshape((10, 10, 2))
  320. with pytest.raises(TypeError):
  321. ax.imshow(arr)
  322. def test_imshow_10_10_5():
  323. fig, ax = plt.subplots()
  324. arr = np.arange(500).reshape((10, 10, 5))
  325. with pytest.raises(TypeError):
  326. ax.imshow(arr)
  327. @image_comparison(['no_interpolation_origin'], remove_text=True)
  328. def test_no_interpolation_origin():
  329. fig, axs = plt.subplots(2)
  330. axs[0].imshow(np.arange(100).reshape((2, 50)), origin="lower",
  331. interpolation='none')
  332. axs[1].imshow(np.arange(100).reshape((2, 50)), interpolation='none')
  333. @image_comparison(['image_shift'], remove_text=True, extensions=['pdf', 'svg'])
  334. def test_image_shift():
  335. imgData = [[1 / x + 1 / y for x in range(1, 100)] for y in range(1, 100)]
  336. tMin = 734717.945208
  337. tMax = 734717.946366
  338. fig, ax = plt.subplots()
  339. ax.imshow(imgData, norm=colors.LogNorm(), interpolation='none',
  340. extent=(tMin, tMax, 1, 100))
  341. ax.set_aspect('auto')
  342. def test_image_edges():
  343. fig = plt.figure(figsize=[1, 1])
  344. ax = fig.add_axes([0, 0, 1, 1], frameon=False)
  345. data = np.tile(np.arange(12), 15).reshape(20, 9)
  346. im = ax.imshow(data, origin='upper', extent=[-10, 10, -10, 10],
  347. interpolation='none', cmap='gray')
  348. x = y = 2
  349. ax.set_xlim([-x, x])
  350. ax.set_ylim([-y, y])
  351. ax.set_xticks([])
  352. ax.set_yticks([])
  353. buf = io.BytesIO()
  354. fig.savefig(buf, facecolor=(0, 1, 0))
  355. buf.seek(0)
  356. im = plt.imread(buf)
  357. r, g, b, a = sum(im[:, 0])
  358. r, g, b, a = sum(im[:, -1])
  359. assert g != 100, 'Expected a non-green edge - but sadly, it was.'
  360. @image_comparison(['image_composite_background'],
  361. remove_text=True, style='mpl20')
  362. def test_image_composite_background():
  363. fig, ax = plt.subplots()
  364. arr = np.arange(12).reshape(4, 3)
  365. ax.imshow(arr, extent=[0, 2, 15, 0])
  366. ax.imshow(arr, extent=[4, 6, 15, 0])
  367. ax.set_facecolor((1, 0, 0, 0.5))
  368. ax.set_xlim([0, 12])
  369. @image_comparison(['image_composite_alpha'], remove_text=True)
  370. def test_image_composite_alpha():
  371. """
  372. Tests that the alpha value is recognized and correctly applied in the
  373. process of compositing images together.
  374. """
  375. fig, ax = plt.subplots()
  376. arr = np.zeros((11, 21, 4))
  377. arr[:, :, 0] = 1
  378. arr[:, :, 3] = np.concatenate(
  379. (np.arange(0, 1.1, 0.1), np.arange(0, 1, 0.1)[::-1]))
  380. arr2 = np.zeros((21, 11, 4))
  381. arr2[:, :, 0] = 1
  382. arr2[:, :, 1] = 1
  383. arr2[:, :, 3] = np.concatenate(
  384. (np.arange(0, 1.1, 0.1), np.arange(0, 1, 0.1)[::-1]))[:, np.newaxis]
  385. ax.imshow(arr, extent=[1, 2, 5, 0], alpha=0.3)
  386. ax.imshow(arr, extent=[2, 3, 5, 0], alpha=0.6)
  387. ax.imshow(arr, extent=[3, 4, 5, 0])
  388. ax.imshow(arr2, extent=[0, 5, 1, 2])
  389. ax.imshow(arr2, extent=[0, 5, 2, 3], alpha=0.6)
  390. ax.imshow(arr2, extent=[0, 5, 3, 4], alpha=0.3)
  391. ax.set_facecolor((0, 0.5, 0, 1))
  392. ax.set_xlim([0, 5])
  393. ax.set_ylim([5, 0])
  394. @check_figures_equal(extensions=["pdf"])
  395. def test_clip_path_disables_compositing(fig_test, fig_ref):
  396. t = np.arange(9).reshape((3, 3))
  397. for fig in [fig_test, fig_ref]:
  398. ax = fig.add_subplot()
  399. ax.imshow(t, clip_path=(mpl.path.Path([(0, 0), (0, 1), (1, 0)]),
  400. ax.transData))
  401. ax.imshow(t, clip_path=(mpl.path.Path([(1, 1), (1, 2), (2, 1)]),
  402. ax.transData))
  403. fig_ref.suppressComposite = True
  404. @image_comparison(['rasterize_10dpi'],
  405. extensions=['pdf', 'svg'], remove_text=True, style='mpl20')
  406. def test_rasterize_dpi():
  407. # This test should check rasterized rendering with high output resolution.
  408. # It plots a rasterized line and a normal image with imshow. So it will
  409. # catch when images end up in the wrong place in case of non-standard dpi
  410. # setting. Instead of high-res rasterization I use low-res. Therefore
  411. # the fact that the resolution is non-standard is easily checked by
  412. # image_comparison.
  413. img = np.asarray([[1, 2], [3, 4]])
  414. fig, axs = plt.subplots(1, 3, figsize=(3, 1))
  415. axs[0].imshow(img)
  416. axs[1].plot([0, 1], [0, 1], linewidth=20., rasterized=True)
  417. axs[1].set(xlim=(0, 1), ylim=(-1, 2))
  418. axs[2].plot([0, 1], [0, 1], linewidth=20.)
  419. axs[2].set(xlim=(0, 1), ylim=(-1, 2))
  420. # Low-dpi PDF rasterization errors prevent proper image comparison tests.
  421. # Hide detailed structures like the axes spines.
  422. for ax in axs:
  423. ax.set_xticks([])
  424. ax.set_yticks([])
  425. ax.spines[:].set_visible(False)
  426. rcParams['savefig.dpi'] = 10
  427. @image_comparison(['bbox_image_inverted'], remove_text=True, style='mpl20')
  428. def test_bbox_image_inverted():
  429. # This is just used to produce an image to feed to BboxImage
  430. image = np.arange(100).reshape((10, 10))
  431. fig, ax = plt.subplots()
  432. bbox_im = BboxImage(
  433. TransformedBbox(Bbox([[100, 100], [0, 0]]), ax.transData),
  434. interpolation='nearest')
  435. bbox_im.set_data(image)
  436. bbox_im.set_clip_on(False)
  437. ax.set_xlim(0, 100)
  438. ax.set_ylim(0, 100)
  439. ax.add_artist(bbox_im)
  440. image = np.identity(10)
  441. bbox_im = BboxImage(TransformedBbox(Bbox([[0.1, 0.2], [0.3, 0.25]]),
  442. ax.figure.transFigure),
  443. interpolation='nearest')
  444. bbox_im.set_data(image)
  445. bbox_im.set_clip_on(False)
  446. ax.add_artist(bbox_im)
  447. def test_get_window_extent_for_AxisImage():
  448. # Create a figure of known size (1000x1000 pixels), place an image
  449. # object at a given location and check that get_window_extent()
  450. # returns the correct bounding box values (in pixels).
  451. im = np.array([[0.25, 0.75, 1.0, 0.75], [0.1, 0.65, 0.5, 0.4],
  452. [0.6, 0.3, 0.0, 0.2], [0.7, 0.9, 0.4, 0.6]])
  453. fig, ax = plt.subplots(figsize=(10, 10), dpi=100)
  454. ax.set_position([0, 0, 1, 1])
  455. ax.set_xlim(0, 1)
  456. ax.set_ylim(0, 1)
  457. im_obj = ax.imshow(
  458. im, extent=[0.4, 0.7, 0.2, 0.9], interpolation='nearest')
  459. fig.canvas.draw()
  460. renderer = fig.canvas.renderer
  461. im_bbox = im_obj.get_window_extent(renderer)
  462. assert_array_equal(im_bbox.get_points(), [[400, 200], [700, 900]])
  463. fig, ax = plt.subplots(figsize=(10, 10), dpi=100)
  464. ax.set_position([0, 0, 1, 1])
  465. ax.set_xlim(1, 2)
  466. ax.set_ylim(0, 1)
  467. im_obj = ax.imshow(
  468. im, extent=[0.4, 0.7, 0.2, 0.9], interpolation='nearest',
  469. transform=ax.transAxes)
  470. fig.canvas.draw()
  471. renderer = fig.canvas.renderer
  472. im_bbox = im_obj.get_window_extent(renderer)
  473. assert_array_equal(im_bbox.get_points(), [[400, 200], [700, 900]])
  474. @image_comparison(['zoom_and_clip_upper_origin.png'],
  475. remove_text=True, style='mpl20')
  476. def test_zoom_and_clip_upper_origin():
  477. image = np.arange(100)
  478. image = image.reshape((10, 10))
  479. fig, ax = plt.subplots()
  480. ax.imshow(image)
  481. ax.set_ylim(2.0, -0.5)
  482. ax.set_xlim(-0.5, 2.0)
  483. def test_nonuniformimage_setcmap():
  484. ax = plt.gca()
  485. im = NonUniformImage(ax)
  486. im.set_cmap('Blues')
  487. def test_nonuniformimage_setnorm():
  488. ax = plt.gca()
  489. im = NonUniformImage(ax)
  490. im.set_norm(plt.Normalize())
  491. def test_jpeg_2d():
  492. # smoke test that mode-L pillow images work.
  493. imd = np.ones((10, 10), dtype='uint8')
  494. for i in range(10):
  495. imd[i, :] = np.linspace(0.0, 1.0, 10) * 255
  496. im = Image.new('L', (10, 10))
  497. im.putdata(imd.flatten())
  498. fig, ax = plt.subplots()
  499. ax.imshow(im)
  500. def test_jpeg_alpha():
  501. plt.figure(figsize=(1, 1), dpi=300)
  502. # Create an image that is all black, with a gradient from 0-1 in
  503. # the alpha channel from left to right.
  504. im = np.zeros((300, 300, 4), dtype=float)
  505. im[..., 3] = np.linspace(0.0, 1.0, 300)
  506. plt.figimage(im)
  507. buff = io.BytesIO()
  508. plt.savefig(buff, facecolor="red", format='jpg', dpi=300)
  509. buff.seek(0)
  510. image = Image.open(buff)
  511. # If this fails, there will be only one color (all black). If this
  512. # is working, we should have all 256 shades of grey represented.
  513. num_colors = len(image.getcolors(256))
  514. assert 175 <= num_colors <= 210
  515. # The fully transparent part should be red.
  516. corner_pixel = image.getpixel((0, 0))
  517. assert corner_pixel == (254, 0, 0)
  518. def test_axesimage_setdata():
  519. ax = plt.gca()
  520. im = AxesImage(ax)
  521. z = np.arange(12, dtype=float).reshape((4, 3))
  522. im.set_data(z)
  523. z[0, 0] = 9.9
  524. assert im._A[0, 0] == 0, 'value changed'
  525. def test_figureimage_setdata():
  526. fig = plt.gcf()
  527. im = FigureImage(fig)
  528. z = np.arange(12, dtype=float).reshape((4, 3))
  529. im.set_data(z)
  530. z[0, 0] = 9.9
  531. assert im._A[0, 0] == 0, 'value changed'
  532. @pytest.mark.parametrize(
  533. "image_cls,x,y,a", [
  534. (NonUniformImage,
  535. np.arange(3.), np.arange(4.), np.arange(12.).reshape((4, 3))),
  536. (PcolorImage,
  537. np.arange(3.), np.arange(4.), np.arange(6.).reshape((3, 2))),
  538. ])
  539. def test_setdata_xya(image_cls, x, y, a):
  540. ax = plt.gca()
  541. im = image_cls(ax)
  542. im.set_data(x, y, a)
  543. x[0] = y[0] = a[0, 0] = 9.9
  544. assert im._A[0, 0] == im._Ax[0] == im._Ay[0] == 0, 'value changed'
  545. im.set_data(x, y, a.reshape((*a.shape, -1))) # Just a smoketest.
  546. def test_minimized_rasterized():
  547. # This ensures that the rasterized content in the colorbars is
  548. # only as thick as the colorbar, and doesn't extend to other parts
  549. # of the image. See #5814. While the original bug exists only
  550. # in Postscript, the best way to detect it is to generate SVG
  551. # and then parse the output to make sure the two colorbar images
  552. # are the same size.
  553. from xml.etree import ElementTree
  554. np.random.seed(0)
  555. data = np.random.rand(10, 10)
  556. fig, ax = plt.subplots(1, 2)
  557. p1 = ax[0].pcolormesh(data)
  558. p2 = ax[1].pcolormesh(data)
  559. plt.colorbar(p1, ax=ax[0])
  560. plt.colorbar(p2, ax=ax[1])
  561. buff = io.BytesIO()
  562. plt.savefig(buff, format='svg')
  563. buff = io.BytesIO(buff.getvalue())
  564. tree = ElementTree.parse(buff)
  565. width = None
  566. for image in tree.iter('image'):
  567. if width is None:
  568. width = image['width']
  569. else:
  570. if image['width'] != width:
  571. assert False
  572. def test_load_from_url():
  573. path = Path(__file__).parent / "baseline_images/pngsuite/basn3p04.png"
  574. url = ('file:'
  575. + ('///' if sys.platform == 'win32' else '')
  576. + path.resolve().as_posix())
  577. with pytest.raises(ValueError, match="Please open the URL"):
  578. plt.imread(url)
  579. with urllib.request.urlopen(url) as file:
  580. plt.imread(file)
  581. @image_comparison(['log_scale_image'], remove_text=True)
  582. def test_log_scale_image():
  583. Z = np.zeros((10, 10))
  584. Z[::2] = 1
  585. fig, ax = plt.subplots()
  586. ax.imshow(Z, extent=[1, 100, 1, 100], cmap='viridis', vmax=1, vmin=-1,
  587. aspect='auto')
  588. ax.set(yscale='log')
  589. @image_comparison(['rotate_image'], remove_text=True)
  590. def test_rotate_image():
  591. delta = 0.25
  592. x = y = np.arange(-3.0, 3.0, delta)
  593. X, Y = np.meshgrid(x, y)
  594. Z1 = np.exp(-(X**2 + Y**2) / 2) / (2 * np.pi)
  595. Z2 = (np.exp(-(((X - 1) / 1.5)**2 + ((Y - 1) / 0.5)**2) / 2) /
  596. (2 * np.pi * 0.5 * 1.5))
  597. Z = Z2 - Z1 # difference of Gaussians
  598. fig, ax1 = plt.subplots(1, 1)
  599. im1 = ax1.imshow(Z, interpolation='none', cmap='viridis',
  600. origin='lower',
  601. extent=[-2, 4, -3, 2], clip_on=True)
  602. trans_data2 = Affine2D().rotate_deg(30) + ax1.transData
  603. im1.set_transform(trans_data2)
  604. # display intended extent of the image
  605. x1, x2, y1, y2 = im1.get_extent()
  606. ax1.plot([x1, x2, x2, x1, x1], [y1, y1, y2, y2, y1], "r--", lw=3,
  607. transform=trans_data2)
  608. ax1.set_xlim(2, 5)
  609. ax1.set_ylim(0, 4)
  610. def test_image_preserve_size():
  611. buff = io.BytesIO()
  612. im = np.zeros((481, 321))
  613. plt.imsave(buff, im, format="png")
  614. buff.seek(0)
  615. img = plt.imread(buff)
  616. assert img.shape[:2] == im.shape
  617. def test_image_preserve_size2():
  618. n = 7
  619. data = np.identity(n, float)
  620. fig = plt.figure(figsize=(n, n), frameon=False)
  621. ax = plt.Axes(fig, [0.0, 0.0, 1.0, 1.0])
  622. ax.set_axis_off()
  623. fig.add_axes(ax)
  624. ax.imshow(data, interpolation='nearest', origin='lower', aspect='auto')
  625. buff = io.BytesIO()
  626. fig.savefig(buff, dpi=1)
  627. buff.seek(0)
  628. img = plt.imread(buff)
  629. assert img.shape == (7, 7, 4)
  630. assert_array_equal(np.asarray(img[:, :, 0], bool),
  631. np.identity(n, bool)[::-1])
  632. @image_comparison(['mask_image_over_under.png'], remove_text=True, tol=1.0)
  633. def test_mask_image_over_under():
  634. # Remove this line when this test image is regenerated.
  635. plt.rcParams['pcolormesh.snap'] = False
  636. delta = 0.025
  637. x = y = np.arange(-3.0, 3.0, delta)
  638. X, Y = np.meshgrid(x, y)
  639. Z1 = np.exp(-(X**2 + Y**2) / 2) / (2 * np.pi)
  640. Z2 = (np.exp(-(((X - 1) / 1.5)**2 + ((Y - 1) / 0.5)**2) / 2) /
  641. (2 * np.pi * 0.5 * 1.5))
  642. Z = 10*(Z2 - Z1) # difference of Gaussians
  643. palette = plt.cm.gray.with_extremes(over='r', under='g', bad='b')
  644. Zm = np.ma.masked_where(Z > 1.2, Z)
  645. fig, (ax1, ax2) = plt.subplots(1, 2)
  646. im = ax1.imshow(Zm, interpolation='bilinear',
  647. cmap=palette,
  648. norm=colors.Normalize(vmin=-1.0, vmax=1.0, clip=False),
  649. origin='lower', extent=[-3, 3, -3, 3])
  650. ax1.set_title('Green=low, Red=high, Blue=bad')
  651. fig.colorbar(im, extend='both', orientation='horizontal',
  652. ax=ax1, aspect=10)
  653. im = ax2.imshow(Zm, interpolation='nearest',
  654. cmap=palette,
  655. norm=colors.BoundaryNorm([-1, -0.5, -0.2, 0, 0.2, 0.5, 1],
  656. ncolors=256, clip=False),
  657. origin='lower', extent=[-3, 3, -3, 3])
  658. ax2.set_title('With BoundaryNorm')
  659. fig.colorbar(im, extend='both', spacing='proportional',
  660. orientation='horizontal', ax=ax2, aspect=10)
  661. @image_comparison(['mask_image'], remove_text=True)
  662. def test_mask_image():
  663. # Test mask image two ways: Using nans and using a masked array.
  664. fig, (ax1, ax2) = plt.subplots(1, 2)
  665. A = np.ones((5, 5))
  666. A[1:2, 1:2] = np.nan
  667. ax1.imshow(A, interpolation='nearest')
  668. A = np.zeros((5, 5), dtype=bool)
  669. A[1:2, 1:2] = True
  670. A = np.ma.masked_array(np.ones((5, 5), dtype=np.uint16), A)
  671. ax2.imshow(A, interpolation='nearest')
  672. def test_mask_image_all():
  673. # Test behavior with an image that is entirely masked does not warn
  674. data = np.full((2, 2), np.nan)
  675. fig, ax = plt.subplots()
  676. ax.imshow(data)
  677. fig.canvas.draw_idle() # would emit a warning
  678. @image_comparison(['imshow_endianess.png'], remove_text=True)
  679. def test_imshow_endianess():
  680. x = np.arange(10)
  681. X, Y = np.meshgrid(x, x)
  682. Z = np.hypot(X - 5, Y - 5)
  683. fig, (ax1, ax2) = plt.subplots(1, 2)
  684. kwargs = dict(origin="lower", interpolation='nearest', cmap='viridis')
  685. ax1.imshow(Z.astype('<f8'), **kwargs)
  686. ax2.imshow(Z.astype('>f8'), **kwargs)
  687. @image_comparison(['imshow_masked_interpolation'],
  688. tol=0 if platform.machine() == 'x86_64' else 0.01,
  689. remove_text=True, style='mpl20')
  690. def test_imshow_masked_interpolation():
  691. cmap = mpl.colormaps['viridis'].with_extremes(over='r', under='b', bad='k')
  692. N = 20
  693. n = colors.Normalize(vmin=0, vmax=N*N-1)
  694. data = np.arange(N*N, dtype=float).reshape(N, N)
  695. data[5, 5] = -1
  696. # This will cause crazy ringing for the higher-order
  697. # interpolations
  698. data[15, 5] = 1e5
  699. # data[3, 3] = np.nan
  700. data[15, 15] = np.inf
  701. mask = np.zeros_like(data).astype('bool')
  702. mask[5, 15] = True
  703. data = np.ma.masked_array(data, mask)
  704. fig, ax_grid = plt.subplots(3, 6)
  705. interps = sorted(mimage._interpd_)
  706. interps.remove('antialiased')
  707. for interp, ax in zip(interps, ax_grid.ravel()):
  708. ax.set_title(interp)
  709. ax.imshow(data, norm=n, cmap=cmap, interpolation=interp)
  710. ax.axis('off')
  711. def test_imshow_no_warn_invalid():
  712. plt.imshow([[1, 2], [3, np.nan]]) # Check that no warning is emitted.
  713. @pytest.mark.parametrize(
  714. 'dtype', [np.dtype(s) for s in 'u2 u4 i2 i4 i8 f4 f8'.split()])
  715. def test_imshow_clips_rgb_to_valid_range(dtype):
  716. arr = np.arange(300, dtype=dtype).reshape((10, 10, 3))
  717. if dtype.kind != 'u':
  718. arr -= 10
  719. too_low = arr < 0
  720. too_high = arr > 255
  721. if dtype.kind == 'f':
  722. arr = arr / 255
  723. _, ax = plt.subplots()
  724. out = ax.imshow(arr).get_array()
  725. assert (out[too_low] == 0).all()
  726. if dtype.kind == 'f':
  727. assert (out[too_high] == 1).all()
  728. assert out.dtype.kind == 'f'
  729. else:
  730. assert (out[too_high] == 255).all()
  731. assert out.dtype == np.uint8
  732. @image_comparison(['imshow_flatfield.png'], remove_text=True, style='mpl20')
  733. def test_imshow_flatfield():
  734. fig, ax = plt.subplots()
  735. im = ax.imshow(np.ones((5, 5)), interpolation='nearest')
  736. im.set_clim(.5, 1.5)
  737. @image_comparison(['imshow_bignumbers.png'], remove_text=True, style='mpl20')
  738. def test_imshow_bignumbers():
  739. rcParams['image.interpolation'] = 'nearest'
  740. # putting a big number in an array of integers shouldn't
  741. # ruin the dynamic range of the resolved bits.
  742. fig, ax = plt.subplots()
  743. img = np.array([[1, 2, 1e12], [3, 1, 4]], dtype=np.uint64)
  744. pc = ax.imshow(img)
  745. pc.set_clim(0, 5)
  746. @image_comparison(['imshow_bignumbers_real.png'],
  747. remove_text=True, style='mpl20')
  748. def test_imshow_bignumbers_real():
  749. rcParams['image.interpolation'] = 'nearest'
  750. # putting a big number in an array of integers shouldn't
  751. # ruin the dynamic range of the resolved bits.
  752. fig, ax = plt.subplots()
  753. img = np.array([[2., 1., 1.e22], [4., 1., 3.]])
  754. pc = ax.imshow(img)
  755. pc.set_clim(0, 5)
  756. @pytest.mark.parametrize(
  757. "make_norm",
  758. [colors.Normalize,
  759. colors.LogNorm,
  760. lambda: colors.SymLogNorm(1),
  761. lambda: colors.PowerNorm(1)])
  762. def test_empty_imshow(make_norm):
  763. fig, ax = plt.subplots()
  764. with pytest.warns(UserWarning,
  765. match="Attempting to set identical low and high xlims"):
  766. im = ax.imshow([[]], norm=make_norm())
  767. im.set_extent([-5, 5, -5, 5])
  768. fig.canvas.draw()
  769. with pytest.raises(RuntimeError):
  770. im.make_image(fig.canvas.get_renderer())
  771. def test_imshow_float16():
  772. fig, ax = plt.subplots()
  773. ax.imshow(np.zeros((3, 3), dtype=np.float16))
  774. # Ensure that drawing doesn't cause crash.
  775. fig.canvas.draw()
  776. def test_imshow_float128():
  777. fig, ax = plt.subplots()
  778. ax.imshow(np.zeros((3, 3), dtype=np.longdouble))
  779. with (ExitStack() if np.can_cast(np.longdouble, np.float64, "equiv")
  780. else pytest.warns(UserWarning)):
  781. # Ensure that drawing doesn't cause crash.
  782. fig.canvas.draw()
  783. def test_imshow_bool():
  784. fig, ax = plt.subplots()
  785. ax.imshow(np.array([[True, False], [False, True]], dtype=bool))
  786. def test_full_invalid():
  787. fig, ax = plt.subplots()
  788. ax.imshow(np.full((10, 10), np.nan))
  789. fig.canvas.draw()
  790. @pytest.mark.parametrize("fmt,counted",
  791. [("ps", b" colorimage"), ("svg", b"<image")])
  792. @pytest.mark.parametrize("composite_image,count", [(True, 1), (False, 2)])
  793. def test_composite(fmt, counted, composite_image, count):
  794. # Test that figures can be saved with and without combining multiple images
  795. # (on a single set of axes) into a single composite image.
  796. X, Y = np.meshgrid(np.arange(-5, 5, 1), np.arange(-5, 5, 1))
  797. Z = np.sin(Y ** 2)
  798. fig, ax = plt.subplots()
  799. ax.set_xlim(0, 3)
  800. ax.imshow(Z, extent=[0, 1, 0, 1])
  801. ax.imshow(Z[::-1], extent=[2, 3, 0, 1])
  802. plt.rcParams['image.composite_image'] = composite_image
  803. buf = io.BytesIO()
  804. fig.savefig(buf, format=fmt)
  805. assert buf.getvalue().count(counted) == count
  806. def test_relim():
  807. fig, ax = plt.subplots()
  808. ax.imshow([[0]], extent=(0, 1, 0, 1))
  809. ax.relim()
  810. ax.autoscale()
  811. assert ax.get_xlim() == ax.get_ylim() == (0, 1)
  812. def test_unclipped():
  813. fig, ax = plt.subplots()
  814. ax.set_axis_off()
  815. im = ax.imshow([[0, 0], [0, 0]], aspect="auto", extent=(-10, 10, -10, 10),
  816. cmap='gray', clip_on=False)
  817. ax.set(xlim=(0, 1), ylim=(0, 1))
  818. fig.canvas.draw()
  819. # The unclipped image should fill the *entire* figure and be black.
  820. # Ignore alpha for this comparison.
  821. assert (np.array(fig.canvas.buffer_rgba())[..., :3] == 0).all()
  822. def test_respects_bbox():
  823. fig, axs = plt.subplots(2)
  824. for ax in axs:
  825. ax.set_axis_off()
  826. im = axs[1].imshow([[0, 1], [2, 3]], aspect="auto", extent=(0, 1, 0, 1))
  827. im.set_clip_path(None)
  828. # Make the image invisible in axs[1], but visible in axs[0] if we pan
  829. # axs[1] up.
  830. im.set_clip_box(axs[0].bbox)
  831. buf_before = io.BytesIO()
  832. fig.savefig(buf_before, format="rgba")
  833. assert {*buf_before.getvalue()} == {0xff} # All white.
  834. axs[1].set(ylim=(-1, 0))
  835. buf_after = io.BytesIO()
  836. fig.savefig(buf_after, format="rgba")
  837. assert buf_before.getvalue() != buf_after.getvalue() # Not all white.
  838. def test_image_cursor_formatting():
  839. fig, ax = plt.subplots()
  840. # Create a dummy image to be able to call format_cursor_data
  841. im = ax.imshow(np.zeros((4, 4)))
  842. data = np.ma.masked_array([0], mask=[True])
  843. assert im.format_cursor_data(data) == '[]'
  844. data = np.ma.masked_array([0], mask=[False])
  845. assert im.format_cursor_data(data) == '[0]'
  846. data = np.nan
  847. assert im.format_cursor_data(data) == '[nan]'
  848. @check_figures_equal()
  849. def test_image_array_alpha(fig_test, fig_ref):
  850. """Per-pixel alpha channel test."""
  851. x = np.linspace(0, 1)
  852. xx, yy = np.meshgrid(x, x)
  853. zz = np.exp(- 3 * ((xx - 0.5) ** 2) + (yy - 0.7 ** 2))
  854. alpha = zz / zz.max()
  855. cmap = mpl.colormaps['viridis']
  856. ax = fig_test.add_subplot()
  857. ax.imshow(zz, alpha=alpha, cmap=cmap, interpolation='nearest')
  858. ax = fig_ref.add_subplot()
  859. rgba = cmap(colors.Normalize()(zz))
  860. rgba[..., -1] = alpha
  861. ax.imshow(rgba, interpolation='nearest')
  862. def test_image_array_alpha_validation():
  863. with pytest.raises(TypeError, match="alpha must be a float, two-d"):
  864. plt.imshow(np.zeros((2, 2)), alpha=[1, 1])
  865. @mpl.style.context('mpl20')
  866. def test_exact_vmin():
  867. cmap = copy(mpl.colormaps["autumn_r"])
  868. cmap.set_under(color="lightgrey")
  869. # make the image exactly 190 pixels wide
  870. fig = plt.figure(figsize=(1.9, 0.1), dpi=100)
  871. ax = fig.add_axes([0, 0, 1, 1])
  872. data = np.array(
  873. [[-1, -1, -1, 0, 0, 0, 0, 43, 79, 95, 66, 1, -1, -1, -1, 0, 0, 0, 34]],
  874. dtype=float,
  875. )
  876. im = ax.imshow(data, aspect="auto", cmap=cmap, vmin=0, vmax=100)
  877. ax.axis("off")
  878. fig.canvas.draw()
  879. # get the RGBA slice from the image
  880. from_image = im.make_image(fig.canvas.renderer)[0][0]
  881. # expand the input to be 190 long and run through norm / cmap
  882. direct_computation = (
  883. im.cmap(im.norm((data * ([[1]] * 10)).T.ravel())) * 255
  884. ).astype(int)
  885. # check than the RBGA values are the same
  886. assert np.all(from_image == direct_computation)
  887. @image_comparison(['image_placement'], extensions=['svg', 'pdf'],
  888. remove_text=True, style='mpl20')
  889. def test_image_placement():
  890. """
  891. The red box should line up exactly with the outside of the image.
  892. """
  893. fig, ax = plt.subplots()
  894. ax.plot([0, 0, 1, 1, 0], [0, 1, 1, 0, 0], color='r', lw=0.1)
  895. np.random.seed(19680801)
  896. ax.imshow(np.random.randn(16, 16), cmap='Blues', extent=(0, 1, 0, 1),
  897. interpolation='none', vmin=-1, vmax=1)
  898. ax.set_xlim(-0.1, 1+0.1)
  899. ax.set_ylim(-0.1, 1+0.1)
  900. # A basic ndarray subclass that implements a quantity
  901. # It does not implement an entire unit system or all quantity math.
  902. # There is just enough implemented to test handling of ndarray
  903. # subclasses.
  904. class QuantityND(np.ndarray):
  905. def __new__(cls, input_array, units):
  906. obj = np.asarray(input_array).view(cls)
  907. obj.units = units
  908. return obj
  909. def __array_finalize__(self, obj):
  910. self.units = getattr(obj, "units", None)
  911. def __getitem__(self, item):
  912. units = getattr(self, "units", None)
  913. ret = super().__getitem__(item)
  914. if isinstance(ret, QuantityND) or units is not None:
  915. ret = QuantityND(ret, units)
  916. return ret
  917. def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
  918. func = getattr(ufunc, method)
  919. if "out" in kwargs:
  920. return NotImplemented
  921. if len(inputs) == 1:
  922. i0 = inputs[0]
  923. unit = getattr(i0, "units", "dimensionless")
  924. out_arr = func(np.asarray(i0), **kwargs)
  925. elif len(inputs) == 2:
  926. i0 = inputs[0]
  927. i1 = inputs[1]
  928. u0 = getattr(i0, "units", "dimensionless")
  929. u1 = getattr(i1, "units", "dimensionless")
  930. u0 = u1 if u0 is None else u0
  931. u1 = u0 if u1 is None else u1
  932. if ufunc in [np.add, np.subtract]:
  933. if u0 != u1:
  934. raise ValueError
  935. unit = u0
  936. elif ufunc == np.multiply:
  937. unit = f"{u0}*{u1}"
  938. elif ufunc == np.divide:
  939. unit = f"{u0}/({u1})"
  940. elif ufunc in (np.greater, np.greater_equal,
  941. np.equal, np.not_equal,
  942. np.less, np.less_equal):
  943. # Comparisons produce unitless booleans for output
  944. unit = None
  945. else:
  946. return NotImplemented
  947. out_arr = func(i0.view(np.ndarray), i1.view(np.ndarray), **kwargs)
  948. else:
  949. return NotImplemented
  950. if unit is None:
  951. out_arr = np.array(out_arr)
  952. else:
  953. out_arr = QuantityND(out_arr, unit)
  954. return out_arr
  955. @property
  956. def v(self):
  957. return self.view(np.ndarray)
  958. def test_quantitynd():
  959. q = QuantityND([1, 2], "m")
  960. q0, q1 = q[:]
  961. assert np.all(q.v == np.asarray([1, 2]))
  962. assert q.units == "m"
  963. assert np.all((q0 + q1).v == np.asarray([3]))
  964. assert (q0 * q1).units == "m*m"
  965. assert (q1 / q0).units == "m/(m)"
  966. with pytest.raises(ValueError):
  967. q0 + QuantityND(1, "s")
  968. def test_imshow_quantitynd():
  969. # generate a dummy ndarray subclass
  970. arr = QuantityND(np.ones((2, 2)), "m")
  971. fig, ax = plt.subplots()
  972. ax.imshow(arr)
  973. # executing the draw should not raise an exception
  974. fig.canvas.draw()
  975. @check_figures_equal(extensions=['png'])
  976. def test_norm_change(fig_test, fig_ref):
  977. # LogNorm should not mask anything invalid permanently.
  978. data = np.full((5, 5), 1, dtype=np.float64)
  979. data[0:2, :] = -1
  980. masked_data = np.ma.array(data, mask=False)
  981. masked_data.mask[0:2, 0:2] = True
  982. cmap = mpl.colormaps['viridis'].with_extremes(under='w')
  983. ax = fig_test.subplots()
  984. im = ax.imshow(data, norm=colors.LogNorm(vmin=0.5, vmax=1),
  985. extent=(0, 5, 0, 5), interpolation='nearest', cmap=cmap)
  986. im.set_norm(colors.Normalize(vmin=-2, vmax=2))
  987. im = ax.imshow(masked_data, norm=colors.LogNorm(vmin=0.5, vmax=1),
  988. extent=(5, 10, 5, 10), interpolation='nearest', cmap=cmap)
  989. im.set_norm(colors.Normalize(vmin=-2, vmax=2))
  990. ax.set(xlim=(0, 10), ylim=(0, 10))
  991. ax = fig_ref.subplots()
  992. ax.imshow(data, norm=colors.Normalize(vmin=-2, vmax=2),
  993. extent=(0, 5, 0, 5), interpolation='nearest', cmap=cmap)
  994. ax.imshow(masked_data, norm=colors.Normalize(vmin=-2, vmax=2),
  995. extent=(5, 10, 5, 10), interpolation='nearest', cmap=cmap)
  996. ax.set(xlim=(0, 10), ylim=(0, 10))
  997. @pytest.mark.parametrize('x', [-1, 1])
  998. @check_figures_equal(extensions=['png'])
  999. def test_huge_range_log(fig_test, fig_ref, x):
  1000. # parametrize over bad lognorm -1 values and large range 1 -> 1e20
  1001. data = np.full((5, 5), x, dtype=np.float64)
  1002. data[0:2, :] = 1E20
  1003. ax = fig_test.subplots()
  1004. ax.imshow(data, norm=colors.LogNorm(vmin=1, vmax=data.max()),
  1005. interpolation='nearest', cmap='viridis')
  1006. data = np.full((5, 5), x, dtype=np.float64)
  1007. data[0:2, :] = 1000
  1008. ax = fig_ref.subplots()
  1009. cmap = mpl.colormaps['viridis'].with_extremes(under='w')
  1010. ax.imshow(data, norm=colors.Normalize(vmin=1, vmax=data.max()),
  1011. interpolation='nearest', cmap=cmap)
  1012. @check_figures_equal()
  1013. def test_spy_box(fig_test, fig_ref):
  1014. # setting up reference and test
  1015. ax_test = fig_test.subplots(1, 3)
  1016. ax_ref = fig_ref.subplots(1, 3)
  1017. plot_data = (
  1018. [[1, 1], [1, 1]],
  1019. [[0, 0], [0, 0]],
  1020. [[0, 1], [1, 0]],
  1021. )
  1022. plot_titles = ["ones", "zeros", "mixed"]
  1023. for i, (z, title) in enumerate(zip(plot_data, plot_titles)):
  1024. ax_test[i].set_title(title)
  1025. ax_test[i].spy(z)
  1026. ax_ref[i].set_title(title)
  1027. ax_ref[i].imshow(z, interpolation='nearest',
  1028. aspect='equal', origin='upper', cmap='Greys',
  1029. vmin=0, vmax=1)
  1030. ax_ref[i].set_xlim(-0.5, 1.5)
  1031. ax_ref[i].set_ylim(1.5, -0.5)
  1032. ax_ref[i].xaxis.tick_top()
  1033. ax_ref[i].title.set_y(1.05)
  1034. ax_ref[i].xaxis.set_ticks_position('both')
  1035. ax_ref[i].xaxis.set_major_locator(
  1036. mticker.MaxNLocator(nbins=9, steps=[1, 2, 5, 10], integer=True)
  1037. )
  1038. ax_ref[i].yaxis.set_major_locator(
  1039. mticker.MaxNLocator(nbins=9, steps=[1, 2, 5, 10], integer=True)
  1040. )
  1041. @image_comparison(["nonuniform_and_pcolor.png"], style="mpl20")
  1042. def test_nonuniform_and_pcolor():
  1043. axs = plt.figure(figsize=(3, 3)).subplots(3, sharex=True, sharey=True)
  1044. for ax, interpolation in zip(axs, ["nearest", "bilinear"]):
  1045. im = NonUniformImage(ax, interpolation=interpolation)
  1046. im.set_data(np.arange(3) ** 2, np.arange(3) ** 2,
  1047. np.arange(9).reshape((3, 3)))
  1048. ax.add_image(im)
  1049. axs[2].pcolorfast( # PcolorImage
  1050. np.arange(4) ** 2, np.arange(4) ** 2, np.arange(9).reshape((3, 3)))
  1051. for ax in axs:
  1052. ax.set_axis_off()
  1053. # NonUniformImage "leaks" out of extents, not PColorImage.
  1054. ax.set(xlim=(0, 10))
  1055. @image_comparison(
  1056. ['rgba_antialias.png'], style='mpl20', remove_text=True,
  1057. tol=0.007 if platform.machine() in ('aarch64', 'ppc64le', 's390x') else 0)
  1058. def test_rgba_antialias():
  1059. fig, axs = plt.subplots(2, 2, figsize=(3.5, 3.5), sharex=False,
  1060. sharey=False, constrained_layout=True)
  1061. N = 250
  1062. aa = np.ones((N, N))
  1063. aa[::2, :] = -1
  1064. x = np.arange(N) / N - 0.5
  1065. y = np.arange(N) / N - 0.5
  1066. X, Y = np.meshgrid(x, y)
  1067. R = np.sqrt(X**2 + Y**2)
  1068. f0 = 10
  1069. k = 75
  1070. # aliased concentric circles
  1071. a = np.sin(np.pi * 2 * (f0 * R + k * R**2 / 2))
  1072. # stripes on lhs
  1073. a[:int(N/2), :][R[:int(N/2), :] < 0.4] = -1
  1074. a[:int(N/2), :][R[:int(N/2), :] < 0.3] = 1
  1075. aa[:, int(N/2):] = a[:, int(N/2):]
  1076. # set some over/unders and NaNs
  1077. aa[20:50, 20:50] = np.nan
  1078. aa[70:90, 70:90] = 1e6
  1079. aa[70:90, 20:30] = -1e6
  1080. aa[70:90, 195:215] = 1e6
  1081. aa[20:30, 195:215] = -1e6
  1082. cmap = copy(plt.cm.RdBu_r)
  1083. cmap.set_over('yellow')
  1084. cmap.set_under('cyan')
  1085. axs = axs.flatten()
  1086. # zoom in
  1087. axs[0].imshow(aa, interpolation='nearest', cmap=cmap, vmin=-1.2, vmax=1.2)
  1088. axs[0].set_xlim([N/2-25, N/2+25])
  1089. axs[0].set_ylim([N/2+50, N/2-10])
  1090. # no anti-alias
  1091. axs[1].imshow(aa, interpolation='nearest', cmap=cmap, vmin=-1.2, vmax=1.2)
  1092. # data antialias: Note no purples, and white in circle. Note
  1093. # that alternating red and blue stripes become white.
  1094. axs[2].imshow(aa, interpolation='antialiased', interpolation_stage='data',
  1095. cmap=cmap, vmin=-1.2, vmax=1.2)
  1096. # rgba antialias: Note purples at boundary with circle. Note that
  1097. # alternating red and blue stripes become purple
  1098. axs[3].imshow(aa, interpolation='antialiased', interpolation_stage='rgba',
  1099. cmap=cmap, vmin=-1.2, vmax=1.2)
  1100. # We check for the warning with a draw() in the test, but we also need to
  1101. # filter the warning as it is emitted by the figure test decorator
  1102. @pytest.mark.filterwarnings(r'ignore:Data with more than .* '
  1103. 'cannot be accurately displayed')
  1104. @pytest.mark.parametrize('origin', ['upper', 'lower'])
  1105. @pytest.mark.parametrize(
  1106. 'dim, size, msg', [['row', 2**23, r'2\*\*23 columns'],
  1107. ['col', 2**24, r'2\*\*24 rows']])
  1108. @check_figures_equal(extensions=('png', ))
  1109. def test_large_image(fig_test, fig_ref, dim, size, msg, origin):
  1110. # Check that Matplotlib downsamples images that are too big for AGG
  1111. # See issue #19276. Currently the fix only works for png output but not
  1112. # pdf or svg output.
  1113. ax_test = fig_test.subplots()
  1114. ax_ref = fig_ref.subplots()
  1115. array = np.zeros((1, size + 2))
  1116. array[:, array.size // 2:] = 1
  1117. if dim == 'col':
  1118. array = array.T
  1119. im = ax_test.imshow(array, vmin=0, vmax=1,
  1120. aspect='auto', extent=(0, 1, 0, 1),
  1121. interpolation='none',
  1122. origin=origin)
  1123. with pytest.warns(UserWarning,
  1124. match=f'Data with more than {msg} cannot be '
  1125. 'accurately displayed.'):
  1126. fig_test.canvas.draw()
  1127. array = np.zeros((1, 2))
  1128. array[:, 1] = 1
  1129. if dim == 'col':
  1130. array = array.T
  1131. im = ax_ref.imshow(array, vmin=0, vmax=1, aspect='auto',
  1132. extent=(0, 1, 0, 1),
  1133. interpolation='none',
  1134. origin=origin)
  1135. @check_figures_equal(extensions=["png"])
  1136. def test_str_norms(fig_test, fig_ref):
  1137. t = np.random.rand(10, 10) * .8 + .1 # between 0 and 1
  1138. axts = fig_test.subplots(1, 5)
  1139. axts[0].imshow(t, norm="log")
  1140. axts[1].imshow(t, norm="log", vmin=.2)
  1141. axts[2].imshow(t, norm="symlog")
  1142. axts[3].imshow(t, norm="symlog", vmin=.3, vmax=.7)
  1143. axts[4].imshow(t, norm="logit", vmin=.3, vmax=.7)
  1144. axrs = fig_ref.subplots(1, 5)
  1145. axrs[0].imshow(t, norm=colors.LogNorm())
  1146. axrs[1].imshow(t, norm=colors.LogNorm(vmin=.2))
  1147. # same linthresh as SymmetricalLogScale's default.
  1148. axrs[2].imshow(t, norm=colors.SymLogNorm(linthresh=2))
  1149. axrs[3].imshow(t, norm=colors.SymLogNorm(linthresh=2, vmin=.3, vmax=.7))
  1150. axrs[4].imshow(t, norm="logit", clim=(.3, .7))
  1151. assert type(axts[0].images[0].norm) is colors.LogNorm # Exactly that class
  1152. with pytest.raises(ValueError):
  1153. axts[0].imshow(t, norm="foobar")
  1154. def test__resample_valid_output():
  1155. resample = functools.partial(mpl._image.resample, transform=Affine2D())
  1156. with pytest.raises(ValueError, match="must be a NumPy array"):
  1157. resample(np.zeros((9, 9)), None)
  1158. with pytest.raises(ValueError, match="different dimensionalities"):
  1159. resample(np.zeros((9, 9)), np.zeros((9, 9, 4)))
  1160. with pytest.raises(ValueError, match="must be RGBA"):
  1161. resample(np.zeros((9, 9, 4)), np.zeros((9, 9, 3)))
  1162. with pytest.raises(ValueError, match="Mismatched types"):
  1163. resample(np.zeros((9, 9), np.uint8), np.zeros((9, 9)))
  1164. with pytest.raises(ValueError, match="must be C-contiguous"):
  1165. resample(np.zeros((9, 9)), np.zeros((9, 9)).T)
  1166. def test_axesimage_get_shape():
  1167. # generate dummy image to test get_shape method
  1168. ax = plt.gca()
  1169. im = AxesImage(ax)
  1170. with pytest.raises(RuntimeError, match="You must first set the image array"):
  1171. im.get_shape()
  1172. z = np.arange(12, dtype=float).reshape((4, 3))
  1173. im.set_data(z)
  1174. assert im.get_shape() == (4, 3)
  1175. assert im.get_size() == im.get_shape()
  1176. def test_non_transdata_image_does_not_touch_aspect():
  1177. ax = plt.figure().add_subplot()
  1178. im = np.arange(4).reshape((2, 2))
  1179. ax.imshow(im, transform=ax.transAxes)
  1180. assert ax.get_aspect() == "auto"
  1181. ax.imshow(im, transform=Affine2D().scale(2) + ax.transData)
  1182. assert ax.get_aspect() == 1
  1183. ax.imshow(im, transform=ax.transAxes, aspect=2)
  1184. assert ax.get_aspect() == 2