from itertools import combinations_with_replacement

import numpy as np
from scipy import ndimage as ndi
from scipy import stats
from scipy import spatial

from ..util import img_as_float
from .peak import peak_local_max
from .util import _prepare_grayscale_input_2D, _prepare_grayscale_input_nD
from .corner_cy import _corner_fast
from ._hessian_det_appx import _hessian_matrix_det
from ..transform import integral_image
from .._shared.utils import safe_as_int
from .corner_cy import _corner_moravec, _corner_orientations
from warnings import warn


def _compute_derivatives(image, mode='constant', cval=0):
    """Compute derivatives in axis directions using the Sobel operator.

    Parameters
    ----------
    image : ndarray
        Input image.
    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
    -------
    derivatives : list of ndarray
        Derivatives in each axis direction.

    """

    derivatives = [ndi.sobel(image, axis=i, mode=mode, cval=cval)
                   for i in range(image.ndim)]

    return derivatives


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.

    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.]])

    See also
    --------
    structure_tensor_eigenvalues

    References
    ----------
    .. [1] https://en.wikipedia.org/wiki/Structure_tensor
    """
    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


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
    --------
    >>> from skimage.feature import hessian_matrix
    >>> square = np.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 = np.gradient(gaussian_filtered)
    axes = range(image.ndim)

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

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

    return H_elems


def hessian_matrix_det(image, sigma=1, approximate=True):
    """Compute the approximate Hessian Determinant over an image.

    The 2D approximate method uses box filters over integral images to
    compute the approximate Hessian Determinant, as described in [1]_.

    Parameters
    ----------
    image : array
        The image over which to compute Hessian Determinant.
    sigma : float, optional
        Standard deviation used for the Gaussian kernel, used for the Hessian
        matrix.
    approximate : bool, optional
        If ``True`` and the image is 2D, use a much faster approximate
        computation. This argument has no effect on 3D and higher images.

    Returns
    -------
    out : array
        The array of the Determinant of Hessians.

    References
    ----------
    .. [1] <NAME>, <NAME>, <NAME>, <NAME>,
           "SURF: Speeded Up Robust Features"
           ftp://ftp.vision.ee.ethz.ch/publications/articles/eth_biwi_00517.pdf

    Notes
    -----
    For 2D images when ``approximate=True``, the running time of this method
    only depends on size of the image. It is independent of `sigma` as one
    would expect. The downside is that the result for `sigma` less than `3`
    is not accurate, i.e., not similar to the result obtained if someone
    computed the Hessian and took its determinant.
    """
    image = img_as_float(image)
    if image.ndim == 2 and approximate:
        integral = integral_image(image)
        return np.array(_hessian_matrix_det(integral, sigma))
    else:  # slower brute-force implementation for nD images
        hessian_mat_array = _symmetric_image(hessian_matrix(image, sigma))
        return np.linalg.det(hessian_mat_array)


def _image_orthogonal_matrix22_eigvals(M00, M01, M11):
    l1 = (M00 + M11) / 2 + np.sqrt(4 * M01 ** 2 + (M00 - M11) ** 2) / 2
    l2 = (M00 + M11) / 2 - np.sqrt(4 * M01 ** 2 + (M00 - M11) ** 2) / 2
    return l1, l2


def _symmetric_compute_eigenvalues(S_elems):
    """Compute eigenvalues from the upperdiagonal entries of a symmetric matrix

    Parameters
    ----------
    S_elems : list of ndarray
        The upper-diagonal elements of the matrix, as returned by
        `hessian_matrix` or `structure_tensor`.

    Returns
    -------
    eigs : ndarray
        The eigenvalues of the matrix, in decreasing order. The eigenvalues are
        the leading dimension. That is, ``eigs[i, j, k]`` contains the
        ith-largest eigenvalue at position (j, k).
    """

    if len(S_elems) == 3:  # Use fast Cython code for 2D
        eigs = np.stack(_image_orthogonal_matrix22_eigvals(*S_elems))
    else:
        matrices = _symmetric_image(S_elems)
        # eigvalsh returns eigenvalues in increasing order. We want decreasing
        eigs = np.linalg.eigvalsh(matrices)[..., ::-1]
        leading_axes = tuple(range(eigs.ndim - 1))
        eigs = np.transpose(eigs, (eigs.ndim - 1,) + leading_axes)
    return eigs


def _symmetric_image(S_elems):
    """Convert the upper-diagonal elements of a matrix to the full
    symmetric matrix.

    Parameters
    ----------
    S_elems : list of array
        The upper-diagonal elements of the matrix, as returned by
        `hessian_matrix` or `structure_tensor`.

    Returns
    -------
    image : array
        An array of shape ``(M, N[, ...], image.ndim, image.ndim)``,
        containing the matrix corresponding to each coordinate.
    """
    image = S_elems[0]
    symmetric_image = np.zeros(image.shape + (image.ndim, image.ndim))
    for idx, (row, col) in \
            enumerate(combinations_with_replacement(range(image.ndim), 2)):
        symmetric_image[..., row, col] = S_elems[idx]
        symmetric_image[..., col, row] = S_elems[idx]
    return symmetric_image


def structure_tensor_eigenvalues(A_elems):
    """Compute eigenvalues of structure tensor.

    Parameters
    ----------
    A_elems : list of ndarray
        The upper-diagonal elements of the structure tensor, as returned
        by `structure_tensor`.

    Returns
    -------
    ndarray
        The eigenvalues of the structure tensor, in decreasing order. The
        eigenvalues are the leading dimension. That is, the coordinate
        [i, j, k] corresponds to the ith-largest eigenvalue at position (j, k).

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

    See also
    --------
    structure_tensor
    """
    return _symmetric_compute_eigenvalues(A_elems)


def structure_tensor_eigvals(Axx, Axy, Ayy):
    """Compute eigenvalues of structure tensor.

    Parameters
    ----------
    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.

    Returns
    -------
    l1 : ndarray
        Larger eigen value for each input matrix.
    l2 : ndarray
        Smaller eigen value for each input matrix.

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

    """
    warn('deprecation warning: the function structure_tensor_eigvals is '
         'deprecated and will be removed in version 0.20. Please use '
         'structure_tensor_eigenvalues instead.',
         category=FutureWarning, stacklevel=2)

    return _image_orthogonal_matrix22_eigvals(Axx, Axy, Ayy)


def hessian_matrix_eigvals(H_elems):
    """Compute eigenvalues of Hessian matrix.

    Parameters
    ----------
    H_elems : list of ndarray
        The upper-diagonal elements of the Hessian matrix, as returned
        by `hessian_matrix`.

    Returns
    -------
    eigs : ndarray
        The eigenvalues of the Hessian matrix, in decreasing order. The
        eigenvalues are the leading dimension. That is, ``eigs[i, j, k]``
        contains the ith-largest eigenvalue at position (j, k).

    Examples
    --------
    >>> from skimage.feature import hessian_matrix, hessian_matrix_eigvals
    >>> square = np.zeros((5, 5))
    >>> square[2, 2] = 4
    >>> H_elems = hessian_matrix(square, sigma=0.1, order='rc')
    >>> hessian_matrix_eigvals(H_elems)[0]
    array([[ 0.,  0.,  2.,  0.,  0.],
           [ 0.,  1.,  0.,  1.,  0.],
           [ 2.,  0., -2.,  0.,  2.],
           [ 0.,  1.,  0.,  1.,  0.],
           [ 0.,  0.,  2.,  0.,  0.]])
    """
    return _symmetric_compute_eigenvalues(H_elems)


def shape_index(image, sigma=1, mode='constant', cval=0):
    """Compute the shape index.

    The shape index, as defined by Koenderink & <NAME> [1]_, is a
    single valued measure of local curvature, assuming the image as a 3D plane
    with intensities representing heights.

    It is derived from the eigen values of the Hessian, and its
    value ranges from -1 to 1 (and is undefined (=NaN) in *flat* regions),
    with following ranges representing following shapes:

    .. table:: Ranges of the shape index and corresponding shapes.

      ===================  =============
      Interval (s in ...)  Shape
      ===================  =============
      [  -1, -7/8)         Spherical cup
      [-7/8, -5/8)         Through
      [-5/8, -3/8)         Rut
      [-3/8, -1/8)         Saddle rut
      [-1/8, +1/8)         Saddle
      [+1/8, +3/8)         Saddle ridge
      [+3/8, +5/8)         Ridge
      [+5/8, +7/8)         Dome
      [+7/8,   +1]         Spherical cap
      ===================  =============

    Parameters
    ----------
    image : ndarray
        Input image.
    sigma : float, optional
        Standard deviation used for the Gaussian kernel, which is used for
        smoothing the input data before Hessian eigen value calculation.
    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
    -------
    s : ndarray
        Shape index

    References
    ----------
    .. [1] <NAME>. & <NAME>.,
           "Surface shape and curvature scales",
           Image and Vision Computing, 1992, 10, 557-564.
           :DOI:`10.1016/0262-8856(92)90076-F`

    Examples
    --------
    >>> from skimage.feature import shape_index
    >>> square = np.zeros((5, 5))
    >>> square[2, 2] = 4
    >>> s = shape_index(square, sigma=0.1)
    >>> s
    array([[ nan,  nan, -0.5,  nan,  nan],
           [ nan, -0. ,  nan, -0. ,  nan],
           [-0.5,  nan, -1. ,  nan, -0.5],
           [ nan, -0. ,  nan, -0. ,  nan],
           [ nan,  nan, -0.5,  nan,  nan]])
    """

    H = hessian_matrix(image, sigma=sigma, mode=mode, cval=cval, order='rc')
    l1, l2 = hessian_matrix_eigvals(H)

    return (2.0 / np.pi) * np.arctan((l2 + l1) / (l2 - l1))


def corner_kitchen_rosenfeld(image, mode='constant', cval=0):
    """Compute Kitchen and Rosenfeld corner measure response image.

    The corner measure is calculated as follows::

        (imxx * imy**2 + imyy * imx**2 - 2 * imxy * imx * imy)
            / (imx**2 + imy**2)

    Where imx and imy are the first and imxx, imxy, imyy the second
    derivatives.

    Parameters
    ----------
    image : ndarray
        Input image.
    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
    -------
    response : ndarray
        Kitchen and Rosenfeld response image.

    References
    ----------
    .. [1] <NAME>., & <NAME>. (1982). Gray-level corner detection.
           Pattern recognition letters, 1(2), 95-102.
           :DOI:`10.1016/0167-8655(82)90020-4`
    """

    imy, imx = _compute_derivatives(image, mode=mode, cval=cval)
    imxy, imxx = _compute_derivatives(imx, mode=mode, cval=cval)
    imyy, imyx = _compute_derivatives(imy, mode=mode, cval=cval)

    numerator = (imxx * imy ** 2 + imyy * imx ** 2 - 2 * imxy * imx * imy)
    denominator = (imx ** 2 + imy ** 2)

    response = np.zeros_like(image, dtype=np.double)

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

    return response


def corner_harris(image, method='k', k=0.05, eps=1e-6, sigma=1):
    """Compute Harris corner measure response image.

    This corner detector uses information from the auto-correlation matrix A::

        A = [(imx**2)   (imx*imy)] = [Axx Axy]
            [(imx*imy)   (imy**2)]   [Axy Ayy]

    Where imx and imy are first derivatives, averaged with a gaussian filter.
    The corner measure is then defined as::

        det(A) - k * trace(A)**2

    or::

        2 * det(A) / (trace(A) + eps)

    Parameters
    ----------
    image : ndarray
        Input image.
    method : {'k', 'eps'}, optional
        Method to compute the response image from the auto-correlation matrix.
    k : float, optional
        Sensitivity factor to separate corners from edges, typically in range
        `[0, 0.2]`. Small values of k result in detection of sharp corners.
    eps : float, optional
        Normalisation factor (Noble's corner measure).
    sigma : float, optional
        Standard deviation used for the Gaussian kernel, which is used as
        weighting function for the auto-correlation matrix.

    Returns
    -------
    response : ndarray
        Harris response image.

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

    Examples
    --------
    >>> from skimage.feature import corner_harris, corner_peaks
    >>> square = np.zeros([10, 10])
    >>> square[2:8, 2:8] = 1
    >>> square.astype(int)
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
    >>> corner_peaks(corner_harris(square), min_distance=1)
    array([[2, 2],
           [2, 7],
           [7, 2],
           [7, 7]])

    """

    Arr, Arc, Acc = structure_tensor(image, sigma, order='rc')

    # determinant
    detA = Arr * Acc - Arc ** 2
    # trace
    traceA = Arr + Acc

    if method == 'k':
        response = detA - k * traceA ** 2
    else:
        response = 2 * detA / (traceA + eps)

    return response


def corner_shi_tomasi(image, sigma=1):
    """Compute Shi-Tomasi (Kanade-Tomasi) corner measure response image.

    This corner detector uses information from the auto-correlation matrix A::

        A = [(imx**2)   (imx*imy)] = [Axx Axy]
            [(imx*imy)   (imy**2)]   [Axy Ayy]

    Where imx and imy are first derivatives, averaged with a gaussian filter.
    The corner measure is then defined as the smaller eigenvalue of A::

        ((Axx + Ayy) - sqrt((Axx - Ayy)**2 + 4 * Axy**2)) / 2

    Parameters
    ----------
    image : ndarray
        Input image.
    sigma : float, optional
        Standard deviation used for the Gaussian kernel, which is used as
        weighting function for the auto-correlation matrix.

    Returns
    -------
    response : ndarray
        Shi-Tomasi response image.

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

    Examples
    --------
    >>> from skimage.feature import corner_shi_tomasi, corner_peaks
    >>> square = np.zeros([10, 10])
    >>> square[2:8, 2:8] = 1
    >>> square.astype(int)
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
    >>> corner_peaks(corner_shi_tomasi(square), min_distance=1)
    array([[2, 2],
           [2, 7],
           [7, 2],
           [7, 7]])

    """

    Arr, Arc, Acc = structure_tensor(image, sigma, order='rc')

    # minimum eigenvalue of A
    response = ((Arr + Acc) - np.sqrt((Arr - Acc) ** 2 + 4 * Arc ** 2)) / 2

    return response


def corner_foerstner(image, sigma=1):
    """Compute Foerstner corner measure response image.

    This corner detector uses information from the auto-correlation matrix A::

        A = [(imx**2)   (imx*imy)] = [Axx Axy]
            [(imx*imy)   (imy**2)]   [Axy Ayy]

    Where imx and imy are first derivatives, averaged with a gaussian filter.
    The corner measure is then defined as::

        w = det(A) / trace(A)           (size of error ellipse)
        q = 4 * det(A) / trace(A)**2    (roundness of error ellipse)

    Parameters
    ----------
    image : ndarray
        Input image.
    sigma : float, optional
        Standard deviation used for the Gaussian kernel, which is used as
        weighting function for the auto-correlation matrix.

    Returns
    -------
    w : ndarray
        Error ellipse sizes.
    q : ndarray
        Roundness of error ellipse.

    References
    ----------
    .. [1] <NAME>., & <NAME>. (1987, June). A fast operator for detection and
           precise location of distinct points, corners and centres of circular
           features. In Proc. ISPRS intercommission conference on fast processing of
           photogrammetric data (pp. 281-305).
           https://cseweb.ucsd.edu/classes/sp02/cse252/foerstner/foerstner.pdf
    .. [2] https://en.wikipedia.org/wiki/Corner_detection

    Examples
    --------
    >>> from skimage.feature import corner_foerstner, corner_peaks
    >>> square = np.zeros([10, 10])
    >>> square[2:8, 2:8] = 1
    >>> square.astype(int)
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
    >>> w, q = corner_foerstner(square)
    >>> accuracy_thresh = 0.5
    >>> roundness_thresh = 0.3
    >>> foerstner = (q > roundness_thresh) * (w > accuracy_thresh) * w
    >>> corner_peaks(foerstner, min_distance=1)
    array([[2, 2],
           [2, 7],
           [7, 2],
           [7, 7]])

    """

    Arr, Arc, Acc = structure_tensor(image, sigma, order='rc')

    # determinant
    detA = Arr * Acc - Arc ** 2
    # trace
    traceA = Arr + Acc

    w = np.zeros_like(image, dtype=np.double)
    q = np.zeros_like(image, dtype=np.double)

    mask = traceA != 0

    w[mask] = detA[mask] / traceA[mask]
    q[mask] = 4 * detA[mask] / traceA[mask] ** 2

    return w, q


def corner_fast(image, n=12, threshold=0.15):
    """Extract FAST corners for a given image.

    Parameters
    ----------
    image : 2D ndarray
        Input image.
    n : int, optional
        Minimum number of consecutive pixels out of 16 pixels on the circle
        that should all be either brighter or darker w.r.t testpixel.
        A point c on the circle is darker w.r.t test pixel p if
        `Ic < Ip - threshold` and brighter if `Ic > Ip + threshold`. Also
        stands for the n in `FAST-n` corner detector.
    threshold : float, optional
        Threshold used in deciding whether the pixels on the circle are
        brighter, darker or similar w.r.t. the test pixel. Decrease the
        threshold when more corners are desired and vice-versa.

    Returns
    -------
    response : ndarray
        FAST corner response image.

    References
    ----------
    .. [1] <NAME>., & <NAME>. (2006, May). Machine learning for high-speed
           corner detection. In European conference on computer vision (pp. 430-443).
           Springer, Berlin, Heidelberg.
           :DOI:`10.1007/11744023_34`
           http://www.edwardrosten.com/work/rosten_2006_machine.pdf
    .. [2] Wikipedia, "Features from accelerated segment test",
           https://en.wikipedia.org/wiki/Features_from_accelerated_segment_test

    Examples
    --------
    >>> from skimage.feature import corner_fast, corner_peaks
    >>> square = np.zeros((12, 12))
    >>> square[3:9, 3:9] = 1
    >>> square.astype(int)
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
    >>> corner_peaks(corner_fast(square, 9), min_distance=1)
    array([[3, 3],
           [3, 8],
           [8, 3],
           [8, 8]])

    """
    image = _prepare_grayscale_input_2D(image)

    image = np.ascontiguousarray(image)
    response = _corner_fast(image, n, threshold)
    return response


def corner_subpix(image, corners, window_size=11, alpha=0.99):
    """Determine subpixel position of corners.

    A statistical test decides whether the corner is defined as the
    intersection of two edges or a single peak. Depending on the classification
    result, the subpixel corner location is determined based on the local
    covariance of the grey-values. If the significance level for either
    statistical test is not sufficient, the corner cannot be classified, and
    the output subpixel position is set to NaN.

    Parameters
    ----------
    image : ndarray
        Input image.
    corners : (N, 2) ndarray
        Corner coordinates `(row, col)`.
    window_size : int, optional
        Search window size for subpixel estimation.
    alpha : float, optional
        Significance level for corner classification.

    Returns
    -------
    positions : (N, 2) ndarray
        Subpixel corner positions. NaN for "not classified" corners.

    References
    ----------
    .. [1] <NAME>., & <NAME>. (1987, June). A fast operator for detection and
           precise location of distinct points, corners and centres of circular
           features. In Proc. ISPRS intercommission conference on fast processing of
           photogrammetric data (pp. 281-305).
           https://cseweb.ucsd.edu/classes/sp02/cse252/foerstner/foerstner.pdf
    .. [2] https://en.wikipedia.org/wiki/Corner_detection

    Examples
    --------
    >>> from skimage.feature import corner_harris, corner_peaks, corner_subpix
    >>> img = np.zeros((10, 10))
    >>> img[:5, :5] = 1
    >>> img[5:, 5:] = 1
    >>> img.astype(int)
    array([[1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
           [1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
           [1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
           [1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
           [1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
           [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
           [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
           [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
           [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]])
    >>> coords = corner_peaks(corner_harris(img), min_distance=2)
    >>> coords_subpix = corner_subpix(img, coords, window_size=7)
    >>> coords_subpix
    array([[4.5, 4.5]])

    """

    # window extent in one direction
    wext = (window_size - 1) // 2

    image = np.pad(image, pad_width=wext, mode='constant', constant_values=0)

    # add pad width, make sure to not modify the input values in-place
    corners = safe_as_int(corners + wext)

    # normal equation arrays
    N_dot = np.zeros((2, 2), dtype=np.double)
    N_edge = np.zeros((2, 2), dtype=np.double)
    b_dot = np.zeros((2, ), dtype=np.double)
    b_edge = np.zeros((2, ), dtype=np.double)

    # critical statistical test values
    redundancy = window_size ** 2 - 2
    t_crit_dot = stats.f.isf(1 - alpha, redundancy, redundancy)
    t_crit_edge = stats.f.isf(alpha, redundancy, redundancy)

    # coordinates of pixels within window
    y, x = np.mgrid[- wext:wext + 1, - wext:wext + 1]

    corners_subpix = np.zeros_like(corners, dtype=np.double)

    for i, (y0, x0) in enumerate(corners):

        # crop window around corner + border for sobel operator
        miny = y0 - wext - 1
        maxy = y0 + wext + 2
        minx = x0 - wext - 1
        maxx = x0 + wext + 2
        window = image[miny:maxy, minx:maxx]

        winy, winx = _compute_derivatives(window, mode='constant', cval=0)

        # compute gradient squares and remove border
        winx_winx = (winx * winx)[1:-1, 1:-1]
        winx_winy = (winx * winy)[1:-1, 1:-1]
        winy_winy = (winy * winy)[1:-1, 1:-1]

        # sum of squared differences (mean instead of gaussian filter)
        Axx = np.sum(winx_winx)
        Axy = np.sum(winx_winy)
        Ayy = np.sum(winy_winy)

        # sum of squared differences weighted with coordinates
        # (mean instead of gaussian filter)
        bxx_x = np.sum(winx_winx * x)
        bxx_y = np.sum(winx_winx * y)
        bxy_x = np.sum(winx_winy * x)
        bxy_y = np.sum(winx_winy * y)
        byy_x = np.sum(winy_winy * x)
        byy_y = np.sum(winy_winy * y)

        # normal equations for subpixel position
        N_dot[0, 0] = Axx
        N_dot[0, 1] = N_dot[1, 0] = - Axy
        N_dot[1, 1] = Ayy

        N_edge[0, 0] = Ayy
        N_edge[0, 1] = N_edge[1, 0] = Axy
        N_edge[1, 1] = Axx

        b_dot[:] = bxx_y - bxy_x, byy_x - bxy_y
        b_edge[:] = byy_y + bxy_x, bxx_x + bxy_y

        # estimated positions
        try:
            est_dot = np.linalg.solve(N_dot, b_dot)
            est_edge = np.linalg.solve(N_edge, b_edge)
        except np.linalg.LinAlgError:
            # if image is constant the system is singular
            corners_subpix[i, :] = np.nan, np.nan
            continue

        # residuals
        ry_dot = y - est_dot[0]
        rx_dot = x - est_dot[1]
        ry_edge = y - est_edge[0]
        rx_edge = x - est_edge[1]
        # squared residuals
        rxx_dot = rx_dot * rx_dot
        rxy_dot = rx_dot * ry_dot
        ryy_dot = ry_dot * ry_dot
        rxx_edge = rx_edge * rx_edge
        rxy_edge = rx_edge * ry_edge
        ryy_edge = ry_edge * ry_edge

        # determine corner class (dot or edge)
        # variance for different models
        var_dot = np.sum(winx_winx * ryy_dot - 2 * winx_winy * rxy_dot
                         + winy_winy * rxx_dot)
        var_edge = np.sum(winy_winy * ryy_edge + 2 * winx_winy * rxy_edge
                          + winx_winx * rxx_edge)

        # test value (F-distributed)
        if var_dot < np.spacing(1) and var_edge < np.spacing(1):
            t = np.nan
        elif var_dot == 0:
            t = np.inf
        else:
            t = var_edge / var_dot

        # 1 for edge, -1 for dot, 0 for "not classified"
        corner_class = int(t < t_crit_edge) - int(t > t_crit_dot)

        if corner_class == -1:
            corners_subpix[i, :] = y0 + est_dot[0], x0 + est_dot[1]
        elif corner_class == 0:
            corners_subpix[i, :] = np.nan, np.nan
        elif corner_class == 1:
            corners_subpix[i, :] = y0 + est_edge[0], x0 + est_edge[1]

    # subtract pad width
    corners_subpix -= wext

    return corners_subpix


def corner_peaks(image, min_distance=1, threshold_abs=None, threshold_rel=None,
                 exclude_border=True, indices=True, num_peaks=np.inf,
                 footprint=None, labels=None, *, num_peaks_per_label=np.inf,
                 p_norm=np.inf):
    """Find peaks in corner measure response image.

    This differs from `skimage.feature.peak_local_max` in that it suppresses
    multiple connected peaks with the same accumulator value.

    Parameters
    ----------
    image : ndarray
        Input image.
    min_distance : int, optional
        The minimal allowed distance separating peaks.
    * : *
        See :py:meth:`skimage.feature.peak_local_max`.
    p_norm : float
        Which Minkowski p-norm to use. Should be in the range [1, inf].
        A finite large p may cause a ValueError if overflow can occur.
        ``inf`` corresponds to the Chebyshev distance and 2 to the
        Euclidean distance.

    Returns
    -------
    output : ndarray or ndarray of bools

        * If `indices = True`  : (row, column, ...) coordinates of peaks.
        * If `indices = False` : Boolean array shaped like `image`, with peaks
          represented by True values.

    See also
    --------
    skimage.feature.peak_local_max

    Notes
    -----
    .. versionchanged:: 0.18
        The default value of `threshold_rel` has changed to None, which
        corresponds to letting `skimage.feature.peak_local_max` decide on the
        default. This is equivalent to `threshold_rel=0`.

    The `num_peaks` limit is applied before suppression of connected peaks.
    To limit the number of peaks after suppression, set `num_peaks=np.inf` and
    post-process the output of this function.

    Examples
    --------
    >>> from skimage.feature import peak_local_max
    >>> response = np.zeros((5, 5))
    >>> response[2:4, 2:4] = 1
    >>> response
    array([[0., 0., 0., 0., 0.],
           [0., 0., 0., 0., 0.],
           [0., 0., 1., 1., 0.],
           [0., 0., 1., 1., 0.],
           [0., 0., 0., 0., 0.]])
    >>> peak_local_max(response)
    array([[2, 2],
           [2, 3],
           [3, 2],
           [3, 3]])
    >>> corner_peaks(response)
    array([[2, 2]])

    """
    if np.isinf(num_peaks):
        num_peaks = None

    # Get the coordinates of the detected peaks
    coords = peak_local_max(image, min_distance=min_distance,
                            threshold_abs=threshold_abs,
                            threshold_rel=threshold_rel,
                            exclude_border=exclude_border,
                            num_peaks=np.inf,
                            footprint=footprint, labels=labels,
                            num_peaks_per_label=num_peaks_per_label)

    if len(coords):
        # Use KDtree to find the peaks that are too close to each other
        tree = spatial.cKDTree(coords)

        rejected_peaks_indices = set()
        for idx, point in enumerate(coords):
            if idx not in rejected_peaks_indices:
                candidates = tree.query_ball_point(point, r=min_distance,
                                                   p=p_norm)
                candidates.remove(idx)
                rejected_peaks_indices.update(candidates)

        # Remove the peaks that are too close to each other
        coords = np.delete(coords, tuple(rejected_peaks_indices),
                           axis=0)[:num_peaks]

    if indices:
        return coords

    peaks = np.zeros_like(image, dtype=bool)
    peaks[tuple(coords.T)] = True

    return peaks


def corner_moravec(image, window_size=1):
    """Compute Moravec corner measure response image.

    This is one of the simplest corner detectors and is comparatively fast but
    has several limitations (e.g. not rotation invariant).

    Parameters
    ----------
    image : ndarray
        Input image.
    window_size : int, optional
        Window size.

    Returns
    -------
    response : ndarray
        Moravec response image.

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

    Examples
    --------
    >>> from skimage.feature import corner_moravec
    >>> square = np.zeros([7, 7])
    >>> square[3, 3] = 1
    >>> square.astype(int)
    array([[0, 0, 0, 0, 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, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0]])
    >>> corner_moravec(square).astype(int)
    array([[0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0],
           [0, 0, 1, 1, 1, 0, 0],
           [0, 0, 1, 2, 1, 0, 0],
           [0, 0, 1, 1, 1, 0, 0],
           [0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0]])
    """
    return _corner_moravec(image, window_size)


def corner_orientations(image, corners, mask):
    """Compute the orientation of corners.

    The orientation of corners is computed using the first order central moment
    i.e. the center of mass approach. The corner orientation is the angle of
    the vector from the corner coordinate to the intensity centroid in the
    local neighborhood around the corner calculated using first order central
    moment.

    Parameters
    ----------
    image : 2D array
        Input grayscale image.
    corners : (N, 2) array
        Corner coordinates as ``(row, col)``.
    mask : 2D array
        Mask defining the local neighborhood of the corner used for the
        calculation of the central moment.

    Returns
    -------
    orientations : (N, 1) array
        Orientations of corners in the range [-pi, pi].

    References
    ----------
    .. [1] <NAME>, <NAME>, <NAME> and <NAME>
          "ORB : An efficient alternative to SIFT and SURF"
          http://www.vision.cs.chubu.ac.jp/CV-R/pdf/Rublee_iccv2011.pdf
    .. [2] <NAME>, "Measuring Corner Properties"
          http://users.cs.cf.ac.uk/Paul.Rosin/corner2.pdf

    Examples
    --------
    >>> from skimage.morphology import octagon
    >>> from skimage.feature import (corner_fast, corner_peaks,
    ...                              corner_orientations)
    >>> square = np.zeros((12, 12))
    >>> square[3:9, 3:9] = 1
    >>> square.astype(int)
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
    >>> corners = corner_peaks(corner_fast(square, 9), min_distance=1)
    >>> corners
    array([[3, 3],
           [3, 8],
           [8, 3],
           [8, 8]])
    >>> orientations = corner_orientations(square, corners, octagon(3, 2))
    >>> np.rad2deg(orientations)
    array([  45.,  135.,  -45., -135.])

    """
    image = _prepare_grayscale_input_2D(image)
    return _corner_orientations(image, corners, mask)

from itertools import combinations_with_replacement

import numpy as np
from scipy import ndimage as ndi
from scipy import stats
from scipy import spatial

from ..util import img_as_float
from .peak import peak_local_max
from .util import _prepare_grayscale_input_2D, _prepare_grayscale_input_nD
from .corner_cy import _corner_fast
from ._hessian_det_appx import _hessian_matrix_det
from ..transform import integral_image
from .._shared.utils import safe_as_int
from .corner_cy import _corner_moravec, _corner_orientations
from warnings import warn


def _compute_derivatives(image, mode='constant', cval=0):
    """Compute derivatives in axis directions using the Sobel operator.

    Parameters
    ----------
    image : ndarray
        Input image.
    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
    -------
    derivatives : list of ndarray
        Derivatives in each axis direction.

    """

    derivatives = [ndi.sobel(image, axis=i, mode=mode, cval=cval)
                   for i in range(image.ndim)]

    return derivatives


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.

    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.]])

    See also
    --------
    structure_tensor_eigenvalues

    References
    ----------
    .. [1] https://en.wikipedia.org/wiki/Structure_tensor
    """
    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


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
    --------
    >>> from skimage.feature import hessian_matrix
    >>> square = np.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 = np.gradient(gaussian_filtered)
    axes = range(image.ndim)

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

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

    return H_elems


def hessian_matrix_det(image, sigma=1, approximate=True):
    """Compute the approximate Hessian Determinant over an image.

    The 2D approximate method uses box filters over integral images to
    compute the approximate Hessian Determinant, as described in [1]_.

    Parameters
    ----------
    image : array
        The image over which to compute Hessian Determinant.
    sigma : float, optional
        Standard deviation used for the Gaussian kernel, used for the Hessian
        matrix.
    approximate : bool, optional
        If ``True`` and the image is 2D, use a much faster approximate
        computation. This argument has no effect on 3D and higher images.

    Returns
    -------
    out : array
        The array of the Determinant of Hessians.

    References
    ----------
    .. [1] <NAME>, <NAME>, <NAME>, <NAME>,
           "SURF: Speeded Up Robust Features"
           ftp://ftp.vision.ee.ethz.ch/publications/articles/eth_biwi_00517.pdf

    Notes
    -----
    For 2D images when ``approximate=True``, the running time of this method
    only depends on size of the image. It is independent of `sigma` as one
    would expect. The downside is that the result for `sigma` less than `3`
    is not accurate, i.e., not similar to the result obtained if someone
    computed the Hessian and took its determinant.
    """
    image = img_as_float(image)
    if image.ndim == 2 and approximate:
        integral = integral_image(image)
        return np.array(_hessian_matrix_det(integral, sigma))
    else:  # slower brute-force implementation for nD images
        hessian_mat_array = _symmetric_image(hessian_matrix(image, sigma))
        return np.linalg.det(hessian_mat_array)


def _image_orthogonal_matrix22_eigvals(M00, M01, M11):
    l1 = (M00 + M11) / 2 + np.sqrt(4 * M01 ** 2 + (M00 - M11) ** 2) / 2
    l2 = (M00 + M11) / 2 - np.sqrt(4 * M01 ** 2 + (M00 - M11) ** 2) / 2
    return l1, l2


def _symmetric_compute_eigenvalues(S_elems):
    """Compute eigenvalues from the upperdiagonal entries of a symmetric matrix

    Parameters
    ----------
    S_elems : list of ndarray
        The upper-diagonal elements of the matrix, as returned by
        `hessian_matrix` or `structure_tensor`.

    Returns
    -------
    eigs : ndarray
        The eigenvalues of the matrix, in decreasing order. The eigenvalues are
        the leading dimension. That is, ``eigs[i, j, k]`` contains the
        ith-largest eigenvalue at position (j, k).
    """

    if len(S_elems) == 3:  # Use fast Cython code for 2D
        eigs = np.stack(_image_orthogonal_matrix22_eigvals(*S_elems))
    else:
        matrices = _symmetric_image(S_elems)
        # eigvalsh returns eigenvalues in increasing order. We want decreasing
        eigs = np.linalg.eigvalsh(matrices)[..., ::-1]
        leading_axes = tuple(range(eigs.ndim - 1))
        eigs = np.transpose(eigs, (eigs.ndim - 1,) + leading_axes)
    return eigs


def _symmetric_image(S_elems):
    """Convert the upper-diagonal elements of a matrix to the full
    symmetric matrix.

    Parameters
    ----------
    S_elems : list of array
        The upper-diagonal elements of the matrix, as returned by
        `hessian_matrix` or `structure_tensor`.

    Returns
    -------
    image : array
        An array of shape ``(M, N[, ...], image.ndim, image.ndim)``,
        containing the matrix corresponding to each coordinate.
    """
    image = S_elems[0]
    symmetric_image = np.zeros(image.shape + (image.ndim, image.ndim))
    for idx, (row, col) in \
            enumerate(combinations_with_replacement(range(image.ndim), 2)):
        symmetric_image[..., row, col] = S_elems[idx]
        symmetric_image[..., col, row] = S_elems[idx]
    return symmetric_image


def structure_tensor_eigenvalues(A_elems):
    """Compute eigenvalues of structure tensor.

    Parameters
    ----------
    A_elems : list of ndarray
        The upper-diagonal elements of the structure tensor, as returned
        by `structure_tensor`.

    Returns
    -------
    ndarray
        The eigenvalues of the structure tensor, in decreasing order. The
        eigenvalues are the leading dimension. That is, the coordinate
        [i, j, k] corresponds to the ith-largest eigenvalue at position (j, k).

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

    See also
    --------
    structure_tensor
    """
    return _symmetric_compute_eigenvalues(A_elems)


def structure_tensor_eigvals(Axx, Axy, Ayy):
    """Compute eigenvalues of structure tensor.

    Parameters
    ----------
    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.

    Returns
    -------
    l1 : ndarray
        Larger eigen value for each input matrix.
    l2 : ndarray
        Smaller eigen value for each input matrix.

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

    """
    warn('deprecation warning: the function structure_tensor_eigvals is '
         'deprecated and will be removed in version 0.20. Please use '
         'structure_tensor_eigenvalues instead.',
         category=FutureWarning, stacklevel=2)

    return _image_orthogonal_matrix22_eigvals(Axx, Axy, Ayy)


def hessian_matrix_eigvals(H_elems):
    """Compute eigenvalues of Hessian matrix.

    Parameters
    ----------
    H_elems : list of ndarray
        The upper-diagonal elements of the Hessian matrix, as returned
        by `hessian_matrix`.

    Returns
    -------
    eigs : ndarray
        The eigenvalues of the Hessian matrix, in decreasing order. The
        eigenvalues are the leading dimension. That is, ``eigs[i, j, k]``
        contains the ith-largest eigenvalue at position (j, k).

    Examples
    --------
    >>> from skimage.feature import hessian_matrix, hessian_matrix_eigvals
    >>> square = np.zeros((5, 5))
    >>> square[2, 2] = 4
    >>> H_elems = hessian_matrix(square, sigma=0.1, order='rc')
    >>> hessian_matrix_eigvals(H_elems)[0]
    array([[ 0.,  0.,  2.,  0.,  0.],
           [ 0.,  1.,  0.,  1.,  0.],
           [ 2.,  0., -2.,  0.,  2.],
           [ 0.,  1.,  0.,  1.,  0.],
           [ 0.,  0.,  2.,  0.,  0.]])
    """
    return _symmetric_compute_eigenvalues(H_elems)


def shape_index(image, sigma=1, mode='constant', cval=0):
    """Compute the shape index.

    The shape index, as defined by Koenderink & <NAME> [1]_, is a
    single valued measure of local curvature, assuming the image as a 3D plane
    with intensities representing heights.

    It is derived from the eigen values of the Hessian, and its
    value ranges from -1 to 1 (and is undefined (=NaN) in *flat* regions),
    with following ranges representing following shapes:

    .. table:: Ranges of the shape index and corresponding shapes.

      ===================  =============
      Interval (s in ...)  Shape
      ===================  =============
      [  -1, -7/8)         Spherical cup
      [-7/8, -5/8)         Through
      [-5/8, -3/8)         Rut
      [-3/8, -1/8)         Saddle rut
      [-1/8, +1/8)         Saddle
      [+1/8, +3/8)         Saddle ridge
      [+3/8, +5/8)         Ridge
      [+5/8, +7/8)         Dome
      [+7/8,   +1]         Spherical cap
      ===================  =============

    Parameters
    ----------
    image : ndarray
        Input image.
    sigma : float, optional
        Standard deviation used for the Gaussian kernel, which is used for
        smoothing the input data before Hessian eigen value calculation.
    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
    -------
    s : ndarray
        Shape index

    References
    ----------
    .. [1] <NAME>. & <NAME>.,
           "Surface shape and curvature scales",
           Image and Vision Computing, 1992, 10, 557-564.
           :DOI:`10.1016/0262-8856(92)90076-F`

    Examples
    --------
    >>> from skimage.feature import shape_index
    >>> square = np.zeros((5, 5))
    >>> square[2, 2] = 4
    >>> s = shape_index(square, sigma=0.1)
    >>> s
    array([[ nan,  nan, -0.5,  nan,  nan],
           [ nan, -0. ,  nan, -0. ,  nan],
           [-0.5,  nan, -1. ,  nan, -0.5],
           [ nan, -0. ,  nan, -0. ,  nan],
           [ nan,  nan, -0.5,  nan,  nan]])
    """

    H = hessian_matrix(image, sigma=sigma, mode=mode, cval=cval, order='rc')
    l1, l2 = hessian_matrix_eigvals(H)

    return (2.0 / np.pi) * np.arctan((l2 + l1) / (l2 - l1))


def corner_kitchen_rosenfeld(image, mode='constant', cval=0):
    """Compute Kitchen and Rosenfeld corner measure response image.

    The corner measure is calculated as follows::

        (imxx * imy**2 + imyy * imx**2 - 2 * imxy * imx * imy)
            / (imx**2 + imy**2)

    Where imx and imy are the first and imxx, imxy, imyy the second
    derivatives.

    Parameters
    ----------
    image : ndarray
        Input image.
    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
    -------
    response : ndarray
        Kitchen and Rosenfeld response image.

    References
    ----------
    .. [1] <NAME>., & <NAME>. (1982). Gray-level corner detection.
           Pattern recognition letters, 1(2), 95-102.
           :DOI:`10.1016/0167-8655(82)90020-4`
    """

    imy, imx = _compute_derivatives(image, mode=mode, cval=cval)
    imxy, imxx = _compute_derivatives(imx, mode=mode, cval=cval)
    imyy, imyx = _compute_derivatives(imy, mode=mode, cval=cval)

    numerator = (imxx * imy ** 2 + imyy * imx ** 2 - 2 * imxy * imx * imy)
    denominator = (imx ** 2 + imy ** 2)

    response = np.zeros_like(image, dtype=np.double)

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

    return response


def corner_harris(image, method='k', k=0.05, eps=1e-6, sigma=1):
    """Compute Harris corner measure response image.

    This corner detector uses information from the auto-correlation matrix A::

        A = [(imx**2)   (imx*imy)] = [Axx Axy]
            [(imx*imy)   (imy**2)]   [Axy Ayy]

    Where imx and imy are first derivatives, averaged with a gaussian filter.
    The corner measure is then defined as::

        det(A) - k * trace(A)**2

    or::

        2 * det(A) / (trace(A) + eps)

    Parameters
    ----------
    image : ndarray
        Input image.
    method : {'k', 'eps'}, optional
        Method to compute the response image from the auto-correlation matrix.
    k : float, optional
        Sensitivity factor to separate corners from edges, typically in range
        `[0, 0.2]`. Small values of k result in detection of sharp corners.
    eps : float, optional
        Normalisation factor (Noble's corner measure).
    sigma : float, optional
        Standard deviation used for the Gaussian kernel, which is used as
        weighting function for the auto-correlation matrix.

    Returns
    -------
    response : ndarray
        Harris response image.

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

    Examples
    --------
    >>> from skimage.feature import corner_harris, corner_peaks
    >>> square = np.zeros([10, 10])
    >>> square[2:8, 2:8] = 1
    >>> square.astype(int)
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
    >>> corner_peaks(corner_harris(square), min_distance=1)
    array([[2, 2],
           [2, 7],
           [7, 2],
           [7, 7]])

    """

    Arr, Arc, Acc = structure_tensor(image, sigma, order='rc')

    # determinant
    detA = Arr * Acc - Arc ** 2
    # trace
    traceA = Arr + Acc

    if method == 'k':
        response = detA - k * traceA ** 2
    else:
        response = 2 * detA / (traceA + eps)

    return response


def corner_shi_tomasi(image, sigma=1):
    """Compute Shi-Tomasi (Kanade-Tomasi) corner measure response image.

    This corner detector uses information from the auto-correlation matrix A::

        A = [(imx**2)   (imx*imy)] = [Axx Axy]
            [(imx*imy)   (imy**2)]   [Axy Ayy]

    Where imx and imy are first derivatives, averaged with a gaussian filter.
    The corner measure is then defined as the smaller eigenvalue of A::

        ((Axx + Ayy) - sqrt((Axx - Ayy)**2 + 4 * Axy**2)) / 2

    Parameters
    ----------
    image : ndarray
        Input image.
    sigma : float, optional
        Standard deviation used for the Gaussian kernel, which is used as
        weighting function for the auto-correlation matrix.

    Returns
    -------
    response : ndarray
        Shi-Tomasi response image.

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

    Examples
    --------
    >>> from skimage.feature import corner_shi_tomasi, corner_peaks
    >>> square = np.zeros([10, 10])
    >>> square[2:8, 2:8] = 1
    >>> square.astype(int)
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
    >>> corner_peaks(corner_shi_tomasi(square), min_distance=1)
    array([[2, 2],
           [2, 7],
           [7, 2],
           [7, 7]])

    """

    Arr, Arc, Acc = structure_tensor(image, sigma, order='rc')

    # minimum eigenvalue of A
    response = ((Arr + Acc) - np.sqrt((Arr - Acc) ** 2 + 4 * Arc ** 2)) / 2

    return response


def corner_foerstner(image, sigma=1):
    """Compute Foerstner corner measure response image.

    This corner detector uses information from the auto-correlation matrix A::

        A = [(imx**2)   (imx*imy)] = [Axx Axy]
            [(imx*imy)   (imy**2)]   [Axy Ayy]

    Where imx and imy are first derivatives, averaged with a gaussian filter.
    The corner measure is then defined as::

        w = det(A) / trace(A)           (size of error ellipse)
        q = 4 * det(A) / trace(A)**2    (roundness of error ellipse)

    Parameters
    ----------
    image : ndarray
        Input image.
    sigma : float, optional
        Standard deviation used for the Gaussian kernel, which is used as
        weighting function for the auto-correlation matrix.

    Returns
    -------
    w : ndarray
        Error ellipse sizes.
    q : ndarray
        Roundness of error ellipse.

    References
    ----------
    .. [1] <NAME>., & <NAME>. (1987, June). A fast operator for detection and
           precise location of distinct points, corners and centres of circular
           features. In Proc. ISPRS intercommission conference on fast processing of
           photogrammetric data (pp. 281-305).
           https://cseweb.ucsd.edu/classes/sp02/cse252/foerstner/foerstner.pdf
    .. [2] https://en.wikipedia.org/wiki/Corner_detection

    Examples
    --------
    >>> from skimage.feature import corner_foerstner, corner_peaks
    >>> square = np.zeros([10, 10])
    >>> square[2:8, 2:8] = 1
    >>> square.astype(int)
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
    >>> w, q = corner_foerstner(square)
    >>> accuracy_thresh = 0.5
    >>> roundness_thresh = 0.3
    >>> foerstner = (q > roundness_thresh) * (w > accuracy_thresh) * w
    >>> corner_peaks(foerstner, min_distance=1)
    array([[2, 2],
           [2, 7],
           [7, 2],
           [7, 7]])

    """

    Arr, Arc, Acc = structure_tensor(image, sigma, order='rc')

    # determinant
    detA = Arr * Acc - Arc ** 2
    # trace
    traceA = Arr + Acc

    w = np.zeros_like(image, dtype=np.double)
    q = np.zeros_like(image, dtype=np.double)

    mask = traceA != 0

    w[mask] = detA[mask] / traceA[mask]
    q[mask] = 4 * detA[mask] / traceA[mask] ** 2

    return w, q


def corner_fast(image, n=12, threshold=0.15):
    """Extract FAST corners for a given image.

    Parameters
    ----------
    image : 2D ndarray
        Input image.
    n : int, optional
        Minimum number of consecutive pixels out of 16 pixels on the circle
        that should all be either brighter or darker w.r.t testpixel.
        A point c on the circle is darker w.r.t test pixel p if
        `Ic < Ip - threshold` and brighter if `Ic > Ip + threshold`. Also
        stands for the n in `FAST-n` corner detector.
    threshold : float, optional
        Threshold used in deciding whether the pixels on the circle are
        brighter, darker or similar w.r.t. the test pixel. Decrease the
        threshold when more corners are desired and vice-versa.

    Returns
    -------
    response : ndarray
        FAST corner response image.

    References
    ----------
    .. [1] <NAME>., & <NAME>. (2006, May). Machine learning for high-speed
           corner detection. In European conference on computer vision (pp. 430-443).
           Springer, Berlin, Heidelberg.
           :DOI:`10.1007/11744023_34`
           http://www.edwardrosten.com/work/rosten_2006_machine.pdf
    .. [2] Wikipedia, "Features from accelerated segment test",
           https://en.wikipedia.org/wiki/Features_from_accelerated_segment_test

    Examples
    --------
    >>> from skimage.feature import corner_fast, corner_peaks
    >>> square = np.zeros((12, 12))
    >>> square[3:9, 3:9] = 1
    >>> square.astype(int)
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
    >>> corner_peaks(corner_fast(square, 9), min_distance=1)
    array([[3, 3],
           [3, 8],
           [8, 3],
           [8, 8]])

    """
    image = _prepare_grayscale_input_2D(image)

    image = np.ascontiguousarray(image)
    response = _corner_fast(image, n, threshold)
    return response


def corner_subpix(image, corners, window_size=11, alpha=0.99):
    """Determine subpixel position of corners.

    A statistical test decides whether the corner is defined as the
    intersection of two edges or a single peak. Depending on the classification
    result, the subpixel corner location is determined based on the local
    covariance of the grey-values. If the significance level for either
    statistical test is not sufficient, the corner cannot be classified, and
    the output subpixel position is set to NaN.

    Parameters
    ----------
    image : ndarray
        Input image.
    corners : (N, 2) ndarray
        Corner coordinates `(row, col)`.
    window_size : int, optional
        Search window size for subpixel estimation.
    alpha : float, optional
        Significance level for corner classification.

    Returns
    -------
    positions : (N, 2) ndarray
        Subpixel corner positions. NaN for "not classified" corners.

    References
    ----------
    .. [1] <NAME>., & <NAME>. (1987, June). A fast operator for detection and
           precise location of distinct points, corners and centres of circular
           features. In Proc. ISPRS intercommission conference on fast processing of
           photogrammetric data (pp. 281-305).
           https://cseweb.ucsd.edu/classes/sp02/cse252/foerstner/foerstner.pdf
    .. [2] https://en.wikipedia.org/wiki/Corner_detection

    Examples
    --------
    >>> from skimage.feature import corner_harris, corner_peaks, corner_subpix
    >>> img = np.zeros((10, 10))
    >>> img[:5, :5] = 1
    >>> img[5:, 5:] = 1
    >>> img.astype(int)
    array([[1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
           [1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
           [1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
           [1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
           [1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
           [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
           [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
           [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
           [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]])
    >>> coords = corner_peaks(corner_harris(img), min_distance=2)
    >>> coords_subpix = corner_subpix(img, coords, window_size=7)
    >>> coords_subpix
    array([[4.5, 4.5]])

    """

    # window extent in one direction
    wext = (window_size - 1) // 2

    image = np.pad(image, pad_width=wext, mode='constant', constant_values=0)

    # add pad width, make sure to not modify the input values in-place
    corners = safe_as_int(corners + wext)

    # normal equation arrays
    N_dot = np.zeros((2, 2), dtype=np.double)
    N_edge = np.zeros((2, 2), dtype=np.double)
    b_dot = np.zeros((2, ), dtype=np.double)
    b_edge = np.zeros((2, ), dtype=np.double)

    # critical statistical test values
    redundancy = window_size ** 2 - 2
    t_crit_dot = stats.f.isf(1 - alpha, redundancy, redundancy)
    t_crit_edge = stats.f.isf(alpha, redundancy, redundancy)

    # coordinates of pixels within window
    y, x = np.mgrid[- wext:wext + 1, - wext:wext + 1]

    corners_subpix = np.zeros_like(corners, dtype=np.double)

    for i, (y0, x0) in enumerate(corners):

        # crop window around corner + border for sobel operator
        miny = y0 - wext - 1
        maxy = y0 + wext + 2
        minx = x0 - wext - 1
        maxx = x0 + wext + 2
        window = image[miny:maxy, minx:maxx]

        winy, winx = _compute_derivatives(window, mode='constant', cval=0)

        # compute gradient squares and remove border
        winx_winx = (winx * winx)[1:-1, 1:-1]
        winx_winy = (winx * winy)[1:-1, 1:-1]
        winy_winy = (winy * winy)[1:-1, 1:-1]

        # sum of squared differences (mean instead of gaussian filter)
        Axx = np.sum(winx_winx)
        Axy = np.sum(winx_winy)
        Ayy = np.sum(winy_winy)

        # sum of squared differences weighted with coordinates
        # (mean instead of gaussian filter)
        bxx_x = np.sum(winx_winx * x)
        bxx_y = np.sum(winx_winx * y)
        bxy_x = np.sum(winx_winy * x)
        bxy_y = np.sum(winx_winy * y)
        byy_x = np.sum(winy_winy * x)
        byy_y = np.sum(winy_winy * y)

        # normal equations for subpixel position
        N_dot[0, 0] = Axx
        N_dot[0, 1] = N_dot[1, 0] = - Axy
        N_dot[1, 1] = Ayy

        N_edge[0, 0] = Ayy
        N_edge[0, 1] = N_edge[1, 0] = Axy
        N_edge[1, 1] = Axx

        b_dot[:] = bxx_y - bxy_x, byy_x - bxy_y
        b_edge[:] = byy_y + bxy_x, bxx_x + bxy_y

        # estimated positions
        try:
            est_dot = np.linalg.solve(N_dot, b_dot)
            est_edge = np.linalg.solve(N_edge, b_edge)
        except np.linalg.LinAlgError:
            # if image is constant the system is singular
            corners_subpix[i, :] = np.nan, np.nan
            continue

        # residuals
        ry_dot = y - est_dot[0]
        rx_dot = x - est_dot[1]
        ry_edge = y - est_edge[0]
        rx_edge = x - est_edge[1]
        # squared residuals
        rxx_dot = rx_dot * rx_dot
        rxy_dot = rx_dot * ry_dot
        ryy_dot = ry_dot * ry_dot
        rxx_edge = rx_edge * rx_edge
        rxy_edge = rx_edge * ry_edge
        ryy_edge = ry_edge * ry_edge

        # determine corner class (dot or edge)
        # variance for different models
        var_dot = np.sum(winx_winx * ryy_dot - 2 * winx_winy * rxy_dot
                         + winy_winy * rxx_dot)
        var_edge = np.sum(winy_winy * ryy_edge + 2 * winx_winy * rxy_edge
                          + winx_winx * rxx_edge)

        # test value (F-distributed)
        if var_dot < np.spacing(1) and var_edge < np.spacing(1):
            t = np.nan
        elif var_dot == 0:
            t = np.inf
        else:
            t = var_edge / var_dot

        # 1 for edge, -1 for dot, 0 for "not classified"
        corner_class = int(t < t_crit_edge) - int(t > t_crit_dot)

        if corner_class == -1:
            corners_subpix[i, :] = y0 + est_dot[0], x0 + est_dot[1]
        elif corner_class == 0:
            corners_subpix[i, :] = np.nan, np.nan
        elif corner_class == 1:
            corners_subpix[i, :] = y0 + est_edge[0], x0 + est_edge[1]

    # subtract pad width
    corners_subpix -= wext

    return corners_subpix


def corner_peaks(image, min_distance=1, threshold_abs=None, threshold_rel=None,
                 exclude_border=True, indices=True, num_peaks=np.inf,
                 footprint=None, labels=None, *, num_peaks_per_label=np.inf,
                 p_norm=np.inf):
    """Find peaks in corner measure response image.

    This differs from `skimage.feature.peak_local_max` in that it suppresses
    multiple connected peaks with the same accumulator value.

    Parameters
    ----------
    image : ndarray
        Input image.
    min_distance : int, optional
        The minimal allowed distance separating peaks.
    * : *
        See :py:meth:`skimage.feature.peak_local_max`.
    p_norm : float
        Which Minkowski p-norm to use. Should be in the range [1, inf].
        A finite large p may cause a ValueError if overflow can occur.
        ``inf`` corresponds to the Chebyshev distance and 2 to the
        Euclidean distance.

    Returns
    -------
    output : ndarray or ndarray of bools

        * If `indices = True`  : (row, column, ...) coordinates of peaks.
        * If `indices = False` : Boolean array shaped like `image`, with peaks
          represented by True values.

    See also
    --------
    skimage.feature.peak_local_max

    Notes
    -----
    .. versionchanged:: 0.18
        The default value of `threshold_rel` has changed to None, which
        corresponds to letting `skimage.feature.peak_local_max` decide on the
        default. This is equivalent to `threshold_rel=0`.

    The `num_peaks` limit is applied before suppression of connected peaks.
    To limit the number of peaks after suppression, set `num_peaks=np.inf` and
    post-process the output of this function.

    Examples
    --------
    >>> from skimage.feature import peak_local_max
    >>> response = np.zeros((5, 5))
    >>> response[2:4, 2:4] = 1
    >>> response
    array([[0., 0., 0., 0., 0.],
           [0., 0., 0., 0., 0.],
           [0., 0., 1., 1., 0.],
           [0., 0., 1., 1., 0.],
           [0., 0., 0., 0., 0.]])
    >>> peak_local_max(response)
    array([[2, 2],
           [2, 3],
           [3, 2],
           [3, 3]])
    >>> corner_peaks(response)
    array([[2, 2]])

    """
    if np.isinf(num_peaks):
        num_peaks = None

    # Get the coordinates of the detected peaks
    coords = peak_local_max(image, min_distance=min_distance,
                            threshold_abs=threshold_abs,
                            threshold_rel=threshold_rel,
                            exclude_border=exclude_border,
                            num_peaks=np.inf,
                            footprint=footprint, labels=labels,
                            num_peaks_per_label=num_peaks_per_label)

    if len(coords):
        # Use KDtree to find the peaks that are too close to each other
        tree = spatial.cKDTree(coords)

        rejected_peaks_indices = set()
        for idx, point in enumerate(coords):
            if idx not in rejected_peaks_indices:
                candidates = tree.query_ball_point(point, r=min_distance,
                                                   p=p_norm)
                candidates.remove(idx)
                rejected_peaks_indices.update(candidates)

        # Remove the peaks that are too close to each other
        coords = np.delete(coords, tuple(rejected_peaks_indices),
                           axis=0)[:num_peaks]

    if indices:
        return coords

    peaks = np.zeros_like(image, dtype=bool)
    peaks[tuple(coords.T)] = True

    return peaks


def corner_moravec(image, window_size=1):
    """Compute Moravec corner measure response image.

    This is one of the simplest corner detectors and is comparatively fast but
    has several limitations (e.g. not rotation invariant).

    Parameters
    ----------
    image : ndarray
        Input image.
    window_size : int, optional
        Window size.

    Returns
    -------
    response : ndarray
        Moravec response image.

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

    Examples
    --------
    >>> from skimage.feature import corner_moravec
    >>> square = np.zeros([7, 7])
    >>> square[3, 3] = 1
    >>> square.astype(int)
    array([[0, 0, 0, 0, 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, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0]])
    >>> corner_moravec(square).astype(int)
    array([[0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0],
           [0, 0, 1, 1, 1, 0, 0],
           [0, 0, 1, 2, 1, 0, 0],
           [0, 0, 1, 1, 1, 0, 0],
           [0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0]])
    """
    return _corner_moravec(image, window_size)


def corner_orientations(image, corners, mask):
    """Compute the orientation of corners.

    The orientation of corners is computed using the first order central moment
    i.e. the center of mass approach. The corner orientation is the angle of
    the vector from the corner coordinate to the intensity centroid in the
    local neighborhood around the corner calculated using first order central
    moment.

    Parameters
    ----------
    image : 2D array
        Input grayscale image.
    corners : (N, 2) array
        Corner coordinates as ``(row, col)``.
    mask : 2D array
        Mask defining the local neighborhood of the corner used for the
        calculation of the central moment.

    Returns
    -------
    orientations : (N, 1) array
        Orientations of corners in the range [-pi, pi].

    References
    ----------
    .. [1] <NAME>, <NAME>, <NAME> and <NAME>
          "ORB : An efficient alternative to SIFT and SURF"
          http://www.vision.cs.chubu.ac.jp/CV-R/pdf/Rublee_iccv2011.pdf
    .. [2] <NAME>, "Measuring Corner Properties"
          http://users.cs.cf.ac.uk/Paul.Rosin/corner2.pdf

    Examples
    --------
    >>> from skimage.morphology import octagon
    >>> from skimage.feature import (corner_fast, corner_peaks,
    ...                              corner_orientations)
    >>> square = np.zeros((12, 12))
    >>> square[3:9, 3:9] = 1
    >>> square.astype(int)
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
    >>> corners = corner_peaks(corner_fast(square, 9), min_distance=1)
    >>> corners
    array([[3, 3],
           [3, 8],
           [8, 3],
           [8, 8]])
    >>> orientations = corner_orientations(square, corners, octagon(3, 2))
    >>> np.rad2deg(orientations)
    array([  45.,  135.,  -45., -135.])

    """
    image = _prepare_grayscale_input_2D(image)
    return _corner_orientations(image, corners, mask)

Compute derivatives in axis directions using the Sobel operator.      Parameters     ----------     image : ndarray         Input image.     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     -------     derivatives : list of ndarray         Derivatives in each axis direction. 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.      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.]])      See also     --------     structure_tensor_eigenvalues      References     ----------     .. [1] https://en.wikipedia.org/wiki/Structure_tensor # The legacy 2D code followed (x, y) convention, so we swap the # axis order to maintain compatibility with old code # structure tensor 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     --------     >>> from skimage.feature import hessian_matrix     >>> square = np.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.]]) Compute the approximate Hessian Determinant over an image.      The 2D approximate method uses box filters over integral images to     compute the approximate Hessian Determinant, as described in [1]_.      Parameters     ----------     image : array         The image over which to compute Hessian Determinant.     sigma : float, optional         Standard deviation used for the Gaussian kernel, used for the Hessian         matrix.     approximate : bool, optional         If ``True`` and the image is 2D, use a much faster approximate         computation. This argument has no effect on 3D and higher images.      Returns     -------     out : array         The array of the Determinant of Hessians.      References     ----------     .. [1] <NAME>, <NAME>, <NAME>, <NAME>,            "SURF: Speeded Up Robust Features"            ftp://ftp.vision.ee.ethz.ch/publications/articles/eth_biwi_00517.pdf      Notes     -----     For 2D images when ``approximate=True``, the running time of this method     only depends on size of the image. It is independent of `sigma` as one     would expect. The downside is that the result for `sigma` less than `3`     is not accurate, i.e., not similar to the result obtained if someone     computed the Hessian and took its determinant. # slower brute-force implementation for nD images Compute eigenvalues from the upperdiagonal entries of a symmetric matrix      Parameters     ----------     S_elems : list of ndarray         The upper-diagonal elements of the matrix, as returned by         `hessian_matrix` or `structure_tensor`.      Returns     -------     eigs : ndarray         The eigenvalues of the matrix, in decreasing order. The eigenvalues are         the leading dimension. That is, ``eigs[i, j, k]`` contains the         ith-largest eigenvalue at position (j, k). # Use fast Cython code for 2D # eigvalsh returns eigenvalues in increasing order. We want decreasing Convert the upper-diagonal elements of a matrix to the full     symmetric matrix.      Parameters     ----------     S_elems : list of array         The upper-diagonal elements of the matrix, as returned by         `hessian_matrix` or `structure_tensor`.      Returns     -------     image : array         An array of shape ``(M, N[, ...], image.ndim, image.ndim)``,         containing the matrix corresponding to each coordinate. Compute eigenvalues of structure tensor.      Parameters     ----------     A_elems : list of ndarray         The upper-diagonal elements of the structure tensor, as returned         by `structure_tensor`.      Returns     -------     ndarray         The eigenvalues of the structure tensor, in decreasing order. The         eigenvalues are the leading dimension. That is, the coordinate         [i, j, k] corresponds to the ith-largest eigenvalue at position (j, k).      Examples     --------     >>> from skimage.feature import structure_tensor     >>> from skimage.feature import structure_tensor_eigenvalues     >>> square = np.zeros((5, 5))     >>> square[2, 2] = 1     >>> A_elems = structure_tensor(square, sigma=0.1, order='rc')     >>> structure_tensor_eigenvalues(A_elems)[0]     array([[0., 0., 0., 0., 0.],            [0., 2., 4., 2., 0.],            [0., 4., 0., 4., 0.],            [0., 2., 4., 2., 0.],            [0., 0., 0., 0., 0.]])      See also     --------     structure_tensor Compute eigenvalues of structure tensor.      Parameters     ----------     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.      Returns     -------     l1 : ndarray         Larger eigen value for each input matrix.     l2 : ndarray         Smaller eigen value for each input matrix.      Examples     --------     >>> from skimage.feature import structure_tensor, structure_tensor_eigvals     >>> square = np.zeros((5, 5))     >>> square[2, 2] = 1     >>> Arr, Arc, Acc = structure_tensor(square, sigma=0.1, order='rc')     >>> structure_tensor_eigvals(Acc, Arc, Arr)[0]     array([[0., 0., 0., 0., 0.],            [0., 2., 4., 2., 0.],            [0., 4., 0., 4., 0.],            [0., 2., 4., 2., 0.],            [0., 0., 0., 0., 0.]]) Compute eigenvalues of Hessian matrix.      Parameters     ----------     H_elems : list of ndarray         The upper-diagonal elements of the Hessian matrix, as returned         by `hessian_matrix`.      Returns     -------     eigs : ndarray         The eigenvalues of the Hessian matrix, in decreasing order. The         eigenvalues are the leading dimension. That is, ``eigs[i, j, k]``         contains the ith-largest eigenvalue at position (j, k).      Examples     --------     >>> from skimage.feature import hessian_matrix, hessian_matrix_eigvals     >>> square = np.zeros((5, 5))     >>> square[2, 2] = 4     >>> H_elems = hessian_matrix(square, sigma=0.1, order='rc')     >>> hessian_matrix_eigvals(H_elems)[0]     array([[ 0.,  0.,  2.,  0.,  0.],            [ 0.,  1.,  0.,  1.,  0.],            [ 2.,  0., -2.,  0.,  2.],            [ 0.,  1.,  0.,  1.,  0.],            [ 0.,  0.,  2.,  0.,  0.]]) Compute the shape index.      The shape index, as defined by Koenderink & <NAME> [1]_, is a     single valued measure of local curvature, assuming the image as a 3D plane     with intensities representing heights.      It is derived from the eigen values of the Hessian, and its     value ranges from -1 to 1 (and is undefined (=NaN) in *flat* regions),     with following ranges representing following shapes:      .. table:: Ranges of the shape index and corresponding shapes.        ===================  =============       Interval (s in ...)  Shape       ===================  =============       [  -1, -7/8)         Spherical cup       [-7/8, -5/8)         Through       [-5/8, -3/8)         Rut       [-3/8, -1/8)         Saddle rut       [-1/8, +1/8)         Saddle       [+1/8, +3/8)         Saddle ridge       [+3/8, +5/8)         Ridge       [+5/8, +7/8)         Dome       [+7/8,   +1]         Spherical cap       ===================  =============      Parameters     ----------     image : ndarray         Input image.     sigma : float, optional         Standard deviation used for the Gaussian kernel, which is used for         smoothing the input data before Hessian eigen value calculation.     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     -------     s : ndarray         Shape index      References     ----------     .. [1] <NAME>. & <NAME>.,            "Surface shape and curvature scales",            Image and Vision Computing, 1992, 10, 557-564.            :DOI:`10.1016/0262-8856(92)90076-F`      Examples     --------     >>> from skimage.feature import shape_index     >>> square = np.zeros((5, 5))     >>> square[2, 2] = 4     >>> s = shape_index(square, sigma=0.1)     >>> s     array([[ nan,  nan, -0.5,  nan,  nan],            [ nan, -0. ,  nan, -0. ,  nan],            [-0.5,  nan, -1. ,  nan, -0.5],            [ nan, -0. ,  nan, -0. ,  nan],            [ nan,  nan, -0.5,  nan,  nan]]) Compute Kitchen and Rosenfeld corner measure response image.      The corner measure is calculated as follows::          (imxx * imy**2 + imyy * imx**2 - 2 * imxy * imx * imy)             / (imx**2 + imy**2)      Where imx and imy are the first and imxx, imxy, imyy the second     derivatives.      Parameters     ----------     image : ndarray         Input image.     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     -------     response : ndarray         Kitchen and Rosenfeld response image.      References     ----------     .. [1] <NAME>., & <NAME>. (1982). Gray-level corner detection.            Pattern recognition letters, 1(2), 95-102.            :DOI:`10.1016/0167-8655(82)90020-4` Compute Harris corner measure response image.      This corner detector uses information from the auto-correlation matrix A::          A = [(imx**2)   (imx*imy)] = [Axx Axy]             [(imx*imy)   (imy**2)]   [Axy Ayy]      Where imx and imy are first derivatives, averaged with a gaussian filter.     The corner measure is then defined as::          det(A) - k * trace(A)**2      or::          2 * det(A) / (trace(A) + eps)      Parameters     ----------     image : ndarray         Input image.     method : {'k', 'eps'}, optional         Method to compute the response image from the auto-correlation matrix.     k : float, optional         Sensitivity factor to separate corners from edges, typically in range         `[0, 0.2]`. Small values of k result in detection of sharp corners.     eps : float, optional         Normalisation factor (Noble's corner measure).     sigma : float, optional         Standard deviation used for the Gaussian kernel, which is used as         weighting function for the auto-correlation matrix.      Returns     -------     response : ndarray         Harris response image.      References     ----------     .. [1] https://en.wikipedia.org/wiki/Corner_detection      Examples     --------     >>> from skimage.feature import corner_harris, corner_peaks     >>> square = np.zeros([10, 10])     >>> square[2:8, 2:8] = 1     >>> square.astype(int)     array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],            [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],            [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],            [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],            [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],            [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],            [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])     >>> corner_peaks(corner_harris(square), min_distance=1)     array([[2, 2],            [2, 7],            [7, 2],            [7, 7]]) # determinant # trace Compute Shi-Tomasi (Kanade-Tomasi) corner measure response image.      This corner detector uses information from the auto-correlation matrix A::          A = [(imx**2)   (imx*imy)] = [Axx Axy]             [(imx*imy)   (imy**2)]   [Axy Ayy]      Where imx and imy are first derivatives, averaged with a gaussian filter.     The corner measure is then defined as the smaller eigenvalue of A::          ((Axx + Ayy) - sqrt((Axx - Ayy)**2 + 4 * Axy**2)) / 2      Parameters     ----------     image : ndarray         Input image.     sigma : float, optional         Standard deviation used for the Gaussian kernel, which is used as         weighting function for the auto-correlation matrix.      Returns     -------     response : ndarray         Shi-Tomasi response image.      References     ----------     .. [1] https://en.wikipedia.org/wiki/Corner_detection      Examples     --------     >>> from skimage.feature import corner_shi_tomasi, corner_peaks     >>> square = np.zeros([10, 10])     >>> square[2:8, 2:8] = 1     >>> square.astype(int)     array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],            [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],            [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],            [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],            [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],            [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],            [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])     >>> corner_peaks(corner_shi_tomasi(square), min_distance=1)     array([[2, 2],            [2, 7],            [7, 2],            [7, 7]]) # minimum eigenvalue of A Compute Foerstner corner measure response image.      This corner detector uses information from the auto-correlation matrix A::          A = [(imx**2)   (imx*imy)] = [Axx Axy]             [(imx*imy)   (imy**2)]   [Axy Ayy]      Where imx and imy are first derivatives, averaged with a gaussian filter.     The corner measure is then defined as::          w = det(A) / trace(A)           (size of error ellipse)         q = 4 * det(A) / trace(A)**2    (roundness of error ellipse)      Parameters     ----------     image : ndarray         Input image.     sigma : float, optional         Standard deviation used for the Gaussian kernel, which is used as         weighting function for the auto-correlation matrix.      Returns     -------     w : ndarray         Error ellipse sizes.     q : ndarray         Roundness of error ellipse.      References     ----------     .. [1] <NAME>., & <NAME>. (1987, June). A fast operator for detection and            precise location of distinct points, corners and centres of circular            features. In Proc. ISPRS intercommission conference on fast processing of            photogrammetric data (pp. 281-305).            https://cseweb.ucsd.edu/classes/sp02/cse252/foerstner/foerstner.pdf     .. [2] https://en.wikipedia.org/wiki/Corner_detection      Examples     --------     >>> from skimage.feature import corner_foerstner, corner_peaks     >>> square = np.zeros([10, 10])     >>> square[2:8, 2:8] = 1     >>> square.astype(int)     array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],            [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],            [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],            [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],            [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],            [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],            [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])     >>> w, q = corner_foerstner(square)     >>> accuracy_thresh = 0.5     >>> roundness_thresh = 0.3     >>> foerstner = (q > roundness_thresh) * (w > accuracy_thresh) * w     >>> corner_peaks(foerstner, min_distance=1)     array([[2, 2],            [2, 7],            [7, 2],            [7, 7]]) # determinant # trace Extract FAST corners for a given image.      Parameters     ----------     image : 2D ndarray         Input image.     n : int, optional         Minimum number of consecutive pixels out of 16 pixels on the circle         that should all be either brighter or darker w.r.t testpixel.         A point c on the circle is darker w.r.t test pixel p if         `Ic < Ip - threshold` and brighter if `Ic > Ip + threshold`. Also         stands for the n in `FAST-n` corner detector.     threshold : float, optional         Threshold used in deciding whether the pixels on the circle are         brighter, darker or similar w.r.t. the test pixel. Decrease the         threshold when more corners are desired and vice-versa.      Returns     -------     response : ndarray         FAST corner response image.      References     ----------     .. [1] <NAME>., & <NAME>. (2006, May). Machine learning for high-speed            corner detection. In European conference on computer vision (pp. 430-443).            Springer, Berlin, Heidelberg.            :DOI:`10.1007/11744023_34`            http://www.edwardrosten.com/work/rosten_2006_machine.pdf     .. [2] Wikipedia, "Features from accelerated segment test",            https://en.wikipedia.org/wiki/Features_from_accelerated_segment_test      Examples     --------     >>> from skimage.feature import corner_fast, corner_peaks     >>> square = np.zeros((12, 12))     >>> square[3:9, 3:9] = 1     >>> square.astype(int)     array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],            [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],            [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],            [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],            [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],            [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],            [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])     >>> corner_peaks(corner_fast(square, 9), min_distance=1)     array([[3, 3],            [3, 8],            [8, 3],            [8, 8]]) Determine subpixel position of corners.      A statistical test decides whether the corner is defined as the     intersection of two edges or a single peak. Depending on the classification     result, the subpixel corner location is determined based on the local     covariance of the grey-values. If the significance level for either     statistical test is not sufficient, the corner cannot be classified, and     the output subpixel position is set to NaN.      Parameters     ----------     image : ndarray         Input image.     corners : (N, 2) ndarray         Corner coordinates `(row, col)`.     window_size : int, optional         Search window size for subpixel estimation.     alpha : float, optional         Significance level for corner classification.      Returns     -------     positions : (N, 2) ndarray         Subpixel corner positions. NaN for "not classified" corners.      References     ----------     .. [1] <NAME>., & <NAME>. (1987, June). A fast operator for detection and            precise location of distinct points, corners and centres of circular            features. In Proc. ISPRS intercommission conference on fast processing of            photogrammetric data (pp. 281-305).            https://cseweb.ucsd.edu/classes/sp02/cse252/foerstner/foerstner.pdf     .. [2] https://en.wikipedia.org/wiki/Corner_detection      Examples     --------     >>> from skimage.feature import corner_harris, corner_peaks, corner_subpix     >>> img = np.zeros((10, 10))     >>> img[:5, :5] = 1     >>> img[5:, 5:] = 1     >>> img.astype(int)     array([[1, 1, 1, 1, 1, 0, 0, 0, 0, 0],            [1, 1, 1, 1, 1, 0, 0, 0, 0, 0],            [1, 1, 1, 1, 1, 0, 0, 0, 0, 0],            [1, 1, 1, 1, 1, 0, 0, 0, 0, 0],            [1, 1, 1, 1, 1, 0, 0, 0, 0, 0],            [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],            [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],            [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],            [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],            [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]])     >>> coords = corner_peaks(corner_harris(img), min_distance=2)     >>> coords_subpix = corner_subpix(img, coords, window_size=7)     >>> coords_subpix     array([[4.5, 4.5]]) # window extent in one direction # add pad width, make sure to not modify the input values in-place # normal equation arrays # critical statistical test values # coordinates of pixels within window # crop window around corner + border for sobel operator # compute gradient squares and remove border # sum of squared differences (mean instead of gaussian filter) # sum of squared differences weighted with coordinates # (mean instead of gaussian filter) # normal equations for subpixel position # estimated positions # if image is constant the system is singular # residuals # squared residuals # determine corner class (dot or edge) # variance for different models # test value (F-distributed) # 1 for edge, -1 for dot, 0 for "not classified" # subtract pad width Find peaks in corner measure response image.      This differs from `skimage.feature.peak_local_max` in that it suppresses     multiple connected peaks with the same accumulator value.      Parameters     ----------     image : ndarray         Input image.     min_distance : int, optional         The minimal allowed distance separating peaks.     * : *         See :py:meth:`skimage.feature.peak_local_max`.     p_norm : float         Which Minkowski p-norm to use. Should be in the range [1, inf].         A finite large p may cause a ValueError if overflow can occur.         ``inf`` corresponds to the Chebyshev distance and 2 to the         Euclidean distance.      Returns     -------     output : ndarray or ndarray of bools          * If `indices = True`  : (row, column, ...) coordinates of peaks.         * If `indices = False` : Boolean array shaped like `image`, with peaks           represented by True values.      See also     --------     skimage.feature.peak_local_max      Notes     -----     .. versionchanged:: 0.18         The default value of `threshold_rel` has changed to None, which         corresponds to letting `skimage.feature.peak_local_max` decide on the         default. This is equivalent to `threshold_rel=0`.      The `num_peaks` limit is applied before suppression of connected peaks.     To limit the number of peaks after suppression, set `num_peaks=np.inf` and     post-process the output of this function.      Examples     --------     >>> from skimage.feature import peak_local_max     >>> response = np.zeros((5, 5))     >>> response[2:4, 2:4] = 1     >>> response     array([[0., 0., 0., 0., 0.],            [0., 0., 0., 0., 0.],            [0., 0., 1., 1., 0.],            [0., 0., 1., 1., 0.],            [0., 0., 0., 0., 0.]])     >>> peak_local_max(response)     array([[2, 2],            [2, 3],            [3, 2],            [3, 3]])     >>> corner_peaks(response)     array([[2, 2]]) # Get the coordinates of the detected peaks # Use KDtree to find the peaks that are too close to each other # Remove the peaks that are too close to each other Compute Moravec corner measure response image.      This is one of the simplest corner detectors and is comparatively fast but     has several limitations (e.g. not rotation invariant).      Parameters     ----------     image : ndarray         Input image.     window_size : int, optional         Window size.      Returns     -------     response : ndarray         Moravec response image.      References     ----------     .. [1] https://en.wikipedia.org/wiki/Corner_detection      Examples     --------     >>> from skimage.feature import corner_moravec     >>> square = np.zeros([7, 7])     >>> square[3, 3] = 1     >>> square.astype(int)     array([[0, 0, 0, 0, 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, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0]])     >>> corner_moravec(square).astype(int)     array([[0, 0, 0, 0, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0],            [0, 0, 1, 1, 1, 0, 0],            [0, 0, 1, 2, 1, 0, 0],            [0, 0, 1, 1, 1, 0, 0],            [0, 0, 0, 0, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0]]) Compute the orientation of corners.      The orientation of corners is computed using the first order central moment     i.e. the center of mass approach. The corner orientation is the angle of     the vector from the corner coordinate to the intensity centroid in the     local neighborhood around the corner calculated using first order central     moment.      Parameters     ----------     image : 2D array         Input grayscale image.     corners : (N, 2) array         Corner coordinates as ``(row, col)``.     mask : 2D array         Mask defining the local neighborhood of the corner used for the         calculation of the central moment.      Returns     -------     orientations : (N, 1) array         Orientations of corners in the range [-pi, pi].      References     ----------     .. [1] <NAME>, <NAME>, <NAME> and <NAME>           "ORB : An efficient alternative to SIFT and SURF"           http://www.vision.cs.chubu.ac.jp/CV-R/pdf/Rublee_iccv2011.pdf     .. [2] <NAME>, "Measuring Corner Properties"           http://users.cs.cf.ac.uk/Paul.Rosin/corner2.pdf      Examples     --------     >>> from skimage.morphology import octagon     >>> from skimage.feature import (corner_fast, corner_peaks,     ...                              corner_orientations)     >>> square = np.zeros((12, 12))     >>> square[3:9, 3:9] = 1     >>> square.astype(int)     array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],            [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],            [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],            [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],            [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],            [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],            [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])     >>> corners = corner_peaks(corner_fast(square, 9), min_distance=1)     >>> corners     array([[3, 3],            [3, 8],            [8, 3],            [8, 8]])     >>> orientations = corner_orientations(square, corners, octagon(3, 2))     >>> np.rad2deg(orientations)     array([  45.,  135.,  -45., -135.])