Ejemplo n.º 1
0
def _clahe(image, kernel_size, clip_limit, nbins):
    """Contrast Limited Adaptive Histogram Equalization.

    Parameters
    ----------
    image : (N1,...,NN) ndarray
        Input image.
    kernel_size: int or N-tuple of int
        Defines the shape of contextual regions used in the algorithm.
    clip_limit : float
        Normalized clipping limit between 0 and 1 (higher values give more
        contrast).
    nbins : int
        Number of gray bins for histogram ("data range").

    Returns
    -------
    out : (N1,...,NN) ndarray
        Equalized image.

    The number of "effective" graylevels in the output image is set by `nbins`;
    selecting a small value (e.g. 128) speeds up processing and still produces
    an output image of good quality. A clip limit of 0 or larger than or equal
    to 1 results in standard (non-contrast limited) AHE.
    """
    ndim = image.ndim
    dtype = image.dtype

    # pad the image such that the shape in each dimension
    # - is a multiple of the kernel_size and
    # - is preceded by half a kernel size
    pad_start_per_dim = [k // 2 for k in kernel_size]

    pad_end_per_dim = [
        (k - s % k) % k + math.ceil(k / 2.0)
        for k, s in zip(kernel_size, image.shape)
    ]

    image = cp.pad(
        image,
        [(p_i, p_f) for p_i, p_f in zip(pad_start_per_dim, pad_end_per_dim)],
        mode="reflect",
    )

    bin_size = 1 + NR_OF_GRAY // nbins
    if True:
        lut = cp.arange(NR_OF_GRAY)
        lut //= bin_size

        image = lut[image]
    else:
        lut = np.arange(NR_OF_GRAY)
        lut //= bin_size
        image = cp.asarray(lut[image.get()])

    # calculate graylevel mappings for each contextual region
    # rearrange image into flattened contextual regions
    ns_hist = [int(s / k) - 1 for s, k in zip(image.shape, kernel_size)]
    hist_blocks_shape = functools.reduce(
        operator.add, [(s, k) for s, k in zip(ns_hist, kernel_size)]
    )
    hist_blocks_axis_order = tuple(range(0, ndim * 2, 2)) + tuple(
        range(1, ndim * 2, 2)
    )
    hist_slices = [
        slice(k // 2, k // 2 + n * k) for k, n in zip(kernel_size, ns_hist)
    ]
    hist_blocks = image[tuple(hist_slices)].reshape(hist_blocks_shape)
    hist_blocks = hist_blocks.transpose(hist_blocks_axis_order)
    hist_block_assembled_shape = hist_blocks.shape
    hist_blocks = hist_blocks.reshape((_prod(ns_hist), -1))

    # Calculate actual clip limit
    if clip_limit > 0.0:
        clim = int(max(clip_limit * _prod(kernel_size), 1))
    else:
        # largest possible value, i.e., do not clip (AHE)
        clim = np.product(kernel_size)

    if True:
        # faster to loop over the arrays on the host
        hist_blocks = cp.asnumpy(hist_blocks)
        hist = np.apply_along_axis(
            np.bincount, -1, hist_blocks, minlength=nbins
        )
        hist = np.apply_along_axis(
            clip_histogram, -1, hist, clip_limit=clim, xp=np
        )
        hist = cp.asarray(hist)
    else:
        hist = cnp.apply_along_axis(
            cp.bincount, -1, hist_blocks, minlength=nbins
        )
        hist = cnp.apply_along_axis(clip_histogram, -1, hist, clip_limit=clim)
    hist = map_histogram(hist, 0, NR_OF_GRAY - 1, _prod(kernel_size))
    hist = hist.reshape(hist_block_assembled_shape[:ndim] + (-1,))

    # duplicate leading mappings in each dim
    map_array = cp.pad(
        hist, [(1, 1) for _ in range(ndim)] + [(0, 0)], mode="edge"
    )

    # Perform multilinear interpolation of graylevel mappings
    # using the convention described here:
    # https://en.wikipedia.org/w/index.php?title=Adaptive_histogram_
    # equalization&oldid=936814673#Efficient_computation_by_interpolation

    # rearrange image into blocks for vectorized processing
    ns_proc = [int(s / k) for s, k in zip(image.shape, kernel_size)]
    blocks_shape = functools.reduce(
        operator.add, [(s, k) for s, k in zip(ns_proc, kernel_size)]
    )
    blocks_axis_order = hist_blocks_axis_order

    blocks = image.reshape(blocks_shape)
    blocks = blocks.transpose(blocks_axis_order)
    blocks_flattened_shape = blocks.shape
    blocks = blocks.reshape((_prod(ns_proc), _prod(blocks.shape[ndim:])))

    # calculate interpolation coefficients
    coeffs = cp.meshgrid(
        *tuple([cp.arange(k) / k for k in kernel_size[::-1]]), indexing="ij"
    )
    coeffs = [cp.transpose(c).flatten() for c in coeffs]
    inv_coeffs = [1 - c for c in coeffs]

    # sum over contributions of neighboring contextual
    # regions in each direction
    result = cp.zeros(blocks.shape, dtype=cp.float32)
    for iedge, edge in enumerate(itertools.product(*((range(2),) * ndim))):
        edge_maps = map_array[
            tuple([slice(e, e + n) for e, n in zip(edge, ns_proc)])
        ]
        edge_maps = edge_maps.reshape((_prod(ns_proc), -1))
        # apply map
        # edge_mapped = cp.asarray(np.take_along_axis(edge_maps.get(), blocks.get(), axis=-1))
        edge_mapped = cp.take_along_axis(edge_maps, blocks, axis=-1)

        # interpolate
        edge_coeffs = functools.reduce(
            operator.mul,
            [[inv_coeffs, coeffs][e][d] for d, e in enumerate(edge[::-1])],
        )

        result += (edge_mapped * edge_coeffs).astype(result.dtype)

    result = result.astype(dtype)

    # rebuild result image from blocks
    result = result.reshape(blocks_flattened_shape)
    blocks_axis_rebuild_order = functools.reduce(
        operator.add,
        [(s, k) for s, k in zip(range(0, ndim), range(ndim, ndim * 2))],
    )
    result = result.transpose(blocks_axis_rebuild_order)
    result = result.reshape(image.shape)

    # undo padding
    unpad_slices = tuple(
        [
            slice(p_i, s - p_f)
            for p_i, p_f, s in zip(
                pad_start_per_dim, pad_end_per_dim, image.shape
            )
        ]
    )
    result = result[unpad_slices]

    return result
Ejemplo n.º 2
0
def match_template(image,
                   template,
                   pad_input=False,
                   mode="constant",
                   constant_values=0):
    """Match a template to a 2-D or 3-D image using normalized correlation.

    The output is an array with values between -1.0 and 1.0. The value at a
    given position corresponds to the correlation coefficient between the image
    and the template.

    For `pad_input=True` matches correspond to the center and otherwise to the
    top-left corner of the template. To find the best match you must search for
    peaks in the response (output) image.

    Parameters
    ----------
    image : (M, N[, D]) array
        2-D or 3-D input image.
    template : (m, n[, d]) array
        Template to locate. It must be `(m <= M, n <= N[, d <= D])`.
    pad_input : bool
        If True, pad `image` so that output is the same size as the image, and
        output values correspond to the template center. Otherwise, the output
        is an array with shape `(M - m + 1, N - n + 1)` for an `(M, N)` image
        and an `(m, n)` template, and matches correspond to origin
        (top-left corner) of the template.
    mode : see `numpy.pad`, optional
        Padding mode.
    constant_values : see `numpy.pad`, optional
        Constant values used in conjunction with ``mode='constant'``.

    Returns
    -------
    output : array
        Response image with correlation coefficients.

    Notes
    -----
    Details on the cross-correlation are presented in [1]_. This implementation
    uses FFT convolutions of the image and the template. Reference [2]_
    presents similar derivations but the approximation presented in this
    reference is not used in our implementation.

    This CuPy implementation does not force the image to float64 internally,
    but will use float32 for single-precision inputs.

    References
    ----------
    .. [1] J. P. Lewis, "Fast Normalized Cross-Correlation", Industrial Light
           and Magic.
    .. [2] Briechle and Hanebeck, "Template Matching using Fast Normalized
           Cross Correlation", Proceedings of the SPIE (2001).
           :DOI:`10.1117/12.421129`

    Examples
    --------
    >>> import cupy as cp
    >>> template = cp.zeros((3, 3))
    >>> template[1, 1] = 1
    >>> template
    array([[ 0.,  0.,  0.],
           [ 0.,  1.,  0.],
           [ 0.,  0.,  0.]])
    >>> image = cp.zeros((6, 6))
    >>> image[1, 1] = 1
    >>> image[4, 4] = -1
    >>> image
    array([[ 0.,  0.,  0.,  0.,  0.,  0.],
           [ 0.,  1.,  0.,  0.,  0.,  0.],
           [ 0.,  0.,  0.,  0.,  0.,  0.],
           [ 0.,  0.,  0.,  0.,  0.,  0.],
           [ 0.,  0.,  0.,  0., -1.,  0.],
           [ 0.,  0.,  0.,  0.,  0.,  0.]])
    >>> result = match_template(image, template)
    >>> cp.round(result, 3)
    array([[ 1.   , -0.125,  0.   ,  0.   ],
           [-0.125, -0.125,  0.   ,  0.   ],
           [ 0.   ,  0.   ,  0.125,  0.125],
           [ 0.   ,  0.   ,  0.125, -1.   ]])
    >>> result = match_template(image, template, pad_input=True)
    >>> cp.round(result, 3)
    array([[-0.125, -0.125, -0.125,  0.   ,  0.   ,  0.   ],
           [-0.125,  1.   , -0.125,  0.   ,  0.   ,  0.   ],
           [-0.125, -0.125, -0.125,  0.   ,  0.   ,  0.   ],
           [ 0.   ,  0.   ,  0.   ,  0.125,  0.125,  0.125],
           [ 0.   ,  0.   ,  0.   ,  0.125, -1.   ,  0.125],
           [ 0.   ,  0.   ,  0.   ,  0.125,  0.125,  0.125]])
    """
    check_nD(image, (2, 3))

    if image.ndim < template.ndim:
        raise ValueError("Dimensionality of template must be less than or "
                         "equal to the dimensionality of image.")
    if any(si < st for si, st in zip(image.shape, template.shape)):
        raise ValueError("Image must be larger than template.")

    image_shape = image.shape

    float_dtype = cp.promote_types(image.dtype, cp.float32)
    image = cp.asarray(image, dtype=float_dtype)
    template = cp.asarray(template, dtype=float_dtype)

    pad_width = tuple((width, width) for width in template.shape)
    if mode == "constant":
        image = cp.pad(
            image,
            pad_width=pad_width,
            mode=mode,
            constant_values=constant_values,
        )
    else:
        image = cp.pad(image, pad_width=pad_width, mode=mode)

    # Use special case for 2-D images for much better performance in
    # computation of integral images
    if image.ndim == 2:
        image_window_sum = _window_sum_2d(image, template.shape)
        image_window_sum2 = _window_sum_2d(image * image, template.shape)
    elif image.ndim == 3:
        image_window_sum = _window_sum_3d(image, template.shape)
        image_window_sum2 = _window_sum_3d(image * image, template.shape)

    template_mean = template.mean()
    template_volume = _prod(template.shape)
    template_ssd = template - template_mean
    template_ssd *= template_ssd
    template_ssd = cp.sum(template_ssd)

    if image.ndim == 2:
        xcorr = fftconvolve(image, template[::-1, ::-1], mode="valid")[1:-1,
                                                                       1:-1]
    elif image.ndim == 3:
        xcorr = fftconvolve(image, template[::-1, ::-1, ::-1],
                            mode="valid")[1:-1, 1:-1, 1:-1]

    numerator = xcorr - image_window_sum * template_mean

    denominator = image_window_sum2
    cp.multiply(image_window_sum, image_window_sum, out=image_window_sum)
    cp.divide(image_window_sum, template_volume, out=image_window_sum)
    denominator -= image_window_sum
    denominator *= template_ssd
    cp.maximum(denominator, 0,
               out=denominator)  # sqrt of negative number not allowed
    cp.sqrt(denominator, out=denominator)

    response = cp.zeros_like(xcorr, dtype=np.float64)

    # avoid zero-division
    mask = denominator > cp.finfo(np.float64).eps

    response[mask] = numerator[mask] / denominator[mask]

    slices = []
    for i in range(template.ndim):
        if pad_input:
            d0 = (template.shape[i] - 1) // 2
            d1 = d0 + image_shape[i]
        else:
            d0 = template.shape[i] - 1
            d1 = d0 + image_shape[i] - template.shape[i] + 1
        slices.append(slice(d0, d1))

    return response[tuple(slices)]
Ejemplo n.º 3
0
def zoom(
    input,
    zoom,
    output=None,
    order=3,
    mode="constant",
    cval=0.0,
    prefilter=True,
    *,
    grid_mode=False,
    allow_float32=True,
):
    """Zoom an array.

    The array is zoomed using spline interpolation of the requested order.

    Args:
        input (cupy.ndarray): The input array.
        zoom (float or sequence): The zoom factor along the axes. If a float,
            ``zoom`` is the same for each axis. If a sequence, ``zoom`` should
            contain one value for each axis.
        output (cupy.ndarray or ~cupy.dtype): The array in which to place the
            output, or the dtype of the returned array.
        order (int): The order of the spline interpolation. If it is not given,
            order 1 is used. It is different from :mod:`scipy.ndimage` and can
            change in the future. The order has to be in the range 0-5.
        mode (str): Points outside the boundaries of the input are filled
            according to the given mode (``'constant'``, ``'nearest'``,
            ``'mirror'`` or ``'opencv'``). Default is ``'constant'``.
        cval (scalar): Value used for points outside the boundaries of
            the input if ``mode='constant'`` or ``mode='opencv'``. Default is
            0.0
        prefilter (bool): It is not used yet. It just exists for compatibility
            with :mod:`scipy.ndimage`.
        grid_mode (bool, optional): If False, the distance from the pixel
            centers is zoomed. Otherwise, the distance including the full pixel
            extent is used. For example, a 1d signal of length 5 is considered
            to have length 4 when `grid_mode` is False, but length 5 when
            `grid_mode` is True. See the following visual illustration:

            .. code-block:: text

                    | pixel 1 | pixel 2 | pixel 3 | pixel 4 | pixel 5 |
                         |<-------------------------------------->|
                                            vs.
                    |<----------------------------------------------->|

            The starting point of the arrow in the diagram above corresponds to
            coordinate location 0 in each mode. This option is unused if
            ``mode='opencv'``.

    Returns:
        cupy.ndarray or None:
            The zoomed input.

    Notes
    -----
    This implementation handles boundary modes 'wrap' and 'reflect' correctly,
    while SciPy prior to release 1.6.0 does not. So, if comparing to older
    SciPy, some disagreement near the borders may occur.

    For ``order > 1`` with ``prefilter == True``, the spline prefilter boundary
    conditions are implemented correctly only for modes 'mirror', 'reflect'
    and 'grid-wrap'.

    .. seealso:: :func:`scipy.ndimage.zoom`
    """

    _check_parameter("zoom", order, mode)

    if not hasattr(zoom, "__iter__") and type(zoom) is not cupy.ndarray:
        zoom = [zoom] * input.ndim
    output_shape = []
    for s, z in zip(input.shape, zoom):
        output_shape.append(int(round(s * z)))
    output_shape = tuple(output_shape)

    if mode == "opencv":
        zoom = []
        offset = []
        for in_size, out_size in zip(input.shape, output_shape):
            if out_size > 1:
                zoom.append(float(in_size) / out_size)
                offset.append((zoom[-1] - 1) / 2.0)
            else:
                zoom.append(0)
                offset.append(0)
        mode = "nearest"

        output = affine_transform(
            input,
            cupy.asarray(zoom),
            offset,
            output_shape,
            output,
            order,
            mode,
            cval,
            prefilter,
        )
    else:
        if order is None:
            order = 1

        if grid_mode:
            # warn about modes that may have surprising behavior
            suggest_mode = None
            if mode == "constant":
                suggest_mode = "grid-constant"
            elif mode == "wrap":
                suggest_mode = "grid-wrap"
            if suggest_mode is not None:
                warnings.warn(
                    ("It is recommended to use mode = {} instead of {} when "
                     "grid_mode is True.").format(suggest_mode, mode))

        zoom = []
        for in_size, out_size in zip(input.shape, output_shape):
            if out_size > 1:
                if grid_mode:
                    zoom.append(in_size / out_size)
                else:
                    zoom.append((in_size - 1) / (out_size - 1))
            else:
                zoom.append(1)

        output = _get_output(output, input, shape=output_shape)
        if input.dtype.kind in "iu":
            input = input.astype(cupy.float32)

        if prefilter and order > 1:
            padded, npad = _prepad_for_spline_filter(input, mode, cval)
            filtered = spline_filter(
                padded,
                order,
                output=input.dtype,
                mode=mode,
                allow_float32=allow_float32,
            )
        else:
            npad = 0
            filtered = input

        # kernel assumes C-contiguous arrays
        if not filtered.flags.c_contiguous:
            filtered = cupy.ascontiguousarray(filtered)

        integer_output = output.dtype.kind in "iu"
        large_int = (max(_misc._prod(input.shape), _misc._prod(output_shape)) >
                     1 << 31)
        kern = _get_zoom_kernel(
            input.ndim,
            large_int,
            output_shape,
            mode,
            order=order,
            integer_output=integer_output,
            nprepad=npad,
            grid_mode=grid_mode,
        )

        zoom = cupy.asarray(zoom, dtype=float, order="C")
        if zoom.ndim != 1:
            raise ValueError("zoom must be 1d")
        if zoom.size != filtered.ndim:
            raise ValueError("len(zoom) must equal input.ndim")
        kern(filtered, zoom, output)
    return output
Ejemplo n.º 4
0
def shift(
    input,
    shift,
    output=None,
    order=3,
    mode="constant",
    cval=0.0,
    prefilter=True,
    *,
    allow_float32=True,
):
    """Shift an array.

    The array is shifted using spline interpolation of the requested order.
    Points outside the boundaries of the input are filled according to the
    given mode.

    Args:
        input (cupy.ndarray): The input array.
        shift (float or sequence): The shift along the axes. If a float,
            ``shift`` is the same for each axis. If a sequence, ``shift``
            should contain one value for each axis.
        output (cupy.ndarray or ~cupy.dtype): The array in which to place the
            output, or the dtype of the returned array.
        order (int): The order of the spline interpolation. If it is not given,
            order 1 is used. It is different from :mod:`scipy.ndimage` and can
            change in the future. The order has to be in the range 0-5.
        mode (str): Points outside the boundaries of the input are filled
            according to the given mode (``'constant'``, ``'nearest'``,
            ``'mirror'`` or ``'opencv'``). Default is ``'constant'``.
        cval (scalar): Value used for points outside the boundaries of
            the input if ``mode='constant'`` or ``mode='opencv'``. Default is
            0.0
        prefilter (bool): It is not used yet. It just exists for compatibility
            with :mod:`scipy.ndimage`.

    Returns:
        cupy.ndarray or None:
            The shifted input.

    Notes
    -----
    This implementation handles boundary modes 'wrap' and 'reflect' correctly,
    while SciPy prior to release 1.6.0 does not. So, if comparing to older
    SciPy, some disagreement near the borders may occur.

    For ``order > 1`` with ``prefilter == True``, the spline prefilter boundary
    conditions are implemented correctly only for modes 'mirror', 'reflect'
    and 'grid-wrap'.


    .. seealso:: :func:`scipy.ndimage.shift`
    """

    _check_parameter("shift", order, mode)

    if not hasattr(shift, "__iter__") and type(shift) is not cupy.ndarray:
        shift = [shift] * input.ndim

    if mode == "opencv":
        mode = "_opencv_edge"

        output = affine_transform(
            input,
            cupy.ones(input.ndim, input.dtype),
            cupy.negative(cupy.asarray(shift)),
            None,
            output,
            order,
            mode,
            cval,
            prefilter,
        )
    else:
        if order is None:
            order = 1
        output = _get_output(output, input)
        if input.dtype.kind in "iu":
            input = input.astype(cupy.float32)

        if prefilter and order > 1:
            padded, npad = _prepad_for_spline_filter(input, mode, cval)
            filtered = spline_filter(
                padded,
                order,
                output=input.dtype,
                mode=mode,
                allow_float32=allow_float32,
            )
        else:
            npad = 0
            filtered = input

        # kernel assumes C-contiguous arrays
        if not filtered.flags.c_contiguous:
            filtered = cupy.ascontiguousarray(filtered)

        integer_output = output.dtype.kind in "iu"
        large_int = _misc._prod(input.shape) > 1 << 31
        kern = _get_shift_kernel(
            input.ndim,
            large_int,
            input.shape,
            mode,
            cval=cval,
            order=order,
            integer_output=integer_output,
            nprepad=npad,
        )
        shift = cupy.asarray(shift, dtype=float, order="C")
        if shift.ndim != 1:
            raise ValueError("shift must be 1d")
        if shift.size != filtered.ndim:
            raise ValueError("len(shift) must equal input.ndim")
        kern(filtered, shift, output)
    return output
Ejemplo n.º 5
0
def affine_transform(
    input,
    matrix,
    offset=0.0,
    output_shape=None,
    output=None,
    order=3,
    mode="constant",
    cval=0.0,
    prefilter=True,
    *,
    allow_float32=True,
):
    """Apply an affine transformation.

    Given an output image pixel index vector ``o``, the pixel value is
    determined from the input image at position
    ``cupy.dot(matrix, o) + offset``.

    Args:
        input (cupy.ndarray): The input array.
        matrix (cupy.ndarray): The inverse coordinate transformation matrix,
            mapping output coordinates to input coordinates. If ``ndim`` is the
            number of dimensions of ``input``, the given matrix must have one
            of the following shapes:

                - ``(ndim, ndim)``: the linear transformation matrix for each
                  output coordinate.
                - ``(ndim,)``: assume that the 2D transformation matrix is
                  diagonal, with the diagonal specified by the given value.
                - ``(ndim + 1, ndim + 1)``: assume that the transformation is
                  specified using homogeneous coordinates. In this case, any
                  value passed to ``offset`` is ignored.
                - ``(ndim, ndim + 1)``: as above, but the bottom row of a
                  homogeneous transformation matrix is always
                  ``[0, 0, ..., 1]``, and may be omitted.

        offset (float or sequence): The offset into the array where the
            transform is applied. If a float, ``offset`` is the same for each
            axis. If a sequence, ``offset`` should contain one value for each
            axis.
        output_shape (tuple of ints): Shape tuple.
        output (cupy.ndarray or ~cupy.dtype): The array in which to place the
            output, or the dtype of the returned array.
        order (int): The order of the spline interpolation. If it is not given,
            order 1 is used. It is different from :mod:`scipy.ndimage` and can
            change in the future. The order has to be in the range 0-5.
        mode (str): Points outside the boundaries of the input are filled
            according to the given mode (``'constant'``, ``'nearest'``,
            ``'mirror'`` or ``'opencv'``). Default is ``'constant'``.
        cval (scalar): Value used for points outside the boundaries of
            the input if ``mode='constant'`` or ``mode='opencv'``. Default is
            0.0
        prefilter (bool): It is not used yet. It just exists for compatibility
            with :mod:`scipy.ndimage`.

    Returns:
        cupy.ndarray or None:
            The transformed input. If ``output`` is given as a parameter,
            ``None`` is returned.

    Notes
    -----
    This implementation handles boundary modes 'wrap' and 'reflect' correctly,
    while SciPy prior to release 1.6.0 does not. So, if comparing to older
    SciPy, some disagreement near the borders may occur.

    For ``order > 1`` with ``prefilter == True``, the spline prefilter boundary
    conditions are implemented correctly only for modes 'mirror', 'reflect'
    and 'grid-wrap'.

    .. seealso:: :func:`scipy.ndimage.affine_transform`
    """

    _check_parameter("affine_transform", order, mode)

    if not hasattr(offset, "__iter__") and type(offset) is not cupy.ndarray:
        offset = [offset] * input.ndim

    matrix = cupy.asarray(matrix, order="C", dtype=float)
    if matrix.ndim not in [1, 2]:
        raise RuntimeError("no proper affine matrix provided")
    if matrix.ndim == 2:
        if matrix.shape[0] == matrix.shape[1] - 1:
            offset = matrix[:, -1]
            matrix = matrix[:, :-1]
        elif matrix.shape[0] == input.ndim + 1:
            offset = matrix[:-1, -1]
            matrix = matrix[:-1, :-1]

    if mode == "opencv":
        m = cupy.zeros((input.ndim + 1, input.ndim + 1), dtype=float)
        m[:-1, :-1] = matrix
        m[:-1, -1] = offset
        m[-1, -1] = 1
        m = cupy.linalg.inv(m)
        m[:2] = cupy.roll(m[:2], 1, axis=0)
        m[:2, :2] = cupy.roll(m[:2, :2], 1, axis=1)
        matrix = m[:-1, :-1]
        offset = m[:-1, -1]

    if output_shape is None:
        output_shape = input.shape

    matrix = matrix.astype(float, copy=False)
    if order is None:
        order = 1
    ndim = input.ndim
    output = _get_output(output, input, shape=output_shape)
    if input.dtype.kind in "iu":
        input = input.astype(cupy.float32)

    if prefilter and order > 1:
        padded, npad = _prepad_for_spline_filter(input, mode, cval)
        filtered = spline_filter(
            padded,
            order,
            output=input.dtype,
            mode=mode,
            allow_float32=allow_float32,
        )
    else:
        npad = 0
        filtered = input

    # kernel assumes C-contiguous arrays
    if not filtered.flags.c_contiguous:
        filtered = cupy.ascontiguousarray(filtered)
    if not matrix.flags.c_contiguous:
        matrix = cupy.ascontiguousarray(matrix)

    integer_output = output.dtype.kind in "iu"
    large_int = (max(_misc._prod(input.shape), _misc._prod(output_shape)) >
                 1 << 31)
    if matrix.ndim == 1:
        offset = cupy.asarray(offset, dtype=float, order="C")
        offset = -offset / matrix
        kern = _get_zoom_shift_kernel(
            ndim,
            large_int,
            output_shape,
            mode,
            cval=cval,
            order=order,
            integer_output=integer_output,
            nprepad=npad,
        )
        kern(filtered, offset, matrix, output)
    else:
        kern = _get_affine_kernel(
            ndim,
            large_int,
            output_shape,
            mode,
            cval=cval,
            order=order,
            integer_output=integer_output,
            nprepad=npad,
        )
        m = cupy.zeros((ndim, ndim + 1), dtype=float)
        m[:, :-1] = matrix
        m[:, -1] = cupy.asarray(offset, dtype=float)
        kern(filtered, m, output)
    return output
Ejemplo n.º 6
0
def map_coordinates(
    input,
    coordinates,
    output=None,
    order=3,
    mode="constant",
    cval=0.0,
    prefilter=True,
    *,
    allow_float32=True,
):
    """Map the input array to new coordinates by interpolation.

    The array of coordinates is used to find, for each point in the output, the
    corresponding coordinates in the input. The value of the input at those
    coordinates is determined by spline interpolation of the requested order.

    The shape of the output is derived from that of the coordinate array by
    dropping the first axis. The values of the array along the first axis are
    the coordinates in the input array at which the output value is found.

    Args:
        input (cupy.ndarray): The input array.
        coordinates (array_like): The coordinates at which ``input`` is
            evaluated.
        output (cupy.ndarray or ~cupy.dtype): The array in which to place the
            output, or the dtype of the returned array.
        order (int): The order of the spline interpolation. If it is not given,
            order 1 is used. It is different from :mod:`scipy.ndimage` and can
            change in the future. The order has to be in the range 0-5.
        mode (str): Points outside the boundaries of the input are filled
            according to the given mode (``'constant'``, ``'nearest'``,
            ``'mirror'`` or ``'opencv'``). Default is ``'constant'``.
        cval (scalar): Value used for points outside the boundaries of
            the input if ``mode='constant'`` or ``mode='opencv'``. Default is
            0.0
        prefilter (bool): It is not used yet. It just exists for compatibility
            with :mod:`scipy.ndimage`.

    Returns:
        cupy.ndarray:
            The result of transforming the input. The shape of the output is
            derived from that of ``coordinates`` by dropping the first axis.

    Notes
    -----
    This implementation handles boundary modes 'wrap' and 'reflect' correctly,
    while SciPy prior to release 1.6.0 does not. So, if comparing to older
    SciPy, some disagreement near the borders may occur.

    For ``order > 1`` with ``prefilter == True``, the spline prefilter boundary
    conditions are implemented correctly only for modes 'mirror', 'reflect'
    and 'grid-wrap'.

    .. seealso:: :func:`scipy.ndimage.map_coordinates`
    """

    _check_parameter("map_coordinates", order, mode)

    if mode == "opencv" or mode == "_opencv_edge":
        input = cupy.pad(input, [(1, 1)] * input.ndim,
                         "constant",
                         constant_values=cval)
        coordinates = cupy.add(coordinates, 1)
        mode = "constant"

    ret = _get_output(output, input, coordinates.shape[1:])
    integer_output = ret.dtype.kind in "iu"

    if input.dtype.kind in "iu":
        input = input.astype(cupy.float32)

    if coordinates.dtype.kind in "iu":
        if order > 1:
            # order > 1 (spline) kernels require floating-point coordinates
            if allow_float32:
                coord_dtype = cupy.promote_types(coordinates.dtype,
                                                 cupy.float32)
            else:
                coord_dtype = cupy.promote_types(coordinates.dtype,
                                                 cupy.float64)
            coordinates = coordinates.astype(coord_dtype)
    elif coordinates.dtype.kind != "f":
        raise ValueError("coordinates should have floating point dtype")
    else:
        if allow_float32:
            coord_dtype = cupy.promote_types(coordinates.dtype, cupy.float32)
        else:
            coord_dtype = cupy.promote_types(coordinates.dtype, cupy.float64)
        coordinates = coordinates.astype(coord_dtype, copy=False)

    if prefilter and order > 1:
        padded, npad = _prepad_for_spline_filter(input, mode, cval)
        filtered = spline_filter(
            padded,
            order,
            output=input.dtype,
            mode=mode,
            allow_float32=allow_float32,
        )
    else:
        npad = 0
        filtered = input

    large_int = max(_misc._prod(input.shape), coordinates.shape[0]) > 1 << 31
    kern = _get_map_kernel(
        filtered.ndim,
        large_int,
        yshape=coordinates.shape,
        mode=mode,
        cval=cval,
        order=order,
        integer_output=integer_output,
        nprepad=npad,
    )
    # kernel assumes C-contiguous arrays
    if not filtered.flags.c_contiguous:
        filtered = cupy.ascontiguousarray(filtered)
    if not coordinates.flags.c_contiguous:
        coordinates = cupy.ascontiguousarray(coordinates)
    kern(filtered, coordinates, ret)
    return ret
Ejemplo n.º 7
0
def zoom(
    input,
    zoom,
    output=None,
    order=3,
    mode="constant",
    cval=0.0,
    prefilter=True,
    *,
    allow_float32=True,
):
    """Zoom an array.

    The array is zoomed using spline interpolation of the requested order.

    Args:
        input (cupy.ndarray): The input array.
        zoom (float or sequence): The zoom factor along the axes. If a float,
            ``zoom`` is the same for each axis. If a sequence, ``zoom`` should
            contain one value for each axis.
        output (cupy.ndarray or ~cupy.dtype): The array in which to place the
            output, or the dtype of the returned array.
        order (int): The order of the spline interpolation. Must be between 0
            and 5.
        mode (str): Points outside the boundaries of the input are filled
            according to the given mode (``'constant'``, ``'nearest'``,
            ``'mirror'`` or ``'opencv'``). Default is ``'constant'``.
        cval (scalar): Value used for points outside the boundaries of
            the input if ``mode='constant'`` or ``mode='opencv'``. Default is
            0.0
        prefilter (bool): It is not used yet. It just exists for compatibility
            with :mod:`scipy.ndimage`.

    Returns:
        cupy.ndarray or None:
            The zoomed input.

    Notes
    -----
    This implementation handles boundary modes 'wrap' and 'reflect' correctly,
    while SciPy does not (at least as of release 1.4.0). So, if comparing to
    SciPy, some disagreement near the borders may occur unless
    ``mode == 'mirror'``.

    For ``order > 1`` with ``prefilter == True``, the spline prefilter boundary
    conditions are implemented correctly only for modes 'mirror', 'reflect'
    and 'wrap'. For the other modes ('constant' and 'nearest'), there is some
    innacuracy near the boundary of the array.

    .. seealso:: :func:`scipy.ndimage.zoom`
    """

    _check_parameter("zoom", order, mode)

    if not hasattr(zoom, "__iter__") and type(zoom) is not cupy.ndarray:
        zoom = [zoom] * input.ndim
    output_shape = []
    for s, z in zip(input.shape, zoom):
        output_shape.append(int(round(s * z)))
    output_shape = tuple(output_shape)

    if mode == "opencv":
        zoom = []
        offset = []
        for in_size, out_size in zip(input.shape, output_shape):
            if out_size > 1:
                zoom.append(float(in_size) / out_size)
                offset.append((zoom[-1] - 1) / 2.0)
            else:
                zoom.append(0)
                offset.append(0)
        mode = "nearest"

        output = affine_transform(
            input,
            cupy.asarray(zoom),
            offset,
            output_shape,
            output,
            order,
            mode,
            cval,
            prefilter,
        )
    else:
        if order is None:
            order = 1

        zoom = []
        for in_size, out_size in zip(input.shape, output_shape):
            if out_size > 1:
                zoom.append(float(in_size - 1) / (out_size - 1))
            else:
                zoom.append(1)

        output = _get_output(output, input, shape=output_shape)
        if input.dtype.kind in "iu":
            input = input.astype(cupy.float32)

        if prefilter and order > 1:
            padded, npad = _prepad_for_spline_filter(input, mode, cval)
            filtered = spline_filter(
                padded,
                order,
                output=input.dtype,
                mode=mode,
                allow_float32=allow_float32,
            )
        else:
            npad = 0
            filtered = input

        # kernel assumes C-contiguous arrays
        if not filtered.flags.c_contiguous:
            filtered = cupy.ascontiguousarray(filtered)

        integer_output = output.dtype.kind in "iu"
        large_int = (max(_misc._prod(input.shape), _misc._prod(output_shape)) >
                     1 << 31)
        kern = _get_zoom_kernel(
            input.ndim,
            large_int,
            output_shape,
            mode,
            order=order,
            integer_output=integer_output,
            nprepad=npad,
        )

        zoom = cupy.asarray(zoom, dtype=float, order="C")
        if zoom.ndim != 1:
            raise ValueError("zoom must be 1d")
        if zoom.size != filtered.ndim:
            raise ValueError("len(zoom) must equal input.ndim")
        kern(filtered, zoom, output)
    return output