test_surface.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  1. # freesurfer-surface - Read and Write Surface Files in Freesurfer’s TriangularSurface Format
  2. #
  3. # Copyright (C) 2020 Fabian Peter Hammerle <fabian@hammerle.me>
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. import copy
  18. import datetime
  19. import unittest.mock
  20. import numpy
  21. import pytest
  22. from conftest import ANNOTATION_FILE_PATH, SURFACE_FILE_PATH
  23. from freesurfer_surface import (
  24. Annotation,
  25. LineSegment,
  26. PolygonalCircuit,
  27. Surface,
  28. Triangle,
  29. Vertex,
  30. setlocale,
  31. )
  32. # pylint: disable=protected-access
  33. def test_read_triangular():
  34. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  35. assert surface.creator == b"fabianpeter"
  36. assert surface.creation_datetime == datetime.datetime(2019, 5, 9, 22, 37, 41)
  37. assert len(surface.vertices) == 155622
  38. assert len(surface.triangles) == 311240
  39. assert not surface.using_old_real_ras
  40. assert surface.volume_geometry_info == (
  41. b"valid = 1 # volume info valid\n",
  42. b"filename = ../mri/filled-pretess255.mgz\n",
  43. b"volume = 256 256 256\n",
  44. b"voxelsize = 1.000000000000000e+00 1.000000000000000e+00 1.000000000000000e+00\n",
  45. b"xras = -1.000000000000000e+00 0.000000000000000e+00 1.862645149230957e-09\n",
  46. b"yras = 0.000000000000000e+00 -6.655682227574289e-09 -1.000000000000000e+00\n",
  47. b"zras = 0.000000000000000e+00 1.000000000000000e+00 -8.300048648379743e-09\n",
  48. b"cras = -2.773597717285156e+00 1.566547393798828e+01 -7.504364013671875e+00\n",
  49. )
  50. assert surface.command_lines == [
  51. b"mris_remove_intersection ../surf/lh.orig ../surf/lh.orig"
  52. b" ProgramVersion: $Name: stable6 $"
  53. b" TimeStamp: 2019/05/09-17:42:36-GMT"
  54. b" BuildTimeStamp: Jan 18 2017 16:38:58"
  55. b" CVS: $Id: mris_remove_intersection.c,v 1.6 2011/03/02 00:04:32 nicks Exp $"
  56. b" User: fabianpeter"
  57. b" Machine: host12345"
  58. b" Platform: Linux"
  59. b" PlatformVersion: 4.15.0-46-generic"
  60. b" CompilerName: GCC"
  61. b" CompilerVersion: 40400"
  62. b" ",
  63. b"mris_make_surfaces -orig_white white.preaparc -orig_pial white.preaparc"
  64. b" -aseg ../mri/aseg.presurf -mgz -T1 brain.finalsurfs"
  65. b" fabian20190509 lh ProgramVersion: $Name: $"
  66. b" TimeStamp: 2019/05/09-20:27:28-GMT"
  67. b" BuildTimeStamp: Jan 18 2017 16:38:58"
  68. b" CVS: $Id: mris_make_surfaces.c,v 1.164.2.4 2016/12/13 22:26:32 zkaufman Exp $"
  69. b" User: fabianpeter"
  70. b" Machine: host12345"
  71. b" Platform: Linux"
  72. b" PlatformVersion: 4.15.0-46-generic"
  73. b" CompilerName: GCC"
  74. b" CompilerVersion: 40400"
  75. b" ",
  76. ]
  77. def test_read_triangular_locale():
  78. with setlocale("de_AT.utf8"):
  79. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  80. assert surface.creation_datetime == datetime.datetime(2019, 5, 9, 22, 37, 41)
  81. @pytest.mark.parametrize(
  82. ("creation_datetime", "expected_str"),
  83. [
  84. (datetime.datetime(2019, 5, 9, 22, 37, 41), b"Thu May 9 22:37:41 2019"),
  85. (datetime.datetime(2019, 4, 24, 23, 29, 22), b"Wed Apr 24 23:29:22 2019"),
  86. ],
  87. )
  88. def test_triangular_strftime(creation_datetime, expected_str):
  89. # pylint: disable=protected-access
  90. assert expected_str == Surface._triangular_strftime(creation_datetime)
  91. def test_write_triangular_missing_geometry(tmpdir):
  92. surface = Surface()
  93. with pytest.raises(ValueError, match=r"\bvolume_geometry_info\b"):
  94. surface.write_triangular(tmpdir.join("surface").strpath)
  95. def test_write_triangular_empty(tmpdir):
  96. surface = Surface()
  97. surface.volume_geometry_info = (
  98. b"valid = 1 # volume info valid\n",
  99. b"filename = ../mri/filled-pretess255.mgz\n",
  100. b"volume = 256 256 256\n",
  101. b"voxelsize = 1.000000000000000e+00 1.000000000000000e+00 1.000000000000000e+00\n",
  102. b"xras = -1.000000000000000e+00 0.000000000000000e+00 1.862645149230957e-09\n",
  103. b"yras = 0.000000000000000e+00 -6.655682227574289e-09 -1.000000000000000e+00\n",
  104. b"zras = 0.000000000000000e+00 1.000000000000000e+00 -8.300048648379743e-09\n",
  105. b"cras = -2.773597717285156e+00 1.566547393798828e+01 -7.504364013671875e+00\n",
  106. )
  107. output_path = tmpdir.join("surface").strpath
  108. surface.write_triangular(
  109. output_path,
  110. creation_datetime=datetime.datetime(2021, 5, 22, 7, 52, 53), # timezone-unaware
  111. )
  112. with open(output_path, "rb") as output_file:
  113. assert (
  114. output_file.read() == b"\xff\xff\xfe"
  115. b"created by pypi.org/project/freesurfer-surface/ on Sat May 22 07:52:53 2021\n\n"
  116. b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x14"
  117. b"valid = 1 # volume info valid\n"
  118. b"filename = ../mri/filled-pretess255.mgz\n"
  119. b"volume = 256 256 256\n"
  120. b"voxelsize = 1.000000000000000e+00 1.000000000000000e+00 1.000000000000000e+00\n"
  121. b"xras = -1.000000000000000e+00 0.000000000000000e+00 1.862645149230957e-09\n"
  122. b"yras = 0.000000000000000e+00 -6.655682227574289e-09 -1.000000000000000e+00\n"
  123. b"zras = 0.000000000000000e+00 1.000000000000000e+00 -8.300048648379743e-09\n"
  124. b"cras = -2.773597717285156e+00 1.566547393798828e+01 -7.504364013671875e+00\n"
  125. )
  126. def test_read_write_triangular_same(tmpdir):
  127. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  128. output_path = tmpdir.join("surface").strpath
  129. surface.write_triangular(output_path, creation_datetime=surface.creation_datetime)
  130. with open(output_path, "rb") as output_file:
  131. with open(SURFACE_FILE_PATH, "rb") as expected_file:
  132. assert expected_file.read() == output_file.read()
  133. def test_read_write_datetime(tmpdir):
  134. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  135. original_creation_datetime = surface.creation_datetime
  136. output_path = tmpdir.join("surface").strpath
  137. surface.write_triangular(output_path)
  138. assert original_creation_datetime == surface.creation_datetime
  139. new_surface = Surface.read_triangular(output_path)
  140. assert new_surface.creation_datetime > original_creation_datetime
  141. assert datetime.datetime.now() > new_surface.creation_datetime
  142. assert (
  143. datetime.datetime.now() - new_surface.creation_datetime
  144. ) < datetime.timedelta(seconds=20)
  145. def test_write_read_triangular_same(tmpdir):
  146. expected_surface = Surface()
  147. expected_surface.creator = b"pytest"
  148. expected_surface.creation_datetime = datetime.datetime.now().replace(microsecond=0)
  149. expected_surface.vertices = [
  150. Vertex(0.0, 0.0, 0.0),
  151. Vertex(1.0, 2.0, 3.0),
  152. Vertex(2.0, 4.0, 6.0),
  153. Vertex(3.0, 5.0, 7.0),
  154. ]
  155. expected_surface.triangles = [
  156. Triangle((0, 1, 2)),
  157. Triangle((0, 1, 3)),
  158. Triangle((3, 2, 1)),
  159. ]
  160. expected_surface.using_old_real_ras = False
  161. expected_surface.volume_geometry_info = tuple(b"?\n" for _ in range(8))
  162. expected_surface.command_lines = [b"?", b"!"]
  163. output_path = tmpdir.join("surface").strpath
  164. expected_surface.write_triangular(
  165. output_path, creation_datetime=expected_surface.creation_datetime
  166. )
  167. resulted_surface = Surface.read_triangular(output_path)
  168. assert numpy.array_equal(expected_surface.vertices, resulted_surface.vertices)
  169. expected_surface.vertices = resulted_surface.vertices = []
  170. assert vars(expected_surface) == vars(resulted_surface)
  171. def test_write_triangular_same_locale(tmpdir):
  172. surface = Surface()
  173. surface.creator = b"pytest"
  174. surface.volume_geometry_info = tuple(b"?" for _ in range(8))
  175. creation_datetime = datetime.datetime(2018, 12, 31, 21, 42)
  176. output_path = tmpdir.join("surface").strpath
  177. with setlocale("de_AT.utf8"):
  178. surface.write_triangular(output_path, creation_datetime=creation_datetime)
  179. resulted_surface = Surface.read_triangular(output_path)
  180. assert resulted_surface.creation_datetime == creation_datetime
  181. with open(output_path, "rb") as output_file:
  182. assert (
  183. output_file.read()
  184. .split(b" on ")[1]
  185. .startswith(b"Mon Dec 31 21:42:00 2018\n")
  186. )
  187. def test_load_annotation():
  188. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  189. assert not surface.annotation
  190. surface.load_annotation_file(ANNOTATION_FILE_PATH)
  191. assert isinstance(surface.annotation, Annotation)
  192. assert len(surface.annotation.vertex_label_index) == 155622
  193. assert surface.annotation.vertex_label_index[0] == 5
  194. def test_add_vertex():
  195. surface = Surface()
  196. assert not surface.vertices
  197. assert surface.add_vertex(Vertex(1.0, 1.5, 2.0)) == 0
  198. assert len(surface.vertices) == 1
  199. assert surface.vertices[0].anterior == pytest.approx(1.5)
  200. assert surface.add_vertex(Vertex(-3.0, 0.0, 4.0)) == 1
  201. assert len(surface.vertices) == 2
  202. assert surface.vertices[1].right == pytest.approx(-3.0)
  203. @pytest.mark.parametrize(
  204. "vertices_coords",
  205. [
  206. ((0, 0, 0), (2, 4, 0), (2, 4, 3)),
  207. ((0, 0, 0), (2, 4, 0), (2, 4, 3), (0, 0, 3)),
  208. ((1, 1, 0), (3, 5, 0), (3, 5, 3), (1, 1, 3)),
  209. ((1, 1, 7), (3, 5, 7), (3, 5, 3), (1, 1, 3)),
  210. ((1, 1, 1), (3, 5, 7), (3, 5, 9), (1, 1, 3)),
  211. ((3, 5, 7), (1, 1, 1), (1, 1, 3)),
  212. ((3, 5, 7), (1, 1, 1), (1, 1, 3), (3, 5, 9)),
  213. ],
  214. )
  215. def test_add_rectangle(vertices_coords):
  216. surface = Surface()
  217. for vertex_coords in vertices_coords:
  218. surface.add_vertex(Vertex(*(float(c) for c in vertex_coords)))
  219. surface.add_rectangle(range(len(vertices_coords)))
  220. assert len(surface.vertices) == 4
  221. assert len(surface.triangles) == 2
  222. assert surface.triangles[0].vertex_indices == (0, 1, 2)
  223. assert surface.triangles[1].vertex_indices == (2, 3, 0)
  224. @pytest.mark.parametrize(
  225. ("vertices_coords", "expected_extra_vertex_coords"),
  226. [
  227. (((0, 0, 0), (2, 4, 0), (2, 4, 3)), (0, 0, 3)),
  228. (((1, 1, 0), (3, 5, 0), (3, 5, 3)), (1, 1, 3)),
  229. (((1, 1, 7), (3, 5, 7), (3, 5, 3)), (1, 1, 3)),
  230. (((1, 1, 1), (3, 5, 7), (3, 5, 9)), (1, 1, 3)),
  231. (((3, 5, 7), (1, 1, 1), (1, 1, 3)), (3, 5, 9)),
  232. ],
  233. )
  234. def test_add_rectangle_3(vertices_coords, expected_extra_vertex_coords):
  235. surface = Surface()
  236. for vertex_coords in vertices_coords:
  237. surface.add_vertex(Vertex(*(float(c) for c in vertex_coords)))
  238. surface.add_rectangle(range(3))
  239. assert tuple(surface.vertices[3]) == pytest.approx(expected_extra_vertex_coords)
  240. def test__triangle_count_by_adjacent_vertex_indices_empty():
  241. surface = Surface()
  242. assert surface._triangle_count_by_adjacent_vertex_indices() == {}
  243. def test__triangle_count_by_adjacent_vertex_indices_none():
  244. surface = Surface()
  245. surface.vertices.append(Vertex(1, 0, 0))
  246. surface.vertices.append(Vertex(2, 0, 0))
  247. surface.vertices.append(Vertex(3, 0, 0))
  248. assert surface._triangle_count_by_adjacent_vertex_indices() == {0: {}, 1: {}, 2: {}}
  249. def test__triangle_count_by_adjacent_vertex_indices_single():
  250. surface = Surface()
  251. surface.triangles.append(
  252. Triangle([surface.add_vertex(Vertex(i, 0, 0)) for i in range(3)])
  253. )
  254. assert surface._triangle_count_by_adjacent_vertex_indices() == {
  255. 0: {1: 1, 2: 1},
  256. 1: {0: 1, 2: 1},
  257. 2: {0: 1, 1: 1},
  258. }
  259. def test__triangle_count_by_adjacent_vertex_indices_multiple():
  260. surface = Surface()
  261. for i in range(5):
  262. surface.add_vertex(Vertex(i, 0, 0))
  263. surface.triangles.append(Triangle((0, 1, 2)))
  264. surface.triangles.append(Triangle((3, 1, 2)))
  265. assert surface._triangle_count_by_adjacent_vertex_indices() == {
  266. 0: {1: 1, 2: 1},
  267. 1: {0: 1, 2: 2, 3: 1},
  268. 2: {0: 1, 1: 2, 3: 1},
  269. 3: {1: 1, 2: 1},
  270. 4: {},
  271. }
  272. surface.triangles.append(Triangle((3, 4, 2)))
  273. assert surface._triangle_count_by_adjacent_vertex_indices() == {
  274. 0: {1: 1, 2: 1},
  275. 1: {0: 1, 2: 2, 3: 1},
  276. 2: {0: 1, 1: 2, 3: 2, 4: 1},
  277. 3: {1: 1, 2: 2, 4: 1},
  278. 4: {2: 1, 3: 1},
  279. }
  280. surface.triangles.append(Triangle((3, 0, 2)))
  281. assert surface._triangle_count_by_adjacent_vertex_indices() == {
  282. 0: {1: 1, 2: 2, 3: 1},
  283. 1: {0: 1, 2: 2, 3: 1},
  284. 2: {0: 2, 1: 2, 3: 3, 4: 1},
  285. 3: {0: 1, 1: 1, 2: 3, 4: 1},
  286. 4: {2: 1, 3: 1},
  287. }
  288. def test__triangle_count_by_adjacent_vertex_indices_real():
  289. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  290. counts = surface._triangle_count_by_adjacent_vertex_indices()
  291. assert len(counts) == len(surface.vertices)
  292. assert all(counts.values())
  293. assert all(
  294. count == 2
  295. for vertex_counts in counts.values()
  296. for count in vertex_counts.values()
  297. )
  298. assert (
  299. sum(
  300. count
  301. for vertex_counts in counts.values()
  302. for count in vertex_counts.values()
  303. )
  304. == len(surface.triangles) * 6
  305. )
  306. def test_find_borders_none():
  307. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  308. assert not list(surface.find_borders())
  309. def test_find_borders_single():
  310. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  311. single_index = surface.add_vertex(Vertex(0, 21, 42))
  312. borders = list(surface.find_borders())
  313. assert len(borders) == 1
  314. assert borders[0] == PolygonalCircuit((single_index,))
  315. def test_find_borders_singles():
  316. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  317. single_indices = [surface.add_vertex(Vertex(i, 21, 42)) for i in range(3)]
  318. borders = set(surface.find_borders())
  319. assert len(borders) == 3
  320. assert PolygonalCircuit((single_indices[0],)) in borders
  321. assert PolygonalCircuit((single_indices[1],)) in borders
  322. assert PolygonalCircuit((single_indices[2],)) in borders
  323. def test_find_borders_single_triangle_simple():
  324. surface = Surface()
  325. vertex_indices = [surface.add_vertex(Vertex(i, 21, 42)) for i in range(3)]
  326. surface.triangles.append(Triangle(vertex_indices))
  327. borders = set(surface.find_borders())
  328. assert len(borders) == 1
  329. assert PolygonalCircuit(vertex_indices) in borders
  330. def test_find_borders_single_triangle_real():
  331. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  332. vertex_indices = [surface.add_vertex(Vertex(i, 21, 42)) for i in range(3)]
  333. surface.triangles.append(Triangle(vertex_indices))
  334. borders = set(surface.find_borders())
  335. assert len(borders) == 1
  336. assert PolygonalCircuit(vertex_indices) in borders
  337. def test_find_borders_remove_triangle():
  338. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  339. triangle = surface.triangles.pop()
  340. borders = set(surface.find_borders())
  341. assert len(borders) == 1
  342. assert triangle in borders
  343. def test_find_borders_remove_non_adjacent_triangles():
  344. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  345. triangles = [surface.triangles.pop(), surface.triangles.pop()]
  346. borders = set(surface.find_borders())
  347. assert len(borders) == 2
  348. assert triangles[0] in borders
  349. assert triangles[1] in borders
  350. def test_find_borders_remove_adjacent_triangles():
  351. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  352. triangles = [surface.triangles.pop(), surface.triangles.pop()]
  353. triangles.append(surface.triangles.pop(270682))
  354. assert triangles[1] == Triangle((136141, 136142, 137076))
  355. assert triangles[2] == Triangle((136141, 136142, 135263))
  356. borders = set(surface.find_borders())
  357. assert len(borders) == 2
  358. assert triangles[0] in borders
  359. assert PolygonalCircuit((137076, 136141, 135263, 136142)) in borders
  360. surface.triangles.pop(270682)
  361. borders = set(surface.find_borders())
  362. assert len(borders) == 2
  363. assert triangles[0] in borders
  364. assert PolygonalCircuit((137076, 136141, 135263, 135264, 136142)) in borders
  365. surface.triangles.pop(274320)
  366. borders = set(surface.find_borders())
  367. assert len(borders) == 2
  368. assert PolygonalCircuit((136143, 138007, 138008, 137078)) in borders
  369. assert PolygonalCircuit((137076, 136141, 135263, 135264, 136142)) in borders
  370. @pytest.mark.parametrize(
  371. ("label_name", "expected_border_lens"),
  372. [
  373. ("precentral", [416]),
  374. ("postcentral", [395]),
  375. ("medialorbitofrontal", [6, 246]),
  376. # ...--2343 2347
  377. # \ / \
  378. # 2345 2348
  379. # / \ /
  380. # ...--2344 2346
  381. ("posteriorcingulate", [4, 190]),
  382. ("unknown", [3, 390]),
  383. ],
  384. )
  385. def test_find_borders_real(label_name, expected_border_lens):
  386. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  387. surface.load_annotation_file(ANNOTATION_FILE_PATH)
  388. (label,) = filter(
  389. lambda l: l.name == label_name, surface.annotation.labels.values()
  390. )
  391. surface.triangles = list(
  392. filter(
  393. lambda t: all(
  394. surface.annotation.vertex_label_index[vertex_idx] == label.index
  395. for vertex_idx in t.vertex_indices
  396. ),
  397. surface.triangles,
  398. )
  399. )
  400. surface.remove_unused_vertices()
  401. borders = list(surface.find_borders())
  402. border_lens = [len(b.vertex_indices) for b in borders]
  403. # self-crossing borders may or may not be split into
  404. # separate polygonal circuits
  405. assert sorted(border_lens) == expected_border_lens or sum(border_lens) == sum(
  406. expected_border_lens
  407. )
  408. def test__get_vertex_label_index():
  409. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  410. surface.load_annotation_file(ANNOTATION_FILE_PATH)
  411. # pylint: disable=protected-access
  412. assert surface._get_vertex_label_index(64290) == 22
  413. assert surface._get_vertex_label_index(72160) == 22
  414. assert surface._get_vertex_label_index(84028) == 24
  415. assert surface._get_vertex_label_index(97356) == 24
  416. assert surface._get_vertex_label_index(123173) == 27
  417. assert surface._get_vertex_label_index(140727) == 27
  418. assert surface._get_vertex_label_index(93859) == 28
  419. assert surface._get_vertex_label_index(78572) == 0
  420. assert surface._get_vertex_label_index(120377) == 0
  421. vertex_index = surface.add_vertex(Vertex(0.0, 21.0, 42.0))
  422. assert surface._get_vertex_label_index(vertex_index) is None
  423. del surface.annotation.vertex_label_index[140727]
  424. assert surface._get_vertex_label_index(140727) is None
  425. def test__find_label_border_segments():
  426. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  427. surface.load_annotation_file(ANNOTATION_FILE_PATH)
  428. (precentral_label,) = filter(
  429. lambda l: l.name == "precentral", surface.annotation.labels.values()
  430. )
  431. # pylint: disable=protected-access
  432. border_segments = set(surface._find_label_border_segments(precentral_label))
  433. assert len(border_segments) == 417
  434. assert LineSegment((33450, 32065)) in border_segments
  435. assert LineSegment((33454, 33450)) in border_segments
  436. for border_vertex_index in [33450, 33454, 32065]:
  437. assert (
  438. surface.annotation.vertex_label_index[border_vertex_index]
  439. == precentral_label.index
  440. )
  441. for other_vertex_index in [32064, 33449, 33455, 33449, 33455]:
  442. assert (
  443. LineSegment((other_vertex_index, border_vertex_index))
  444. not in border_segments
  445. )
  446. assert (
  447. LineSegment((border_vertex_index, other_vertex_index))
  448. not in border_segments
  449. )
  450. def test__find_label_border_segments_incomplete_annotation():
  451. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  452. surface.load_annotation_file(ANNOTATION_FILE_PATH)
  453. (precentral_label,) = filter(
  454. lambda l: l.name == "precentral", surface.annotation.labels.values()
  455. )
  456. # pylint: disable=protected-access
  457. assert surface._find_label_border_segments(precentral_label)
  458. surface.triangles.append(
  459. Triangle(
  460. [
  461. surface.add_vertex(Vertex(0.0, 21.0 * factor, 42.0 * factor))
  462. for factor in range(3)
  463. ]
  464. )
  465. )
  466. border_segments = set(surface._find_label_border_segments(precentral_label))
  467. assert len(border_segments) == 417
  468. def test_find_label_border_polygonal_chains_missing_annotation():
  469. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  470. annotation = Annotation.read(ANNOTATION_FILE_PATH)
  471. (precentral_label,) = filter(
  472. lambda l: l.name == "precentral", annotation.labels.values()
  473. )
  474. with pytest.raises(RuntimeError, match=r"\bload_annotation_file\b"):
  475. next(surface.find_label_border_polygonal_chains(precentral_label))
  476. def test_find_label_border_polygonal_chains():
  477. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  478. surface.load_annotation_file(ANNOTATION_FILE_PATH)
  479. (precentral_label,) = filter(
  480. lambda l: l.name == "precentral", surface.annotation.labels.values()
  481. )
  482. (border_chain,) = surface.find_label_border_polygonal_chains(precentral_label)
  483. vertex_indices_normalized = list(border_chain.normalized().vertex_indices)
  484. assert len(vertex_indices_normalized) == 418
  485. assert vertex_indices_normalized[66:73] == [
  486. 61044,
  487. 62119,
  488. 62118,
  489. 62107,
  490. 62118,
  491. 63255,
  492. 63264,
  493. ]
  494. assert vertex_indices_normalized[:4] == [32065, 32072, 32073, 32080]
  495. assert vertex_indices_normalized[-4:] == [36281, 34870, 33454, 33450]
  496. def test_find_label_border_polygonal_chains_long_leaf():
  497. surface = Surface()
  498. with unittest.mock.patch.object(
  499. surface,
  500. "_find_label_border_segments",
  501. return_value=[
  502. LineSegment((0, 1)),
  503. LineSegment((1, 2)),
  504. LineSegment((0, 3)),
  505. LineSegment((2, 3)),
  506. LineSegment((2, 4)),
  507. LineSegment((4, 5)), # leaf
  508. ],
  509. ):
  510. (border_chain,) = surface.find_label_border_polygonal_chains("dummy")
  511. assert list(border_chain.normalized().vertex_indices) == [0, 1, 2, 4, 5, 4, 2, 3]
  512. def test__unused_vertices():
  513. surface = Surface()
  514. assert not surface._unused_vertices()
  515. for i in range(4):
  516. surface.add_vertex(Vertex(i, i, i))
  517. assert surface._unused_vertices() == {0, 1, 2, 3}
  518. surface.triangles.append(Triangle((0, 2, 3)))
  519. assert surface._unused_vertices() == {1}
  520. surface.triangles.append(Triangle((0, 3, 1)))
  521. assert not surface._unused_vertices()
  522. del surface.triangles[0]
  523. assert surface._unused_vertices() == {2}
  524. def test__unused_vertices_real():
  525. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  526. assert not surface._unused_vertices()
  527. surface.triangles = list(
  528. filter(lambda t: 42 not in t.vertex_indices, surface.triangles)
  529. )
  530. assert surface._unused_vertices() == {42}
  531. def test_remove_unused_vertices_all():
  532. surface = Surface()
  533. for i in range(5):
  534. surface.add_vertex(Vertex(i, i, i))
  535. assert len(surface.vertices) == 5
  536. surface.remove_unused_vertices()
  537. assert not surface.vertices
  538. surface.remove_unused_vertices()
  539. assert not surface.vertices
  540. def test_remove_unused_vertices_almost_all():
  541. surface = Surface()
  542. for i in range(5):
  543. surface.add_vertex(Vertex(i, i, i))
  544. assert len(surface.vertices) == 5
  545. surface.triangles.append(Triangle((0, 2, 3)))
  546. surface.remove_unused_vertices()
  547. assert len(surface.vertices) == 3
  548. assert surface.vertices[0] == pytest.approx(Vertex(0, 0, 0))
  549. assert surface.vertices[1] == pytest.approx(Vertex(2, 2, 2))
  550. assert surface.vertices[2] == pytest.approx(Vertex(3, 3, 3))
  551. assert len(surface.triangles) == 1
  552. assert surface.triangles[0] == Triangle((0, 1, 2))
  553. del surface.triangles[0]
  554. surface.remove_unused_vertices()
  555. assert not surface.vertices
  556. def test_remove_unused_vertices_some():
  557. surface = Surface()
  558. for i in range(9):
  559. surface.add_vertex(Vertex(i, i, i))
  560. surface.triangles.append(Triangle((0, 2, 3)))
  561. surface.triangles.append(Triangle((3, 4, 5)))
  562. surface.triangles.append(Triangle((3, 2, 5)))
  563. surface.triangles.append(Triangle((3, 2, 8)))
  564. surface.remove_unused_vertices()
  565. assert len(surface.vertices) == 6
  566. assert surface.vertices[0] == pytest.approx(Vertex(0, 0, 0))
  567. assert surface.vertices[1] == pytest.approx(Vertex(2, 2, 2))
  568. assert surface.vertices[2] == pytest.approx(Vertex(3, 3, 3))
  569. assert surface.vertices[3] == pytest.approx(Vertex(4, 4, 4))
  570. assert surface.vertices[4] == pytest.approx(Vertex(5, 5, 5))
  571. assert surface.vertices[5] == pytest.approx(Vertex(8, 8, 8))
  572. assert len(surface.triangles) == 4
  573. assert surface.triangles[0] == Triangle((0, 1, 2))
  574. assert surface.triangles[1] == Triangle((2, 3, 4))
  575. assert surface.triangles[2] == Triangle((2, 1, 4))
  576. assert surface.triangles[3] == Triangle((2, 1, 5))
  577. surface.remove_unused_vertices()
  578. assert len(surface.triangles) == 4
  579. def test_remove_unused_vertices_none():
  580. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  581. assert len(surface.vertices) == 155622
  582. assert len(surface.triangles) == 311240
  583. surface.remove_unused_vertices()
  584. assert len(surface.vertices) == 155622
  585. assert len(surface.triangles) == 311240
  586. def test_remove_unused_vertices_single():
  587. surface = Surface.read_triangular(SURFACE_FILE_PATH)
  588. assert len(surface.vertices) == 155622
  589. assert len(surface.triangles) == 311240
  590. assert surface.triangles[-1] == Triangle((136143, 138007, 137078))
  591. surface.triangles = list(
  592. filter(lambda t: 42 not in t.vertex_indices, surface.triangles)
  593. )
  594. assert surface._unused_vertices() == {42}
  595. surface.remove_unused_vertices()
  596. assert len(surface.vertices) == 155622 - 1
  597. assert len(surface.triangles) == 311240 - 7
  598. assert surface.triangles[-1] == Triangle((136142, 138006, 137077))
  599. assert all(
  600. vertex_index < len(surface.vertices)
  601. for triangle in surface.triangles
  602. for vertex_index in triangle.vertex_indices
  603. )
  604. def test_select_vertices():
  605. surface = Surface()
  606. for i in range(4):
  607. surface.add_vertex(Vertex(i, i, i))
  608. assert numpy.allclose(
  609. surface.select_vertices([2, 1]), [surface.vertices[2], surface.vertices[1]]
  610. )
  611. assert numpy.allclose(
  612. surface.select_vertices([3, 2]), [surface.vertices[3], surface.vertices[2]]
  613. )
  614. assert numpy.allclose(surface.select_vertices((3, 2)), [[3, 3, 3], [2, 2, 2]])
  615. assert numpy.allclose(
  616. surface.select_vertices(filter(lambda i: i % 2 == 1, range(4))),
  617. [[1, 1, 1], [3, 3, 3]],
  618. )
  619. def test_unite_2():
  620. surface_a = Surface()
  621. for i in range(0, 4):
  622. surface_a.add_vertex(Vertex(i, i, i))
  623. surface_a.triangles.append(Triangle((0, 1, 2)))
  624. surface_a.triangles.append(Triangle((1, 2, 3)))
  625. surface_b = Surface()
  626. for i in range(10, 14):
  627. surface_b.add_vertex(Vertex(i, i, i))
  628. surface_b.triangles.append(Triangle((0, 1, 3)))
  629. surface_a_copy = copy.deepcopy(surface_a)
  630. surface_b_copy = copy.deepcopy(surface_b)
  631. union = Surface.unite([surface_a, surface_b])
  632. assert numpy.allclose(surface_a.vertices, surface_a_copy.vertices)
  633. assert numpy.allclose(surface_b.vertices, surface_b_copy.vertices)
  634. assert numpy.allclose(union.vertices[:4], surface_a.vertices)
  635. assert numpy.allclose(union.vertices[4:], surface_b.vertices)
  636. assert surface_a.triangles == surface_a_copy.triangles
  637. assert surface_b.triangles == surface_b_copy.triangles
  638. assert union.triangles[:2] == surface_a.triangles
  639. assert union.triangles[2:] == [Triangle((4, 5, 7))]
  640. def test_unite_3():
  641. surface_a = Surface()
  642. for i in range(0, 4):
  643. surface_a.add_vertex(Vertex(i, i, i))
  644. surface_a.triangles.append(Triangle((0, 1, 2)))
  645. surface_a.triangles.append(Triangle((1, 2, 3)))
  646. surface_b = Surface()
  647. for i in range(10, 14):
  648. surface_b.add_vertex(Vertex(i, i, i))
  649. surface_b.triangles.append(Triangle((0, 1, 3)))
  650. surface_c = Surface()
  651. for i in range(20, 23):
  652. surface_c.add_vertex(Vertex(i, i, i))
  653. surface_c.triangles.append(Triangle((0, 1, 2)))
  654. surface_c.triangles.append(Triangle((0, 1, 2)))
  655. union = Surface.unite(filter(None, [surface_a, surface_b, surface_c]))
  656. assert numpy.allclose(union.vertices[:4], surface_a.vertices)
  657. assert numpy.allclose(union.vertices[4:8], surface_b.vertices)
  658. assert numpy.allclose(union.vertices[8:], surface_c.vertices)
  659. assert union.triangles[:2] == surface_a.triangles
  660. assert union.triangles[2:3] == [Triangle((4, 5, 7))]
  661. assert union.triangles[3:] == [Triangle((8, 9, 10)), Triangle((8, 9, 10))]
  662. def test_unite_real():
  663. surface_a = Surface.read_triangular(SURFACE_FILE_PATH)
  664. surface_b = Surface()
  665. for i in range(5):
  666. surface_b.add_vertex(Vertex(i, i, i))
  667. surface_b.triangles.append(Triangle((0, 1, 3)))
  668. surface_b.triangles.append(Triangle((1, 3, 4)))
  669. union = Surface.unite((surface_a, surface_b))
  670. assert numpy.allclose(union.vertices[:-5], surface_a.vertices)
  671. assert numpy.allclose(union.vertices[-5:], surface_b.vertices)
  672. assert union.triangles[:-2] == surface_a.triangles
  673. assert union.triangles[-2:] == [
  674. Triangle((155622, 155623, 155625)),
  675. Triangle((155623, 155625, 155626)),
  676. ]
  677. assert union.creator == surface_a.creator
  678. assert union.creation_datetime == surface_a.creation_datetime
  679. assert union.using_old_real_ras == surface_a.using_old_real_ras
  680. assert union.volume_geometry_info == surface_a.volume_geometry_info
  681. assert union.command_lines == surface_a.command_lines
  682. assert union.annotation == surface_a.annotation