Skip to content

Commit

Permalink
[FIX] use b0 ref from eddy-processed data (#739)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattcieslak authored May 1, 2024
1 parent 4a920f5 commit 255acbf
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 58 deletions.
71 changes: 46 additions & 25 deletions qsiprep/workflows/dwi/fsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
GatherEddyInputs,
boilerplate_from_eddy_config,
)
from ...interfaces.gradients import ExtractB0s
from ...interfaces.images import ConformDwi, IntraModalMerge, SplitDWIsFSL
from ...interfaces.nilearn import EnhanceB0
from ...interfaces.reports import TopupSummary
Expand Down Expand Up @@ -135,8 +136,6 @@ def init_fsl_hmc_wf(
outputnode = pe.Node(
niu.IdentityInterface(
fields=[
"b0_template",
"b0_template_mask",
"pre_sdc_template",
"bval_files",
"hmc_optimization_data",
Expand All @@ -162,6 +161,9 @@ def init_fsl_hmc_wf(
"down_fa_image",
"down_fa_corrected_image",
"t2w_image",
# Not from SDC, but from the eddy-resampled data
"b0_template",
"b0_template_mask",
]
),
name="outputnode",
Expand Down Expand Up @@ -205,10 +207,15 @@ def init_fsl_hmc_wf(
SplitDWIsFSL(b0_threshold=b0_threshold, deoblique_bvecs=True), name="split_eddy_lps"
)

# Convert the b=0 template from pre_eddy_b0_ref to LPS+
b0_ref_to_lps = pe.Node(ConformDwi(orientation="LPS"), name="b0_ref_to_lps")
b0_ref_mask_to_lps = pe.Node(ConformDwi(orientation="LPS"), name="b0_ref_mask_to_lps")
b0_ref_brain_to_lps = pe.Node(ConformDwi(orientation="LPS"), name="b0_ref_brain_to_lps")
extract_b0_series = pe.Node(ExtractB0s(b0_threshold=b0_threshold), name="extract_b0_series")
b0_ref_for_coreg = init_dwi_reference_wf(
register_t1=False,
gen_report=False,
desc="b0_for_coreg",
name="b0_ref_for_coreg",
source_file=source_file,
omp_nthreads=omp_nthreads,
)

workflow.connect([
# These images and gradients should be in LAS+
Expand All @@ -222,13 +229,6 @@ def init_fsl_hmc_wf(
('t1_brain', 'inputnode.t1_brain'),
('t1_seg', 'inputnode.t1_seg'),
('t1_mask', 'inputnode.t1_mask')]),
# Convert distorted ref to LPS+
(pre_eddy_b0_ref_wf, b0_ref_to_lps, [
('outputnode.ref_image', 'dwi_file')]),
(pre_eddy_b0_ref_wf, b0_ref_mask_to_lps, [
('outputnode.dwi_mask', 'dwi_file')]),
(pre_eddy_b0_ref_wf, b0_ref_brain_to_lps, [
('outputnode.ref_image_brain', 'dwi_file')]),
(gather_inputs, eddy, [
('eddy_indices', 'in_index'),
('eddy_acqp', 'in_acqp'),
Expand All @@ -238,7 +238,6 @@ def init_fsl_hmc_wf(
('dwi_file', 'in_file'),
('bval_file', 'in_bval'),
('bvec_file', 'in_bvec')]),
(pre_eddy_b0_ref_wf, eddy, [('outputnode.dwi_mask', 'in_mask')]),
(gather_inputs, outputnode, [
('forward_transforms', 'to_dwi_ref_affines')]),
(gather_inputs, enhance_pre_sdc, [
Expand Down Expand Up @@ -266,8 +265,15 @@ def init_fsl_hmc_wf(
(slice_quality, 'slice_quality'),
(slice_quality, 'hmc_optimization_data')]),
(eddy, spm_motion, [('out_parameter', 'eddy_motion')]),
(b0_ref_mask_to_lps, outputnode, [('dwi_file', 'b0_template_mask')]),
(spm_motion, outputnode, [('spm_motion_file', 'motion_params')])

(spm_motion, outputnode, [('spm_motion_file', 'motion_params')]),
# Create a b=0 reference from Eddy's output
(back_to_lps, extract_b0_series, [
('dwi_file', 'dwi_series'),
('bval_file', 'bval_file')]),
(extract_b0_series, b0_ref_for_coreg, [("b0_average", "inputnode.b0_template")]),
(b0_ref_for_coreg, outputnode, [
('outputnode.dwi_mask', 'b0_template_mask')]),
]) # fmt:skip

# Fieldmap correction to be done in LAS+: TOPUP for rpe series or epi fieldmap
Expand Down Expand Up @@ -305,21 +311,34 @@ def init_fsl_hmc_wf(
topup_to_eddy_reg = pe.Node(
fsl.FLIRT(dof=6, output_type="NIFTI_GZ"), name="topup_to_eddy_reg"
)
transform_mask_to_eddy = pe.Node(
fsl.ApplyXFM(apply_xfm=True, interp="nearestneighbour", output_type="NIFTI_GZ"),
name="transform_mask_to_eddy",
)

workflow.connect([
(gather_inputs, topup, [
('topup_datain', 'encoding_file'),
('topup_imain', 'in_file'),
('topup_config', 'config')]),
(gather_inputs, ds_topupcsv, [('b0_csv', 'in_file')]),
(topup, eddy, [
('out_field', 'field')]),
(gather_inputs, topup_to_eddy_reg, [
('topup_first', 'in_file'),
('eddy_first', 'reference')]),
(gather_inputs, ds_topupcsv, [('b0_csv', 'in_file')]),
(topup_to_eddy_reg, eddy, [('out_matrix_file', 'field_mat')]),

# Use corrected images from TOPUP to make a mask for eddy
(topup, unwarped_mean, [('out_corrected', 'in_files')]),
(unwarped_mean, pre_eddy_b0_ref_wf, [('out_avg', 'inputnode.b0_template')]),

# Ensure that the mask is aligned with eddy's first image
(pre_eddy_b0_ref_wf, transform_mask_to_eddy, [("outputnode.dwi_mask", "in_file")]),
(topup_to_eddy_reg, transform_mask_to_eddy, [("out_matrix_file", "in_matrix_file")]),
(gather_inputs, transform_mask_to_eddy, [("eddy_first", "reference")]),
(transform_mask_to_eddy, eddy, [('out_file', 'in_mask')]),

# Save reports
(gather_inputs, topup_summary, [('topup_report', 'summary')]),
(topup_summary, ds_report_topupsummary, [('out_report', 'in_file')])
Expand All @@ -330,7 +349,7 @@ def init_fsl_hmc_wf(
workflow.connect([
# There will be no SDC warps, they are applied by eddy
(gather_inputs, outputnode, [('forward_warps', 'to_dwi_ref_warps')]),
(b0_ref_to_lps, outputnode, [('dwi_file', 'b0_template')]),
(b0_ref_for_coreg, outputnode, [('outputnode.ref_image', 'b0_template')]),
]) # fmt:skip
else:
# If we're not using TOPUP we need to make a mask for eddy based on the
Expand All @@ -341,7 +360,9 @@ def init_fsl_hmc_wf(
(gather_inputs, distorted_merge, [
('topup_imain', 'in_files')]),
(distorted_merge, pre_eddy_b0_ref_wf, [
('out_avg', 'inputnode.b0_template')])]) # fmt:skip
('out_avg', 'inputnode.b0_template')]),
(pre_eddy_b0_ref_wf, eddy, [("outputnode.dwi_mask", "in_mask")]),
]) # fmt:skip

if fieldmap_type in ("epi", "rpe_series") and "drbuddi" in pepolar_method.lower():
outputnode.inputs.sdc_method = "DRBUDDI"
Expand Down Expand Up @@ -403,10 +424,10 @@ def init_fsl_hmc_wf(

workflow.connect([
# Send to SDC workflow
(b0_ref_to_lps, b0_sdc_wf, [
('dwi_file', 'inputnode.b0_ref')]),
(b0_ref_brain_to_lps, b0_sdc_wf, [('dwi_file', 'inputnode.b0_ref_brain')]),
(b0_ref_mask_to_lps, b0_sdc_wf, [('dwi_file', 'inputnode.b0_mask')]),
(b0_ref_for_coreg, b0_sdc_wf, [
('outputnode.ref_image', 'inputnode.b0_ref'),
('outputnode.ref_image_brain', 'inputnode.b0_ref_brain'),
('outputnode.dwi_mask', 'inputnode.b0_mask')]),
(inputnode, b0_sdc_wf, [
('t1_brain', 'inputnode.t1_brain'),
('t1_2_mni_reverse_transform',
Expand All @@ -421,7 +442,7 @@ def init_fsl_hmc_wf(
if not fieldmap_type:
outputnode.inputs.sdc_method = "None"
workflow.connect([
(b0_ref_to_lps, outputnode, [
('dwi_file', 'b0_template')])
(b0_ref_for_coreg, outputnode, [
('outputnode.ref_image', 'b0_template')])
]) # fmt:skip
return workflow
64 changes: 31 additions & 33 deletions qsiprep/workflows/dwi/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
from pathlib import Path

import nibabel as nb
import pkg_resources as pkgr
from nipype.interfaces import ants
from nipype.interfaces import utility as niu
from nipype.pipeline import engine as pe
from nipype.utils.filemanip import split_filename
Expand Down Expand Up @@ -121,44 +119,44 @@ def init_dwi_reference_wf(
if dwi_file is not None:
inputnode.inputs.b0_template = dwi_file

# b=0 images are too diverse and tricky to reliably mask.
# Instead register the t1w to the b=0 and use that brain mask
if register_t1:
affine_transform = pkgr.resource_filename("qsiprep", "data/affine.json")
register_t1_to_raw = pe.Node(
ants.Registration(from_file=affine_transform),
name="register_t1_to_raw",
n_procs=omp_nthreads,
)
t1_mask_to_b0 = pe.Node(
ants.ApplyTransforms(interpolation="MultiLabel", invert_transform_flags=[True]),
name="t1_mask_to_b0",
n_procs=omp_nthreads,
)
workflow.connect([
(inputnode, register_t1_to_raw, [
('t1_brain', 'fixed_image'),
('t1_mask', 'fixed_image_masks'),
('b0_template', 'moving_image'),
]),
(register_t1_to_raw, t1_mask_to_b0, [('forward_transforms', 'transforms')]),
]) # fmt:skip
else:
# T1w is already aligned
t1_mask_to_b0 = pe.Node(
ants.ApplyTransforms(transforms="identity"), name="t1_mask_to_b0", n_procs=omp_nthreads
)
# Synthstrip is used now
# if register_t1:
# affine_transform = pkgr.resource_filename("qsiprep", "data/affine.json")
# register_t1_to_raw = pe.Node(
# ants.Registration(from_file=affine_transform),
# name="register_t1_to_raw",
# n_procs=omp_nthreads,
# )
# t1_mask_to_b0 = pe.Node(
# ants.ApplyTransforms(interpolation="MultiLabel", invert_transform_flags=[True]),
# name="t1_mask_to_b0",
# n_procs=omp_nthreads,
# )
# workflow.connect([
# (inputnode, register_t1_to_raw, [
# ('t1_brain', 'fixed_image'),
# ('t1_mask', 'fixed_image_masks'),
# ('b0_template', 'moving_image'),
# ]),
# (register_t1_to_raw, t1_mask_to_b0, [('forward_transforms', 'transforms')]),
# ]) # fmt:skip
# else:
# # T1w is already aligned
# t1_mask_to_b0 = pe.Node(
# ants.ApplyTransforms(transforms="identity"), name="t1_mask_to_b0",
# n_procs=omp_nthreads
# )

# Use synthstrip to extract the brain
synthstrip_wf = init_synthstrip_wf(
do_padding=True, omp_nthreads=omp_nthreads, name="synthstrip_wf"
)

workflow.connect([
(inputnode, t1_mask_to_b0, [
('t1_mask', 'input_image'),
('b0_template', 'reference_image'),
]),
# (inputnode, t1_mask_to_b0, [
# ('t1_mask', 'input_image'),
# ('b0_template', 'reference_image'),
# ]),
(inputnode, outputnode, [
('b0_template', 'raw_ref_image'),
('b0_template', 'ref_image'),
Expand Down

0 comments on commit 255acbf

Please sign in to comment.