-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
Speed up the application of PiecewiseAffineTransform #6963
base: main
Are you sure you want to change the base?
Conversation
Replace manual iteration over individual affine transforms with np.einsum
7869e93
to
4b55de1
Compare
Hey, sorry for our delayed response on this. I'll have a look. |
I am noticing that for the old and new implementation the number seems to change a bit. E.g. for the example below the residuals change on a scale <1e-15. import skimage as ski
src = np.array([[0, 3], [0, -1], [1, 2], [-1, 0], [1, 1], [-1, 1]])
dst = np.array([[0, 0], [1, 0], [1, 1], [1, 2], [0, 2], [0, 1]])
tform = ski.transform.PiecewiseAffineTransform()
tform.estimate(src, dst)
tform.residuals(src, dst) Probably floating point errors but not sure yet? I don't like that our tests don't catch this. While we are at it, would you mind adapting the above example with outputs as a doctest? :) |
With import numpy as np
import skimage as ski
rng = np.random.default_rng(42)
src = rng.random((100, 2))
dst = rng.random((100, 2))
tform = ski.transform.PiecewiseAffineTransform()
tform.estimate(src, dst)
x = rng.random((2000, 2))
%timeit tform(x) # requires ipython the propsed solution seems to be ~7 times faster. 👍 |
I've added the doctest to the |
and try to address failure due to "PiecewiseAffineTransform.estimate:58:Unexpected indentation." See https://dev.azure.com/scikit-image/scikit-image/_build/results?buildId=10795&view=logs&j=feb0b66f-d63c-5533-bf34-a72e08b12053&t=26b7b1f7-60f2-5676-21d5-45b533330524&l=1050
@lagru Let me know if there is anything else needed on my end to merge the PR. |
@svepe, I think this looks good. However, I am hesitant to approve this because this performance optimization does change our results, if only slightly. We have a policy to to never silently change the results if input stays the same. And because we don't seem to have a good test coverage for this, I am hesitant to assume from a single doctest that this is only noise due to floating errors. Let's see what @scikit-image/core thinks and I'll bring it up in the next meeting as well. |
Point in favor of merging this is that the snippet below seems to pass always. We use Details
import numpy as np
import skimage as ski
class NewPiecewiseAffineTransform(ski.transform.PiecewiseAffineTransform):
def __call__(self, coords):
coords = np.asarray(coords)
# determine triangle index for each coordinate
simplex = self._tesselation.find_simplex(coords)
# stack of affine transforms to be applied to every coord
affines = np.stack([affine.params for affine in self.affines])[simplex]
# convert coords to homgeneous points
points = np.c_[coords, np.ones((coords.shape[0], 1))]
# apply affine transform to every point
result = np.einsum("ikj,ij->ik", affines, points)
# coordinates outside of mesh
result[simplex == -1, :] = -1
# convert back to 2d coords
result = result[:, :2]
return result
rng = np.random.default_rng(42)
for _ in range(100):
src = rng.random((100, 2))
dst = rng.random((100, 2))
tform = ski.transform.PiecewiseAffineTransform()
tform.estimate(src, dst)
tform2 = NewPiecewiseAffineTransform()
tform2.estimate(src, dst)
x = rng.random((2000, 2))
np.testing.assert_allclose(tform(x), tform2(x)) Additional disclaimer before approving this: I still need to wrap my head around the |
This optimization seems fine. I would recommend that we keep a version of the (original) code in a comment that explains what's happening under the hood. A text comment may also help with that. The slight change in residual is not a concern, being so small. |
@stefanv Do you mean appending
Before the main/einsum call? |
The speedup is achieved by replacing manual iteration over individual affine transforms with
np.einsum
.Closes #6864