Browse Source

CorticalParcellationStats._read(): load whole-brain measurements

Fabian Peter Hammerle 4 years ago
parent
commit
ac9a01b2b2
3 changed files with 106 additions and 56 deletions
  1. 4 0
      README.rst
  2. 31 8
      freesurfer_stats/__init__.py
  3. 71 48
      tests/test_cortical_parcellation_stats.py

+ 4 - 0
README.rst

@@ -31,3 +31,7 @@ Usage
     'mris_anatomical_stats -th3 -mgz -cortex ../label/lh.cortex.label'
     >>> stats.hemisphere
     'left'
+    >>> stats.general_measurements['Estimated Total Intracranial Volume']
+    (1670487.274486, 'mm^3')
+    >>> stats.general_measurements['White Surface Total Area']
+    (98553.0, 'mm^2')

+ 31 - 8
freesurfer_stats/__init__.py

@@ -14,7 +14,10 @@ https://surfer.nmr.mgh.harvard.edu/
 >>> stats.headers['cmdline'][:64]
 'mris_anatomical_stats -th3 -mgz -cortex ../label/lh.cortex.label'
 >>> stats.hemisphere
-'left'
+>>> stats.general_measurements['Estimated Total Intracranial Volume']
+(1670487.274486, 'mm^3')
+>>> stats.general_measurements['White Surface Total Area']
+(98553.0, 'mm^2')
 """
 
 import datetime
@@ -29,24 +32,33 @@ from freesurfer_stats.version import __version__
 class CorticalParcellationStats:
 
     _HEMISPHERE_PREFIX_TO_SIDE = {'lh': 'left', 'rh': 'right'}
+    _GENERAL_MEASUREMENTS_REGEX = re.compile(
+        r'^Measure \S+, ([^,\s]+),? ([^,]+), ([\d\.]+), (\S+)$')
 
     def __init__(self):
-        # type: typing.Dict[str, typing.Union[str, datetime.datetime]]
-        self.headers = {}
+        self.headers \
+            = {}  # type: typing.Dict[str, typing.Union[str, datetime.datetime]]
+        self.general_measurements \
+            = {}  # type: typing.Dict[str, typing.Tuple[float, int]]
 
     @property
     def hemisphere(self) -> str:
         return self._HEMISPHERE_PREFIX_TO_SIDE[self.headers['hemi']]
 
+    @staticmethod
+    def _read_header_line(stream: typing.TextIO) -> None:
+        line = stream.readline()
+        assert line.startswith('# ')
+        return line[2:].rstrip()
+
     def _read_headers(self, stream: typing.TextIO) -> None:
         self.headers = {}
         while True:
-            line = stream.readline().rstrip()
-            if line.startswith('# Measure'):
+            line = self._read_header_line(stream)
+            if line.startswith('Measure'):
                 break
-            elif line != '#':
-                prefix, attr_name, attr_value = line.split(' ', maxsplit=2)
-                assert prefix == '#'
+            elif line:
+                attr_name, attr_value = line.split(' ', maxsplit=1)
                 attr_value = attr_value.lstrip()
                 if attr_name in ['cvs_version', 'mrisurf.c-cvs_version']:
                     attr_value = attr_value.strip('$').rstrip()
@@ -67,6 +79,17 @@ class CorticalParcellationStats:
             == '# Table of FreeSurfer cortical parcellation anatomical statistics'
         assert stream.readline().rstrip() == '#'
         self._read_headers(stream)
+        self.general_measurements = {}
+        line = self._read_header_line(stream)
+        while not line.startswith('NTableCols'):
+            key, name, value, unit \
+                = self._GENERAL_MEASUREMENTS_REGEX.match(line).groups()
+            if key == 'SupraTentorialVolNotVent' and name.lower() == 'supratentorial volume':
+                name += ' Without Ventricles'
+            assert name not in self.general_measurements, \
+                (key, name, self.general_measurements)
+            self.general_measurements[name] = (float(value), unit)
+            line = self._read_header_line(stream)
 
     @classmethod
     def read(cls, path: str) -> 'CorticalParcellationStats':

+ 71 - 48
tests/test_cortical_parcellation_stats.py

@@ -1,55 +1,78 @@
 import datetime
 import os
 
+import pytest
+
 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.headers == {
-        'CreationTime': datetime.datetime(2019, 5, 9, 21, 5, 54, tzinfo=datetime.timezone.utc),
-        'generating_program': 'mris_anatomical_stats',
-        'cvs_version': 'Id: mris_anatomical_stats.c,v 1.79 2016/03/14 15:15:34 greve Exp',
-        'mrisurf.c-cvs_version': 'Id: mrisurf.c,v 1.781.2.6 2016/12/27 16:47:14 zkaufman Exp',
-        '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',
-        'sysname': 'Linux',
-        'hostname': 'another-hostname',
-        'machine': 'x86_64',
-        'user': 'some-username',
-        'SUBJECTS_DIR': '/home/some-username/freesurfer-subjects',
-        'anatomy_type': 'surface',
-        'subjectname': 'fabian',
-        'hemi': 'lh',
-        'AnnotationFile': '../label/lh.aparc.DKTatlas.annot',
-        'AnnotationFileTimeStamp': datetime.datetime(2019, 5, 9, 23, 5, 40),
-    }
-    assert stats.hemisphere == 'left'
-
-
-def test_read_pial():
-    stats = CorticalParcellationStats.read(os.path.join(
-        SUBJECTS_DIR, 'fabian', 'stats', 'rh.aparc.pial.stats'))
-    assert stats.headers == {
-        'CreationTime': datetime.datetime(2019, 5, 9, 21, 3, 42, tzinfo=datetime.timezone.utc),
-        'generating_program': 'mris_anatomical_stats',
-        'cvs_version': 'Id: mris_anatomical_stats.c,v 1.79 2016/03/14 15:15:34 greve Exp',
-        'mrisurf.c-cvs_version': 'Id: mrisurf.c,v 1.781.2.6 2016/12/27 16:47:14 zkaufman Exp',
-        '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',
-        'sysname': 'Linux',
-        'hostname': 'some-hostname',
-        'machine': 'x86_64',
-        'user': 'some-username',
-        'SUBJECTS_DIR': '/home/some-username/freesurfer-subjects',
-        'anatomy_type': 'surface',
-        'subjectname': 'fabian',
-        'hemi': 'rh',
-        'AnnotationFile': '../label/rh.aparc.annot',
-        'AnnotationFileTimeStamp': datetime.datetime(2019, 5, 9, 22, 27, 28),
-    }
-    assert stats.hemisphere == 'right'
+@pytest.mark.parametrize(('path', 'headers', 'hemisphere', 'general_measurements'), [
+    (os.path.join(SUBJECTS_DIR, 'fabian', 'stats', 'lh.aparc.DKTatlas.stats'),
+     {'CreationTime': datetime.datetime(2019, 5, 9, 21, 5, 54, tzinfo=datetime.timezone.utc),
+      'generating_program': 'mris_anatomical_stats',
+      'cvs_version': 'Id: mris_anatomical_stats.c,v 1.79 2016/03/14 15:15:34 greve Exp',
+      'mrisurf.c-cvs_version': 'Id: mrisurf.c,v 1.781.2.6 2016/12/27 16:47:14 zkaufman Exp',
+      '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',
+      'sysname': 'Linux',
+      'hostname': 'another-hostname',
+      'machine': 'x86_64',
+      'user': 'some-username',
+      'SUBJECTS_DIR': '/home/some-username/freesurfer-subjects',
+      'anatomy_type': 'surface',
+      'subjectname': 'fabian',
+      'hemi': 'lh',
+      'AnnotationFile': '../label/lh.aparc.DKTatlas.annot',
+      'AnnotationFileTimeStamp': datetime.datetime(2019, 5, 9, 23, 5, 40)},
+     'left',
+     {'White Surface Total Area': (98553, 'mm^2'),
+      'Mean Thickness': (2.50462, 'mm'),
+      'Brain Segmentation Volume': (1327432.000000, 'mm^3'),
+      'Brain Segmentation Volume Without Ventricles': (1316285.000000, 'mm^3'),
+      'Brain Segmentation Volume Without Ventricles from Surf': (1315572.548920, 'mm^3'),
+      'Total cortical gray matter volume': (553998.311189, 'mm^3'),
+      'Supratentorial volume': (1172669.548920, 'mm^3'),
+      'Supratentorial volume Without Ventricles': (1164180.548920, 'mm^3'),
+      'Estimated Total Intracranial Volume': (1670487.274486, 'mm^3')}),
+    (os.path.join(
+        SUBJECTS_DIR, 'fabian', 'stats', 'rh.aparc.pial.stats'),
+     {'CreationTime': datetime.datetime(2019, 5, 9, 21, 3, 42, tzinfo=datetime.timezone.utc),
+      'generating_program': 'mris_anatomical_stats',
+      'cvs_version': 'Id: mris_anatomical_stats.c,v 1.79 2016/03/14 15:15:34 greve Exp',
+      'mrisurf.c-cvs_version': 'Id: mrisurf.c,v 1.781.2.6 2016/12/27 16:47:14 zkaufman Exp',
+      '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',
+      'sysname': 'Linux',
+      'hostname': 'some-hostname',
+      'machine': 'x86_64',
+      'user': 'some-username',
+      'SUBJECTS_DIR': '/home/some-username/freesurfer-subjects',
+      'anatomy_type': 'surface',
+      'subjectname': 'fabian',
+      'hemi': 'rh',
+      'AnnotationFile': '../label/rh.aparc.annot',
+      'AnnotationFileTimeStamp': datetime.datetime(2019, 5, 9, 22, 27, 28)},
+     'right',
+     {'Pial Surface Total Area': (121260, 'mm^2'),
+      'Mean Thickness': (2.4817, 'mm'),
+      'Brain Segmentation Volume': (1327432.000000, 'mm^3'),
+      'Brain Segmentation Volume Without Ventricles': (1316285.000000, 'mm^3'),
+      'Brain Segmentation Volume Without Ventricles from Surf': (1315572.548920, 'mm^3'),
+      'Total cortical gray matter volume': (553998.311189, 'mm^3'),
+      'Supratentorial volume': (1172669.548920, 'mm^3'),
+      'Supratentorial volume Without Ventricles': (1164180.548920, 'mm^3'),
+      'Estimated Total Intracranial Volume': (1670487.274486, 'mm^3')}),
+])
+def test_read(path, headers, hemisphere, general_measurements):
+    stats = CorticalParcellationStats.read(path)
+    assert headers == stats.headers
+    assert hemisphere == stats.hemisphere
+    assert general_measurements.keys() == stats.general_measurements.keys()
+    for key, expected_value_unit in general_measurements.items():
+        expected_value, expected_unit = expected_value_unit
+        assert expected_value \
+            == pytest.approx(stats.general_measurements[key][0])
+        assert expected_unit == stats.general_measurements[key][1]