Skip to content

Commit

Permalink
Merge pull request scilus#128 from Manonedde/preproc_dwi
Browse files Browse the repository at this point in the history
[Subworkflow][WIP] Preprocessing DWI
  • Loading branch information
AlexVCaron authored Apr 29, 2024
2 parents 9a8a46e + 8863c9b commit 7c2b3df
Show file tree
Hide file tree
Showing 11 changed files with 640 additions and 17 deletions.
7 changes: 3 additions & 4 deletions modules/nf-scil/betcrop/fslbetcrop/main.nf
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,9 @@ process BETCROP_FSLBETCROP {
scil_extract_b0.py $image $bval $bvec ${prefix}__b0.nii.gz --mean \
$b0_thr --force_b0_threshold
bet ${prefix}__b0.nii.gz ${prefix}__b0_bet.nii.gz -m -R $bet_f
scil_image_math.py convert ${prefix}__b0_bet_mask.nii.gz ${prefix}__image_bet_mask.nii.gz --data_type uint8 -f
mrcalc $image ${prefix}__b0_bet_mask.nii.gz -mult ${prefix}__image_bet.nii.gz -quiet -nthreads 1
bet ${prefix}__b0.nii.gz ${prefix}__image_bet.nii.gz -m -R $bet_f
scil_image_math.py convert ${prefix}__image_bet_mask.nii.gz ${prefix}__image_bet_mask.nii.gz --data_type uint8 -f
mrcalc $image ${prefix}__image_bet_mask.nii.gz -mult ${prefix}__image_bet.nii.gz -quiet -nthreads 1 -force
else
bet $image ${prefix}__image_bet.nii.gz -m -R $bet_f
scil_image_math.py convert ${prefix}__image_bet_mask.nii.gz ${prefix}__image_bet_mask.nii.gz --data_type uint8 -f
Expand Down
4 changes: 2 additions & 2 deletions modules/nf-scil/preproc/topup/main.nf
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ process PREPROC_TOPUP {
scil_volume_math.py concatenate $b0 $b0 ${prefix}__concatenated_b0.nii.gz
scil_volume_math.py mean ${prefix}__concatenated_b0.nii.gz ${prefix}__b0_mean.nii.gz
else
scil_dwi_extract_b0.py $dwi $bval $bvec ${prefix}__b0_mean.nii.gz --mean ----b0_threshold $b0_thr_extract_b0 --skip_b0_check
scil_dwi_extract_b0.py $dwi $bval $bvec ${prefix}__b0_mean.nii.gz --mean --b0_threshold $b0_thr_extract_b0 --skip_b0_check
fi
if [[ -f "$rev_b0" ]];
then
scil_volume_math.py concatenate $rev_b0 $rev_b0 ${prefix}__concatenated_rev_b0.nii.gz
scil_volume_math.py mean ${prefix}__concatenated_rev_b0.nii.gz ${prefix}__rev_b0_mean.nii.gz
else
scil_dwi_extract_b0.py $rev_dwi $rev_bval $rev_bvec ${prefix}__rev_b0_mean.nii.gz --mean ----b0_threshold $b0_thr_extract_b0 --skip_b0_check
scil_dwi_extract_b0.py $rev_dwi $rev_bval $rev_bvec ${prefix}__rev_b0_mean.nii.gz --mean --b0_threshold $b0_thr_extract_b0 --skip_b0_check
fi
antsRegistrationSyNQuick.sh -d 3 -f ${prefix}__b0_mean.nii.gz -m ${prefix}__rev_b0_mean.nii.gz -o output -t r -e 1
Expand Down
125 changes: 125 additions & 0 deletions subworkflows/nf-scil/preproc_dwi/main.nf
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
include { DENOISING_MPPCA as DENOISE_DWI } from '../../../modules/nf-scil/denoising/mppca/main'
include { DENOISING_MPPCA as DENOISE_REVDWI } from '../../../modules/nf-scil/denoising/mppca/main'
include { BETCROP_FSLBETCROP } from '../../../modules/nf-scil/betcrop/fslbetcrop/main'
include { BETCROP_CROPVOLUME } from '../../../modules/nf-scil/betcrop/cropvolume/main'
include { PREPROC_N4 as N4_DWI } from '../../../modules/nf-scil/preproc/n4/main'
include { PREPROC_NORMALIZE as NORMALIZE_DWI } from '../../../modules/nf-scil/preproc/normalize/main'
include { IMAGE_RESAMPLE as RESAMPLE_DWI } from '../../../modules/nf-scil/image/resample/main'
include { IMAGE_RESAMPLE as RESAMPLE_MASK } from '../../../modules/nf-scil/image/resample/main'
include { UTILS_EXTRACTB0 as EXTRACTB0_RESAMPLE } from '../../../modules/nf-scil/utils/extractb0/main'
include { UTILS_EXTRACTB0 as EXTRACTB0_TOPUP } from '../../../modules/nf-scil/utils/extractb0/main'
include { TOPUP_EDDY } from '../topup_eddy/main'


workflow PREPROC_DWI {

take:
ch_dwi // channel: [ val(meta), [ dwi, bval, bvec ] ]
ch_rev_dwi // channel: [ val(meta), [ rev_dwi, bval, bvec ] ], optional
ch_b0 // Channel: [ val(meta), [ b0 ] ], optional
ch_rev_b0 // channel: [ val(meta), [ reverse b0 ] ], optional
ch_config_topup // channel: [ 'config_topup' ], optional

main:

ch_versions = Channel.empty()

ch_denoise_dwi = ch_dwi
.multiMap { meta, dwi, bval, bvec ->
dwi: [ meta, dwi ]
bvs_files: [ meta, bval, bvec ]
}

// ** Denoised DWI ** //
DENOISE_DWI ( ch_denoise_dwi.dwi )
ch_versions = ch_versions.mix(DENOISE_DWI.out.versions.first())

if ( ch_rev_dwi )
{
ch_denoise_rev_dwi = ch_rev_dwi
.multiMap { meta, dwi, bval, bvec ->
rev_dwi: [ [id: "${meta.id}_rev", cache: meta], dwi ]
rev_bvs_files: [ meta, bval, bvec ]
}
// ** Denoised reverse DWI ** //
DENOISE_REVDWI ( ch_denoise_rev_dwi.rev_dwi )
ch_versions = ch_versions.mix(DENOISE_REVDWI.out.versions.first())

ch_topup_eddy_rev_dwi = DENOISE_REVDWI.out.image
.map{ meta, dwi -> [ meta.cache, dwi ] }
.join(ch_denoise_rev_dwi.rev_bvs_files)
}
else
{
ch_topup_eddy_rev_dwi = [] // or Channel.empty()
}

// ** Eddy Topup ** //
ch_topup_eddy_dwi = DENOISE_DWI.out.image.join(ch_denoise_dwi.bvs_files)

if ( ! ch_b0 ) {
EXTRACTB0_TOPUP { ch_topup_eddy_dwi }
ch_versions = ch_versions.mix(EXTRACTB0_TOPUP.out.versions.first())
ch_b0 = EXTRACTB0_TOPUP.out.b0
}

TOPUP_EDDY ( ch_topup_eddy_dwi, ch_b0, ch_topup_eddy_rev_dwi, ch_rev_b0, ch_config_topup )
ch_versions = ch_versions.mix(TOPUP_EDDY.out.versions.first())

// ** Bet-crop DWI ** //
ch_betcrop_dwi = TOPUP_EDDY.out.dwi
.join(TOPUP_EDDY.out.bval)
.join(TOPUP_EDDY.out.bvec)
BETCROP_FSLBETCROP ( ch_betcrop_dwi )
ch_versions = ch_versions.mix(BETCROP_FSLBETCROP.out.versions.first())

// ** Crop b0 ** //
ch_crop_b0 = TOPUP_EDDY.out.b0
.join(BETCROP_FSLBETCROP.out.bbox)
BETCROP_CROPVOLUME ( ch_crop_b0 )
ch_versions = ch_versions.mix(BETCROP_CROPVOLUME.out.versions.first())

// ** N4 DWI ** //
ch_N4 = BETCROP_FSLBETCROP.out.image
.join(BETCROP_CROPVOLUME.out.image)
.join(BETCROP_FSLBETCROP.out.mask)
N4_DWI ( ch_N4 )
ch_versions = ch_versions.mix(N4_DWI.out.versions.first())

// ** Normalize DWI ** //
ch_normalize = N4_DWI.out.image
.join(BETCROP_FSLBETCROP.out.mask)
.join(TOPUP_EDDY.out.bval)
.join(TOPUP_EDDY.out.bvec)
NORMALIZE_DWI ( ch_normalize )
ch_versions = ch_versions.mix(NORMALIZE_DWI.out.versions.first())

// ** Resample DWI ** //
ch_resample_dwi = NORMALIZE_DWI.out.dwi.map{ it + [[]] }
RESAMPLE_DWI ( ch_resample_dwi )
ch_versions = ch_versions.mix(RESAMPLE_DWI.out.versions.first())

// ** Extract b0 ** //
ch_dwi_extract_b0 = RESAMPLE_DWI.out.image
.join(TOPUP_EDDY.out.bval)
.join(TOPUP_EDDY.out.bvec)

EXTRACTB0_RESAMPLE { ch_dwi_extract_b0 }
ch_versions = ch_versions.mix(EXTRACTB0_RESAMPLE.out.versions.first())

// ** Resample mask ** //
ch_resample_mask = BETCROP_FSLBETCROP.out.mask.map{ it + [[]] }
RESAMPLE_MASK ( ch_resample_mask )
ch_versions = ch_versions.mix(RESAMPLE_MASK.out.versions.first())

emit:
dwi_resample = RESAMPLE_DWI.out.image // channel: [ val(meta), [ dwi_resample ] ]
bval = TOPUP_EDDY.out.bval // channel: [ val(meta), [ bval_corrected ] ]
bvec = TOPUP_EDDY.out.bvec // channel: [ val(meta), [ bvec_corrected ] ]
b0 = EXTRACTB0_RESAMPLE.out.b0 // channel: [ val(meta), [ b0 ] ]
b0_mask = RESAMPLE_MASK.out.image // channel: [ val(meta), [ b0_mask ] ]
dwi_bounding_box = BETCROP_FSLBETCROP.out.bbox // channel: [ val(meta), [ dwi_bounding_box ] ]
dwi_topup_eddy = TOPUP_EDDY.out.dwi // channel: [ val(meta), [ dwi_topup_eddy ] ]
dwi_n4 = N4_DWI.out.image // channel: [ val(meta), [ dwi_n4 ] ]
versions = ch_versions // channel: [ versions.yml ]
}
143 changes: 143 additions & 0 deletions subworkflows/nf-scil/preproc_dwi/meta.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
name: "preproc_dwi"
description: |
Subworkflow performing pre-processing of DWI image from from brain extraction to motion correction and resampling.
It requires at least one input channels including a DWI image with corresponding b-value (bval) and b-vector (bvec) files (ch_dwi).
The next 3 channels are optional and includes a reversed DWI image with correspond bval and bvec files (ch_rev_dwi),
a reversed phase encoded b = 0 image (ch_rev_b0) and a channel for Topup configuration file.
1) Required channel include the following main steps:
Brain Extraction, Denoising, Eddy, N4 Bias Correction, Normalization, and Resampling.
2) Required and optional channels include the following main steps:
Brain Extraction, Denoising, Topup+Eddy, N4 Bias Correction, Normalization and Resampling.
The resulting file from this subworkflows is a DWI that has been denoised, corrected for susceptibility,
eddy currents and motion, normalized, cropped and resampled.
The next step would be to extract diffusion profiles for the corrected DWI, using a module implementing either
DTI (see RECONST_DTI), fODF (see RECONST_FODF) or MAP-MRI (NotImplementedYet).
--------- Steps --------------------
Brain Extraction (bet, FSL).
Extract brain mask from the b0 image and applied to the whole DWI.
This brain extraction is required to remove the skull and prepare the DWI to the T1 Registration.
Denoising (dwidenoise, MPPCA method, MRtrix3).
Used to remove the noise induced by the MRI acquisition,
enhance the signal to noise ratio and improve the image quality and following metrics.
The denoising is performed in the original spatial resolution and uses the MP-PCA method.
Topup (FSL - optional).
Topup uses the b=0 and reversed phase encoded b=0 images to extract the deformation field and
corrects the brain deformation induced by the magnetic field susceptibility artefacts.
Eddy (FSL).
Eddy corrects eddy-currents, motion artefacts and performs slice-wise outlier detection and correction.
When Topup is run, the eddy command is performed using the topup output.
N4 Bias Correction (N4BiasFieldCorrection, ANTs).
The N4 Bias Correction normalizes the image intensities and reduces this intensity bias
(the center of the brain is less intense than its outer boundary due to multi-channel
head coils). N4 correction is performed on the b=0 and applied to the whole DWI.
Normalize (dwinormalise, MRtrix3).
The DWI is normalized to have a mean value in the WM of approximately 1000.
This task permits analyzing datasets from different MRI scanners with the same acquisition scheme.
Resample (DIPY).
The DWI is resampled to 1 mm isotropic spatial resolution, which is usually the spatial
resolution of the T1. This spatial resolution is modifiable in the configuration file.
See Tractoflow for more details, https://www.sciencedirect.com/science/article/pii/S105381192030375X?via%3Dihub
keywords:
- Brain extraction
- Crop
- Denoising
- Topup-Eddy correction
- Normalization
- Resampling
components:
- betcrop/cropvolume
- betcrop/fslbetcrop
- denoising/mppca
- image/resample
- preproc/n4
- preproc/normalize
- utils/extractb0
- topup_eddy
input:
- ch_dwi:
type: file
description: |
The input channel containing the DWI file, B-values and B-vectors in FSL format files
Structure: [ val(meta), path(dwi), path(bval), path(bvec) ]
pattern: "*.{nii,nii.gz|bval|bvec}"
- ch_rev_dwi:
type: file
description: |
The input channel containing the reverse DWI file, reverse B-values and reverse B-vectors in FSL format files
Structure: [ val(meta), path(rev_dwi), path(bval), path(bvec) ]
pattern: "*.{nii,nii.gz|bval|bvec}"
- ch_b0:
type: file
description: |
The input channel containing the b0 file. This input is optional.
Structure: [ val(meta), path(rev_b0) ]
pattern: "*.{nii,nii.gz}"
- ch_rev_b0:
type: file
description: |
The input channel containing the reverse b0 file. This input is optional.
Structure: [ val(meta), path(rev_b0) ]
pattern: "*.{nii,nii.gz}"
- ch_config_topup:
type: file
description: |
The input channel containing the config file for Topup. This input is optional. See https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup/TopupUsersGuide#Configuration_files
Structure: [ path(config_file) ]
pattern: "*.{cnf}"
output:
- dwi_resample:
type: file
description: |
Channel containing DWI denoised, corrected for susceptibility and/or eddy currents and motion, normalized, cropped and resampled.
Structure: [ val(meta), path(dwi) ]
pattern: "*_resampled.{nii,nii.gz}"
- bval:
type: file
description: |
Channel containing Eddy-corrected B-values in FSL format
Structure: [ val(meta), path(bval) ]
pattern: "*__bval_eddy"
- bvec:
type: file
description: |
Channel containing Eddy-corrected B-vectors in FSL format
Structure: [ val(meta), path(bvec) ]
pattern: "*__dwi_eddy_corrected.bvec"
- b0:
type: file
description: |
Channel containing b0 corrected file.
Structure: [ val(meta), path(b0) ]
pattern: "*__b0_bet.nii.gz"
- b0_mask:
type: file
description: |
Channel containing b0 corrected binary mask file.
Structure: [ val(meta), path(b0_mask) ]
pattern: "*__b0_bet_mask.nii.gz"
- dwi_bounding_box:
type: file
description: |
Channel containing the bounding box defining the limits of the crop.
Structure: [ val(meta), path(bounding_box) ]
pattern: "*.{pkl}"
- dwi_topup_eddy:
type: file
description: |
Channel containing DWI output after denoised, correction for susceptibility and/or eddy currents and motion (Topup-Eddy).
Structure: [ val(meta), path(dwi) ]
pattern: "*_corrected.{nii,nii.gz}"
- dwi_n4:
type: file
description: |
Channel containing DWI after denoised, corrected for susceptibility and/or eddy currents and motion, cropped and normalized (N4).
Structure: [ val(meta), path(dwi) ]
pattern: "*_normalized.{nii,nii.gz}"
- versions:
type: file
description: |
File containing software versions
Structure: [ path(versions.yml) ]
pattern: "versions.yml"
authors:
- "@medde"
Loading

0 comments on commit 7c2b3df

Please sign in to comment.