__init__.py 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. """
  2. Python Library to Read Surface Files in Freesurfer's TriangularSurface Format
  3. compatible with Freesurfer's MRISwriteTriangularSurface()
  4. https://github.com/freesurfer/freesurfer/blob/release_6_0_0/include/mrisurf.h#L1281
  5. https://github.com/freesurfer/freesurfer/blob/release_6_0_0/utils/mrisurf.c
  6. https://raw.githubusercontent.com/freesurfer/freesurfer/release_6_0_0/utils/mrisurf.c
  7. Freesurfer
  8. https://surfer.nmr.mgh.harvard.edu/
  9. >>> from freesurfer_surface import Surface
  10. >>>
  11. >>> surface = Surface.read_triangular('bert/surf/lh.pial'))
  12. """
  13. import collections
  14. import re
  15. import struct
  16. import typing
  17. try:
  18. from freesurfer_surface.version import __version__
  19. except ImportError: # pragma: no cover
  20. __version__ = None
  21. Vertex = collections.namedtuple('Vertex', ['right', 'anterior', 'superior'])
  22. class Surface:
  23. _MAGIC_NUMBER = b'\xff\xff\xfe'
  24. _TAG_CMDLINE = b'\x00\x00\x00\x03'
  25. _TAG_OLD_SURF_GEOM = b'\x00\x00\x00\x14'
  26. _TAG_OLD_USEREALRAS = b'\x00\x00\x00\x02'
  27. def __init__(self):
  28. self.creator = None
  29. self.vertices = []
  30. self.triangles_vertex_indices = []
  31. # self._triangles = []
  32. self.using_old_real_ras = False
  33. self.volume_geometry_info = None
  34. self.command_lines = []
  35. @classmethod
  36. def _read_cmdlines(cls, stream: typing.BinaryIO) -> typing.Iterator[str]:
  37. while True:
  38. tag = stream.read(4)
  39. if not tag:
  40. return
  41. assert tag == cls._TAG_CMDLINE # might be TAG_GROUP_AVG_SURFACE_AREA
  42. # TAGwrite
  43. # https://github.com/freesurfer/freesurfer/blob/release_6_0_0/utils/tags.c#L94
  44. str_length, = struct.unpack('>Q', stream.read(8))
  45. yield stream.read(str_length - 1)
  46. assert stream.read(1) == b'\x00'
  47. def _read_triangular(self, stream: typing.BinaryIO):
  48. assert stream.read(3) == self._MAGIC_NUMBER
  49. self.creator = re.match(rb'^created by (\w+) on .* \d{4}\n',
  50. stream.readline()).groups()
  51. assert stream.read(1) == b'\n'
  52. # fwriteInt
  53. # https://github.com/freesurfer/freesurfer/blob/release_6_0_0/utils/fio.c#L290
  54. vertices_num, triangles_num = struct.unpack('>II', stream.read(4 * 2))
  55. self.vertices = [Vertex(*struct.unpack('>fff', stream.read(4 * 3)))
  56. for _ in range(vertices_num)]
  57. self.triangles_vertex_indices = [struct.unpack('>III', stream.read(4 * 3))
  58. for _ in range(triangles_num)]
  59. assert all(vertex_idx < vertices_num
  60. for triangle_vertex_index in self.triangles_vertex_indices
  61. for vertex_idx in triangle_vertex_index)
  62. assert stream.read(4) == self._TAG_OLD_USEREALRAS
  63. using_old_real_ras, = struct.unpack('>I', stream.read(4))
  64. assert using_old_real_ras in [0, 1], using_old_real_ras
  65. self.using_old_real_ras = bool(using_old_real_ras)
  66. assert stream.read(4) == self._TAG_OLD_SURF_GEOM
  67. # writeVolGeom
  68. # https://github.com/freesurfer/freesurfer/blob/release_6_0_0/utils/transform.c#L368
  69. self.volume_geometry_info = [stream.readline() for _ in range(8)]
  70. self.command_lines = list(self._read_cmdlines(stream))
  71. @classmethod
  72. def read_triangular(cls, surface_file_path: str) -> 'Surface':
  73. surface = cls()
  74. with open(surface_file_path, 'rb') as surface_file:
  75. # pylint: disable=protected-access
  76. surface._read_triangular(surface_file)
  77. return surface