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

ENH: Temporary integration of FSL eddy #144

Merged
merged 28 commits into from Feb 6, 2021
Merged

Conversation

slimnsour
Copy link
Contributor

Porting over from our old dmriprep project. I also had to install a full version of FSL in the Dockerfile in order for the pipeline to have access to eddy.

Copy link
Member

@oesteban oesteban left a comment

Choose a reason for hiding this comment

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

Shaping up! Great contribution 👍

Dockerfile Outdated
Comment on lines 98 to 127
ENV FSLDIR="/opt/fsl-6.0.1" \
PATH="/opt/fsl-6.0.1/bin:$PATH" \
FSLOUTPUTTYPE="NIFTI_GZ"

RUN apt-get update -qq && \
apt-get install -y -q --no-install-recommends \
bc \
dc \
file \
libfontconfig1 \
libfreetype6 \
libgl1-mesa-dev \
libglu1-mesa-dev \
libgomp1 \
libice6 \
libxcursor1 \
libxft2 \
libxinerama1 \
libxrandr2 \
libxrender1 \
libxt6 \
python \
wget && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
echo "Downloading FSL ..." && \
wget -q http://fsl.fmrib.ox.ac.uk/fsldownloads/fslinstaller.py && \
chmod 775 fslinstaller.py && \
/usr/bin/python fslinstaller.py -d /opt/fsl-6.0.1 -V 6.0.1 -q

Copy link
Member

Choose a reason for hiding this comment

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

Is it necessary to upgrade to FSL 6? Unless the interface has changed dramatically, I would leave that for a different PR

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 put this in because the current docker container didn't have eddy_openmp which the node is looking for (in terms of that specific command and also parameters). I thought just installing FSL 6 is the best way to go for this, I'm open to changing it to something else that works though.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The Nipype interface for EddyCorrect (the older command for eddy) might work here instead. eddy_openmp introduced better susceptibility distortion correction but that wouldn't be necessary if we use sdcflows for that.

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 that case I can just use that. EddyCorrect doesn't need the index file and acqp inputs, so would it be best to scrap those nodes for now and just use EddyCorrect?

Copy link
Collaborator

Choose a reason for hiding this comment

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

One thing I'm unsure of is whether the old eddy_correct can handle multi-shell or high b-value data. But our test case (the travelling human phantoms) is single-shell.

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 edited it to just use eddy_correct for now, but the other functions needed for eddy_openmp are still there in case we want to upgrade. Let me know what you guys think.

dmriprep/workflows/dwi/util.py Outdated Show resolved Hide resolved
dmriprep/workflows/dwi/util.py Outdated Show resolved Hide resolved
dmriprep/workflows/dwi/util.py Outdated Show resolved Hide resolved
dmriprep/workflows/dwi/util.py Outdated Show resolved Hide resolved
if metadata.get("PhaseEncodingDirection"):
pe_dir = metadata.get("PhaseEncodingDirection")
else:
pe_dir = metadata.get("PhaseEncodingAxis")
Copy link
Member

Choose a reason for hiding this comment

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

Is this valid?

(still think this should be passed as 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.

This was an old backup check that I don't believe the PhaseEncodingAxis part is accurate now that you mention it. I do still find getting the PhaseEncodingDirection from each subject's metadata useful if its not manually passed as an input. So is it alright to change to use inputs, otherwise revert to PhaseEncodingDirection?

dmriprep/workflows/dwi/util.py Outdated Show resolved Hide resolved
else:
total_readout = total_readout_time

# Construct the acqp file lines
Copy link
Member

Choose a reason for hiding this comment

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

lines? AFAICT, this will generate just one line right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, double checked and it generates one line.

Copy link
Collaborator

Choose a reason for hiding this comment

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

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, added sdcflows' function in e3d347c

dmriprep/workflows/dwi/util.py Outdated Show resolved Hide resolved
dmriprep/workflows/dwi/util.py Outdated Show resolved Hide resolved
@pep8speaks
Copy link

pep8speaks commented Dec 16, 2020

Hello @slimnsour, Thank you for updating!

Cheers! There are no style issues detected in this Pull Request. 🍻 To test for issues locally, pip install flake8 and then run flake8 dmriprep.

Comment last updated at 2021-02-06 21:20:13 UTC

name="gen_idx",
)

eddy_quad = pe.Node(EddyQuad(verbose=True), name="eddy_quad")
Copy link
Collaborator

@josephmje josephmje Dec 17, 2020

Choose a reason for hiding this comment

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

We won't need to include eddy_quad. That's used for creating QC pages but this pipeline will have its own version.

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 for the catch, fixed in 6605fe1

@oesteban
Copy link
Member

oesteban commented Dec 18, 2020

Okay, I've sent some more concrete suggestions to slimnsour#1 - please review and merge if you agree.

EDIT
BTW, after you merge my PR, I'd still like to check on a couple of things. The most relevant is the whether Eddy writes out the correction parameters. If so, this PR should also include the post processing (namely, decomposing the affines and getting separate HMC and ECC parameters).

@oesteban
Copy link
Member

BTW - we will need to also tinker with the parameters so that we can run a fast (and inaccurate) eddy process on Circle (i.e., with the --sloppy flag)

@slimnsour
Copy link
Contributor Author

Sorry for the delay on this. I figured out that the issue with the tests was that there were outdated python2 print statements in code used by eddy_correct. I created an updated file that lets eddy_correct run correctly and it is used to overwrite the base FSL one through our .docker folder.

And as for the --sloppy implementation, I'm not sure what else can be done with the limited eddy_correct parameters to make it faster. I'd be happy to implement any solutions if you guys have any (maybe a parameter that skips eddy_correct altogether?)

@slimnsour
Copy link
Contributor Author

I believe the current build fails are related to current issues with svgutils' new version (see nipreps/niworkflows#595) messing with niworkflows, it has the same AttributeError: 'numpy.int64' object has no attribute 'value' message. I'm investigating further for potential fixes.

@oesteban
Copy link
Member

oesteban commented Jan 8, 2021

Yup, can you check whether the version of svgutils being installed on the docker build step of CircleCI has changed here w.r.t. the latest successful master?

@slimnsour
Copy link
Contributor Author

From sshing into the circleci build I can confirm that it has the problematic svgutils, 0.3.2. Also, going back to 0.3.1 no longer causes the int64 issues. Would the best move here be blocking 0.3.2 from being installed until a fix is put in?

@oesteban
Copy link
Member

oesteban commented Jan 8, 2021

If you want to be a good citizen, you probably want to report this in their repo, pin svgutils<0.3.2, and then undo the pinning if it's not a bug and rather a misuse no longer allowed after 0.3.1.

@slimnsour
Copy link
Contributor Author

Thanks for mentioning the issue. It seems it was fixed in btel/svg_utils#57 (comment) (which I have also tested and can confirm it works) but a new release isn't out yet. Either way I'll exclude the problematic release.

@josephmje
Copy link
Collaborator

Thanks @slimnsour! I just checked the QC reports. ds000206 looks good but the mask in ds001771 looks wonky. Should we try rerunning that workflow?

@slimnsour
Copy link
Contributor Author

slimnsour commented Jan 12, 2021

Good call, not sure what happened there. I'll try rerunning. Edit: I just checked my own test output and it seemed fine too.

@slimnsour
Copy link
Contributor Author

Rerun finished and mask looks good! Let me know if there's anything else to do, otherwise I think we can merge this.

Copy link
Member

@oesteban oesteban left a comment

Choose a reason for hiding this comment

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

I'll check tomorrow on this issue about FSL 5, our Docker image and eddy_openmp.

setup.cfg Outdated Show resolved Hide resolved
dmriprep/workflows/dwi/eddy.py Outdated Show resolved Hide resolved
dmriprep/workflows/dwi/eddy.py Outdated Show resolved Hide resolved
dmriprep/workflows/dwi/eddy.py Outdated Show resolved Hide resolved
included in FSL {EddyCorrect().version} [@eddy].
"""

eddy_correct = pe.Node(EddyCorrect(), name="eddy_correct",)
Copy link
Member

Choose a reason for hiding this comment

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

I've been checking and EddyCorrect only returns the data after correction, which is a big issue. We will need the head-motion parameters, at the very least, for further processing.

Sorry to push back on this. I went to our earlier conversation to check why we pivoted that way. We (or I) thought that FSL 5 does not have eddy_openmp - so fell back to the deprecated version.

However:

/opt/fsl-5.0/bin/eddy_openmp
***************************************************
The following COMPULSORY options have not been set:
	--imain	File containing all the images to estimate distortions for
	--mask	Mask to indicate brain
	--index	File containing indices for all volumes in --imain into --acqp and --topup
	--acqp	File containing acquisition parameters
	--bvecs	File containing the b-vectors for all volumes in --imain
	--bvals	File containing the b-values for all volumes in --imain
	--out	Basename for output
***************************************************

Part of FSL (ID: 5.0.11)

At least 5.0.11 has eddy_openmp.

Copy link
Collaborator

Choose a reason for hiding this comment

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

My mistake. Upon opening the code for eddy_correct, it looks like it was just a wrapper for flirt. Thankfully, @slimnsour has kept the functions for writing the index and acquisition parameters file. v5.0.11 introduced the ability to do slice-to-volume motion correction. This could be useful if the dwi json includes slice timing values.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry for the delay on this. I figured out that the issue with the tests was that there were outdated python2 print statements in code used by eddy_correct. I created an updated file that lets eddy_correct run correctly and it is used to overwrite the base FSL one through our .docker folder.

And as for the --sloppy implementation, I'm not sure what else can be done with the limited eddy_correct parameters to make it faster. I'd be happy to implement any solutions if you guys have any (maybe a parameter that skips eddy_correct altogether?)

If migrating to eddy_openmp, one suggestion could be decreasing the number of iterations with niter.

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 for the feedback guys, attempting to implement this now with 60efdaf.

It seems FSL 5.0.11 is not available using apt-get, so I am putting in an installation step in the Dockerfile to get eddy_openmp. And I set the --sloppy tag to make niter only 1 (which may be overkill but should be much faster).

Copy link
Collaborator

Choose a reason for hiding this comment

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

An alternative to installing FSL 5.0.11 could be to just include the eddy_openmp script in the .docker folder (similar to how topup was added). But I'm not sure if there are added dependencies that are also required.

Copy link
Member

Choose a reason for hiding this comment

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

Are we using FSL BET?

Copy link
Member

Choose a reason for hiding this comment

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

(or does eddy_openmp use it internally?)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, the masking workflow still uses BET. I don't think eddy_openmp uses BET internally. It would rely on the mask that we pass it.

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 suggestion, I've tested just using the one script and it seems to be working just fine so it's pushed in f463e66. I was worried that eddy_openmp would have dependencies that we couldn't individually account for so I initially just installed the entire FSL 5.0.11.

dmriprep/workflows/dwi/eddy.py Show resolved Hide resolved
included in FSL {Eddy().version} [@eddy].
"""
eddy = pe.Node(
Eddy(repol=True, cnr_maps=True, residuals=True, method="jac"),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
Eddy(repol=True, cnr_maps=True, residuals=True, method="jac"),
Eddy(),

Not necessary to output cnr maps or residual image. method is also jacobians by default. Not sure how we want to handle outlier slices/volumes. This would decide whether we should keep repol=True or not. It could definitely be False in sloppy mode.

@slimnsour
Copy link
Contributor Author

slimnsour commented Jan 14, 2021

It seems the ds000206 dataset doesn't have the necessary info for running get_trt (it reaches the end of the function here https://github.com/nipreps/sdcflows/blob/4ce58c0907accc1b92770caa602e2a11a0962825/sdcflows/utils/epimanip.py#L206 without returning anything).

Do you guys have any suggestions on how we can go about this? Perhaps a parameter that allows for a default value to be used if no trt is calculated would work here.

For reference, the in_meta for ds000206 is:

in_meta = {'EchoTime': 0.092, 'FlipAngle': 90, 'MagneticFieldStrength': 3, 'Manufacturer': 'Philips', 'ManufacturersModelName': 'Achieva', 'PhaseEncodingDirection': 'j', 'RepetitionTime': 6.96372}

@arokem
Copy link
Collaborator

arokem commented Jan 14, 2021

I wonder we could drop a dummy value and raise a warning in these cases.

Reading the "Does it matter what I put into my --acqp file?" section here, it seems like it might work in many cases.

Could you give it a try with this dataset? Maybe we can look at the outputs to see whether it does something crazy?

@slimnsour
Copy link
Contributor Author

Thanks for the suggestion @arokem, I think that's a fair addition. I have implemented this now with a backup value of 0.05 and the outputs look reasonable enough, as FSL's eddy page says. Let me know what you guys think, but otherwise everything looks to be finished here.

@oesteban
Copy link
Member

If the parameter has no influence over the outcome, then I think dropping a dummy value is fine.

However, if it does, then I would fix our test dataset, write the dummy value there, and let dMRIPrep break when people do not specify their dataset in full. That will force them to write the dummy value themselves, which hopefully will bring them to consider two things:

  • Whether they can actually obtain the right value and write it.
  • Otherwise, clearly document the hack with the dataset, so that reuse of the dataset can be done acknowledging there's a dummy value in place.

Let's take advantage of BIDS here!

Copy link
Member

@oesteban oesteban left a comment

Choose a reason for hiding this comment

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

Just minimal comments. PLease add the reportlet to the report specification file, so that they are collected and integrated with the final report

@@ -298,7 +298,7 @@ jobs:
- /tmp/ds000206/work
- run:
name: Run full diffusion workflow on ds000206
no_output_timeout: 2h
no_output_timeout: 4h
Copy link
Member

Choose a reason for hiding this comment

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

Is there a way of running this in some sort of "sloppy/fast" mode? 4h is a lot!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

4h is a lot for future testing, and this was already cut down by decreasing the --niter tag from 5 to 1 in sloppy mode as per @josephmje's suggestion. I will try to decrease other parameters like --repol (and perhaps --mbs_niter?).

Copy link
Member

Choose a reason for hiding this comment

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

I guess :D - I have no idea of how to control eddy.

dmriprep/workflows/dwi/base.py Outdated Show resolved Hide resolved
)

eddy_report = pe.Node(
SimpleBeforeAfter(before_label="Distorted", after_label="Eddy Corrected",),
Copy link
Member

Choose a reason for hiding this comment

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

I wonder whether there is a better reportlet. Although you can see that the average image seems to have a better SNR after eddy, do you think that some sort of standard deviation map across orientations or similar will give us a better picture of what this step did (and whether it did it well)?

WDTY @arokem ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

The developers of eddy seem to like showing an animation of flipping through all the different directions rather quickly.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Another option is to show before and after mean maps across all directions (is that what this does?).

Unfortunately, I think that an std map would capture both artifactual and real variance (e.g., due to diffusion anisotropy).

Copy link
Member

Choose a reason for hiding this comment

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

So, now that you made me check what this exactly does I can tell that it shows the b=0 reference we generate (averaging all b=0 in the dataset after regressing out signal drift) and the first volume of the eddy-corrected output (which happens to be a b=0, as it is the most common case). So at the very least, we want to plot two b=0 references generated the same way.

I'm not sure that we are going to see much averaging all directions... however, I believe the std map would be still useful - since you have the reference std map (that would be the "Before" on the flickering mosaic) and then you plot the std map after correction (the "After"), adjusted to have consistent intensity ranges, we should see how some of the variability (that from the artifact) goes away. Because of the structure of the artifact, I would expect better SNR (less noise in the "after" plot) at the edges of the WM, esp. around the ventricles. WDYT?

Copy link
Collaborator

Choose a reason for hiding this comment

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

For the averaging, I imagine the "pre" image would have a lot more blur than the "post"?

But for the std image: I think that you have me convinced.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds like a good idea! I'd just need some direction so I can implement it - where is the std map input coming from in the workflow?

Copy link
Collaborator

Choose a reason for hiding this comment

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

This paper expands on what FSL currently outputs for QCing eddy: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6264528/pdf/main.pdf

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just checking up on this @oesteban, you mentioned in the meeting that you would be able to throw something together for the reports you guys were interested in?

Copy link
Member

Choose a reason for hiding this comment

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

Yup, sorry I've been booked up. Let's merge this and address the reportlet on a different PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good!

@oesteban
Copy link
Member

Please rebase on upstream/master when #151 is merged :)

@@ -91,6 +91,13 @@ sections:
the final images are resampled using cubic B-Spline interpolation.
static: false
subtitle: Alignment of diffusion and anatomical MRI data (surface driven)
- bids: {datatype: figures, desc: eddy, suffix: dwi}
caption: Head-motion and eddy current corrected diffusion data
description: Geometrical distortions derived from the so-called Eddy-currents,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
description: Geometrical distortions derived from the so-called Eddy-currents,
description: Eddy current-induced geometric distortions

Copy link
Member

@oesteban oesteban Feb 5, 2021

Choose a reason for hiding this comment

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

@slimnsour would you accept this suggestion?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup, fixed in caba8c3

@@ -91,6 +91,13 @@ sections:
the final images are resampled using cubic B-Spline interpolation.
static: false
subtitle: Alignment of diffusion and anatomical MRI data (surface driven)
- bids: {datatype: figures, desc: eddy, suffix: dwi}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This section should be moved before the alignment with the anatomical data.

Copy link
Member

Choose a reason for hiding this comment

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

agree

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, fixed in caba8c3

@josephmje
Copy link
Collaborator

If the parameter has no influence over the outcome, then I think dropping a dummy value is fine.

Just chiming in to confirm that eddy does not use total readout time if we don't provide the fieldmap or topup arguments. I've tested this out with a few different dummy values in the past and would get the same result.

We may face an issue when attempting to apply a true fieldmap to the dwi data and total readout time can't be calculated. But this can just be resolved by sending a warning/erroring out.

Copy link
Member

@oesteban oesteban left a comment

Choose a reason for hiding this comment

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

LGTM, please address Michael's comments

@@ -91,6 +91,13 @@ sections:
the final images are resampled using cubic B-Spline interpolation.
static: false
subtitle: Alignment of diffusion and anatomical MRI data (surface driven)
- bids: {datatype: figures, desc: eddy, suffix: dwi}
Copy link
Member

Choose a reason for hiding this comment

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

agree

slimnsour and others added 24 commits February 6, 2021 15:51
Co-authored-by: Oscar Esteban <code@oscaresteban.es>
Co-authored-by: Oscar Esteban <code@oscaresteban.es>
Co-authored-by: Michael Joseph <josephmje.22@gmail.com>
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.

None yet

5 participants