-
Notifications
You must be signed in to change notification settings - Fork 24
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
[WIP] Eddy Motion Correction (EMC) #62
Changes from 4 commits
5f51f9d
e4e8f82
47fe0e9
45a16c2
fe6cfe9
f440a36
a461d51
2b34811
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,3 +8,4 @@ include dmriprep/_version.py | |
|
||
# data | ||
include dmriprep/config/reports-spec.yml | ||
recursive-include * *.json |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"dimension": 3, | ||
"float": true, | ||
"winsorize_lower_quantile": 0.002, | ||
"winsorize_upper_quantile": 0.998, | ||
"collapse_output_transforms": true, | ||
"write_composite_transform": false, | ||
"use_histogram_matching": [ false, false ], | ||
"use_estimate_learning_rate_once": [ true, true ], | ||
"transforms": [ "Rigid", "Affine" ], | ||
"number_of_iterations": [ [ 100, 100 ], [ 100 ] ], | ||
"output_warped_image": true, | ||
"transform_parameters": [ [ 0.2 ], [ 0.15 ] ], | ||
"convergence_threshold": [ 1e-06, 1e-06 ], | ||
"convergence_window_size": [ 20, 20 ], | ||
"metric": [ "Mattes", "Mattes" ], | ||
"sampling_percentage": [ 0.15, 0.2 ], | ||
"sampling_strategy": [ "Random", "Random" ], | ||
"smoothing_sigmas": [ [ 8.0, 2.0 ], [ 2.0 ] ], | ||
"sigma_units": [ "mm", "mm" ], | ||
"metric_weight": [ 1.0, 1.0 ], | ||
"shrink_factors": [ [ 2, 1 ], [ 1 ] ], | ||
"radius_or_number_of_bins": [ 48, 48 ], | ||
"interpolation": "BSpline" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"dimension": 3, | ||
"float": true, | ||
"winsorize_lower_quantile": 0.002, | ||
"winsorize_upper_quantile": 0.998, | ||
"collapse_output_transforms": true, | ||
"write_composite_transform": false, | ||
"use_histogram_matching": [ false ], | ||
"use_estimate_learning_rate_once": [ true ], | ||
"transforms": [ "Rigid" ], | ||
"number_of_iterations": [ [ 100, 100 ] ], | ||
"output_warped_image": true, | ||
"transform_parameters": [ [ 0.2 ] ], | ||
"convergence_threshold": [ 1e-06 ], | ||
"convergence_window_size": [ 20 ], | ||
"metric": [ "Mattes" ], | ||
"sampling_percentage": [ 0.15 ], | ||
"sampling_strategy": [ "Random" ], | ||
"smoothing_sigmas": [ [ 8.0, 2.0 ] ], | ||
"sigma_units": [ "mm"], | ||
"metric_weight": [ 1.0 ], | ||
"shrink_factors": [ [ 2, 1 ] ], | ||
"radius_or_number_of_bins": [ 48 ], | ||
"interpolation": "BSpline" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"dimension": 3, | ||
"float": true, | ||
"winsorize_lower_quantile": 0.002, | ||
"winsorize_upper_quantile": 0.998, | ||
"collapse_output_transforms": true, | ||
"write_composite_transform": false, | ||
"use_histogram_matching": [ false, false ], | ||
"use_estimate_learning_rate_once": [ true, true ], | ||
"transforms": [ "Rigid", "Affine" ], | ||
"number_of_iterations": [ [ 1000, 1000 ], [ 1000 ] ], | ||
"output_warped_image": true, | ||
"transform_parameters": [ [ 0.2 ], [ 0.15 ] ], | ||
"convergence_threshold": [ 1e-08, 1e-08 ], | ||
"convergence_window_size": [ 20, 20 ], | ||
"metric": [ "Mattes", "Mattes" ], | ||
"sampling_percentage": [ 0.15, 0.2 ], | ||
"sampling_strategy": [ "Random", "Random" ], | ||
"smoothing_sigmas": [ [ 8.0, 2.0 ], [ 2.0 ] ], | ||
"sigma_units": [ "mm", "mm" ], | ||
"metric_weight": [ 1.0, 1.0 ], | ||
"shrink_factors": [ [ 2, 1 ], [ 1 ] ], | ||
"radius_or_number_of_bins": [ 48, 48 ], | ||
"interpolation": "BSpline" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"dimension": 3, | ||
"float": true, | ||
"winsorize_lower_quantile": 0.002, | ||
"winsorize_upper_quantile": 0.998, | ||
"collapse_output_transforms": true, | ||
"write_composite_transform": false, | ||
"use_histogram_matching": [ false ], | ||
"use_estimate_learning_rate_once": [ true ], | ||
"transforms": [ "Rigid" ], | ||
"number_of_iterations": [ [ 1000, 1000 ] ], | ||
"output_warped_image": true, | ||
"transform_parameters": [ [ 0.2 ], [ 0.15 ] ], | ||
"convergence_threshold": [ 1e-08, 1e-08 ], | ||
"convergence_window_size": [ 20, 20 ], | ||
"metric": [ "Mattes" ], | ||
"sampling_percentage": [ 0.15 ], | ||
"sampling_strategy": [ "Random" ], | ||
"smoothing_sigmas": [ [ 8.0, 2.0 ] ], | ||
"sigma_units": [ "mm" ], | ||
"metric_weight": [ 1.0 ], | ||
"shrink_factors": [ [ 2, 1 ] ], | ||
"radius_or_number_of_bins": [ 48 ], | ||
"interpolation": "BSpline" | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,139 @@ | ||||||||||
import os | ||||||||||
import re | ||||||||||
from pathlib import Path | ||||||||||
from nipype.interfaces.base import ( | ||||||||||
traits, TraitedSpec, BaseInterfaceInputSpec, SimpleInterface, File, isdefined, | ||||||||||
InputMultiObject, OutputMultiPath, OutputMultiObject, | ||||||||||
) | ||||||||||
from ..utils.bids import splitext as _splitext, _copy_any | ||||||||||
|
||||||||||
__all__ = ['BIDS_NAME'] | ||||||||||
|
||||||||||
BIDS_NAME = re.compile( | ||||||||||
r'^(.*\/)?(?P<subject_id>sub-[a-zA-Z0-9]+)(_(?P<session_id>ses-[a-zA-Z0-9]+))?' | ||||||||||
'(_(?P<task_id>task-[a-zA-Z0-9]+))?(_(?P<acq_id>acq-[a-zA-Z0-9]+))?' | ||||||||||
'(_(?P<rec_id>rec-[a-zA-Z0-9]+))?(_(?P<run_id>run-[a-zA-Z0-9]+))?') | ||||||||||
|
||||||||||
|
||||||||||
class DerivativesDataSinkInputSpec(BaseInterfaceInputSpec): | ||||||||||
base_directory = traits.Directory( | ||||||||||
desc='Path to the base directory for storing data.') | ||||||||||
in_file = InputMultiObject(File(exists=True), mandatory=True, | ||||||||||
desc='the object to be saved') | ||||||||||
source_file = File(exists=False, mandatory=True, desc='the original file') | ||||||||||
prefix = traits.Str(mandatory=False, desc='prefix for output files') | ||||||||||
space = traits.Str('', usedefault=True, desc='Label for space field') | ||||||||||
desc = traits.Str('', usedefault=True, desc='Label for description field') | ||||||||||
suffix = traits.Str('', usedefault=True, desc='suffix appended to source_file') | ||||||||||
keep_dtype = traits.Bool(False, usedefault=True, desc='keep datatype suffix') | ||||||||||
extra_values = traits.List(traits.Str) | ||||||||||
compress = traits.Bool(desc="force compression (True) or uncompression (False)" | ||||||||||
" of the output file (default: same as input)") | ||||||||||
extension = traits.Str() | ||||||||||
|
||||||||||
|
||||||||||
class DerivativesDataSinkOutputSpec(TraitedSpec): | ||||||||||
out_file = OutputMultiObject(File(exists=True, desc='written file path')) | ||||||||||
compression = OutputMultiPath( | ||||||||||
traits.Bool, desc='whether ``in_file`` was compressed/uncompressed ' | ||||||||||
'or `it was copied directly.') | ||||||||||
|
||||||||||
|
||||||||||
class DerivativesDataSink(SimpleInterface): | ||||||||||
""" | ||||||||||
Saves the `in_file` into a BIDS-Derivatives folder provided | ||||||||||
by `base_directory`, given the input reference `source_file`. | ||||||||||
>>> from pathlib import Path | ||||||||||
>>> import tempfile | ||||||||||
>>> from dmriprep.utils.bids import collect_data | ||||||||||
>>> tmpdir = Path(tempfile.mkdtemp()) | ||||||||||
>>> tmpfile = tmpdir / 'a_temp_file.nii.gz' | ||||||||||
>>> tmpfile.open('w').close() # "touch" the file | ||||||||||
>>> dsink = DerivativesDataSink(base_directory=str(tmpdir)) | ||||||||||
>>> dsink.inputs.in_file = str(tmpfile) | ||||||||||
>>> dsink.inputs.source_file = collect_data('ds114', '01')[0]['t1w'][0] | ||||||||||
>>> dsink.inputs.keep_dtype = True | ||||||||||
>>> dsink.inputs.suffix = 'target-mni' | ||||||||||
>>> res = dsink.run() | ||||||||||
>>> res.outputs.out_file # doctest: +ELLIPSIS | ||||||||||
'.../dmriprep/sub-01/ses-retest/anat/sub-01_ses-retest_target-mni_T1w.nii.gz' | ||||||||||
>>> bids_dir = tmpdir / 'bidsroot' / 'sub-02' / 'ses-noanat' / 'func' | ||||||||||
>>> bids_dir.mkdir(parents=True, exist_ok=True) | ||||||||||
>>> tricky_source = bids_dir / 'sub-02_ses-noanat_task-rest_run-01_bold.nii.gz' | ||||||||||
>>> tricky_source.open('w').close() | ||||||||||
>>> dsink = DerivativesDataSink(base_directory=str(tmpdir)) | ||||||||||
>>> dsink.inputs.in_file = str(tmpfile) | ||||||||||
>>> dsink.inputs.source_file = str(tricky_source) | ||||||||||
>>> dsink.inputs.keep_dtype = True | ||||||||||
>>> dsink.inputs.desc = 'preproc' | ||||||||||
>>> res = dsink.run() | ||||||||||
>>> res.outputs.out_file # doctest: +ELLIPSIS | ||||||||||
'.../dmriprep/sub-02/ses-noanat/func/sub-02_ses-noanat_task-rest_run-01_\ | ||||||||||
desc-preproc_bold.nii.gz' | ||||||||||
""" | ||||||||||
input_spec = DerivativesDataSinkInputSpec | ||||||||||
output_spec = DerivativesDataSinkOutputSpec | ||||||||||
out_path_base = "dmriprep" | ||||||||||
_always_run = True | ||||||||||
|
||||||||||
def __init__(self, out_path_base=None, **inputs): | ||||||||||
super(DerivativesDataSink, self).__init__(**inputs) | ||||||||||
self._results['out_file'] = [] | ||||||||||
if out_path_base: | ||||||||||
self.out_path_base = out_path_base | ||||||||||
|
||||||||||
def _run_interface(self, runtime): | ||||||||||
src_fname, _ = _splitext(self.inputs.source_file) | ||||||||||
src_fname, dtype = src_fname.rsplit('_', 1) | ||||||||||
_, ext = _splitext(self.inputs.in_file[0]) | ||||||||||
if self.inputs.compress is True and not ext.endswith('.gz'): | ||||||||||
ext += '.gz' | ||||||||||
elif self.inputs.compress is False and ext.endswith('.gz'): | ||||||||||
ext = ext[:-3] | ||||||||||
|
||||||||||
m = BIDS_NAME.search(src_fname) | ||||||||||
|
||||||||||
mod = os.path.basename(os.path.dirname(self.inputs.source_file)) | ||||||||||
|
||||||||||
base_directory = runtime.cwd | ||||||||||
if isdefined(self.inputs.base_directory): | ||||||||||
base_directory = str(self.inputs.base_directory) | ||||||||||
|
||||||||||
out_path = '{}/{subject_id}'.format(self.out_path_base, **m.groupdict()) | ||||||||||
if m.groupdict().get('session_id') is not None: | ||||||||||
out_path += '/{session_id}'.format(**m.groupdict()) | ||||||||||
out_path += '/{}'.format(mod) | ||||||||||
|
||||||||||
out_path = os.path.join(base_directory, out_path) | ||||||||||
|
||||||||||
os.makedirs(out_path, exist_ok=True) | ||||||||||
|
||||||||||
if isdefined(self.inputs.prefix): | ||||||||||
base_fname = os.path.join(out_path, self.inputs.prefix) | ||||||||||
else: | ||||||||||
base_fname = os.path.join(out_path, src_fname) | ||||||||||
|
||||||||||
formatstr = '{bname}{space}{desc}{suffix}{dtype}{ext}' | ||||||||||
if len(self.inputs.in_file) > 1 and not isdefined(self.inputs.extra_values): | ||||||||||
formatstr = '{bname}{space}{desc}{suffix}{i:04d}{dtype}{ext}' | ||||||||||
|
||||||||||
space = '_space-{}'.format(self.inputs.space) if self.inputs.space else '' | ||||||||||
desc = '_desc-{}'.format(self.inputs.desc) if self.inputs.desc else '' | ||||||||||
suffix = '_{}'.format(self.inputs.suffix) if self.inputs.suffix else '' | ||||||||||
dtype = '' if not self.inputs.keep_dtype else ('_%s' % dtype) | ||||||||||
|
||||||||||
self._results['compression'] = [] | ||||||||||
for i, fname in enumerate(self.inputs.in_file): | ||||||||||
out_file = formatstr.format( | ||||||||||
bname=base_fname, | ||||||||||
space=space, | ||||||||||
desc=desc, | ||||||||||
suffix=suffix, | ||||||||||
i=i, | ||||||||||
dtype=dtype, | ||||||||||
ext=ext) | ||||||||||
if isdefined(self.inputs.extra_values): | ||||||||||
out_file = out_file.format(extra_value=self.inputs.extra_values[i]) | ||||||||||
self._results['out_file'].append(out_file) | ||||||||||
self._results['compression'].append(_copy_any(fname, out_file)) | ||||||||||
return runtime | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We already have a DerivativesDataSink: dmriprep/dmriprep/interfaces/__init__.py Lines 13 to 16 in 0a609b4
Unless there is some new functionality here, this can go away. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yep! (didn't see that it already lived in init.py initially. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this choice based on speed?
Wouldn't it make more sense some cross-correlation based measure? MI is just going to exacerbate the problems you mentioned about the different content of each direction (pinging @arokem and @mattcieslak).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree @oesteban -- cross-corr seems like it would be preferable here. Curious what @mattcieslak thinks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remember, the fixed (model-estimated) images here will have the same contrast as the moving images so most metrics would be ok here. MI is much faster than CC, so any benefit of CC would be outweighed by its extra computation time. Also, this is only the coarse iteration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, but MI has much flatter gradient - I'm going to make up a number here, but 1 CC iteration is as effective as 10 MI iterations.