Skip to content

Commit cf36097

Browse files
authored
Merge pull request #38 from fliem/euler
Euler number
2 parents 81fde04 + 860fc8b commit cf36097

File tree

4 files changed

+96
-22
lines changed

4 files changed

+96
-22
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ RUN wget -qO- https://surfer.nmr.mgh.harvard.edu/pub/dist/freesurfer/6.0.1/frees
1919

2020
RUN apt-get install -y python3
2121
RUN apt-get install -y python3-pip
22-
RUN pip3 install nibabel
22+
RUN pip3 install nibabel pandas
2323
RUN apt-get install -y python2.7
2424
RUN apt-get install -y python-pip
2525

README.md

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,15 @@ This App has the following command line arguments:
2525
$ docker run -ti --rm bids/freesurfer --help
2626
usage: run.py [-h]
2727
[--participant_label PARTICIPANT_LABEL [PARTICIPANT_LABEL ...]]
28+
[--session_label SESSION_LABEL [SESSION_LABEL ...]]
2829
[--n_cpus N_CPUS]
29-
[--stages {autorecon1,autorecon2,autorecon2-cp,autorecon2-wm,autorecon2-pial,autorecon3,autorecon-all,all}
30-
[{autorecon1,autorecon2,autorecon2-cp,autorecon2-wm,autorecon2-pial,autorecon3,autorecon-all,all} ...]]
31-
[--template_name TEMPLATE_NAME] --license_key LICENSE_KEY
30+
[--stages {autorecon1,autorecon2,autorecon2-cp,autorecon2-wm,autorecon-pial,autorecon3,autorecon-all,all}
31+
[{autorecon1,autorecon2,autorecon2-cp,autorecon2-wm,autorecon-pial,autorecon3,autorecon-all,all} ...]]
32+
[--steps {cross-sectional,template,longitudinal}
33+
[{cross-sectional,template,longitudinal} ...]]
34+
[--template_name TEMPLATE_NAME] --license_file LICENSE_FILE
3235
[--acquisition_label ACQUISITION_LABEL]
36+
[--refine_pial_acquisition_label REFINE_PIAL_ACQUISITION_LABEL]
3337
[--multiple_sessions {longitudinal,multiday}]
3438
[--refine_pial {T2,FLAIR,None,T1only}]
3539
[--hires_mode {auto,enable,disable}]
@@ -39,6 +43,7 @@ This App has the following command line arguments:
3943
[-v] [--bids_validator_config BIDS_VALIDATOR_CONFIG]
4044
[--skip_bids_validator]
4145
bids_dir output_dir {participant,group1,group2}
46+
4247
FreeSurfer recon-all + custom template generation.
4348

4449
positional arguments:
@@ -51,10 +56,10 @@ This App has the following command line arguments:
5156
{participant,group1,group2}
5257
Level of the analysis that will be performed. Multiple
5358
participant level analyses can be run independently
54-
(in parallel) using the same output_dir. "goup1"
55-
creates study specific group template. "group2 exports
56-
group stats tables for cortical parcellation and
57-
subcortical segmentation.
59+
(in parallel) using the same output_dir. "group1"
60+
creates study specific group template. "group2"
61+
exports group stats tables for cortical parcellation,
62+
subcortical segmentation a table with euler numbers.
5863

5964
optional arguments:
6065
-h, --help show this help message and exit
@@ -65,21 +70,34 @@ This App has the following command line arguments:
6570
parameter is not provided all subjects should be
6671
analyzed. Multiple participants can be specified with
6772
a space separated list.
73+
--session_label SESSION_LABEL [SESSION_LABEL ...]
74+
The label of the session that should be analyzed. The
75+
label corresponds to ses-<session_label> from the BIDS
76+
spec (so it does not include "ses-"). If this
77+
parameter is not provided all sessions should be
78+
analyzed. Multiple sessions can be specified with a
79+
space separated list.
6880
--n_cpus N_CPUS Number of CPUs/cores available to use.
69-
--stages {autorecon1,autorecon2,autorecon2-cp,autorecon2-wm,autorecon2-pial,autorecon3,autorecon-all,all}
70-
[{autorecon1,autorecon2,autorecon2-cp,autorecon2-wm,autorecon2-pial,autorecon3,autorecon-all,all} ...]
81+
--stages {autorecon1,autorecon2,autorecon2-cp,autorecon2-wm,autorecon-pial,autorecon3,autorecon-all,all}
82+
[{autorecon1,autorecon2,autorecon2-cp,autorecon2-wm,autorecon-pial,autorecon3,autorecon-all,all} ...]
7183
Autorecon stages to run.
84+
--steps {cross-sectional,template,longitudinal} [{cross-sectional,template,longitudinal} ...]
85+
Longitudinal pipeline steps to run.
7286
--template_name TEMPLATE_NAME
7387
Name for the custom group level template generated for
7488
this dataset
7589
--license_file LICENSE_FILE
7690
Path to FreeSurfer license key file. To obtain it you
77-
need to register (for free) visit
91+
need to register (for free) at
7892
https://surfer.nmr.mgh.harvard.edu/registration.html
7993
--acquisition_label ACQUISITION_LABEL
8094
If the dataset contains multiple T1 weighted images
8195
from different acquisitions which one should be used?
8296
Corresponds to "acq-<acquisition_label>"
97+
--refine_pial_acquisition_label REFINE_PIAL_ACQUISITION_LABEL
98+
If the dataset contains multiple T2 or FLAIR weighted
99+
images from different acquisitions which one should be
100+
used? Corresponds to "acq-<acquisition_label>"
83101
--multiple_sessions {longitudinal,multiday}
84102
For datasets with multiday sessions where you do not
85103
want to use the longitudinal pipeline, i.e., sessions
@@ -100,15 +118,15 @@ This App has the following command line arguments:
100118
stats from.
101119
--measurements {area,volume,thickness,thicknessstd,meancurv,gauscurv,foldind,curvind}
102120
[{area,volume,thickness,thicknessstd,meancurv,gauscurv,foldind,curvind} ...]
103-
Group2 option: cortical measurements to extract stats for.
121+
Group2 option: cortical measurements to extract stats
122+
for.
104123
-v, --version show program's version number and exit
105124
--bids_validator_config BIDS_VALIDATOR_CONFIG
106125
JSON file specifying configuration of bids-validator:
107126
See https://github.com/INCF/bids-validator for more
108127
info
109128
--skip_bids_validator
110-
skips bids validation
111-
129+
skips bids validation
112130

113131
#### Participant level
114132
To run it in participant level mode (for one participant):
@@ -125,6 +143,7 @@ To run it in participant level mode (for one participant):
125143
After doing this for all subjects (potentially in parallel) the
126144
group level analyses can be run.
127145

146+
##### Template creation
128147
To create a study specific template run:
129148

130149
docker run -ti --rm \
@@ -134,13 +153,24 @@ To create a study specific template run:
134153
/bids_dataset /outputs group1 \
135154
--license_file "license.txt"
136155

156+
##### Stats and quality tables export
137157
To export tables with aggregated measurements within regions of
138-
cortical parcellation and subcortical segementation run:
158+
cortical parcellation and subcortical segementation, and a table with
159+
euler numbers (a quality metric, see
160+
[Rosen et. al, 2017](https://www.biorxiv.org/content/early/2017/10/01/125161))
161+
run:
139162

140163
docker run -ti --rm \
141164
-v /Users/filo/data/ds005:/bids_dataset:ro \
142165
-v /Users/filo/outputs:/outputs \
143166
bids/freesurfer \
144167
/bids_dataset /outputs group2 \
145168
--license_file "license.txt"
146-
Also see *--parcellations* and *--measurements* arguments.
169+
Also see the *--parcellations* and *--measurements* arguments.
170+
171+
This step writes ouput into `<output_dir>/00_group2_stats_tables/`. E.g.:
172+
173+
* `lh.aparc.thickness.tsv` contains cortical thickness values for the
174+
left hemisphere extracted via the aparac parcellation.
175+
* `aseg.tsv` contains subcortical information from the aseg segmentation.
176+
* `euler.tsv` contains the euler numbers

circle.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@ test:
2828
override:
2929
# print version
3030
- docker run -ti --rm --read-only -v $PWD/license.txt:/license.txt -v /tmp:/tmp -v /var/tmp:/var/tmp -v ${HOME}/data/ds114_test1:/bids_dataset bids/${CIRCLE_PROJECT_REPONAME,,} --version
31+
- docker run -ti --rm --read-only -v $PWD/license.txt:/license.txt -v /tmp:/tmp -v /var/tmp:/var/tmp -v ${HOME}/data/ds114_test1:/bids_dataset bids/${CIRCLE_PROJECT_REPONAME,,} -h
3132
- docker run -ti --rm --read-only -v $PWD/license.txt:/license.txt -v /tmp:/tmp -v /var/tmp:/var/tmp -v ${HOME}/data/ds114_test1:/bids_dataset -v ${HOME}/outputs1:/outputs bids/${CIRCLE_PROJECT_REPONAME,,} /bids_dataset /outputs participant --participant_label 01 --license_file=/license.txt --stages autorecon1 && cat ${HOME}/outputs1/sub-01/scripts/recon-all.done :
3233
timeout: 21600
3334
- docker run -ti --rm --read-only -v $PWD/license.txt:/license.txt -v /tmp:/tmp -v /var/tmp:/var/tmp -v ${HOME}/data/ds114_test2:/bids_dataset -v ${HOME}/outputs2:/outputs bids/${CIRCLE_PROJECT_REPONAME,,} /bids_dataset /outputs participant --participant_label 01 --steps cross-sectional --session_label test --license_file=/license.txt --stages autorecon1 && cat ${HOME}/outputs2/sub-01_ses-test/scripts/recon-all.done :
3435
timeout: 21600
3536
# group2 tests
36-
- docker run -ti --rm --read-only -v $PWD/license.txt:/license.txt -v /tmp:/tmp -v /var/tmp:/var/tmp -v ${HOME}/data/ds114_test1:/bids_dataset -v ${HOME}/data/ds114_test1_freesurfer_precomp_v6.0.0:/outputs bids/${CIRCLE_PROJECT_REPONAME,,} /bids_dataset /outputs group2 --license_file=/license.txt && mkdir -p ${HOME}/outputs1/ && sudo mv ${HOME}/data/ds114_test1_freesurfer_precomp_v6.0.0/00_group* ${HOME}/outputs1/ && cat ${HOME}/outputs1/00_group2_stats_tables/lh.aparc.thickness.tsv :
37+
- docker run -ti --rm --read-only -v $PWD/license.txt:/license.txt -v /tmp:/tmp -v /var/tmp:/var/tmp -v ${HOME}/data/ds114_test1:/bids_dataset -v ${HOME}/data/ds114_test1_freesurfer_precomp_v6.0.0:/outputs bids/${CIRCLE_PROJECT_REPONAME,,} /bids_dataset /outputs group2 --license_file=/license.txt && mkdir -p ${HOME}/outputs1/ && sudo mv ${HOME}/data/ds114_test1_freesurfer_precomp_v6.0.0/00_group* ${HOME}/outputs1/ && cat ${HOME}/outputs1/00_group2_stats_tables/lh.aparc.thickness.tsv && cat ${HOME}/outputs1/00_group2_stats_tables/euler.tsv:
3738
timeout: 21600
38-
- docker run -ti --rm --read-only -v $PWD/license.txt:/license.txt -v /tmp:/tmp -v /var/tmp:/var/tmp -v ${HOME}/data/ds114_test2:/bids_dataset -v ${HOME}/data/ds114_test2_freesurfer_precomp_v6.0.0:/outputs bids/${CIRCLE_PROJECT_REPONAME,,} /bids_dataset /outputs group2 --license_file=/license.txt && mkdir -p ${HOME}/outputs2/ && sudo mv ${HOME}/data/ds114_test2_freesurfer_precomp_v6.0.0/00_group* ${HOME}/outputs2/ && cat ${HOME}/outputs2/00_group2_stats_tables/lh.aparc.thickness.tsv :
39+
- docker run -ti --rm --read-only -v $PWD/license.txt:/license.txt -v /tmp:/tmp -v /var/tmp:/var/tmp -v ${HOME}/data/ds114_test2:/bids_dataset -v ${HOME}/data/ds114_test2_freesurfer_precomp_v6.0.0:/outputs bids/${CIRCLE_PROJECT_REPONAME,,} /bids_dataset /outputs group2 --license_file=/license.txt && mkdir -p ${HOME}/outputs2/ && sudo mv ${HOME}/data/ds114_test2_freesurfer_precomp_v6.0.0/00_group* ${HOME}/outputs2/ && cat ${HOME}/outputs2/00_group2_stats_tables/lh.aparc.thickness.tsv && cat ${HOME}/outputs2/00_group2_stats_tables/euler.tsv:
3940
timeout: 21600
4041

4142
deployment:

run.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
from shutil import rmtree
99
import subprocess
1010
from warnings import warn
11-
11+
import pandas as pd
12+
import re
1213

1314
def run(command, env={}, ignore_errors=False):
1415
merged_env = os.environ
@@ -39,7 +40,8 @@ def run(command, env={}, ignore_errors=False):
3940
'Multiple participant level analyses can be run independently '
4041
'(in parallel) using the same output_dir. '
4142
'"group1" creates study specific group template. '
42-
'"group2 exports group stats tables for cortical parcellation and subcortical segmentation.',
43+
'"group2" exports group stats tables for cortical parcellation, subcortical segmentation '
44+
'a table with euler numbers.',
4345
choices=['participant', 'group1', 'group2'])
4446
parser.add_argument('--participant_label', help='The label of the participant that should be analyzed. The label '
4547
'corresponds to sub-<participant_label> from the BIDS spec '
@@ -163,7 +165,6 @@ def run(command, env={}, ignore_errors=False):
163165
env = {'FS_LICENSE': args.license_file}
164166
else:
165167
raise Exception("Provided license file does not exist")
166-
raise Exception("Provided license file does not exist")
167168

168169
# running participant level
169170
if args.analysis_level == "participant":
@@ -491,4 +492,46 @@ def run(command, env={}, ignore_errors=False):
491492
print("\nTable export finished for %d subjects/sessions." % len(subjects))
492493

493494
else:
494-
print("\nNo subjects included in the analysis. Skipping group2 level.")
495+
print("\nNo subjects included in the analysis. Skipping group2 level stats tables.")
496+
497+
498+
# This extracts the euler numbers for the orig.nofix surfaces from the recon-all.log file
499+
# see Rosen et al. (2017), https://www.biorxiv.org/content/early/2017/10/01/125161
500+
def extract_euler(logfile):
501+
with open(logfile) as fi:
502+
logtext = fi.read()
503+
p = re.compile(r"orig.nofix lheno =\s+(-?\d+), rheno =\s+(-?\d+)")
504+
results = p.findall(logtext)
505+
if len(results) != 1:
506+
raise Exception("Euler number could not be extracted from {}".format(logfile))
507+
lh_euler, rh_euler = results[0]
508+
return int(lh_euler), int(rh_euler)
509+
510+
511+
512+
euler_out_file = os.path.join(table_dir, "euler.tsv")
513+
print("Writing euler tables to %s." % euler_out_file)
514+
515+
# get freesurfer subjects
516+
os.chdir(output_dir)
517+
subjects = []
518+
for s in subjects_to_analyze:
519+
subjects += glob("sub-{}*".format(s))
520+
# remove long subjects as they don't have orig.nofix surfaces,
521+
# therefore no euler numbers
522+
subjects = list(filter(lambda s: ".long.sub-" not in s, subjects))
523+
if len(subjects) > 0:
524+
df = pd.DataFrame([], columns=["subject", "lh_euler", "rh_euler"])
525+
for subject in subjects:
526+
logfile = os.path.join(output_dir, subject, "scripts/recon-all.log")
527+
lh_euler, rh_euler = extract_euler(logfile)
528+
df_subject = pd.DataFrame({"subject": [subject],
529+
"lh_euler": [lh_euler],
530+
"rh_euler": [rh_euler]},
531+
columns=["subject", "lh_euler", "rh_euler"])
532+
df = df.append(df_subject)
533+
df["mean_euler_bh"] = df[["lh_euler", "rh_euler"]].mean(1)
534+
df.sort_values("subject", inplace=True)
535+
df.to_csv(euler_out_file, sep="\t", index=False)
536+
else:
537+
print("\nNo subjects included in the analysis. Skipping group2 level euler number table.")

0 commit comments

Comments
 (0)