Browse Source

recursively search for freesurfer's hippocampus volume files; csv output

Fabian Peter Hammerle 5 years ago
commit
658cd31d97

+ 14 - 0
Pipfile

@@ -0,0 +1,14 @@
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+freesurfer-volume-reader = {editable = true, path = "."}
+
+[dev-packages]
+pytest = "*"
+pylint = ">=2.3.0"
+
+[requires]
+python_version = "3"

+ 246 - 0
Pipfile.lock

@@ -0,0 +1,246 @@
+{
+    "_meta": {
+        "hash": {
+            "sha256": "52976adb8c969665fe068d4030d9e1c2fc535bbe86cbfb5faaa4879164fc6f3c"
+        },
+        "pipfile-spec": 6,
+        "requires": {
+            "python_version": "3"
+        },
+        "sources": [
+            {
+                "name": "pypi",
+                "url": "https://pypi.org/simple",
+                "verify_ssl": true
+            }
+        ]
+    },
+    "default": {
+        "freesurfer-volume-reader": {
+            "editable": true,
+            "path": "."
+        },
+        "numpy": {
+            "hashes": [
+                "sha256:0e2eed77804b2a6a88741f8fcac02c5499bba3953ec9c71e8b217fad4912c56c",
+                "sha256:1c666f04553ef70fda54adf097dbae7080645435fc273e2397f26bbf1d127bbb",
+                "sha256:1f46532afa7b2903bfb1b79becca2954c0a04389d19e03dc73f06b039048ac40",
+                "sha256:315fa1b1dfc16ae0f03f8fd1c55f23fd15368710f641d570236f3d78af55e340",
+                "sha256:3d5fcea4f5ed40c3280791d54da3ad2ecf896f4c87c877b113576b8280c59441",
+                "sha256:48241759b99d60aba63b0e590332c600fc4b46ad597c9b0a53f350b871ef0634",
+                "sha256:4b4f2924b36d857cf302aec369caac61e43500c17eeef0d7baacad1084c0ee84",
+                "sha256:54fe3b7ed9e7eb928bbc4318f954d133851865f062fa4bbb02ef8940bc67b5d2",
+                "sha256:5a8f021c70e6206c317974c93eaaf9bc2b56295b6b1cacccf88846e44a1f33fc",
+                "sha256:754a6be26d938e6ca91942804eb209307b73f806a1721176278a6038869a1686",
+                "sha256:771147e654e8b95eea1293174a94f34e2e77d5729ad44aefb62fbf8a79747a15",
+                "sha256:78a6f89da87eeb48014ec652a65c4ffde370c036d780a995edaeb121d3625621",
+                "sha256:7fde5c2a3a682a9e101e61d97696687ebdba47637611378b4127fe7e47fdf2bf",
+                "sha256:80d99399c97f646e873dd8ce87c38cfdbb668956bbc39bc1e6cac4b515bba2a0",
+                "sha256:88a72c1e45a0ae24d1f249a529d9f71fe82e6fa6a3fd61414b829396ec585900",
+                "sha256:a4f4460877a16ac73302a9c077ca545498d9fe64e6a81398d8e1a67e4695e3df",
+                "sha256:a61255a765b3ac73ee4b110b28fccfbf758c985677f526c2b4b39c48cc4b509d",
+                "sha256:ab4896a8c910b9a04c0142871d8800c76c8a2e5ff44763513e1dd9d9631ce897",
+                "sha256:abbd6b1c2ef6199f4b7ca9f818eb6b31f17b73a6110aadc4e4298c3f00fab24e",
+                "sha256:b16d88da290334e33ea992c56492326ea3b06233a00a1855414360b77ca72f26",
+                "sha256:b78a1defedb0e8f6ae1eb55fa6ac74ab42acc4569c3a2eacc2a407ee5d42ebcb",
+                "sha256:cfef82c43b8b29ca436560d51b2251d5117818a8d1fb74a8384a83c096745dad",
+                "sha256:d160e57731fcdec2beda807ebcabf39823c47e9409485b5a3a1db3a8c6ce763e"
+            ],
+            "version": "==1.16.3"
+        },
+        "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": [
+                "sha256:6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4",
+                "sha256:b65db1bbaac9f9f4d190199bb8680af6f6f84fd3769a5ea883df8a91fe68b4c4"
+            ],
+            "version": "==2.2.5"
+        },
+        "atomicwrites": {
+            "hashes": [
+                "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4",
+                "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"
+            ],
+            "version": "==1.3.0"
+        },
+        "attrs": {
+            "hashes": [
+                "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79",
+                "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"
+            ],
+            "version": "==19.1.0"
+        },
+        "isort": {
+            "hashes": [
+                "sha256:01cb7e1ca5e6c5b3f235f0385057f70558b70d2f00320208825fa62887292f43",
+                "sha256:268067462aed7eb2a1e237fcb287852f22077de3fb07964e87e00f829eea2d1a"
+            ],
+            "version": "==4.3.17"
+        },
+        "lazy-object-proxy": {
+            "hashes": [
+                "sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33",
+                "sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39",
+                "sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019",
+                "sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088",
+                "sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b",
+                "sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e",
+                "sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6",
+                "sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b",
+                "sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5",
+                "sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff",
+                "sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd",
+                "sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7",
+                "sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff",
+                "sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d",
+                "sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2",
+                "sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35",
+                "sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4",
+                "sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514",
+                "sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252",
+                "sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109",
+                "sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f",
+                "sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c",
+                "sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92",
+                "sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577",
+                "sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d",
+                "sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d",
+                "sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f",
+                "sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a",
+                "sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b"
+            ],
+            "version": "==1.3.1"
+        },
+        "mccabe": {
+            "hashes": [
+                "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
+                "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
+            ],
+            "version": "==0.6.1"
+        },
+        "more-itertools": {
+            "hashes": [
+                "sha256:2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7",
+                "sha256:c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a"
+            ],
+            "markers": "python_version > '2.7'",
+            "version": "==7.0.0"
+        },
+        "pluggy": {
+            "hashes": [
+                "sha256:19ecf9ce9db2fce065a7a0586e07cfb4ac8614fe96edf628a264b1c70116cf8f",
+                "sha256:84d306a647cc805219916e62aab89caa97a33a1dd8c342e87a37f91073cd4746"
+            ],
+            "version": "==0.9.0"
+        },
+        "py": {
+            "hashes": [
+                "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa",
+                "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"
+            ],
+            "version": "==1.8.0"
+        },
+        "pylint": {
+            "hashes": [
+                "sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09",
+                "sha256:723e3db49555abaf9bf79dc474c6b9e2935ad82230b10c1138a71ea41ac0fff1"
+            ],
+            "index": "pypi",
+            "version": "==2.3.1"
+        },
+        "pytest": {
+            "hashes": [
+                "sha256:3773f4c235918987d51daf1db66d51c99fac654c81d6f2f709a046ab446d5e5d",
+                "sha256:b7802283b70ca24d7119b32915efa7c409982f59913c1a6c0640aacf118b95f5"
+            ],
+            "index": "pypi",
+            "version": "==4.4.1"
+        },
+        "six": {
+            "hashes": [
+                "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
+                "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
+            ],
+            "version": "==1.12.0"
+        },
+        "typed-ast": {
+            "hashes": [
+                "sha256:04894d268ba6eab7e093d43107869ad49e7b5ef40d1a94243ea49b352061b200",
+                "sha256:16616ece19daddc586e499a3d2f560302c11f122b9c692bc216e821ae32aa0d0",
+                "sha256:252fdae740964b2d3cdfb3f84dcb4d6247a48a6abe2579e8029ab3be3cdc026c",
+                "sha256:2af80a373af123d0b9f44941a46df67ef0ff7a60f95872412a145f4500a7fc99",
+                "sha256:2c88d0a913229a06282b285f42a31e063c3bf9071ff65c5ea4c12acb6977c6a7",
+                "sha256:2ea99c029ebd4b5a308d915cc7fb95b8e1201d60b065450d5d26deb65d3f2bc1",
+                "sha256:3d2e3ab175fc097d2a51c7a0d3fda442f35ebcc93bb1d7bd9b95ad893e44c04d",
+                "sha256:4766dd695548a15ee766927bf883fb90c6ac8321be5a60c141f18628fb7f8da8",
+                "sha256:56b6978798502ef66625a2e0f80cf923da64e328da8bbe16c1ff928c70c873de",
+                "sha256:5cddb6f8bce14325b2863f9d5ac5c51e07b71b462361fd815d1d7706d3a9d682",
+                "sha256:644ee788222d81555af543b70a1098f2025db38eaa99226f3a75a6854924d4db",
+                "sha256:64cf762049fc4775efe6b27161467e76d0ba145862802a65eefc8879086fc6f8",
+                "sha256:68c362848d9fb71d3c3e5f43c09974a0ae319144634e7a47db62f0f2a54a7fa7",
+                "sha256:6c1f3c6f6635e611d58e467bf4371883568f0de9ccc4606f17048142dec14a1f",
+                "sha256:b213d4a02eec4ddf622f4d2fbc539f062af3788d1f332f028a2e19c42da53f15",
+                "sha256:bb27d4e7805a7de0e35bd0cb1411bc85f807968b2b0539597a49a23b00a622ae",
+                "sha256:c9d414512eaa417aadae7758bc118868cd2396b0e6138c1dd4fda96679c079d3",
+                "sha256:f0937165d1e25477b01081c4763d2d9cdc3b18af69cb259dd4f640c9b900fe5e",
+                "sha256:fb96a6e2c11059ecf84e6741a319f93f683e440e341d4489c9b161eca251cf2a",
+                "sha256:fc71d2d6ae56a091a8d94f33ec9d0f2001d1cb1db423d8b4355debfe9ce689b7"
+            ],
+            "markers": "implementation_name == 'cpython'",
+            "version": "==1.3.4"
+        },
+        "wrapt": {
+            "hashes": [
+                "sha256:4aea003270831cceb8a90ff27c4031da6ead7ec1886023b80ce0dfe0adf61533"
+            ],
+            "version": "==1.11.1"
+        }
+    }
+}

+ 9 - 0
README.md

@@ -0,0 +1,9 @@
+## Tests
+
+```sh
+pip3 install --user pipenv
+git clone https://github.com/fphammerle/freesurfer-volume-reader.git
+cd freesurfer-volume-reader
+pipenv run pylint freesurfer_volume_reader
+pipenv run pytest
+```

+ 79 - 0
freesurfer_volume_reader/__init__.py

@@ -0,0 +1,79 @@
+import argparse
+import os
+import re
+import typing
+
+import pandas
+
+# https://surfer.nmr.mgh.harvard.edu/fswiki/HippocampalSubfields
+HIPPOCAMPAL_VOLUME_FILENAME_PATTERN = r'^(?P<h>[lr])h\.hippoSfVolumes' \
+                                      r'(?P<T1>-T1)?(-(?P<analysis_id>.+?))?\.v10.txt$'
+HIPPOCAMPAL_VOLUME_FILENAME_REGEX = re.compile(HIPPOCAMPAL_VOLUME_FILENAME_PATTERN)
+DEFAULT_HIPPOCAMPAL_VOLUME_FIND_FILENAME_PATTERN = re.sub(r'\?P<.+?>', '',
+                                                          HIPPOCAMPAL_VOLUME_FILENAME_PATTERN)
+
+VOLUME_FILENAME_HEMISPHERE_MAP = {'l': 'left', 'r': 'right'}
+
+
+def find_hippocampal_volume_files(root_dir_path: str, filename_regex: typing.Pattern = HIPPOCAMPAL_VOLUME_FILENAME_REGEX) -> typing.Iterator[str]:
+    for dirpath, _, filenames in os.walk(root_dir_path):
+        for filename in filter(filename_regex.search, filenames):
+            yield os.path.join(dirpath, filename)
+
+
+def read_hippocampal_volumes(volume_file_path: str) -> dict:
+    subfield_volumes = {}
+    with open(volume_file_path, 'r') as volume_file:
+        for line in volume_file.read().rstrip().split('\n'):
+            subfield_name, subfield_volume_str = line.split(' ')
+            subfield_volumes[subfield_name] = float(subfield_volume_str)
+    return subfield_volumes
+
+
+def parse_hippocampal_volume_file_path(volume_file_path: str) -> dict:
+    subject_dir_path = os.path.dirname(os.path.dirname(os.path.abspath(volume_file_path)))
+    filename_match = HIPPOCAMPAL_VOLUME_FILENAME_REGEX.match(os.path.basename(volume_file_path))
+    assert filename_match, volume_file_path
+    filename_groups = filename_match.groupdict()
+    assert filename_groups['T1'] or filename_groups['analysis_id'], volume_file_path
+    return {
+        'subject': os.path.basename(subject_dir_path),
+        'hemisphere': VOLUME_FILENAME_HEMISPHERE_MAP[filename_groups['h']],
+        'T1_input': filename_groups['T1'] is not None,
+        'analysis_id': filename_groups['analysis_id'],
+    }
+
+
+def read_hippocampal_volume_file_dataframe(volume_file_path: str) -> pandas.DataFrame:
+    volumes_frame = pandas.DataFrame(
+        read_hippocampal_volumes(volume_file_path).items(),
+        columns=['subfield', 'volume'])
+    for key, value in parse_hippocampal_volume_file_path(volume_file_path).items():
+        volumes_frame[key] = value
+    # volumes_frame['hemisphere'] = volumes_frame['hemisphere'].astype('category')
+    return volumes_frame
+
+
+def main():
+    # TODO add description
+    argparser = argparse.ArgumentParser(description='Read hippocampal volumes computed by Freesurfer'
+                                        '\nhttps://surfer.nmr.mgh.harvard.edu/fswiki/HippocampalSubfields')
+    argparser.add_argument('--filename-regex', dest='filename_pattern',
+                           default=DEFAULT_HIPPOCAMPAL_VOLUME_FIND_FILENAME_PATTERN,
+                           help='default: %(default)s')
+    argparser.add_argument('--output-format', choices=['csv'], default='csv',
+                           help='default: %(default)s')
+    # TODO default to $SUBJECTS_DIR
+    argparser.add_argument('root_dir_path')
+    args = argparser.parse_args()
+    volume_frames = []
+    for volume_file_path in find_hippocampal_volume_files(root_dir_path=args.root_dir_path,
+                                                          filename_regex=re.compile(args.filename_pattern)):
+        volume_frame = read_hippocampal_volume_file_dataframe(volume_file_path)
+        volume_frame['source_path'] = os.path.abspath(volume_file_path)
+        volume_frames.append(volume_frame)
+    united_volume_frame = pandas.concat(volume_frames, ignore_index=True)
+    print(united_volume_frame.to_csv(index=False))
+
+if __name__ == '__main__':
+    main()

+ 38 - 0
setup.py

@@ -0,0 +1,38 @@
+import setuptools
+
+setuptools.setup(
+    name='freesurfer-volume-reader',
+    use_scm_version=True,
+    author='Fabian Peter Hammerle',
+    author_email='fabian@hammerle.me',
+    url='https://github.com/fphammerle/freesurfer-volumes-reader',
+    # TODO add license
+    keywords=[
+        'brain',
+        'freesurfer',
+        'hippocampus',
+        'neuroimaging',
+        'reader',
+        'subfields',
+    ],
+    classifiers=[
+        # TODO add classifiers
+        'Programming Language :: Python',
+    ],
+    packages=setuptools.find_packages(),
+    entry_points={
+        'console_scripts': [
+            'freesurfer-volume-reader = freesurfer_volume_reader:main',
+        ],
+    },
+    install_requires=[
+        'pandas',
+    ],
+    setup_requires=[
+        'setuptools_scm',
+    ],
+    tests_require=[
+        'pylint>=2.3.0',
+        'pytest',
+    ],
+)

+ 130 - 0
tests/hippocampus_test.py

@@ -0,0 +1,130 @@
+import os
+import re
+
+import pandas.util.testing
+import pytest
+
+import freesurfer_volume_reader
+
+SUBJECTS_DIR = os.path.join(os.path.dirname(__file__), 'subjects')
+
+
+@pytest.mark.parametrize(('root_dir_path', 'expected_file_paths'), [
+    (SUBJECTS_DIR,
+     {os.path.join(SUBJECTS_DIR, 'alice', 'mri', 'lh.hippoSfVolumes-T1.v10.txt'),
+      os.path.join(SUBJECTS_DIR, 'bert', 'mri', 'lh.hippoSfVolumes-T1-T2.v10.txt'),
+      os.path.join(SUBJECTS_DIR, 'bert', 'mri', 'lh.hippoSfVolumes-T1.v10.txt')}),
+    (os.path.join(SUBJECTS_DIR, 'bert'),
+     {os.path.join(SUBJECTS_DIR, 'bert', 'mri', 'lh.hippoSfVolumes-T1-T2.v10.txt'),
+      os.path.join(SUBJECTS_DIR, 'bert', 'mri', 'lh.hippoSfVolumes-T1.v10.txt')}),
+    (os.path.join(SUBJECTS_DIR, 'bert', 'mri'),
+     {os.path.join(SUBJECTS_DIR, 'bert', 'mri', 'lh.hippoSfVolumes-T1-T2.v10.txt'),
+      os.path.join(SUBJECTS_DIR, 'bert', 'mri', 'lh.hippoSfVolumes-T1.v10.txt')}),
+])
+def test_find_hippocampal_volume_files(root_dir_path, expected_file_paths):
+    assert expected_file_paths == set(
+        freesurfer_volume_reader.find_hippocampal_volume_files(root_dir_path=root_dir_path))
+
+
+@pytest.mark.parametrize(('root_dir_path', 'filename_pattern', 'expected_file_paths'), [
+    (SUBJECTS_DIR,
+     r'hippoSfVolumes-T1\.v10',
+     {os.path.join(SUBJECTS_DIR, 'alice', 'mri', 'lh.hippoSfVolumes-T1.v10.txt'),
+      os.path.join(SUBJECTS_DIR, 'bert', 'mri', 'lh.hippoSfVolumes-T1.v10.txt')}),
+    (os.path.join(SUBJECTS_DIR, 'bert'),
+     r'hippoSfVolumes-T1-T2',
+     {os.path.join(SUBJECTS_DIR, 'bert', 'mri', 'lh.hippoSfVolumes-T1-T2.v10.txt')}),
+])
+def test_find_hippocampal_volume_files_pattern(root_dir_path, filename_pattern,
+                                               expected_file_paths):
+    assert expected_file_paths == set(freesurfer_volume_reader.find_hippocampal_volume_files(
+        root_dir_path=root_dir_path, filename_regex=re.compile(filename_pattern)))
+
+
+@pytest.mark.parametrize(('volume_file_path', 'expected_volumes'), [
+    (os.path.join(SUBJECTS_DIR, 'bert/mri/lh.hippoSfVolumes-T1.v10.txt'),
+     {'Hippocampal_tail': 123.456789,
+      'subiculum': 234.567891,
+      'CA1': 34.567891,
+      'hippocampal-fissure': 345.678912,
+      'presubiculum': 456.789123,
+      'parasubiculum':  45.678912,
+      'molecular_layer_HP': 56.789123,
+      'GC-ML-DG': 567.891234,
+      'CA3': 678.912345,
+      'CA4': 789.123456,
+      'fimbria': 89.123456,
+      'HATA': 91.234567,
+      'Whole_hippocampus': 1234.567899}),
+])
+def test_read_hippocampal_volumes(volume_file_path, expected_volumes):
+    assert expected_volumes == freesurfer_volume_reader.read_hippocampal_volumes(
+        volume_file_path)
+
+
+def test_read_hippocampal_volumes_not_found():
+    with pytest.raises(FileNotFoundError):
+        freesurfer_volume_reader.read_hippocampal_volumes(
+            os.path.join(SUBJECTS_DIR, 'non-existing', 'lh.hippoSfVolumes-T1.v10.txt'))
+
+
+@pytest.mark.parametrize(('volume_file_path', 'expected_attrs'), [
+    ('bert/mri/lh.hippoSfVolumes-T1.v10.txt',
+     {'subject': 'bert', 'hemisphere': 'left', 'T1_input': True, 'analysis_id': None}),
+    ('bert/mri/lh.hippoSfVolumes-T1-T2.v10.txt',
+     {'subject': 'bert', 'hemisphere': 'left', 'T1_input': True, 'analysis_id': 'T2'}),
+    ('bert/mri/lh.hippoSfVolumes-T2.v10.txt',
+     {'subject': 'bert', 'hemisphere': 'left', 'T1_input': False, 'analysis_id': 'T2'}),
+    ('bert/mri/lh.hippoSfVolumes-T1-T2-high-res.v10.txt',
+     {'subject': 'bert', 'hemisphere': 'left', 'T1_input': True, 'analysis_id': 'T2-high-res'}),
+    ('bert/mri/lh.hippoSfVolumes-T2-high-res.v10.txt',
+     {'subject': 'bert', 'hemisphere': 'left', 'T1_input': False, 'analysis_id': 'T2-high-res'}),
+    ('bert/mri/lh.hippoSfVolumes-PD.v10.txt',
+     {'subject': 'bert', 'hemisphere': 'left', 'T1_input': False, 'analysis_id': 'PD'}),
+    ('bert/mri/rh.hippoSfVolumes-T1.v10.txt',
+     {'subject': 'bert', 'hemisphere': 'right', 'T1_input': True, 'analysis_id': None}),
+    ('bert/mri/rh.hippoSfVolumes-T1-T2.v10.txt',
+     {'subject': 'bert', 'hemisphere': 'right', 'T1_input': True, 'analysis_id': 'T2'}),
+    ('freesurfer/subjects/bert/mri/lh.hippoSfVolumes-T1.v10.txt',
+     {'subject': 'bert', 'hemisphere': 'left', 'T1_input': True, 'analysis_id': None}),
+    ('../../bert/mri/lh.hippoSfVolumes-T1.v10.txt',
+     {'subject': 'bert', 'hemisphere': 'left', 'T1_input': True, 'analysis_id': None}),
+])
+def test_parse_hippocampal_volume_file_path(volume_file_path, expected_attrs):
+    assert expected_attrs == freesurfer_volume_reader.parse_hippocampal_volume_file_path(
+        volume_file_path=volume_file_path)
+
+
+@pytest.mark.parametrize('volume_file_path', [
+    'bert/mri/lh.hippoSfLabels-T1.v10.mgz',
+    'bert/mri/lh.hippoSfVolumes-T1.v9.txt',
+    'bert/mri/lh.hippoSfVolumes.v10.txt',
+    'bert/mri/mh.hippoSfVolumes-T1.v10.txt',
+])
+def test_parse_hippocampal_volume_file_path_invalid(volume_file_path):
+    with pytest.raises(Exception):
+        freesurfer_volume_reader.parse_hippocampal_volume_file_path(
+            volume_file_path=volume_file_path)
+
+
+@pytest.mark.parametrize(('volume_file_path', 'expected_dataframe'), [
+    (os.path.join(SUBJECTS_DIR, 'alice', 'mri', 'lh.hippoSfVolumes-T1.v10.txt'),
+     pandas.DataFrame({
+         'subfield': ['Hippocampal_tail', 'subiculum', 'CA1', 'hippocampal-fissure',
+                      'presubiculum', 'parasubiculum', 'molecular_layer_HP', 'GC-ML-DG',
+                      'CA3', 'CA4', 'fimbria', 'HATA', 'Whole_hippocampus'],
+         'volume': [173.456789, 734.567891, 34.567891, 345.678917, 456.789173, 45.678917,
+                    56.789173, 567.891734, 678.917345, 789.173456, 89.173456, 91.734567,
+                    1734.567899],
+         'subject': 'alice',
+         'hemisphere': 'left',
+         'T1_input': True,
+         'analysis_id': None,
+     })),
+])
+def test_read_hippocampal_volume_file_dataframe(volume_file_path, expected_dataframe):
+    pandas.util.testing.assert_frame_equal(
+        left=expected_dataframe,
+        right=freesurfer_volume_reader.read_hippocampal_volume_file_dataframe(
+            volume_file_path=volume_file_path),
+    )

+ 13 - 0
tests/subjects/alice/mri/lh.hippoSfVolumes-T1.v10.txt

@@ -0,0 +1,13 @@
+Hippocampal_tail 173.456789
+subiculum 734.567891
+CA1 34.567891
+hippocampal-fissure 345.678917
+presubiculum 456.789173
+parasubiculum 45.678917
+molecular_layer_HP 56.789173
+GC-ML-DG 567.891734
+CA3 678.917345
+CA4 789.173456
+fimbria 89.173456
+HATA 91.734567
+Whole_hippocampus 1734.567899

+ 13 - 0
tests/subjects/bert/mri/lh.hippoSfVolumes-T1-T2.v10.txt

@@ -0,0 +1,13 @@
+Hippocampal_tail 124.456789
+subiculum 244.567891
+CA1 44.567891
+hippocampal-fissure 445.678912
+presubiculum 456.789124
+parasubiculum 45.678912
+molecular_layer_HP 56.789124
+GC-ML-DG 567.891244
+CA3 678.912445
+CA4 789.124456
+fimbria 89.124456
+HATA 91.244567
+Whole_hippocampus 1244.567899

+ 13 - 0
tests/subjects/bert/mri/lh.hippoSfVolumes-T1.v10.txt

@@ -0,0 +1,13 @@
+Hippocampal_tail 123.456789
+subiculum 234.567891
+CA1 34.567891
+hippocampal-fissure 345.678912
+presubiculum 456.789123
+parasubiculum 45.678912
+molecular_layer_HP 56.789123
+GC-ML-DG 567.891234
+CA3 678.912345
+CA4 789.123456
+fimbria 89.123456
+HATA 91.234567
+Whole_hippocampus 1234.567899