Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 18 additions & 18 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -109,38 +109,38 @@ Head motion correction model
****************************

Although FSL's ``eddy`` is technically model-free, it is an option for
``--hmc-model`` along with ``3dSHORE`` and ``none``. Choosing ``eddy`` (the
default) runs FSL's ``eddy`` for head motion correction and eddy current
correction. This will work for single-shell and multi-shell sampling schemes.
The ``3dSHORE`` (aka "SHORELine") option works for multi-shell, Cartesian
grid sampling (DSI) and random q-space sampling (CS-DSI).
``--hmc-model`` along with ``3dSHORE``, ``tensor``, ``tortoise`` and ``none``.
Choosing ``eddy`` (the default) runs FSL's ``eddy`` for head motion correction
and eddy current correction. This will work for single-shell and multi-shell
sampling schemes. The ``3dSHORE`` (aka "SHORELine") option works for
multi-shell, Cartesian grid sampling (DSI) and random q-space sampling
(CS-DSI), and ``tensor`` runs the same SHORELine iterations with a tensor
model.

The option ``none`` will register all the b=0 images to one another and the
b>0 images will have the transform from the nearest b=0 image applied. This
is not recommended. Between ``eddy`` and ``3dSHORE``, all sampling schemes
can be motion corrected, though eddy-current correction for non-shelled data
requires the DIFFPREP options described below.
requires the DIFFPREP option described below.

For non-shelled acquisitions such as compressed-sensing DSI (CS-DSI), FSL
``eddy`` cannot be used, and ``3dSHORE`` corrects motion but does not correct
eddy currents. In these cases, TORTOISE DIFFPREP is available via
``--hmc-model``:
``--hmc-model tortoise``.

- ``diffprep_motion`` — rigid head-motion correction only.
- ``diffprep_quadratic`` — rigid motion plus 24-parameter quadratic
eddy-current correction (recommended).
- ``diffprep_cubic`` — rigid motion plus cubic eddy-current correction.
This option runs TORTOISE v4 DIFFPREP, which fits a signal model over
arbitrary q-space and therefore does not require shells. By default it
corrects rigid head motion together with 24-parameter quadratic eddy currents.
Advanced TORTOISE settings can be supplied with ``--diffprep-config``,
including ``"correction_mode"``, which selects between ``"motion"`` (rigid
head motion only), ``"quadratic"`` (the default) and ``"cubic"``.

These options run TORTOISE v4 DIFFPREP, which fits a signal model over
arbitrary q-space and therefore does not require shells. Advanced TORTOISE
settings can be supplied with ``--diffprep-config``.

The ``diffprep_*`` backends also perform susceptibility distortion correction,
The ``tortoise`` backend also performs susceptibility distortion correction,
preferring TORTOISE-native tools:

- **Reverse phase-encoded** data is corrected with DRBUDDI
(``--pepolar-method DRBUDDI``; ``TOPUP`` is not supported with the DIFFPREP
backends). This covers both an ``epi`` fieldmap (a blip-up/blip-down b=0 or EPI
(``--pepolar-method DRBUDDI``; ``TOPUP`` is not supported with the tortoise
backend). This covers both an ``epi`` fieldmap (a blip-up/blip-down b=0 or EPI
in ``fmap/``) and a reverse phase-encoded *DWI series* (``rpe_series``). For a
reverse-PE series, DIFFPREP is run **once per phase-encoding direction** (a
single run models one phase axis for the whole file), then the corrected
Expand Down
23 changes: 11 additions & 12 deletions qsiprep/cli/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,20 +593,17 @@ def _bids_filter(value, parser):
'3dSHORE',
'eddy',
'tensor',
'diffprep_motion',
'diffprep_quadratic',
'diffprep_cubic',
'tortoise',
],
help='model used to generate target images for hmc. If "none" the '
'non-b0 images will be warped using the same transform as their '
'nearest b0 image. If "3dSHORE", SHORELine will be used. if "tensor", '
'SHORELine iterations with a tensor model will be used. The '
'"diffprep_*" options run TORTOISE DIFFPREP: "diffprep_motion" '
'corrects rigid head motion only, "diffprep_quadratic" adds '
'24-parameter quadratic eddy-current correction (recommended for '
'non-shelled / CS-DSI schemes), "diffprep_cubic" adds cubic eddy '
'correction. DIFFPREP works on arbitrary q-space (no shells '
'required).',
'SHORELine iterations with a tensor model will be used. '
'"tortoise" uses TORTOISE DIFFPREP; '
'by default this performs rigid head motion correction and '
'24-parameter quadratic eddy-current correction. '
'"tortoise" works on arbitrary q-space (no shells required). '
'For fine-grained control over "tortoise" settings, use --diffprep-config.',
)
g_moco.add_argument(
'--eddy-config',
Expand All @@ -620,8 +617,10 @@ def _bids_filter(value, parser):
'--diffprep-config',
action='store',
help='path to a json file with settings for the call to TORTOISE '
'DIFFPREP (used only when --hmc-model is one of the diffprep_* '
'options). If no json is specified, a default one will be used. The '
'DIFFPREP (used only when --hmc-model is tortoise). This is also where '
'the correction mode is chosen: "correction_mode" may be "motion" '
'(rigid only), "quadratic" (the default) or "cubic". '
'If no json is specified, a default one will be used. The '
'current default can be found here: '
'https://github.com/PennLINC/qsiprep/blob/main/qsiprep/data/diffprep_params.json',
)
Expand Down
1 change: 1 addition & 0 deletions qsiprep/data/diffprep_params.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"correction_mode": "quadratic",
"b0_id": -1,
"is_human_brain": true,
"rot_eddy_center": "isocenter",
Expand Down
33 changes: 22 additions & 11 deletions qsiprep/interfaces/tortoise.py
Original file line number Diff line number Diff line change
Expand Up @@ -803,21 +803,32 @@ def bmtxt_to_fsl(bmtxt_file, working_dir=None):


def generate_diffprep_boilerplate(correction_mode):
"""Methods boilerplate describing the DIFFPREP HMC backend."""
"""Methods boilerplate describing the DIFFPREP HMC backend.

``correction_mode`` comes from ``--diffprep-config`` (default
``quadratic``), so the transform model has to be named from it rather than
assumed.
"""
mode_desc = {
'motion': 'rigid head motion only',
'quadratic': 'rigid head motion together with quadratic eddy currents',
'cubic': 'rigid head motion together with cubic eddy currents',
}[correction_mode]
'motion': ('rigid head motion only', 'rigid-body transform'),
'quadratic': (
'rigid head motion together with quadratic eddy currents',
'24-parameter Okan-quadratic transform',
),
'cubic': (
'rigid head motion together with cubic eddy currents',
'cubic transform',
),
}
corrects, transform = mode_desc[correction_mode]
return (
f'\n\nHead motion correction was performed with DIFFPREP '
f'[@diffprep], part of the TORTOISE [@tortoisev4] software package, '
f'in {correction_mode} mode (correcting {mode_desc}). DIFFPREP fits a '
'\n\nHead motion correction was performed with DIFFPREP '
'[@diffprep], part of the TORTOISE [@tortoisev4] software package, '
f'in {correction_mode} mode (correcting {corrects}). DIFFPREP fits a '
'SHORE/MAPMRI signal model to the data and iteratively registers each '
"volume to a model-predicted target using TORTOISE's 24-parameter "
'Okan-quadratic transform. The corrected volumes and motion-rotated '
'bmatrix were then passed to the rest of the pipeline.\n\n'
f"volume to a model-predicted target using TORTOISE's {transform}. "
'The corrected volumes and motion-rotated bmatrix were then passed to '
'the rest of the pipeline.\n\n'
)


Expand Down
64 changes: 55 additions & 9 deletions qsiprep/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ def test_diffprep(data_dir, output_dir, working_dir):
"""TORTOISE DIFFPREP head-motion/eddy correction on non-shelled data.

This tests the following features:
- The TORTOISE DIFFPREP HMC backend (--hmc-model diffprep_quadratic) on a
- The TORTOISE DIFFPREP HMC backend (--hmc-model tortoise) on a
compressed-sensing DSI (non-shelled) scheme, where FSL eddy cannot run
- The fieldmap-less path: with no fieldmap and no T2w, DIFFPREP performs
head-motion/eddy correction only and does not error out
Expand All @@ -349,7 +349,7 @@ def test_diffprep(data_dir, output_dir, working_dir):
f'-w={work_dir}',
'--sloppy',
'--b1-biascorrect-stage=none',
'--hmc-model=diffprep_quadratic',
'--hmc-model=tortoise',
'--output-resolution=5',
]

Expand Down Expand Up @@ -394,7 +394,7 @@ def test_diffprep_drbuddi(data_dir, output_dir, working_dir):
'--anat-modality=none',
'--denoise-method=none',
'--b1-biascorrect-stage=none',
'--hmc-model=diffprep_quadratic',
'--hmc-model=tortoise',
'--pepolar-method=DRBUDDI',
'--output-resolution=2',
]
Expand Down Expand Up @@ -442,7 +442,7 @@ def test_diffprep_drbuddi_rpe_series(data_dir, output_dir, working_dir):
'--denoise-method=none',
'--b0-motion-corr-to=first',
'--b1-biascorrect-stage=none',
'--hmc-model=diffprep_quadratic',
'--hmc-model=tortoise',
'--pepolar-method=DRBUDDI',
'--output-resolution=5',
]
Expand Down Expand Up @@ -501,7 +501,7 @@ def test_diffprep_csdsi_rpe_series(data_dir, output_dir, working_dir):
'--denoise-method=none',
'--b0-motion-corr-to=first',
'--b1-biascorrect-stage=none',
'--hmc-model=diffprep_quadratic',
'--hmc-model=tortoise',
'--pepolar-method=DRBUDDI',
'--output-resolution=5',
]
Expand Down Expand Up @@ -828,18 +828,41 @@ def test_forrest_gump_patch2self(data_dir, output_dir, working_dir):
_run_and_generate(TEST_NAME, parameters, test_main=False)


@pytest.mark.parametrize('model', ['diffprep_motion', 'diffprep_quadratic', 'diffprep_cubic'])
def test_parser_accepts_diffprep_hmc_models(model, tmp_path):
def test_parser_accepts_tortoise(tmp_path):
"""``tortoise`` is the single --hmc-model value for the DIFFPREP backend."""
from qsiprep.cli.parser import _build_parser

parser = _build_parser()
bids = tmp_path / 'bids'
bids.mkdir()
out = tmp_path / 'out'
opts = parser.parse_args(
[str(bids), str(out), 'participant', '--hmc-model', model, '--output-resolution', '2']
[str(bids), str(out), 'participant', '--hmc-model', 'tortoise', '--output-resolution', '2']
)
assert opts.hmc_model == model
assert opts.hmc_model == 'tortoise'


def test_parser_rejects_removed_diffprep_hmc_models(tmp_path):
"""The per-mode values were replaced by "tortoise" + --diffprep-config."""
from qsiprep.cli.parser import _build_parser

parser = _build_parser()
bids = tmp_path / 'bids'
bids.mkdir()
out = tmp_path / 'out'
for removed in ('diffprep_motion', 'diffprep_quadratic', 'diffprep_cubic'):
with pytest.raises(SystemExit):
parser.parse_args(
[
str(bids),
str(out),
'participant',
'--hmc-model',
removed,
'--output-resolution',
'2',
]
)


def test_validate_diffprep_config_missing(tmp_path):
Expand All @@ -856,6 +879,29 @@ def test_validate_diffprep_config_default_is_valid():
validate_diffprep_config(str(load_data('diffprep_params.json')))


def test_validate_diffprep_config_rejects_bad_correction_mode(tmp_path):
"""A typo must fail at parse time, not deep inside workflow construction."""
import json

from qsiprep.utils.misc import validate_diffprep_config

cfg = tmp_path / 'bad_mode.json'
cfg.write_text(json.dumps({'correction_mode': 'quadratik'}))
with pytest.raises(ValueError, match='correction_mode'):
validate_diffprep_config(str(cfg))


def test_validate_diffprep_config_accepts_each_correction_mode(tmp_path):
import json

from qsiprep.utils.misc import validate_diffprep_config

for mode in ('motion', 'quadratic', 'cubic'):
cfg = tmp_path / f'{mode}.json'
cfg.write_text(json.dumps({'correction_mode': mode}))
validate_diffprep_config(str(cfg))


def _check_arg_specified(argname, arglist):
for arg in arglist:
if arg.startswith(argname):
Expand Down
Loading