-
Notifications
You must be signed in to change notification settings - Fork 27
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
*The problem* of SDCFlows (and how to fix it) #345
Comments
Re: step 3The voxel zoom is relevant to convert to voxels to mm (reasonably). To convert from Hz (vox/s) to voxels we need the readout time (s). And we actually have it already there, exactly in the way I was mentioning above: sdcflows/sdcflows/transform.py Lines 197 to 200 in 891e81b
|
Addresses the issue of properly applying fieldmaps on distorted data. Resolves: #345.
Okay, I will have to hold the reins a little because there's no way around reconstructing the field on a rotated grid. However, I think it would be cheap to start with an approximation and then consider if we want the very precise thing. See the above draft PR (and then new |
Okay, just doing some math. Let's define the following spaces:
Each has an accompanying affine matrix
A transform
and we will use the inverses when going in the opposite direction. There is a fieldmap function Finally, there is a constant readout field The problem is to correcting a bold volume in Given The field at that location is The uncorrected location in If we project That is, at each voxel in the BOLD reference space, we interpolate the fieldmap in Hz and the per-volume readout vector in seconds to retrieve a "real coordinate" that we project into the volume space and interpolate. |
Okay, rereading what you wrote, I think I disagree with steps 3 and 4. I think step three is to convert to a coordinate shift map: readout_vec = np.zeros((3,), 'int8')
readout_vec["ijk".index(pedir[0])] = 1
if pedir[1:] == "-":
readout_vec *= -1
volspace_readout = trt * (vol.affine @ readout_vec)
refspace_readout = mc_affine @ volspace_readout
coordinate_shift = refspace_readout * fieldmap Now for step four, I simply interpolate from a shifted world location: target_coord = boldref.affine @ target_index
volume_coord = inv(mc_affine) @ (target_coord + coordinate_shift * field[target_index]) (This is all pseudocode; we need to handle the |
Yes, let's simplify further by dividing the problem into two halves. Let's address first the most practical part of the problem: correcting the BOLD volume in
Without distortion ( Please note that the VOX/RAS conversions are necessary because Now, let's have a nonzero fieldmap which effectively is equivalent to applying Please also note how the "traditional" correction works, where you first use topup/applytopup to create a corrected version of the dataset, and then apply head motion correction: Then the second part of the problem is as follows. When we compute the fieldmap, we do not get it with reference to the target images (or references of target images). We get them in their native (fieldmap) space. However, I think I'm going to leave it for later because you just posted something :) EDIT: corrected subindex in (*) |
Translating into nitransforms'esque: has no direct API method, instead you can implement the physical to physical mapping: with: vec_c_V_t = t_BV_xfm.map(vec_c_B) However, the above ( volume_t_in_boldref = t_BV_xfm[t].apply(bold_volume[t]) In reality, apply will move all timepoints: bold_in_boldref = t_BV_xfm.apply(bold_volume, reference=boldref) Please note how pulling information through the transform in the image and mapping have I/O spaces "swapped". |
(if we can agree on this first half #345 (comment), let's dig into the second half of the problem) |
Sorry, had to run some errands, but have now wrapped my head around your comment, @oesteban. The fundamental difference between our two proposals is: Yours: Mine: I believe you're right that calculating I think we agree on the math at this point; the only thing I'm not sure we agree on is what the intermediate representation of the fieldmap should be in That feels like an empirical question of which works better, so happy to move on to the second half. |
Thanks for initiating the conversation with the maths, in hindsight it's the only way that would've worked. Unfortunately, it's a bit late for me. I'll try to write down the second half tomorrow. (I'm on my phone now) |
Okay, I decided to start writing this up into a single document with derivations that we could include in the docs, and worked myself around to your voxel displacement map: We are looking to resample a volume We would like to find an additional displacement Stepping back from $$\Delta \vec c_V^t = \Phi_F(\mathcal{T}{BF}\mathcal T{VB}^t\vec c_V^t)\vec R_V$$ This is the fieldmap, resampled into the volume space $$\begin{align} Using our earlier calculations, we can decompose this into the fieldmap resampled $$\begin{align} Hence, by resampling We are able to fully calculate the index-to-index mapping: |
I spent some time this morning to see if we could recast this final calculation as a traditional concatenation of transforms, but convinced myself that that would not work. We need to be able to create a voxel-shift-map in the target space, but operate on source indices. However, I realized that this target space can be anything, not just the BOLD reference. If we want to do this correctly and consistently, we should resample the fieldmap to every target space that we do single-shot resampling to. Here's the doc, btw: https://hackmd.io/@effigies/rethinking-resampling. It still assumes that our target space is the BOLD reference, but I'll aim to update it to be more general if we want to use this doc. |
Addresses the issue of properly applying fieldmaps on distorted data. Resolves: #345.
Addresses the issue of properly applying fieldmaps on distorted data. Resolves: #345.
Addresses the issue of properly applying fieldmaps on distorted data. Resolves: #345.
Addresses the issue of properly applying fieldmaps on distorted data. Resolves: #345.
Addresses the issue of properly applying fieldmaps on distorted data. Resolves: #345.
What would you like to see added in this software?
Instead of directly starting from coding, I will explain what I currently think has happened.
I have come to the realization that I've unintendedly been over-complicating the implementation of SDCFlows' interface.
Let me first thank you for your patience on this front, @mgxd, @effigies, @eilidhmacnicol and others. It took too much time for me to realize.
Why first share rather than code away?
I'm under the impression that SDCFlows needs better documentation and better communication from its architect (which would be me, in this case). The notebook describing the problem was a good move, but a second notebook describing correction has been missing and has contributed to the failure to set clear goals (and very likely would have made me realize of the following). Since I don't have time to write that notebook right now, I will explain what I just realized.
Daunted by the size of the forest, I missed that I should've cared for the first tree only
Since October 2022 I've been banging my head against the same wall, trying to interpolate a B-Spline field on a grid that is not aligned with the target grid in a manageable manner (memory, time). This problem became apparent with Mathias' data because there's a rotation of the image w.r.t. the fieldmap. This seemed essential as well to implement the vision of being able to correct each volume of the EPI series with the exact fieldmap realization (when the head moves, the deformation field changes because the phase-encoding axis doesn't while inhomogeneity of the field does with the head).
Well, after some thought, that is wrong -- or better said, pretty stupid. Instead:
Preconditions:
The algorithm:
antsApplyTransform
, then the fieldmap is the moving and EPI is the reference). This isnitransforms.linear.Affine.from_file("fieldmap-to-EPI.h5").map((x, y, z))
, where x, y, z are the coordinates in mm of each voxel of the EPI grid. This can be done easily at once just by rotating the affine of the EPI through the fieldmap-to-EPI transform (meaning, we just get another NIfTI file of the EPI that a decent viewer with good implementation of the orientation will render aligned with the fieldmap, despite them being on different grids). The latter is also doable with a nitransforms one-liner.At this point, it would be convenient to convert the fieldmap from Hz into a voxel shift map (if I'm not wrong, this is just accounting for voxel zoom in the PE direction).(EDIT: see comment below)4.1. Project all the voxels of the reference onto the volume
4.2. Convert all those coordinates in mm into voxel coordinates
4.3. Instead of directly interpolating the BOLD value on the projected location, now you pull up your voxel shift map, and at that voxel coordinate, you get the displacement of that voxel to account for distortion. You update the coordinates of each voxel with this amount (minus/plus) and now yes, interpolate because you'll be off-grid almost always.
4.4. Now you have a data array on the same grid of the reference, and you can use its affine to build a new NIfTI where head motion AND susceptibility distortions are not present.
I hope the algorithm is clearly described. If not, it would be critical that you poked at me until we're all on the same page.
The good news is the above is trivial to implement with our current kit. And no one is doing this because of the fragmentation of code. I think this would be as trivial to implement for FSL as it is for us (in their case only with TOPUP, as opposed to us, where it would work for all estimation approaches). But I believe AFNI and others would not be as close.
Do you have any interest in helping implement the feature?
Yes
Additional information / screenshots
No response
The text was updated successfully, but these errors were encountered: