test_image.py 58 KB

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