"""Image projection. """ import numpy as np from scipy.ndimage import interpolation as ndii __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. For each pixel, given its homogeneous coordinate :math:`\mathbf{x} = [x, y, 1]^T`, its target position is calculated by multiplying with the given matrix, :math:`H`, to give :math:`H \mathbf{x}`. E.g., to rotate by theta degrees clockwise, the matrix should be :: [[cos(theta) -sin(theta) 0] [sin(theta) cos(theta) 0] [0 0 1]] or, to translate x by 10 and y by 20, :: [[1 0 10] [0 1 20] [0 0 1 ]]. Parameters ---------- image : 2-D array Input image. H : array of shape ``(3, 3)`` Transformation matrix H that defines the homography. 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. """ if image.ndim < 2: raise ValueError("Input must have more than 1 dimension.") image = np.atleast_3d(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] rows, cols = np.mgrid[:rows, :cols] tf_coords = np.empty(shape=cols.shape, dtype=[('cols', float), ('rows', float), ('z', float)]) tf_coords['cols'], tf_coords['rows'] = cols, rows tf_coords['z'] = 1 tf_coords = tf_coords.view((float, 3)) tf_coords = np.dot(tf_coords, np.linalg.inv(H).transpose()) tf_coords[np.absolute(tf_coords) < eps] = 0. # normalize coordinates tf_coords[..., :2] /= tf_coords[..., 2, np.newaxis] # y-coordinate mapping _stackcopy(coords[0,...], tf_coords[...,1]) # x-coordinate mapping _stackcopy(coords[1,...], tf_coords[...,0]) # colour-coordinate mapping coords[2,...] = range(bands) # Prefilter not necessary for order 1 interpolation prefilter = order > 1 mapped = ndii.map_coordinates(image, coords, prefilter=prefilter, mode=mode, order=order, cval=cval) return mapped.squeeze()