Skip to content

Commit 7a87d97

Browse files
committed
major changes
1 parent 2f18074 commit 7a87d97

File tree

5 files changed

+126
-91
lines changed

5 files changed

+126
-91
lines changed

README.md

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,23 @@
22

33
# DSST Defacing Pipeline
44

5-
The DSST Defacing Pipeline has been developed to make the process of defacing anatomical scans of large datasets, visually inspecting for accuracy and fixing scans that fail visual inspection more efficient and straightforward. The pipeline _requires_ the input dataset to be in BIDS format. A conceptual description of the pipeline can found [here](#conceptual-design).
5+
The DSST Defacing Pipeline has been developed to make the process of defacing anatomical scans of large datasets,
6+
visually inspecting for accuracy and fixing scans that fail visual inspection more efficient and straightforward. The
7+
pipeline _requires_ the input dataset to be in BIDS format. A conceptual description of the pipeline can
8+
found [here](#conceptual-design).
69

710
## Usage Instructions
11+
812
### Clone this repository
13+
914
```bash
1015
git clone [email protected]:nih-fmrif/dsst-defacing-pipeline.git
1116
```
17+
1218
### Run `dsst_defacing_wf.py`
19+
1320
To deface anatomical scans in the dataset, run `dsst_defacing_wf.py` script.
21+
1422
```
1523
% python src/dsst_defacing_wf.py -h
1624
usage: dsst_defacing_wf.py [-h] --input INPUT --output OUTPUT [--participant-id SUBJ_ID] [--session-id SESS_ID] [--no-clean]
@@ -31,85 +39,112 @@ optional arguments:
3139
3240
```
3341

34-
The script can be run serially on a BIDS dataset or in parallel at subject/session level. The three methods of running the script have been described below with example commands:
42+
The script can be run serially on a BIDS dataset or in parallel at subject/session level. The three methods of running
43+
the script have been described below with example commands:
3544

36-
**NOTE:** In the example commands below, <path/to/BIDS/input/dataset> and <path/to/desired/output/directory> are placeholders for paths to input and output directories respectively.
45+
For readability of example commands, the following bash variables have defined as follows:
46+
47+
```bash
48+
INPUT_DIR="<path/to/BIDS/input/dataset>"
49+
OUTPUT_DIR="<path/to/desired/defacing/output/directory>"
50+
```
51+
52+
**NOTE:** In the example commands below, <path/to/BIDS/input/dataset> and <path/to/desired/output/directory> are
53+
placeholders for paths to input and output directories respectively.
3754

3855
#### Option 1: Serially
56+
3957
If you have a small dataset with less than 10 subjects, then it might be easiest to run the defacing algorithm serially.
4058

4159
```bash
42-
python dsst_defacing_wf.py -i <path/to/BIDS/input/dataset> -o <path/to/desired/output/directory>
60+
python dsst_defacing_wf.py -i $INPUT_DIR -o $OUTPUT_DIR
4361
```
4462

4563
#### Option 2: In parallel at subject level
46-
If you have dataset with over 10 subjects, then it might be more practical to run the pipeline in parallel for every subject in the dataset using the `-p/--participant-id` option as follows:
64+
65+
If you have dataset with over 10 subjects, then it might be more practical to run the pipeline in parallel for every
66+
subject in the dataset using the `-p/--participant-id` option as follows:
4767

4868
```bash
49-
python dsst_defacing_wf.py -i <path/to/BIDS/input/dataset> -o <path/to/desired/defacing/output/directory> -p sub-<index>
69+
python dsst_defacing_wf.py -i ${INPUT_DIR} -o ${OUTPUT_DIR} -p sub-<index>
5070
```
5171

52-
a. Assuming these scripts are run on the NIH HPC system, the first step would be to create a `swarm` file:
72+
a. Assuming these scripts are run on the NIH HPC system, the first step would be to create a `swarm` file:
5373

5474
```bash
55-
for i in `ls -d <path/to/BIDS/input/dataset>*`; do \
56-
SUBJ=$(echo $i | sed 's|<path/to/BIDS/input/dataset>||g' ); \
57-
echo "python dsst_defacing_wf.py -i <path/to/BIDS/input/dataset> -o <path/to/desired/defacing/output/directory> -s $SUBJ"; \
75+
76+
for i in `ls -d ${INPUT_DIR}/*`; do \
77+
SUBJ=$(echo $i | sed "s|${INPUT_DIR}/||g" ); \
78+
echo "python src/dsst_defacing_wf.py -i ${INPUT_DIR} -o ${OUTPUT_DIR} -s ${SUBJ}"; \
5879
done > defacing_parallel_subject_level.swarm
5980
```
60-
Purpose: Loop through the dataset and find all subject directories to construct `dsst_defacing_wf.py` command with `-p/--participant-id` option.
6181

62-
b. Run the swarm file with following command to start a swarm job
82+
Purpose: Loop through the dataset and find all subject directories to construct `dsst_defacing_wf.py` command
83+
with `-p/--participant-id` option.
84+
85+
b. Run the swarm file with following command to start a swarm job
86+
6387
```bash
64-
swarm -f defacing_parallel_subject_level.swarm --module afni,fsl --merge-output --logdir swarm_log
88+
swarm -f defacing_parallel_subject_level.swarm --merge-output --logdir ${OUTPUT_DIR}/swarm_log
6589
```
6690

6791
#### Option 3: In parallel at session level
68-
If the input dataset has multiple sessions per subject, then run the pipeline on every session in the dataset parallelly. Similar to Option 2, the following commands loop through the dataset to find subject and session IDs to create a `swarm` file to be run on NIH HPC systems.
92+
93+
If the input dataset has multiple sessions per subject, then run the pipeline on every session in the dataset
94+
parallelly. Similar to Option 2, the following commands loop through the dataset to find subject and session IDs to
95+
create a `swarm` file to be run on NIH HPC systems.
6996

7097
```bash
71-
for i in `ls -d <path/to/BIDS/input/dataset>*`; do
72-
SUBJ=$(echo $i | sed "s|<path/to/BIDS/input/dataset>/||g" );
73-
for j in `ls -d <path/to/BIDS/input/dataset>/${SUBJ}/*`; do
74-
SESS=$(echo $j | sed "s|<path/to/BIDS/input/dataset>/${SUBJ}/||g" )
75-
echo "python dsst_defacing_wf.py -i <path/to/BIDS/input/dataset> -o <path/to/desired/defacing/output/directory> -p ${SUBJ} -s ${SESS}";
98+
for i in `ls -d ${INPUT_DIR}/*`; do
99+
SUBJ=$(echo $i | sed "s|${INPUT_DIR}/||g" );
100+
for j in `ls -d ${INPUT_DIR}/${SUBJ}/*`; do
101+
SESS=$(echo $j | sed "s|${INPUT_DIR}/${SUBJ}/||g" )
102+
echo "python src/dsst_defacing_wf.py -i ${INPUT_DIR} -o ${OUTPUT_DIR} -p ${SUBJ} -s ${SESS}";
76103
done;
77104
done > defacing_parallel_session_level.swarm
78105
```
106+
79107
```bash
80-
swarm -f defacing_parallel_session_level.swarm --module afni,fsl --merge-output --logdir swarm_log
108+
swarm -f defacing_parallel_session_level.swarm --merge-output --logdir ${OUTPUT_DIR}/swarm_log
81109
```
82110

83111
### Visually inspect defaced scans using VisualQC
84112

85-
Pre-requisite: Install VisualQC from https://raamana.github.io/visualqc/installation.html#stable-release[](https://raamana.github.io/visualqc/installation.html#stable-release)
113+
Pre-requisite: Install VisualQC
114+
from https://raamana.github.io/visualqc/installation.html#stable-release[](https://raamana.github.io/visualqc/installation.html#stable-release)
115+
116+
Once VisualQC is installed, please run the following command to open VisualQC deface GUI to start visually inspecting
117+
defaced scans:
86118

87-
Once VisualQC is installed, please run the following command to open VisualQC deface GUI to start visually inspecting defaced scans:
88119
```bash
89120
sh <path/to/defacing/output/directory>/visualqc_prep/defacing_qc_cmd
90121
```
91122

92123
Visual QC defacing accuracy gallery https://raamana.github.io/visualqc/gallery_defacing.html
93124

94-
95125
## Terminology
96-
While describing the process, we frequently use the following terms:
126+
127+
While describing the process, we frequently use the following terms:
97128

98129
- **Primary Scan:** The best quality T1w scan within a session. For programmatic selection, we assume that the most
99130
recently acquired T1w scan is of the best quality.
100131
- **Other/Secondary Scans:** All scans *except* the primary scan are grouped together and referred to as "other" or "
101132
secondary" scans for a given session.
102-
- **Mapping File:** A JSON file that assigns maps a primary scan (or `primary_t1`) to all other scans within a session. Please find an example file [here]().
133+
- **Mapping File:** A JSON file that assigns maps a primary scan (or `primary_t1`) to all other scans within a session.
134+
Please find an example file [here]().
103135
- **[VisualQC](https://raamana.github.io/visualqc):** A suite of QC tools developed by Pradeep Raamana (Assistant
104136
Professor at University of Pittsburgh).
105137

106138
## Conceptual design
107-
1. Generate a ["primary" scans](#terminology) to [other scans'](#terminology) mapping file.
139+
140+
1. Generate a ["primary" scans](#terminology) to [other scans'](#terminology) mapping file.
108141
2. Deface primary scans
109142
with [@afni_refacer_run](https://afni.nimh.nih.gov/pub/dist/doc/htmldoc/tutorials/refacer/refacer_run.html) program
110-
developed by the AFNI Team.
111-
3. To deface remaining scans in the session, register them to the primary scan (using FSL `flirt` command) and then use the primary scan's defacemask to generate a defaced image (using `fslmaths` command).
112-
4. Visually inspect defaced scans with [VisualQC](https://raamana.github.io/visualqc) deface tool or any other preferred tool.
143+
developed by the AFNI Team.
144+
3. To deface remaining scans in the session, register them to the primary scan (using FSL `flirt` command) and then use
145+
the primary scan's defacemask to generate a defaced image (using `fslmaths` command).
146+
4. Visually inspect defaced scans with [VisualQC](https://raamana.github.io/visualqc) deface tool or any other preferred
147+
tool.
113148
5. Correct/fix defaced scans that failed visual inspection. See [here]() for more info on types of failures.
114149

115150
![Defacing Pipeline flowchart](images/pipeline_screen_quality.png)
@@ -120,7 +155,8 @@ While describing the process, we frequently use the following terms:
120155
BN, Milev R, Müller DJ, Kennedy SH, Scott CJM, Strother SC, and Arnott SR (2021)
121156
[Multisite Comparison of MRI Defacing Software Across Multiple Cohorts](10.3389/fpsyt.2021.617997). Front. Psychiatry
122157
12:617997. doi:10.3389/fpsyt.2021.617997
123-
2. `@afni_refacer_run` is the defacing tool used under the hood. [AFNI Refacer program](https://afni.nimh.nih.gov/pub/dist/doc/htmldoc/tutorials/refacer/refacer_run.html).
158+
2. `@afni_refacer_run` is the defacing tool used under the
159+
hood. [AFNI Refacer program](https://afni.nimh.nih.gov/pub/dist/doc/htmldoc/tutorials/refacer/refacer_run.html).
124160
3. FSL's [FLIRT](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FLIRT)
125161
and [`fslmaths`](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/Fslutils?highlight=%28fslmaths%29) programs have been used
126162
for registration and masking steps in the workflow.

src/deface.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,33 @@ def copy_over_sidecar(scan_filepath, input_anat_dir, output_anat_dir):
6868
shutil.copy2(json_sidecar, output_anat_dir / filename)
6969

7070

71-
def reorganize_into_bids(input_bids_dir, subj_dir, sess_dir, primary_t1, defacing_outdir, no_clean):
71+
def vqcdeface_prep(bids_input_dir, defaced_anat_dir, bids_defaced_outdir):
72+
defacing_qc_dir = bids_defaced_outdir.parent / 'QC_prep' / 'defacing_QC'
73+
interested_files = [f for f in defaced_anat_dir.rglob('*.nii.gz') if
74+
'work_dir' not in str(f).split('/')]
75+
print(interested_files)
76+
for defaced_img in interested_files:
77+
entities = defaced_img.name.split('.')[0].split('_')
78+
vqcd_subj_dir = defacing_qc_dir / f"{'/'.join(entities)}"
79+
vqcd_subj_dir.mkdir(parents=True, exist_ok=True)
80+
81+
defaced_link = vqcd_subj_dir / 'defaced.nii.gz'
82+
if not defaced_link.is_symlink():
83+
defaced_link.symlink_to(defaced_img)
84+
print(list(bids_input_dir.rglob(defaced_img.name)))
85+
img = list(bids_input_dir.rglob(defaced_img.name))[0]
86+
img_link = vqcd_subj_dir / 'orig.nii.gz'
87+
if not img_link.is_symlink(): img_link.symlink_to(img)
88+
89+
90+
def reorganize_into_bids(input_bids_dir, subj_dir, sess_dir, primary_t1, bids_defaced_outdir, no_clean):
7291
subj_id = subj_dir.name
7392
sess_id = sess_dir.name if sess_dir else None
7493

7594
if sess_id:
76-
anat_dirs = list(defacing_outdir.joinpath(subj_id, sess_id).rglob('anat'))
95+
anat_dirs = list(bids_defaced_outdir.joinpath(subj_id, sess_id).rglob('anat'))
7796
else:
78-
anat_dirs = list(defacing_outdir.joinpath(subj_id).rglob('anat'))
97+
anat_dirs = list(bids_defaced_outdir.joinpath(subj_id).rglob('anat'))
7998
# make workdir for each session within anat dir
8099
for anat_dir in anat_dirs:
81100
# iterate over all nii files within an anat dir to rename all primary and "other" scans
@@ -86,21 +105,24 @@ def reorganize_into_bids(input_bids_dir, subj_dir, sess_dir, primary_t1, defacin
86105
compress_to_gz(nii_filepath, gz_file)
87106

88107
# copy over corresponding json sidecar
89-
copy_over_sidecar(Path(primary_t1), input_bids_dir / anat_dir.relative_to(defacing_outdir), anat_dir)
108+
copy_over_sidecar(Path(primary_t1), input_bids_dir / anat_dir.relative_to(bids_defaced_outdir),
109+
anat_dir)
90110

91111
elif nii_filepath.name.endswith('_defaced.nii.gz'):
92112
new_filename = '_'.join(nii_filepath.name.split('_')[:-1]) + '.nii.gz'
93113
shutil.copy2(nii_filepath, str(anat_dir / new_filename))
94114

95-
copy_over_sidecar(nii_filepath, input_bids_dir / anat_dir.relative_to(defacing_outdir), anat_dir)
115+
copy_over_sidecar(nii_filepath, input_bids_dir / anat_dir.relative_to(bids_defaced_outdir), anat_dir)
96116

97117
# move QC images and afni intermediate files to a new directory
98-
intermediate_files_dir = anat_dir / 'workdir'
118+
intermediate_files_dir = anat_dir / 'work_dir'
99119
intermediate_files_dir.mkdir(parents=True, exist_ok=True)
100120
for dirpath in anat_dir.glob('*'):
101121
if dirpath.name.startswith('workdir') or dirpath.name.endswith('QC'):
102122
shutil.move(dirpath, intermediate_files_dir)
103123

124+
vqcdeface_prep(input_bids_dir, anat_dir, bids_defaced_outdir)
125+
104126
if not no_clean:
105127
shutil.rmtree(intermediate_files_dir)
106128

src/dsst_defacing_wf.py

Lines changed: 3 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import argparse
22
import json
3-
import re
43
import subprocess
54
from pathlib import Path
65

@@ -56,35 +55,6 @@ def get_sess_dirs(subj_dir_path, mapping_dict):
5655
return sess_dirs
5756

5857

59-
def create_defacing_id_list(qc_dir):
60-
rel_paths_to_orig = [re.sub('/orig.nii.gz', '', str(o.relative_to(qc_dir))) for o in qc_dir.rglob('orig.nii.gz')]
61-
with open(qc_dir / 'defacing_id_list.txt', 'w') as f:
62-
f.write('\n'.join(rel_paths_to_orig))
63-
64-
65-
def vqcdeface_prep(input_dir, defacing_output_dir):
66-
defacing_qc_dir = defacing_output_dir.parent / 'QC_prep' / 'defacing_QC'
67-
interested_files = [f for f in defacing_output_dir.rglob('*.nii.gz') if
68-
'workdir' not in str(f).split('/')]
69-
for defaced_img in interested_files:
70-
entities = defaced_img.name.split('.')[0].split('_')
71-
vqcd_subj_dir = defacing_qc_dir / f"{'/'.join(entities)}"
72-
vqcd_subj_dir.mkdir(parents=True, exist_ok=True)
73-
74-
defaced_link = vqcd_subj_dir / 'defaced.nii.gz'
75-
if not defaced_link.exists():
76-
defaced_link.symlink_to(defaced_img)
77-
img = list(input_dir.rglob(defaced_img.name))[0]
78-
img_link = vqcd_subj_dir / 'orig.nii.gz'
79-
if not img_link.exists(): img_link.symlink_to(img)
80-
81-
create_defacing_id_list(defacing_qc_dir)
82-
83-
vqcdeface_cmd = f"vqcdeface -u {defacing_qc_dir} -i {defacing_qc_dir / 'defacing_id_list.txt'} -m orig.nii.gz -d defaced.nii.gz -r defaced_render"
84-
85-
return vqcdeface_cmd
86-
87-
8858
def main():
8959
# get command line arguments
9060
input_dir, output, subj_id, sess_id, no_clean = get_args()
@@ -93,8 +63,8 @@ def main():
9363
mapping_dict = generate_mappings.crawl(input_dir, output)
9464

9565
# create a separate bids tree with only defaced scans
96-
defacing_outputs = output / 'bids_defaced'
97-
defacing_outputs.mkdir(parents=True, exist_ok=True)
66+
bids_defaced_outdir = output / 'bids_defaced'
67+
bids_defaced_outdir.mkdir(parents=True, exist_ok=True)
9868

9969
afni_refacer_failures = [] # list to capture afni_refacer_run failures
10070

@@ -114,24 +84,13 @@ def main():
11484
# calling deface.py script
11585
for subj_sess in subj_sess_list:
11686
missing_refacer_out = deface.deface_primary_scan(input_dir, subj_sess[0], subj_sess[1], mapping_dict,
117-
defacing_outputs, no_clean)
87+
bids_defaced_outdir, no_clean)
11888
if missing_refacer_out is not None:
11989
afni_refacer_failures.extend(missing_refacer_out)
12090

12191
with open(output / 'logs' / 'failed_afni_refacer_output.txt', 'w') as f:
12292
f.write('\n'.join(afni_refacer_failures)) # TODO Not very useful when running the pipeline in parallel
12393

124-
# unload fsl module and use fsleyes installed on conda env
125-
# os.environ['TMP_DISPLAY'] =
126-
127-
# prep for visual inspection using visualqc deface
128-
print(f"Preparing for QC by visual inspection...\n")
129-
130-
vqcdeface_cmd = vqcdeface_prep(input_dir, defacing_outputs)
131-
print(f"Run the following command to start a VisualQC Deface session:\n\t{vqcdeface_cmd}\n")
132-
with open(output / 'QC_prep' / 'defacing_qc_cmd', 'w') as f:
133-
f.write(vqcdeface_cmd + '\n')
134-
13594

13695
if __name__ == "__main__":
13796
main()

src/generate_mappings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def primary_scans_qc_prep(mapping_dict, qc_prep):
7676

7777
id_list.append(dest)
7878
primary_link = dest / 'primary.nii.gz'
79-
if not primary_link.exists():
79+
if not primary_link.is_symlink():
8080
try:
8181
primary_link.symlink_to(primary)
8282
except:

0 commit comments

Comments
 (0)