test_simplification.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. import base64
  2. import io
  3. import numpy as np
  4. from numpy.testing import assert_array_almost_equal, assert_array_equal
  5. import pytest
  6. from matplotlib.testing.decorators import image_comparison
  7. import matplotlib.pyplot as plt
  8. from matplotlib import patches, transforms
  9. from matplotlib.path import Path
  10. # NOTE: All of these tests assume that path.simplify is set to True
  11. # (the default)
  12. @image_comparison(['clipping'], remove_text=True)
  13. def test_clipping():
  14. t = np.arange(0.0, 2.0, 0.01)
  15. s = np.sin(2*np.pi*t)
  16. fig, ax = plt.subplots()
  17. ax.plot(t, s, linewidth=1.0)
  18. ax.set_ylim((-0.20, -0.28))
  19. @image_comparison(['overflow'], remove_text=True)
  20. def test_overflow():
  21. x = np.array([1.0, 2.0, 3.0, 2.0e5])
  22. y = np.arange(len(x))
  23. fig, ax = plt.subplots()
  24. ax.plot(x, y)
  25. ax.set_xlim(2, 6)
  26. @image_comparison(['clipping_diamond'], remove_text=True)
  27. def test_diamond():
  28. x = np.array([0.0, 1.0, 0.0, -1.0, 0.0])
  29. y = np.array([1.0, 0.0, -1.0, 0.0, 1.0])
  30. fig, ax = plt.subplots()
  31. ax.plot(x, y)
  32. ax.set_xlim(-0.6, 0.6)
  33. ax.set_ylim(-0.6, 0.6)
  34. def test_noise():
  35. np.random.seed(0)
  36. x = np.random.uniform(size=50000) * 50
  37. fig, ax = plt.subplots()
  38. p1 = ax.plot(x, solid_joinstyle='round', linewidth=2.0)
  39. # Ensure that the path's transform takes the new axes limits into account.
  40. fig.canvas.draw()
  41. path = p1[0].get_path()
  42. transform = p1[0].get_transform()
  43. path = transform.transform_path(path)
  44. simplified = path.cleaned(simplify=True)
  45. assert simplified.vertices.size == 25512
  46. def test_antiparallel_simplification():
  47. def _get_simplified(x, y):
  48. fig, ax = plt.subplots()
  49. p1 = ax.plot(x, y)
  50. path = p1[0].get_path()
  51. transform = p1[0].get_transform()
  52. path = transform.transform_path(path)
  53. simplified = path.cleaned(simplify=True)
  54. simplified = transform.inverted().transform_path(simplified)
  55. return simplified
  56. # test ending on a maximum
  57. x = [0, 0, 0, 0, 0, 1]
  58. y = [.5, 1, -1, 1, 2, .5]
  59. simplified = _get_simplified(x, y)
  60. assert_array_almost_equal([[0., 0.5],
  61. [0., -1.],
  62. [0., 2.],
  63. [1., 0.5]],
  64. simplified.vertices[:-2, :])
  65. # test ending on a minimum
  66. x = [0, 0, 0, 0, 0, 1]
  67. y = [.5, 1, -1, 1, -2, .5]
  68. simplified = _get_simplified(x, y)
  69. assert_array_almost_equal([[0., 0.5],
  70. [0., 1.],
  71. [0., -2.],
  72. [1., 0.5]],
  73. simplified.vertices[:-2, :])
  74. # test ending in between
  75. x = [0, 0, 0, 0, 0, 1]
  76. y = [.5, 1, -1, 1, 0, .5]
  77. simplified = _get_simplified(x, y)
  78. assert_array_almost_equal([[0., 0.5],
  79. [0., 1.],
  80. [0., -1.],
  81. [0., 0.],
  82. [1., 0.5]],
  83. simplified.vertices[:-2, :])
  84. # test no anti-parallel ending at max
  85. x = [0, 0, 0, 0, 0, 1]
  86. y = [.5, 1, 2, 1, 3, .5]
  87. simplified = _get_simplified(x, y)
  88. assert_array_almost_equal([[0., 0.5],
  89. [0., 3.],
  90. [1., 0.5]],
  91. simplified.vertices[:-2, :])
  92. # test no anti-parallel ending in middle
  93. x = [0, 0, 0, 0, 0, 1]
  94. y = [.5, 1, 2, 1, 1, .5]
  95. simplified = _get_simplified(x, y)
  96. assert_array_almost_equal([[0., 0.5],
  97. [0., 2.],
  98. [0., 1.],
  99. [1., 0.5]],
  100. simplified.vertices[:-2, :])
  101. # Only consider angles in 0 <= angle <= pi/2, otherwise
  102. # using min/max will get the expected results out of order:
  103. # min/max for simplification code depends on original vector,
  104. # and if angle is outside above range then simplification
  105. # min/max will be opposite from actual min/max.
  106. @pytest.mark.parametrize('angle', [0, np.pi/4, np.pi/3, np.pi/2])
  107. @pytest.mark.parametrize('offset', [0, .5])
  108. def test_angled_antiparallel(angle, offset):
  109. scale = 5
  110. np.random.seed(19680801)
  111. # get 15 random offsets
  112. # TODO: guarantee offset > 0 results in some offsets < 0
  113. vert_offsets = (np.random.rand(15) - offset) * scale
  114. # always start at 0 so rotation makes sense
  115. vert_offsets[0] = 0
  116. # always take the first step the same direction
  117. vert_offsets[1] = 1
  118. # compute points along a diagonal line
  119. x = np.sin(angle) * vert_offsets
  120. y = np.cos(angle) * vert_offsets
  121. # will check these later
  122. x_max = x[1:].max()
  123. x_min = x[1:].min()
  124. y_max = y[1:].max()
  125. y_min = y[1:].min()
  126. if offset > 0:
  127. p_expected = Path([[0, 0],
  128. [x_max, y_max],
  129. [x_min, y_min],
  130. [x[-1], y[-1]],
  131. [0, 0]],
  132. codes=[1, 2, 2, 2, 0])
  133. else:
  134. p_expected = Path([[0, 0],
  135. [x_max, y_max],
  136. [x[-1], y[-1]],
  137. [0, 0]],
  138. codes=[1, 2, 2, 0])
  139. p = Path(np.vstack([x, y]).T)
  140. p2 = p.cleaned(simplify=True)
  141. assert_array_almost_equal(p_expected.vertices,
  142. p2.vertices)
  143. assert_array_equal(p_expected.codes, p2.codes)
  144. def test_sine_plus_noise():
  145. np.random.seed(0)
  146. x = (np.sin(np.linspace(0, np.pi * 2.0, 50000)) +
  147. np.random.uniform(size=50000) * 0.01)
  148. fig, ax = plt.subplots()
  149. p1 = ax.plot(x, solid_joinstyle='round', linewidth=2.0)
  150. # Ensure that the path's transform takes the new axes limits into account.
  151. fig.canvas.draw()
  152. path = p1[0].get_path()
  153. transform = p1[0].get_transform()
  154. path = transform.transform_path(path)
  155. simplified = path.cleaned(simplify=True)
  156. assert simplified.vertices.size == 25240
  157. @image_comparison(['simplify_curve'], remove_text=True)
  158. def test_simplify_curve():
  159. pp1 = patches.PathPatch(
  160. Path([(0, 0), (1, 0), (1, 1), (np.nan, 1), (0, 0), (2, 0), (2, 2),
  161. (0, 0)],
  162. [Path.MOVETO, Path.CURVE3, Path.CURVE3, Path.CURVE3, Path.CURVE3,
  163. Path.CURVE3, Path.CURVE3, Path.CLOSEPOLY]),
  164. fc="none")
  165. fig, ax = plt.subplots()
  166. ax.add_patch(pp1)
  167. ax.set_xlim((0, 2))
  168. ax.set_ylim((0, 2))
  169. @image_comparison(['hatch_simplify'], remove_text=True)
  170. def test_hatch():
  171. fig, ax = plt.subplots()
  172. ax.add_patch(plt.Rectangle((0, 0), 1, 1, fill=False, hatch="/"))
  173. ax.set_xlim((0.45, 0.55))
  174. ax.set_ylim((0.45, 0.55))
  175. @image_comparison(['fft_peaks'], remove_text=True)
  176. def test_fft_peaks():
  177. fig, ax = plt.subplots()
  178. t = np.arange(65536)
  179. p1 = ax.plot(abs(np.fft.fft(np.sin(2*np.pi*.01*t)*np.blackman(len(t)))))
  180. # Ensure that the path's transform takes the new axes limits into account.
  181. fig.canvas.draw()
  182. path = p1[0].get_path()
  183. transform = p1[0].get_transform()
  184. path = transform.transform_path(path)
  185. simplified = path.cleaned(simplify=True)
  186. assert simplified.vertices.size == 36
  187. def test_start_with_moveto():
  188. # Should be entirely clipped away to a single MOVETO
  189. data = b"""
  190. ZwAAAAku+v9UAQAA+Tj6/z8CAADpQ/r/KAMAANlO+v8QBAAAyVn6//UEAAC6ZPr/2gUAAKpv+v+8
  191. BgAAm3r6/50HAACLhfr/ewgAAHyQ+v9ZCQAAbZv6/zQKAABepvr/DgsAAE+x+v/lCwAAQLz6/7wM
  192. AAAxx/r/kA0AACPS+v9jDgAAFN36/zQPAAAF6Pr/AxAAAPfy+v/QEAAA6f36/5wRAADbCPv/ZhIA
  193. AMwT+/8uEwAAvh77//UTAACwKfv/uRQAAKM0+/98FQAAlT/7/z0WAACHSvv//RYAAHlV+/+7FwAA
  194. bGD7/3cYAABea/v/MRkAAFF2+//pGQAARIH7/6AaAAA3jPv/VRsAACmX+/8JHAAAHKL7/7ocAAAP
  195. rfv/ah0AAAO4+/8YHgAA9sL7/8QeAADpzfv/bx8AANzY+/8YIAAA0OP7/78gAADD7vv/ZCEAALf5
  196. +/8IIgAAqwT8/6kiAACeD/z/SiMAAJIa/P/oIwAAhiX8/4QkAAB6MPz/HyUAAG47/P+4JQAAYkb8
  197. /1AmAABWUfz/5SYAAEpc/P95JwAAPmf8/wsoAAAzcvz/nCgAACd9/P8qKQAAHIj8/7cpAAAQk/z/
  198. QyoAAAWe/P/MKgAA+aj8/1QrAADus/z/2isAAOO+/P9eLAAA2Mn8/+AsAADM1Pz/YS0AAMHf/P/g
  199. LQAAtur8/10uAACr9fz/2C4AAKEA/f9SLwAAlgv9/8ovAACLFv3/QDAAAIAh/f+1MAAAdSz9/ycx
  200. AABrN/3/mDEAAGBC/f8IMgAAVk39/3UyAABLWP3/4TIAAEFj/f9LMwAANm79/7MzAAAsef3/GjQA
  201. ACKE/f9+NAAAF4/9/+E0AAANmv3/QzUAAAOl/f+iNQAA+a/9/wA2AADvuv3/XDYAAOXF/f+2NgAA
  202. 29D9/w83AADR2/3/ZjcAAMfm/f+7NwAAvfH9/w44AACz/P3/XzgAAKkH/v+vOAAAnxL+//04AACW
  203. Hf7/SjkAAIwo/v+UOQAAgjP+/905AAB5Pv7/JDoAAG9J/v9pOgAAZVT+/606AABcX/7/7zoAAFJq
  204. /v8vOwAASXX+/207AAA/gP7/qjsAADaL/v/lOwAALZb+/x48AAAjof7/VTwAABqs/v+LPAAAELf+
  205. /788AAAHwv7/8TwAAP7M/v8hPQAA9df+/1A9AADr4v7/fT0AAOLt/v+oPQAA2fj+/9E9AADQA///
  206. +T0AAMYO//8fPgAAvRn//0M+AAC0JP//ZT4AAKsv//+GPgAAojr//6U+AACZRf//wj4AAJBQ///d
  207. PgAAh1v///c+AAB+Zv//Dz8AAHRx//8lPwAAa3z//zk/AABih///TD8AAFmS//9dPwAAUJ3//2w/
  208. AABHqP//ej8AAD6z//+FPwAANb7//48/AAAsyf//lz8AACPU//+ePwAAGt///6M/AAAR6v//pj8A
  209. AAj1//+nPwAA/////w=="""
  210. verts = np.frombuffer(base64.decodebytes(data), dtype='<i4')
  211. verts = verts.reshape((len(verts) // 2, 2))
  212. path = Path(verts)
  213. segs = path.iter_segments(transforms.IdentityTransform(),
  214. clip=(0.0, 0.0, 100.0, 100.0))
  215. segs = list(segs)
  216. assert len(segs) == 1
  217. assert segs[0][1] == Path.MOVETO
  218. def test_throw_rendering_complexity_exceeded():
  219. plt.rcParams['path.simplify'] = False
  220. xx = np.arange(200000)
  221. yy = np.random.rand(200000)
  222. yy[1000] = np.nan
  223. fig, ax = plt.subplots()
  224. ax.plot(xx, yy)
  225. with pytest.raises(OverflowError):
  226. fig.savefig(io.BytesIO())
  227. @image_comparison(['clipper_edge'], remove_text=True)
  228. def test_clipper():
  229. dat = (0, 1, 0, 2, 0, 3, 0, 4, 0, 5)
  230. fig = plt.figure(figsize=(2, 1))
  231. fig.subplots_adjust(left=0, bottom=0, wspace=0, hspace=0)
  232. ax = fig.add_axes((0, 0, 1.0, 1.0), ylim=(0, 5), autoscale_on=False)
  233. ax.plot(dat)
  234. ax.xaxis.set_major_locator(plt.MultipleLocator(1))
  235. ax.yaxis.set_major_locator(plt.MultipleLocator(1))
  236. ax.xaxis.set_ticks_position('bottom')
  237. ax.yaxis.set_ticks_position('left')
  238. ax.set_xlim(5, 9)
  239. @image_comparison(['para_equal_perp'], remove_text=True)
  240. def test_para_equal_perp():
  241. x = np.array([0, 1, 2, 1, 0, -1, 0, 1] + [1] * 128)
  242. y = np.array([1, 1, 2, 1, 0, -1, 0, 0] + [0] * 128)
  243. fig, ax = plt.subplots()
  244. ax.plot(x + 1, y + 1)
  245. ax.plot(x + 1, y + 1, 'ro')
  246. @image_comparison(['clipping_with_nans'])
  247. def test_clipping_with_nans():
  248. x = np.linspace(0, 3.14 * 2, 3000)
  249. y = np.sin(x)
  250. x[::100] = np.nan
  251. fig, ax = plt.subplots()
  252. ax.plot(x, y)
  253. ax.set_ylim(-0.25, 0.25)
  254. def test_clipping_full():
  255. p = Path([[1e30, 1e30]] * 5)
  256. simplified = list(p.iter_segments(clip=[0, 0, 100, 100]))
  257. assert simplified == []
  258. p = Path([[50, 40], [75, 65]], [1, 2])
  259. simplified = list(p.iter_segments(clip=[0, 0, 100, 100]))
  260. assert ([(list(x), y) for x, y in simplified] ==
  261. [([50, 40], 1), ([75, 65], 2)])
  262. p = Path([[50, 40]], [1])
  263. simplified = list(p.iter_segments(clip=[0, 0, 100, 100]))
  264. assert ([(list(x), y) for x, y in simplified] ==
  265. [([50, 40], 1)])