Skip to content

Commit

Permalink
v3.0 support for skull stripped T1s, README reformatting and updates,…
Browse files Browse the repository at this point in the history
… new containers
  • Loading branch information
leonyichencai committed Jun 13, 2022
1 parent 5d1ec47 commit 860a7eb
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 144 deletions.
248 changes: 120 additions & 128 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,166 +1,158 @@
# Synb0-DISCO

## Contents

* [Overview](#overview)
* [Dockerized Application](#dockerized-application)
* [Docker Instructions](#docker-instructions)
* [Singularity Instructions](#singularity-instructions)
* [Non-containerized Instructions](#non-containerized-instructions)
* [Flags](#flags)
* [Inputs](#inputs)
* [Outputs](#outputs)
* [After Running](#after-running)

## Overview

This repository implements the paper "Synthesized b0 for diffusion distortion correction" and "Distortion correction of diffusion weighted MRI without reverse phase-encoding scans or field-maps".

This tool aims to enable susceptibility distortion correction with historical and/or limited datasets that do not include specific sequences for distortion correction (i.e. reverse phase-encoded scans). In short, we synthesize an "undistorted" b=0 image that matches the geometry of structural T1w images and also matches the contrast from diffusion images. We can then use this 'undistorted' image in standard pipelines (i.e. TOPUP) and tell the algorithm that this synthetic image has an infinite bandwidth. Note that the processing below enables both image synthesis, and also synthesis + full pipeline correction, if desired.

For deployment we provide a docker container which uses the trained model to predict the undistorted b0 to be used in susceptability distortion correction for diffusion weighted MRI. Please use the following citation to refer to this work:
Please use the following citations to refer to this work:

Schilling KG, Blaber J, Hansen C, Cai L, Rogers B, Anderson AW, Smith S, Kanakaraj P, Rex T, Resnick SM, Shafer AT, Cutting LE, Woodward N, Zald D, Landman BA. Distortion correction of diffusion weighted MRI without reverse phase-encoding scans or field-maps. PLoS One. 2020 Jul 31;15(7):e0236418. doi: 10.1371/journal.pone.0236418. PMID: 32735601; PMCID: PMC7394453.

Schilling KG, Blaber J, Huo Y, Newton A, Hansen C, Nath V, Shafer AT, Williams O, Resnick SM, Rogers B, Anderson AW, Landman BA. Synthesized b0 for diffusion distortion correction (Synb0-DisCo). Magn Reson Imaging. 2019 Dec;64:62-70. doi: 10.1016/j.mri.2019.05.008. Epub 2019 May 7. PMID: 31075422; PMCID: PMC6834894.

Schilling KG, Blaber J, Hansen C, Cai L, Rogers B, Anderson AW, Smith S, Kanakaraj P, Rex T, Resnick SM, Shafer AT, Cutting LE, Woodward N, Zald D, Landman BA. Distortion correction of diffusion weighted MRI without reverse phase-encoding scans or field-maps. PLoS One. 2020 Jul 31;15(7):e0236418. doi: 10.1371/journal.pone.0236418. PMID: 32735601; PMCID: PMC7394453.
## Dockerized Application

For deployment we provide a [Docker container](https://hub.docker.com/repository/docker/leonyichencai/synb0-disco) which uses the trained model to predict the undistorted b0 to be used in susceptability distortion correction for diffusion weighted MRI. For those who prefer, Docker containers can be converted to Singularity containers (see below).

# synb0_25iso_app
[Docker Hub](https://hub.docker.com/repository/docker/hansencb/synb0)
## Docker Instructions:

# Run Instructions:
For docker:
```
sudo docker run --rm \
-v $(pwd)/INPUTS/:/INPUTS/ \
-v $(pwd)/OUTPUTS:/OUTPUTS/ \
-v <path to license.txt>:/extra/freesurfer/license.txt \
--user $(id -u):$(id -g) \
hansencb/synb0
Flags:
--notopup Skips the application of FSL's topup susceptibility correction
* as a default, we run topup for you, although you may want to run this on
your own (for example with your own config file, or if you would like to
utilize multiple b0's)
See INPUTS/OUTPUTS sections below.
In short, if within your current directory you have your INPUTS
and OUTPUTS folder, you can run this command copy/paste with the
only change being <path to license.txt> should point to
freesurfer licesnse.txt file on your system.
If INPUTS and OUTPUTS are not within your current directory, you
will need to change $(pwd)/INPUTS/ to the full path to your
input directory, and similarly for OUTPUTS.
*** For Mac users, Docker defaults allows only 2gb of RAM
and 2 cores - we suggest giving Docker access to >8Gb
of RAM
*** Additionally on MAC, if permissions issues prevent binding the
path to the license.txt file, we suggest moving the freesurfer
license.txt file to the current path and replacing the path line to
" $(pwd)/license.txt:/extra/freesurfer/license.txt "
leonyichencai/synb0-disco:v3.0
<flags>
```
For singularity:
+ Build synb0.simg in the current directory:

* If within your current directory you have your INPUTS and OUTPUTS folder, you can run this command copy/paste with the only change being \<path to license.txt\> should point to the freesurfer license.txt file on your system.
* If INPUTS and OUTPUTS are not within your current directory, you will need to change $(pwd)/INPUTS/ to the full path to your input directory, and similarly for OUTPUTS.
* For Mac users, Docker defaults allows only 2gb of RAM and 2 cores - we suggest giving Docker access to >8Gb of RAM
* Additionally on MAC, if permissions issues prevent binding the path to the license.txt file, we suggest moving the freesurfer license.txt file to the current path and replacing the path line to " $(pwd)/license.txt:/extra/freesurfer/license.txt "

## Singularity Instructions

First, build the synb0.simg container in the current directory:

```
singularity pull docker://hansencb/synb0
singularity pull docker://leonyichencai/synb0-disco:v3.0
```
+ Run the synb0.simg container:

Then, to run the synb0.simg container:

```
singularity run -e \
-B INPUTS/:/INPUTS \
-B OUTPUTS/:/OUTPUTS \
-B <path to license.txt>:/extra/freesurfer/license.txt \
<path to synb0.simg>
<flags>
```

<path to license.txt> should point to freesurfer licesnse.txt file
<path to synb0.simg> should point to the singularity container
* \<path to license.txt\> should point to freesurfer licesnse.txt file
* \<path to synb0.simg\> should point to the singularity container

Flags:
--notopup Skips the application of FSL's topup susceptibility correction
* as a default, we run topup for you, although you may want to run this on
your own (for example with your own config file, or if you would like to
utilize multiple b0's)
```
INPUTS:
```
INPUTS directory must contain the following:
b0.nii.gz, T1.nii.gz, and acqparams.txt
b0.nii.gz includes the non-diffusion weighted image(s).
T1.nii.gz is the T1-weighted image.
acqparams.txt describes the acqusition parameters, and is described in detail
on the FslWiki for topup (https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup).Briefly,
it describes the direction of distortion and tells TOPUP that the synthesized image
has an effective echo spacing of 0 (infinite bandwidth). An example acqparams.txt is
displayed below, in which distortion is in the second dimension, note that the second
row corresponds to the synthesized, undistorted, b0:
$ cat acqparams.txt
0 1 0 0.062
0 1 0 0.000
T1_mask.nii.gz is an optional user provided mask which excludes the skull in the T1 image.
If not provided a mask will be estimated using FSL's BET.
```
Without singularity or Docker:
```
If you choose to run this in bash, the script that is containerized is located in
src/pipeline.sh. The paths in pipeline.sh are specific to the docker/singularity file
system, but the processing can be replicated using the scripts in src.
## Non-containerized Instructions

These utilize freesurfer, FSL, ANTS, and a python environment with pytorch.
```
If you choose to run this in bash, the primary script is located in src/pipeline.sh. The paths in pipeline.sh are specific to the docker/singularity file
systems, but the processing can be replicated using the scripts in src. These utilize freesurfer, FSL, ANTS, and a python environment with pytorch.

OUTPUTS:
```
After running, the outputs directory contains the following:
T1_mask.nii.gz: brain extracted (skullstripped) T1
T1_norm.nii.gz: normalized T1
epi_reg_d.mat: epi_reg b0 to T1 in FSL format
epi_reg_d_ANTS.txt: epi_reg to T1 in ANTS format
Ants registration of T1_norm to/from MNI space:
ANTS0GenericAffine.mat
ANTS1InverseWarp.nii.gz
ANTS1Warp.nii.gz
T1_norm_lin_atlas_2_5.nii.gz: linear transform T1 to MNI
b0_d_lin_atlas_2_5.nii.gz : linear transform distorted b0 in MNI space
T1_norm_nonlin_atlas_2_5.nii.gz: nonlinear transform T1 to MNI
b0_d_nonlin_atlas_2_5.nii.gz : nonlinear transform distorted b0 in MNI space
Inferences (predictions) for each of five folds:
T1 input path: /OUTPUTS/T1_norm_lin_atlas_2_5.nii.gz
b0 input path: /OUTPUTS/b0_d_lin_atlas_2_5.nii.gz
b0_u_lin_atlas_2_5_FOLD_1.nii.gz
b0_u_lin_atlas_2_5_FOLD_2.nii.gz
b0_u_lin_atlas_2_5_FOLD_3.nii.gz
b0_u_lin_atlas_2_5_FOLD_4.nii.gz
b0_u_lin_atlas_2_5_FOLD_5.nii.gz
Ensemble average of inferences:
b0_u_lin_atlas_2_5_merged.nii.gz
b0_u_lin_atlas_2_5.nii.gz
b0_u.nii.gz: Syntehtic b0 native space
b0_d_smooth.nii.gz: smoothed b0
b0_all.nii.gz: stack of distorted and synthetized image as input to topup
topup outputs to be used for eddy:
topup_movpar.txt
b0_all_topup.nii.gz
b0_all.topup_log
topup_fieldcoef.nii.gz
```
## Flags:

AFTER RUNNING:
```
After running, we envision using the topup outputs directly with FSL's
eddy command, exactly as would be done if a full set of reverse PE
scans was acquired. For example:
**--notopup**

Skip the application of FSL's topup susceptibility correction. As a default, we run topup for you, although you may want to run this on your own (for example with your own config file, or if you would like to utilize multiple b0's).

**--stripped**

Lets the container know the supplied T1 has already been skull stripped. As a default, we assume it is not skull stripped. *Please note this feature requires a well-stripped T1 as stripping artifacts can affect performance.*

## Inputs

The INPUTS directory must contain the following:
* b0.nii.gz: the non-diffusion weighted image(s)
* T1.nii.gz: the T1-weighted image (either raw or skull-stripped, see [Flags](#flags))
* acqparams.txt: A text file that describes the acqusition parameters, and is described in detail on the FslWiki for topup (https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup). Briefly,
it describes the direction of distortion and tells TOPUP that the synthesized image has an effective echo spacing of 0 (infinite bandwidth). An example acqparams.txt is
displayed below, in which distortion is in the second dimension, note that the second row corresponds to the synthesized, undistorted, b0:
```
$ cat acqparams.txt
0 1 0 0.062
0 1 0 0.000
```

## Outputs

After running, the OUTPUTS directory contains the following preprocessing files:

* T1_mask.nii.gz: brain extracted (skull-stripped) T1 (a copy of the input if T1.nii.gz is already skull-stripped)
* T1_norm.nii.gz: normalized T1
* epi_reg_d.mat: epi_reg b0 to T1 in FSL format
* epi_reg_d_ANTS.txt: epi_reg to T1 in ANTS format
* ANTS0GenericAffine.mat: Affine ANTs registration of T1_norm to/from MNI space
* ANTS1Warp.nii.gz: Deformable ANTs registration of T1_norm to/from MNI space
* ANTS1InverseWarp.nii.gz: Inverse deformable ANTs registration of T1_norm to/from MNI space
* T1_norm_lin_atlas_2_5.nii.gz: linear transform T1 to MNI
* b0_d_lin_atlas_2_5.nii.gz: linear transform distorted b0 in MNI space
* T1_norm_nonlin_atlas_2_5.nii.gz: nonlinear transform T1 to MNI
* b0_d_nonlin_atlas_2_5.nii.gz: nonlinear transform distorted b0 in MNI space

The OUTPUTS directory also contains inferences (predictions) for each of five folds utilizing T1_norm_lin_atlas_2_5.nii.gz and b0_d_lin_atlas_2_5.nii.gz as inputs:

* b0_u_lin_atlas_2_5_FOLD_1.nii.gz
* b0_u_lin_atlas_2_5_FOLD_2.nii.gz
* b0_u_lin_atlas_2_5_FOLD_3.nii.gz
* b0_u_lin_atlas_2_5_FOLD_4.nii.gz
* b0_u_lin_atlas_2_5_FOLD_5.nii.gz

After inference the ensemble average is taken in atlas space:

* b0_u_lin_atlas_2_5_merged.nii.gz
* b0_u_lin_atlas_2_5.nii.gz

It is then moved to native space for the undistorted, synthetic output:

* b0_u.nii.gz: Synthetic b0 native space

The undistorted synthetic output, and a smoothed distorted input can then be stacked together for topup:

* b0_d_smooth.nii.gz: smoothed b0
* b0_all.nii.gz: stack of distorted and synthetized image as input to topup

Finally, the topup outputs to be used for eddy:

* topup_movpar.txt
* b0_all_topup.nii.gz
* b0_all.topup_log
* topup_fieldcoef.nii.gz


## After Running

After running, we envision using the topup outputs directly with FSL's eddy command, exactly as would be done if a full set of reverse PE scans was acquired. For example:

```
eddy --imain=path/to/diffusiondata.nii.gz --mask=path/to/brainmask.nii.gz \
--acqp=path/to/acqparams.txt --index=path/to/index.txt \
--bvecs=path/to/bvecs.txt --bvals=path/to/bvals.txt
--topup=path/to/OUTPUTS/topup --out=eddy_unwarped_images
```

where imain is the original diffusion data, mask is a brain mask, acqparams
is from before, index is the traditional eddy index file which contains an
index (most likely a 1) for every volume in the diffusion dataset, topup points
to the output of the singularity/docker pipeline, and out is the eddy-corrected
images utilizing the field coefficients from the previous step.
Alternatively, if you choose to run --notopup flag, the file you are interested in
is b0_all. This is a concatenation of the real b0 and the synthesized undistorted
b0. We run topup with this file, although you may chose to do so utilizing your
topup version or config file.
where imain is the original diffusion data, mask is a brain mask, acqparams is from before, index is the traditional eddy index file which contains an index (most likely a 1) for every volume in the diffusion dataset, topup points to the output of the singularity/docker pipeline, and out is the eddy-corrected images utilizing the field coefficients from the previous step.

Alternatively, if you choose to run --notopup flag, the file you are interested in is b0_all. This is a concatenation of the real b0 and the synthesized undistorted b0. We run topup with this file, although you may chose to do so utilizing your topup version or config file.
2 changes: 1 addition & 1 deletion Singularity
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
Bootstrap: docker
From: hansencb/synb0
From: leonyichencai/synb0-disco:v3.0
Binary file modified atlases/mni_icbm152_t1_tal_nlin_asym_09c_mask.nii.gz
Binary file not shown.
26 changes: 14 additions & 12 deletions data_processing/prepare_input.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
# Get inputs
B0_D_PATH=$1
T1_PATH=$2
T1_MASK_PATH=$3
T1_ATLAS_PATH=$4
T1_ATLAS_2_5_PATH=$5
RESULTS_PATH=$6
T1_ATLAS_PATH=$3
T1_ATLAS_2_5_PATH=$4
RESULTS_PATH=$5

echo -------
echo INPUTS:
Expand Down Expand Up @@ -35,18 +34,21 @@ NORMALIZE_CMD="normalize_T1.sh $T1_PATH $T1_N3_PATH $T1_NORM_PATH"
echo $NORMALIZE_CMD
eval $NORMALIZE_CMD

# Skull strip T1
# If 1mm T1 atlas is stripped (aka input T1 is stripped), copy input T1 over as T1_stripped
# Otherwise, extract brain from input T1
#
# This step exists because epi_reg requires an extracted brain. It uses the name of the atlas to
# track stripped status--not a super elegant solution
echo -------
if [ ! -f $T1_MASK_PATH ]; then
T1_MASK_PATH=$JOB_PATH/T1_mask.nii.gz
if [[ "$T1_ATLAS_PATH" == *"mask"* ]]; then
echo Copying user provided T1 Mask
cp $T1_PATH $T1_MASK_PATH
else
echo Skull stripping T1
T1_MASK_PATH=$JOB_PATH/T1_mask.nii.gz
BET_CMD="bet $T1_PATH $T1_MASK_PATH -R"
echo $BET_CMD
eval $BET_CMD
else
echo Copying user provided T1 Mask
cp $T1_MASK_PATH $JOB_PATH/T1_mask.nii.gz
T1_MASK_PATH=$JOB_PATH/T1_mask.nii.gz
fi

# epi_reg distorted b0 to T1; wont be perfect since B0 is distorted
Expand All @@ -66,7 +68,7 @@ C3D_CMD="c3d_affine_tool -ref $T1_PATH -src $B0_D_PATH $EPI_REG_D_MAT_PATH -fsl2
echo $C3D_CMD
eval $C3D_CMD

# ANTs register T1 to atlas
# ANTs register T1 to atlas (both must either be full T1s or stripped T1s)
echo -------
echo ANTS syn registration
ANTS_OUT=$JOB_PATH/ANTS
Expand Down
10 changes: 7 additions & 3 deletions src/pipeline.sh
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
#!/bin/bash

TOPUP=1
MNI_T1_1_MM_FILE=/extra/atlases/mni_icbm152_t1_tal_nlin_asym_09c.nii.gz

for arg in "$@"
do
case $arg in
-i|--notopup)
TOPUP=0
TOPUP=0
;;
-s|--stripped)
MNI_T1_1_MM_FILE=/extra/atlases/mni_icbm152_t1_tal_nlin_asym_09c_mask.nii.gz
;;
esac
done


# Set path for executable
export PATH=$PATH:/extra

Expand All @@ -31,7 +35,7 @@ export PATH=$PATH:$ANTSPATH:/extra/ANTS/ANTs/Scripts
source /extra/pytorch/bin/activate

# Prepare input
/extra/prepare_input.sh /INPUTS/b0.nii.gz /INPUTS/T1.nii.gz /INPUTS/T1_mask.nii.gz /extra/atlases/mni_icbm152_t1_tal_nlin_asym_09c.nii.gz /extra/atlases/mni_icbm152_t1_tal_nlin_asym_09c_2_5.nii.gz /OUTPUTS
/extra/prepare_input.sh /INPUTS/b0.nii.gz /INPUTS/T1.nii.gz $MNI_T1_1_MM_FILE /extra/atlases/mni_icbm152_t1_tal_nlin_asym_09c_2_5.nii.gz /OUTPUTS

# Run inference
NUM_FOLDS=5
Expand Down

0 comments on commit 860a7eb

Please sign in to comment.