Browse Source

added Surface.read_triangular(), setup.py & dev pipenv

Fabian Peter Hammerle 5 years ago
commit
0303eb775c
5 changed files with 371 additions and 0 deletions
  1. 5 0
      .gitignore
  2. 15 0
      Pipfile
  3. 204 0
      Pipfile.lock
  4. 91 0
      freesurfer_surface/__init__.py
  5. 56 0
      setup.py

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+.coverage
+.ipynb_checkpoints/
+build/
+dist/
+version.py

+ 15 - 0
Pipfile

@@ -0,0 +1,15 @@
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+freesurfer-surface = {editable = true, path = "."}
+
+[dev-packages]
+pylint = ">=2.3.0,<3"
+pytest = "<5"
+pytest-cov = "<3,>=2"
+
+[requires]
+python_version = "3"

+ 204 - 0
Pipfile.lock

@@ -0,0 +1,204 @@
+{
+    "_meta": {
+        "hash": {
+            "sha256": "89cb6a7b074d4cd601cb477ebfc3f70743642b99a90279bfbf6562bc1e0b434c"
+        },
+        "pipfile-spec": 6,
+        "requires": {
+            "python_version": "3"
+        },
+        "sources": [
+            {
+                "name": "pypi",
+                "url": "https://pypi.org/simple",
+                "verify_ssl": true
+            }
+        ]
+    },
+    "default": {
+        "freesurfer-surface": {
+            "editable": true,
+            "path": "."
+        }
+    },
+    "develop": {
+        "astroid": {
+            "hashes": [
+                "sha256:6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4",
+                "sha256:b65db1bbaac9f9f4d190199bb8680af6f6f84fd3769a5ea883df8a91fe68b4c4"
+            ],
+            "version": "==2.2.5"
+        },
+        "atomicwrites": {
+            "hashes": [
+                "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4",
+                "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"
+            ],
+            "version": "==1.3.0"
+        },
+        "attrs": {
+            "hashes": [
+                "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79",
+                "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"
+            ],
+            "version": "==19.1.0"
+        },
+        "coverage": {
+            "hashes": [
+                "sha256:3684fabf6b87a369017756b551cef29e505cb155ddb892a7a29277b978da88b9",
+                "sha256:39e088da9b284f1bd17c750ac672103779f7954ce6125fd4382134ac8d152d74",
+                "sha256:3c205bc11cc4fcc57b761c2da73b9b72a59f8d5ca89979afb0c1c6f9e53c7390",
+                "sha256:465ce53a8c0f3a7950dfb836438442f833cf6663d407f37d8c52fe7b6e56d7e8",
+                "sha256:48020e343fc40f72a442c8a1334284620f81295256a6b6ca6d8aa1350c763bbe",
+                "sha256:5296fc86ab612ec12394565c500b412a43b328b3907c0d14358950d06fd83baf",
+                "sha256:5f61bed2f7d9b6a9ab935150a6b23d7f84b8055524e7be7715b6513f3328138e",
+                "sha256:68a43a9f9f83693ce0414d17e019daee7ab3f7113a70c79a3dd4c2f704e4d741",
+                "sha256:6b8033d47fe22506856fe450470ccb1d8ba1ffb8463494a15cfc96392a288c09",
+                "sha256:7ad7536066b28863e5835e8cfeaa794b7fe352d99a8cded9f43d1161be8e9fbd",
+                "sha256:7bacb89ccf4bedb30b277e96e4cc68cd1369ca6841bde7b005191b54d3dd1034",
+                "sha256:839dc7c36501254e14331bcb98b27002aa415e4af7ea039d9009409b9d2d5420",
+                "sha256:8f9a95b66969cdea53ec992ecea5406c5bd99c9221f539bca1e8406b200ae98c",
+                "sha256:932c03d2d565f75961ba1d3cec41ddde00e162c5b46d03f7423edcb807734eab",
+                "sha256:988529edadc49039d205e0aa6ce049c5ccda4acb2d6c3c5c550c17e8c02c05ba",
+                "sha256:998d7e73548fe395eeb294495a04d38942edb66d1fa61eb70418871bc621227e",
+                "sha256:9de60893fb447d1e797f6bf08fdf0dbcda0c1e34c1b06c92bd3a363c0ea8c609",
+                "sha256:9e80d45d0c7fcee54e22771db7f1b0b126fb4a6c0a2e5afa72f66827207ff2f2",
+                "sha256:a545a3dfe5082dc8e8c3eb7f8a2cf4f2870902ff1860bd99b6198cfd1f9d1f49",
+                "sha256:a5d8f29e5ec661143621a8f4de51adfb300d7a476224156a39a392254f70687b",
+                "sha256:aca06bfba4759bbdb09bf52ebb15ae20268ee1f6747417837926fae990ebc41d",
+                "sha256:bb23b7a6fd666e551a3094ab896a57809e010059540ad20acbeec03a154224ce",
+                "sha256:bfd1d0ae7e292105f29d7deaa9d8f2916ed8553ab9d5f39ec65bcf5deadff3f9",
+                "sha256:c62ca0a38958f541a73cf86acdab020c2091631c137bd359c4f5bddde7b75fd4",
+                "sha256:c709d8bda72cf4cd348ccec2a4881f2c5848fd72903c185f363d361b2737f773",
+                "sha256:c968a6aa7e0b56ecbd28531ddf439c2ec103610d3e2bf3b75b813304f8cb7723",
+                "sha256:df785d8cb80539d0b55fd47183264b7002077859028dfe3070cf6359bf8b2d9c",
+                "sha256:f406628ca51e0ae90ae76ea8398677a921b36f0bd71aab2099dfed08abd0322f",
+                "sha256:f46087bbd95ebae244a0eda01a618aff11ec7a069b15a3ef8f6b520db523dcf1",
+                "sha256:f8019c5279eb32360ca03e9fac40a12667715546eed5c5eb59eb381f2f501260",
+                "sha256:fc5f4d209733750afd2714e9109816a29500718b32dd9a5db01c0cb3a019b96a"
+            ],
+            "version": "==4.5.3"
+        },
+        "isort": {
+            "hashes": [
+                "sha256:1349c6f7c2a0f7539f5f2ace51a9a8e4a37086ce4de6f78f5f53fb041d0a3cd5",
+                "sha256:f09911f6eb114e5592abe635aded8bf3d2c3144ebcfcaf81ee32e7af7b7d1870"
+            ],
+            "version": "==4.3.18"
+        },
+        "lazy-object-proxy": {
+            "hashes": [
+                "sha256:118d53f8819f9457732dd0e418752f2850f395c5405b2e12485f52336e4ad0f5",
+                "sha256:495c583b363c3eded649e2c00177093f03f856f5c9f95b527420084a9ce17b9d",
+                "sha256:55fa9eab93482891ce97473e63610efdd9c8fa5c05cca9f60468c412e602e499",
+                "sha256:642fc0a9b61920669dab66e400f79f1b8b0e8f698dcde85f7e9ae5528dbcaf4a",
+                "sha256:7003959a170fde9b92936c38562810f94679c80608fb4b007e026b915bef8b27",
+                "sha256:7e63da94f5a1ddb0d2dcdb5d17ff4d1d33f51f3368bdf0475d5f56c0f3b99592",
+                "sha256:7fb11d33d99a374e4b0c3fb20128890b9cf784ca7e4b91ecbb191d34618bd9fe",
+                "sha256:8758715ea005afa293783797498d64f40ab14d1ded208b3e282760cde9512f1d",
+                "sha256:8995543f47a8b81962e384f12791114af9f4997be7a0db71abc40d2a2dfee961",
+                "sha256:91c7e1316116fedda36818ce7cd269378fffc4219781536eff441ea1e68e1caf",
+                "sha256:9b41ec246d31ca6a840dcf67673b2668adc5a095c64310d26d73292588563ea3",
+                "sha256:a8be3cfd7c3154e8d39276c627c5e7ee55d1f2094597b060ece99620ef9fe86b",
+                "sha256:afcab74f471652b643900e0862b31892ac5fe5a75e435b786a1825855f4effdf",
+                "sha256:d49a90c27074f44c8dc147d83e31140523948ee147b3248634c540e053caea58",
+                "sha256:d6957cadc9c079ef4697564af500d52fba6d14fb2f08d20ce92f52201fb77050",
+                "sha256:da7f2a6c82a11dc4e05bab73522f0d6dd4f3bbc8378cd4b0769137f342cdb3f0",
+                "sha256:f03a21f6f6e54778860122a620f70c8b148ec4ee175968782bcaaa94955a46f9",
+                "sha256:f6c718ffca055852479880debbe717da952fcfd60067a0ddb6fe3b053b1d4de0"
+            ],
+            "version": "==1.4.0"
+        },
+        "mccabe": {
+            "hashes": [
+                "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
+                "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
+            ],
+            "version": "==0.6.1"
+        },
+        "more-itertools": {
+            "hashes": [
+                "sha256:2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7",
+                "sha256:c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a"
+            ],
+            "markers": "python_version > '2.7'",
+            "version": "==7.0.0"
+        },
+        "pluggy": {
+            "hashes": [
+                "sha256:25a1bc1d148c9a640211872b4ff859878d422bccb59c9965e04eed468a0aa180",
+                "sha256:964cedd2b27c492fbf0b7f58b3284a09cf7f99b0f715941fb24a439b3af1bd1a"
+            ],
+            "version": "==0.11.0"
+        },
+        "py": {
+            "hashes": [
+                "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa",
+                "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"
+            ],
+            "version": "==1.8.0"
+        },
+        "pylint": {
+            "hashes": [
+                "sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09",
+                "sha256:723e3db49555abaf9bf79dc474c6b9e2935ad82230b10c1138a71ea41ac0fff1"
+            ],
+            "index": "pypi",
+            "version": "==2.3.1"
+        },
+        "pytest": {
+            "hashes": [
+                "sha256:136632a40451162cdfc18fe4d7ecc5d169b558a3d4bbb1603d4005308a42fd03",
+                "sha256:62b129bf8368554ca7a942cbdb57ea26aafef46cc65bc317cdac3967e54483a3"
+            ],
+            "index": "pypi",
+            "version": "==4.4.2"
+        },
+        "pytest-cov": {
+            "hashes": [
+                "sha256:2b097cde81a302e1047331b48cadacf23577e431b61e9c6f49a1170bbe3d3da6",
+                "sha256:e00ea4fdde970725482f1f35630d12f074e121a23801aabf2ae154ec6bdd343a"
+            ],
+            "index": "pypi",
+            "version": "==2.7.1"
+        },
+        "six": {
+            "hashes": [
+                "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
+                "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
+            ],
+            "version": "==1.12.0"
+        },
+        "typed-ast": {
+            "hashes": [
+                "sha256:132eae51d6ef3ff4a8c47c393a4ef5ebf0d1aecc96880eb5d6c8ceab7017cc9b",
+                "sha256:18141c1484ab8784006c839be8b985cfc82a2e9725837b0ecfa0203f71c4e39d",
+                "sha256:2baf617f5bbbfe73fd8846463f5aeafc912b5ee247f410700245d68525ec584a",
+                "sha256:3d90063f2cbbe39177e9b4d888e45777012652d6110156845b828908c51ae462",
+                "sha256:4304b2218b842d610aa1a1d87e1dc9559597969acc62ce717ee4dfeaa44d7eee",
+                "sha256:4983ede548ffc3541bae49a82675996497348e55bafd1554dc4e4a5d6eda541a",
+                "sha256:5315f4509c1476718a4825f45a203b82d7fdf2a6f5f0c8f166435975b1c9f7d4",
+                "sha256:6cdfb1b49d5345f7c2b90d638822d16ba62dc82f7616e9b4caa10b72f3f16649",
+                "sha256:7b325f12635598c604690efd7a0197d0b94b7d7778498e76e0710cd582fd1c7a",
+                "sha256:8d3b0e3b8626615826f9a626548057c5275a9733512b137984a68ba1598d3d2f",
+                "sha256:8f8631160c79f53081bd23446525db0bc4c5616f78d04021e6e434b286493fd7",
+                "sha256:912de10965f3dc89da23936f1cc4ed60764f712e5fa603a09dd904f88c996760",
+                "sha256:b010c07b975fe853c65d7bbe9d4ac62f1c69086750a574f6292597763781ba18",
+                "sha256:c908c10505904c48081a5415a1e295d8403e353e0c14c42b6d67f8f97fae6616",
+                "sha256:c94dd3807c0c0610f7c76f078119f4ea48235a953512752b9175f9f98f5ae2bd",
+                "sha256:ce65dee7594a84c466e79d7fb7d3303e7295d16a83c22c7c4037071b059e2c21",
+                "sha256:eaa9cfcb221a8a4c2889be6f93da141ac777eb8819f077e1d09fb12d00a09a93",
+                "sha256:f3376bc31bad66d46d44b4e6522c5c21976bf9bca4ef5987bb2bf727f4506cbb",
+                "sha256:f9202fa138544e13a4ec1a6792c35834250a85958fde1251b6a22e07d1260ae7"
+            ],
+            "markers": "implementation_name == 'cpython'",
+            "version": "==1.3.5"
+        },
+        "wrapt": {
+            "hashes": [
+                "sha256:4aea003270831cceb8a90ff27c4031da6ead7ec1886023b80ce0dfe0adf61533"
+            ],
+            "version": "==1.11.1"
+        }
+    }
+}

+ 91 - 0
freesurfer_surface/__init__.py

@@ -0,0 +1,91 @@
+"""
+Python Library to Read Surface Files in Freesurfer's TriangularSurface Format
+
+compatible with Freesurfer's MRISwriteTriangularSurface()
+https://github.com/freesurfer/freesurfer/blob/release_6_0_0/include/mrisurf.h#L1281
+https://github.com/freesurfer/freesurfer/blob/release_6_0_0/utils/mrisurf.c
+https://raw.githubusercontent.com/freesurfer/freesurfer/release_6_0_0/utils/mrisurf.c
+
+Freesurfer
+https://surfer.nmr.mgh.harvard.edu/
+
+>>> from freesurfer_surface import Surface
+>>>
+>>> surface = Surface.read_triangular('bert/surf/lh.pial'))
+"""
+
+import collections
+import re
+import struct
+import typing
+
+try:
+    from freesurfer_surface.version import __version__
+except ImportError:  # pragma: no cover
+    __version__ = None
+
+Vertex = collections.namedtuple('Vertex', ['right', 'anterior', 'superior'])
+
+
+class Surface:
+
+    _MAGIC_NUMBER = b'\xff\xff\xfe'
+
+    _TAG_CMDLINE = b'\x00\x00\x00\x03'
+    _TAG_OLD_SURF_GEOM = b'\x00\x00\x00\x14'
+    _TAG_OLD_USEREALRAS = b'\x00\x00\x00\x02'
+
+    def __init__(self):
+        self.creator = None
+        self.vertices = []
+        self.triangles_vertex_indices = []
+        # self._triangles = []
+        self.using_old_real_ras = False
+        self.volume_geometry_info = None
+        self.command_lines = []
+
+    @classmethod
+    def _read_cmdlines(cls, stream: typing.BinaryIO) -> typing.Iterator[str]:
+        while True:
+            tag = stream.read(4)
+            if not tag:
+                return
+            assert tag == cls._TAG_CMDLINE  # might be TAG_GROUP_AVG_SURFACE_AREA
+            # TAGwrite
+            # https://github.com/freesurfer/freesurfer/blob/release_6_0_0/utils/tags.c#L94
+            str_length, = struct.unpack('>Q', stream.read(8))
+            yield stream.read(str_length - 1)
+            assert stream.read(1) == b'\x00'
+
+    def _read_triangular(self, stream: typing.BinaryIO):
+        assert stream.read(3) == self._MAGIC_NUMBER
+        self.creator = re.match(rb'^created by (\w+) on .* \d{4}\n',
+                                stream.readline()).groups()
+        assert stream.read(1) == b'\n'
+        # fwriteInt
+        # https://github.com/freesurfer/freesurfer/blob/release_6_0_0/utils/fio.c#L290
+        vertices_num, triangles_num = struct.unpack('>II', stream.read(4 * 2))
+        self.vertices = [Vertex(*struct.unpack('>fff', stream.read(4 * 3)))
+                         for _ in range(vertices_num)]
+        self.triangles_vertex_indices = [struct.unpack('>III', stream.read(4 * 3))
+                                         for _ in range(triangles_num)]
+        assert all(vertex_idx < vertices_num
+                   for triangle_vertex_index in self.triangles_vertex_indices
+                   for vertex_idx in triangle_vertex_index)
+        assert stream.read(4) == self._TAG_OLD_USEREALRAS
+        using_old_real_ras, = struct.unpack('>I', stream.read(4))
+        assert using_old_real_ras in [0, 1], using_old_real_ras
+        self.using_old_real_ras = bool(using_old_real_ras)
+        assert stream.read(4) == self._TAG_OLD_SURF_GEOM
+        # writeVolGeom
+        # https://github.com/freesurfer/freesurfer/blob/release_6_0_0/utils/transform.c#L368
+        self.volume_geometry_info = [stream.readline() for _ in range(8)]
+        self.command_lines = list(self._read_cmdlines(stream))
+
+    @classmethod
+    def read_triangular(cls, surface_file_path: str) -> 'Surface':
+        surface = cls()
+        with open(surface_file_path, 'rb') as surface_file:
+            # pylint: disable=protected-access
+            surface._read_triangular(surface_file)
+        return surface

+ 56 - 0
setup.py

@@ -0,0 +1,56 @@
+import os
+
+import setuptools
+
+import freesurfer_surface
+
+
+LONG_DESCRIPTION = freesurfer_surface.__doc__.strip()
+
+setuptools.setup(
+    name='freesurfer-surface',
+    use_scm_version={
+        'write_to': os.path.join('freesurfer_surface', 'version.py'),
+        # `version` triggers pylint C0103
+        'write_to_template': "__version__ = '{version}'\n",
+    },
+    description=LONG_DESCRIPTION.split(sep='\n', maxsplit=1)[0],
+    long_description=LONG_DESCRIPTION,
+    author='Fabian Peter Hammerle',
+    author_email='fabian@hammerle.me',
+    # url='https://github.com/fphammerle/freesurfer-surface',
+    # TODO add license
+    keywords=[
+        'brain',
+        'freesurfer',
+        'mesh',
+        'neuroimaging',
+        'reader',
+        'surface',
+        'triangle',
+        'vertex',
+    ],
+    classifiers=[
+        'Development Status :: 3 - Alpha',
+        'Intended Audience :: Healthcare Industry',
+        'Intended Audience :: Science/Research',
+        # TODO
+        # 'Programming Language :: Python :: 3.4',
+        # 'Programming Language :: Python :: 3.5',
+        # 'Programming Language :: Python :: 3.6',
+        # 'Programming Language :: Python :: 3.7',
+        'Topic :: Scientific/Engineering :: Information Analysis',
+        'Topic :: Scientific/Engineering :: Medical Science Apps.',
+        'Topic :: Utilities',
+    ],
+    packages=setuptools.find_packages(),
+    install_requires=[],
+    setup_requires=[
+        'setuptools_scm',
+    ],
+    tests_require=[
+        'pylint>=2.3.0,<3',
+        'pytest<5',
+        'pytest-cov<3,>=2',
+    ],
+)