Browse Source

CorticalParcellationStats.read: stricter parsing to avoid silent errors; added "BrainVolStatsFixed" header

Fabian Peter Hammerle 4 years ago
parent
commit
3a2481c161
4 changed files with 41 additions and 16 deletions
  1. 2 1
      .pylintrc
  2. 5 0
      CHANGELOG.md
  3. 32 14
      freesurfer_stats/__init__.py
  4. 2 1
      tests/test_cortical_parcellation_stats.py

+ 2 - 1
.pylintrc

@@ -1,3 +1,4 @@
 [MESSAGES CONTROL]
 
-disable=missing-docstring
+disable=bad-continuation, # black (will supersede yapf) https://github.com/PyCQA/pylint/pull/3571
+        missing-docstring

+ 5 - 0
CHANGELOG.md

@@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 ## [Unreleased]
+### Fixed
+- fixed parsing of `BrainVolStatsFixed` header
+  (https://github.com/fphammerle/freesurfer-stats/pull/1,
+  https://github.com/fphammerle/freesurfer-stats/pull/9,
+  @soichih)
 
 ## [1.1.0] - 2020-05-06
 ### Added

+ 32 - 14
freesurfer_stats/__init__.py

@@ -50,6 +50,7 @@ import datetime
 import re
 import typing
 
+import numpy
 import pandas
 
 from freesurfer_stats.version import __version__
@@ -114,9 +115,25 @@ class CorticalParcellationStats:
     @classmethod
     def _format_column_name(cls, name: str, unit: typing.Optional[str]) -> str:
         column_name = name.lower()
-        if unit not in ['unitless', 'NA']:
-            column_name += '_' + unit
-        return cls._COLUMN_NAMES_NON_SAFE_REGEX.sub('_', column_name)
+        if unit not in ["unitless", "NA"]:
+            column_name += "_" + unit
+        return cls._COLUMN_NAMES_NON_SAFE_REGEX.sub("_", column_name)
+
+    @classmethod
+    def _parse_whole_brain_measurements_line(
+        cls, line: str,
+    ) -> typing.Tuple[str, numpy.ndarray]:
+        match = cls._GENERAL_MEASUREMENTS_REGEX.match(line)
+        if not match:
+            raise ValueError("unexpected line: {!r}".format(line))
+        key, name, value, unit = match.groups()
+        if (
+            key == "SupraTentorialVolNotVent"
+            and name.lower() == "supratentorial volume"
+        ):
+            name += " Without Ventricles"
+        column_name = cls._format_column_name(name, unit)
+        return column_name, pandas.to_numeric([value], errors="raise")
 
     @classmethod
     def _read_column_attributes(cls, num: int, stream: typing.TextIO) \
@@ -140,17 +157,18 @@ class CorticalParcellationStats:
         self._read_headers(stream)
         self.whole_brain_measurements = pandas.DataFrame()
         line = self._read_header_line(stream)
-        while not line.startswith('NTableCols'):
-            match = self._GENERAL_MEASUREMENTS_REGEX.match(line)
-            if match:
-                key, name, value, unit = match.groups()
-                if key == 'SupraTentorialVolNotVent' and name.lower() == 'supratentorial volume':
-                    name += ' Without Ventricles'
-                column_name = self._format_column_name(name, unit)
-                assert column_name not in self.whole_brain_measurements, \
-                    (key, name, column_name, self.whole_brain_measurements)
-                self.whole_brain_measurements[column_name] \
-                    = pandas.to_numeric([value], errors='raise')
+        while not line.startswith("NTableCols"):
+            if line.startswith("BrainVolStatsFixed"):
+                # https://surfer.nmr.mgh.harvard.edu/fswiki/BrainVolStatsFixed
+                assert (
+                    line.startswith("BrainVolStatsFixed see ")
+                    or line == "BrainVolStatsFixed-NotNeeded because voxelvolume=1mm3"
+                )
+                self.headers["BrainVolStatsFixed"] = line[len("BrainVolStatsFixed-") :]
+            else:
+                column_name, value = self._parse_whole_brain_measurements_line(line)
+                assert column_name not in self.whole_brain_measurements, column_name
+                self.whole_brain_measurements[column_name] = value
             line = self._read_header_line(stream)
         columns = self._read_column_attributes(
             int(line[len('NTableCols '):]), stream)

+ 2 - 1
tests/test_cortical_parcellation_stats.py

@@ -158,7 +158,8 @@ from freesurfer_stats import CorticalParcellationStats
        'mrisurf.c-cvs_version': '7.0.0',
        'subjectname': 'output',
        'sysname': 'Linux',
-       'user': 'hayashis'},
+       'user': 'hayashis',
+       'BrainVolStatsFixed': 'NotNeeded because voxelvolume=1mm3'},
       'right',
       {'white_surface_total_area_mm^2': 83579.2,
        'mean_thickness_mm': 2.35815,