__init__.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. """
  2. Python Library to Read FreeSurfer's cortical parcellation anatomical statistics
  3. ([lh]h.aparc(.*)?.stats)
  4. Freesurfer
  5. https://surfer.nmr.mgh.harvard.edu/
  6. >>> from freesurfer_stats import CorticalParcellationStats
  7. >>> stats = CorticalParcellationStats.read('tests/subjects/fabian/stats/lh.aparc.DKTatlas.stats')
  8. >>> stats.headers['CreationTime'].isoformat()
  9. '2019-05-09T21:05:54+00:00'
  10. >>> stats.headers['cvs_version']
  11. 'Id: mris_anatomical_stats.c,v 1.79 2016/03/14 15:15:34 greve Exp'
  12. >>> stats.headers['cmdline'][:64]
  13. 'mris_anatomical_stats -th3 -mgz -cortex ../label/lh.cortex.label'
  14. >>> stats.hemisphere
  15. >>> stats.general_measurements['Estimated Total Intracranial Volume']
  16. (1670487.274486, 'mm^3')
  17. >>> stats.general_measurements['White Surface Total Area']
  18. (98553.0, 'mm^2')
  19. """
  20. import datetime
  21. import re
  22. import typing
  23. import pandas
  24. from freesurfer_stats.version import __version__
  25. class CorticalParcellationStats:
  26. _HEMISPHERE_PREFIX_TO_SIDE = {'lh': 'left', 'rh': 'right'}
  27. _GENERAL_MEASUREMENTS_REGEX = re.compile(
  28. r'^Measure \S+, ([^,\s]+),? ([^,]+), ([\d\.]+), (\S+)$')
  29. def __init__(self):
  30. self.headers \
  31. = {} # type: typing.Dict[str, typing.Union[str, datetime.datetime]]
  32. self.general_measurements \
  33. = {} # type: typing.Dict[str, typing.Tuple[float, int]]
  34. @property
  35. def hemisphere(self) -> str:
  36. return self._HEMISPHERE_PREFIX_TO_SIDE[self.headers['hemi']]
  37. @staticmethod
  38. def _read_header_line(stream: typing.TextIO) -> None:
  39. line = stream.readline()
  40. assert line.startswith('# ')
  41. return line[2:].rstrip()
  42. def _read_headers(self, stream: typing.TextIO) -> None:
  43. self.headers = {}
  44. while True:
  45. line = self._read_header_line(stream)
  46. if line.startswith('Measure'):
  47. break
  48. elif line:
  49. attr_name, attr_value = line.split(' ', maxsplit=1)
  50. attr_value = attr_value.lstrip()
  51. if attr_name in ['cvs_version', 'mrisurf.c-cvs_version']:
  52. attr_value = attr_value.strip('$').rstrip()
  53. if attr_name == 'CreationTime':
  54. attr_dt = datetime.datetime.strptime(
  55. attr_value, '%Y/%m/%d-%H:%M:%S-%Z')
  56. if attr_dt.tzinfo is None:
  57. assert attr_value.endswith('-GMT')
  58. attr_dt = attr_dt.replace(tzinfo=datetime.timezone.utc)
  59. attr_value = attr_dt
  60. if attr_name == 'AnnotationFileTimeStamp':
  61. attr_value = datetime.datetime.strptime(
  62. attr_value, '%Y/%m/%d %H:%M:%S')
  63. self.headers[attr_name] = attr_value
  64. def _read(self, stream: typing.TextIO) -> None:
  65. assert stream.readline().rstrip() \
  66. == '# Table of FreeSurfer cortical parcellation anatomical statistics'
  67. assert stream.readline().rstrip() == '#'
  68. self._read_headers(stream)
  69. self.general_measurements = {}
  70. line = self._read_header_line(stream)
  71. while not line.startswith('NTableCols'):
  72. key, name, value, unit \
  73. = self._GENERAL_MEASUREMENTS_REGEX.match(line).groups()
  74. if key == 'SupraTentorialVolNotVent' and name.lower() == 'supratentorial volume':
  75. name += ' Without Ventricles'
  76. assert name not in self.general_measurements, \
  77. (key, name, self.general_measurements)
  78. self.general_measurements[name] = (float(value), unit)
  79. line = self._read_header_line(stream)
  80. @classmethod
  81. def read(cls, path: str) -> 'CorticalParcellationStats':
  82. stats = cls()
  83. with open(path, 'r') as stream:
  84. # pylint: disable=protected-access
  85. stats._read(stream)
  86. return stats