Skip to content

ENH: Add FSL eddy_quad interface #2825

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

Merged
merged 13 commits into from
Jan 22, 2019
Merged

Conversation

richford
Copy link
Contributor

@richford richford commented Dec 13, 2018

Summary

This PR adds an interface to eddy_quad, one of two tools in the EDDY QC framework.
See the EDDY QC documentation for further details. Thanks to @akeshavan for help with my first nipype PR.

List of changes proposed in this PR (pull-request)

  • Add EddyQuadInputSpec class
  • Add EddyQuadOutputSpec class
  • Add EddyQuad class with doctest

Acknowledgment

  • (Mandatory) I acknowledge that this contribution will be available under the Apache 2 license.

@richford
Copy link
Contributor Author

Hmm, the travis PR checks failed, but only for Python 3.4 because xdist could not be loaded. Not sure why this is happening. I don't think that anything I changed in the PR is causing this. Any help would be appreciated.

Copy link
Member

@effigies effigies left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution! Overall, this looks great.

I have a few suggestions/comments/questions.

And don't worry about the Python 3.4 tests. I made an issue in #2826.

os.path.join(out_dir, 'vdm.png')
)

if os.path.exists(vdm):
Copy link
Member

Choose a reason for hiding this comment

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

These checks are a little bit worrisome. Is there no way you can predict whether it should exist from the inputs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks. In 8bac064, I inspect the qc.json file to see if those files should exist.

)

outputs['out_avg_b0_png'] = glob(os.path.abspath(
os.path.join(out_dir, 'avg_b0_pe*.png')
Copy link
Member

Choose a reason for hiding this comment

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

Is the use of glob here a time saver, or is the number of outputs unknown until after it's been run? If the former, it would be better to actually predict the outputs. If the latter, then this is fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call. In 8bac064, I now inspect the qc.json which has flags that indicate the presence of certain output files and the number of expected files for each type.

base_name = traits.Str(
'eddy_corrected',
argstr='%s',
desc="Basename (including path) specified when running EDDY",
Copy link
Member

Choose a reason for hiding this comment

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

As a non-EDDY user, this means little to me. Will the intent here be obvious to EDDY users?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I improved the description in 8bac064. I think it should be clear to EDDY users. But honestly, I'm no EDDY expert myself.

"file is present")
)
output_dir = traits.Str(
'eddy_corrected.qc',
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
'eddy_corrected.qc',
name_template='%s.qc',
name_source=['base_name'],

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks. I think I resolved this in 8bac064. Is that correct?

@effigies effigies added this to the 1.1.8 milestone Dec 14, 2018
richford pushed a commit to richford/nipype that referenced this pull request Dec 14, 2018
- Remove redundant `__init__` method in `EddyQuad` class.
- Use `os.path.abspath()` earlier in order to remove from later
statements.
- Improve `EddyQuad`'s `base_name` input description.
- Remove unnecessary `mandatory=False` params.
- Rename `slspec` to `slice_spec`.
- Use default for `EddyQuad`'s `base_name` input.
- Use a name template for `EddyQuad`'s `output_dir` input.
@codecov-io
Copy link

codecov-io commented Dec 14, 2018

Codecov Report

Merging #2825 into master will decrease coverage by 0.01%.
The diff coverage is 54.34%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2825      +/-   ##
==========================================
- Coverage   67.39%   67.38%   -0.02%     
==========================================
  Files         341      341              
  Lines       43428    43474      +46     
  Branches     5396     5401       +5     
==========================================
+ Hits        29270    29294      +24     
- Misses      13461    13474      +13     
- Partials      697      706       +9
Flag Coverage Δ
#smoketests 50.47% <54.34%> (ø) ⬆️
#unittests 64.79% <54.34%> (-0.02%) ⬇️
Impacted Files Coverage Δ
nipype/interfaces/fsl/__init__.py 100% <ø> (ø) ⬆️
nipype/interfaces/fsl/epi.py 63.22% <54.34%> (-0.98%) ⬇️
nipype/testing/utils.py 89.65% <0%> (-1.73%) ⬇️
nipype/interfaces/dynamic_slicer.py 17.47% <0%> (ø) ⬆️
nipype/interfaces/nipy/preprocess.py 45.79% <0%> (ø) ⬆️
nipype/interfaces/io.py 53.99% <0%> (ø) ⬆️
nipype/interfaces/freesurfer/preprocess.py 66.11% <0%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f2a21fd...41d82ad. Read the comment docs.

@richford
Copy link
Contributor Author

Thanks for your review @effigies. I responded inline to your reviews and closed the ones that I felt were trivially resolved, leaving the more involved ones to you to review and close if you agree that I've addressed them. Thanks again for your help!

Copy link
Member

@effigies effigies left a comment

Choose a reason for hiding this comment

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

Thanks for making the changes. Sorry to do this to you, but I've got another round of comments, and some of them are to undo some of what you've done... Thanks for your patience.

.zenodo.json Outdated
"affiliation": "University of Washington",
"name": "Richie-Halford, Adam",
"orcid": "0000-0001-9276-9084"
},
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
},
}

class EddyQuadOutputSpec(TraitedSpec):
out_qc_json = File(
exists=True,
mandatory=True,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
mandatory=True,


out_qc_pdf = File(
exists=True,
mandatory=True,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
mandatory=True,

)

out_avg_b_png = traits.List(
File(
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
File(
File(exists=True),

exists=True,
mandatory=True,
desc=("Image showing mid-sagittal, -coronal and -axial slices of "
"each averaged b-shell volume.")
Copy link
Member

Choose a reason for hiding this comment

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

desc should be a parameter to List, not File, and you can drop mandatory. I've suggested removing it from the other output spec files... I don't think it's enforced, but if it is, it will cause problems if you have the remove_unnecessary_outputs config option set, and no node takes those files as inputs.

mandatory=False,
desc=("Image showing mid-sagittal, -coronal and -axial slices of "
"each b-shell CNR volume. Generated when CNR maps are "
"available.")
Copy link
Member

Choose a reason for hiding this comment

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

Again.


out_vdm_png = File(
exists=True,
mandatory=False,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
mandatory=False,


out_residuals = File(
exists=True,
mandatory=False,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
mandatory=False,


out_clean_volumes = File(
exists=True,
mandatory=False,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
mandatory=False,

outputs['out_qc_pdf'] = os.path.join(out_dir, 'qc.pdf')

with open(outputs['out_qc_json']) as fp:
qc = json.load(fp)
Copy link
Member

Choose a reason for hiding this comment

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

Ah, we'd prefer glob to this. The reason is that _list_outputs() should be runnable without having already run the interface. With glob you'll just get an empty list, while attempting to read a JSON file will actually fail.

If there's no way to predict the outputs entirely without running, that's fine. I was just suggesting that, if you could, that's a little nicer. But we should only depend on self.inputs to determine the expected outputs. And I can't remember whether you used sorted(glob(...)) before, but I would recommend sorting the list so it's consistent across runs. (glob does not have to return sorted lists.)

@effigies
Copy link
Member

Hi, just a reminder that the 1.1.8 release is targeted for January 28. Please let us know if you'd like to try to finish this up for that release.

@richford
Copy link
Contributor Author

Yes. Sorry for the delay and thanks for the nudge. I'll try to get this done in the next week.

@richford
Copy link
Contributor Author

@effigies I think this is ready for review. I changed back to glob and tried to rely on self.inputs as much as I could. I also added a fallback check for the output_dir after @akeshavan and I found that self.inputs.output_dir was undefined when it used the default even though it passed the correct value to the FSL CLI.

Copy link
Member

@effigies effigies left a comment

Choose a reason for hiding this comment

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

Awesome. Looks good. A few minor touchups and suggestions, and I think this is good to go. Feel free to push back if I'm getting too nit-picky, by the way.

# If the output directory isn't defined, the interface seems to use
# the default but not set its value in `self.inputs.output_dir`
if not isdefined(self.inputs.output_dir):
out_dir = os.path.abspath(os.path.basename(self.inputs.base_name) + '.qc.nii.gz')
Copy link
Member

Choose a reason for hiding this comment

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

The default output is a directory named eddy_corrected.qc.nii.gz/? This looks weird, so just want to check there isn't a typo.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@akeshavan, we tested this on your system. I think this is correct but it is very weird. Could you confirm? (I don't want to take the hours needed to build the docker image on my laptop. 😏)

@effigies
Copy link
Member

Oh, can you please merge master to fix the tests?

@effigies
Copy link
Member

@akeshavan Feel free to merge if you're happy with this.

Adam Richie-Halford and others added 13 commits January 21, 2019 07:20
- Remove redundant `__init__` method in `EddyQuad` class.
- Use `os.path.abspath()` earlier in order to remove from later
statements.
- Improve `EddyQuad`'s `base_name` input description.
- Remove unnecessary `mandatory=False` params.
- Rename `slspec` to `slice_spec`.
- Use default for `EddyQuad`'s `base_name` input.
- Use a name template for `EddyQuad`'s `output_dir` input.
Co-Authored-By: richford <richford@users.noreply.github.com>
@richford richford force-pushed the fsl-eddy-quad-interface branch from 2fe91b9 to 41d82ad Compare January 21, 2019 15:21
@richford
Copy link
Contributor Author

Oh, can you please merge master to fix the tests?

Just rebased onto master (with a force push for the change in history).

@effigies
Copy link
Member

I think we can take your word on the default output_dir. Please open a new PR if you discover it should be something else.

@effigies effigies merged commit 6b74fd2 into nipy:master Jan 22, 2019
yarikoptic added a commit to yarikoptic/nipype that referenced this pull request Jan 25, 2019
* origin/master: (63 commits)
  Update nipype/interfaces/dipy/tracks.py
  Update nipype/interfaces/dipy/reconstruction.py
  MNT: Install numpy!=1.16.0 from conda in Docker
  Add FSL auto test
  remake specs
  Update nipype/interfaces/io.py
  Remove return type named tuple
  Update nipype/info.py
  STY: Whitespace, line length
  Remove out_ prefix from EddyQuad outputs
  Apply minor edits from code review
  Use os.path.basename for the fallback output_dir in EddyQuad._list_outputs()
  Add output_dir check to EddyQuad._list_outputs()
  Remove redundant out_avg_b_png lines
  Add glob stuff back in
  Edit in response to @effigies comments on PR nipy#2825
  Fix tests for EddyQuad in interfaces/fsl/epi.py
  Add name to .zenodo.json
  Add doctest for EddyQuad
  fix: made the outputdir be mandatory and use the default val
  ...
yarikoptic added a commit to yarikoptic/nipype that referenced this pull request Feb 4, 2019
1.1.8 (January 28, 2019)

  * FIX: ANTS LaplacianThickness cmdline opts fixed up (nipy#2846)
  * FIX: Resolve LinAlgError during SVD (nipy#2838)
  * ENH: Add interfaces wrapping DIPY worflows (nipy#2830)
  * ENH: Update BIDSDataGrabber for pybids 0.7 (nipy#2737)
  * ENH: Add FSL `eddy_quad` interface (nipy#2825)
  * ENH: Support tckgen -select in MRtrix3 v3+ (nipy#2823)
  * ENH: Support for BIDS event files (nipy#2845)
  * ENH: CompositeTransformUtil, new ANTs interface (nipy#2785)
  * RF: Move pytest and pytest-xdist from general requirement into tests_required (nipy#2850)
  * DOC: Add S3DataGrabber example (nipy#2849)
  * DOC: Skip conftest module in API generation (nipy#2852)
  * DOC: Hyperlink DOIs to preferred resolver (nipy#2833)
  * MAINT: Install numpy!=1.16.0 from conda in Docker (nipy#2862)
  * MAINT: Drop pytest-xdist requirement, minimum pytest version  (nipy#2856)
  * MAINT: Disable numpy 1.16.0 for Py2.7 (nipy#2855)

* tag '1.1.8': (79 commits)
  MNT: Add @feilong to .zenodo, update ordering
  MNT: Update .mailmap
  MNT: Update .zenodo ordering
  Accept invitation as Zenodo release co-author (see nipy#2864)
  MAINT: Update .mailmap
  BF: allowing bids_event_file as alternate input
  MNT: Update .zenodo ordering
  MNT: Version 1.1.8
  DOC: 1.1.8 changelog
  Update nipype/interfaces/dipy/tracks.py
  Update nipype/interfaces/dipy/reconstruction.py
  MNT: Install numpy!=1.16.0 from conda in Docker
  Add FSL auto test
  remake specs
  Update nipype/interfaces/io.py
  Remove return type named tuple
  Update nipype/info.py
  STY: Whitespace, line length
  Remove out_ prefix from EddyQuad outputs
  Apply minor edits from code review
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants