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

rf: Calculate RMSD from motion transforms #3427

Merged
merged 6 commits into from
Feb 12, 2025

Conversation

@effigies effigies force-pushed the feat/derivative-motion-params branch from 05e25c8 to c28a615 Compare February 10, 2025 19:51
Copy link

codecov bot commented Feb 10, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 72.92%. Comparing base (f53185e) to head (d1ebb00).
Report is 7 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3427      +/-   ##
==========================================
+ Coverage   72.70%   72.92%   +0.21%     
==========================================
  Files          57       57              
  Lines        4327     4358      +31     
  Branches      546      547       +1     
==========================================
+ Hits         3146     3178      +32     
+ Misses       1065     1064       -1     
  Partials      116      116              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@effigies
Copy link
Member Author

effigies commented Feb 10, 2025

Comparing the ds005 output from this (right) and master (left):

image

@effigies
Copy link
Member Author

@oesteban What do you think about this?

@oesteban
Copy link
Member

Were we not generated rmsd? (looking at fmriprep/interfaces/tests/data/sub-01_task-mixedgamblestask_run-01_desc-motion_timeseries.tsv)

It does look to replicate both our and their work well, which is great. Do I understand correctly if I say this increases our dependency on MCFLIRT instead of reducing it? Perhaps we should report both (our traditional rmsd and your fsl-like calculation)?

Also, cross-comparison with https://github.com/nipreps/nifreeze/blob/237a9cb0c4a3ca9681ac6adda3f3330337f5c4b7/src/nifreeze/registration/utils.py#L82-L114 would be very interesting :)

@effigies
Copy link
Member Author

Sorry, I realized that I said more live to Mathias than I put into this PR. The basic problem is when passing:

fmriprep $BIDS $OUT participant --derivatives fmriprep=$MINIMAL

The HMC pipeline was generating ITK transforms, motion parameters, and RMSD. If we were getting pre-computed transforms, then we were not running HMC and so not getting motion parameters or RMSD. #3424 dropped the motion parameters from the HMC pipeline and reconstructed them in the confounds pipeline, and this aims to do the same thing for RMSD.

I don't think this increases our dependence on FSL; in fact it means we can generate FSL-equivalent motion parameters / RMSD, given rigid transform matrices from any HMC estimation tool.

@oesteban
Copy link
Member

That's very cool. I can try to have a deeper look into the code tomorrow, if you can wait.

Comment on lines +133 to +134
np.diag(t @ t.T)
+ np.trace(M.transpose(0, 2, 1) @ M, axis1=1, axis2=2) * Rmax**2 / 5
Copy link
Collaborator

Choose a reason for hiding this comment

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

for my understanding - why the difference in transposing t and M?

Copy link
Member Author

Choose a reason for hiding this comment

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

t has shape (n, 3) so t @ t.T is (n, n). We then take the diagonal, so we get a vector of shape (n,). If we swapped the order, we'd get (3, 3), and thus a 3-vector.

M has shape (n, 3, 3) so M.transpose(0,2, 1) @ M has shape (n, 3, 3). The trace(..., axis1=1, axis2=2) is the sum of the diagonals of the (3, 3) submatrices, so (n,). The trace(M) == trace(M.T), so we could just as easily do:

Suggested change
np.diag(t @ t.T)
+ np.trace(M.transpose(0, 2, 1) @ M, axis1=1, axis2=2) * Rmax**2 / 5
np.diag(t @ t.T)
+ np.trace(M @ M.transpose(0, 2, 1), axis1=1, axis2=2) * Rmax**2 / 5

It's all just a way of getting sums of squares, and definitely not the most efficient, but it pretty closely follows the FSL code, while using vectorized operations.

@effigies
Copy link
Member Author

Going to merge. @oesteban feel free to do a post-review merge, and we can back it out if you aren't comfortable with it.

@effigies effigies merged commit 8c58b70 into nipreps:master Feb 12, 2025
14 of 15 checks passed
effigies added a commit that referenced this pull request Feb 18, 2025
This builds on #3427 to add a couple final things to the transform-only
workflow:

```
fmriprep $BIDS $OUT participant --derivatives fmriprep=$MINIMAL
```

BOLD masks were not getting generated from the boldref and dummy scans
were not being estimated.

This PR factors out the validation and dummy scan detection from
`init_raw_boldref_wf` and uses the full workflow in fit mode and the
subworkflow in transform mode.

Will rebase after #3427 is merged. Can compare now with:
c28a615...6c78e54
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.

3 participants