Пример #1
0
def structure_tensor(image, sigma=1, mode="constant", cval=0):
    """Compute structure tensor using sum of squared differences.

    The structure tensor A is defined as::

        A = [Axx Axy]
            [Axy Ayy]

    which is approximated by the weighted sum of squared differences in a local
    window around each pixel in the image.

    Parameters
    ----------
    image : ndarray
        Input image.
    sigma : float, optional
        Standard deviation used for the Gaussian kernel, which is used as a
        weighting function for the local summation of squared differences.
    mode : {'constant', 'reflect', 'wrap', 'nearest', 'mirror'}, optional
        How to handle values outside the image borders.
    cval : float, optional
        Used in conjunction with mode 'constant', the value outside
        the image boundaries.

    Returns
    -------
    Axx : ndarray
        Element of the structure tensor for each pixel in the input image.
    Axy : ndarray
        Element of the structure tensor for each pixel in the input image.
    Ayy : ndarray
        Element of the structure tensor for each pixel in the input image.

    Examples
    --------
    >>> from skimage.feature import structure_tensor
    >>> square = np.zeros((5, 5))
    >>> square[2, 2] = 1
    >>> Axx, Axy, Ayy = structure_tensor(square, sigma=0.1)
    >>> Axx
    array([[0., 0., 0., 0., 0.],
           [0., 1., 0., 1., 0.],
           [0., 4., 0., 4., 0.],
           [0., 1., 0., 1., 0.],
           [0., 0., 0., 0., 0.]])

    """

    image = _prepare_grayscale_input_2D(image)

    imx, imy = _compute_derivatives(image, mode=mode, cval=cval)

    # structure tensore
    Axx = ndi.gaussian_filter(imx * imx, sigma, mode=mode, cval=cval)
    Axy = ndi.gaussian_filter(imx * imy, sigma, mode=mode, cval=cval)
    Ayy = ndi.gaussian_filter(imy * imy, sigma, mode=mode, cval=cval)

    return Axx, Axy, Ayy
Пример #2
0
def test_orders_gauss():
    # Check order inputs to Gaussians
    arr = cp.zeros((1, ))
    assert_equal(0, sndi.gaussian_filter(arr, 1, order=0).get())
    assert_equal(0, sndi.gaussian_filter(arr, 1, order=3).get())
    assert_raises(ValueError, sndi.gaussian_filter, arr, 1, -1)
    assert_equal(0, sndi.gaussian_filter1d(arr, 1, axis=-1, order=0).get())
    assert_equal(0, sndi.gaussian_filter1d(arr, 1, axis=-1, order=3).get())
    assert_raises(ValueError, sndi.gaussian_filter1d, arr, 1, -1, -1)
Пример #3
0
def _smooth(image, sigma, mode, cval, multichannel=None):
    """Return image with each channel smoothed by the Gaussian filter."""
    smoothed = cp.empty_like(image)

    # apply Gaussian filter to all channels independently
    if multichannel:
        sigma = (sigma,) * (image.ndim - 1) + (0,)
    ndi.gaussian_filter(image, sigma, output=smoothed, mode=mode, cval=cval)
    return smoothed
Пример #4
0
def test_gaussian_truncate():
    # Test that Gaussian filters can be truncated at different widths.
    # These tests only check that the result has the expected number
    # of nonzero elements.
    arr = cp.zeros((100, 100), float)
    arr[50, 50] = 1
    num_nonzeros_2 = (sndi.gaussian_filter(arr, 5, truncate=2) > 0).sum().get()
    assert_equal(num_nonzeros_2, 21**2)
    num_nonzeros_5 = (sndi.gaussian_filter(arr, 5, truncate=5) > 0).sum().get()
    assert_equal(num_nonzeros_5, 51**2)

    # Test truncate when sigma is a sequence.
    f = sndi.gaussian_filter(arr, [0.5, 2.5], truncate=3.5).get()
    fpos = f > 0
    n0 = fpos.any(axis=0).sum()
    # n0 should be 2*int(2.5*3.5 + 0.5) + 1
    assert_equal(n0, 19)
    n1 = fpos.any(axis=1).sum()
    # n1 should be 2*int(0.5*3.5 + 0.5) + 1
    assert_equal(n1, 5)

    # Test gaussian_filter1d.
    x = cp.zeros(51)
    x[25] = 1
    f = sndi.gaussian_filter1d(x, sigma=2, truncate=3.5).get()
    n = (f > 0).sum()
    assert_equal(n, 15)

    # Test gaussian_laplace
    y = sndi.gaussian_laplace(x, sigma=2, truncate=3.5).get()
    nonzero_indices = np.nonzero(y != 0)[0]
    n = nonzero_indices.ptp() + 1
    assert_equal(n, 15)

    # Test gaussian_gradient_magnitude
    y = sndi.gaussian_gradient_magnitude(x, sigma=2, truncate=3.5).get()
    nonzero_indices = np.nonzero(y != 0)[0]
    n = nonzero_indices.ptp() + 1
    assert_equal(n, 15)
Пример #5
0
def test_multiple_modes():
    # Test that the filters with multiple mode cababilities for different
    # dimensions give the same result as applying a single mode.
    arr = cp.array([[1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.0, 0.0, 0.0]])

    mode1 = "reflect"
    mode2 = ["reflect", "reflect"]

    assert_array_equal(
        sndi.gaussian_filter(arr, 1, mode=mode1),
        sndi.gaussian_filter(arr, 1, mode=mode2),
    )
    assert_array_equal(sndi.prewitt(arr, mode=mode1),
                       sndi.prewitt(arr, mode=mode2))
    assert_array_equal(sndi.sobel(arr, mode=mode1), sndi.sobel(arr,
                                                               mode=mode2))
    assert_array_equal(sndi.laplace(arr, mode=mode1),
                       sndi.laplace(arr, mode=mode2))
    assert_array_equal(
        sndi.gaussian_laplace(arr, 1, mode=mode1),
        sndi.gaussian_laplace(arr, 1, mode=mode2),
    )
    assert_array_equal(
        sndi.maximum_filter(arr, size=5, mode=mode1),
        sndi.maximum_filter(arr, size=5, mode=mode2),
    )
    assert_array_equal(
        sndi.minimum_filter(arr, size=5, mode=mode1),
        sndi.minimum_filter(arr, size=5, mode=mode2),
    )
    assert_array_equal(
        sndi.gaussian_gradient_magnitude(arr, 1, mode=mode1),
        sndi.gaussian_gradient_magnitude(arr, 1, mode=mode2),
    )
    assert_array_equal(
        sndi.uniform_filter(arr, 5, mode=mode1),
        sndi.uniform_filter(arr, 5, mode=mode2),
    )
Пример #6
0
def test_image_shape():
    """Test that shape of output image in deconvolution is same as input.

    This addresses issue #1172.
    """
    point = cp.zeros((5, 5), np.float)
    point[2, 2] = 1.0
    psf = ndi.gaussian_filter(point, sigma=1.0)
    # image shape: (45, 45), as reported in #1172
    image = cp.asarray(test_img[110:155, 225:270])  # just the face
    image_conv = ndi.convolve(image, psf)
    deconv_sup = restoration.wiener(image_conv, psf, 1)
    deconv_un = restoration.unsupervised_wiener(image_conv, psf)[0]
    # test the shape
    assert image.shape == deconv_sup.shape
    assert image.shape == deconv_un.shape
    # test the reconstruction error
    sup_relative_error = cp.abs(deconv_sup - image) / image
    un_relative_error = cp.abs(deconv_un - image) / image
    cp.testing.assert_array_less(np.median(sup_relative_error.get()), 0.1)
    cp.testing.assert_array_less(np.median(un_relative_error.get()), 0.1)
Пример #7
0
def test_multiple_modes_sequentially():
    # Test that the filters with multiple mode cababilities for different
    # dimensions give the same result as applying the filters with
    # different modes sequentially
    arr = cp.array([[1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.0, 0.0, 0.0]])

    modes = ["reflect", "wrap"]

    expected = sndi.gaussian_filter1d(arr, 1, axis=0, mode=modes[0])
    expected = sndi.gaussian_filter1d(expected, 1, axis=1, mode=modes[1])
    assert_array_equal(expected, sndi.gaussian_filter(arr, 1, mode=modes))

    expected = sndi.uniform_filter1d(arr, 5, axis=0, mode=modes[0])
    expected = sndi.uniform_filter1d(expected, 5, axis=1, mode=modes[1])
    assert_array_equal(expected, sndi.uniform_filter(arr, 5, mode=modes))

    expected = sndi.maximum_filter1d(arr, size=5, axis=0, mode=modes[0])
    expected = sndi.maximum_filter1d(expected, size=5, axis=1, mode=modes[1])
    assert_array_equal(expected, sndi.maximum_filter(arr, size=5, mode=modes))

    expected = sndi.minimum_filter1d(arr, size=5, axis=0, mode=modes[0])
    expected = sndi.minimum_filter1d(expected, size=5, axis=1, mode=modes[1])
    assert_array_equal(expected, sndi.minimum_filter(arr, size=5, mode=modes))
Пример #8
0
def resize(
    image,
    output_shape,
    order=None,
    mode="reflect",
    cval=0,
    clip=True,
    preserve_range=False,
    anti_aliasing=None,
    anti_aliasing_sigma=None,
):
    """Resize image to match a certain size.

    Performs interpolation to up-size or down-size N-dimensional images. Note
    that anti-aliasing should be enabled when down-sizing images to avoid
    aliasing artifacts. For down-sampling with an integer factor also see
    `skimage.transform.downscale_local_mean`.

    Parameters
    ----------
    image : ndarray
        Input image.
    output_shape : tuple or ndarray
        Size of the generated output image `(rows, cols[, ...][, dim])`. If
        `dim` is not provided, the number of channels is preserved. In case the
        number of input channels does not equal the number of output channels a
        n-dimensional interpolation is applied.

    Returns
    -------
    resized : ndarray
        Resized version of the input.

    Other parameters
    ----------------
    order : int, optional
        The order of the spline interpolation, default is 0 if
        image.dtype is bool and 1 otherwise. The order has to be in
        the range 0-5. See `skimage.transform.warp` for detail.
    mode : {'constant', 'edge', 'symmetric', 'reflect', 'wrap'}, optional
        Points outside the boundaries of the input are filled according
        to the given mode.  Modes match the behaviour of `numpy.pad`.
    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 range of values of the input image.
        This is enabled by default, since higher order interpolation may
        produce values outside the given input range.
    preserve_range : bool, optional
        Whether to keep the original range of values. Otherwise, the input
        image is converted according to the conventions of `img_as_float`.
        Also see https://scikit-image.org/docs/dev/user_guide/data_types.html
    anti_aliasing : bool, optional
        Whether to apply a Gaussian filter to smooth the image prior
        to down-scaling. It is crucial to filter when down-sampling
        the image to avoid aliasing artifacts. If input image data
        type is bool, no anti-aliasing is applied.
    anti_aliasing_sigma : {float, tuple of floats}, optional
        Standard deviation for Gaussian filtering to avoid aliasing artifacts.
        By default, this value is chosen as (s - 1) / 2 where s is the
        down-scaling factor, where s > 1. For the up-size case, s < 1, no
        anti-aliasing is performed prior to rescaling.

    Notes
    -----
    Modes 'reflect' and 'symmetric' are similar, but differ in whether the edge
    pixels are duplicated during the reflection.  As an example, if an array
    has values [0, 1, 2] and was padded to the right by four values using
    symmetric, the result would be [0, 1, 2, 2, 1, 0, 0], while for reflect it
    would be [0, 1, 2, 1, 0, 1, 2].

    Examples
    --------
    >>> from skimage import data
    >>> from skimage.transform import resize
    >>> image = data.camera()
    >>> resize(image, (100, 100)).shape
    (100, 100)

    """
    output_shape = tuple(output_shape)
    output_ndim = len(output_shape)
    input_shape = image.shape
    if output_ndim > image.ndim:
        # append dimensions to input_shape
        input_shape = input_shape + (1,) * (output_ndim - image.ndim)
        image = cp.reshape(image, input_shape)
    elif output_ndim == image.ndim - 1:
        # multichannel case: append shape of last axis
        output_shape = output_shape + (image.shape[-1],)
    elif output_ndim < image.ndim - 1:
        raise ValueError(
            "len(output_shape) cannot be smaller than the image " "dimensions"
        )

    if anti_aliasing is None:
        anti_aliasing = not image.dtype == bool

    if image.dtype == bool and anti_aliasing:
        warn(
            "Input image dtype is bool. Gaussian convolution is not defined "
            "with bool data type. Please set anti_aliasing to False or "
            "explicitely cast input image to another data type. Starting "
            "from version 0.19 a ValueError will be raised instead of this "
            "warning.",
            FutureWarning,
            stacklevel=2,
        )

    factors = np.asarray(input_shape, dtype=float) / np.asarray(
        output_shape, dtype=float
    )

    if anti_aliasing:
        if anti_aliasing_sigma is None:
            anti_aliasing_sigma = np.maximum(0, (factors - 1) / 2)
        else:
            anti_aliasing_sigma = np.atleast_1d(
                anti_aliasing_sigma
            ) * np.ones_like(factors)
            if np.any(anti_aliasing_sigma < 0):
                raise ValueError(
                    "Anti-aliasing standard deviation must be "
                    "greater than or equal to zero"
                )
            elif np.any((anti_aliasing_sigma > 0) & (factors <= 1)):
                warn(
                    "Anti-aliasing standard deviation greater than zero but "
                    "not down-sampling along all axes"
                )

        # Translate modes used by np.pad to those used by ndi.gaussian_filter
        np_pad_to_ndimage = {
            "constant": "constant",
            "edge": "nearest",
            "symmetric": "reflect",
            "reflect": "mirror",
            "wrap": "wrap",
        }
        try:
            ndi_mode = np_pad_to_ndimage[mode]
        except KeyError:
            raise ValueError(
                "Unknown mode, or cannot translate mode. The "
                "mode should be one of 'constant', 'edge', "
                "'symmetric', 'reflect', or 'wrap'. See the "
                "documentation of numpy.pad for more info."
            )

        image = ndi.gaussian_filter(
            image, anti_aliasing_sigma, cval=cval, mode=ndi_mode
        )

    # 2-dimensional interpolation
    if len(output_shape) == 2 or (
        len(output_shape) == 3 and output_shape[2] == input_shape[2]
    ):
        rows = output_shape[0]
        cols = output_shape[1]
        input_rows = input_shape[0]
        input_cols = input_shape[1]
        if rows == 1 and cols == 1:
            tform = AffineTransform(
                translation=(input_cols / 2.0 - 0.5, input_rows / 2.0 - 0.5),
                xp=np,
            )
        else:
            # 3 control points necessary to estimate exact AffineTransform
            src_corners = np.array([[1, 1], [1, rows], [cols, rows]]) - 1
            dst_corners = np.zeros(src_corners.shape, dtype=np.double)
            # take into account that 0th pixel is at position (0.5, 0.5)
            dst_corners[:, 0] = factors[1] * (src_corners[:, 0] + 0.5) - 0.5
            dst_corners[:, 1] = factors[0] * (src_corners[:, 1] + 0.5) - 0.5

            tform = AffineTransform(xp=np)
            tform.estimate(src_corners, dst_corners)

        # Make sure the transform is exactly metric, to ensure fast warping.
        tform.params[2] = np.asarray((0, 0, 1))
        tform.params[0, 1] = 0
        tform.params[1, 0] = 0

        # transfer the Affine to the GPU
        tform.params = cp.asarray(tform.params)

        out = warp(
            image,
            tform,
            output_shape=output_shape,
            order=order,
            mode=mode,
            cval=cval,
            clip=clip,
            preserve_range=preserve_range,
        )

    else:  # n-dimensional interpolation
        order = _validate_interpolation_order(image.dtype, order)

        coord_arrays = [
            factors[i] * (cp.arange(d) + 0.5) - 0.5
            for i, d in enumerate(output_shape)
        ]

        coord_map = cp.stack(
            cp.meshgrid(*coord_arrays, sparse=False, indexing="ij")
        )

        image = convert_to_float(image, preserve_range)

        ndi_mode = _to_ndimage_mode(mode)
        out = ndi.map_coordinates(
            image, coord_map, order=order, mode=ndi_mode, cval=cval
        )

        _clip_warp_output(image, out, order, mode, cval, clip)

    return out
Пример #9
0
def threshold_local(
    image,
    block_size,
    method="gaussian",
    offset=0,
    mode="reflect",
    param=None,
    cval=0,
):
    """Compute a threshold mask image based on local pixel neighborhood.

    Also known as adaptive or dynamic thresholding. The threshold value is
    the weighted mean for the local neighborhood of a pixel subtracted by a
    constant. Alternatively the threshold can be determined dynamically by a
    given function, using the 'generic' method.

    Parameters
    ----------
    image : (N, M) ndarray
        Input image.
    block_size : int
        Odd size of pixel neighborhood which is used to calculate the
        threshold value (e.g. 3, 5, 7, ..., 21, ...).
    method : {'generic', 'gaussian', 'mean', 'median'}, optional
        Method used to determine adaptive threshold for local neighbourhood in
        weighted mean image.

        * 'generic': use custom function (see ``param`` parameter)
        * 'gaussian': apply gaussian filter (see ``param`` parameter for custom\
                      sigma value)
        * 'mean': apply arithmetic mean filter
        * 'median': apply median rank filter

        By default the 'gaussian' method is used.
    offset : float, optional
        Constant subtracted from weighted mean of neighborhood to calculate
        the local threshold value. Default offset is 0.
    mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional
        The mode parameter determines how the array borders are handled, where
        cval is the value when mode is equal to 'constant'.
        Default is 'reflect'.
    param : {int, function}, optional
        Either specify sigma for 'gaussian' method or function object for
        'generic' method. This functions takes the flat array of local
        neighbourhood as a single argument and returns the calculated
        threshold for the centre pixel.
    cval : float, optional
        Value to fill past edges of input if mode is 'constant'.

    Returns
    -------
    threshold : (N, M) ndarray
        Threshold image. All pixels in the input image higher than the
        corresponding pixel in the threshold image are considered foreground.

    References
    ----------
    .. [1] https://docs.opencv.org/modules/imgproc/doc/miscellaneous_transformations.html?highlight=threshold#adaptivethreshold

    Examples
    --------
    >>> from skimage.data import camera
    >>> image = camera()[:50, :50]
    >>> binary_image1 = image > threshold_local(image, 15, 'mean')
    >>> func = lambda arr: arr.mean()
    >>> binary_image2 = image > threshold_local(image, 15, 'generic',
    ...                                         param=func)
    """
    if block_size % 2 == 0:
        raise ValueError("The kwarg ``block_size`` must be odd! Given "
                         "``block_size`` {0} is even.".format(block_size))
    check_nD(image, 2)
    thresh_image = cp.zeros(image.shape, "double")
    if method == "generic":
        raise NotImplementedError("TODO: implement generic_filter")
        ndi.generic_filter(image,
                           param,
                           block_size,
                           output=thresh_image,
                           mode=mode,
                           cval=cval)
    elif method == "gaussian":
        if param is None:
            # automatically determine sigma which covers > 99% of distribution
            sigma = (block_size - 1) / 6.0
        else:
            sigma = param
        ndi.gaussian_filter(image,
                            sigma,
                            output=thresh_image,
                            mode=mode,
                            cval=cval)
    elif method == "mean":
        mask = 1.0 / block_size * cp.ones((block_size, ))
        # separation of filters to speedup convolution
        ndi.convolve1d(image,
                       mask,
                       axis=0,
                       output=thresh_image,
                       mode=mode,
                       cval=cval)
        ndi.convolve1d(
            thresh_image,
            mask,
            axis=1,
            output=thresh_image,
            mode=mode,
            cval=cval,
        )
    elif method == "median":
        ndi.median_filter(image,
                          block_size,
                          output=thresh_image,
                          mode=mode,
                          cval=cval)
    else:
        raise ValueError("Invalid method specified. Please use `generic`, "
                         "`gaussian`, `mean`, or `median`.")

    return thresh_image - offset
Пример #10
0
def pca_noise_estimate(
    data,
    gtab,
    patch_radius=1,
    correct_bias=True,
    smooth=2,
    *,
    allow_single=False,
):
    """ PCA based local noise estimation.

    Parameters
    ----------
    data: 4D array
        the input dMRI data.

    gtab: gradient table object
      gradient information for the data gives us the bvals and bvecs of
      diffusion data, which is needed here to select between the noise
      estimation methods.
    patch_radius : int
        The radius of the local patch to be taken around each voxel (in
        voxels). Default: 1 (estimate noise in blocks of 3x3x3 voxels).
    correct_bias : bool
      Whether to correct for bias due to Rician noise. This is an implementation
      of equation 8 in [1]_.

    smooth : int
      Radius of a Gaussian smoothing filter to apply to the noise estimate
      before returning. Default: 2.

    Returns
    -------
    sigma_corr: 3D array
        The local noise standard deviation estimate.

    References
    ----------
    .. [1] Manjon JV, Coupe P, Concha L, Buades A, Collins DL "Diffusion
           Weighted Image Denoising Using Overcomplete Local PCA". PLoS ONE
           8(9): e73021. doi:10.1371/journal.pone.0073021.
    """
    # first identify the number of the b0 images
    K = np.count_nonzero(gtab.b0s_mask)

    if K > 1:
        # If multiple b0 values then use MUBE noise estimate
        data0 = data[..., cp.asarray(gtab.b0s_mask)]
        # sibe = False

    else:
        # if only one b0 value then SIBE noise estimate
        data0 = data[..., cp.asarray(~gtab.b0s_mask)]
        # sibe = True

    n0, n1, n2, n3 = data0.shape
    nsamples = n0 * n1 * n2

    if allow_single:
        data_dtype = cp.promote_types(data0.dtype, cp.float32)
    else:
        data_dtype = cp.float64
    data0 = data0.astype(data_dtype, copy=False)
    X = data0.reshape(nsamples, n3)
    # Demean:
    X = X - X.mean(axis=0, keepdims=True)
    # compute the covariance matrix, x
    r = cp.dot(X.T, X)
    # (symmetric) eigen decomposition
    w, v = cp.linalg.eigh(r)
    # project smallest eigenvector/value onto the data space
    I = X.dot(v[:, 0:1]).reshape(n0, n1, n2)
    del r, w, v

    s = 2 * patch_radius + 1
    sum_reg = ndi.uniform_filter(I, size=s)
    sigma_sq = I - sum_reg
    sigma_sq *= sigma_sq

    # find the SNR and make the correction for bias due to Rician noise:
    if correct_bias:
        mean = ndi.uniform_filter(data0.mean(-1), size=s, mode="reflect")
        snr = mean / cp.sqrt(sigma_sq)
        snr_sq = snr * snr
        # snr_sq = cp.asnumpy(snr_sq)  # transfer to host to use sps.iv
        # xi is practically equal to 1 above 37.4, and we overflow, raising
        # warnings and creating ot-a-numbers.
        # Instead, we will replace these values with 1 below
        with np.errstate(over="ignore", invalid="ignore"):
            tmp1 = snr_sq / 4
            tmp = sps.i0(tmp1)
            tmp *= 2 + snr_sq
            tmp += snr_sq * sps.i1(tmp1)
            tmp *= tmp
            tmp *= (np.pi / 8) * cp.exp(-snr_sq / 2)
            xi = 2 + snr_sq - tmp
            xi = xi.astype(data_dtype, copy=False)
            # xi = (2 + snr_sq - (np.pi / 8) * cp.exp(-snr_sq / 2) *
            #       ((2 + snr_sq) * sps.i0(snr_sq / 4) +
            #       (snr_sq) * sps.i1(snr_sq / 4)) ** 2).astype(float)
        xi[snr > 37.4] = 1
        sigma_corr = sigma_sq / xi
        sigma_corr[cp.isnan(sigma_corr)] = 0
    else:
        sigma_corr = sigma_sq

    if smooth is not None:
        ndi.gaussian_filter(sigma_corr, smooth, output=sigma_corr)

    cp.sqrt(sigma_corr, out=sigma_corr)
    return sigma_corr
Пример #11
0
def gaussian(
    image,
    sigma=1,
    output=None,
    mode="nearest",
    cval=0,
    multichannel=None,
    preserve_range=False,
    truncate=4.0,
):
    """Multi-dimensional Gaussian filter.

    Parameters
    ----------
    image : array-like
        Input image (grayscale or color) to filter.
    sigma : scalar or sequence of scalars, optional
        Standard deviation for Gaussian kernel. The standard
        deviations of the Gaussian filter are given for each axis as a
        sequence, or as a single number, in which case it is equal for
        all axes.
    output : array, optional
        The ``output`` parameter passes an array in which to store the
        filter output.
    mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional
        The ``mode`` parameter determines how the array borders are
        handled, where ``cval`` is the value when mode is equal to
        'constant'. Default is 'nearest'.
    cval : scalar, optional
        Value to fill past edges of input if ``mode`` is 'constant'. Default
        is 0.0
    multichannel : bool, optional (default: None)
        Whether the last axis of the image is to be interpreted as multiple
        channels. If True, each channel is filtered separately (channels are
        not mixed together). Only 3 channels are supported. If ``None``,
        the function will attempt to guess this, and raise a warning if
        ambiguous, when the array has shape (M, N, 3).
    preserve_range : bool, optional
        Whether to keep the original range of values. Otherwise, the input
        image is converted according to the conventions of ``img_as_float``.
        Also see
        https://scikit-image.org/docs/dev/user_guide/data_types.html
    truncate : float, optional
        Truncate the filter at this many standard deviations.

    Returns
    -------
    filtered_image : ndarray
        the filtered array

    Notes
    -----
    This function is a wrapper around :func:`scipy.ndi.gaussian_filter`.

    Integer arrays are converted to float.

    The ``output`` should be floating point data type since gaussian converts
    to float provided ``image``. If ``output`` is not provided, another array
    will be allocated and returned as the result.

    The multi-dimensional filter is implemented as a sequence of
    one-dimensional convolution filters. The intermediate arrays are
    stored in the same data type as the output. Therefore, for output
    types with a limited precision, the results may be imprecise
    because intermediate results may be stored with insufficient
    precision.

    Examples
    --------

    >>> import cupy as cp
    >>> a = cp.zeros((3, 3))
    >>> a[1, 1] = 1
    >>> a
    array([[0., 0., 0.],
           [0., 1., 0.],
           [0., 0., 0.]])
    >>> gaussian(a, sigma=0.4)  # mild smoothing
    array([[0.00163116, 0.03712502, 0.00163116],
           [0.03712502, 0.84496158, 0.03712502],
           [0.00163116, 0.03712502, 0.00163116]])
    >>> gaussian(a, sigma=1)  # more smoothing
    array([[0.05855018, 0.09653293, 0.05855018],
           [0.09653293, 0.15915589, 0.09653293],
           [0.05855018, 0.09653293, 0.05855018]])
    >>> # Several modes are possible for handling boundaries
    >>> gaussian(a, sigma=1, mode='reflect')
    array([[0.08767308, 0.12075024, 0.08767308],
           [0.12075024, 0.16630671, 0.12075024],
           [0.08767308, 0.12075024, 0.08767308]])
    >>> # For RGB images, each is filtered separately
    >>> from skimage.data import astronaut
    >>> image = astronaut()
    >>> filtered_img = gaussian(image, sigma=1, multichannel=True)

    """

    spatial_dims = None
    try:
        spatial_dims = _guess_spatial_dimensions(image)
    except ValueError:
        spatial_dims = image.ndim
    if spatial_dims is None and multichannel is None:
        msg = ("Images with dimensions (M, N, 3) are interpreted as 2D+RGB "
               "by default. Use `multichannel=False` to interpret as "
               "3D image with last dimension of length 3.")
        warn(RuntimeWarning(msg))
        multichannel = True
    # Note: slight refactor to avoid overhead of cp.any(cp.asarray(sigma))
    sigma_msg = "Sigma values less than zero are not valid"
    if not isinstance(sigma, Iterable):
        if sigma < 0:
            raise ValueError(sigma_msg)
    elif any(s < 0 for s in sigma):
        raise ValueError(sigma_msg)
    if multichannel:
        # do not filter across channels
        if not isinstance(sigma, Iterable):
            sigma = [sigma] * (image.ndim - 1)
        if len(sigma) != image.ndim:
            sigma = tuple(sigma) + (0, )  # zero on channels axis
        sigma = tuple(sigma)
    image = convert_to_float(image, preserve_range)
    if output is None:
        output = cp.empty_like(image)
    elif not np.issubdtype(output.dtype, np.floating):
        raise ValueError("Provided output data type is not float")
    ndi.gaussian_filter(image,
                        sigma,
                        output=output,
                        mode=mode,
                        cval=cval,
                        truncate=truncate)
    return output
Пример #12
0
def structure_tensor(image, sigma=1, mode="constant", cval=0, order=None):
    """Compute structure tensor using sum of squared differences.

    The (2-dimensional) structure tensor A is defined as::

        A = [Arr Arc]
            [Arc Acc]

    which is approximated by the weighted sum of squared differences in a local
    window around each pixel in the image. This formula can be extended to a
    larger number of dimensions (see [1]_).

    Parameters
    ----------
    image : ndarray
        Input image.
    sigma : float, optional
        Standard deviation used for the Gaussian kernel, which is used as a
        weighting function for the local summation of squared differences.
    mode : {'constant', 'reflect', 'wrap', 'nearest', 'mirror'}, optional
        How to handle values outside the image borders.
    cval : float, optional
        Used in conjunction with mode 'constant', the value outside
        the image boundaries.
    order : {'rc', 'xy'}, optional
        NOTE: Only applies in 2D. Higher dimensions must always use 'rc' order.
        This parameter allows for the use of reverse or forward order of
        the image axes in gradient computation. 'rc' indicates the use of
        the first axis initially (Arr, Arc, Acc), whilst 'xy' indicates the
        usage of the last axis initially (Axx, Axy, Ayy).

    Returns
    -------
    A_elems : list of ndarray
        Upper-diagonal elements of the structure tensor for each pixel in the
        input image.

    See also
    --------
    structure_tensor_eigenvalues

    References
    ----------
    .. [1] https://en.wikipedia.org/wiki/Structure_tensor\

    Examples
    --------
    >>> from skimage.feature import structure_tensor
    >>> square = np.zeros((5, 5))
    >>> square[2, 2] = 1
    >>> Arr, Arc, Acc = structure_tensor(square, sigma=0.1, order="rc")
    >>> Acc
    array([[0., 0., 0., 0., 0.],
           [0., 1., 0., 1., 0.],
           [0., 4., 0., 4., 0.],
           [0., 1., 0., 1., 0.],
           [0., 0., 0., 0., 0.]])

    """
    if order == "xy" and image.ndim > 2:
        raise ValueError('Only "rc" order is supported for dim > 2.')

    if order is None:
        if image.ndim == 2:
            # The legacy 2D code followed (x, y) convention, so we swap the
            # axis order to maintain compatibility with old code
            warn(
                "deprecation warning: the default order of the structure "
                'tensor values will be "row-column" instead of "xy" starting '
                'in skimage version 0.20. Use order="rc" or order="xy" to '
                'set this explicitly.  (Specify order="xy" to maintain the '
                "old behavior.)",
                category=FutureWarning,
                stacklevel=2,
            )
            order = "xy"
        else:
            order = "rc"

    image = _prepare_grayscale_input_nD(image)

    derivatives = _compute_derivatives(image, mode=mode, cval=cval)

    if order == "xy":
        derivatives = reversed(derivatives)

    # structure tensor
    A_elems = [
        ndi.gaussian_filter(der0 * der1, sigma, mode=mode, cval=cval)
        for der0, der1 in combinations_with_replacement(derivatives, 2)
    ]

    return A_elems
Пример #13
0
def hessian_matrix(image, sigma=1, mode="constant", cval=0, order="rc"):
    """Compute Hessian matrix.

    The Hessian matrix is defined as::

        H = [Hrr Hrc]
            [Hrc Hcc]

    which is computed by convolving the image with the second derivatives
    of the Gaussian kernel in the respective r- and c-directions.

    Parameters
    ----------
    image : ndarray
        Input image.
    sigma : float
        Standard deviation used for the Gaussian kernel, which is used as
        weighting function for the auto-correlation matrix.
    mode : {'constant', 'reflect', 'wrap', 'nearest', 'mirror'}, optional
        How to handle values outside the image borders.
    cval : float, optional
        Used in conjunction with mode 'constant', the value outside
        the image boundaries.
    order : {'rc', 'xy'}, optional
        This parameter allows for the use of reverse or forward order of
        the image axes in gradient computation. 'rc' indicates the use of
        the first axis initially (Hrr, Hrc, Hcc), whilst 'xy' indicates the
        usage of the last axis initially (Hxx, Hxy, Hyy)

    Returns
    -------
    Hrr : ndarray
        Element of the Hessian matrix for each pixel in the input image.
    Hrc : ndarray
        Element of the Hessian matrix for each pixel in the input image.
    Hcc : ndarray
        Element of the Hessian matrix for each pixel in the input image.

    Examples
    --------
    >>> import cupy as cp
    >>> from cupyimg.skimage.feature import hessian_matrix
    >>> square = cp.zeros((5, 5))
    >>> square[2, 2] = 4
    >>> Hrr, Hrc, Hcc = hessian_matrix(square, sigma=0.1, order='rc')
    >>> Hrc
    array([[ 0.,  0.,  0.,  0.,  0.],
           [ 0.,  1.,  0., -1.,  0.],
           [ 0.,  0.,  0.,  0.,  0.],
           [ 0., -1.,  0.,  1.,  0.],
           [ 0.,  0.,  0.,  0.,  0.]])
    """

    image = img_as_float(image)

    gaussian_filtered = ndi.gaussian_filter(image,
                                            sigma=sigma,
                                            mode=mode,
                                            cval=cval)

    gradients = cnp.gradient(gaussian_filtered)
    axes = range(image.ndim)

    if order == "rc":
        axes = reversed(axes)

    H_elems = [
        cnp.gradient(gradients[ax0], axis=ax1)
        for ax0, ax1 in combinations_with_replacement(axes, 2)
    ]

    return H_elems
Пример #14
0
def daisy(
    image,
    step=4,
    radius=15,
    rings=3,
    histograms=8,
    orientations=8,
    normalization="l1",
    sigmas=None,
    ring_radii=None,
    visualize=False,
):
    """Extract DAISY feature descriptors densely for the given image.

    DAISY is a feature descriptor similar to SIFT formulated in a way that
    allows for fast dense extraction. Typically, this is practical for
    bag-of-features image representations.

    The implementation follows Tola et al. [1]_ but deviate on the following
    points:

      * Histogram bin contribution are smoothed with a circular Gaussian
        window over the tonal range (the angular range).
      * The sigma values of the spatial Gaussian smoothing in this code do not
        match the sigma values in the original code by Tola et al. [2]_. In
        their code, spatial smoothing is applied to both the input image and
        the center histogram. However, this smoothing is not documented in [1]_
        and, therefore, it is omitted.

    Parameters
    ----------
    image : (M, N) array
        Input image (grayscale).
    step : int, optional
        Distance between descriptor sampling points.
    radius : int, optional
        Radius (in pixels) of the outermost ring.
    rings : int, optional
        Number of rings.
    histograms  : int, optional
        Number of histograms sampled per ring.
    orientations : int, optional
        Number of orientations (bins) per histogram.
    normalization : [ 'l1' | 'l2' | 'daisy' | 'off' ], optional
        How to normalize the descriptors

          * 'l1': L1-normalization of each descriptor.
          * 'l2': L2-normalization of each descriptor.
          * 'daisy': L2-normalization of individual histograms.
          * 'off': Disable normalization.

    sigmas : 1D array of float, optional
        Standard deviation of spatial Gaussian smoothing for the center
        histogram and for each ring of histograms. The array of sigmas should
        be sorted from the center and out. I.e. the first sigma value defines
        the spatial smoothing of the center histogram and the last sigma value
        defines the spatial smoothing of the outermost ring. Specifying sigmas
        overrides the following parameter.

            ``rings = len(sigmas) - 1``

    ring_radii : 1D array of int, optional
        Radius (in pixels) for each ring. Specifying ring_radii overrides the
        following two parameters.

            ``rings = len(ring_radii)``
            ``radius = ring_radii[-1]``

        If both sigmas and ring_radii are given, they must satisfy the
        following predicate since no radius is needed for the center
        histogram.

            ``len(ring_radii) == len(sigmas) + 1``

    visualize : bool, optional
        Generate a visualization of the DAISY descriptors

    Returns
    -------
    descs : array
        Grid of DAISY descriptors for the given image as an array
        dimensionality  (P, Q, R) where

            ``P = ceil((M - radius*2) / step)``
            ``Q = ceil((N - radius*2) / step)``
            ``R = (rings * histograms + 1) * orientations``

    descs_img : (M, N, 3) array (only if visualize==True)
        Visualization of the DAISY descriptors.

    References
    ----------
    .. [1] Tola et al. "Daisy: An efficient dense descriptor applied to wide-
           baseline stereo." Pattern Analysis and Machine Intelligence, IEEE
           Transactions on 32.5 (2010): 815-830.
    .. [2] http://cvlab.epfl.ch/software/daisy
    """

    check_nD(image, 2, "img")

    image = img_as_float(image)

    # Validate parameters.
    if (
        sigmas is not None
        and ring_radii is not None
        and len(sigmas) - 1 != len(ring_radii)
    ):
        raise ValueError("`len(sigmas)-1 != len(ring_radii)`")
    if ring_radii is not None:
        rings = len(ring_radii)
        radius = ring_radii[-1]
    if sigmas is not None:
        rings = len(sigmas) - 1
    if sigmas is None:
        sigmas = [radius * (i + 1) / float(2 * rings) for i in range(rings)]
    if ring_radii is None:
        ring_radii = [radius * (i + 1) / float(rings) for i in range(rings)]
    if normalization not in ["l1", "l2", "daisy", "off"]:
        raise ValueError("Invalid normalization method.")

    # Compute image derivatives.
    dx = cp.zeros(image.shape)
    dy = cp.zeros(image.shape)
    dx[:, :-1] = cp.diff(image, n=1, axis=1)
    dy[:-1, :] = cp.diff(image, n=1, axis=0)

    # Compute gradient orientation and magnitude and their contribution
    # to the histograms.
    grad_mag = cp.sqrt(dx * dx + dy * dy)
    grad_ori = cp.arctan2(dy, dx)
    pi = cp.pi
    orientation_kappa = orientations / pi
    orientation_angles = [
        2 * o * pi / orientations - pi for o in range(orientations)
    ]
    hist = cp.empty((orientations,) + image.shape, dtype=float)
    for i, o in enumerate(orientation_angles):
        # Weigh bin contribution by the circular normal distribution
        hist[i, :, :] = cp.exp(orientation_kappa * cp.cos(grad_ori - o))
        # Weigh bin contribution by the gradient magnitude
        hist[i, :, :] = cp.multiply(hist[i, :, :], grad_mag)

    # Smooth orientation histograms for the center and all rings.
    sigmas = [sigmas[0]] + sigmas
    hist_smooth = cp.empty((rings + 1,) + hist.shape, dtype=float)
    for i in range(rings + 1):
        for j in range(orientations):
            hist_smooth[i, j, :, :] = gaussian_filter(
                hist[j, :, :], sigma=sigmas[i]
            )

    # Assemble descriptor grid.
    theta = [2 * pi * j / histograms for j in range(histograms)]
    desc_dims = (rings * histograms + 1) * orientations
    descs = cp.empty(
        (desc_dims, image.shape[0] - 2 * radius, image.shape[1] - 2 * radius)
    )
    descs[:orientations, :, :] = hist_smooth[
        0, :, radius:-radius, radius:-radius
    ]
    idx = orientations
    # ring_radii = ring_radii.get()
    # theta = theta.get()
    for i in range(rings):
        for j in range(histograms):
            y_min = radius + int(round(ring_radii[i] * math.sin(theta[j])))
            y_max = descs.shape[1] + y_min
            x_min = radius + int(round(ring_radii[i] * math.cos(theta[j])))
            x_max = descs.shape[2] + x_min
            descs[idx : idx + orientations, :, :] = hist_smooth[
                i + 1, :, y_min:y_max, x_min:x_max
            ]
            idx += orientations
    descs = descs[:, ::step, ::step]
    descs = descs.swapaxes(0, 1).swapaxes(1, 2)

    # Normalize descriptors.
    if normalization != "off":
        descs += 1e-10
        if normalization == "l1":
            descs /= cp.sum(descs, axis=2)[:, :, cp.newaxis]
        elif normalization == "l2":
            descs /= cp.sqrt(cp.sum(descs * descs, axis=2))[:, :, cp.newaxis]
        elif normalization == "daisy":
            for i in range(0, desc_dims, orientations):
                dtmp = descs[:, :, i : i + orientations]
                norms = cp.sqrt(cp.sum(dtmp * dtmp, axis=2))
                descs[:, :, i : i + orientations] /= norms[:, :, cp.newaxis]

    if visualize:
        from skimage import draw
        from skimage.color import gray2rgb

        image = cp.asnumpy(image)
        descs_img = gray2rgb(image)
        for i in range(descs.shape[0]):
            for j in range(descs.shape[1]):
                # Draw center histogram sigma
                color = [1, 0, 0]
                desc_y = i * step + radius
                desc_x = j * step + radius
                rows, cols, val = draw.circle_perimeter_aa(
                    desc_y, desc_x, int(sigmas[0])
                )
                draw.set_color(descs_img, (rows, cols), color, alpha=val)
                max_bin = cp.max(descs[i, j, :])
                for o_num, o in enumerate(orientation_angles):
                    # Draw center histogram bins
                    bin_size = descs[i, j, o_num] / max_bin
                    dy = sigmas[0] * bin_size * math.sin(o)
                    dx = sigmas[0] * bin_size * math.cos(o)
                    rows, cols, val = draw.line_aa(
                        desc_y, desc_x, int(desc_y + dy), int(desc_x + dx)
                    )
                    draw.set_color(descs_img, (rows, cols), color, alpha=val)
                for r_num, r in enumerate(ring_radii):
                    color_offset = float(1 + r_num) / rings
                    color = (1 - color_offset, 1, color_offset)
                    for t_num, t in enumerate(theta):
                        # Draw ring histogram sigmas
                        hist_y = desc_y + int(round(r * math.sin(t)))
                        hist_x = desc_x + int(round(r * math.cos(t)))
                        rows, cols, val = draw.circle_perimeter_aa(
                            hist_y, hist_x, int(sigmas[r_num + 1])
                        )
                        draw.set_color(
                            descs_img, (rows, cols), color, alpha=val
                        )
                        for o_num, o in enumerate(orientation_angles):
                            # Draw histogram bins
                            bin_size = descs[
                                i,
                                j,
                                orientations
                                + r_num * histograms * orientations
                                + t_num * orientations
                                + o_num,
                            ]
                            bin_size /= max_bin
                            dy = sigmas[r_num + 1] * bin_size * math.sin(o)
                            dx = sigmas[r_num + 1] * bin_size * math.cos(o)
                            rows, cols, val = draw.line_aa(
                                hist_y,
                                hist_x,
                                int(hist_y + dy),
                                int(hist_x + dx),
                            )
                            draw.set_color(
                                descs_img, (rows, cols), color, alpha=val
                            )
        return descs, descs_img
    else:
        return descs