-
Notifications
You must be signed in to change notification settings - Fork 59
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
Apply fixes to skimage.transform scheduled for scikit-image 0.19.2 #208
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -763,7 +763,7 @@ def estimate(self, src, dst, weights=None): | |
src_matrix, src, has_nan1 = _center_and_normalize_points(src) | ||
dst_matrix, dst, has_nan2 = _center_and_normalize_points(dst) | ||
if has_nan1 or has_nan2: | ||
self.params = xp.full((d, d), np.nan) | ||
self.params = xp.full((d + 1, d + 1), xp.nan) | ||
return False | ||
# params: a0, a1, a2, b0, b1, b2, c0, c1 | ||
A = xp.zeros((n * d, (d + 1) ** 2)) | ||
|
@@ -792,6 +792,7 @@ def estimate(self, src, dst, weights=None): | |
# because it is a rank-defective transform, which would map points | ||
# to a line rather than a plane. | ||
if xp.isclose(V[-1, -1], 0): | ||
self.params = xp.full((d + 1, d + 1), xp.nan) | ||
return False | ||
|
||
H = np.zeros( | ||
|
@@ -1050,39 +1051,38 @@ def estimate(self, src, dst): | |
Returns | ||
------- | ||
success : bool | ||
True, if model estimation succeeds. | ||
True, if all pieces of the model are successfully estimated. | ||
|
||
""" | ||
|
||
ndim = src.shape[1] | ||
# forward piecewise affine | ||
# triangulate input positions into mesh | ||
xp = cp.get_array_module(src) | ||
if xp is cp: | ||
# TODO: grlee77 :update if spatial.Delaunay is implemented for GPU | ||
# transfer to CPU for use of spatial.Delaunay | ||
src = cp.asnumpy(src) | ||
dst = cp.asnumpy(dst) | ||
# TODO: update if spatial.Delaunay become available in CuPy | ||
self._tesselation = spatial.Delaunay(cp.asnumpy(src)) | ||
|
||
ok = True | ||
|
||
self._tesselation = spatial.Delaunay(src) | ||
# find affine mapping from source positions to destination | ||
self.affines = [] | ||
for tri in self._tesselation.vertices: | ||
for tri in xp.asarray(self._tesselation.vertices): | ||
affine = AffineTransform(dimensionality=ndim) | ||
affine.estimate(src[tri, :], dst[tri, :]) | ||
ok &= affine.estimate(src[tri, :], dst[tri, :]) | ||
self.affines.append(affine) | ||
|
||
# inverse piecewise affine | ||
# triangulate input positions into mesh | ||
self._inverse_tesselation = spatial.Delaunay(dst) | ||
# TODO: update if spatial.Delaunay become available in CuPy | ||
self._inverse_tesselation = spatial.Delaunay(cp.asnumpy(dst)) | ||
# find affine mapping from source positions to destination | ||
self.inverse_affines = [] | ||
for tri in self._inverse_tesselation.vertices: | ||
for tri in xp.asarray(self._inverse_tesselation.vertices): | ||
affine = AffineTransform(dimensionality=ndim) | ||
affine.estimate(dst[tri, :], src[tri, :]) | ||
ok &= affine.estimate(dst[tri, :], src[tri, :]) | ||
self.inverse_affines.append(affine) | ||
|
||
return True | ||
return ok | ||
|
||
def __call__(self, coords): | ||
"""Apply forward transformation. | ||
|
@@ -1102,12 +1102,12 @@ def __call__(self, coords): | |
""" | ||
|
||
xp = cp.get_array_module(coords) | ||
if xp == cp: | ||
coords = coords.get() | ||
out = np.empty_like(coords, np.double) | ||
out = xp.empty_like(coords, xp.double) | ||
|
||
# determine triangle index for each coordinate | ||
simplex = self._tesselation.find_simplex(coords) | ||
# coords must be on host for calls to _tesselation methods | ||
simplex = self._tesselation.find_simplex(cp.asnumpy(coords)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we coerce to a NumPy array here? Note: This comes up again below, but only commenting here for simplicity. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
simplex = xp.asarray(simplex) | ||
|
||
# coordinates outside of mesh | ||
out[simplex == -1, :] = -1 | ||
|
@@ -1120,8 +1120,6 @@ def __call__(self, coords): | |
|
||
out[index_mask, :] = affine(coords[index_mask, :]) | ||
|
||
if xp == cp: | ||
out = xp.asarray(out) | ||
return out | ||
|
||
def inverse(self, coords): | ||
|
@@ -1142,12 +1140,12 @@ def inverse(self, coords): | |
""" | ||
|
||
xp = cp.get_array_module(coords) | ||
if xp == cp: | ||
coords = coords.get() | ||
out = np.empty_like(coords, np.double) | ||
out = xp.empty_like(coords, xp.double) | ||
|
||
# determine triangle index for each coordinate | ||
simplex = self._inverse_tesselation.find_simplex(coords) | ||
# coords must be on host for calls to _tesselation methods | ||
simplex = self._inverse_tesselation.find_simplex(cp.asnumpy(coords)) | ||
simplex = xp.asarray(simplex) | ||
|
||
# coordinates outside of mesh | ||
out[simplex == -1, :] = -1 | ||
|
@@ -1159,8 +1157,7 @@ def inverse(self, coords): | |
index_mask = simplex == index | ||
|
||
out[index_mask, :] = affine(coords[index_mask, :]) | ||
if xp == cp: | ||
out = xp.asarray(out) | ||
|
||
return out | ||
|
||
|
||
|
@@ -1344,7 +1341,9 @@ def estimate(self, src, dst): | |
|
||
self.params = _umeyama(src, dst, False) | ||
|
||
return True | ||
# _umeyama will return nan if the problem is not well-conditioned. | ||
xp = cp.get_array_module(self.params) | ||
return not xp.any(xp.isnan(self.params)) | ||
|
||
@property | ||
def rotation(self): | ||
|
@@ -1460,7 +1459,9 @@ def estimate(self, src, dst): | |
|
||
self.params = _umeyama(src, dst, estimate_scale=True) | ||
|
||
return True | ||
# _umeyama will return nan if the problem is not well-conditioned. | ||
xp = cp.get_array_module(self.params) | ||
return not xp.any(xp.isnan(self.params)) | ||
|
||
@property | ||
def scale(self): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we open an issue about this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could, it is probably not very easy to address though. We would need a Delaunay triangulation class with a
vertices
attribute andfind_simplex
method for the GPU (There are several other attributes/methods on this class in SciPy, but they are not used here). On the SciPy side the implementation relies on the QHull C library.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sure there are GPU implementations of Delaunay triangulation, but hadn't looked into whether there is a compatibly licensed one we could reuse.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we did find one, ideally it would be available via upstream
cupyx.scipy.spatial
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idk maybe it could happen. There has been work in the past like CudaHull