Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support complex-valued dwidenoising #679

Merged
merged 28 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
56772f6
Limit DWI data to magnitude and collect phase sep.
tsalo Jan 24, 2024
f1bea5f
Draft steps to do complex dwidenoise.
tsalo Jan 24, 2024
3d6b10d
Feed in dwidenoisecomplex throughout package.
tsalo Jan 24, 2024
3d1ce20
Fix up some interfaces.
tsalo Jan 24, 2024
a6bc0a3
Fix BIDSDataGrabber.
tsalo Jan 25, 2024
970f367
Keep working.
tsalo Jan 25, 2024
88844cf
Add docstrings.
tsalo Jan 25, 2024
a305138
Grab phase within init_merge_and_denoise_wf.
tsalo Jan 25, 2024
ad1974c
Simplify boilerplate.
tsalo Jan 25, 2024
9f06f2a
Remove unused phase_available.
tsalo Jan 25, 2024
35df866
Use layout to get file's metadata.
tsalo Jan 26, 2024
0774bc3
Merge remote-tracking branch 'upstream/master' into phase
tsalo Jan 26, 2024
b182f12
Merge remote-tracking branch 'upstream/master' into phase
tsalo Jan 26, 2024
ddea10e
Merge remote-tracking branch 'upstream/master' into phase
tsalo Jan 28, 2024
2aef726
Fix connection in phase denoising.
tsalo Jan 29, 2024
39c50ff
Generate out_file names in interfaces.
tsalo Jan 29, 2024
310d9f2
Reduce memory usage of PhaseToRad interface.
tsalo Jan 30, 2024
16aeb98
Merge remote-tracking branch 'upstream/master' into phase
tsalo Feb 2, 2024
e09cfb5
Use updated conversion code.
tsalo Feb 7, 2024
ae88d98
Merge remote-tracking branch 'upstream/master' into phase
tsalo Feb 12, 2024
f155c35
Merge remote-tracking branch 'upstream/master' into phase
tsalo Feb 12, 2024
d9c8bdb
Merge remote-tracking branch 'upstream/master' into phase
tsalo Feb 12, 2024
2e04518
Fix imports.
tsalo Feb 12, 2024
3d3588f
Merge remote-tracking branch 'upstream/master' into phase
tsalo Feb 22, 2024
09394f3
Rerun black and isort.
tsalo Feb 22, 2024
30aab41
Merge remote-tracking branch 'upstream/master' into phase
tsalo Feb 28, 2024
ae8acc7
Update docstrings to reflect Nipreps licensing.
tsalo Feb 29, 2024
2e983b5
Strict phase file matching.
tsalo Feb 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions qsiprep/cli/run.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a great idea - it doesn't require a new commandline flag and is easy to understand

Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,14 @@ def get_parser():
action='store',
nargs="+",
default=[],
choices=['fieldmaps'],
help='ignore selected aspects of the input dataset to disable '
'corresponding parts of the workflow (a space delimited list)')
choices=['fieldmaps', 'phase'],
help=(
'ignore selected aspects of the input dataset to disable '
'corresponding parts of the workflow (a space delimited list). '
'Ignoring "phase" will disable complex-valued denoising, '
'when phase DWI data are available.'
),
)
g_conf.add_argument(
'--longitudinal',
action='store_true',
Expand Down
1 change: 1 addition & 0 deletions qsiprep/interfaces/bids.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ class BIDSDataGrabberOutputSpec(TraitedSpec):
t2w = OutputMultiPath(desc='output T2w images')
flair = OutputMultiPath(desc='output FLAIR images')
dwi = OutputMultiPath(desc='output DWI images')
dwi_phase = OutputMultiPath(desc='output DWI images')


class BIDSDataGrabber(SimpleInterface):
Expand Down
36 changes: 36 additions & 0 deletions qsiprep/interfaces/dwi_merge.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Handle merging and spliting of DSI files."""
import os.path as op
import json

import nibabel as nb
import numpy as np
import pandas as pd
from nilearn.image import concat_imgs, load_img, index_img, math_img, iter_img
Expand All @@ -9,6 +11,7 @@
from nipype.utils.filemanip import fname_presuffix
from nipype.interfaces import ants
from nipype import logging

from .fmap import get_distortion_grouping
from ..workflows.dwi.util import _get_concatenated_bids_name
LOGGER = logging.getLogger('nipype.workflow')
Expand Down Expand Up @@ -659,3 +662,36 @@ def create_provenance_dataframe(bids_sources, harmonized_niis, b0_means,
image_df = pd.concat(series_confounds, axis=0, ignore_index=True)
image_df['original_file'] = bids_sources
return image_df


class _PhaseToRadInputSpec(BaseInterfaceInputSpec):
phase_file = File(exists=True, mandatory=True)


class _PhaseToRadOutputSpec(TraitedSpec):
phase_file = File(exists=True)


class PhaseToRad(SimpleInterface):
input_spec = _PhaseToRadInputSpec
output_spec = _PhaseToRadOutputSpec

def _run_interface(self, runtime):
phase_img = nb.load(self.inputs.phase_file)

phase_data = phase_img.get_fdata()
imax = phase_data.max()
imin = phase_data.min()
scaled = (phase_data - imin) / (imax - imin)
rad_data = 2 * np.pi * scaled
out_img = nb.Nifti1Image(rad_data, phase_img.affine, phase_img.header)

self._results['phase_file'] = fname_presuffix(
self.inputs.phase_file,
suffix='_rad.nii.gz',
newpath=runtime.cwd,
use_ext=False,
)
out_img.to_filename(self._results['phase_file'])

return runtime
58 changes: 58 additions & 0 deletions qsiprep/interfaces/mrtrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -1356,3 +1356,61 @@ class TransformHeader(CommandLine):
input_spec = _TransformHeaderInputSpec
output_spec = _TransformHeaderOutputSpec
_cmd = "mrtransform -strides -1,-2,3"


class _PolarToComplexInputSpec(CommandLineInputSpec):
mag_file = traits.File(
exists=True,
mandatory=True,
position=0,
argstr="%s"
)
phase_file = traits.File(
exists=True,
mandatory=True,
position=1,
argstr="%s"
)
out_file = traits.File(
exists=False,
mandatory=True,
position=-1,
argstr="-polar %s"
)


class _PolarToComplexOutputSpec(TraitedSpec):
out_file = File(exists=True)


class PolarToComplex(CommandLine):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you remember where you found the mrcalc commands? Was it on the mrtrix forum?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

input_spec = _PolarToComplexInputSpec
output_spec = _PolarToComplexOutputSpec

_cmd = "mrcalc"


class _ComplexToMagnitudeInputSpec(CommandLineInputSpec):
complex_file = traits.File(
exists=True,
mandatory=True,
position=0,
argstr="%s"
)
out_file = traits.File(
exists=False,
mandatory=True,
position=-1,
argstr="-abs %s"
)


class _ComplexToMagnitudeOutputSpec(TraitedSpec):
out_file = File(exists=True)


class ComplexToMagnitude(CommandLine):
input_spec = _ComplexToMagnitudeInputSpec
output_spec = _ComplexToMagnitudeOutputSpec

_cmd = "mrcalc"
8 changes: 3 additions & 5 deletions qsiprep/utils/bids.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,7 @@ def collect_participants(bids_dir, participant_label=None, strict=False,


def collect_data(bids_dir, participant_label, filters=None, bids_validate=True):
"""
Uses pybids to retrieve the input data for a given participant

"""
"""Use pybids to retrieve the input data for a given participant."""
if isinstance(bids_dir, BIDSLayout):
layout = bids_dir
else:
Expand All @@ -151,7 +148,8 @@ def collect_data(bids_dir, participant_label, filters=None, bids_validate=True):
't2w': {'datatype': 'anat', 'suffix': 'T2w'},
't1w': {'datatype': 'anat', 'suffix': 'T1w'},
'roi': {'datatype': 'anat', 'suffix': 'roi'},
'dwi': {'datatype': 'dwi', 'suffix': 'dwi'}
'dwi': {'datatype': 'dwi', 'part': ['mag', None], 'suffix': 'dwi'},
'dwi_phase': {'datatype': 'dwi', 'part': 'phase', 'suffix': 'dwi'},
}
bids_filters = filters or {}
for acq, entities in bids_filters.items():
Expand Down
1 change: 0 additions & 1 deletion qsiprep/utils/grouping.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ def group_dwi_scans(bids_layout, subject_data, using_fsl=False, combine_scans=Tr
A dict where the keys are the BIDS derivatives name of the output file after
concatenation. The values are lists of dwi files in that group
"""

# Handle the grouping of multiple dwi files within a session
dwi_session_groups = get_session_groups(bids_layout, subject_data, combine_scans)

Expand Down
19 changes: 12 additions & 7 deletions qsiprep/workflows/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,7 @@ def init_qsiprep_wf(
anatomical_contrst : str
Which contrast to use for the anatomical reference
ignore : list
Preprocessing steps to skip (may include "slicetiming",
"fieldmaps")
Preprocessing steps to skip (may include "slicetiming", "fieldmaps", "phase")
low_mem : bool
Write uncompressed .nii files in some cases to reduce memory usage
anat_only : bool
Expand Down Expand Up @@ -369,7 +368,7 @@ def init_single_subject_wf(
name : str
Name of workflow
ignore : list
Preprocessing steps to skip (may include "sbref", "fieldmaps")
Preprocessing steps to skip (may include "sbref", "fieldmaps", "phase")
debug : bool
Do inaccurate but fast normalization
low_mem : bool
Expand Down Expand Up @@ -479,12 +478,17 @@ def init_single_subject_wf(
LOGGER.warning("Building a test workflow")
else:
subject_data, layout = collect_data(
bids_dir,
subject_id,
filters=bids_filters,
bids_validate=False
bids_dir,
subject_id,
filters=bids_filters,
bids_validate=False,
)

if 'phase' in ignore:
subject_data['dwi_phase'] = []

phase_available = bool(subject_data['dwi_phase'])

# Warn about --dwi-only and non-none --anat-modality
if dwi_only and anatomical_contrast != "none":
LOGGER.warn(
Expand Down Expand Up @@ -741,6 +745,7 @@ def init_single_subject_wf(
fmap_bspline=fmap_bspline,
fmap_demean=fmap_demean,
t2w_sdc=bool(subject_data.get('t2w')),
phase_available=phase_available,
sloppy=debug,
source_file=source_file
)
Expand Down
7 changes: 7 additions & 0 deletions qsiprep/workflows/dwi/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def init_dwi_preproc_wf(dwi_only,
low_mem,
sloppy,
source_file,
phase_available,
layout=None):
"""
This workflow controls the dwi preprocessing stages of qsiprep.
Expand Down Expand Up @@ -104,6 +105,7 @@ def init_dwi_preproc_wf(dwi_only,
low_mem=False,
sloppy=True,
source_file='/data/bids/sub-1/dwi/sub-1_dwi.nii.gz',
phase_available=False,
layout=None)

**Parameters**
Expand Down Expand Up @@ -172,6 +174,10 @@ def init_dwi_preproc_wf(dwi_only,
(default is 1)
sloppy : bool
Use low-quality settings for motion correction
phase_available : bool
True if phase data are available for the DWI scan.
If True, and ``denoise_method`` is ``dwidenoise``, then ``dwidenoise``
will be run on the complex-valued data.
source_file : str
The file name template used for derivatives

Expand Down Expand Up @@ -324,6 +330,7 @@ def init_dwi_preproc_wf(dwi_only,
source_file=source_file,
low_mem=low_mem,
denoise_before_combining=denoise_before_combining,
phase_available=phase_available,
omp_nthreads=omp_nthreads)
test_pre_hmc_connect = pe.Node(TestInput(), name='test_pre_hmc_connect')
if hmc_model in ('none', '3dSHORE','tensor'):
Expand Down
Loading