ENH: Implement fast coordinate transformations.

This commit is contained in:
Stefan van der Walt
2012-05-02 21:31:23 -07:00
parent 12669d62e6
commit 7df3707c33
7 changed files with 268 additions and 8 deletions
+2
View File
@@ -4,3 +4,5 @@ from .finite_radon_transform import *
from .project import *
from ._project import homography as fast_homography
from .integral import *
from ._warp import warp
from ._swirl import swirl
+70
View File
@@ -0,0 +1,70 @@
from __future__ import division
import numpy as np
from ._warp import warp
def _swirl_mapping(xy, center, rotation, strength, radius):
x, y = xy.T
x0, y0 = center
radius = radius / 5 * np.log(2)
rho = np.sqrt((x - x0)**2 + (y - y0)**2)
theta = rotation + strength * \
np.exp(-rho / radius) + \
np.arctan2(y - y0, x - x0)
xy[..., 0] = x0 + rho * np.cos(theta)
xy[..., 1] = y0 + rho * np.sin(theta)
return xy
def swirl(image, center=None, strength=1, radius=100, rotation=0,
output_shape=None, order=1, mode='constant', cval=0):
"""Perform a swirl transformation.
Parameters
----------
image : ndarray
Input image.
center : (x,y) tuple or (2,) ndarray
Center coordinate of transformation.
strength : float
The amount of swirling applied.
radius : float
The extent of the swirling in pixels. The effect dies out
rapidly beyond radius.
rotation : float
Additional rotation applied to the image.
Returns
-------
swirled : ndarray
Swirled version of the input.
Other parameters
----------------
output_shape : tuple or ndarray
Size of the generated output image.
order : int
Order of splines used in interpolation, passed as-is to ndimage.
mode : string
How to handle values outside the image borders, passed as-is
to ndimage.
cval : string
Used in conjunction with mode 'constant', the value outside
the image boundaries.
"""
if center is None:
center = np.array(image.shape)[:2] / 2
warp_args = {'center': center,
'rotation': rotation,
'strength': strength,
'radius': radius}
return warp(image, _swirl_mapping, tf_args=warp_args,
output_shape=output_shape,
order=order, mode=mode, cval=cval)
+91
View File
@@ -0,0 +1,91 @@
__all__ = ['warp']
import numpy as np
from scipy import ndimage
from skimage.util import img_as_float
eps = np.finfo(float).eps
def _stackcopy(a, b):
"""a[:,:,0] = a[:,:,1] = ... = b"""
if a.ndim == 3:
a.transpose().swapaxes(1, 2)[:] = b
else:
a[:] = b
def warp(image, coord_tf, tf_args={},
output_shape=None, order=1, mode='constant', cval=0.):
"""Warp an image according to a given coordinate transformation.
Parameters
----------
image : 2-D array
Input image.
coord_tf : callable xy = f(xy, **kwargs)
Function that transforms an Nx2 array of ``(x, y)`` coordinates
in the *output image* into their corresponding coordinates in the
*source image*. Note that this is a reverse mapping (also
see examples below).
tf_args : dict, optional
Keyword arguments passed to `coord_tf`.
output_shape : tuple (rows, cols)
Shape of the output image generated.
order : int
Order of splines used in interpolation.
mode : string
How to handle values outside the image borders. Passed as-is
to ndimage.
cval : string
Used in conjunction with mode 'constant', the value outside
the image boundaries.
Examples
--------
Shift an image to the right:
>>> from skimage import data
>>> image = data.camera()
>>>
>>> def shift_right(xy):
... xy[:, 0] -= 10
... return xy
>>>
>>> warp(image, shift_right)
"""
if image.ndim < 2:
raise ValueError("Input must have more than 1 dimension.")
image = np.atleast_3d(img_as_float(image))
ishape = np.array(image.shape)
bands = ishape[2]
if output_shape is None:
output_shape = ishape
coords = np.empty(np.r_[3, output_shape], dtype=float)
# Construct transformed coordinates
rows, cols = output_shape[:2]
tf_coords = np.indices((cols, rows), dtype=float).reshape(2, -1).T
tf_coords = coord_tf(tf_coords, **tf_args)
tf_coords = tf_coords.T.reshape((-1, cols, rows)).swapaxes(1, 2)
# y-coordinate mapping
_stackcopy(coords[1, ...], tf_coords[0, ...])
# x-coordinate mapping
_stackcopy(coords[0, ...], tf_coords[1, ...])
# colour-coordinate mapping
coords[2, ...] = range(bands)
# Prefilter not necessary for order 1 interpolation
prefilter = order > 1
mapped = ndimage.map_coordinates(image, coords, prefilter=prefilter,
mode=mode, order=order, cval=cval)
# The spline filters sometimes return results outside [0, 1],
# so clip to ensure valid data
return np.clip(mapped.squeeze(), 0, 1)
+3 -7
View File
@@ -4,18 +4,12 @@
import numpy as np
from scipy.ndimage import interpolation as ndii
from .warp import _stackcopy
__all__ = ['homography']
eps = np.finfo(float).eps
def _stackcopy(a, b):
"""a[:,:,0] = a[:,:,1] = ... = b"""
if a.ndim == 3:
a.transpose().swapaxes(1, 2)[:] = b
else:
a[:] = b
def homography(image, H, output_shape=None, order=1,
mode='constant', cval=0.):
"""Perform a projective transformation (homography) on an image.
@@ -106,6 +100,8 @@ def homography(image, H, output_shape=None, order=1,
coords = np.empty(np.r_[3, output_shape], dtype=float)
# TODO: Refactor this method to use transform.warp instead.
# Construct transformed coordinates
rows, cols = output_shape[:2]
rows, cols = np.mgrid[:rows, :cols]
+1 -1
View File
@@ -1,7 +1,7 @@
import numpy as np
from numpy.testing import assert_array_almost_equal
from skimage.transform.project import _stackcopy
from skimage.transform._warp import _stackcopy
from skimage.transform import homography, fast_homography
from skimage import data
from skimage.color import rgb2gray
+18
View File
@@ -0,0 +1,18 @@
import numpy as np
from numpy.testing import assert_array_almost_equal
from skimage import transform as tf, data, img_as_float
def test_roundtrip():
image = img_as_float(data.checkerboard())
swirl_params = {'radius': 80, 'rotation': 0, 'order': 2, 'mode': 'reflect'}
unswirled = tf.swirl(
tf.swirl(image, strength=10, **swirl_params),
strength=-10, **swirl_params
)
assert np.mean(np.abs(image - unswirled)) < 0.01
if __name__ == "__main__":
np.testing.run_module_suite()