def test_int_cast_possible(): testing.assert_equal(safe_as_int(7.1, atol=0.11), 7) testing.assert_equal(safe_as_int(-7.1, atol=0.11), -7) testing.assert_equal(safe_as_int(41.9, atol=0.11), 42) testing.assert_array_equal(safe_as_int([2, 42, 5789234.0, 87, 4]), np.r_[2, 42, 5789234, 87, 4]) testing.assert_array_equal( safe_as_int(np.r_[[[3, 4, 1.000000001], [7, 2, -8.999999999], [6, 9, -4234918347.]]]), np.r_[[[3, 4, 1], [7, 2, -9], [6, 9, -4234918347]]])
def test_int_cast_possible(): testing.assert_equal(safe_as_int(7.1, atol=0.11), 7) testing.assert_equal(safe_as_int(-7.1, atol=0.11), -7) testing.assert_equal(safe_as_int(41.9, atol=0.11), 42) testing.assert_array_equal(safe_as_int([2, 42, 5789234.0, 87, 4]), np.r_[2, 42, 5789234, 87, 4]) testing.assert_array_equal(safe_as_int(np.r_[[[3, 4, 1.000000001], [7, 2, -8.999999999], [6, 9, -4234918347.]]]), np.r_[[[3, 4, 1], [7, 2, -9], [6, 9, -4234918347]]])
def test_int_cast_not_possible(): with testing.raises(ValueError): safe_as_int(7.1) with testing.raises(ValueError): safe_as_int([7.1, 0.9]) with testing.raises(ValueError): safe_as_int(np.r_[7.1, 0.9]) with testing.raises(ValueError): safe_as_int((7.1, 0.9)) with testing.raises(ValueError): safe_as_int(((3, 4, 1), (2, 7.6, 289))) with testing.raises(ValueError): safe_as_int(7.1, 0.09) with testing.raises(ValueError): safe_as_int([7.1, 0.9], 0.09) with testing.raises(ValueError): safe_as_int(np.r_[7.1, 0.9], 0.09) with testing.raises(ValueError): safe_as_int((7.1, 0.9), 0.09) with testing.raises(ValueError): safe_as_int(((3, 4, 1), (2, 7.6, 289)), 0.25)
def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, mode='constant', cval=0., clip=True): """Warp an image according to a given coordinate transformation. Parameters ---------- image : ndarray Input image. inverse_map : transformation object, callable ``cr = f(cr, **kwargs)``, or ndarray Inverse coordinate map, which transforms coordinates in the output images into their corresponding coordinates in the input image. There are a number of different options to define this map, depending on the dimensionality of the input image. A 2-D image can have 2 dimensions for gray-scale images, or 3 dimensions with color information. - For 2-D images, you can directly pass a transformation object, e.g. `skimage.transform.SimilarityTransform`, or its inverse. - For 2-D images, you can pass a ``(3, 3)`` homogeneous transformation matrix, e.g. `skimage.transform.SimilarityTransform.params`. - For 2-D images, a function that transforms a ``(M, 2)`` array of ``(col, row)`` coordinates in the output image to their corresponding coordinates in the input image. Extra parameters to the function can be specified through `map_args`. - For N-D images, you can directly pass an array of coordinates. The first dimension specifies the coordinates in the input image, while the subsequent dimensions determine the position in the output image. E.g. in case of 2-D images, you need to pass an array of shape ``(2, rows, cols)``, where `rows` and `cols` determine the shape of the output image, and the first dimension contains the ``(row, col)`` coordinate in the input image. See `scipy.ndimage.map_coordinates` for further documentation. Note, that a ``(3, 3)`` matrix is interpreted as a homogeneous transformation matrix, so you cannot interpolate values from a 3-D input, if the output is of shape ``(3,)``. See example section for usage. map_args : dict, optional Keyword arguments passed to `inverse_map`. output_shape : tuple (rows, cols), optional Shape of the output image generated. By default the shape of the input image is preserved. Note that, even for multi-band images, only rows and columns need to be specified. order : int, optional The order of interpolation. The order has to be in the range 0-5: - 0: Nearest-neighbor - 1: Bi-linear (default) - 2: Bi-quadratic - 3: Bi-cubic - 4: Bi-quartic - 5: Bi-quintic mode : string, optional Points outside the boundaries of the input are filled according to the given mode ('constant', 'nearest', 'reflect' or 'wrap'). cval : float, optional Used in conjunction with mode 'constant', the value outside the image boundaries. clip : bool, optional Whether to clip the output to the float range of ``[0, 1]``, or ``[-1, 1]`` for input images with negative values. This is enabled by default, since higher order interpolation may produce values outside the given input range. Notes ----- In case of a `SimilarityTransform`, `AffineTransform` and `ProjectiveTransform` and `order` in [0, 3] this function uses the underlying transformation matrix to warp the image with a much faster routine. Examples -------- >>> from skimage.transform import warp >>> from skimage import data >>> image = data.camera() The following image warps are all equal but differ substantially in execution time. The image is shifted to the bottom. Use a geometric transform to warp an image (fast): >>> from skimage.transform import SimilarityTransform >>> tform = SimilarityTransform(translation=(0, -10)) >>> warped = warp(image, tform) Use a callable (slow): >>> def shift_down(xy): ... xy[:, 1] -= 10 ... return xy >>> warped = warp(image, shift_down) Use a transformation matrix to warp an image (fast): >>> matrix = np.array([[1, 0, 0], [0, 1, -10], [0, 0, 1]]) >>> warped = warp(image, matrix) >>> from skimage.transform import ProjectiveTransform >>> warped = warp(image, ProjectiveTransform(matrix=matrix)) You can also use the inverse of a geometric transformation (fast): >>> warped = warp(image, tform.inverse) For N-D images you can pass a coordinate array, that specifies the coordinates in the input image for every element in the output image. E.g. if you want to rescale a 3-D cube, you can do: >>> cube_shape = np.array([30, 30, 30]) >>> cube = np.random.rand(*cube_shape) Setup the coordinate array, that defines the scaling: >>> scale = 0.1 >>> output_shape = (scale * cube_shape).astype(int) >>> coords0, coords1, coords2 = np.mgrid[:output_shape[0], ... :output_shape[1], :output_shape[2]] >>> coords = np.array([coords0, coords1, coords2]) Assume that the cube contains spatial data, where the first array element center is at coordinate (0.5, 0.5, 0.5) in real space, i.e. we have to account for this extra offset when scaling the image: >>> coords = (coords + 0.5) / scale - 0.5 >>> warped = warp(cube, coords) """ image = img_as_float(image) input_shape = np.array(image.shape) if output_shape is None: output_shape = input_shape else: output_shape = safe_as_int(output_shape) out = None if order == 2: # When fixing this issue, make sure to fix the branches further # below in this function warnings.warn("Bi-quadratic interpolation behavior has changed due " "to a bug in the implementation of scikit-image. " "The new version now serves as a wrapper " "around SciPy's interpolation functions, which itself " "is not verified to be a correct implementation. Until " "skimage's implementation is fixed, we recommend " "to use bi-linear or bi-cubic interpolation instead.") if order in (0, 1, 3) and not map_args: # use fast Cython version for specific interpolation orders and input matrix = None if isinstance(inverse_map, np.ndarray) and inverse_map.shape == (3, 3): # inverse_map is a transformation matrix as numpy array matrix = inverse_map elif isinstance(inverse_map, HOMOGRAPHY_TRANSFORMS): # inverse_map is a homography matrix = inverse_map.params elif (hasattr(inverse_map, '__name__') and inverse_map.__name__ == 'inverse' and get_bound_method_class(inverse_map) \ in HOMOGRAPHY_TRANSFORMS): # inverse_map is the inverse of a homography matrix = np.linalg.inv(six.get_method_self(inverse_map).params) if matrix is not None: matrix = matrix.astype(np.double) if image.ndim == 2: out = _warp_fast(image, matrix, output_shape=output_shape, order=order, mode=mode, cval=cval) elif image.ndim == 3: dims = [] for dim in range(image.shape[2]): dims.append(_warp_fast(image[..., dim], matrix, output_shape=output_shape, order=order, mode=mode, cval=cval)) out = np.dstack(dims) if out is None: # use ndimage.map_coordinates if (isinstance(inverse_map, np.ndarray) and inverse_map.shape == (3, 3)): # inverse_map is a transformation matrix as numpy array, # this is only used for order >= 4. inverse_map = ProjectiveTransform(matrix=inverse_map) if isinstance(inverse_map, np.ndarray): # inverse_map is directly given as coordinates coords = inverse_map else: # inverse_map is given as function, that transforms (N, 2) # destination coordinates to their corresponding source # coordinates. This is only supported for 2(+1)-D images. if image.ndim < 2 or image.ndim > 3: raise ValueError("Only 2-D images (grayscale or color) are " "supported, when providing a callable " "`inverse_map`.") def coord_map(*args): return inverse_map(*args, **map_args) if len(input_shape) == 3 and len(output_shape) == 2: # Input image is 2D and has color channel, but output_shape is # given for 2-D images. Automatically add the color channel # dimensionality. output_shape = (output_shape[0], output_shape[1], input_shape[2]) coords = warp_coords(coord_map, output_shape) # Pre-filtering not necessary for order 0, 1 interpolation prefilter = order > 1 out = ndimage.map_coordinates(image, coords, prefilter=prefilter, mode=mode, order=order, cval=cval) if clip: # The spline filters sometimes return results outside [0, 1], # so clip to ensure valid data if np.min(image) < 0: min_val = -1 else: min_val = 0 max_val = 1 clipped = np.clip(out, min_val, max_val) if mode == 'constant' and not (0 <= cval <= 1): clipped[out == cval] = cval out = clipped return out
def estimate(self, src, dst, order=2): """Set the transformation matrix with the explicit transformation parameters. You can determine the over-, well- and under-determined parameters with the total least-squares method. Number of source and destination coordinates must match. The transformation is defined as:: X = sum[j=0:order]( sum[i=0:j]( a_ji * x**(j - i) * y**i )) Y = sum[j=0:order]( sum[i=0:j]( b_ji * x**(j - i) * y**i )) These equations can be transformed to the following form:: 0 = sum[j=0:order]( sum[i=0:j]( a_ji * x**(j - i) * y**i )) - X 0 = sum[j=0:order]( sum[i=0:j]( b_ji * x**(j - i) * y**i )) - Y which exist for each set of corresponding points, so we have a set of N * 2 equations. The coefficients appear linearly so we can write A x = 0, where:: A = [[1 x y x**2 x*y y**2 ... 0 ... 0 -X] [0 ... 0 1 x y x**2 x*y y**2 -Y] ... ... ] x.T = [a00 a10 a11 a20 a21 a22 ... ann b00 b10 b11 b20 b21 b22 ... bnn c3] In case of total least-squares the solution of this homogeneous system of equations is the right singular vector of A which corresponds to the smallest singular value normed by the coefficient c3. Parameters ---------- src : (N, 2) array Source coordinates. dst : (N, 2) array Destination coordinates. order : int, optional Polynomial order (number of coefficients is order + 1). """ xs = src[:, 0] ys = src[:, 1] xd = dst[:, 0] yd = dst[:, 1] rows = src.shape[0] # number of unknown polynomial coefficients order = safe_as_int(order) u = (order + 1) * (order + 2) A = np.zeros((rows * 2, u + 1)) pidx = 0 for j in range(order + 1): for i in range(j + 1): A[:rows, pidx] = xs ** (j - i) * ys ** i A[rows:, pidx + u // 2] = xs ** (j - i) * ys ** i pidx += 1 A[:rows, -1] = xd A[rows:, -1] = yd _, _, V = np.linalg.svd(A) # solution is right singular vector that corresponds to smallest # singular value params = - V[-1, :-1] / V[-1, -1] self.params = params.reshape((2, u // 2))
def warp_coords(coord_map, shape, dtype=np.float64): """Build the source coordinates for the output of a 2-D image warp. Parameters ---------- coord_map : callable like GeometricTransform.inverse Return input coordinates for given output coordinates. Coordinates are in the shape (P, 2), where P is the number of coordinates and each element is a ``(row, col)`` pair. shape : tuple Shape of output image ``(rows, cols[, bands])``. dtype : np.dtype or string dtype for return value (sane choices: float32 or float64). Returns ------- coords : (ndim, rows, cols[, bands]) array of dtype `dtype` Coordinates for `scipy.ndimage.map_coordinates`, that will yield an image of shape (orows, ocols, bands) by drawing from source points according to the `coord_transform_fn`. Notes ----- This is a lower-level routine that produces the source coordinates for 2-D images used by `warp()`. It is provided separately from `warp` to give additional flexibility to users who would like, for example, to re-use a particular coordinate mapping, to use specific dtypes at various points along the the image-warping process, or to implement different post-processing logic than `warp` performs after the call to `ndimage.map_coordinates`. Examples -------- Produce a coordinate map that shifts an image up and to the right: >>> from skimage import data >>> from scipy.ndimage import map_coordinates >>> >>> def shift_up10_left20(xy): ... return xy - np.array([-20, 10])[None, :] >>> >>> image = data.astronaut().astype(np.float32) >>> coords = warp_coords(shift_up10_left20, image.shape) >>> warped_image = map_coordinates(image, coords) """ shape = safe_as_int(shape) rows, cols = shape[0], shape[1] coords_shape = [len(shape), rows, cols] if len(shape) == 3: coords_shape.append(shape[2]) coords = np.empty(coords_shape, dtype=dtype) # Reshape grid coordinates into a (P, 2) array of (row, col) pairs tf_coords = np.indices((cols, rows), dtype=dtype).reshape(2, -1).T # Map each (row, col) pair to the source image according to # the user-provided mapping tf_coords = coord_map(tf_coords) # 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, ...]) if len(shape) == 3: coords[2, ...] = range(shape[2]) return coords
def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, mode='constant', cval=0., reverse_map=None): """Warp an image according to a given coordinate transformation. Parameters ---------- image : 2-D or 3-D array Input image. inverse_map : transformation object, callable ``xy = f(xy, **kwargs)``, (3, 3) array Inverse coordinate map. A function that transforms a (N, 2) array of ``(x, y)`` coordinates in the *output image* into their corresponding coordinates in the *source image* (e.g. a transformation object or its inverse). See example section for usage. map_args : dict, optional Keyword arguments passed to `inverse_map`. output_shape : tuple (rows, cols), optional Shape of the output image generated. By default the shape of the input image is preserved. Note that, even for multi-band images, only rows and columns need to be specified. order : int, optional The order of interpolation. The order has to be in the range 0-5: * 0: Nearest-neighbor * 1: Bi-linear (default) * 2: Bi-quadratic * 3: Bi-cubic * 4: Bi-quartic * 5: Bi-quintic mode : string, optional Points outside the boundaries of the input are filled according to the given mode ('constant', 'nearest', 'reflect' or 'wrap'). cval : float, optional Used in conjunction with mode 'constant', the value outside the image boundaries. Notes ----- In case of a `SimilarityTransform`, `AffineTransform` and `ProjectiveTransform` and `order` in [0, 3] this function uses the underlying transformation matrix to warp the image with a much faster routine. Examples -------- >>> from skimage.transform import warp >>> from skimage import data >>> image = data.camera() The following image warps are all equal but differ substantially in execution time. The image is shifted to the bottom. Use a geometric transform to warp an image (fast): >>> from skimage.transform import SimilarityTransform >>> tform = SimilarityTransform(translation=(0, -10)) >>> warped = warp(image, tform) Use a callable (slow): >>> def shift_down(xy): ... xy[:, 1] -= 10 ... return xy >>> warped = warp(image, shift_down) Use a transformation matrix to warp an image (fast): >>> matrix = np.array([[1, 0, 0], [0, 1, -10], [0, 0, 1]]) >>> warped = warp(image, matrix) >>> from skimage.transform import ProjectiveTransform >>> warped = warp(image, ProjectiveTransform(matrix=matrix)) You can also use the inverse of a geometric transformation (fast): >>> warped = warp(image, tform.inverse) """ # Backward API compatibility if reverse_map is not None: warnings.warn('`reverse_map` parameter is deprecated and replaced by ' 'the `inverse_map` parameter.') inverse_map = reverse_map if image.ndim < 2 or image.ndim > 3: raise ValueError("Input must have 2 or 3 dimensions.") orig_ndim = image.ndim image = np.atleast_3d(img_as_float(image)) ishape = np.array(image.shape) bands = ishape[2] if output_shape is None: output_shape = ishape else: output_shape = safe_as_int(output_shape) out = None # use fast Cython version for specific interpolation orders and input if order in range(4) and not map_args: matrix = None # inverse_map is a transformation matrix as numpy array if isinstance(inverse_map, np.ndarray) and inverse_map.shape == (3, 3): matrix = inverse_map # inverse_map is a homography elif isinstance(inverse_map, HOMOGRAPHY_TRANSFORMS): matrix = inverse_map.params # inverse_map is the inverse of a homography elif (hasattr(inverse_map, '__name__') and inverse_map.__name__ == 'inverse' and get_bound_method_class(inverse_map) \ in HOMOGRAPHY_TRANSFORMS): matrix = np.linalg.inv(six.get_method_self(inverse_map).params) if matrix is not None: matrix = matrix.astype(np.double) # transform all bands dims = [] for dim in range(image.shape[2]): dims.append(_warp_fast(image[..., dim], matrix, output_shape=output_shape, order=order, mode=mode, cval=cval)) out = np.dstack(dims) if orig_ndim == 2: out = out[..., 0] if out is None: # use ndimage.map_coordinates rows, cols = output_shape[:2] # inverse_map is a transformation matrix as numpy array if isinstance(inverse_map, np.ndarray) and inverse_map.shape == (3, 3): inverse_map = ProjectiveTransform(matrix=inverse_map) def coord_map(*args): return inverse_map(*args, **map_args) coords = warp_coords(coord_map, (rows, cols, bands)) # Pre-filtering not necessary for order 0, 1 interpolation prefilter = order > 1 out = 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 clipped = np.clip(out, 0, 1) if mode == 'constant' and not (0 <= cval <= 1): clipped[out == cval] = cval out = clipped if out.ndim == 3 and orig_ndim == 2: # remove singleton dimension introduced by atleast_3d return out[..., 0] else: return out
def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, mode='constant', cval=0., reverse_map=None): """Warp an image according to a given coordinate transformation. Parameters ---------- image : 2-D or 3-D array Input image. inverse_map : transformation object, callable ``xy = f(xy, **kwargs)``, (3, 3) array Inverse coordinate map. A function that transforms a (N, 2) array of ``(x, y)`` coordinates in the *output image* into their corresponding coordinates in the *source image* (e.g. a transformation object or its inverse). See example section for usage. map_args : dict, optional Keyword arguments passed to `inverse_map`. output_shape : tuple (rows, cols), optional Shape of the output image generated. By default the shape of the input image is preserved. Note that, even for multi-band images, only rows and columns need to be specified. order : int, optional The order of interpolation. The order has to be in the range 0-5: * 0: Nearest-neighbor * 1: Bi-linear (default) * 2: Bi-quadratic * 3: Bi-cubic * 4: Bi-quartic * 5: Bi-quintic mode : string, optional Points outside the boundaries of the input are filled according to the given mode ('constant', 'nearest', 'reflect' or 'wrap'). cval : float, optional Used in conjunction with mode 'constant', the value outside the image boundaries. Notes ----- In case of a `SimilarityTransform`, `AffineTransform` and `ProjectiveTransform` and `order` in [0, 3] this function uses the underlying transformation matrix to warp the image with a much faster routine. Examples -------- >>> from skimage.transform import warp >>> from skimage import data >>> image = data.camera() The following image warps are all equal but differ substantially in execution time. The image is shifted to the bottom. Use a geometric transform to warp an image (fast): >>> from skimage.transform import SimilarityTransform >>> tform = SimilarityTransform(translation=(0, -10)) >>> warped = warp(image, tform) Use a callable (slow): >>> def shift_down(xy): ... xy[:, 1] -= 10 ... return xy >>> warped = warp(image, shift_down) Use a transformation matrix to warp an image (fast): >>> matrix = np.array([[1, 0, 0], [0, 1, -10], [0, 0, 1]]) >>> warped = warp(image, matrix) >>> from skimage.transform import ProjectiveTransform >>> warped = warp(image, ProjectiveTransform(matrix=matrix)) You can also use the inverse of a geometric transformation (fast): >>> warped = warp(image, tform.inverse) """ # Backward API compatibility if reverse_map is not None: warnings.warn('`reverse_map` parameter is deprecated and replaced by ' 'the `inverse_map` parameter.') inverse_map = reverse_map if image.ndim < 2 or image.ndim > 3: raise ValueError("Input must have 2 or 3 dimensions.") orig_ndim = image.ndim image = np.atleast_3d(img_as_float(image)) ishape = np.array(image.shape) bands = ishape[2] if output_shape is None: output_shape = ishape else: output_shape = safe_as_int(output_shape) out = None # use fast Cython version for specific interpolation orders and input if order in range(4) and not map_args: matrix = None # inverse_map is a transformation matrix as numpy array if isinstance(inverse_map, np.ndarray) and inverse_map.shape == (3, 3): matrix = inverse_map # inverse_map is a homography elif isinstance(inverse_map, HOMOGRAPHY_TRANSFORMS): matrix = inverse_map.params # inverse_map is the inverse of a homography elif (hasattr(inverse_map, '__name__') and inverse_map.__name__ == 'inverse' and get_bound_method_class(inverse_map) \ in HOMOGRAPHY_TRANSFORMS): matrix = np.linalg.inv(six.get_method_self(inverse_map).params) if matrix is not None: matrix = matrix.astype(np.double) # transform all bands dims = [] for dim in range(image.shape[2]): dims.append( _warp_fast(image[..., dim], matrix, output_shape=output_shape, order=order, mode=mode, cval=cval)) out = np.dstack(dims) if orig_ndim == 2: out = out[..., 0] if out is None: # use ndimage.map_coordinates rows, cols = output_shape[:2] # inverse_map is a transformation matrix as numpy array if isinstance(inverse_map, np.ndarray) and inverse_map.shape == (3, 3): inverse_map = ProjectiveTransform(matrix=inverse_map) def coord_map(*args): return inverse_map(*args, **map_args) coords = warp_coords(coord_map, (rows, cols, bands)) # Pre-filtering not necessary for order 0, 1 interpolation prefilter = order > 1 out = 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 clipped = np.clip(out, 0, 1) if mode == 'constant' and not (0 <= cval <= 1): clipped[out == cval] = cval out = clipped if out.ndim == 3 and orig_ndim == 2: # remove singleton dimension introduced by atleast_3d return out[..., 0] else: return out
def warp_coords(coord_map, shape, dtype=np.float64): """Build the source coordinates for the output pixels of an image warp. Parameters ---------- coord_map : callable like GeometricTransform.inverse Return input coordinates for given output coordinates. Coordinates are in the shape (P, 2), where P is the number of coordinates and each element is a ``(x, y)`` pair. shape : tuple Shape of output image ``(rows, cols[, bands])``. dtype : np.dtype or string dtype for return value (sane choices: float32 or float64). Returns ------- coords : (ndim, rows, cols[, bands]) array of dtype `dtype` Coordinates for `scipy.ndimage.map_coordinates`, that will yield an image of shape (orows, ocols, bands) by drawing from source points according to the `coord_transform_fn`. Notes ----- This is a lower-level routine that produces the source coordinates used by `warp()`. It is provided separately from `warp` to give additional flexibility to users who would like, for example, to re-use a particular coordinate mapping, to use specific dtypes at various points along the the image-warping process, or to implement different post-processing logic than `warp` performs after the call to `ndimage.map_coordinates`. Examples -------- Produce a coordinate map that Shifts an image up and to the right: >>> from skimage import data >>> from scipy.ndimage import map_coordinates >>> >>> def shift_up10_left20(xy): ... return xy - np.array([-20, 10])[None, :] >>> >>> image = data.lena().astype(np.float32) >>> coords = warp_coords(shift_up10_left20, image.shape) >>> warped_image = map_coordinates(image, coords) """ shape = safe_as_int(shape) rows, cols = shape[0], shape[1] coords_shape = [len(shape), rows, cols] if len(shape) == 3: coords_shape.append(shape[2]) coords = np.empty(coords_shape, dtype=dtype) # Reshape grid coordinates into a (P, 2) array of (x, y) pairs tf_coords = np.indices((cols, rows), dtype=dtype).reshape(2, -1).T # Map each (x, y) pair to the source image according to # the user-provided mapping tf_coords = coord_map(tf_coords) # 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, ...]) if len(shape) == 3: coords[2, ...] = range(shape[2]) return coords
def estimate(self, src, dst, order=2): """Set the transformation matrix with the explicit transformation parameters. You can determine the over-, well- and under-determined parameters with the total least-squares method. Number of source and destination coordinates must match. The transformation is defined as:: X = sum[j=0:order]( sum[i=0:j]( a_ji * x**(j - i) * y**i )) Y = sum[j=0:order]( sum[i=0:j]( b_ji * x**(j - i) * y**i )) These equations can be transformed to the following form:: 0 = sum[j=0:order]( sum[i=0:j]( a_ji * x**(j - i) * y**i )) - X 0 = sum[j=0:order]( sum[i=0:j]( b_ji * x**(j - i) * y**i )) - Y which exist for each set of corresponding points, so we have a set of N * 2 equations. The coefficients appear linearly so we can write A x = 0, where:: A = [[1 x y x**2 x*y y**2 ... 0 ... 0 -X] [0 ... 0 1 x y x**2 x*y y**2 -Y] ... ... ] x.T = [a00 a10 a11 a20 a21 a22 ... ann b00 b10 b11 b20 b21 b22 ... bnn c3] In case of total least-squares the solution of this homogeneous system of equations is the right singular vector of A which corresponds to the smallest singular value normed by the coefficient c3. Parameters ---------- src : (N, 2) array Source coordinates. dst : (N, 2) array Destination coordinates. order : int, optional Polynomial order (number of coefficients is order + 1). """ xs = src[:, 0] ys = src[:, 1] xd = dst[:, 0] yd = dst[:, 1] rows = src.shape[0] # number of unknown polynomial coefficients order = safe_as_int(order) u = (order + 1) * (order + 2) A = np.zeros((rows * 2, u + 1)) pidx = 0 for j in range(order + 1): for i in range(j + 1): A[:rows, pidx] = xs**(j - i) * ys**i A[rows:, pidx + u // 2] = xs**(j - i) * ys**i pidx += 1 A[:rows, -1] = xd A[rows:, -1] = yd _, _, V = np.linalg.svd(A) # solution is right singular vector that corresponds to smallest # singular value params = -V[-1, :-1] / V[-1, -1] self.params = params.reshape((2, u // 2))
def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, mode='constant', cval=0., clip=True): """Warp an image according to a given coordinate transformation. Parameters ---------- image : ndarray Input image. inverse_map : transformation object, callable ``cr = f(cr, **kwargs)``, or ndarray Inverse coordinate map, which transforms coordinates in the output images into their corresponding coordinates in the input image. There are a number of different options to define this map, depending on the dimensionality of the input image. A 2-D image can have 2 dimensions for gray-scale images, or 3 dimensions with color information. - For 2-D images, you can directly pass a transformation object, e.g. `skimage.transform.SimilarityTransform`, or its inverse. - For 2-D images, you can pass a ``(3, 3)`` homogeneous transformation matrix, e.g. `skimage.transform.SimilarityTransform.params`. - For 2-D images, a function that transforms a ``(M, 2)`` array of ``(col, row)`` coordinates in the output image to their corresponding coordinates in the input image. Extra parameters to the function can be specified through `map_args`. - For N-D images, you can directly pass an array of coordinates. The first dimension specifies the coordinates in the input image, while the subsequent dimensions determine the position in the output image. E.g. in case of 2-D images, you need to pass an array of shape ``(2, rows, cols)``, where `rows` and `cols` determine the shape of the output image, and the first dimension contains the ``(row, col)`` coordinate in the input image. See `scipy.ndimage.map_coordinates` for further documentation. Note, that a ``(3, 3)`` matrix is interpreted as a homogeneous transformation matrix, so you cannot interpolate values from a 3-D input, if the output is of shape ``(3,)``. See example section for usage. map_args : dict, optional Keyword arguments passed to `inverse_map`. output_shape : tuple (rows, cols), optional Shape of the output image generated. By default the shape of the input image is preserved. Note that, even for multi-band images, only rows and columns need to be specified. order : int, optional The order of interpolation. The order has to be in the range 0-5: - 0: Nearest-neighbor - 1: Bi-linear (default) - 2: Bi-quadratic - 3: Bi-cubic - 4: Bi-quartic - 5: Bi-quintic mode : string, optional Points outside the boundaries of the input are filled according to the given mode ('constant', 'nearest', 'reflect' or 'wrap'). cval : float, optional Used in conjunction with mode 'constant', the value outside the image boundaries. clip : bool, optional Whether to clip the output to the float range of ``[0, 1]``, or ``[-1, 1]`` for input images with negative values. This is enabled by default, since higher order interpolation may produce values outside the given input range. Notes ----- In case of a `SimilarityTransform`, `AffineTransform` and `ProjectiveTransform` and `order` in [0, 3] this function uses the underlying transformation matrix to warp the image with a much faster routine. Examples -------- >>> from skimage.transform import warp >>> from skimage import data >>> image = data.camera() The following image warps are all equal but differ substantially in execution time. The image is shifted to the bottom. Use a geometric transform to warp an image (fast): >>> from skimage.transform import SimilarityTransform >>> tform = SimilarityTransform(translation=(0, -10)) >>> warped = warp(image, tform) Use a callable (slow): >>> def shift_down(xy): ... xy[:, 1] -= 10 ... return xy >>> warped = warp(image, shift_down) Use a transformation matrix to warp an image (fast): >>> matrix = np.array([[1, 0, 0], [0, 1, -10], [0, 0, 1]]) >>> warped = warp(image, matrix) >>> from skimage.transform import ProjectiveTransform >>> warped = warp(image, ProjectiveTransform(matrix=matrix)) You can also use the inverse of a geometric transformation (fast): >>> warped = warp(image, tform.inverse) For N-D images you can pass a coordinate array, that specifies the coordinates in the input image for every element in the output image. E.g. if you want to rescale a 3-D cube, you can do: >>> cube_shape = np.array([30, 30, 30]) >>> cube = np.random.rand(*cube_shape) Setup the coordinate array, that defines the scaling: >>> scale = 0.1 >>> output_shape = (scale * cube_shape).astype(int) >>> coords0, coords1, coords2 = \ ... np.mgrid[:output_shape[0], :output_shape[1], :output_shape[2]] >>> coords = np.array([coords0, coords1, coords2]) Assume that the cube contains spatial data, where the first array element center is at coordinate (0.5, 0.5, 0.5) in real space, i.e. we have to account for this extra offset when scaling the image: >>> coords = (coords + 0.5) / scale - 0.5 >>> warped = warp(cube, coords) """ image = img_as_float(image) input_shape = np.array(image.shape) if output_shape is None: output_shape = input_shape else: output_shape = safe_as_int(output_shape) out = None if order in range(4) and not map_args: # use fast Cython version for specific interpolation orders and input matrix = None if isinstance(inverse_map, np.ndarray) and inverse_map.shape == (3, 3): # inverse_map is a transformation matrix as numpy array matrix = inverse_map elif isinstance(inverse_map, HOMOGRAPHY_TRANSFORMS): # inverse_map is a homography matrix = inverse_map.params elif (hasattr(inverse_map, '__name__') and inverse_map.__name__ == 'inverse' and get_bound_method_class(inverse_map) \ in HOMOGRAPHY_TRANSFORMS): # inverse_map is the inverse of a homography matrix = np.linalg.inv(six.get_method_self(inverse_map).params) if matrix is not None: matrix = matrix.astype(np.double) if image.ndim == 2: out = _warp_fast(image, matrix, output_shape=output_shape, order=order, mode=mode, cval=cval) elif image.ndim == 3: dims = [] for dim in range(image.shape[2]): dims.append( _warp_fast(image[..., dim], matrix, output_shape=output_shape, order=order, mode=mode, cval=cval)) out = np.dstack(dims) if out is None: # use ndimage.map_coordinates if (isinstance(inverse_map, np.ndarray) and inverse_map.shape == (3, 3)): # inverse_map is a transformation matrix as numpy array, # this is only used for order >= 4. inverse_map = ProjectiveTransform(matrix=inverse_map) if isinstance(inverse_map, np.ndarray): # inverse_map is directly given as coordinates coords = inverse_map else: # inverse_map is given as function, that transforms (N, 2) # destination coordinates to their corresponding source # coordinates. This is only supported for 2(+1)-D images. if image.ndim < 2 or image.ndim > 3: raise ValueError("Only 2-D images (grayscale or color) are " "supported, when providing a callable " "`inverse_map`.") def coord_map(*args): return inverse_map(*args, **map_args) if len(input_shape) == 3 and len(output_shape) == 2: # Input image is 2D and has color channel, but output_shape is # given for 2-D images. Automatically add the color channel # dimensionality. output_shape = (output_shape[0], output_shape[1], input_shape[2]) coords = warp_coords(coord_map, output_shape) # Pre-filtering not necessary for order 0, 1 interpolation prefilter = order > 1 out = ndimage.map_coordinates(image, coords, prefilter=prefilter, mode=mode, order=order, cval=cval) if clip: # The spline filters sometimes return results outside [0, 1], # so clip to ensure valid data if np.min(image) < 0: min_val = -1 else: min_val = 0 max_val = 1 clipped = np.clip(out, min_val, max_val) if mode == 'constant' and not (0 <= cval <= 1): clipped[out == cval] = cval out = clipped return out