Browse Source

added CorticalParcellationStats.read() reading stats file headers

Fabian Peter Hammerle 4 years ago
parent
commit
994cf548d4
8 changed files with 310 additions and 2 deletions
  1. 6 0
      .gitignore
  2. 1 0
      Pipfile
  3. 81 2
      Pipfile.lock
  4. 30 0
      README.rst
  5. 85 0
      freesurfer_stats/__init__.py
  6. 63 0
      setup.py
  7. 3 0
      tests/conftest.py
  8. 41 0
      tests/test_cortical_parcellation_stats.py

+ 6 - 0
.gitignore

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

+ 1 - 0
Pipfile

@@ -4,6 +4,7 @@ verify_ssl = true
 name = "pypi"
 
 [packages]
+freesurfer-stats = {editable = true, path = "."}
 
 [dev-packages]
 isort = "*"

+ 81 - 2
Pipfile.lock

@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "4d36755dcb7b9b978cd9245d8344b9982f3ef6f4dfd998b195d2e85a97ea0953"
+            "sha256": "887083d56b0181f0c91a4a797c8d73595ac931841d04af70fd435f18133f1bf5"
         },
         "pipfile-spec": 6,
         "requires": {
@@ -15,7 +15,86 @@
             }
         ]
     },
-    "default": {},
+    "default": {
+        "freesurfer-stats": {
+            "editable": true,
+            "path": "."
+        },
+        "numpy": {
+            "hashes": [
+                "sha256:0778076e764e146d3078b17c24c4d89e0ecd4ac5401beff8e1c87879043a0633",
+                "sha256:141c7102f20abe6cf0d54c4ced8d565b86df4d3077ba2343b61a6db996cefec7",
+                "sha256:14270a1ee8917d11e7753fb54fc7ffd1934f4d529235beec0b275e2ccf00333b",
+                "sha256:27e11c7a8ec9d5838bc59f809bfa86efc8a4fd02e58960fa9c49d998e14332d5",
+                "sha256:2a04dda79606f3d2f760384c38ccd3d5b9bb79d4c8126b67aff5eb09a253763e",
+                "sha256:3c26010c1b51e1224a3ca6b8df807de6e95128b0908c7e34f190e7775455b0ca",
+                "sha256:52c40f1a4262c896420c6ea1c6fda62cf67070e3947e3307f5562bd783a90336",
+                "sha256:6e4f8d9e8aa79321657079b9ac03f3cf3fd067bf31c1cca4f56d49543f4356a5",
+                "sha256:7242be12a58fec245ee9734e625964b97cf7e3f2f7d016603f9e56660ce479c7",
+                "sha256:7dc253b542bfd4b4eb88d9dbae4ca079e7bf2e2afd819ee18891a43db66c60c7",
+                "sha256:94f5bd885f67bbb25c82d80184abbf7ce4f6c3c3a41fbaa4182f034bba803e69",
+                "sha256:a89e188daa119ffa0d03ce5123dee3f8ffd5115c896c2a9d4f0dbb3d8b95bfa3",
+                "sha256:ad3399da9b0ca36e2f24de72f67ab2854a62e623274607e37e0ce5f5d5fa9166",
+                "sha256:b0348be89275fd1d4c44ffa39530c41a21062f52299b1e3ee7d1c61f060044b8",
+                "sha256:b5554368e4ede1856121b0dfa35ce71768102e4aa55e526cb8de7f374ff78722",
+                "sha256:cbddc56b2502d3f87fda4f98d948eb5b11f36ff3902e17cb6cc44727f2200525",
+                "sha256:d79f18f41751725c56eceab2a886f021d70fd70a6188fd386e29a045945ffc10",
+                "sha256:dc2ca26a19ab32dc475dbad9dfe723d3a64c835f4c23f625c2b6566ca32b9f29",
+                "sha256:dd9bcd4f294eb0633bb33d1a74febdd2b9018b8b8ed325f861fffcd2c7660bb8",
+                "sha256:e8baab1bc7c9152715844f1faca6744f2416929de10d7639ed49555a85549f52",
+                "sha256:ec31fe12668af687b99acf1567399632a7c47b0e17cfb9ae47c098644ef36797",
+                "sha256:f12b4f7e2d8f9da3141564e6737d79016fe5336cc92de6814eba579744f65b0a",
+                "sha256:f58ac38d5ca045a377b3b377c84df8175ab992c970a53332fa8ac2373df44ff7"
+            ],
+            "version": "==1.16.4"
+        },
+        "pandas": {
+            "hashes": [
+                "sha256:071e42b89b57baa17031af8c6b6bbd2e9a5c68c595bc6bf9adabd7a9ed125d3b",
+                "sha256:17450e25ae69e2e6b303817bdf26b2cd57f69595d8550a77c308be0cd0fd58fa",
+                "sha256:17916d818592c9ec891cbef2e90f98cc85e0f1e89ed0924c9b5220dc3209c846",
+                "sha256:2538f099ab0e9f9c9d09bbcd94b47fd889bad06dc7ae96b1ed583f1dc1a7a822",
+                "sha256:366f30710172cb45a6b4f43b66c220653b1ea50303fbbd94e50571637ffb9167",
+                "sha256:42e5ad741a0d09232efbc7fc648226ed93306551772fc8aecc6dce9f0e676794",
+                "sha256:4e718e7f395ba5bfe8b6f6aaf2ff1c65a09bb77a36af6394621434e7cc813204",
+                "sha256:4f919f409c433577a501e023943e582c57355d50a724c589e78bc1d551a535a2",
+                "sha256:4fe0d7e6438212e839fc5010c78b822664f1a824c0d263fd858f44131d9166e2",
+                "sha256:5149a6db3e74f23dc3f5a216c2c9ae2e12920aa2d4a5b77e44e5b804a5f93248",
+                "sha256:627594338d6dd995cfc0bacd8e654cd9e1252d2a7c959449228df6740d737eb8",
+                "sha256:83c702615052f2a0a7fb1dd289726e29ec87a27272d775cb77affe749cca28f8",
+                "sha256:8c872f7fdf3018b7891e1e3e86c55b190e6c5cee70cab771e8f246c855001296",
+                "sha256:90f116086063934afd51e61a802a943826d2aac572b2f7d55caaac51c13db5b5",
+                "sha256:a3352bacac12e1fc646213b998bce586f965c9d431773d9e91db27c7c48a1f7d",
+                "sha256:bcdd06007cca02d51350f96debe51331dec429ac8f93930a43eb8fb5639e3eb5",
+                "sha256:c1bd07ebc15285535f61ddd8c0c75d0d6293e80e1ee6d9a8d73f3f36954342d0",
+                "sha256:c9a4b7c55115eb278c19aa14b34fcf5920c8fe7797a09b7b053ddd6195ea89b3",
+                "sha256:cc8fc0c7a8d5951dc738f1c1447f71c43734244453616f32b8aa0ef6013a5dfb",
+                "sha256:d7b460bc316064540ce0c41c1438c416a40746fd8a4fb2999668bf18f3c4acf1"
+            ],
+            "version": "==0.24.2"
+        },
+        "python-dateutil": {
+            "hashes": [
+                "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
+                "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"
+            ],
+            "version": "==2.8.0"
+        },
+        "pytz": {
+            "hashes": [
+                "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda",
+                "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141"
+            ],
+            "version": "==2019.1"
+        },
+        "six": {
+            "hashes": [
+                "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
+                "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
+            ],
+            "version": "==1.12.0"
+        }
+    },
     "develop": {
         "astroid": {
             "hashes": [

+ 30 - 0
README.rst

@@ -0,0 +1,30 @@
+freesurfer-stats
+================
+
+TODO add badges
+
+Python Library to Read FreeSurfer's cortical parcellation anatomical statistics
+
+Freesurfer https://surfer.nmr.mgh.harvard.edu/
+
+Install
+-------
+
+.. code:: sh
+
+    pip3 install --user freesurfer-stats
+
+Releases follow the `semantic versioning <https://semver.org/>` scheme.
+
+Usage
+-----
+
+.. code:: python
+
+    >>> stats = CorticalParcellationStats.read('tests/subjects/fabian/stats/lh.aparc.DKTatlas.stats')
+    >>> stats.creation_time.isoformat()
+    '2019-05-09T21:05:54+00:00'
+    >>> stats.cvs_version
+    'Id: mris_anatomical_stats.c,v 1.79 2016/03/14 15:15:34 greve Exp'
+    >>> stats.cmdline[:64]
+    'mris_anatomical_stats -th3 -mgz -cortex ../label/lh.cortex.label'

+ 85 - 0
freesurfer_stats/__init__.py

@@ -0,0 +1,85 @@
+"""
+Python Library to Read FreeSurfer's cortical parcellation anatomical statistics
+([lh]h.aparc(.*)?.stats)
+
+Freesurfer
+https://surfer.nmr.mgh.harvard.edu/
+
+>>> stats = CorticalParcellationStats.read('tests/subjects/fabian/stats/lh.aparc.DKTatlas.stats')
+>>> stats.creation_time.isoformat()
+'2019-05-09T21:05:54+00:00'
+>>> stats.cvs_version
+'Id: mris_anatomical_stats.c,v 1.79 2016/03/14 15:15:34 greve Exp'
+>>> stats.cmdline[:64]
+'mris_anatomical_stats -th3 -mgz -cortex ../label/lh.cortex.label'
+"""
+
+import datetime
+import re
+import typing
+
+import pandas
+
+from freesurfer_stats.version import __version__
+
+
+class CorticalParcellationStats:
+
+    def __init__(self):
+        self.creation_time = None  # type: typing.Optional[datetime.datetime]
+        self.generating_program = None  # type: typing.Optional[str]
+        self.cvs_version = None  # type: typing.Optional[str]
+        self.mrisurf_ccvs_version = None  # type: typing.Optional[str]
+        self.cmdline = None  # type: typing.Optional[str]
+        self.sysname = None  # type: typing.Optional[str]
+        self.hostname = None  # type: typing.Optional[str]
+        self.machine = None  # type: typing.Optional[str]
+        self.user = None  # type: typing.Optional[str]
+
+    def _read_headers(self, stream: typing.TextIO) -> None:
+        creation_time_str = stream.readline()[len('# CreationTime '):].rstrip()
+        self.creation_time = datetime.datetime.strptime(
+            creation_time_str, '%Y/%m/%d-%H:%M:%S-%Z')
+        if self.creation_time.tzinfo is None:
+            assert creation_time_str.endswith('-GMT')
+            self.creation_time = self.creation_time.replace(
+                tzinfo=datetime.timezone.utc)
+        while True:
+            line = stream.readline().rstrip()
+            if line == '#':
+                break
+            prefix, attr_name, attr_value = line.split(' ', maxsplit=2)
+            assert prefix == '#'
+            attr_value = attr_value.lstrip()
+            if attr_name == 'generating_program':
+                self.generating_program = attr_value
+            elif attr_name == 'cvs_version':
+                self.cvs_version = attr_value.strip('$').rstrip()
+            elif attr_name == 'mrisurf.c-cvs_version':
+                self.mrisurf_ccvs_version = attr_value.strip('$').rstrip()
+            elif attr_name == 'cmdline':
+                self.cmdline = attr_value
+            elif attr_name == 'sysname':
+                self.sysname = attr_value
+            elif attr_name == 'hostname':
+                self.hostname = attr_value
+            elif attr_name == 'machine':
+                self.machine = attr_value
+            elif attr_name == 'user':
+                self.user = attr_value
+            else:
+                raise ValueError(attr_name)
+
+    def _read(self, stream: typing.TextIO) -> None:
+        assert stream.readline().rstrip() \
+            == '# Table of FreeSurfer cortical parcellation anatomical statistics'
+        assert stream.readline().rstrip() == '#'
+        self._read_headers(stream)
+
+    @classmethod
+    def read(cls, path: str) -> 'CorticalParcellationStats':
+        stats = cls()
+        with open(path, 'r') as stream:
+            # pylint: disable=protected-access
+            stats._read(stream)
+        return stats

+ 63 - 0
setup.py

@@ -0,0 +1,63 @@
+import os
+
+import setuptools
+
+with open('README.rst', 'r') as readme:
+    LONG_DESCRIPTION = readme.read()
+
+setuptools.setup(
+    name='freesurfer-stats',
+    use_scm_version={
+        'write_to': os.path.join('freesurfer_stats', 'version.py'),
+        # `version` triggers pylint C0103
+        'write_to_template': "__version__ = '{version}'\n",
+    },
+    description="Python Library to Read FreeSurfer's cortical parcellation anatomical statistics",
+    long_description=LONG_DESCRIPTION,
+    author='Fabian Peter Hammerle',
+    author_email='fabian@hammerle.me',
+    url='https://github.com/fphammerle/freesurfer-stats',
+    # TODO add license
+    keywords=[
+        'anatomy',
+        'aparc',
+        'area',
+        'brain',
+        'cortex',
+        'dataframe',
+        'freesurfer',
+        'mris_anatomical_stats',
+        'neuroimaging',
+        'pandas',
+        'parcellation',
+        'reader',
+        'statistics',
+        'surface',
+        'volume',
+    ],
+    classifiers=[
+        'Development Status :: 3 - Alpha',
+        'Intended Audience :: Healthcare Industry',
+        'Intended Audience :: Science/Research',
+        '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=[
+        # TODO add lower version constraint
+        'pandas<1',
+    ],
+    setup_requires=[
+        'setuptools_scm',
+    ],
+    tests_require=[
+        'pylint>=2.3.0,<3',
+        'pytest-cov<3,>=2',
+        'pytest-timeout<2',
+        'pytest<5',
+    ],
+)

+ 3 - 0
tests/conftest.py

@@ -0,0 +1,3 @@
+import os
+
+SUBJECTS_DIR = os.path.join(os.path.dirname(__file__), 'subjects')

+ 41 - 0
tests/test_cortical_parcellation_stats.py

@@ -0,0 +1,41 @@
+import datetime
+import os
+
+from conftest import SUBJECTS_DIR
+from freesurfer_stats import CorticalParcellationStats
+
+
+def test_read_dktatlas():
+    stats = CorticalParcellationStats.read(os.path.join(
+        SUBJECTS_DIR, 'fabian', 'stats', 'lh.aparc.DKTatlas.stats'))
+    assert stats.creation_time == datetime.datetime(
+        2019, 5, 9, 21, 5, 54, tzinfo=datetime.timezone.utc)
+    assert stats.generating_program == 'mris_anatomical_stats'
+    assert stats.cvs_version == 'Id: mris_anatomical_stats.c,v 1.79 2016/03/14 15:15:34 greve Exp'
+    assert stats.mrisurf_ccvs_version \
+        == 'Id: mrisurf.c,v 1.781.2.6 2016/12/27 16:47:14 zkaufman Exp'
+    assert stats.cmdline == 'mris_anatomical_stats -th3 -mgz -cortex ../label/lh.cortex.label' \
+        ' -f ../stats/lh.aparc.DKTatlas.stats -b -a ../label/lh.aparc.DKTatlas.annot' \
+        ' -c ../label/aparc.annot.DKTatlas.ctab fabian lh white'
+    assert stats.sysname == 'Linux'
+    assert stats.hostname == 'another-hostname'
+    assert stats.machine == 'x86_64'
+    assert stats.user == 'some-username'
+
+
+def test_read_pial():
+    stats = CorticalParcellationStats.read(os.path.join(
+        SUBJECTS_DIR, 'fabian', 'stats', 'rh.aparc.pial.stats'))
+    assert stats.creation_time == datetime.datetime(
+        2019, 5, 9, 21, 3, 42, tzinfo=datetime.timezone.utc)
+    assert stats.generating_program == 'mris_anatomical_stats'
+    assert stats.cvs_version == 'Id: mris_anatomical_stats.c,v 1.79 2016/03/14 15:15:34 greve Exp'
+    assert stats.mrisurf_ccvs_version \
+        == 'Id: mrisurf.c,v 1.781.2.6 2016/12/27 16:47:14 zkaufman Exp'
+    assert stats.cmdline == 'mris_anatomical_stats -th3 -mgz -cortex ../label/rh.cortex.label' \
+        ' -f ../stats/rh.aparc.pial.stats -b -a ../label/rh.aparc.annot' \
+        ' -c ../label/aparc.annot.ctab fabian rh pial'
+    assert stats.sysname == 'Linux'
+    assert stats.hostname == 'some-hostname'
+    assert stats.machine == 'x86_64'
+    assert stats.user == 'some-username'