__all__ = ['warp'] import numpy as np from scipy import ndimage from skimage.util import img_as_float def _stackcopy(a, b): """Copy b into each color layer of a, such that:: a[:,:,0] = a[:,:,1] = ... = b Parameters ---------- a : (M, N) or (M, N, P) ndarray Target array. b : (M, N) Source array. Notes ----- Color images are stored as an ``MxNx3`` or ``MxNx4`` arrays. """ if a.ndim == 3: a[:] = b[:, :, np.newaxis] else: a[:] = b def warp(image, reverse_map, map_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. reverse_map : callable xy = f(xy, **kwargs) Reverse coordinate map. A function that transforms a Px2 array of ``(x, y)`` coordinates in the *output image* into their corresponding coordinates in the *source image*. Also see examples below. map_args : dict, optional Keyword arguments passed to `reverse_map`. output_shape : tuple (rows, cols) Shape of the output image generated. order : int Order of splines used in interpolation. See `scipy.ndimage.map_coordinates` for detail. mode : string How to handle values outside the image borders. See `scipy.ndimage.map_coordinates` for detail. 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] # Reshape grid coordinates into a (P, 2) array of (x, y) pairs tf_coords = np.indices((cols, rows), dtype=float).reshape(2, -1).T # Map each (x, y) pair to the source image according to # the user-provided mapping tf_coords = reverse_map(tf_coords, **map_args) # Reshape back to a (2, M, N) coordinate grid tf_coords = tf_coords.T.reshape((-1, cols, rows)).swapaxes(1, 2) # Place the y-coordinate mapping _stackcopy(coords[1, ...], tf_coords[0, ...]) # Place the 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)