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

FIX: Interpolation of B-Spline grid on the target EPI grid #294

Closed
wants to merge 4 commits into from

Conversation

oesteban
Copy link
Member

This PR fixes #289 and will address #218, although no solution (computationally tractable) has been found yet.

np.cross(ctrl_nii.affine[:-1, :-1].T, target_nii.affine[:-1, :-1].T),
axis=1,
), 0, atol=1e-3):
raise RuntimeError("Image's and B-Spline's grids are not aligned.")
Copy link
Member Author

Choose a reason for hiding this comment

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

@mgxd here's where your data break now. I'm 99% that this is the problem, the lazy (fast) evaluation of B-Spline weights assumes the alignment of the two coordinate systems (i.e., all direction cosines are collinear).

cc/ @effigies

coords[axis] = np.arange(sample_shape[axis], dtype=dtype)

# Calculate the index component of samples w.r.t. B-Spline knots along current axis
ijk_samples = (target_to_grid @ coords)[axis]
Copy link
Member Author

Choose a reason for hiding this comment

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

This line addresses the problem stated in #289 in just one shot.

That's why at "fit" time, when the estimated fieldmap is applied to generate reportlets (and validate everything), the interpolation works (and yields the same output of topup, saving some details). But when it reaches the correction of the EPI, the B-Spline grid is still a grid, but it's not aligned with the EPI grid.

@oesteban oesteban marked this pull request as draft October 27, 2022 15:52
@oesteban oesteban force-pushed the fix/coeff2fmap-not-aligned branch from 5e4bfd9 to 687ae2d Compare November 15, 2022 15:40
@oesteban
Copy link
Member Author

Cython improvement:

In [1]: import bspline

In [2]: from sdcflows.transform import _cubic_bspline
221111-11:56:02,394 nipype.utils WARNING:
	 A newer version (1.8.4) of nipy/nipype is available. You are using 1.8.4.dev0

In [3]: from numpy import frompyfunc

In [4]: bs = frompyfunc(bspline.bs_eval, 1, 1)

In [5]: %timeit bs(data)
699 ns ± 0.701 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

In [6]: %timeit _cubic_bspline(data)
23.5 µs ± 102 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

@oesteban
Copy link
Member Author

I have split this PR into smaller ones (#302, #301, #304, and #303). Since I had not managed to resolve the rotation issue, I'm going to open a fresh branch with the Cython code finally resolving this issue.

(I believe that, in the presence of this rotation, there's no way around calculating the b-spline for all combinations of voxels and control points in three dimensions, and hence, Cython)

@oesteban oesteban closed this Nov 18, 2022
@oesteban
Copy link
Member Author

oesteban commented Nov 18, 2022

@effigies, same test, with scipy.signal.cubic

In [1]: import bspline

In [2]: from scipy.signal import cubic

In [3]: from numpy import frompyfunc

In [4]: bs = frompyfunc(bspline.bs_eval, 1, 1)

In [5]: import numpy as np

In [6]: data = np.array([1.4, 2.1, 0.7, -1, 3.0])

In [7]: %timeit bs(data)
699 ns ± 0.701 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

In [6]: %timeit cubic(data)
22.2 µs ± 50 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

So basically the same as our _cubic_bspline implementation with np.piecewise.

@effigies
Copy link
Member

Could be worth pushing the cython implementation upstream into scipy?

@oesteban
Copy link
Member Author

that'd be interesting :)

I'm not sure I have the bandwidth:

  • it will require more thorough testing than ours,
  • they will probably want a more comprehensive implementation (e.g., allowing several bspline degrees, and/or derivatives)

so, happy to consider it if we find it necessary to go around our problems, but I don't know how much workload we will be able to take on.

@oesteban
Copy link
Member Author

BTW:

In [1]: import numpy as np
   ...: from scipy.signal import cubic
   ...: from sdcflows.lib.bspline import bs_eval

In [2]: our_cubic = np.frompyfunc(bs_eval, 1, 1)

In [3]: data = np.array([1.4, 2.1, 0.7, -1, 3.0])

In [4]: np.allclose(cubic(data), our_cubic(data).astype("float32"))
Out[4]: True

(for some weird reason, our_cubic returns dtype object)

@effigies
Copy link
Member

I believe cubic() is a special implementation from bspline(), so I don't think you would need to generalize it. And I would hope they have tests.

Anyway, agreed that it could be a deep rabbit hole and we should not block any of our process on it. I wonder if just posting your implementation as an issue on scipy could start the ball rolling and somebody else might eventually pick it up and do the integration work.

@oesteban
Copy link
Member Author

Anyway, agreed that it could be a deep rabbit hole and we should not block any of our process on it. I wonder if just posting your implementation as an issue on scipy could start the ball rolling and somebody else might eventually pick it up and do the integration work.

This is something I can definitely do. I will try to first finish up the sdcflows problem and then think of how to make it more widely available.

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.

B-Spline grid and target EPI are assumed to have the same direction cosines
2 participants