浏览代码

CorticalParcellationStats: read measurements of structures

Fabian Peter Hammerle 4 年之前
父节点
当前提交
b7e7b6a979

+ 16 - 0
README.rst

@@ -35,3 +35,19 @@ Usage
     (1670487.274486, 'mm^3')
     >>> stats.whole_brain_measurements['White Surface Total Area']
     (98553.0, 'mm^2')
+    >>> stats.structure_measurements['postcentral']
+    {'Structure Name': 'postcentral',
+     'Number of Vertices': 8102,
+     'Surface Area': 5258.0,
+     'Gray Matter Volume': 12037.0,
+     'Average Thickness': 2.109,
+     'Thickness StdDev': 0.568,
+     ...}
+    >>> stats.structure_measurement_units
+    {'Structure Name': None,
+     'Number of Vertices': None,
+     'Surface Area': 'mm^2',
+     'Gray Matter Volume': 'mm^3',
+     'Average Thickness': 'mm',
+     'Thickness StdDev': 'mm',
+     ...}

+ 71 - 1
freesurfer_stats/__init__.py

@@ -18,6 +18,22 @@ https://surfer.nmr.mgh.harvard.edu/
 (1670487.274486, 'mm^3')
 >>> stats.whole_brain_measurements['White Surface Total Area']
 (98553.0, 'mm^2')
+>>> stats.structure_measurements['postcentral']
+{'Structure Name': 'postcentral',
+ 'Number of Vertices': 8102,
+ 'Surface Area': 5258.0,
+ 'Gray Matter Volume': 12037.0,
+ 'Average Thickness': 2.109,
+ 'Thickness StdDev': 0.568,
+ ...}
+>>> stats.structure_measurement_units
+{'Structure Name': None,
+ 'Number of Vertices': None,
+ 'Surface Area': 'mm^2',
+ 'Gray Matter Volume': 'mm^3',
+ 'Average Thickness': 'mm',
+ 'Thickness StdDev': 'mm',
+ ...}
 """
 
 import datetime
@@ -40,17 +56,29 @@ class CorticalParcellationStats:
             = {}  # type: typing.Dict[str, typing.Union[str, datetime.datetime]]
         self.whole_brain_measurements \
             = {}  # type: typing.Dict[str, typing.Tuple[float, int]]
+        self.structure_measurements \
+            = {}  # type: typing.Dict[str, typing.Dict[str, typing.Union[str, int, float]]]
+        self.structure_measurement_units \
+            = {}  # type: typing.Dict[str, typing.Union[str, None]]
 
     @property
     def hemisphere(self) -> str:
         return self._HEMISPHERE_PREFIX_TO_SIDE[self.headers['hemi']]
 
     @staticmethod
-    def _read_header_line(stream: typing.TextIO) -> None:
+    def _read_header_line(stream: typing.TextIO) -> str:
         line = stream.readline()
         assert line.startswith('# ')
         return line[2:].rstrip()
 
+    @classmethod
+    def _read_column_header_line(cls, stream: typing.TextIO) -> typing.Tuple[int, str, str]:
+        line = cls._read_header_line(stream)
+        assert line.startswith('TableCol'), line
+        line = line[len('TableCol '):].lstrip()
+        index, key, value = line.split(maxsplit=2)
+        return int(index), key, value
+
     def _read_headers(self, stream: typing.TextIO) -> None:
         self.headers = {}
         while True:
@@ -74,6 +102,27 @@ class CorticalParcellationStats:
                         attr_value, '%Y/%m/%d %H:%M:%S')
                 self.headers[attr_name] = attr_value
 
+    @staticmethod
+    def _filter_unit(unit: str) -> typing.Union[str, None]:
+        if unit in ['unitless', 'NA']:
+            return None
+        return unit
+
+    @classmethod
+    def _read_column_attributes(cls, num: int, stream: typing.TextIO) \
+            -> typing.List[typing.Dict[str, str]]:
+        columns = []
+        for column_index in range(1, int(num) + 1):
+            column_attrs = {}
+            for _ in range(3):
+                column_index_line, key, value \
+                    = cls._read_column_header_line(stream)
+                assert column_index_line == column_index
+                assert key not in column_attrs
+                column_attrs[key] = value
+            columns.append(column_attrs)
+        return columns
+
     def _read(self, stream: typing.TextIO) -> None:
         assert stream.readline().rstrip() \
             == '# Table of FreeSurfer cortical parcellation anatomical statistics'
@@ -90,6 +139,27 @@ class CorticalParcellationStats:
                 (key, name, self.whole_brain_measurements)
             self.whole_brain_measurements[name] = (float(value), unit)
             line = self._read_header_line(stream)
+        columns = self._read_column_attributes(
+            int(line[len('NTableCols '):]), stream)
+        assert self._read_header_line(stream) \
+            == 'ColHeaders ' + ' '.join(c['ColHeader'] for c in columns)
+        assert columns[0]['ColHeader'] == 'StructName'
+        column_names = [c['FieldName'] for c in columns]
+        self.structure_measurements = {}
+        for line in stream:
+            values = line.rstrip().split()
+            assert len(values) == len(column_names)
+            struct_name = values[0]
+            assert struct_name not in self.structure_measurements
+            for column_index, column_attrs in enumerate(columns):
+                if column_attrs['ColHeader'] in ['NumVert', 'FoldInd']:
+                    values[column_index] = int(values[column_index])
+                elif column_attrs['ColHeader'] != 'StructName':
+                    values[column_index] = float(values[column_index])
+            self.structure_measurements[struct_name] \
+                = dict(zip(column_names, values))
+        self.structure_measurement_units = {
+            c['FieldName']: self._filter_unit(c['Units']) for c in columns}
 
     @classmethod
     def read(cls, path: str) -> 'CorticalParcellationStats':

+ 19 - 0
tests/conftest.py

@@ -1,3 +1,22 @@
 import os
 
+import pytest
+
 SUBJECTS_DIR = os.path.join(os.path.dirname(__file__), 'subjects')
+
+
+def assert_approx_equal(value_a, value_b):
+    if isinstance(value_a, dict):
+        assert isinstance(value_b, dict)
+        assert value_a.keys() == value_b.keys()
+        for key, sub_a in value_a.items():
+            assert_approx_equal(sub_a, value_b[key])
+    elif isinstance(value_a, tuple):
+        assert len(value_a) == len(value_b)
+        for sub_a, sub_b in zip(value_a, value_b):
+            assert_approx_equal(sub_a, sub_b)
+    elif isinstance(value_a, float):
+        assert isinstance(value_b, float)
+        assert value_a == pytest.approx(value_b)
+    else:
+        assert value_a == value_b

+ 63 - 0
tests/subjects/fabian/stats/lh.aparc.DKTatlas.stats.short

@@ -0,0 +1,63 @@
+# Table of FreeSurfer cortical parcellation anatomical statistics 
+# 
+# CreationTime 2019/05/09-21:05:54-GMT
+# 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 2019/05/09 23:05:40
+# Measure Cortex, NumVert, Number of Vertices, 147577, unitless
+# Measure Cortex, WhiteSurfArea, White Surface Total Area, 98553, mm^2
+# Measure Cortex, MeanThickness, Mean Thickness, 2.50462, mm
+# Measure BrainSeg, BrainSegVol, Brain Segmentation Volume, 1327432.000000, mm^3
+# Measure BrainSegNotVent, BrainSegVolNotVent, Brain Segmentation Volume Without Ventricles, 1316285.000000, mm^3
+# Measure BrainSegNotVentSurf, BrainSegVolNotVentSurf, Brain Segmentation Volume Without Ventricles from Surf, 1315572.548920, mm^3
+# Measure Cortex, CortexVol Total cortical gray matter volume, 553998.311189, mm^3
+# Measure SupraTentorial, SupraTentorialVol, Supratentorial volume, 1172669.548920, mm^3
+# Measure SupraTentorialNotVent, SupraTentorialVolNotVent, Supratentorial volume, 1164180.548920, mm^3
+# Measure EstimatedTotalIntraCranialVol, eTIV, Estimated Total Intracranial Volume, 1670487.274486, mm^3
+# NTableCols 10
+# TableCol  1 ColHeader StructName
+# TableCol  1 FieldName Structure Name
+# TableCol  1 Units     NA
+# TableCol  2 ColHeader NumVert
+# TableCol  2 FieldName Number of Vertices
+# TableCol  2 Units     unitless
+# TableCol  3 ColHeader SurfArea
+# TableCol  3 FieldName Surface Area
+# TableCol  3 Units     mm^2
+# TableCol  4 ColHeader GrayVol
+# TableCol  4 FieldName Gray Matter Volume
+# TableCol  4 Units     mm^3
+# TableCol  5 ColHeader ThickAvg 
+# TableCol  5 FieldName Average Thickness
+# TableCol  5 Units     mm
+# TableCol  6 ColHeader ThickStd
+# TableCol  6 FieldName Thickness StdDev
+# TableCol  6 Units     mm 
+# TableCol  7 ColHeader MeanCurv
+# TableCol  7 FieldName Integrated Rectified Mean Curvature
+# TableCol  7 Units     mm^-1
+# TableCol  8 ColHeader GausCurv 
+# TableCol  8 FieldName Integrated Rectified Gaussian Curvature
+# TableCol  8 Units     mm^-2
+# TableCol  9 ColHeader  FoldInd
+# TableCol  9 FieldName  Folding Index 
+# TableCol  9 Units      unitless 
+# TableCol 10 ColHeader CurvInd
+# TableCol 10 FieldName Intrinsic Curvature Index
+# TableCol 10 Units     unitless
+# ColHeaders StructName NumVert SurfArea GrayVol ThickAvg ThickStd MeanCurv GausCurv FoldInd CurvInd
+caudalanteriorcingulate                  2061   1472   4258  2.653 0.644     0.135     0.020       27     1.6
+caudalmiddlefrontal                      4451   3039   8239  2.456 0.486     0.116     0.020       42     3.7
+insula                                   3439   2304   7594  3.193 0.620     0.116     0.027       33     3.5

+ 62 - 0
tests/subjects/fabian/stats/rh.aparc.pial.stats.short

@@ -0,0 +1,62 @@
+# Table of FreeSurfer cortical parcellation anatomical statistics 
+# 
+# CreationTime 2019/05/09-21:03:42-GMT
+# 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 2019/05/09 22:27:28
+# Measure Cortex, NumVert, Number of Vertices, 149612, unitless
+# Measure Cortex, PialSurfArea, Pial Surface Total Area, 121260, mm^2
+# Measure Cortex, MeanThickness, Mean Thickness, 2.4817, mm
+# Measure BrainSeg, BrainSegVol, Brain Segmentation Volume, 1327432.000000, mm^3
+# Measure BrainSegNotVent, BrainSegVolNotVent, Brain Segmentation Volume Without Ventricles, 1316285.000000, mm^3
+# Measure BrainSegNotVentSurf, BrainSegVolNotVentSurf, Brain Segmentation Volume Without Ventricles from Surf, 1315572.548920, mm^3
+# Measure Cortex, CortexVol Total cortical gray matter volume, 553998.311189, mm^3
+# Measure SupraTentorial, SupraTentorialVol, Supratentorial volume, 1172669.548920, mm^3
+# Measure SupraTentorialNotVent, SupraTentorialVolNotVent, Supratentorial volume, 1164180.548920, mm^3
+# Measure EstimatedTotalIntraCranialVol, eTIV, Estimated Total Intracranial Volume, 1670487.274486, mm^3
+# NTableCols 10
+# TableCol  1 ColHeader StructName
+# TableCol  1 FieldName Structure Name
+# TableCol  1 Units     NA
+# TableCol  2 ColHeader NumVert
+# TableCol  2 FieldName Number of Vertices
+# TableCol  2 Units     unitless
+# TableCol  3 ColHeader SurfArea
+# TableCol  3 FieldName Surface Area
+# TableCol  3 Units     mm^2
+# TableCol  4 ColHeader GrayVol
+# TableCol  4 FieldName Gray Matter Volume
+# TableCol  4 Units     mm^3
+# TableCol  5 ColHeader ThickAvg 
+# TableCol  5 FieldName Average Thickness
+# TableCol  5 Units     mm
+# TableCol  6 ColHeader ThickStd
+# TableCol  6 FieldName Thickness StdDev
+# TableCol  6 Units     mm 
+# TableCol  7 ColHeader MeanCurv
+# TableCol  7 FieldName Integrated Rectified Mean Curvature
+# TableCol  7 Units     mm^-1
+# TableCol  8 ColHeader GausCurv 
+# TableCol  8 FieldName Integrated Rectified Gaussian Curvature
+# TableCol  8 Units     mm^-2
+# TableCol  9 ColHeader  FoldInd
+# TableCol  9 FieldName  Folding Index 
+# TableCol  9 Units      unitless 
+# TableCol 10 ColHeader CurvInd
+# TableCol 10 FieldName Intrinsic Curvature Index
+# TableCol 10 Units     unitless
+# ColHeaders StructName NumVert SurfArea GrayVol ThickAvg ThickStd MeanCurv GausCurv FoldInd CurvInd
+bankssts                                 1344    825   2171  2.436 0.381     0.115     0.028       19     1.7
+transversetemporal                        651    545   1061  2.251 0.317     0.110     0.021        3     0.6

+ 133 - 67
tests/test_cortical_parcellation_stats.py

@@ -3,76 +3,142 @@ import os
 
 import pytest
 
-from conftest import SUBJECTS_DIR
+from conftest import SUBJECTS_DIR, assert_approx_equal
 from freesurfer_stats import CorticalParcellationStats
 
 
-@pytest.mark.parametrize(('path', 'headers', 'hemisphere', 'whole_brain_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, whole_brain_measurements):
+@pytest.mark.parametrize(
+    ('path', 'headers', 'hemisphere',
+     'whole_brain_measurements', 'structure_measurements'),
+    [(os.path.join(SUBJECTS_DIR, 'fabian', 'stats', 'lh.aparc.DKTatlas.stats.short'),
+      {'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')},
+      {'caudalanteriorcingulate': {
+          'Structure Name': 'caudalanteriorcingulate',
+          'Number of Vertices': 2061,
+          'Surface Area': 1472.0,
+          'Gray Matter Volume': 4258.0,
+          'Average Thickness': 2.653,
+          'Thickness StdDev': 0.644,
+          'Integrated Rectified Mean Curvature': 0.135,
+          'Integrated Rectified Gaussian Curvature': 0.020,
+          'Folding Index': 27,
+          'Intrinsic Curvature Index': 1.6},
+       'caudalmiddlefrontal': {
+           'Structure Name': 'caudalmiddlefrontal',
+           'Number of Vertices': 4451,
+           'Surface Area': 3039.0,
+           'Gray Matter Volume': 8239.0,
+           'Average Thickness': 2.456,
+           'Thickness StdDev': 0.486,
+           'Integrated Rectified Mean Curvature': 0.116,
+           'Integrated Rectified Gaussian Curvature': 0.020,
+           'Folding Index': 42,
+           'Intrinsic Curvature Index': 3.7},
+       'insula': {
+           'Structure Name': 'insula',
+           'Number of Vertices': 3439,
+           'Surface Area': 2304.0,
+           'Gray Matter Volume': 7594.0,
+           'Average Thickness': 3.193,
+           'Thickness StdDev': 0.620,
+           'Integrated Rectified Mean Curvature': 0.116,
+           'Integrated Rectified Gaussian Curvature': 0.027,
+           'Folding Index': 33,
+           'Intrinsic Curvature Index': 3.5}}),
+     (os.path.join(
+         SUBJECTS_DIR, 'fabian', 'stats', 'rh.aparc.pial.stats.short'),
+      {'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')},
+      {'bankssts': {
+          'Structure Name': 'bankssts',
+          'Number of Vertices': 1344,
+          'Surface Area': 825.0,
+          'Gray Matter Volume': 2171.0,
+          'Average Thickness': 2.436,
+          'Thickness StdDev': 0.381,
+          'Integrated Rectified Mean Curvature': 0.115,
+          'Integrated Rectified Gaussian Curvature': 0.028,
+          'Folding Index': 19,
+          'Intrinsic Curvature Index': 1.7},
+       'transversetemporal': {
+           'Structure Name': 'transversetemporal',
+           'Number of Vertices': 651,
+           'Surface Area': 545.0,
+           'Gray Matter Volume': 1061.0,
+           'Average Thickness': 2.251,
+           'Thickness StdDev': 0.317,
+           'Integrated Rectified Mean Curvature': 0.110,
+           'Integrated Rectified Gaussian Curvature': 0.021,
+           'Folding Index': 3,
+           'Intrinsic Curvature Index': 0.6}})],
+)
+def test_read(path, headers, hemisphere, whole_brain_measurements, structure_measurements):
     stats = CorticalParcellationStats.read(path)
     assert headers == stats.headers
     assert hemisphere == stats.hemisphere
-    assert whole_brain_measurements.keys() == stats.whole_brain_measurements.keys()
-    for key, expected_value_unit in whole_brain_measurements.items():
-        expected_value, expected_unit = expected_value_unit
-        assert expected_value \
-            == pytest.approx(stats.whole_brain_measurements[key][0])
-        assert expected_unit == stats.whole_brain_measurements[key][1]
+    assert_approx_equal(whole_brain_measurements,
+                        stats.whole_brain_measurements)
+    assert stats.structure_measurement_units == {
+        'Structure Name': None,
+        'Number of Vertices': None,
+        'Surface Area': 'mm^2',
+        'Gray Matter Volume': 'mm^3',
+        'Average Thickness': 'mm',
+        'Thickness StdDev': 'mm',
+        'Integrated Rectified Mean Curvature': 'mm^-1',
+        'Integrated Rectified Gaussian Curvature': 'mm^-2',
+        'Folding Index': None,
+        'Intrinsic Curvature Index': None,
+    }
+    assert_approx_equal(structure_measurements, stats.structure_measurements)