-
Notifications
You must be signed in to change notification settings - Fork 533
[ENH/WIP] Add SPM Fieldmap Tool wrapper #1905
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
Conversation
Added: - VDM Calculation pre-substracted phase case (SIEMENS machines) Todo: - Support VDM Application - Support different magnitude and phase file types
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.
My two cents, however I'm not very familiar with SPM. Someone more knowledgeable should look at this
nipype/interfaces/spm/preprocess.py
Outdated
class FieldMap(SPMCommand): | ||
"""Use spm to calculate fieldmap vdm. | ||
|
||
http://www.fil.ion.ucl.ac.uk/spm/doc/manual.pdf#page=19 |
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.
Please fix link, this leads to page 16 (Slice timing correction)
nipype/interfaces/spm/preprocess.py
Outdated
|
||
|
||
class FieldMap(SPMCommand): | ||
"""Use spm to calculate fieldmap vdm. |
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.
what "vdm" means? (should be in the documentation, so please expand the acronym in this docstring)
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.
VDM stands for Voxel Displacement Map. Docstring will be modified in the next commit.
nipype/interfaces/spm/preprocess.py
Outdated
field='subj.defaults.defaultsval.uflags.ws', | ||
desc='weighted smoothing'); | ||
# Brain mask defaults parameters | ||
template = traits.File(copyfile=False, exists=True, |
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.
please use File
from nipype.interfaces.base
, not from traits
.
nipype/interfaces/spm/preprocess.py
Outdated
field='subj.defaults.defaultsval.mflags.reg', | ||
desc='regularization value used in the segmentation'); | ||
# EPI unwarping for quality check | ||
epi = traits.File(copyfile=False, exists=True, mandatory=True, |
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.
File from base
nipype/interfaces/spm/preprocess.py
Outdated
matchvdm = traits.Bool(True, usedefault=True, | ||
field='subj.matchvdm', | ||
desc='match VDM to EPI'); | ||
sessname = traits.String('_run-', usedefault=True, |
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.
please, modify the top from ..base import ...
to add Str
.
Then use it here directly: sessname = Str(...)
nipype/interfaces/spm/preprocess.py
Outdated
writeunwarped = traits.Bool(False, usedefault=True, | ||
field='subj.writeunwarped', | ||
desc='write unwarped EPI'); | ||
anat = traits.File(copyfile=False, exists=True, |
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.
File from base
nipype/interfaces/spm/preprocess.py
Outdated
magnitude = File(mandatory=True, exists=True, copyfile=False, | ||
field='subj.data.presubphasemag.magnitude', | ||
desc='presubstracted magnitude file') | ||
et = traits.List(traits.Float(), minlen=2, maxlen=2, mandatory=True, |
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.
maybe:
et = traits.Tuple(traits.Float, traits.Float, mandatory=True, ...)
?
nipype/interfaces/spm/preprocess.py
Outdated
def _format_arg(self, opt, spec, val): | ||
"""Convert input to appropriate format for spm | ||
""" | ||
if opt == 'phase' or opt == 'magnitude' or opt == 'anat': |
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.
if opt in ['phase', 'magnitude', 'anat']:
nipype/interfaces/spm/preprocess.py
Outdated
""" | ||
if opt == 'phase' or opt == 'magnitude' or opt == 'anat': | ||
return scans_for_fname(filename_to_list(val)) | ||
if opt == 'epi' or opt == 'magnitude': |
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.
if opt in ['epi', 'magnitude']:
However, I don't see any difference with including these two options with the previous condition, finally, it always falls into scans_for_fname(filename_to_list(val))
anyways
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 must have done this for a reason that I can't remember anymore... I will merge these if
statements in the next commit.
Codecov Report
@@ Coverage Diff @@
## master #1905 +/- ##
=========================================
+ Coverage 67.49% 67.5% +<.01%
=========================================
Files 1225 1226 +1
Lines 60313 60375 +62
Branches 8649 8656 +7
=========================================
+ Hits 40709 40756 +47
- Misses 18488 18502 +14
- Partials 1116 1117 +1
Continue to review full report at Codecov.
|
All the requested changes have been included, I'll try to review again but I don't want my potential review to block this PR
nipype/interfaces/spm/preprocess.py
Outdated
>>> from nipype.interfaces.spm import FieldMap | ||
>>> fm = FieldMap() | ||
>>> fm.inputs.phase = 'phasediff.nii' | ||
>>> fm.inputs.magnitude = 'magnitude1.nii' |
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.
phasediff.nii
and magnitude1.nii
don't exist in nipype/testing/data
. Either add them or use existing files (such as phase.nii
and magnitude.nii
).
as suggested by @effigies
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.
A few comments. One to get your doctest fixed, and a number because I felt that your inputspec might benefit from clearer names.
I marked a few with specific suggestions, but in general you may want to consider using more verbose names, rather than copying the field names.
nipype/interfaces/spm/preprocess.py
Outdated
>>> fm = FieldMap() | ||
>>> fm.inputs.phase = 'phase.nii' | ||
>>> fm.inputs.magnitude = 'magnitude.nii' | ||
>>> fm.inputs.et = [5.19, 7.65] |
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.
This should be a tuple, not a list.
nipype/interfaces/spm/preprocess.py
Outdated
outputs['vdm'] = [] | ||
for phase in filename_to_list(self.inputs.phase): | ||
outputs['vdm'].append(fname_presuffix(phase, prefix='vdm5_sc')) | ||
outputs['vdm'] = list_to_filename(outputs['vdm']) |
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'm a bit confused by this. self.inputs.phase
is a single file, so this could just be:
if self.inputs.jobtype == 'calculatevdm':
outputs['vdm'] = fname_presuffix(self.inputs.phase, prefix='vdm5_sc')
Or am I missing something?
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.
You're not, I just adapted another more complicated wrapper's piece of code without thinking about making it clearer. Thanks for noticing!
nipype/interfaces/spm/preprocess.py
Outdated
""" | ||
einputs = super(FieldMap, self)._parse_inputs() | ||
jobtype = self.inputs.jobtype | ||
return [{'%s' % (jobtype): einputs[0]}] |
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.
What's the advantage of this over the following?
return [{self.inputs.jobtype: einputs[0]}]
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 like better your style, but I originally used the syntax of another wrapper (typically Coregister
).
nipype/interfaces/spm/preprocess.py
Outdated
magnitude = File(mandatory=True, exists=True, copyfile=False, | ||
field='subj.data.presubphasemag.magnitude', | ||
desc='presubstracted magnitude file') | ||
et = traits.Tuple(traits.Float, traits.Float, mandatory=True, |
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 see this is matches the final part of the field name, so it may be that SPM users will find this intuitive, but you may want to consider echo_times
or similar for clarity.
nipype/interfaces/spm/preprocess.py
Outdated
class FieldMapInputSpec(SPMCommandInputSpec): | ||
jobtype = traits.Enum('calculatevdm', 'applyvdm', usedefault=True, | ||
desc='one of: calculatevdm, applyvdm') | ||
phase = File(mandatory=True, exists=True, copyfile=False, |
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.
We often use the suffix _file
to indicate inputs that should be files. So consider making this phase_file
. Similarly below with magnitude_file
.
Not a big deal, if you think that would be disorienting to SPM users.
nipype/interfaces/spm/preprocess.py
Outdated
epifm = traits.Bool(False, usedefault=True, | ||
field='subj.defaults.defaultsval.epifm', | ||
desc='epi-based field map'); | ||
ajm = traits.Bool(False, usedefault=True, |
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.
Here, again, I would consider jacobian_modulation
or similar.
nipype/interfaces/spm/preprocess.py
Outdated
template = File(copyfile=False, exists=True, | ||
field='subj.defaults.defaultsval.mflags.template', | ||
desc='template image for brain masking'); | ||
fwhm = traits.Range(low=0, value=5, usedefault=True, |
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.
You've got fwhm
twice. Perhaps this one should be mask_fwhm
, and the other unwarp_fwhm
?
Also, please merge master again, to update the tests. |
nipype/interfaces/spm/preprocess.py
Outdated
>>> fm.inputs.echo_times = (5.19, 7.65) | ||
>>> fm.inputs.blip_direction = 1 | ||
>>> fm.inputs.total_readout_time = 15.6 | ||
>>> fm.inputs.epi_file = 'bold.nii' |
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.
Sorry for another round, but, this needs to be 'epi.nii'
, or some other file in nipype/testing/data
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.
No worries! It's on me! Btw, I went through the CONTRIBUTING.md
file, but did not see any paragraph explaining how to run tests before submitting a PR. Maybe it would have save you some reviews of my code and others'.
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.
Ah, that should probably be more clear. I just run
PYTHONPATH="." py.test --doctest-modules nipype
That tests all of nipype. You can specify a specific file you want to test, as well:
PYTHONPATH="." py.test --doctest-modules nipype/interfaces/spm/preprocess.py
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.
LGTM.
I am working on implementing the apply vdm feature. |
I will soon make a pull request so the apply VDM also work on multiple frames (e.g. fMRI images). This is a tiny change of my previous pull request and I am actually sorry it was not already included in the previous one. |
Added:
Todo: