Example #1
0
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]]])
Example #2
0
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]]])
Example #3
0
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)
Example #4
0
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)
Example #5
0
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
Example #6
0
    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))
Example #7
0
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
Example #8
0
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
Example #9
0
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
Example #10
0
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
Example #11
0
    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))
Example #12
0
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