update
This commit is contained in:
3
.CondaPkg/env/Lib/site-packages/skimage/feature/__init__.py
vendored
Normal file
3
.CondaPkg/env/Lib/site-packages/skimage/feature/__init__.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import lazy_loader as lazy
|
||||
|
||||
__getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
|
||||
88
.CondaPkg/env/Lib/site-packages/skimage/feature/__init__.pyi
vendored
Normal file
88
.CondaPkg/env/Lib/site-packages/skimage/feature/__init__.pyi
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
# Explicitly setting `__all__` is necessary for type inference engines
|
||||
# to know which symbols are exported. See
|
||||
# https://peps.python.org/pep-0484/#stub-files
|
||||
|
||||
__all__ = [
|
||||
'canny',
|
||||
'Cascade',
|
||||
'daisy',
|
||||
'hog',
|
||||
'graycomatrix',
|
||||
'graycoprops',
|
||||
'local_binary_pattern',
|
||||
'multiblock_lbp',
|
||||
'draw_multiblock_lbp',
|
||||
'peak_local_max',
|
||||
'structure_tensor',
|
||||
'structure_tensor_eigenvalues',
|
||||
'hessian_matrix',
|
||||
'hessian_matrix_det',
|
||||
'hessian_matrix_eigvals',
|
||||
'shape_index',
|
||||
'corner_kitchen_rosenfeld',
|
||||
'corner_harris',
|
||||
'corner_shi_tomasi',
|
||||
'corner_foerstner',
|
||||
'corner_subpix',
|
||||
'corner_peaks',
|
||||
'corner_moravec',
|
||||
'corner_fast',
|
||||
'corner_orientations',
|
||||
'match_template',
|
||||
'BRIEF',
|
||||
'CENSURE',
|
||||
'ORB',
|
||||
'SIFT',
|
||||
'match_descriptors',
|
||||
'plot_matches',
|
||||
'blob_dog',
|
||||
'blob_doh',
|
||||
'blob_log',
|
||||
'haar_like_feature',
|
||||
'haar_like_feature_coord',
|
||||
'draw_haar_like_feature',
|
||||
'multiscale_basic_features',
|
||||
'learn_gmm',
|
||||
'fisher_vector',
|
||||
]
|
||||
|
||||
from ._canny import canny
|
||||
from ._cascade import Cascade
|
||||
from ._daisy import daisy
|
||||
from ._hog import hog
|
||||
from .texture import (
|
||||
graycomatrix,
|
||||
graycoprops,
|
||||
local_binary_pattern,
|
||||
multiblock_lbp,
|
||||
draw_multiblock_lbp,
|
||||
)
|
||||
from .peak import peak_local_max
|
||||
from .corner import (
|
||||
corner_kitchen_rosenfeld,
|
||||
corner_harris,
|
||||
corner_shi_tomasi,
|
||||
corner_foerstner,
|
||||
corner_subpix,
|
||||
corner_peaks,
|
||||
corner_fast,
|
||||
structure_tensor,
|
||||
structure_tensor_eigenvalues,
|
||||
hessian_matrix,
|
||||
hessian_matrix_eigvals,
|
||||
hessian_matrix_det,
|
||||
corner_moravec,
|
||||
corner_orientations,
|
||||
shape_index,
|
||||
)
|
||||
from .template import match_template
|
||||
from .brief import BRIEF
|
||||
from .censure import CENSURE
|
||||
from .orb import ORB
|
||||
from .sift import SIFT
|
||||
from .match import match_descriptors
|
||||
from .util import plot_matches
|
||||
from .blob import blob_dog, blob_log, blob_doh
|
||||
from .haar import haar_like_feature, haar_like_feature_coord, draw_haar_like_feature
|
||||
from ._basic_features import multiscale_basic_features
|
||||
from ._fisher_vector import learn_gmm, fisher_vector
|
||||
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/__init__.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/__init__.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/_basic_features.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/_basic_features.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/_canny.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/_canny.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/_daisy.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/_daisy.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/_fisher_vector.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/_fisher_vector.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/_hog.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/_hog.cpython-312.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/blob.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/blob.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/brief.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/brief.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/censure.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/censure.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/corner.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/corner.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/haar.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/haar.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/match.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/match.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/orb.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/orb.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/peak.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/peak.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/sift.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/sift.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/template.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/template.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/texture.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/texture.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/util.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/__pycache__/util.cpython-312.pyc
vendored
Normal file
Binary file not shown.
198
.CondaPkg/env/Lib/site-packages/skimage/feature/_basic_features.py
vendored
Normal file
198
.CondaPkg/env/Lib/site-packages/skimage/feature/_basic_features.py
vendored
Normal file
@@ -0,0 +1,198 @@
|
||||
from itertools import combinations_with_replacement
|
||||
import itertools
|
||||
import numpy as np
|
||||
from skimage import filters, feature
|
||||
from skimage.util.dtype import img_as_float32
|
||||
from .._shared._dependency_checks import is_wasm
|
||||
|
||||
if not is_wasm:
|
||||
from concurrent.futures import ThreadPoolExecutor as PoolExecutor
|
||||
else:
|
||||
from contextlib import AbstractContextManager
|
||||
|
||||
# Threading isn't supported on WASM, mock ThreadPoolExecutor as a fallback
|
||||
class PoolExecutor(AbstractContextManager):
|
||||
def __init__(self, *_, **__):
|
||||
pass
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
pass
|
||||
|
||||
def map(self, fn, iterables):
|
||||
return map(fn, iterables)
|
||||
|
||||
|
||||
def _texture_filter(gaussian_filtered):
|
||||
H_elems = [
|
||||
np.gradient(np.gradient(gaussian_filtered)[ax0], axis=ax1)
|
||||
for ax0, ax1 in combinations_with_replacement(range(gaussian_filtered.ndim), 2)
|
||||
]
|
||||
eigvals = feature.hessian_matrix_eigvals(H_elems)
|
||||
return eigvals
|
||||
|
||||
|
||||
def _singlescale_basic_features_singlechannel(
|
||||
img, sigma, intensity=True, edges=True, texture=True
|
||||
):
|
||||
results = ()
|
||||
gaussian_filtered = filters.gaussian(img, sigma=sigma, preserve_range=False)
|
||||
if intensity:
|
||||
results += (gaussian_filtered,)
|
||||
if edges:
|
||||
results += (filters.sobel(gaussian_filtered),)
|
||||
if texture:
|
||||
results += (*_texture_filter(gaussian_filtered),)
|
||||
return results
|
||||
|
||||
|
||||
def _mutiscale_basic_features_singlechannel(
|
||||
img,
|
||||
intensity=True,
|
||||
edges=True,
|
||||
texture=True,
|
||||
sigma_min=0.5,
|
||||
sigma_max=16,
|
||||
num_sigma=None,
|
||||
num_workers=None,
|
||||
):
|
||||
"""Features for a single channel nd image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
img : ndarray
|
||||
Input image, which can be grayscale or multichannel.
|
||||
intensity : bool, default True
|
||||
If True, pixel intensities averaged over the different scales
|
||||
are added to the feature set.
|
||||
edges : bool, default True
|
||||
If True, intensities of local gradients averaged over the different
|
||||
scales are added to the feature set.
|
||||
texture : bool, default True
|
||||
If True, eigenvalues of the Hessian matrix after Gaussian blurring
|
||||
at different scales are added to the feature set.
|
||||
sigma_min : float, optional
|
||||
Smallest value of the Gaussian kernel used to average local
|
||||
neighborhoods before extracting features.
|
||||
sigma_max : float, optional
|
||||
Largest value of the Gaussian kernel used to average local
|
||||
neighborhoods before extracting features.
|
||||
num_sigma : int, optional
|
||||
Number of values of the Gaussian kernel between sigma_min and sigma_max.
|
||||
If None, sigma_min multiplied by powers of 2 are used.
|
||||
num_workers : int or None, optional
|
||||
The number of parallel threads to use. If set to ``None``, the full
|
||||
set of available cores are used.
|
||||
|
||||
Returns
|
||||
-------
|
||||
features : list
|
||||
List of features, each element of the list is an array of shape as img.
|
||||
"""
|
||||
# computations are faster as float32
|
||||
img = np.ascontiguousarray(img_as_float32(img))
|
||||
if num_sigma is None:
|
||||
num_sigma = int(np.log2(sigma_max) - np.log2(sigma_min) + 1)
|
||||
sigmas = np.logspace(
|
||||
np.log2(sigma_min),
|
||||
np.log2(sigma_max),
|
||||
num=num_sigma,
|
||||
base=2,
|
||||
endpoint=True,
|
||||
)
|
||||
with PoolExecutor(max_workers=num_workers) as ex:
|
||||
out_sigmas = list(
|
||||
ex.map(
|
||||
lambda s: _singlescale_basic_features_singlechannel(
|
||||
img, s, intensity=intensity, edges=edges, texture=texture
|
||||
),
|
||||
sigmas,
|
||||
)
|
||||
)
|
||||
features = itertools.chain.from_iterable(out_sigmas)
|
||||
return features
|
||||
|
||||
|
||||
def multiscale_basic_features(
|
||||
image,
|
||||
intensity=True,
|
||||
edges=True,
|
||||
texture=True,
|
||||
sigma_min=0.5,
|
||||
sigma_max=16,
|
||||
num_sigma=None,
|
||||
num_workers=None,
|
||||
*,
|
||||
channel_axis=None,
|
||||
):
|
||||
"""Local features for a single- or multi-channel nd image.
|
||||
|
||||
Intensity, gradient intensity and local structure are computed at
|
||||
different scales thanks to Gaussian blurring.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Input image, which can be grayscale or multichannel.
|
||||
intensity : bool, default True
|
||||
If True, pixel intensities averaged over the different scales
|
||||
are added to the feature set.
|
||||
edges : bool, default True
|
||||
If True, intensities of local gradients averaged over the different
|
||||
scales are added to the feature set.
|
||||
texture : bool, default True
|
||||
If True, eigenvalues of the Hessian matrix after Gaussian blurring
|
||||
at different scales are added to the feature set.
|
||||
sigma_min : float, optional
|
||||
Smallest value of the Gaussian kernel used to average local
|
||||
neighborhoods before extracting features.
|
||||
sigma_max : float, optional
|
||||
Largest value of the Gaussian kernel used to average local
|
||||
neighborhoods before extracting features.
|
||||
num_sigma : int, optional
|
||||
Number of values of the Gaussian kernel between sigma_min and sigma_max.
|
||||
If None, sigma_min multiplied by powers of 2 are used.
|
||||
num_workers : int or None, optional
|
||||
The number of parallel threads to use. If set to ``None``, the full
|
||||
set of available cores are used.
|
||||
channel_axis : int or None, optional
|
||||
If None, the image is assumed to be a grayscale (single channel) image.
|
||||
Otherwise, this parameter indicates which axis of the array corresponds
|
||||
to channels.
|
||||
|
||||
.. versionadded:: 0.19
|
||||
``channel_axis`` was added in 0.19.
|
||||
|
||||
Returns
|
||||
-------
|
||||
features : np.ndarray
|
||||
Array of shape ``image.shape + (n_features,)``. When `channel_axis` is
|
||||
not None, all channels are concatenated along the features dimension.
|
||||
(i.e. ``n_features == n_features_singlechannel * n_channels``)
|
||||
"""
|
||||
if not any([intensity, edges, texture]):
|
||||
raise ValueError(
|
||||
"At least one of `intensity`, `edges` or `textures`"
|
||||
"must be True for features to be computed."
|
||||
)
|
||||
if channel_axis is None:
|
||||
image = image[..., np.newaxis]
|
||||
channel_axis = -1
|
||||
elif channel_axis != -1:
|
||||
image = np.moveaxis(image, channel_axis, -1)
|
||||
|
||||
all_results = (
|
||||
_mutiscale_basic_features_singlechannel(
|
||||
image[..., dim],
|
||||
intensity=intensity,
|
||||
edges=edges,
|
||||
texture=texture,
|
||||
sigma_min=sigma_min,
|
||||
sigma_max=sigma_max,
|
||||
num_sigma=num_sigma,
|
||||
num_workers=num_workers,
|
||||
)
|
||||
for dim in range(image.shape[-1])
|
||||
)
|
||||
features = list(itertools.chain.from_iterable(all_results))
|
||||
out = np.stack(features, axis=-1)
|
||||
return out
|
||||
262
.CondaPkg/env/Lib/site-packages/skimage/feature/_canny.py
vendored
Normal file
262
.CondaPkg/env/Lib/site-packages/skimage/feature/_canny.py
vendored
Normal file
@@ -0,0 +1,262 @@
|
||||
"""
|
||||
canny.py - Canny Edge detector
|
||||
|
||||
Reference: Canny, J., A Computational Approach To Edge Detection, IEEE Trans.
|
||||
Pattern Analysis and Machine Intelligence, 8:679-714, 1986
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import scipy.ndimage as ndi
|
||||
|
||||
from ..util.dtype import dtype_limits
|
||||
from .._shared.filters import gaussian
|
||||
from .._shared.utils import _supported_float_type, check_nD
|
||||
from ._canny_cy import _nonmaximum_suppression_bilinear
|
||||
|
||||
|
||||
def _preprocess(image, mask, sigma, mode, cval):
|
||||
"""Generate a smoothed image and an eroded mask.
|
||||
|
||||
The image is smoothed using a gaussian filter ignoring masked
|
||||
pixels and the mask is eroded.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : array
|
||||
Image to be smoothed.
|
||||
mask : array
|
||||
Mask with 1's for significant pixels, 0's for masked pixels.
|
||||
sigma : scalar or sequence of scalars
|
||||
Standard deviation for Gaussian kernel. The standard
|
||||
deviations of the Gaussian filter are given for each axis as a
|
||||
sequence, or as a single number, in which case it is equal for
|
||||
all axes.
|
||||
mode : str, {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}
|
||||
The ``mode`` parameter determines how the array borders are
|
||||
handled, where ``cval`` is the value when mode is equal to
|
||||
'constant'.
|
||||
cval : float, optional
|
||||
Value to fill past edges of input if `mode` is 'constant'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
smoothed_image : ndarray
|
||||
The smoothed array
|
||||
eroded_mask : ndarray
|
||||
The eroded mask.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function calculates the fractional contribution of masked pixels
|
||||
by applying the function to the mask (which gets you the fraction of
|
||||
the pixel data that's due to significant points). We then mask the image
|
||||
and apply the function. The resulting values will be lower by the
|
||||
bleed-over fraction, so you can recalibrate by dividing by the function
|
||||
on the mask to recover the effect of smoothing from just the significant
|
||||
pixels.
|
||||
"""
|
||||
gaussian_kwargs = dict(sigma=sigma, mode=mode, cval=cval, preserve_range=False)
|
||||
compute_bleedover = mode == 'constant' or mask is not None
|
||||
float_type = _supported_float_type(image.dtype)
|
||||
if mask is None:
|
||||
if compute_bleedover:
|
||||
mask = np.ones(image.shape, dtype=float_type)
|
||||
masked_image = image
|
||||
|
||||
eroded_mask = np.ones(image.shape, dtype=bool)
|
||||
eroded_mask[:1, :] = 0
|
||||
eroded_mask[-1:, :] = 0
|
||||
eroded_mask[:, :1] = 0
|
||||
eroded_mask[:, -1:] = 0
|
||||
|
||||
else:
|
||||
mask = mask.astype(bool, copy=False)
|
||||
masked_image = np.zeros_like(image)
|
||||
masked_image[mask] = image[mask]
|
||||
|
||||
# Make the eroded mask. Setting the border value to zero will wipe
|
||||
# out the image edges for us.
|
||||
s = ndi.generate_binary_structure(2, 2)
|
||||
eroded_mask = ndi.binary_erosion(mask, s, border_value=0)
|
||||
|
||||
if compute_bleedover:
|
||||
# Compute the fractional contribution of masked pixels by applying
|
||||
# the function to the mask (which gets you the fraction of the
|
||||
# pixel data that's due to significant points)
|
||||
bleed_over = (
|
||||
gaussian(mask.astype(float_type, copy=False), **gaussian_kwargs)
|
||||
+ np.finfo(float_type).eps
|
||||
)
|
||||
|
||||
# Smooth the masked image
|
||||
smoothed_image = gaussian(masked_image, **gaussian_kwargs)
|
||||
|
||||
# Lower the result by the bleed-over fraction, so you can
|
||||
# recalibrate by dividing by the function on the mask to recover
|
||||
# the effect of smoothing from just the significant pixels.
|
||||
if compute_bleedover:
|
||||
smoothed_image /= bleed_over
|
||||
|
||||
return smoothed_image, eroded_mask
|
||||
|
||||
|
||||
def canny(
|
||||
image,
|
||||
sigma=1.0,
|
||||
low_threshold=None,
|
||||
high_threshold=None,
|
||||
mask=None,
|
||||
use_quantiles=False,
|
||||
*,
|
||||
mode='constant',
|
||||
cval=0.0,
|
||||
):
|
||||
"""Edge filter an image using the Canny algorithm.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : 2D array
|
||||
Grayscale input image to detect edges on; can be of any dtype.
|
||||
sigma : float, optional
|
||||
Standard deviation of the Gaussian filter.
|
||||
low_threshold : float, optional
|
||||
Lower bound for hysteresis thresholding (linking edges).
|
||||
If None, low_threshold is set to 10% of dtype's max.
|
||||
high_threshold : float, optional
|
||||
Upper bound for hysteresis thresholding (linking edges).
|
||||
If None, high_threshold is set to 20% of dtype's max.
|
||||
mask : array, dtype=bool, optional
|
||||
Mask to limit the application of Canny to a certain area.
|
||||
use_quantiles : bool, optional
|
||||
If ``True`` then treat low_threshold and high_threshold as
|
||||
quantiles of the edge magnitude image, rather than absolute
|
||||
edge magnitude values. If ``True`` then the thresholds must be
|
||||
in the range [0, 1].
|
||||
mode : str, {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}
|
||||
The ``mode`` parameter determines how the array borders are
|
||||
handled during Gaussian filtering, where ``cval`` is the value when
|
||||
mode is equal to 'constant'.
|
||||
cval : float, optional
|
||||
Value to fill past edges of input if `mode` is 'constant'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
output : 2D array (image)
|
||||
The binary edge map.
|
||||
|
||||
See also
|
||||
--------
|
||||
skimage.filters.sobel
|
||||
|
||||
Notes
|
||||
-----
|
||||
The steps of the algorithm are as follows:
|
||||
|
||||
* Smooth the image using a Gaussian with ``sigma`` width.
|
||||
|
||||
* Apply the horizontal and vertical Sobel operators to get the gradients
|
||||
within the image. The edge strength is the norm of the gradient.
|
||||
|
||||
* Thin potential edges to 1-pixel wide curves. First, find the normal
|
||||
to the edge at each point. This is done by looking at the
|
||||
signs and the relative magnitude of the X-Sobel and Y-Sobel
|
||||
to sort the points into 4 categories: horizontal, vertical,
|
||||
diagonal and antidiagonal. Then look in the normal and reverse
|
||||
directions to see if the values in either of those directions are
|
||||
greater than the point in question. Use interpolation to get a mix of
|
||||
points instead of picking the one that's the closest to the normal.
|
||||
|
||||
* Perform a hysteresis thresholding: first label all points above the
|
||||
high threshold as edges. Then recursively label any point above the
|
||||
low threshold that is 8-connected to a labeled point as an edge.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Canny, J., A Computational Approach To Edge Detection, IEEE Trans.
|
||||
Pattern Analysis and Machine Intelligence, 8:679-714, 1986
|
||||
:DOI:`10.1109/TPAMI.1986.4767851`
|
||||
.. [2] William Green's Canny tutorial
|
||||
https://en.wikipedia.org/wiki/Canny_edge_detector
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage import feature
|
||||
>>> rng = np.random.default_rng()
|
||||
>>> # Generate noisy image of a square
|
||||
>>> im = np.zeros((256, 256))
|
||||
>>> im[64:-64, 64:-64] = 1
|
||||
>>> im += 0.2 * rng.random(im.shape)
|
||||
>>> # First trial with the Canny filter, with the default smoothing
|
||||
>>> edges1 = feature.canny(im)
|
||||
>>> # Increase the smoothing for better results
|
||||
>>> edges2 = feature.canny(im, sigma=3)
|
||||
|
||||
"""
|
||||
|
||||
# Regarding masks, any point touching a masked point will have a gradient
|
||||
# that is "infected" by the masked point, so it's enough to erode the
|
||||
# mask by one and then mask the output. We also mask out the border points
|
||||
# because who knows what lies beyond the edge of the image?
|
||||
|
||||
if np.issubdtype(image.dtype, np.int64) or np.issubdtype(image.dtype, np.uint64):
|
||||
raise ValueError("64-bit integer images are not supported")
|
||||
|
||||
check_nD(image, 2)
|
||||
dtype_max = dtype_limits(image, clip_negative=False)[1]
|
||||
|
||||
if low_threshold is None:
|
||||
low_threshold = 0.1
|
||||
elif use_quantiles:
|
||||
if not (0.0 <= low_threshold <= 1.0):
|
||||
raise ValueError("Quantile thresholds must be between 0 and 1.")
|
||||
else:
|
||||
low_threshold /= dtype_max
|
||||
|
||||
if high_threshold is None:
|
||||
high_threshold = 0.2
|
||||
elif use_quantiles:
|
||||
if not (0.0 <= high_threshold <= 1.0):
|
||||
raise ValueError("Quantile thresholds must be between 0 and 1.")
|
||||
else:
|
||||
high_threshold /= dtype_max
|
||||
|
||||
if high_threshold < low_threshold:
|
||||
raise ValueError("low_threshold should be lower then high_threshold")
|
||||
|
||||
# Image filtering
|
||||
smoothed, eroded_mask = _preprocess(image, mask, sigma, mode, cval)
|
||||
|
||||
# Gradient magnitude estimation
|
||||
jsobel = ndi.sobel(smoothed, axis=1)
|
||||
isobel = ndi.sobel(smoothed, axis=0)
|
||||
magnitude = isobel * isobel
|
||||
magnitude += jsobel * jsobel
|
||||
np.sqrt(magnitude, out=magnitude)
|
||||
|
||||
if use_quantiles:
|
||||
low_threshold, high_threshold = np.percentile(
|
||||
magnitude, [100.0 * low_threshold, 100.0 * high_threshold]
|
||||
)
|
||||
|
||||
# Non-maximum suppression
|
||||
low_masked = _nonmaximum_suppression_bilinear(
|
||||
isobel, jsobel, magnitude, eroded_mask, low_threshold
|
||||
)
|
||||
|
||||
# Double thresholding and edge tracking
|
||||
#
|
||||
# Segment the low-mask, then only keep low-segments that have
|
||||
# some high_mask component in them
|
||||
#
|
||||
low_mask = low_masked > 0
|
||||
strel = np.ones((3, 3), bool)
|
||||
labels, count = ndi.label(low_mask, strel)
|
||||
if count == 0:
|
||||
return low_mask
|
||||
|
||||
high_mask = low_mask & (low_masked >= high_threshold)
|
||||
nonzero_sums = np.unique(labels[high_mask])
|
||||
good_label = np.zeros((count + 1,), bool)
|
||||
good_label[nonzero_sums] = True
|
||||
output_mask = good_label[labels]
|
||||
return output_mask
|
||||
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_canny_cy.cp312-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_canny_cy.cp312-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_canny_cy.cp312-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_canny_cy.cp312-win_amd64.pyd
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_cascade.cp312-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_cascade.cp312-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_cascade.cp312-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_cascade.cp312-win_amd64.pyd
vendored
Normal file
Binary file not shown.
249
.CondaPkg/env/Lib/site-packages/skimage/feature/_daisy.py
vendored
Normal file
249
.CondaPkg/env/Lib/site-packages/skimage/feature/_daisy.py
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
from numpy import arctan2, exp, pi, sqrt
|
||||
|
||||
from .. import draw
|
||||
from ..util.dtype import img_as_float
|
||||
from .._shared.filters import gaussian
|
||||
from .._shared.utils import check_nD
|
||||
from ..color import gray2rgb
|
||||
|
||||
|
||||
def daisy(
|
||||
image,
|
||||
step=4,
|
||||
radius=15,
|
||||
rings=3,
|
||||
histograms=8,
|
||||
orientations=8,
|
||||
normalization='l1',
|
||||
sigmas=None,
|
||||
ring_radii=None,
|
||||
visualize=False,
|
||||
):
|
||||
'''Extract DAISY feature descriptors densely for the given image.
|
||||
|
||||
DAISY is a feature descriptor similar to SIFT formulated in a way that
|
||||
allows for fast dense extraction. Typically, this is practical for
|
||||
bag-of-features image representations.
|
||||
|
||||
The implementation follows Tola et al. [1]_ but deviate on the following
|
||||
points:
|
||||
|
||||
* Histogram bin contribution are smoothed with a circular Gaussian
|
||||
window over the tonal range (the angular range).
|
||||
* The sigma values of the spatial Gaussian smoothing in this code do not
|
||||
match the sigma values in the original code by Tola et al. [2]_. In
|
||||
their code, spatial smoothing is applied to both the input image and
|
||||
the center histogram. However, this smoothing is not documented in [1]_
|
||||
and, therefore, it is omitted.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : (M, N) array
|
||||
Input image (grayscale).
|
||||
step : int, optional
|
||||
Distance between descriptor sampling points.
|
||||
radius : int, optional
|
||||
Radius (in pixels) of the outermost ring.
|
||||
rings : int, optional
|
||||
Number of rings.
|
||||
histograms : int, optional
|
||||
Number of histograms sampled per ring.
|
||||
orientations : int, optional
|
||||
Number of orientations (bins) per histogram.
|
||||
normalization : [ 'l1' | 'l2' | 'daisy' | 'off' ], optional
|
||||
How to normalize the descriptors
|
||||
|
||||
* 'l1': L1-normalization of each descriptor.
|
||||
* 'l2': L2-normalization of each descriptor.
|
||||
* 'daisy': L2-normalization of individual histograms.
|
||||
* 'off': Disable normalization.
|
||||
|
||||
sigmas : 1D array of float, optional
|
||||
Standard deviation of spatial Gaussian smoothing for the center
|
||||
histogram and for each ring of histograms. The array of sigmas should
|
||||
be sorted from the center and out. I.e. the first sigma value defines
|
||||
the spatial smoothing of the center histogram and the last sigma value
|
||||
defines the spatial smoothing of the outermost ring. Specifying sigmas
|
||||
overrides the following parameter.
|
||||
|
||||
``rings = len(sigmas) - 1``
|
||||
|
||||
ring_radii : 1D array of int, optional
|
||||
Radius (in pixels) for each ring. Specifying ring_radii overrides the
|
||||
following two parameters.
|
||||
|
||||
``rings = len(ring_radii)``
|
||||
``radius = ring_radii[-1]``
|
||||
|
||||
If both sigmas and ring_radii are given, they must satisfy the
|
||||
following predicate since no radius is needed for the center
|
||||
histogram.
|
||||
|
||||
``len(ring_radii) == len(sigmas) + 1``
|
||||
|
||||
visualize : bool, optional
|
||||
Generate a visualization of the DAISY descriptors
|
||||
|
||||
Returns
|
||||
-------
|
||||
descs : array
|
||||
Grid of DAISY descriptors for the given image as an array
|
||||
dimensionality (P, Q, R) where
|
||||
|
||||
``P = ceil((M - radius*2) / step)``
|
||||
``Q = ceil((N - radius*2) / step)``
|
||||
``R = (rings * histograms + 1) * orientations``
|
||||
|
||||
descs_img : (M, N, 3) array (only if visualize==True)
|
||||
Visualization of the DAISY descriptors.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Tola et al. "Daisy: An efficient dense descriptor applied to wide-
|
||||
baseline stereo." Pattern Analysis and Machine Intelligence, IEEE
|
||||
Transactions on 32.5 (2010): 815-830.
|
||||
.. [2] http://cvlab.epfl.ch/software/daisy
|
||||
'''
|
||||
|
||||
check_nD(image, 2, 'img')
|
||||
|
||||
image = img_as_float(image)
|
||||
float_dtype = image.dtype
|
||||
|
||||
# Validate parameters.
|
||||
if (
|
||||
sigmas is not None
|
||||
and ring_radii is not None
|
||||
and len(sigmas) - 1 != len(ring_radii)
|
||||
):
|
||||
raise ValueError('`len(sigmas)-1 != len(ring_radii)`')
|
||||
if ring_radii is not None:
|
||||
rings = len(ring_radii)
|
||||
radius = ring_radii[-1]
|
||||
if sigmas is not None:
|
||||
rings = len(sigmas) - 1
|
||||
if sigmas is None:
|
||||
sigmas = [radius * (i + 1) / float(2 * rings) for i in range(rings)]
|
||||
if ring_radii is None:
|
||||
ring_radii = [radius * (i + 1) / float(rings) for i in range(rings)]
|
||||
if normalization not in ['l1', 'l2', 'daisy', 'off']:
|
||||
raise ValueError('Invalid normalization method.')
|
||||
|
||||
# Compute image derivatives.
|
||||
dx = np.zeros(image.shape, dtype=float_dtype)
|
||||
dy = np.zeros(image.shape, dtype=float_dtype)
|
||||
dx[:, :-1] = np.diff(image, n=1, axis=1)
|
||||
dy[:-1, :] = np.diff(image, n=1, axis=0)
|
||||
|
||||
# Compute gradient orientation and magnitude and their contribution
|
||||
# to the histograms.
|
||||
grad_mag = sqrt(dx**2 + dy**2)
|
||||
grad_ori = arctan2(dy, dx)
|
||||
orientation_kappa = orientations / pi
|
||||
orientation_angles = [2 * o * pi / orientations - pi for o in range(orientations)]
|
||||
hist = np.empty((orientations,) + image.shape, dtype=float_dtype)
|
||||
for i, o in enumerate(orientation_angles):
|
||||
# Weigh bin contribution by the circular normal distribution
|
||||
hist[i, :, :] = exp(orientation_kappa * np.cos(grad_ori - o))
|
||||
# Weigh bin contribution by the gradient magnitude
|
||||
hist[i, :, :] = np.multiply(hist[i, :, :], grad_mag)
|
||||
|
||||
# Smooth orientation histograms for the center and all rings.
|
||||
sigmas = [sigmas[0]] + sigmas
|
||||
hist_smooth = np.empty((rings + 1,) + hist.shape, dtype=float_dtype)
|
||||
for i in range(rings + 1):
|
||||
for j in range(orientations):
|
||||
hist_smooth[i, j, :, :] = gaussian(
|
||||
hist[j, :, :], sigma=sigmas[i], mode='reflect'
|
||||
)
|
||||
|
||||
# Assemble descriptor grid.
|
||||
theta = [2 * pi * j / histograms for j in range(histograms)]
|
||||
desc_dims = (rings * histograms + 1) * orientations
|
||||
descs = np.empty(
|
||||
(desc_dims, image.shape[0] - 2 * radius, image.shape[1] - 2 * radius),
|
||||
dtype=float_dtype,
|
||||
)
|
||||
descs[:orientations, :, :] = hist_smooth[0, :, radius:-radius, radius:-radius]
|
||||
idx = orientations
|
||||
for i in range(rings):
|
||||
for j in range(histograms):
|
||||
y_min = radius + int(round(ring_radii[i] * math.sin(theta[j])))
|
||||
y_max = descs.shape[1] + y_min
|
||||
x_min = radius + int(round(ring_radii[i] * math.cos(theta[j])))
|
||||
x_max = descs.shape[2] + x_min
|
||||
descs[idx : idx + orientations, :, :] = hist_smooth[
|
||||
i + 1, :, y_min:y_max, x_min:x_max
|
||||
]
|
||||
idx += orientations
|
||||
descs = descs[:, ::step, ::step]
|
||||
descs = descs.swapaxes(0, 1).swapaxes(1, 2)
|
||||
|
||||
# Normalize descriptors.
|
||||
if normalization != 'off':
|
||||
descs += 1e-10
|
||||
if normalization == 'l1':
|
||||
descs /= np.sum(descs, axis=2)[:, :, np.newaxis]
|
||||
elif normalization == 'l2':
|
||||
descs /= sqrt(np.sum(descs**2, axis=2))[:, :, np.newaxis]
|
||||
elif normalization == 'daisy':
|
||||
for i in range(0, desc_dims, orientations):
|
||||
norms = sqrt(np.sum(descs[:, :, i : i + orientations] ** 2, axis=2))
|
||||
descs[:, :, i : i + orientations] /= norms[:, :, np.newaxis]
|
||||
|
||||
if visualize:
|
||||
descs_img = gray2rgb(image)
|
||||
for i in range(descs.shape[0]):
|
||||
for j in range(descs.shape[1]):
|
||||
# Draw center histogram sigma
|
||||
color = [1, 0, 0]
|
||||
desc_y = i * step + radius
|
||||
desc_x = j * step + radius
|
||||
rows, cols, val = draw.circle_perimeter_aa(
|
||||
desc_y, desc_x, int(sigmas[0])
|
||||
)
|
||||
draw.set_color(descs_img, (rows, cols), color, alpha=val)
|
||||
max_bin = np.max(descs[i, j, :])
|
||||
for o_num, o in enumerate(orientation_angles):
|
||||
# Draw center histogram bins
|
||||
bin_size = descs[i, j, o_num] / max_bin
|
||||
dy = sigmas[0] * bin_size * math.sin(o)
|
||||
dx = sigmas[0] * bin_size * math.cos(o)
|
||||
rows, cols, val = draw.line_aa(
|
||||
desc_y, desc_x, int(desc_y + dy), int(desc_x + dx)
|
||||
)
|
||||
draw.set_color(descs_img, (rows, cols), color, alpha=val)
|
||||
for r_num, r in enumerate(ring_radii):
|
||||
color_offset = float(1 + r_num) / rings
|
||||
color = (1 - color_offset, 1, color_offset)
|
||||
for t_num, t in enumerate(theta):
|
||||
# Draw ring histogram sigmas
|
||||
hist_y = desc_y + int(round(r * math.sin(t)))
|
||||
hist_x = desc_x + int(round(r * math.cos(t)))
|
||||
rows, cols, val = draw.circle_perimeter_aa(
|
||||
hist_y, hist_x, int(sigmas[r_num + 1])
|
||||
)
|
||||
draw.set_color(descs_img, (rows, cols), color, alpha=val)
|
||||
for o_num, o in enumerate(orientation_angles):
|
||||
# Draw histogram bins
|
||||
bin_size = descs[
|
||||
i,
|
||||
j,
|
||||
orientations
|
||||
+ r_num * histograms * orientations
|
||||
+ t_num * orientations
|
||||
+ o_num,
|
||||
]
|
||||
bin_size /= max_bin
|
||||
dy = sigmas[r_num + 1] * bin_size * math.sin(o)
|
||||
dx = sigmas[r_num + 1] * bin_size * math.cos(o)
|
||||
rows, cols, val = draw.line_aa(
|
||||
hist_y, hist_x, int(hist_y + dy), int(hist_x + dx)
|
||||
)
|
||||
draw.set_color(descs_img, (rows, cols), color, alpha=val)
|
||||
return descs, descs_img
|
||||
else:
|
||||
return descs
|
||||
265
.CondaPkg/env/Lib/site-packages/skimage/feature/_fisher_vector.py
vendored
Normal file
265
.CondaPkg/env/Lib/site-packages/skimage/feature/_fisher_vector.py
vendored
Normal file
@@ -0,0 +1,265 @@
|
||||
"""
|
||||
fisher_vector.py - Implementation of the Fisher vector encoding algorithm
|
||||
|
||||
This module contains the source code for Fisher vector computation. The
|
||||
computation is separated into two distinct steps, which are called separately
|
||||
by the user, namely:
|
||||
|
||||
learn_gmm: Used to estimate the GMM for all vectors/descriptors computed for
|
||||
all examples in the dataset (e.g. estimated using all the SIFT
|
||||
vectors computed for all images in the dataset, or at least a subset
|
||||
of this).
|
||||
|
||||
fisher_vector: Used to compute the Fisher vector representation for a
|
||||
single set of descriptors/vector (e.g. the SIFT
|
||||
descriptors for a single image in your dataset, or
|
||||
perhaps a test image).
|
||||
|
||||
Reference: Perronnin, F. and Dance, C. Fisher kernels on Visual Vocabularies
|
||||
for Image Categorization, IEEE Conference on Computer Vision and
|
||||
Pattern Recognition, 2007
|
||||
|
||||
Origin Author: Dan Oneata (Author of the original implementation for the Fisher
|
||||
vector computation using scikit-learn and NumPy. Subsequently ported to
|
||||
scikit-image (here) by other authors.)
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
class FisherVectorException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class DescriptorException(FisherVectorException):
|
||||
pass
|
||||
|
||||
|
||||
def learn_gmm(descriptors, *, n_modes=32, gm_args=None):
|
||||
"""Estimate a Gaussian mixture model (GMM) given a set of descriptors and
|
||||
number of modes (i.e. Gaussians). This function is essentially a wrapper
|
||||
around the scikit-learn implementation of GMM, namely the
|
||||
:class:`sklearn.mixture.GaussianMixture` class.
|
||||
|
||||
Due to the nature of the Fisher vector, the only enforced parameter of the
|
||||
underlying scikit-learn class is the covariance_type, which must be 'diag'.
|
||||
|
||||
There is no simple way to know what value to use for `n_modes` a-priori.
|
||||
Typically, the value is usually one of ``{16, 32, 64, 128}``. One may train
|
||||
a few GMMs and choose the one that maximises the log probability of the
|
||||
GMM, or choose `n_modes` such that the downstream classifier trained on
|
||||
the resultant Fisher vectors has maximal performance.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
descriptors : np.ndarray (N, M) or list [(N1, M), (N2, M), ...]
|
||||
List of NumPy arrays, or a single NumPy array, of the descriptors
|
||||
used to estimate the GMM. The reason a list of NumPy arrays is
|
||||
permissible is because often when using a Fisher vector encoding,
|
||||
descriptors/vectors are computed separately for each sample/image in
|
||||
the dataset, such as SIFT vectors for each image. If a list if passed
|
||||
in, then each element must be a NumPy array in which the number of
|
||||
rows may differ (e.g. different number of SIFT vector for each image),
|
||||
but the number of columns for each must be the same (i.e. the
|
||||
dimensionality must be the same).
|
||||
n_modes : int
|
||||
The number of modes/Gaussians to estimate during the GMM estimate.
|
||||
gm_args : dict
|
||||
Keyword arguments that can be passed into the underlying scikit-learn
|
||||
:class:`sklearn.mixture.GaussianMixture` class.
|
||||
|
||||
Returns
|
||||
-------
|
||||
gmm : :class:`sklearn.mixture.GaussianMixture`
|
||||
The estimated GMM object, which contains the necessary parameters
|
||||
needed to compute the Fisher vector.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://scikit-learn.org/stable/modules/generated/sklearn.mixture.GaussianMixture.html
|
||||
|
||||
Examples
|
||||
--------
|
||||
.. testsetup::
|
||||
>>> import pytest; _ = pytest.importorskip('sklearn')
|
||||
|
||||
>>> from skimage.feature import fisher_vector
|
||||
>>> rng = np.random.Generator(np.random.PCG64())
|
||||
>>> sift_for_images = [rng.standard_normal((10, 128)) for _ in range(10)]
|
||||
>>> num_modes = 16
|
||||
>>> # Estimate 16-mode GMM with these synthetic SIFT vectors
|
||||
>>> gmm = learn_gmm(sift_for_images, n_modes=num_modes)
|
||||
"""
|
||||
|
||||
try:
|
||||
from sklearn.mixture import GaussianMixture
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
'scikit-learn is not installed. Please ensure it is installed in '
|
||||
'order to use the Fisher vector functionality.'
|
||||
)
|
||||
|
||||
if not isinstance(descriptors, (list, np.ndarray)):
|
||||
raise DescriptorException(
|
||||
'Please ensure descriptors are either a NumPy array, '
|
||||
'or a list of NumPy arrays.'
|
||||
)
|
||||
|
||||
d_mat_1 = descriptors[0]
|
||||
if isinstance(descriptors, list) and not isinstance(d_mat_1, np.ndarray):
|
||||
raise DescriptorException(
|
||||
'Please ensure descriptors are a list of NumPy arrays.'
|
||||
)
|
||||
|
||||
if isinstance(descriptors, list):
|
||||
expected_shape = descriptors[0].shape
|
||||
ranks = [len(e.shape) == len(expected_shape) for e in descriptors]
|
||||
if not all(ranks):
|
||||
raise DescriptorException(
|
||||
'Please ensure all elements of your descriptor list ' 'are of rank 2.'
|
||||
)
|
||||
dims = [e.shape[1] == descriptors[0].shape[1] for e in descriptors]
|
||||
if not all(dims):
|
||||
raise DescriptorException(
|
||||
'Please ensure all descriptors are of the same dimensionality.'
|
||||
)
|
||||
|
||||
if not isinstance(n_modes, int) or n_modes <= 0:
|
||||
raise FisherVectorException('Please ensure n_modes is a positive integer.')
|
||||
|
||||
if gm_args:
|
||||
has_cov_type = 'covariance_type' in gm_args
|
||||
cov_type_not_diag = gm_args['covariance_type'] != 'diag'
|
||||
if has_cov_type and cov_type_not_diag:
|
||||
raise FisherVectorException('Covariance type must be "diag".')
|
||||
|
||||
if isinstance(descriptors, list):
|
||||
descriptors = np.vstack(descriptors)
|
||||
|
||||
if gm_args:
|
||||
has_cov_type = 'covariance_type' in gm_args
|
||||
if has_cov_type:
|
||||
gmm = GaussianMixture(n_components=n_modes, **gm_args)
|
||||
else:
|
||||
gmm = GaussianMixture(
|
||||
n_components=n_modes, covariance_type='diag', **gm_args
|
||||
)
|
||||
else:
|
||||
gmm = GaussianMixture(n_components=n_modes, covariance_type='diag')
|
||||
|
||||
gmm.fit(descriptors)
|
||||
|
||||
return gmm
|
||||
|
||||
|
||||
def fisher_vector(descriptors, gmm, *, improved=False, alpha=0.5):
|
||||
"""Compute the Fisher vector given some descriptors/vectors,
|
||||
and an associated estimated GMM.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
descriptors : np.ndarray, shape=(n_descriptors, descriptor_length)
|
||||
NumPy array of the descriptors for which the Fisher vector
|
||||
representation is to be computed.
|
||||
gmm : :class:`sklearn.mixture.GaussianMixture`
|
||||
An estimated GMM object, which contains the necessary parameters needed
|
||||
to compute the Fisher vector.
|
||||
improved : bool, default=False
|
||||
Flag denoting whether to compute improved Fisher vectors or not.
|
||||
Improved Fisher vectors are L2 and power normalized. Power
|
||||
normalization is simply f(z) = sign(z) pow(abs(z), alpha) for some
|
||||
0 <= alpha <= 1.
|
||||
alpha : float, default=0.5
|
||||
The parameter for the power normalization step. Ignored if
|
||||
improved=False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
fisher_vector : np.ndarray
|
||||
The computation Fisher vector, which is given by a concatenation of the
|
||||
gradients of a GMM with respect to its parameters (mixture weights,
|
||||
means, and covariance matrices). For D-dimensional input descriptors or
|
||||
vectors, and a K-mode GMM, the Fisher vector dimensionality will be
|
||||
2KD + K. Thus, its dimensionality is invariant to the number of
|
||||
descriptors/vectors.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Perronnin, F. and Dance, C. Fisher kernels on Visual Vocabularies
|
||||
for Image Categorization, IEEE Conference on Computer Vision and
|
||||
Pattern Recognition, 2007
|
||||
.. [2] Perronnin, F. and Sanchez, J. and Mensink T. Improving the Fisher
|
||||
Kernel for Large-Scale Image Classification, ECCV, 2010
|
||||
|
||||
Examples
|
||||
--------
|
||||
.. testsetup::
|
||||
>>> import pytest; _ = pytest.importorskip('sklearn')
|
||||
|
||||
>>> from skimage.feature import fisher_vector, learn_gmm
|
||||
>>> sift_for_images = [np.random.random((10, 128)) for _ in range(10)]
|
||||
>>> num_modes = 16
|
||||
>>> # Estimate 16-mode GMM with these synthetic SIFT vectors
|
||||
>>> gmm = learn_gmm(sift_for_images, n_modes=num_modes)
|
||||
>>> test_image_descriptors = np.random.random((25, 128))
|
||||
>>> # Compute the Fisher vector
|
||||
>>> fv = fisher_vector(test_image_descriptors, gmm)
|
||||
"""
|
||||
try:
|
||||
from sklearn.mixture import GaussianMixture
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
'scikit-learn is not installed. Please ensure it is installed in '
|
||||
'order to use the Fisher vector functionality.'
|
||||
)
|
||||
|
||||
if not isinstance(descriptors, np.ndarray):
|
||||
raise DescriptorException('Please ensure descriptors is a NumPy array.')
|
||||
|
||||
if not isinstance(gmm, GaussianMixture):
|
||||
raise FisherVectorException(
|
||||
'Please ensure gmm is a sklearn.mixture.GaussianMixture object.'
|
||||
)
|
||||
|
||||
if improved and not isinstance(alpha, float):
|
||||
raise FisherVectorException(
|
||||
'Please ensure that the alpha parameter is a float.'
|
||||
)
|
||||
|
||||
num_descriptors = len(descriptors)
|
||||
|
||||
mixture_weights = gmm.weights_
|
||||
means = gmm.means_
|
||||
covariances = gmm.covariances_
|
||||
|
||||
posterior_probabilities = gmm.predict_proba(descriptors)
|
||||
|
||||
# Statistics necessary to compute GMM gradients wrt its parameters
|
||||
pp_sum = posterior_probabilities.mean(axis=0, keepdims=True).T
|
||||
pp_x = posterior_probabilities.T.dot(descriptors) / num_descriptors
|
||||
pp_x_2 = posterior_probabilities.T.dot(np.power(descriptors, 2)) / num_descriptors
|
||||
|
||||
# Compute GMM gradients wrt its parameters
|
||||
d_pi = pp_sum.squeeze() - mixture_weights
|
||||
|
||||
d_mu = pp_x - pp_sum * means
|
||||
|
||||
d_sigma_t1 = pp_sum * np.power(means, 2)
|
||||
d_sigma_t2 = pp_sum * covariances
|
||||
d_sigma_t3 = 2 * pp_x * means
|
||||
d_sigma = -pp_x_2 - d_sigma_t1 + d_sigma_t2 + d_sigma_t3
|
||||
|
||||
# Apply analytical diagonal normalization
|
||||
sqrt_mixture_weights = np.sqrt(mixture_weights)
|
||||
d_pi /= sqrt_mixture_weights
|
||||
d_mu /= sqrt_mixture_weights[:, np.newaxis] * np.sqrt(covariances)
|
||||
d_sigma /= np.sqrt(2) * sqrt_mixture_weights[:, np.newaxis] * covariances
|
||||
|
||||
# Concatenate GMM gradients to form Fisher vector representation
|
||||
fisher_vector = np.hstack((d_pi, d_mu.ravel(), d_sigma.ravel()))
|
||||
|
||||
if improved:
|
||||
fisher_vector = np.sign(fisher_vector) * np.power(np.abs(fisher_vector), alpha)
|
||||
fisher_vector = fisher_vector / np.linalg.norm(fisher_vector)
|
||||
|
||||
return fisher_vector
|
||||
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_haar.cp312-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_haar.cp312-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_haar.cp312-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_haar.cp312-win_amd64.pyd
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_hessian_det_appx.cp312-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_hessian_det_appx.cp312-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_hessian_det_appx.cp312-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_hessian_det_appx.cp312-win_amd64.pyd
vendored
Normal file
Binary file not shown.
341
.CondaPkg/env/Lib/site-packages/skimage/feature/_hog.py
vendored
Normal file
341
.CondaPkg/env/Lib/site-packages/skimage/feature/_hog.py
vendored
Normal file
@@ -0,0 +1,341 @@
|
||||
import numpy as np
|
||||
|
||||
from . import _hoghistogram
|
||||
from .._shared import utils
|
||||
|
||||
|
||||
def _hog_normalize_block(block, method, eps=1e-5):
|
||||
if method == 'L1':
|
||||
out = block / (np.sum(np.abs(block)) + eps)
|
||||
elif method == 'L1-sqrt':
|
||||
out = np.sqrt(block / (np.sum(np.abs(block)) + eps))
|
||||
elif method == 'L2':
|
||||
out = block / np.sqrt(np.sum(block**2) + eps**2)
|
||||
elif method == 'L2-Hys':
|
||||
out = block / np.sqrt(np.sum(block**2) + eps**2)
|
||||
out = np.minimum(out, 0.2)
|
||||
out = out / np.sqrt(np.sum(out**2) + eps**2)
|
||||
else:
|
||||
raise ValueError('Selected block normalization method is invalid.')
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def _hog_channel_gradient(channel):
|
||||
"""Compute unnormalized gradient image along `row` and `col` axes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
channel : (M, N) ndarray
|
||||
Grayscale image or one of image channel.
|
||||
|
||||
Returns
|
||||
-------
|
||||
g_row, g_col : channel gradient along `row` and `col` axes correspondingly.
|
||||
"""
|
||||
g_row = np.empty(channel.shape, dtype=channel.dtype)
|
||||
g_row[0, :] = 0
|
||||
g_row[-1, :] = 0
|
||||
g_row[1:-1, :] = channel[2:, :] - channel[:-2, :]
|
||||
g_col = np.empty(channel.shape, dtype=channel.dtype)
|
||||
g_col[:, 0] = 0
|
||||
g_col[:, -1] = 0
|
||||
g_col[:, 1:-1] = channel[:, 2:] - channel[:, :-2]
|
||||
|
||||
return g_row, g_col
|
||||
|
||||
|
||||
@utils.channel_as_last_axis(multichannel_output=False)
|
||||
def hog(
|
||||
image,
|
||||
orientations=9,
|
||||
pixels_per_cell=(8, 8),
|
||||
cells_per_block=(3, 3),
|
||||
block_norm='L2-Hys',
|
||||
visualize=False,
|
||||
transform_sqrt=False,
|
||||
feature_vector=True,
|
||||
*,
|
||||
channel_axis=None,
|
||||
):
|
||||
"""Extract Histogram of Oriented Gradients (HOG) for a given image.
|
||||
|
||||
Compute a Histogram of Oriented Gradients (HOG) by
|
||||
|
||||
1. (optional) global image normalization
|
||||
2. computing the gradient image in `row` and `col`
|
||||
3. computing gradient histograms
|
||||
4. normalizing across blocks
|
||||
5. flattening into a feature vector
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : (M, N[, C]) ndarray
|
||||
Input image.
|
||||
orientations : int, optional
|
||||
Number of orientation bins.
|
||||
pixels_per_cell : 2-tuple (int, int), optional
|
||||
Size (in pixels) of a cell.
|
||||
cells_per_block : 2-tuple (int, int), optional
|
||||
Number of cells in each block.
|
||||
block_norm : str {'L1', 'L1-sqrt', 'L2', 'L2-Hys'}, optional
|
||||
Block normalization method:
|
||||
|
||||
``L1``
|
||||
Normalization using L1-norm.
|
||||
``L1-sqrt``
|
||||
Normalization using L1-norm, followed by square root.
|
||||
``L2``
|
||||
Normalization using L2-norm.
|
||||
``L2-Hys``
|
||||
Normalization using L2-norm, followed by limiting the
|
||||
maximum values to 0.2 (`Hys` stands for `hysteresis`) and
|
||||
renormalization using L2-norm. (default)
|
||||
For details, see [3]_, [4]_.
|
||||
|
||||
visualize : bool, optional
|
||||
Also return an image of the HOG. For each cell and orientation bin,
|
||||
the image contains a line segment that is centered at the cell center,
|
||||
is perpendicular to the midpoint of the range of angles spanned by the
|
||||
orientation bin, and has intensity proportional to the corresponding
|
||||
histogram value.
|
||||
transform_sqrt : bool, optional
|
||||
Apply power law compression to normalize the image before
|
||||
processing. DO NOT use this if the image contains negative
|
||||
values. Also see `notes` section below.
|
||||
feature_vector : bool, optional
|
||||
Return the data as a feature vector by calling .ravel() on the result
|
||||
just before returning.
|
||||
channel_axis : int or None, optional
|
||||
If None, the image is assumed to be a grayscale (single channel) image.
|
||||
Otherwise, this parameter indicates which axis of the array corresponds
|
||||
to channels.
|
||||
|
||||
.. versionadded:: 0.19
|
||||
`channel_axis` was added in 0.19.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : (n_blocks_row, n_blocks_col, n_cells_row, n_cells_col, n_orient) ndarray
|
||||
HOG descriptor for the image. If `feature_vector` is True, a 1D
|
||||
(flattened) array is returned.
|
||||
hog_image : (M, N) ndarray, optional
|
||||
A visualisation of the HOG image. Only provided if `visualize` is True.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If the image is too small given the values of pixels_per_cell and
|
||||
cells_per_block.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://en.wikipedia.org/wiki/Histogram_of_oriented_gradients
|
||||
|
||||
.. [2] Dalal, N and Triggs, B, Histograms of Oriented Gradients for
|
||||
Human Detection, IEEE Computer Society Conference on Computer
|
||||
Vision and Pattern Recognition 2005 San Diego, CA, USA,
|
||||
https://lear.inrialpes.fr/people/triggs/pubs/Dalal-cvpr05.pdf,
|
||||
:DOI:`10.1109/CVPR.2005.177`
|
||||
|
||||
.. [3] Lowe, D.G., Distinctive image features from scale-invatiant
|
||||
keypoints, International Journal of Computer Vision (2004) 60: 91,
|
||||
http://www.cs.ubc.ca/~lowe/papers/ijcv04.pdf,
|
||||
:DOI:`10.1023/B:VISI.0000029664.99615.94`
|
||||
|
||||
.. [4] Dalal, N, Finding People in Images and Videos,
|
||||
Human-Computer Interaction [cs.HC], Institut National Polytechnique
|
||||
de Grenoble - INPG, 2006,
|
||||
https://tel.archives-ouvertes.fr/tel-00390303/file/NavneetDalalThesis.pdf
|
||||
|
||||
Notes
|
||||
-----
|
||||
The presented code implements the HOG extraction method from [2]_ with
|
||||
the following changes: (I) blocks of (3, 3) cells are used ((2, 2) in the
|
||||
paper); (II) no smoothing within cells (Gaussian spatial window with sigma=8pix
|
||||
in the paper); (III) L1 block normalization is used (L2-Hys in the paper).
|
||||
|
||||
Power law compression, also known as Gamma correction, is used to reduce
|
||||
the effects of shadowing and illumination variations. The compression makes
|
||||
the dark regions lighter. When the kwarg `transform_sqrt` is set to
|
||||
``True``, the function computes the square root of each color channel
|
||||
and then applies the hog algorithm to the image.
|
||||
"""
|
||||
image = np.atleast_2d(image)
|
||||
float_dtype = utils._supported_float_type(image.dtype)
|
||||
image = image.astype(float_dtype, copy=False)
|
||||
|
||||
multichannel = channel_axis is not None
|
||||
ndim_spatial = image.ndim - 1 if multichannel else image.ndim
|
||||
if ndim_spatial != 2:
|
||||
raise ValueError(
|
||||
'Only images with two spatial dimensions are '
|
||||
'supported. If using with color/multichannel '
|
||||
'images, specify `channel_axis`.'
|
||||
)
|
||||
|
||||
"""
|
||||
The first stage applies an optional global image normalization
|
||||
equalisation that is designed to reduce the influence of illumination
|
||||
effects. In practice we use gamma (power law) compression, either
|
||||
computing the square root or the log of each color channel.
|
||||
Image texture strength is typically proportional to the local surface
|
||||
illumination so this compression helps to reduce the effects of local
|
||||
shadowing and illumination variations.
|
||||
"""
|
||||
|
||||
if transform_sqrt:
|
||||
image = np.sqrt(image)
|
||||
|
||||
"""
|
||||
The second stage computes first order image gradients. These capture
|
||||
contour, silhouette and some texture information, while providing
|
||||
further resistance to illumination variations. The locally dominant
|
||||
color channel is used, which provides color invariance to a large
|
||||
extent. Variant methods may also include second order image derivatives,
|
||||
which act as primitive bar detectors - a useful feature for capturing,
|
||||
e.g. bar like structures in bicycles and limbs in humans.
|
||||
"""
|
||||
|
||||
if multichannel:
|
||||
g_row_by_ch = np.empty_like(image, dtype=float_dtype)
|
||||
g_col_by_ch = np.empty_like(image, dtype=float_dtype)
|
||||
g_magn = np.empty_like(image, dtype=float_dtype)
|
||||
|
||||
for idx_ch in range(image.shape[2]):
|
||||
(
|
||||
g_row_by_ch[:, :, idx_ch],
|
||||
g_col_by_ch[:, :, idx_ch],
|
||||
) = _hog_channel_gradient(image[:, :, idx_ch])
|
||||
g_magn[:, :, idx_ch] = np.hypot(
|
||||
g_row_by_ch[:, :, idx_ch], g_col_by_ch[:, :, idx_ch]
|
||||
)
|
||||
|
||||
# For each pixel select the channel with the highest gradient magnitude
|
||||
idcs_max = g_magn.argmax(axis=2)
|
||||
rr, cc = np.meshgrid(
|
||||
np.arange(image.shape[0]),
|
||||
np.arange(image.shape[1]),
|
||||
indexing='ij',
|
||||
sparse=True,
|
||||
)
|
||||
g_row = g_row_by_ch[rr, cc, idcs_max]
|
||||
g_col = g_col_by_ch[rr, cc, idcs_max]
|
||||
else:
|
||||
g_row, g_col = _hog_channel_gradient(image)
|
||||
|
||||
"""
|
||||
The third stage aims to produce an encoding that is sensitive to
|
||||
local image content while remaining resistant to small changes in
|
||||
pose or appearance. The adopted method pools gradient orientation
|
||||
information locally in the same way as the SIFT [Lowe 2004]
|
||||
feature. The image window is divided into small spatial regions,
|
||||
called "cells". For each cell we accumulate a local 1-D histogram
|
||||
of gradient or edge orientations over all the pixels in the
|
||||
cell. This combined cell-level 1-D histogram forms the basic
|
||||
"orientation histogram" representation. Each orientation histogram
|
||||
divides the gradient angle range into a fixed number of
|
||||
predetermined bins. The gradient magnitudes of the pixels in the
|
||||
cell are used to vote into the orientation histogram.
|
||||
"""
|
||||
|
||||
s_row, s_col = image.shape[:2]
|
||||
c_row, c_col = pixels_per_cell
|
||||
b_row, b_col = cells_per_block
|
||||
|
||||
n_cells_row = int(s_row // c_row) # number of cells along row-axis
|
||||
n_cells_col = int(s_col // c_col) # number of cells along col-axis
|
||||
|
||||
# compute orientations integral images
|
||||
orientation_histogram = np.zeros(
|
||||
(n_cells_row, n_cells_col, orientations), dtype=float
|
||||
)
|
||||
g_row = g_row.astype(float, copy=False)
|
||||
g_col = g_col.astype(float, copy=False)
|
||||
|
||||
_hoghistogram.hog_histograms(
|
||||
g_col,
|
||||
g_row,
|
||||
c_col,
|
||||
c_row,
|
||||
s_col,
|
||||
s_row,
|
||||
n_cells_col,
|
||||
n_cells_row,
|
||||
orientations,
|
||||
orientation_histogram,
|
||||
)
|
||||
|
||||
# now compute the histogram for each cell
|
||||
hog_image = None
|
||||
|
||||
if visualize:
|
||||
from .. import draw
|
||||
|
||||
radius = min(c_row, c_col) // 2 - 1
|
||||
orientations_arr = np.arange(orientations)
|
||||
# set dr_arr, dc_arr to correspond to midpoints of orientation bins
|
||||
orientation_bin_midpoints = np.pi * (orientations_arr + 0.5) / orientations
|
||||
dr_arr = radius * np.sin(orientation_bin_midpoints)
|
||||
dc_arr = radius * np.cos(orientation_bin_midpoints)
|
||||
hog_image = np.zeros((s_row, s_col), dtype=float_dtype)
|
||||
for r in range(n_cells_row):
|
||||
for c in range(n_cells_col):
|
||||
for o, dr, dc in zip(orientations_arr, dr_arr, dc_arr):
|
||||
centre = tuple([r * c_row + c_row // 2, c * c_col + c_col // 2])
|
||||
rr, cc = draw.line(
|
||||
int(centre[0] - dc),
|
||||
int(centre[1] + dr),
|
||||
int(centre[0] + dc),
|
||||
int(centre[1] - dr),
|
||||
)
|
||||
hog_image[rr, cc] += orientation_histogram[r, c, o]
|
||||
|
||||
"""
|
||||
The fourth stage computes normalization, which takes local groups of
|
||||
cells and contrast normalizes their overall responses before passing
|
||||
to next stage. Normalization introduces better invariance to illumination,
|
||||
shadowing, and edge contrast. It is performed by accumulating a measure
|
||||
of local histogram "energy" over local groups of cells that we call
|
||||
"blocks". The result is used to normalize each cell in the block.
|
||||
Typically each individual cell is shared between several blocks, but
|
||||
its normalizations are block dependent and thus different. The cell
|
||||
thus appears several times in the final output vector with different
|
||||
normalizations. This may seem redundant but it improves the performance.
|
||||
We refer to the normalized block descriptors as Histogram of Oriented
|
||||
Gradient (HOG) descriptors.
|
||||
"""
|
||||
|
||||
n_blocks_row = (n_cells_row - b_row) + 1
|
||||
n_blocks_col = (n_cells_col - b_col) + 1
|
||||
if n_blocks_col <= 0 or n_blocks_row <= 0:
|
||||
min_row = b_row * c_row
|
||||
min_col = b_col * c_col
|
||||
raise ValueError(
|
||||
'The input image is too small given the values of '
|
||||
'pixels_per_cell and cells_per_block. '
|
||||
'It should have at least: '
|
||||
f'{min_row} rows and {min_col} cols.'
|
||||
)
|
||||
normalized_blocks = np.zeros(
|
||||
(n_blocks_row, n_blocks_col, b_row, b_col, orientations), dtype=float_dtype
|
||||
)
|
||||
|
||||
for r in range(n_blocks_row):
|
||||
for c in range(n_blocks_col):
|
||||
block = orientation_histogram[r : r + b_row, c : c + b_col, :]
|
||||
normalized_blocks[r, c, :] = _hog_normalize_block(block, method=block_norm)
|
||||
|
||||
"""
|
||||
The final step collects the HOG descriptors from all blocks of a dense
|
||||
overlapping grid of blocks covering the detection window into a combined
|
||||
feature vector for use in the window classifier.
|
||||
"""
|
||||
|
||||
if feature_vector:
|
||||
normalized_blocks = normalized_blocks.ravel()
|
||||
|
||||
if visualize:
|
||||
return normalized_blocks, hog_image
|
||||
else:
|
||||
return normalized_blocks
|
||||
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_hoghistogram.cp312-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_hoghistogram.cp312-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_hoghistogram.cp312-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_hoghistogram.cp312-win_amd64.pyd
vendored
Normal file
Binary file not shown.
10
.CondaPkg/env/Lib/site-packages/skimage/feature/_orb_descriptor_positions.py
vendored
Normal file
10
.CondaPkg/env/Lib/site-packages/skimage/feature/_orb_descriptor_positions.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import os
|
||||
import numpy as np
|
||||
|
||||
# Putting this in cython was giving strange bugs for different versions
|
||||
# of cython which seemed to indicate troubles with the __file__ variable
|
||||
# not being defined. Keeping it in pure python makes it more reliable
|
||||
this_dir = os.path.dirname(__file__)
|
||||
POS = np.loadtxt(os.path.join(this_dir, "orb_descriptor_positions.txt"), dtype=np.int8)
|
||||
POS0 = np.ascontiguousarray(POS[:, :2])
|
||||
POS1 = np.ascontiguousarray(POS[:, 2:])
|
||||
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_sift.cp312-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_sift.cp312-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_sift.cp312-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_sift.cp312-win_amd64.pyd
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_texture.cp312-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_texture.cp312-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_texture.cp312-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/_texture.cp312-win_amd64.pyd
vendored
Normal file
Binary file not shown.
715
.CondaPkg/env/Lib/site-packages/skimage/feature/blob.py
vendored
Normal file
715
.CondaPkg/env/Lib/site-packages/skimage/feature/blob.py
vendored
Normal file
@@ -0,0 +1,715 @@
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
import scipy.ndimage as ndi
|
||||
from scipy import spatial
|
||||
|
||||
from .._shared.filters import gaussian
|
||||
from .._shared.utils import _supported_float_type, check_nD
|
||||
from ..transform import integral_image
|
||||
from ..util import img_as_float
|
||||
from ._hessian_det_appx import _hessian_matrix_det
|
||||
from .peak import peak_local_max
|
||||
|
||||
# This basic blob detection algorithm is based on:
|
||||
# http://www.cs.utah.edu/~jfishbau/advimproc/project1/ (04.04.2013)
|
||||
# Theory behind: https://en.wikipedia.org/wiki/Blob_detection (04.04.2013)
|
||||
|
||||
|
||||
def _compute_disk_overlap(d, r1, r2):
|
||||
"""
|
||||
Compute fraction of surface overlap between two disks of radii
|
||||
``r1`` and ``r2``, with centers separated by a distance ``d``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
d : float
|
||||
Distance between centers.
|
||||
r1 : float
|
||||
Radius of the first disk.
|
||||
r2 : float
|
||||
Radius of the second disk.
|
||||
|
||||
Returns
|
||||
-------
|
||||
fraction: float
|
||||
Fraction of area of the overlap between the two disks.
|
||||
"""
|
||||
|
||||
ratio1 = (d**2 + r1**2 - r2**2) / (2 * d * r1)
|
||||
ratio1 = np.clip(ratio1, -1, 1)
|
||||
acos1 = math.acos(ratio1)
|
||||
|
||||
ratio2 = (d**2 + r2**2 - r1**2) / (2 * d * r2)
|
||||
ratio2 = np.clip(ratio2, -1, 1)
|
||||
acos2 = math.acos(ratio2)
|
||||
|
||||
a = -d + r2 + r1
|
||||
b = d - r2 + r1
|
||||
c = d + r2 - r1
|
||||
d = d + r2 + r1
|
||||
area = r1**2 * acos1 + r2**2 * acos2 - 0.5 * math.sqrt(abs(a * b * c * d))
|
||||
return area / (math.pi * (min(r1, r2) ** 2))
|
||||
|
||||
|
||||
def _compute_sphere_overlap(d, r1, r2):
|
||||
"""
|
||||
Compute volume overlap fraction between two spheres of radii
|
||||
``r1`` and ``r2``, with centers separated by a distance ``d``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
d : float
|
||||
Distance between centers.
|
||||
r1 : float
|
||||
Radius of the first sphere.
|
||||
r2 : float
|
||||
Radius of the second sphere.
|
||||
|
||||
Returns
|
||||
-------
|
||||
fraction: float
|
||||
Fraction of volume of the overlap between the two spheres.
|
||||
|
||||
Notes
|
||||
-----
|
||||
See for example http://mathworld.wolfram.com/Sphere-SphereIntersection.html
|
||||
for more details.
|
||||
"""
|
||||
vol = (
|
||||
math.pi
|
||||
/ (12 * d)
|
||||
* (r1 + r2 - d) ** 2
|
||||
* (d**2 + 2 * d * (r1 + r2) - 3 * (r1**2 + r2**2) + 6 * r1 * r2)
|
||||
)
|
||||
return vol / (4.0 / 3 * math.pi * min(r1, r2) ** 3)
|
||||
|
||||
|
||||
def _blob_overlap(blob1, blob2, *, sigma_dim=1):
|
||||
"""Finds the overlapping area fraction between two blobs.
|
||||
|
||||
Returns a float representing fraction of overlapped area. Note that 0.0
|
||||
is *always* returned for dimension greater than 3.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
blob1 : sequence of arrays
|
||||
A sequence of ``(row, col, sigma)`` or ``(pln, row, col, sigma)``,
|
||||
where ``row, col`` (or ``(pln, row, col)``) are coordinates
|
||||
of blob and ``sigma`` is the standard deviation of the Gaussian kernel
|
||||
which detected the blob.
|
||||
blob2 : sequence of arrays
|
||||
A sequence of ``(row, col, sigma)`` or ``(pln, row, col, sigma)``,
|
||||
where ``row, col`` (or ``(pln, row, col)``) are coordinates
|
||||
of blob and ``sigma`` is the standard deviation of the Gaussian kernel
|
||||
which detected the blob.
|
||||
sigma_dim : int, optional
|
||||
The dimensionality of the sigma value. Can be 1 or the same as the
|
||||
dimensionality of the blob space (2 or 3).
|
||||
|
||||
Returns
|
||||
-------
|
||||
f : float
|
||||
Fraction of overlapped area (or volume in 3D).
|
||||
"""
|
||||
ndim = len(blob1) - sigma_dim
|
||||
if ndim > 3:
|
||||
return 0.0
|
||||
root_ndim = math.sqrt(ndim)
|
||||
|
||||
# we divide coordinates by sigma * sqrt(ndim) to rescale space to isotropy,
|
||||
# giving spheres of radius = 1 or < 1.
|
||||
if blob1[-1] == blob2[-1] == 0:
|
||||
return 0.0
|
||||
elif blob1[-1] > blob2[-1]:
|
||||
max_sigma = blob1[-sigma_dim:]
|
||||
r1 = 1
|
||||
r2 = blob2[-1] / blob1[-1]
|
||||
else:
|
||||
max_sigma = blob2[-sigma_dim:]
|
||||
r2 = 1
|
||||
r1 = blob1[-1] / blob2[-1]
|
||||
pos1 = blob1[:ndim] / (max_sigma * root_ndim)
|
||||
pos2 = blob2[:ndim] / (max_sigma * root_ndim)
|
||||
|
||||
d = np.sqrt(np.sum((pos2 - pos1) ** 2))
|
||||
if d > r1 + r2: # centers farther than sum of radii, so no overlap
|
||||
return 0.0
|
||||
|
||||
# one blob is inside the other
|
||||
if d <= abs(r1 - r2):
|
||||
return 1.0
|
||||
|
||||
if ndim == 2:
|
||||
return _compute_disk_overlap(d, r1, r2)
|
||||
|
||||
else: # ndim=3 http://mathworld.wolfram.com/Sphere-SphereIntersection.html
|
||||
return _compute_sphere_overlap(d, r1, r2)
|
||||
|
||||
|
||||
def _prune_blobs(blobs_array, overlap, *, sigma_dim=1):
|
||||
"""Eliminated blobs with area overlap.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
blobs_array : ndarray
|
||||
A 2d array with each row representing 3 (or 4) values,
|
||||
``(row, col, sigma)`` or ``(pln, row, col, sigma)`` in 3D,
|
||||
where ``(row, col)`` (``(pln, row, col)``) are coordinates of the blob
|
||||
and ``sigma`` is the standard deviation of the Gaussian kernel which
|
||||
detected the blob.
|
||||
This array must not have a dimension of size 0.
|
||||
overlap : float
|
||||
A value between 0 and 1. If the fraction of area overlapping for 2
|
||||
blobs is greater than `overlap` the smaller blob is eliminated.
|
||||
sigma_dim : int, optional
|
||||
The number of columns in ``blobs_array`` corresponding to sigmas rather
|
||||
than positions.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A : ndarray
|
||||
`array` with overlapping blobs removed.
|
||||
"""
|
||||
sigma = blobs_array[:, -sigma_dim:].max()
|
||||
distance = 2 * sigma * math.sqrt(blobs_array.shape[1] - sigma_dim)
|
||||
tree = spatial.cKDTree(blobs_array[:, :-sigma_dim])
|
||||
pairs = np.array(list(tree.query_pairs(distance)))
|
||||
if len(pairs) == 0:
|
||||
return blobs_array
|
||||
else:
|
||||
for i, j in pairs:
|
||||
blob1, blob2 = blobs_array[i], blobs_array[j]
|
||||
if _blob_overlap(blob1, blob2, sigma_dim=sigma_dim) > overlap:
|
||||
# note: this test works even in the anisotropic case because
|
||||
# all sigmas increase together.
|
||||
if blob1[-1] > blob2[-1]:
|
||||
blob2[-1] = 0
|
||||
else:
|
||||
blob1[-1] = 0
|
||||
|
||||
return np.stack([b for b in blobs_array if b[-1] > 0])
|
||||
|
||||
|
||||
def _format_exclude_border(img_ndim, exclude_border):
|
||||
"""Format an ``exclude_border`` argument as a tuple of ints for calling
|
||||
``peak_local_max``.
|
||||
"""
|
||||
if isinstance(exclude_border, tuple):
|
||||
if len(exclude_border) != img_ndim:
|
||||
raise ValueError(
|
||||
"`exclude_border` should have the same length as the "
|
||||
"dimensionality of the image."
|
||||
)
|
||||
for exclude in exclude_border:
|
||||
if not isinstance(exclude, int):
|
||||
raise ValueError(
|
||||
"exclude border, when expressed as a tuple, must only "
|
||||
"contain ints."
|
||||
)
|
||||
return exclude_border + (0,)
|
||||
elif isinstance(exclude_border, int):
|
||||
return (exclude_border,) * img_ndim + (0,)
|
||||
elif exclude_border is True:
|
||||
raise ValueError("exclude_border cannot be True")
|
||||
elif exclude_border is False:
|
||||
return (0,) * (img_ndim + 1)
|
||||
else:
|
||||
raise ValueError(f'Unsupported value ({exclude_border}) for exclude_border')
|
||||
|
||||
|
||||
def blob_dog(
|
||||
image,
|
||||
min_sigma=1,
|
||||
max_sigma=50,
|
||||
sigma_ratio=1.6,
|
||||
threshold=0.5,
|
||||
overlap=0.5,
|
||||
*,
|
||||
threshold_rel=None,
|
||||
exclude_border=False,
|
||||
):
|
||||
r"""Finds blobs in the given grayscale image.
|
||||
|
||||
Blobs are found using the Difference of Gaussian (DoG) method [1]_, [2]_.
|
||||
For each blob found, the method returns its coordinates and the standard
|
||||
deviation of the Gaussian kernel that detected the blob.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Input grayscale image, blobs are assumed to be light on dark
|
||||
background (white on black).
|
||||
min_sigma : scalar or sequence of scalars, optional
|
||||
The minimum standard deviation for Gaussian kernel. Keep this low to
|
||||
detect smaller blobs. The standard deviations of the Gaussian filter
|
||||
are given for each axis as a sequence, or as a single number, in
|
||||
which case it is equal for all axes.
|
||||
max_sigma : scalar or sequence of scalars, optional
|
||||
The maximum standard deviation for Gaussian kernel. Keep this high to
|
||||
detect larger blobs. The standard deviations of the Gaussian filter
|
||||
are given for each axis as a sequence, or as a single number, in
|
||||
which case it is equal for all axes.
|
||||
sigma_ratio : float, optional
|
||||
The ratio between the standard deviation of Gaussian Kernels used for
|
||||
computing the Difference of Gaussians
|
||||
threshold : float or None, optional
|
||||
The absolute lower bound for scale space maxima. Local maxima smaller
|
||||
than `threshold` are ignored. Reduce this to detect blobs with lower
|
||||
intensities. If `threshold_rel` is also specified, whichever threshold
|
||||
is larger will be used. If None, `threshold_rel` is used instead.
|
||||
overlap : float, optional
|
||||
A value between 0 and 1. If the area of two blobs overlaps by a
|
||||
fraction greater than `threshold`, the smaller blob is eliminated.
|
||||
threshold_rel : float or None, optional
|
||||
Minimum intensity of peaks, calculated as
|
||||
``max(dog_space) * threshold_rel``, where ``dog_space`` refers to the
|
||||
stack of Difference-of-Gaussian (DoG) images computed internally. This
|
||||
should have a value between 0 and 1. If None, `threshold` is used
|
||||
instead.
|
||||
exclude_border : tuple of ints, int, or False, optional
|
||||
If tuple of ints, the length of the tuple must match the input array's
|
||||
dimensionality. Each element of the tuple will exclude peaks from
|
||||
within `exclude_border`-pixels of the border of the image along that
|
||||
dimension.
|
||||
If nonzero int, `exclude_border` excludes peaks from within
|
||||
`exclude_border`-pixels of the border of the image.
|
||||
If zero or False, peaks are identified regardless of their
|
||||
distance from the border.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A : (n, image.ndim + sigma) ndarray
|
||||
A 2d array with each row representing 2 coordinate values for a 2D
|
||||
image, or 3 coordinate values for a 3D image, plus the sigma(s) used.
|
||||
When a single sigma is passed, outputs are:
|
||||
``(r, c, sigma)`` or ``(p, r, c, sigma)`` where ``(r, c)`` or
|
||||
``(p, r, c)`` are coordinates of the blob and ``sigma`` is the standard
|
||||
deviation of the Gaussian kernel which detected the blob. When an
|
||||
anisotropic gaussian is used (sigmas per dimension), the detected sigma
|
||||
is returned for each dimension.
|
||||
|
||||
See also
|
||||
--------
|
||||
skimage.filters.difference_of_gaussians
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://en.wikipedia.org/wiki/Blob_detection#The_difference_of_Gaussians_approach
|
||||
.. [2] Lowe, D. G. "Distinctive Image Features from Scale-Invariant
|
||||
Keypoints." International Journal of Computer Vision 60, 91–110 (2004).
|
||||
https://www.cs.ubc.ca/~lowe/papers/ijcv04.pdf
|
||||
:DOI:`10.1023/B:VISI.0000029664.99615.94`
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage import data, feature
|
||||
>>> coins = data.coins()
|
||||
>>> feature.blob_dog(coins, threshold=.05, min_sigma=10, max_sigma=40)
|
||||
array([[128., 155., 10.],
|
||||
[198., 155., 10.],
|
||||
[124., 338., 10.],
|
||||
[127., 102., 10.],
|
||||
[193., 281., 10.],
|
||||
[126., 208., 10.],
|
||||
[267., 115., 10.],
|
||||
[197., 102., 10.],
|
||||
[198., 215., 10.],
|
||||
[123., 279., 10.],
|
||||
[126., 46., 10.],
|
||||
[259., 247., 10.],
|
||||
[196., 43., 10.],
|
||||
[ 54., 276., 10.],
|
||||
[267., 358., 10.],
|
||||
[ 58., 100., 10.],
|
||||
[259., 305., 10.],
|
||||
[185., 347., 16.],
|
||||
[261., 174., 16.],
|
||||
[ 46., 336., 16.],
|
||||
[ 54., 217., 10.],
|
||||
[ 55., 157., 10.],
|
||||
[ 57., 41., 10.],
|
||||
[260., 47., 16.]])
|
||||
|
||||
Notes
|
||||
-----
|
||||
The radius of each blob is approximately :math:`\sqrt{2}\sigma` for
|
||||
a 2-D image and :math:`\sqrt{3}\sigma` for a 3-D image.
|
||||
""" # noqa: E501
|
||||
image = img_as_float(image)
|
||||
float_dtype = _supported_float_type(image.dtype)
|
||||
image = image.astype(float_dtype, copy=False)
|
||||
|
||||
# if both min and max sigma are scalar, function returns only one sigma
|
||||
scalar_sigma = np.isscalar(max_sigma) and np.isscalar(min_sigma)
|
||||
|
||||
# Gaussian filter requires that sequence-type sigmas have same
|
||||
# dimensionality as image. This broadcasts scalar kernels
|
||||
if np.isscalar(max_sigma):
|
||||
max_sigma = np.full(image.ndim, max_sigma, dtype=float_dtype)
|
||||
if np.isscalar(min_sigma):
|
||||
min_sigma = np.full(image.ndim, min_sigma, dtype=float_dtype)
|
||||
|
||||
# Convert sequence types to array
|
||||
min_sigma = np.asarray(min_sigma, dtype=float_dtype)
|
||||
max_sigma = np.asarray(max_sigma, dtype=float_dtype)
|
||||
|
||||
if sigma_ratio <= 1.0:
|
||||
raise ValueError('sigma_ratio must be > 1.0')
|
||||
|
||||
# k such that min_sigma*(sigma_ratio**k) > max_sigma
|
||||
k = int(np.mean(np.log(max_sigma / min_sigma) / np.log(sigma_ratio) + 1))
|
||||
|
||||
# a geometric progression of standard deviations for gaussian kernels
|
||||
sigma_list = np.array([min_sigma * (sigma_ratio**i) for i in range(k + 1)])
|
||||
|
||||
# computing difference between two successive Gaussian blurred images
|
||||
# to obtain an approximation of the scale invariant Laplacian of the
|
||||
# Gaussian operator
|
||||
dog_image_cube = np.empty(image.shape + (k,), dtype=float_dtype)
|
||||
gaussian_previous = gaussian(image, sigma=sigma_list[0], mode='reflect')
|
||||
for i, s in enumerate(sigma_list[1:]):
|
||||
gaussian_current = gaussian(image, sigma=s, mode='reflect')
|
||||
dog_image_cube[..., i] = gaussian_previous - gaussian_current
|
||||
gaussian_previous = gaussian_current
|
||||
|
||||
# normalization factor for consistency in DoG magnitude
|
||||
sf = 1 / (sigma_ratio - 1)
|
||||
dog_image_cube *= sf
|
||||
|
||||
exclude_border = _format_exclude_border(image.ndim, exclude_border)
|
||||
local_maxima = peak_local_max(
|
||||
dog_image_cube,
|
||||
threshold_abs=threshold,
|
||||
threshold_rel=threshold_rel,
|
||||
exclude_border=exclude_border,
|
||||
footprint=np.ones((3,) * (image.ndim + 1)),
|
||||
)
|
||||
|
||||
# Catch no peaks
|
||||
if local_maxima.size == 0:
|
||||
return np.empty((0, image.ndim + (1 if scalar_sigma else image.ndim)))
|
||||
|
||||
# Convert local_maxima to float64
|
||||
lm = local_maxima.astype(float_dtype)
|
||||
|
||||
# translate final column of lm, which contains the index of the
|
||||
# sigma that produced the maximum intensity value, into the sigma
|
||||
sigmas_of_peaks = sigma_list[local_maxima[:, -1]]
|
||||
|
||||
if scalar_sigma:
|
||||
# select one sigma column, keeping dimension
|
||||
sigmas_of_peaks = sigmas_of_peaks[:, 0:1]
|
||||
|
||||
# Remove sigma index and replace with sigmas
|
||||
lm = np.hstack([lm[:, :-1], sigmas_of_peaks])
|
||||
|
||||
sigma_dim = sigmas_of_peaks.shape[1]
|
||||
|
||||
return _prune_blobs(lm, overlap, sigma_dim=sigma_dim)
|
||||
|
||||
|
||||
def blob_log(
|
||||
image,
|
||||
min_sigma=1,
|
||||
max_sigma=50,
|
||||
num_sigma=10,
|
||||
threshold=0.2,
|
||||
overlap=0.5,
|
||||
log_scale=False,
|
||||
*,
|
||||
threshold_rel=None,
|
||||
exclude_border=False,
|
||||
):
|
||||
r"""Finds blobs in the given grayscale image.
|
||||
|
||||
Blobs are found using the Laplacian of Gaussian (LoG) method [1]_.
|
||||
For each blob found, the method returns its coordinates and the standard
|
||||
deviation of the Gaussian kernel that detected the blob.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Input grayscale image, blobs are assumed to be light on dark
|
||||
background (white on black).
|
||||
min_sigma : scalar or sequence of scalars, optional
|
||||
the minimum standard deviation for Gaussian kernel. Keep this low to
|
||||
detect smaller blobs. The standard deviations of the Gaussian filter
|
||||
are given for each axis as a sequence, or as a single number, in
|
||||
which case it is equal for all axes.
|
||||
max_sigma : scalar or sequence of scalars, optional
|
||||
The maximum standard deviation for Gaussian kernel. Keep this high to
|
||||
detect larger blobs. The standard deviations of the Gaussian filter
|
||||
are given for each axis as a sequence, or as a single number, in
|
||||
which case it is equal for all axes.
|
||||
num_sigma : int, optional
|
||||
The number of intermediate values of standard deviations to consider
|
||||
between `min_sigma` and `max_sigma`.
|
||||
threshold : float or None, optional
|
||||
The absolute lower bound for scale space maxima. Local maxima smaller
|
||||
than `threshold` are ignored. Reduce this to detect blobs with lower
|
||||
intensities. If `threshold_rel` is also specified, whichever threshold
|
||||
is larger will be used. If None, `threshold_rel` is used instead.
|
||||
overlap : float, optional
|
||||
A value between 0 and 1. If the area of two blobs overlaps by a
|
||||
fraction greater than `threshold`, the smaller blob is eliminated.
|
||||
log_scale : bool, optional
|
||||
If set intermediate values of standard deviations are interpolated
|
||||
using a logarithmic scale to the base `10`. If not, linear
|
||||
interpolation is used.
|
||||
threshold_rel : float or None, optional
|
||||
Minimum intensity of peaks, calculated as
|
||||
``max(log_space) * threshold_rel``, where ``log_space`` refers to the
|
||||
stack of Laplacian-of-Gaussian (LoG) images computed internally. This
|
||||
should have a value between 0 and 1. If None, `threshold` is used
|
||||
instead.
|
||||
exclude_border : tuple of ints, int, or False, optional
|
||||
If tuple of ints, the length of the tuple must match the input array's
|
||||
dimensionality. Each element of the tuple will exclude peaks from
|
||||
within `exclude_border`-pixels of the border of the image along that
|
||||
dimension.
|
||||
If nonzero int, `exclude_border` excludes peaks from within
|
||||
`exclude_border`-pixels of the border of the image.
|
||||
If zero or False, peaks are identified regardless of their
|
||||
distance from the border.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A : (n, image.ndim + sigma) ndarray
|
||||
A 2d array with each row representing 2 coordinate values for a 2D
|
||||
image, or 3 coordinate values for a 3D image, plus the sigma(s) used.
|
||||
When a single sigma is passed, outputs are:
|
||||
``(r, c, sigma)`` or ``(p, r, c, sigma)`` where ``(r, c)`` or
|
||||
``(p, r, c)`` are coordinates of the blob and ``sigma`` is the standard
|
||||
deviation of the Gaussian kernel which detected the blob. When an
|
||||
anisotropic gaussian is used (sigmas per dimension), the detected sigma
|
||||
is returned for each dimension.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://en.wikipedia.org/wiki/Blob_detection#The_Laplacian_of_Gaussian
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage import data, feature, exposure
|
||||
>>> img = data.coins()
|
||||
>>> img = exposure.equalize_hist(img) # improves detection
|
||||
>>> feature.blob_log(img, threshold = .3)
|
||||
array([[124. , 336. , 11.88888889],
|
||||
[198. , 155. , 11.88888889],
|
||||
[194. , 213. , 17.33333333],
|
||||
[121. , 272. , 17.33333333],
|
||||
[263. , 244. , 17.33333333],
|
||||
[194. , 276. , 17.33333333],
|
||||
[266. , 115. , 11.88888889],
|
||||
[128. , 154. , 11.88888889],
|
||||
[260. , 174. , 17.33333333],
|
||||
[198. , 103. , 11.88888889],
|
||||
[126. , 208. , 11.88888889],
|
||||
[127. , 102. , 11.88888889],
|
||||
[263. , 302. , 17.33333333],
|
||||
[197. , 44. , 11.88888889],
|
||||
[185. , 344. , 17.33333333],
|
||||
[126. , 46. , 11.88888889],
|
||||
[113. , 323. , 1. ]])
|
||||
|
||||
Notes
|
||||
-----
|
||||
The radius of each blob is approximately :math:`\sqrt{2}\sigma` for
|
||||
a 2-D image and :math:`\sqrt{3}\sigma` for a 3-D image.
|
||||
""" # noqa: E501
|
||||
image = img_as_float(image)
|
||||
float_dtype = _supported_float_type(image.dtype)
|
||||
image = image.astype(float_dtype, copy=False)
|
||||
|
||||
# if both min and max sigma are scalar, function returns only one sigma
|
||||
scalar_sigma = True if np.isscalar(max_sigma) and np.isscalar(min_sigma) else False
|
||||
|
||||
# Gaussian filter requires that sequence-type sigmas have same
|
||||
# dimensionality as image. This broadcasts scalar kernels
|
||||
if np.isscalar(max_sigma):
|
||||
max_sigma = np.full(image.ndim, max_sigma, dtype=float_dtype)
|
||||
if np.isscalar(min_sigma):
|
||||
min_sigma = np.full(image.ndim, min_sigma, dtype=float_dtype)
|
||||
|
||||
# Convert sequence types to array
|
||||
min_sigma = np.asarray(min_sigma, dtype=float_dtype)
|
||||
max_sigma = np.asarray(max_sigma, dtype=float_dtype)
|
||||
|
||||
if log_scale:
|
||||
start = np.log10(min_sigma)
|
||||
stop = np.log10(max_sigma)
|
||||
sigma_list = np.logspace(start, stop, num_sigma)
|
||||
else:
|
||||
sigma_list = np.linspace(min_sigma, max_sigma, num_sigma)
|
||||
|
||||
# computing gaussian laplace
|
||||
image_cube = np.empty(image.shape + (len(sigma_list),), dtype=float_dtype)
|
||||
for i, s in enumerate(sigma_list):
|
||||
# average s**2 provides scale invariance
|
||||
image_cube[..., i] = -ndi.gaussian_laplace(image, s) * np.mean(s) ** 2
|
||||
|
||||
exclude_border = _format_exclude_border(image.ndim, exclude_border)
|
||||
local_maxima = peak_local_max(
|
||||
image_cube,
|
||||
threshold_abs=threshold,
|
||||
threshold_rel=threshold_rel,
|
||||
exclude_border=exclude_border,
|
||||
footprint=np.ones((3,) * (image.ndim + 1)),
|
||||
)
|
||||
|
||||
# Catch no peaks
|
||||
if local_maxima.size == 0:
|
||||
return np.empty((0, image.ndim + (1 if scalar_sigma else image.ndim)))
|
||||
|
||||
# Convert local_maxima to float64
|
||||
lm = local_maxima.astype(float_dtype)
|
||||
|
||||
# translate final column of lm, which contains the index of the
|
||||
# sigma that produced the maximum intensity value, into the sigma
|
||||
sigmas_of_peaks = sigma_list[local_maxima[:, -1]]
|
||||
|
||||
if scalar_sigma:
|
||||
# select one sigma column, keeping dimension
|
||||
sigmas_of_peaks = sigmas_of_peaks[:, 0:1]
|
||||
|
||||
# Remove sigma index and replace with sigmas
|
||||
lm = np.hstack([lm[:, :-1], sigmas_of_peaks])
|
||||
|
||||
sigma_dim = sigmas_of_peaks.shape[1]
|
||||
|
||||
return _prune_blobs(lm, overlap, sigma_dim=sigma_dim)
|
||||
|
||||
|
||||
def blob_doh(
|
||||
image,
|
||||
min_sigma=1,
|
||||
max_sigma=30,
|
||||
num_sigma=10,
|
||||
threshold=0.01,
|
||||
overlap=0.5,
|
||||
log_scale=False,
|
||||
*,
|
||||
threshold_rel=None,
|
||||
):
|
||||
"""Finds blobs in the given grayscale image.
|
||||
|
||||
Blobs are found using the Determinant of Hessian method [1]_. For each blob
|
||||
found, the method returns its coordinates and the standard deviation
|
||||
of the Gaussian Kernel used for the Hessian matrix whose determinant
|
||||
detected the blob. Determinant of Hessians is approximated using [2]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : 2D ndarray
|
||||
Input grayscale image.Blobs can either be light on dark or vice versa.
|
||||
min_sigma : float, optional
|
||||
The minimum standard deviation for Gaussian Kernel used to compute
|
||||
Hessian matrix. Keep this low to detect smaller blobs.
|
||||
max_sigma : float, optional
|
||||
The maximum standard deviation for Gaussian Kernel used to compute
|
||||
Hessian matrix. Keep this high to detect larger blobs.
|
||||
num_sigma : int, optional
|
||||
The number of intermediate values of standard deviations to consider
|
||||
between `min_sigma` and `max_sigma`.
|
||||
threshold : float or None, optional
|
||||
The absolute lower bound for scale space maxima. Local maxima smaller
|
||||
than `threshold` are ignored. Reduce this to detect blobs with lower
|
||||
intensities. If `threshold_rel` is also specified, whichever threshold
|
||||
is larger will be used. If None, `threshold_rel` is used instead.
|
||||
overlap : float, optional
|
||||
A value between 0 and 1. If the area of two blobs overlaps by a
|
||||
fraction greater than `threshold`, the smaller blob is eliminated.
|
||||
log_scale : bool, optional
|
||||
If set intermediate values of standard deviations are interpolated
|
||||
using a logarithmic scale to the base `10`. If not, linear
|
||||
interpolation is used.
|
||||
threshold_rel : float or None, optional
|
||||
Minimum intensity of peaks, calculated as
|
||||
``max(doh_space) * threshold_rel``, where ``doh_space`` refers to the
|
||||
stack of Determinant-of-Hessian (DoH) images computed internally. This
|
||||
should have a value between 0 and 1. If None, `threshold` is used
|
||||
instead.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A : (n, 3) ndarray
|
||||
A 2d array with each row representing 3 values, ``(y,x,sigma)``
|
||||
where ``(y,x)`` are coordinates of the blob and ``sigma`` is the
|
||||
standard deviation of the Gaussian kernel of the Hessian Matrix whose
|
||||
determinant detected the blob.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://en.wikipedia.org/wiki/Blob_detection#The_determinant_of_the_Hessian
|
||||
.. [2] Herbert Bay, Andreas Ess, Tinne Tuytelaars, Luc Van Gool,
|
||||
"SURF: Speeded Up Robust Features"
|
||||
ftp://ftp.vision.ee.ethz.ch/publications/articles/eth_biwi_00517.pdf
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage import data, feature
|
||||
>>> img = data.coins()
|
||||
>>> feature.blob_doh(img)
|
||||
array([[197. , 153. , 20.33333333],
|
||||
[124. , 336. , 20.33333333],
|
||||
[126. , 153. , 20.33333333],
|
||||
[195. , 100. , 23.55555556],
|
||||
[192. , 212. , 23.55555556],
|
||||
[121. , 271. , 30. ],
|
||||
[126. , 101. , 20.33333333],
|
||||
[193. , 275. , 23.55555556],
|
||||
[123. , 205. , 20.33333333],
|
||||
[270. , 363. , 30. ],
|
||||
[265. , 113. , 23.55555556],
|
||||
[262. , 243. , 23.55555556],
|
||||
[185. , 348. , 30. ],
|
||||
[156. , 302. , 30. ],
|
||||
[123. , 44. , 23.55555556],
|
||||
[260. , 173. , 30. ],
|
||||
[197. , 44. , 20.33333333]])
|
||||
|
||||
Notes
|
||||
-----
|
||||
The radius of each blob is approximately `sigma`.
|
||||
Computation of Determinant of Hessians is independent of the standard
|
||||
deviation. Therefore detecting larger blobs won't take more time. In
|
||||
methods line :py:meth:`blob_dog` and :py:meth:`blob_log` the computation
|
||||
of Gaussians for larger `sigma` takes more time. The downside is that
|
||||
this method can't be used for detecting blobs of radius less than `3px`
|
||||
due to the box filters used in the approximation of Hessian Determinant.
|
||||
""" # noqa: E501
|
||||
check_nD(image, 2)
|
||||
|
||||
image = img_as_float(image)
|
||||
float_dtype = _supported_float_type(image.dtype)
|
||||
image = image.astype(float_dtype, copy=False)
|
||||
|
||||
image = integral_image(image)
|
||||
|
||||
if log_scale:
|
||||
start, stop = math.log(min_sigma, 10), math.log(max_sigma, 10)
|
||||
sigma_list = np.logspace(start, stop, num_sigma)
|
||||
else:
|
||||
sigma_list = np.linspace(min_sigma, max_sigma, num_sigma)
|
||||
|
||||
image_cube = np.empty(shape=image.shape + (len(sigma_list),), dtype=float_dtype)
|
||||
for j, s in enumerate(sigma_list):
|
||||
image_cube[..., j] = _hessian_matrix_det(image, s)
|
||||
|
||||
local_maxima = peak_local_max(
|
||||
image_cube,
|
||||
threshold_abs=threshold,
|
||||
threshold_rel=threshold_rel,
|
||||
exclude_border=False,
|
||||
footprint=np.ones((3,) * image_cube.ndim),
|
||||
)
|
||||
|
||||
# Catch no peaks
|
||||
if local_maxima.size == 0:
|
||||
return np.empty((0, 3))
|
||||
# Convert local_maxima to float64
|
||||
lm = local_maxima.astype(np.float64)
|
||||
# Convert the last index to its corresponding scale value
|
||||
lm[:, -1] = sigma_list[local_maxima[:, -1]]
|
||||
return _prune_blobs(lm, overlap)
|
||||
209
.CondaPkg/env/Lib/site-packages/skimage/feature/brief.py
vendored
Normal file
209
.CondaPkg/env/Lib/site-packages/skimage/feature/brief.py
vendored
Normal file
@@ -0,0 +1,209 @@
|
||||
import copy
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .._shared.filters import gaussian
|
||||
from .._shared.utils import check_nD
|
||||
from .brief_cy import _brief_loop
|
||||
from .util import (
|
||||
DescriptorExtractor,
|
||||
_mask_border_keypoints,
|
||||
_prepare_grayscale_input_2D,
|
||||
)
|
||||
|
||||
|
||||
class BRIEF(DescriptorExtractor):
|
||||
"""BRIEF binary descriptor extractor.
|
||||
|
||||
BRIEF (Binary Robust Independent Elementary Features) is an efficient
|
||||
feature point descriptor. It is highly discriminative even when using
|
||||
relatively few bits and is computed using simple intensity difference
|
||||
tests.
|
||||
|
||||
For each keypoint, intensity comparisons are carried out for a specifically
|
||||
distributed number N of pixel-pairs resulting in a binary descriptor of
|
||||
length N. For binary descriptors the Hamming distance can be used for
|
||||
feature matching, which leads to lower computational cost in comparison to
|
||||
the L2 norm.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
descriptor_size : int, optional
|
||||
Size of BRIEF descriptor for each keypoint. Sizes 128, 256 and 512
|
||||
recommended by the authors. Default is 256.
|
||||
patch_size : int, optional
|
||||
Length of the two dimensional square patch sampling region around
|
||||
the keypoints. Default is 49.
|
||||
mode : {'normal', 'uniform'}, optional
|
||||
Probability distribution for sampling location of decision pixel-pairs
|
||||
around keypoints.
|
||||
rng : {`numpy.random.Generator`, int}, optional
|
||||
Pseudo-random number generator (RNG).
|
||||
By default, a PCG64 generator is used (see :func:`numpy.random.default_rng`).
|
||||
If `rng` is an int, it is used to seed the generator.
|
||||
|
||||
The PRNG is used for the random sampling of the decision
|
||||
pixel-pairs. From a square window with length `patch_size`,
|
||||
pixel pairs are sampled using the `mode` parameter to build
|
||||
the descriptors using intensity comparison.
|
||||
|
||||
For matching across images, the same `rng` should be used to construct
|
||||
descriptors. To facilitate this:
|
||||
|
||||
(a) `rng` defaults to 1
|
||||
(b) Subsequent calls of the ``extract`` method will use the same rng/seed.
|
||||
sigma : float, optional
|
||||
Standard deviation of the Gaussian low-pass filter applied to the image
|
||||
to alleviate noise sensitivity, which is strongly recommended to obtain
|
||||
discriminative and good descriptors.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
descriptors : (Q, `descriptor_size`) array of dtype bool
|
||||
2D ndarray of binary descriptors of size `descriptor_size` for Q
|
||||
keypoints after filtering out border keypoints with value at an
|
||||
index ``(i, j)`` either being ``True`` or ``False`` representing
|
||||
the outcome of the intensity comparison for i-th keypoint on j-th
|
||||
decision pixel-pair. It is ``Q == np.sum(mask)``.
|
||||
mask : (N,) array of dtype bool
|
||||
Mask indicating whether a keypoint has been filtered out
|
||||
(``False``) or is described in the `descriptors` array (``True``).
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.feature import (corner_harris, corner_peaks, BRIEF,
|
||||
... match_descriptors)
|
||||
>>> import numpy as np
|
||||
>>> square1 = np.zeros((8, 8), dtype=np.int32)
|
||||
>>> square1[2:6, 2:6] = 1
|
||||
>>> square1
|
||||
array([[0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)
|
||||
>>> square2 = np.zeros((9, 9), dtype=np.int32)
|
||||
>>> square2[2:7, 2:7] = 1
|
||||
>>> square2
|
||||
array([[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, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 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]], dtype=int32)
|
||||
>>> keypoints1 = corner_peaks(corner_harris(square1), min_distance=1)
|
||||
>>> keypoints2 = corner_peaks(corner_harris(square2), min_distance=1)
|
||||
>>> extractor = BRIEF(patch_size=5)
|
||||
>>> extractor.extract(square1, keypoints1)
|
||||
>>> descriptors1 = extractor.descriptors
|
||||
>>> extractor.extract(square2, keypoints2)
|
||||
>>> descriptors2 = extractor.descriptors
|
||||
>>> matches = match_descriptors(descriptors1, descriptors2)
|
||||
>>> matches
|
||||
array([[0, 0],
|
||||
[1, 1],
|
||||
[2, 2],
|
||||
[3, 3]])
|
||||
>>> keypoints1[matches[:, 0]]
|
||||
array([[2, 2],
|
||||
[2, 5],
|
||||
[5, 2],
|
||||
[5, 5]])
|
||||
>>> keypoints2[matches[:, 1]]
|
||||
array([[2, 2],
|
||||
[2, 6],
|
||||
[6, 2],
|
||||
[6, 6]])
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, descriptor_size=256, patch_size=49, mode='normal', sigma=1, rng=1
|
||||
):
|
||||
mode = mode.lower()
|
||||
if mode not in ('normal', 'uniform'):
|
||||
raise ValueError("`mode` must be 'normal' or 'uniform'.")
|
||||
|
||||
self.descriptor_size = descriptor_size
|
||||
self.patch_size = patch_size
|
||||
self.mode = mode
|
||||
self.sigma = sigma
|
||||
|
||||
if isinstance(rng, np.random.Generator):
|
||||
# Spawn an independent RNG from parent RNG provided by the user.
|
||||
# This is necessary so that we can safely deepcopy the RNG.
|
||||
# See https://github.com/scikit-learn/scikit-learn/issues/16988#issuecomment-1518037853
|
||||
bg = rng._bit_generator
|
||||
ss = bg._seed_seq
|
||||
(child_ss,) = ss.spawn(1)
|
||||
self.rng = np.random.Generator(type(bg)(child_ss))
|
||||
elif rng is None:
|
||||
self.rng = np.random.default_rng(np.random.SeedSequence())
|
||||
else:
|
||||
self.rng = np.random.default_rng(rng)
|
||||
|
||||
self.descriptors = None
|
||||
self.mask = None
|
||||
|
||||
def extract(self, image, keypoints):
|
||||
"""Extract BRIEF binary descriptors for given keypoints in image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : 2D array
|
||||
Input image.
|
||||
keypoints : (N, 2) array
|
||||
Keypoint coordinates as ``(row, col)``.
|
||||
|
||||
"""
|
||||
check_nD(image, 2)
|
||||
|
||||
# Copy RNG so we can repeatedly call extract with the same random values
|
||||
rng = copy.deepcopy(self.rng)
|
||||
|
||||
image = _prepare_grayscale_input_2D(image)
|
||||
|
||||
# Gaussian low-pass filtering to alleviate noise sensitivity
|
||||
image = np.ascontiguousarray(gaussian(image, sigma=self.sigma, mode='reflect'))
|
||||
|
||||
# Sampling pairs of decision pixels in patch_size x patch_size window
|
||||
desc_size = self.descriptor_size
|
||||
patch_size = self.patch_size
|
||||
if self.mode == 'normal':
|
||||
samples = (patch_size / 5.0) * rng.standard_normal(desc_size * 8)
|
||||
samples = np.array(samples, dtype=np.int32)
|
||||
samples = samples[
|
||||
(samples < (patch_size // 2)) & (samples > -(patch_size - 2) // 2)
|
||||
]
|
||||
|
||||
pos1 = samples[: desc_size * 2].reshape(desc_size, 2)
|
||||
pos2 = samples[desc_size * 2 : desc_size * 4].reshape(desc_size, 2)
|
||||
elif self.mode == 'uniform':
|
||||
samples = rng.integers(
|
||||
-(patch_size - 2) // 2, (patch_size // 2) + 1, (desc_size * 2, 2)
|
||||
)
|
||||
samples = np.array(samples, dtype=np.int32)
|
||||
pos1, pos2 = np.split(samples, 2)
|
||||
|
||||
pos1 = np.ascontiguousarray(pos1)
|
||||
pos2 = np.ascontiguousarray(pos2)
|
||||
|
||||
# Removing keypoints that are within (patch_size / 2) distance from the
|
||||
# image border
|
||||
self.mask = _mask_border_keypoints(image.shape, keypoints, patch_size // 2)
|
||||
|
||||
keypoints = np.array(
|
||||
keypoints[self.mask, :], dtype=np.int64, order='C', copy=False
|
||||
)
|
||||
|
||||
self.descriptors = np.zeros(
|
||||
(keypoints.shape[0], desc_size), dtype=bool, order='C'
|
||||
)
|
||||
|
||||
_brief_loop(image, self.descriptors.view(np.uint8), keypoints, pos1, pos2)
|
||||
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/brief_cy.cp312-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/brief_cy.cp312-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/brief_cy.cp312-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/brief_cy.cp312-win_amd64.pyd
vendored
Normal file
Binary file not shown.
343
.CondaPkg/env/Lib/site-packages/skimage/feature/censure.py
vendored
Normal file
343
.CondaPkg/env/Lib/site-packages/skimage/feature/censure.py
vendored
Normal file
@@ -0,0 +1,343 @@
|
||||
import numpy as np
|
||||
from scipy.ndimage import maximum_filter, minimum_filter, convolve
|
||||
|
||||
from ..transform import integral_image
|
||||
from .corner import structure_tensor
|
||||
from ..morphology import octagon, star
|
||||
from .censure_cy import _censure_dob_loop
|
||||
from ..feature.util import (
|
||||
FeatureDetector,
|
||||
_prepare_grayscale_input_2D,
|
||||
_mask_border_keypoints,
|
||||
)
|
||||
from .._shared.utils import check_nD
|
||||
|
||||
# The paper(Reference [1]) mentions the sizes of the Octagon shaped filter
|
||||
# kernel for the first seven scales only. The sizes of the later scales
|
||||
# have been extrapolated based on the following statement in the paper.
|
||||
# "These octagons scale linearly and were experimentally chosen to correspond
|
||||
# to the seven DOBs described in the previous section."
|
||||
OCTAGON_OUTER_SHAPE = [
|
||||
(5, 2),
|
||||
(5, 3),
|
||||
(7, 3),
|
||||
(9, 4),
|
||||
(9, 7),
|
||||
(13, 7),
|
||||
(15, 10),
|
||||
(15, 11),
|
||||
(15, 12),
|
||||
(17, 13),
|
||||
(17, 14),
|
||||
]
|
||||
OCTAGON_INNER_SHAPE = [
|
||||
(3, 0),
|
||||
(3, 1),
|
||||
(3, 2),
|
||||
(5, 2),
|
||||
(5, 3),
|
||||
(5, 4),
|
||||
(5, 5),
|
||||
(7, 5),
|
||||
(7, 6),
|
||||
(9, 6),
|
||||
(9, 7),
|
||||
]
|
||||
|
||||
# The sizes for the STAR shaped filter kernel for different scales have been
|
||||
# taken from the OpenCV implementation.
|
||||
STAR_SHAPE = [1, 2, 3, 4, 6, 8, 11, 12, 16, 22, 23, 32, 45, 46, 64, 90, 128]
|
||||
STAR_FILTER_SHAPE = [
|
||||
(1, 0),
|
||||
(3, 1),
|
||||
(4, 2),
|
||||
(5, 3),
|
||||
(7, 4),
|
||||
(8, 5),
|
||||
(9, 6),
|
||||
(11, 8),
|
||||
(13, 10),
|
||||
(14, 11),
|
||||
(15, 12),
|
||||
(16, 14),
|
||||
]
|
||||
|
||||
|
||||
def _filter_image(image, min_scale, max_scale, mode):
|
||||
response = np.zeros(
|
||||
(image.shape[0], image.shape[1], max_scale - min_scale + 1), dtype=np.float64
|
||||
)
|
||||
|
||||
if mode == 'dob':
|
||||
# make response[:, :, i] contiguous memory block
|
||||
item_size = response.itemsize
|
||||
response.strides = (
|
||||
item_size * response.shape[1],
|
||||
item_size,
|
||||
item_size * response.shape[0] * response.shape[1],
|
||||
)
|
||||
|
||||
integral_img = integral_image(image)
|
||||
|
||||
for i in range(max_scale - min_scale + 1):
|
||||
n = min_scale + i
|
||||
|
||||
# Constant multipliers for the outer region and the inner region
|
||||
# of the bi-level filters with the constraint of keeping the
|
||||
# DC bias 0.
|
||||
inner_weight = 1.0 / (2 * n + 1) ** 2
|
||||
outer_weight = 1.0 / (12 * n**2 + 4 * n)
|
||||
|
||||
_censure_dob_loop(
|
||||
n, integral_img, response[:, :, i], inner_weight, outer_weight
|
||||
)
|
||||
|
||||
# NOTE : For the Octagon shaped filter, we implemented and evaluated the
|
||||
# slanted integral image based image filtering but the performance was
|
||||
# more or less equal to image filtering using
|
||||
# scipy.ndimage.filters.convolve(). Hence we have decided to use the
|
||||
# later for a much cleaner implementation.
|
||||
elif mode == 'octagon':
|
||||
# TODO : Decide the shapes of Octagon filters for scales > 7
|
||||
|
||||
for i in range(max_scale - min_scale + 1):
|
||||
mo, no = OCTAGON_OUTER_SHAPE[min_scale + i - 1]
|
||||
mi, ni = OCTAGON_INNER_SHAPE[min_scale + i - 1]
|
||||
response[:, :, i] = convolve(image, _octagon_kernel(mo, no, mi, ni))
|
||||
|
||||
elif mode == 'star':
|
||||
for i in range(max_scale - min_scale + 1):
|
||||
m = STAR_SHAPE[STAR_FILTER_SHAPE[min_scale + i - 1][0]]
|
||||
n = STAR_SHAPE[STAR_FILTER_SHAPE[min_scale + i - 1][1]]
|
||||
response[:, :, i] = convolve(image, _star_kernel(m, n))
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def _octagon_kernel(mo, no, mi, ni):
|
||||
outer = (mo + 2 * no) ** 2 - 2 * no * (no + 1)
|
||||
inner = (mi + 2 * ni) ** 2 - 2 * ni * (ni + 1)
|
||||
outer_weight = 1.0 / (outer - inner)
|
||||
inner_weight = 1.0 / inner
|
||||
c = ((mo + 2 * no) - (mi + 2 * ni)) // 2
|
||||
outer_oct = octagon(mo, no)
|
||||
inner_oct = np.zeros((mo + 2 * no, mo + 2 * no))
|
||||
inner_oct[c:-c, c:-c] = octagon(mi, ni)
|
||||
bfilter = outer_weight * outer_oct - (outer_weight + inner_weight) * inner_oct
|
||||
return bfilter
|
||||
|
||||
|
||||
def _star_kernel(m, n):
|
||||
c = m + m // 2 - n - n // 2
|
||||
outer_star = star(m)
|
||||
inner_star = np.zeros_like(outer_star)
|
||||
inner_star[c:-c, c:-c] = star(n)
|
||||
outer_weight = 1.0 / (np.sum(outer_star - inner_star))
|
||||
inner_weight = 1.0 / np.sum(inner_star)
|
||||
bfilter = outer_weight * outer_star - (outer_weight + inner_weight) * inner_star
|
||||
return bfilter
|
||||
|
||||
|
||||
def _suppress_lines(feature_mask, image, sigma, line_threshold):
|
||||
Arr, Arc, Acc = structure_tensor(image, sigma, order='rc')
|
||||
feature_mask[(Arr + Acc) ** 2 > line_threshold * (Arr * Acc - Arc**2)] = False
|
||||
|
||||
|
||||
class CENSURE(FeatureDetector):
|
||||
"""CENSURE keypoint detector.
|
||||
|
||||
min_scale : int, optional
|
||||
Minimum scale to extract keypoints from.
|
||||
max_scale : int, optional
|
||||
Maximum scale to extract keypoints from. The keypoints will be
|
||||
extracted from all the scales except the first and the last i.e.
|
||||
from the scales in the range [min_scale + 1, max_scale - 1]. The filter
|
||||
sizes for different scales is such that the two adjacent scales
|
||||
comprise of an octave.
|
||||
mode : {'DoB', 'Octagon', 'STAR'}, optional
|
||||
Type of bi-level filter used to get the scales of the input image.
|
||||
Possible values are 'DoB', 'Octagon' and 'STAR'. The three modes
|
||||
represent the shape of the bi-level filters i.e. box(square), octagon
|
||||
and star respectively. For instance, a bi-level octagon filter consists
|
||||
of a smaller inner octagon and a larger outer octagon with the filter
|
||||
weights being uniformly negative in both the inner octagon while
|
||||
uniformly positive in the difference region. Use STAR and Octagon for
|
||||
better features and DoB for better performance.
|
||||
non_max_threshold : float, optional
|
||||
Threshold value used to suppress maximas and minimas with a weak
|
||||
magnitude response obtained after Non-Maximal Suppression.
|
||||
line_threshold : float, optional
|
||||
Threshold for rejecting interest points which have ratio of principal
|
||||
curvatures greater than this value.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
keypoints : (N, 2) array
|
||||
Keypoint coordinates as ``(row, col)``.
|
||||
scales : (N,) array
|
||||
Corresponding scales.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Motilal Agrawal, Kurt Konolige and Morten Rufus Blas
|
||||
"CENSURE: Center Surround Extremas for Realtime Feature
|
||||
Detection and Matching",
|
||||
https://link.springer.com/chapter/10.1007/978-3-540-88693-8_8
|
||||
:DOI:`10.1007/978-3-540-88693-8_8`
|
||||
|
||||
.. [2] Adam Schmidt, Marek Kraft, Michal Fularz and Zuzanna Domagala
|
||||
"Comparative Assessment of Point Feature Detectors and
|
||||
Descriptors in the Context of Robot Navigation"
|
||||
http://yadda.icm.edu.pl/yadda/element/bwmeta1.element.baztech-268aaf28-0faf-4872-a4df-7e2e61cb364c/c/Schmidt_comparative.pdf
|
||||
:DOI:`10.1.1.465.1117`
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.data import astronaut
|
||||
>>> from skimage.color import rgb2gray
|
||||
>>> from skimage.feature import CENSURE
|
||||
>>> img = rgb2gray(astronaut()[100:300, 100:300])
|
||||
>>> censure = CENSURE()
|
||||
>>> censure.detect(img)
|
||||
>>> censure.keypoints
|
||||
array([[ 4, 148],
|
||||
[ 12, 73],
|
||||
[ 21, 176],
|
||||
[ 91, 22],
|
||||
[ 93, 56],
|
||||
[ 94, 22],
|
||||
[ 95, 54],
|
||||
[100, 51],
|
||||
[103, 51],
|
||||
[106, 67],
|
||||
[108, 15],
|
||||
[117, 20],
|
||||
[122, 60],
|
||||
[125, 37],
|
||||
[129, 37],
|
||||
[133, 76],
|
||||
[145, 44],
|
||||
[146, 94],
|
||||
[150, 114],
|
||||
[153, 33],
|
||||
[154, 156],
|
||||
[155, 151],
|
||||
[184, 63]])
|
||||
>>> censure.scales
|
||||
array([2, 6, 6, 2, 4, 3, 2, 3, 2, 6, 3, 2, 2, 3, 2, 2, 2, 3, 2, 2, 4, 2,
|
||||
2])
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
min_scale=1,
|
||||
max_scale=7,
|
||||
mode='DoB',
|
||||
non_max_threshold=0.15,
|
||||
line_threshold=10,
|
||||
):
|
||||
mode = mode.lower()
|
||||
if mode not in ('dob', 'octagon', 'star'):
|
||||
raise ValueError("`mode` must be one of 'DoB', 'Octagon', 'STAR'.")
|
||||
|
||||
if min_scale < 1 or max_scale < 1 or max_scale - min_scale < 2:
|
||||
raise ValueError(
|
||||
'The scales must be >= 1 and the number of ' 'scales should be >= 3.'
|
||||
)
|
||||
|
||||
self.min_scale = min_scale
|
||||
self.max_scale = max_scale
|
||||
self.mode = mode
|
||||
self.non_max_threshold = non_max_threshold
|
||||
self.line_threshold = line_threshold
|
||||
|
||||
self.keypoints = None
|
||||
self.scales = None
|
||||
|
||||
def detect(self, image):
|
||||
"""Detect CENSURE keypoints along with the corresponding scale.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : 2D ndarray
|
||||
Input image.
|
||||
|
||||
"""
|
||||
|
||||
# (1) First we generate the required scales on the input grayscale
|
||||
# image using a bi-level filter and stack them up in `filter_response`.
|
||||
|
||||
# (2) We then perform Non-Maximal suppression in 3 x 3 x 3 window on
|
||||
# the filter_response to suppress points that are neither minima or
|
||||
# maxima in 3 x 3 x 3 neighborhood. We obtain a boolean ndarray
|
||||
# `feature_mask` containing all the minimas and maximas in
|
||||
# `filter_response` as True.
|
||||
# (3) Then we suppress all the points in the `feature_mask` for which
|
||||
# the corresponding point in the image at a particular scale has the
|
||||
# ratio of principal curvatures greater than `line_threshold`.
|
||||
# (4) Finally, we remove the border keypoints and return the keypoints
|
||||
# along with its corresponding scale.
|
||||
|
||||
check_nD(image, 2)
|
||||
|
||||
num_scales = self.max_scale - self.min_scale
|
||||
|
||||
image = np.ascontiguousarray(_prepare_grayscale_input_2D(image))
|
||||
|
||||
# Generating all the scales
|
||||
filter_response = _filter_image(
|
||||
image, self.min_scale, self.max_scale, self.mode
|
||||
)
|
||||
|
||||
# Suppressing points that are neither minima or maxima in their
|
||||
# 3 x 3 x 3 neighborhood to zero
|
||||
minimas = minimum_filter(filter_response, (3, 3, 3)) == filter_response
|
||||
maximas = maximum_filter(filter_response, (3, 3, 3)) == filter_response
|
||||
|
||||
feature_mask = minimas | maximas
|
||||
feature_mask[filter_response < self.non_max_threshold] = False
|
||||
|
||||
for i in range(1, num_scales):
|
||||
# sigma = (window_size - 1) / 6.0, so the window covers > 99% of
|
||||
# the kernel's distribution
|
||||
# window_size = 7 + 2 * (min_scale - 1 + i)
|
||||
# Hence sigma = 1 + (min_scale - 1 + i)/ 3.0
|
||||
_suppress_lines(
|
||||
feature_mask[:, :, i],
|
||||
image,
|
||||
(1 + (self.min_scale + i - 1) / 3.0),
|
||||
self.line_threshold,
|
||||
)
|
||||
|
||||
rows, cols, scales = np.nonzero(feature_mask[..., 1:num_scales])
|
||||
keypoints = np.column_stack([rows, cols])
|
||||
scales = scales + self.min_scale + 1
|
||||
|
||||
if self.mode == 'dob':
|
||||
self.keypoints = keypoints
|
||||
self.scales = scales
|
||||
return
|
||||
|
||||
cumulative_mask = np.zeros(keypoints.shape[0], dtype=bool)
|
||||
|
||||
if self.mode == 'octagon':
|
||||
for i in range(self.min_scale + 1, self.max_scale):
|
||||
c = (OCTAGON_OUTER_SHAPE[i - 1][0] - 1) // 2 + OCTAGON_OUTER_SHAPE[
|
||||
i - 1
|
||||
][1]
|
||||
cumulative_mask |= _mask_border_keypoints(image.shape, keypoints, c) & (
|
||||
scales == i
|
||||
)
|
||||
elif self.mode == 'star':
|
||||
for i in range(self.min_scale + 1, self.max_scale):
|
||||
c = (
|
||||
STAR_SHAPE[STAR_FILTER_SHAPE[i - 1][0]]
|
||||
+ STAR_SHAPE[STAR_FILTER_SHAPE[i - 1][0]] // 2
|
||||
)
|
||||
cumulative_mask |= _mask_border_keypoints(image.shape, keypoints, c) & (
|
||||
scales == i
|
||||
)
|
||||
|
||||
self.keypoints = keypoints[cumulative_mask]
|
||||
self.scales = scales[cumulative_mask]
|
||||
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/censure_cy.cp312-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/censure_cy.cp312-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/censure_cy.cp312-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/censure_cy.cp312-win_amd64.pyd
vendored
Normal file
Binary file not shown.
1355
.CondaPkg/env/Lib/site-packages/skimage/feature/corner.py
vendored
Normal file
1355
.CondaPkg/env/Lib/site-packages/skimage/feature/corner.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/corner_cy.cp312-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/corner_cy.cp312-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/corner_cy.cp312-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/corner_cy.cp312-win_amd64.pyd
vendored
Normal file
Binary file not shown.
339
.CondaPkg/env/Lib/site-packages/skimage/feature/haar.py
vendored
Normal file
339
.CondaPkg/env/Lib/site-packages/skimage/feature/haar.py
vendored
Normal file
@@ -0,0 +1,339 @@
|
||||
from itertools import chain
|
||||
from operator import add
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ._haar import haar_like_feature_coord_wrapper
|
||||
from ._haar import haar_like_feature_wrapper
|
||||
from ..color import gray2rgb
|
||||
from ..draw import rectangle
|
||||
from ..util import img_as_float
|
||||
|
||||
FEATURE_TYPE = ('type-2-x', 'type-2-y', 'type-3-x', 'type-3-y', 'type-4')
|
||||
|
||||
|
||||
def _validate_feature_type(feature_type):
|
||||
"""Transform feature type to an iterable and check that it exists."""
|
||||
if feature_type is None:
|
||||
feature_type_ = FEATURE_TYPE
|
||||
else:
|
||||
if isinstance(feature_type, str):
|
||||
feature_type_ = [feature_type]
|
||||
else:
|
||||
feature_type_ = feature_type
|
||||
for feat_t in feature_type_:
|
||||
if feat_t not in FEATURE_TYPE:
|
||||
raise ValueError(
|
||||
f'The given feature type is unknown. Got {feat_t} instead of one '
|
||||
f'of {FEATURE_TYPE}.'
|
||||
)
|
||||
return feature_type_
|
||||
|
||||
|
||||
def haar_like_feature_coord(width, height, feature_type=None):
|
||||
"""Compute the coordinates of Haar-like features.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
width : int
|
||||
Width of the detection window.
|
||||
height : int
|
||||
Height of the detection window.
|
||||
feature_type : str or list of str or None, optional
|
||||
The type of feature to consider:
|
||||
|
||||
- 'type-2-x': 2 rectangles varying along the x axis;
|
||||
- 'type-2-y': 2 rectangles varying along the y axis;
|
||||
- 'type-3-x': 3 rectangles varying along the x axis;
|
||||
- 'type-3-y': 3 rectangles varying along the y axis;
|
||||
- 'type-4': 4 rectangles varying along x and y axis.
|
||||
|
||||
By default all features are extracted.
|
||||
|
||||
Returns
|
||||
-------
|
||||
feature_coord : (n_features, n_rectangles, 2, 2), ndarray of list of \
|
||||
tuple coord
|
||||
Coordinates of the rectangles for each feature.
|
||||
feature_type : (n_features,), ndarray of str
|
||||
The corresponding type for each feature.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from skimage.transform import integral_image
|
||||
>>> from skimage.feature import haar_like_feature_coord
|
||||
>>> feat_coord, feat_type = haar_like_feature_coord(2, 2, 'type-4')
|
||||
>>> feat_coord # doctest: +SKIP
|
||||
array([ list([[(0, 0), (0, 0)], [(0, 1), (0, 1)],
|
||||
[(1, 1), (1, 1)], [(1, 0), (1, 0)]])], dtype=object)
|
||||
>>> feat_type
|
||||
array(['type-4'], dtype=object)
|
||||
|
||||
"""
|
||||
feature_type_ = _validate_feature_type(feature_type)
|
||||
|
||||
feat_coord, feat_type = zip(
|
||||
*[
|
||||
haar_like_feature_coord_wrapper(width, height, feat_t)
|
||||
for feat_t in feature_type_
|
||||
]
|
||||
)
|
||||
|
||||
return np.concatenate(feat_coord), np.hstack(feat_type)
|
||||
|
||||
|
||||
def haar_like_feature(
|
||||
int_image, r, c, width, height, feature_type=None, feature_coord=None
|
||||
):
|
||||
"""Compute the Haar-like features for a region of interest (ROI) of an
|
||||
integral image.
|
||||
|
||||
Haar-like features have been successfully used for image classification and
|
||||
object detection [1]_. It has been used for real-time face detection
|
||||
algorithm proposed in [2]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
int_image : (M, N) ndarray
|
||||
Integral image for which the features need to be computed.
|
||||
r : int
|
||||
Row-coordinate of top left corner of the detection window.
|
||||
c : int
|
||||
Column-coordinate of top left corner of the detection window.
|
||||
width : int
|
||||
Width of the detection window.
|
||||
height : int
|
||||
Height of the detection window.
|
||||
feature_type : str or list of str or None, optional
|
||||
The type of feature to consider:
|
||||
|
||||
- 'type-2-x': 2 rectangles varying along the x axis;
|
||||
- 'type-2-y': 2 rectangles varying along the y axis;
|
||||
- 'type-3-x': 3 rectangles varying along the x axis;
|
||||
- 'type-3-y': 3 rectangles varying along the y axis;
|
||||
- 'type-4': 4 rectangles varying along x and y axis.
|
||||
|
||||
By default all features are extracted.
|
||||
|
||||
If using with `feature_coord`, it should correspond to the feature
|
||||
type of each associated coordinate feature.
|
||||
feature_coord : ndarray of list of tuples or None, optional
|
||||
The array of coordinates to be extracted. This is useful when you want
|
||||
to recompute only a subset of features. In this case `feature_type`
|
||||
needs to be an array containing the type of each feature, as returned
|
||||
by :func:`haar_like_feature_coord`. By default, all coordinates are
|
||||
computed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
haar_features : (n_features,) ndarray of int or float
|
||||
Resulting Haar-like features. Each value is equal to the subtraction of
|
||||
sums of the positive and negative rectangles. The data type depends of
|
||||
the data type of `int_image`: `int` when the data type of `int_image`
|
||||
is `uint` or `int` and `float` when the data type of `int_image` is
|
||||
`float`.
|
||||
|
||||
Notes
|
||||
-----
|
||||
When extracting those features in parallel, be aware that the choice of the
|
||||
backend (i.e. multiprocessing vs threading) will have an impact on the
|
||||
performance. The rule of thumb is as follows: use multiprocessing when
|
||||
extracting features for all possible ROI in an image; use threading when
|
||||
extracting the feature at specific location for a limited number of ROIs.
|
||||
Refer to the example
|
||||
:ref:`sphx_glr_auto_examples_applications_plot_haar_extraction_selection_classification.py`
|
||||
for more insights.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from skimage.transform import integral_image
|
||||
>>> from skimage.feature import haar_like_feature
|
||||
>>> img = np.ones((5, 5), dtype=np.uint8)
|
||||
>>> img_ii = integral_image(img)
|
||||
>>> feature = haar_like_feature(img_ii, 0, 0, 5, 5, 'type-3-x')
|
||||
>>> feature
|
||||
array([-1, -2, -3, -4, -5, -1, -2, -3, -4, -5, -1, -2, -3, -4, -5, -1, -2,
|
||||
-3, -4, -1, -2, -3, -4, -1, -2, -3, -4, -1, -2, -3, -1, -2, -3, -1,
|
||||
-2, -3, -1, -2, -1, -2, -1, -2, -1, -1, -1])
|
||||
|
||||
You can compute the feature for some pre-computed coordinates.
|
||||
|
||||
>>> from skimage.feature import haar_like_feature_coord
|
||||
>>> feature_coord, feature_type = zip(
|
||||
... *[haar_like_feature_coord(5, 5, feat_t)
|
||||
... for feat_t in ('type-2-x', 'type-3-x')])
|
||||
>>> # only select one feature over two
|
||||
>>> feature_coord = np.concatenate([x[::2] for x in feature_coord])
|
||||
>>> feature_type = np.concatenate([x[::2] for x in feature_type])
|
||||
>>> feature = haar_like_feature(img_ii, 0, 0, 5, 5,
|
||||
... feature_type=feature_type,
|
||||
... feature_coord=feature_coord)
|
||||
>>> feature
|
||||
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, 0, 0, 0, 0, 0, 0, -1, -3, -5, -2, -4, -1,
|
||||
-3, -5, -2, -4, -2, -4, -2, -4, -2, -1, -3, -2, -1, -1, -1, -1, -1])
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://en.wikipedia.org/wiki/Haar-like_feature
|
||||
.. [2] Oren, M., Papageorgiou, C., Sinha, P., Osuna, E., & Poggio, T.
|
||||
(1997, June). Pedestrian detection using wavelet templates.
|
||||
In Computer Vision and Pattern Recognition, 1997. Proceedings.,
|
||||
1997 IEEE Computer Society Conference on (pp. 193-199). IEEE.
|
||||
http://tinyurl.com/y6ulxfta
|
||||
:DOI:`10.1109/CVPR.1997.609319`
|
||||
.. [3] Viola, Paul, and Michael J. Jones. "Robust real-time face
|
||||
detection." International journal of computer vision 57.2
|
||||
(2004): 137-154.
|
||||
https://www.merl.com/publications/docs/TR2004-043.pdf
|
||||
:DOI:`10.1109/CVPR.2001.990517`
|
||||
|
||||
"""
|
||||
if feature_coord is None:
|
||||
feature_type_ = _validate_feature_type(feature_type)
|
||||
|
||||
return np.hstack(
|
||||
list(
|
||||
chain.from_iterable(
|
||||
haar_like_feature_wrapper(
|
||||
int_image, r, c, width, height, feat_t, feature_coord
|
||||
)
|
||||
for feat_t in feature_type_
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
if feature_coord.shape[0] != feature_type.shape[0]:
|
||||
raise ValueError(
|
||||
"Inconsistent size between feature coordinates" "and feature types."
|
||||
)
|
||||
|
||||
mask_feature = [feature_type == feat_t for feat_t in FEATURE_TYPE]
|
||||
haar_feature_idx, haar_feature = zip(
|
||||
*[
|
||||
(
|
||||
np.flatnonzero(mask),
|
||||
haar_like_feature_wrapper(
|
||||
int_image, r, c, width, height, feat_t, feature_coord[mask]
|
||||
),
|
||||
)
|
||||
for mask, feat_t in zip(mask_feature, FEATURE_TYPE)
|
||||
if np.count_nonzero(mask)
|
||||
]
|
||||
)
|
||||
|
||||
haar_feature_idx = np.concatenate(haar_feature_idx)
|
||||
haar_feature = np.concatenate(haar_feature)
|
||||
|
||||
haar_feature[haar_feature_idx] = haar_feature.copy()
|
||||
return haar_feature
|
||||
|
||||
|
||||
def draw_haar_like_feature(
|
||||
image,
|
||||
r,
|
||||
c,
|
||||
width,
|
||||
height,
|
||||
feature_coord,
|
||||
color_positive_block=(1.0, 0.0, 0.0),
|
||||
color_negative_block=(0.0, 1.0, 0.0),
|
||||
alpha=0.5,
|
||||
max_n_features=None,
|
||||
rng=None,
|
||||
):
|
||||
"""Visualization of Haar-like features.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : (M, N) ndarray
|
||||
The region of an integral image for which the features need to be
|
||||
computed.
|
||||
r : int
|
||||
Row-coordinate of top left corner of the detection window.
|
||||
c : int
|
||||
Column-coordinate of top left corner of the detection window.
|
||||
width : int
|
||||
Width of the detection window.
|
||||
height : int
|
||||
Height of the detection window.
|
||||
feature_coord : ndarray of list of tuples or None, optional
|
||||
The array of coordinates to be extracted. This is useful when you want
|
||||
to recompute only a subset of features. In this case `feature_type`
|
||||
needs to be an array containing the type of each feature, as returned
|
||||
by :func:`haar_like_feature_coord`. By default, all coordinates are
|
||||
computed.
|
||||
color_positive_block : tuple of 3 floats
|
||||
Floats specifying the color for the positive block. Corresponding
|
||||
values define (R, G, B) values. Default value is red (1, 0, 0).
|
||||
color_negative_block : tuple of 3 floats
|
||||
Floats specifying the color for the negative block Corresponding values
|
||||
define (R, G, B) values. Default value is blue (0, 1, 0).
|
||||
alpha : float
|
||||
Value in the range [0, 1] that specifies opacity of visualization. 1 -
|
||||
fully transparent, 0 - opaque.
|
||||
max_n_features : int, default=None
|
||||
The maximum number of features to be returned.
|
||||
By default, all features are returned.
|
||||
rng : {`numpy.random.Generator`, int}, optional
|
||||
Pseudo-random number generator.
|
||||
By default, a PCG64 generator is used (see :func:`numpy.random.default_rng`).
|
||||
If `rng` is an int, it is used to seed the generator.
|
||||
|
||||
The rng is used when generating a set of features smaller than
|
||||
the total number of available features.
|
||||
|
||||
Returns
|
||||
-------
|
||||
features : (M, N), ndarray
|
||||
An image in which the different features will be added.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from skimage.feature import haar_like_feature_coord
|
||||
>>> from skimage.feature import draw_haar_like_feature
|
||||
>>> feature_coord, _ = haar_like_feature_coord(2, 2, 'type-4')
|
||||
>>> image = draw_haar_like_feature(np.zeros((2, 2)),
|
||||
... 0, 0, 2, 2,
|
||||
... feature_coord,
|
||||
... max_n_features=1)
|
||||
>>> image
|
||||
array([[[0. , 0.5, 0. ],
|
||||
[0.5, 0. , 0. ]],
|
||||
<BLANKLINE>
|
||||
[[0.5, 0. , 0. ],
|
||||
[0. , 0.5, 0. ]]])
|
||||
|
||||
"""
|
||||
rng = np.random.default_rng(rng)
|
||||
color_positive_block = np.asarray(color_positive_block, dtype=np.float64)
|
||||
color_negative_block = np.asarray(color_negative_block, dtype=np.float64)
|
||||
|
||||
if max_n_features is None:
|
||||
feature_coord_ = feature_coord
|
||||
else:
|
||||
feature_coord_ = rng.choice(feature_coord, size=max_n_features, replace=False)
|
||||
|
||||
output = np.copy(image)
|
||||
if len(image.shape) < 3:
|
||||
output = gray2rgb(image)
|
||||
output = img_as_float(output)
|
||||
|
||||
for coord in feature_coord_:
|
||||
for idx_rect, rect in enumerate(coord):
|
||||
coord_start, coord_end = rect
|
||||
coord_start = tuple(map(add, coord_start, [r, c]))
|
||||
coord_end = tuple(map(add, coord_end, [r, c]))
|
||||
rr, cc = rectangle(coord_start, coord_end)
|
||||
|
||||
if ((idx_rect + 1) % 2) == 0:
|
||||
new_value = (1 - alpha) * output[rr, cc] + alpha * color_positive_block
|
||||
else:
|
||||
new_value = (1 - alpha) * output[rr, cc] + alpha * color_negative_block
|
||||
output[rr, cc] = new_value
|
||||
|
||||
return output
|
||||
103
.CondaPkg/env/Lib/site-packages/skimage/feature/match.py
vendored
Normal file
103
.CondaPkg/env/Lib/site-packages/skimage/feature/match.py
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
import numpy as np
|
||||
from scipy.spatial.distance import cdist
|
||||
|
||||
|
||||
def match_descriptors(
|
||||
descriptors1,
|
||||
descriptors2,
|
||||
metric=None,
|
||||
p=2,
|
||||
max_distance=np.inf,
|
||||
cross_check=True,
|
||||
max_ratio=1.0,
|
||||
):
|
||||
"""Brute-force matching of descriptors.
|
||||
|
||||
For each descriptor in the first set this matcher finds the closest
|
||||
descriptor in the second set (and vice-versa in the case of enabled
|
||||
cross-checking).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
descriptors1 : (M, P) array
|
||||
Descriptors of size P about M keypoints in the first image.
|
||||
descriptors2 : (N, P) array
|
||||
Descriptors of size P about N keypoints in the second image.
|
||||
metric : {'euclidean', 'cityblock', 'minkowski', 'hamming', ...} , optional
|
||||
The metric to compute the distance between two descriptors. See
|
||||
`scipy.spatial.distance.cdist` for all possible types. The hamming
|
||||
distance should be used for binary descriptors. By default the L2-norm
|
||||
is used for all descriptors of dtype float or double and the Hamming
|
||||
distance is used for binary descriptors automatically.
|
||||
p : int, optional
|
||||
The p-norm to apply for ``metric='minkowski'``.
|
||||
max_distance : float, optional
|
||||
Maximum allowed distance between descriptors of two keypoints
|
||||
in separate images to be regarded as a match.
|
||||
cross_check : bool, optional
|
||||
If True, the matched keypoints are returned after cross checking i.e. a
|
||||
matched pair (keypoint1, keypoint2) is returned if keypoint2 is the
|
||||
best match for keypoint1 in second image and keypoint1 is the best
|
||||
match for keypoint2 in first image.
|
||||
max_ratio : float, optional
|
||||
Maximum ratio of distances between first and second closest descriptor
|
||||
in the second set of descriptors. This threshold is useful to filter
|
||||
ambiguous matches between the two descriptor sets. The choice of this
|
||||
value depends on the statistics of the chosen descriptor, e.g.,
|
||||
for SIFT descriptors a value of 0.8 is usually chosen, see
|
||||
D.G. Lowe, "Distinctive Image Features from Scale-Invariant Keypoints",
|
||||
International Journal of Computer Vision, 2004.
|
||||
|
||||
Returns
|
||||
-------
|
||||
matches : (Q, 2) array
|
||||
Indices of corresponding matches in first and second set of
|
||||
descriptors, where ``matches[:, 0]`` denote the indices in the first
|
||||
and ``matches[:, 1]`` the indices in the second set of descriptors.
|
||||
|
||||
"""
|
||||
|
||||
if descriptors1.shape[1] != descriptors2.shape[1]:
|
||||
raise ValueError("Descriptor length must equal.")
|
||||
|
||||
if metric is None:
|
||||
if np.issubdtype(descriptors1.dtype, bool):
|
||||
metric = 'hamming'
|
||||
else:
|
||||
metric = 'euclidean'
|
||||
|
||||
kwargs = {}
|
||||
# Scipy raises an error if p is passed as an extra argument when it isn't
|
||||
# necessary for the chosen metric.
|
||||
if metric == 'minkowski':
|
||||
kwargs['p'] = p
|
||||
distances = cdist(descriptors1, descriptors2, metric=metric, **kwargs)
|
||||
|
||||
indices1 = np.arange(descriptors1.shape[0])
|
||||
indices2 = np.argmin(distances, axis=1)
|
||||
|
||||
if cross_check:
|
||||
matches1 = np.argmin(distances, axis=0)
|
||||
mask = indices1 == matches1[indices2]
|
||||
indices1 = indices1[mask]
|
||||
indices2 = indices2[mask]
|
||||
|
||||
if max_distance < np.inf:
|
||||
mask = distances[indices1, indices2] < max_distance
|
||||
indices1 = indices1[mask]
|
||||
indices2 = indices2[mask]
|
||||
|
||||
if max_ratio < 1.0:
|
||||
best_distances = distances[indices1, indices2]
|
||||
distances[indices1, indices2] = np.inf
|
||||
second_best_indices2 = np.argmin(distances[indices1], axis=1)
|
||||
second_best_distances = distances[indices1, second_best_indices2]
|
||||
second_best_distances[second_best_distances == 0] = np.finfo(np.float64).eps
|
||||
ratio = best_distances / second_best_distances
|
||||
mask = ratio < max_ratio
|
||||
indices1 = indices1[mask]
|
||||
indices2 = indices2[mask]
|
||||
|
||||
matches = np.column_stack((indices1, indices2))
|
||||
|
||||
return matches
|
||||
366
.CondaPkg/env/Lib/site-packages/skimage/feature/orb.py
vendored
Normal file
366
.CondaPkg/env/Lib/site-packages/skimage/feature/orb.py
vendored
Normal file
@@ -0,0 +1,366 @@
|
||||
import numpy as np
|
||||
|
||||
from ..feature.util import (
|
||||
FeatureDetector,
|
||||
DescriptorExtractor,
|
||||
_mask_border_keypoints,
|
||||
_prepare_grayscale_input_2D,
|
||||
)
|
||||
|
||||
from .corner import corner_fast, corner_orientations, corner_peaks, corner_harris
|
||||
from ..transform import pyramid_gaussian
|
||||
from .._shared.utils import check_nD
|
||||
from .._shared.compat import NP_COPY_IF_NEEDED
|
||||
|
||||
from .orb_cy import _orb_loop
|
||||
|
||||
|
||||
OFAST_MASK = np.zeros((31, 31))
|
||||
OFAST_UMAX = [15, 15, 15, 15, 14, 14, 14, 13, 13, 12, 11, 10, 9, 8, 6, 3]
|
||||
for i in range(-15, 16):
|
||||
for j in range(-OFAST_UMAX[abs(i)], OFAST_UMAX[abs(i)] + 1):
|
||||
OFAST_MASK[15 + j, 15 + i] = 1
|
||||
|
||||
|
||||
class ORB(FeatureDetector, DescriptorExtractor):
|
||||
"""Oriented FAST and rotated BRIEF feature detector and binary descriptor
|
||||
extractor.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
n_keypoints : int, optional
|
||||
Number of keypoints to be returned. The function will return the best
|
||||
`n_keypoints` according to the Harris corner response if more than
|
||||
`n_keypoints` are detected. If not, then all the detected keypoints
|
||||
are returned.
|
||||
fast_n : int, optional
|
||||
The `n` parameter in `skimage.feature.corner_fast`. Minimum number of
|
||||
consecutive pixels out of 16 pixels on the circle that should all be
|
||||
either brighter or darker w.r.t test-pixel. 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.
|
||||
fast_threshold : float, optional
|
||||
The ``threshold`` parameter in ``feature.corner_fast``. Threshold used
|
||||
to decide 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.
|
||||
harris_k : float, optional
|
||||
The `k` parameter in `skimage.feature.corner_harris`. Sensitivity
|
||||
factor to separate corners from edges, typically in range ``[0, 0.2]``.
|
||||
Small values of `k` result in detection of sharp corners.
|
||||
downscale : float, optional
|
||||
Downscale factor for the image pyramid. Default value 1.2 is chosen so
|
||||
that there are more dense scales which enable robust scale invariance
|
||||
for a subsequent feature description.
|
||||
n_scales : int, optional
|
||||
Maximum number of scales from the bottom of the image pyramid to
|
||||
extract the features from.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
keypoints : (N, 2) array
|
||||
Keypoint coordinates as ``(row, col)``.
|
||||
scales : (N,) array
|
||||
Corresponding scales.
|
||||
orientations : (N,) array
|
||||
Corresponding orientations in radians.
|
||||
responses : (N,) array
|
||||
Corresponding Harris corner responses.
|
||||
descriptors : (Q, `descriptor_size`) array of dtype bool
|
||||
2D array of binary descriptors of size `descriptor_size` for Q
|
||||
keypoints after filtering out border keypoints with value at an
|
||||
index ``(i, j)`` either being ``True`` or ``False`` representing
|
||||
the outcome of the intensity comparison for i-th keypoint on j-th
|
||||
decision pixel-pair. It is ``Q == np.sum(mask)``.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Ethan Rublee, Vincent Rabaud, Kurt Konolige and Gary Bradski
|
||||
"ORB: An efficient alternative to SIFT and SURF"
|
||||
http://www.vision.cs.chubu.ac.jp/CV-R/pdf/Rublee_iccv2011.pdf
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.feature import ORB, match_descriptors
|
||||
>>> img1 = np.zeros((100, 100))
|
||||
>>> img2 = np.zeros_like(img1)
|
||||
>>> rng = np.random.default_rng(19481137) # do not copy this value
|
||||
>>> square = rng.random((20, 20))
|
||||
>>> img1[40:60, 40:60] = square
|
||||
>>> img2[53:73, 53:73] = square
|
||||
>>> detector_extractor1 = ORB(n_keypoints=5)
|
||||
>>> detector_extractor2 = ORB(n_keypoints=5)
|
||||
>>> detector_extractor1.detect_and_extract(img1)
|
||||
>>> detector_extractor2.detect_and_extract(img2)
|
||||
>>> matches = match_descriptors(detector_extractor1.descriptors,
|
||||
... detector_extractor2.descriptors)
|
||||
>>> matches
|
||||
array([[0, 0],
|
||||
[1, 1],
|
||||
[2, 2],
|
||||
[3, 4],
|
||||
[4, 3]])
|
||||
>>> detector_extractor1.keypoints[matches[:, 0]]
|
||||
array([[59. , 59. ],
|
||||
[40. , 40. ],
|
||||
[57. , 40. ],
|
||||
[46. , 58. ],
|
||||
[58.8, 58.8]])
|
||||
>>> detector_extractor2.keypoints[matches[:, 1]]
|
||||
array([[72., 72.],
|
||||
[53., 53.],
|
||||
[70., 53.],
|
||||
[59., 71.],
|
||||
[72., 72.]])
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
downscale=1.2,
|
||||
n_scales=8,
|
||||
n_keypoints=500,
|
||||
fast_n=9,
|
||||
fast_threshold=0.08,
|
||||
harris_k=0.04,
|
||||
):
|
||||
self.downscale = downscale
|
||||
self.n_scales = n_scales
|
||||
self.n_keypoints = n_keypoints
|
||||
self.fast_n = fast_n
|
||||
self.fast_threshold = fast_threshold
|
||||
self.harris_k = harris_k
|
||||
|
||||
self.keypoints = None
|
||||
self.scales = None
|
||||
self.responses = None
|
||||
self.orientations = None
|
||||
self.descriptors = None
|
||||
|
||||
def _build_pyramid(self, image):
|
||||
image = _prepare_grayscale_input_2D(image)
|
||||
return list(
|
||||
pyramid_gaussian(
|
||||
image, self.n_scales - 1, self.downscale, channel_axis=None
|
||||
)
|
||||
)
|
||||
|
||||
def _detect_octave(self, octave_image):
|
||||
dtype = octave_image.dtype
|
||||
# Extract keypoints for current octave
|
||||
fast_response = corner_fast(octave_image, self.fast_n, self.fast_threshold)
|
||||
keypoints = corner_peaks(fast_response, min_distance=1)
|
||||
|
||||
if len(keypoints) == 0:
|
||||
return (
|
||||
np.zeros((0, 2), dtype=dtype),
|
||||
np.zeros((0,), dtype=dtype),
|
||||
np.zeros((0,), dtype=dtype),
|
||||
)
|
||||
|
||||
mask = _mask_border_keypoints(octave_image.shape, keypoints, distance=16)
|
||||
keypoints = keypoints[mask]
|
||||
|
||||
orientations = corner_orientations(octave_image, keypoints, OFAST_MASK)
|
||||
|
||||
harris_response = corner_harris(octave_image, method='k', k=self.harris_k)
|
||||
responses = harris_response[keypoints[:, 0], keypoints[:, 1]]
|
||||
|
||||
return keypoints, orientations, responses
|
||||
|
||||
def detect(self, image):
|
||||
"""Detect oriented FAST keypoints along with the corresponding scale.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : 2D array
|
||||
Input image.
|
||||
|
||||
"""
|
||||
check_nD(image, 2)
|
||||
|
||||
pyramid = self._build_pyramid(image)
|
||||
|
||||
keypoints_list = []
|
||||
orientations_list = []
|
||||
scales_list = []
|
||||
responses_list = []
|
||||
|
||||
for octave in range(len(pyramid)):
|
||||
octave_image = np.ascontiguousarray(pyramid[octave])
|
||||
|
||||
if np.squeeze(octave_image).ndim < 2:
|
||||
# No further keypoints can be detected if the image is not really 2d
|
||||
break
|
||||
|
||||
keypoints, orientations, responses = self._detect_octave(octave_image)
|
||||
|
||||
keypoints_list.append(keypoints * self.downscale**octave)
|
||||
orientations_list.append(orientations)
|
||||
scales_list.append(
|
||||
np.full(
|
||||
keypoints.shape[0],
|
||||
self.downscale**octave,
|
||||
dtype=octave_image.dtype,
|
||||
)
|
||||
)
|
||||
responses_list.append(responses)
|
||||
|
||||
keypoints = np.vstack(keypoints_list)
|
||||
orientations = np.hstack(orientations_list)
|
||||
scales = np.hstack(scales_list)
|
||||
responses = np.hstack(responses_list)
|
||||
|
||||
if keypoints.shape[0] < self.n_keypoints:
|
||||
self.keypoints = keypoints
|
||||
self.scales = scales
|
||||
self.orientations = orientations
|
||||
self.responses = responses
|
||||
else:
|
||||
# Choose best n_keypoints according to Harris corner response
|
||||
best_indices = responses.argsort()[::-1][: self.n_keypoints]
|
||||
self.keypoints = keypoints[best_indices]
|
||||
self.scales = scales[best_indices]
|
||||
self.orientations = orientations[best_indices]
|
||||
self.responses = responses[best_indices]
|
||||
|
||||
def _extract_octave(self, octave_image, keypoints, orientations):
|
||||
mask = _mask_border_keypoints(octave_image.shape, keypoints, distance=20)
|
||||
keypoints = np.array(
|
||||
keypoints[mask], dtype=np.intp, order='C', copy=NP_COPY_IF_NEEDED
|
||||
)
|
||||
orientations = np.array(orientations[mask], order='C', copy=False)
|
||||
|
||||
descriptors = _orb_loop(octave_image, keypoints, orientations)
|
||||
|
||||
return descriptors, mask
|
||||
|
||||
def extract(self, image, keypoints, scales, orientations):
|
||||
"""Extract rBRIEF binary descriptors for given keypoints in image.
|
||||
|
||||
Note that the keypoints must be extracted using the same `downscale`
|
||||
and `n_scales` parameters. Additionally, if you want to extract both
|
||||
keypoints and descriptors you should use the faster
|
||||
`detect_and_extract`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : 2D array
|
||||
Input image.
|
||||
keypoints : (N, 2) array
|
||||
Keypoint coordinates as ``(row, col)``.
|
||||
scales : (N,) array
|
||||
Corresponding scales.
|
||||
orientations : (N,) array
|
||||
Corresponding orientations in radians.
|
||||
|
||||
"""
|
||||
check_nD(image, 2)
|
||||
|
||||
pyramid = self._build_pyramid(image)
|
||||
|
||||
descriptors_list = []
|
||||
mask_list = []
|
||||
|
||||
# Determine octaves from scales
|
||||
octaves = (np.log(scales) / np.log(self.downscale)).astype(np.intp)
|
||||
|
||||
for octave in range(len(pyramid)):
|
||||
# Mask for all keypoints in current octave
|
||||
octave_mask = octaves == octave
|
||||
|
||||
if np.sum(octave_mask) > 0:
|
||||
octave_image = np.ascontiguousarray(pyramid[octave])
|
||||
|
||||
octave_keypoints = keypoints[octave_mask]
|
||||
octave_keypoints /= self.downscale**octave
|
||||
octave_orientations = orientations[octave_mask]
|
||||
|
||||
descriptors, mask = self._extract_octave(
|
||||
octave_image, octave_keypoints, octave_orientations
|
||||
)
|
||||
|
||||
descriptors_list.append(descriptors)
|
||||
mask_list.append(mask)
|
||||
|
||||
self.descriptors = np.vstack(descriptors_list).view(bool)
|
||||
self.mask_ = np.hstack(mask_list)
|
||||
|
||||
def detect_and_extract(self, image):
|
||||
"""Detect oriented FAST keypoints and extract rBRIEF descriptors.
|
||||
|
||||
Note that this is faster than first calling `detect` and then
|
||||
`extract`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : 2D array
|
||||
Input image.
|
||||
|
||||
"""
|
||||
check_nD(image, 2)
|
||||
|
||||
pyramid = self._build_pyramid(image)
|
||||
|
||||
keypoints_list = []
|
||||
responses_list = []
|
||||
scales_list = []
|
||||
orientations_list = []
|
||||
descriptors_list = []
|
||||
|
||||
for octave in range(len(pyramid)):
|
||||
octave_image = np.ascontiguousarray(pyramid[octave])
|
||||
|
||||
if np.squeeze(octave_image).ndim < 2:
|
||||
# No further keypoints can be detected if the image is not really 2d
|
||||
break
|
||||
|
||||
keypoints, orientations, responses = self._detect_octave(octave_image)
|
||||
|
||||
if len(keypoints) == 0:
|
||||
keypoints_list.append(keypoints)
|
||||
responses_list.append(responses)
|
||||
descriptors_list.append(np.zeros((0, 256), dtype=bool))
|
||||
continue
|
||||
|
||||
descriptors, mask = self._extract_octave(
|
||||
octave_image, keypoints, orientations
|
||||
)
|
||||
|
||||
scaled_keypoints = keypoints[mask] * self.downscale**octave
|
||||
keypoints_list.append(scaled_keypoints)
|
||||
responses_list.append(responses[mask])
|
||||
orientations_list.append(orientations[mask])
|
||||
scales_list.append(
|
||||
self.downscale**octave
|
||||
* np.ones(scaled_keypoints.shape[0], dtype=np.intp)
|
||||
)
|
||||
descriptors_list.append(descriptors)
|
||||
|
||||
if len(scales_list) == 0:
|
||||
raise RuntimeError(
|
||||
"ORB found no features. Try passing in an image containing "
|
||||
"greater intensity contrasts between adjacent pixels."
|
||||
)
|
||||
|
||||
keypoints = np.vstack(keypoints_list)
|
||||
responses = np.hstack(responses_list)
|
||||
scales = np.hstack(scales_list)
|
||||
orientations = np.hstack(orientations_list)
|
||||
descriptors = np.vstack(descriptors_list).view(bool)
|
||||
|
||||
if keypoints.shape[0] < self.n_keypoints:
|
||||
self.keypoints = keypoints
|
||||
self.scales = scales
|
||||
self.orientations = orientations
|
||||
self.responses = responses
|
||||
self.descriptors = descriptors
|
||||
else:
|
||||
# Choose best n_keypoints according to Harris corner response
|
||||
best_indices = responses.argsort()[::-1][: self.n_keypoints]
|
||||
self.keypoints = keypoints[best_indices]
|
||||
self.scales = scales[best_indices]
|
||||
self.orientations = orientations[best_indices]
|
||||
self.responses = responses[best_indices]
|
||||
self.descriptors = descriptors[best_indices]
|
||||
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/orb_cy.cp312-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/orb_cy.cp312-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/orb_cy.cp312-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/orb_cy.cp312-win_amd64.pyd
vendored
Normal file
Binary file not shown.
256
.CondaPkg/env/Lib/site-packages/skimage/feature/orb_descriptor_positions.txt
vendored
Normal file
256
.CondaPkg/env/Lib/site-packages/skimage/feature/orb_descriptor_positions.txt
vendored
Normal file
@@ -0,0 +1,256 @@
|
||||
8 -3 9 5
|
||||
4 2 7 -12
|
||||
-11 9 -8 2
|
||||
7 -12 12 -13
|
||||
2 -13 2 12
|
||||
1 -7 1 6
|
||||
-2 -10 -2 -4
|
||||
-13 -13 -11 -8
|
||||
-13 -3 -12 -9
|
||||
10 4 11 9
|
||||
-13 -8 -8 -9
|
||||
-11 7 -9 12
|
||||
7 7 12 6
|
||||
-4 -5 -3 0
|
||||
-13 2 -12 -3
|
||||
-9 0 -7 5
|
||||
12 -6 12 -1
|
||||
-3 6 -2 12
|
||||
-6 -13 -4 -8
|
||||
11 -13 12 -8
|
||||
4 7 5 1
|
||||
5 -3 10 -3
|
||||
3 -7 6 12
|
||||
-8 -7 -6 -2
|
||||
-2 11 -1 -10
|
||||
-13 12 -8 10
|
||||
-7 3 -5 -3
|
||||
-4 2 -3 7
|
||||
-10 -12 -6 11
|
||||
5 -12 6 -7
|
||||
5 -6 7 -1
|
||||
1 0 4 -5
|
||||
9 11 11 -13
|
||||
4 7 4 12
|
||||
2 -1 4 4
|
||||
-4 -12 -2 7
|
||||
-8 -5 -7 -10
|
||||
4 11 9 12
|
||||
0 -8 1 -13
|
||||
-13 -2 -8 2
|
||||
-3 -2 -2 3
|
||||
-6 9 -4 -9
|
||||
8 12 10 7
|
||||
0 9 1 3
|
||||
7 -5 11 -10
|
||||
-13 -6 -11 0
|
||||
10 7 12 1
|
||||
-6 -3 -6 12
|
||||
10 -9 12 -4
|
||||
-13 8 -8 -12
|
||||
-13 0 -8 -4
|
||||
3 3 7 8
|
||||
5 7 10 -7
|
||||
-1 7 1 -12
|
||||
3 -10 5 6
|
||||
2 -4 3 -10
|
||||
-13 0 -13 5
|
||||
-13 -7 -12 12
|
||||
-13 3 -11 8
|
||||
-7 12 -4 7
|
||||
6 -10 12 8
|
||||
-9 -1 -7 -6
|
||||
-2 -5 0 12
|
||||
-12 5 -7 5
|
||||
3 -10 8 -13
|
||||
-7 -7 -4 5
|
||||
-3 -2 -1 -7
|
||||
2 9 5 -11
|
||||
-11 -13 -5 -13
|
||||
-1 6 0 -1
|
||||
5 -3 5 2
|
||||
-4 -13 -4 12
|
||||
-9 -6 -9 6
|
||||
-12 -10 -8 -4
|
||||
10 2 12 -3
|
||||
7 12 12 12
|
||||
-7 -13 -6 5
|
||||
-4 9 -3 4
|
||||
7 -1 12 2
|
||||
-7 6 -5 1
|
||||
-13 11 -12 5
|
||||
-3 7 -2 -6
|
||||
7 -8 12 -7
|
||||
-13 -7 -11 -12
|
||||
1 -3 12 12
|
||||
2 -6 3 0
|
||||
-4 3 -2 -13
|
||||
-1 -13 1 9
|
||||
7 1 8 -6
|
||||
1 -1 3 12
|
||||
9 1 12 6
|
||||
-1 -9 -1 3
|
||||
-13 -13 -10 5
|
||||
7 7 10 12
|
||||
12 -5 12 9
|
||||
6 3 7 11
|
||||
5 -13 6 10
|
||||
2 -12 2 3
|
||||
3 8 4 -6
|
||||
2 6 12 -13
|
||||
9 -12 10 3
|
||||
-8 4 -7 9
|
||||
-11 12 -4 -6
|
||||
1 12 2 -8
|
||||
6 -9 7 -4
|
||||
2 3 3 -2
|
||||
6 3 11 0
|
||||
3 -3 8 -8
|
||||
7 8 9 3
|
||||
-11 -5 -6 -4
|
||||
-10 11 -5 10
|
||||
-5 -8 -3 12
|
||||
-10 5 -9 0
|
||||
8 -1 12 -6
|
||||
4 -6 6 -11
|
||||
-10 12 -8 7
|
||||
4 -2 6 7
|
||||
-2 0 -2 12
|
||||
-5 -8 -5 2
|
||||
7 -6 10 12
|
||||
-9 -13 -8 -8
|
||||
-5 -13 -5 -2
|
||||
8 -8 9 -13
|
||||
-9 -11 -9 0
|
||||
1 -8 1 -2
|
||||
7 -4 9 1
|
||||
-2 1 -1 -4
|
||||
11 -6 12 -11
|
||||
-12 -9 -6 4
|
||||
3 7 7 12
|
||||
5 5 10 8
|
||||
0 -4 2 8
|
||||
-9 12 -5 -13
|
||||
0 7 2 12
|
||||
-1 2 1 7
|
||||
5 11 7 -9
|
||||
3 5 6 -8
|
||||
-13 -4 -8 9
|
||||
-5 9 -3 -3
|
||||
-4 -7 -3 -12
|
||||
6 5 8 0
|
||||
-7 6 -6 12
|
||||
-13 6 -5 -2
|
||||
1 -10 3 10
|
||||
4 1 8 -4
|
||||
-2 -2 2 -13
|
||||
2 -12 12 12
|
||||
-2 -13 0 -6
|
||||
4 1 9 3
|
||||
-6 -10 -3 -5
|
||||
-3 -13 -1 1
|
||||
7 5 12 -11
|
||||
4 -2 5 -7
|
||||
-13 9 -9 -5
|
||||
7 1 8 6
|
||||
7 -8 7 6
|
||||
-7 -4 -7 1
|
||||
-8 11 -7 -8
|
||||
-13 6 -12 -8
|
||||
2 4 3 9
|
||||
10 -5 12 3
|
||||
-6 -5 -6 7
|
||||
8 -3 9 -8
|
||||
2 -12 2 8
|
||||
-11 -2 -10 3
|
||||
-12 -13 -7 -9
|
||||
-11 0 -10 -5
|
||||
5 -3 11 8
|
||||
-2 -13 -1 12
|
||||
-1 -8 0 9
|
||||
-13 -11 -12 -5
|
||||
-10 -2 -10 11
|
||||
-3 9 -2 -13
|
||||
2 -3 3 2
|
||||
-9 -13 -4 0
|
||||
-4 6 -3 -10
|
||||
-4 12 -2 -7
|
||||
-6 -11 -4 9
|
||||
6 -3 6 11
|
||||
-13 11 -5 5
|
||||
11 11 12 6
|
||||
7 -5 12 -2
|
||||
-1 12 0 7
|
||||
-4 -8 -3 -2
|
||||
-7 1 -6 7
|
||||
-13 -12 -8 -13
|
||||
-7 -2 -6 -8
|
||||
-8 5 -6 -9
|
||||
-5 -1 -4 5
|
||||
-13 7 -8 10
|
||||
1 5 5 -13
|
||||
1 0 10 -13
|
||||
9 12 10 -1
|
||||
5 -8 10 -9
|
||||
-1 11 1 -13
|
||||
-9 -3 -6 2
|
||||
-1 -10 1 12
|
||||
-13 1 -8 -10
|
||||
8 -11 10 -6
|
||||
2 -13 3 -6
|
||||
7 -13 12 -9
|
||||
-10 -10 -5 -7
|
||||
-10 -8 -8 -13
|
||||
4 -6 8 5
|
||||
3 12 8 -13
|
||||
-4 2 -3 -3
|
||||
5 -13 10 -12
|
||||
4 -13 5 -1
|
||||
-9 9 -4 3
|
||||
0 3 3 -9
|
||||
-12 1 -6 1
|
||||
3 2 4 -8
|
||||
-10 -10 -10 9
|
||||
8 -13 12 12
|
||||
-8 -12 -6 -5
|
||||
2 2 3 7
|
||||
10 6 11 -8
|
||||
6 8 8 -12
|
||||
-7 10 -6 5
|
||||
-3 -9 -3 9
|
||||
-1 -13 -1 5
|
||||
-3 -7 -3 4
|
||||
-8 -2 -8 3
|
||||
4 2 12 12
|
||||
2 -5 3 11
|
||||
6 -9 11 -13
|
||||
3 -1 7 12
|
||||
11 -1 12 4
|
||||
-3 0 -3 6
|
||||
4 -11 4 12
|
||||
2 -4 2 1
|
||||
-10 -6 -8 1
|
||||
-13 7 -11 1
|
||||
-13 12 -11 -13
|
||||
6 0 11 -13
|
||||
0 -1 1 4
|
||||
-13 3 -9 -2
|
||||
-9 8 -6 -3
|
||||
-13 -6 -8 -2
|
||||
5 -9 8 10
|
||||
2 7 3 -9
|
||||
-1 -6 -1 -1
|
||||
9 5 11 -2
|
||||
11 -3 12 -8
|
||||
3 0 3 5
|
||||
-1 4 0 10
|
||||
3 -6 4 5
|
||||
-13 0 -10 5
|
||||
5 8 12 11
|
||||
8 9 9 -6
|
||||
7 -4 8 -12
|
||||
-10 4 -10 9
|
||||
7 3 12 4
|
||||
9 -7 10 -2
|
||||
7 0 12 -2
|
||||
-1 -6 0 -11
|
||||
417
.CondaPkg/env/Lib/site-packages/skimage/feature/peak.py
vendored
Normal file
417
.CondaPkg/env/Lib/site-packages/skimage/feature/peak.py
vendored
Normal file
@@ -0,0 +1,417 @@
|
||||
from warnings import warn
|
||||
|
||||
import numpy as np
|
||||
import scipy.ndimage as ndi
|
||||
|
||||
from .. import measure
|
||||
from .._shared.coord import ensure_spacing
|
||||
|
||||
|
||||
def _get_high_intensity_peaks(image, mask, num_peaks, min_distance, p_norm):
|
||||
"""
|
||||
Return the highest intensity peak coordinates.
|
||||
"""
|
||||
# get coordinates of peaks
|
||||
coord = np.nonzero(mask)
|
||||
intensities = image[coord]
|
||||
# Highest peak first
|
||||
idx_maxsort = np.argsort(-intensities, kind="stable")
|
||||
coord = np.transpose(coord)[idx_maxsort]
|
||||
|
||||
if np.isfinite(num_peaks):
|
||||
max_out = int(num_peaks)
|
||||
else:
|
||||
max_out = None
|
||||
|
||||
coord = ensure_spacing(coord, spacing=min_distance, p_norm=p_norm, max_out=max_out)
|
||||
|
||||
if len(coord) > num_peaks:
|
||||
coord = coord[:num_peaks]
|
||||
|
||||
return coord
|
||||
|
||||
|
||||
def _get_peak_mask(image, footprint, threshold, mask=None):
|
||||
"""
|
||||
Return the mask containing all peak candidates above thresholds.
|
||||
"""
|
||||
if footprint.size == 1 or image.size == 1:
|
||||
return image > threshold
|
||||
|
||||
image_max = ndi.maximum_filter(image, footprint=footprint, mode='nearest')
|
||||
|
||||
out = image == image_max
|
||||
|
||||
# no peak for a trivial image
|
||||
image_is_trivial = np.all(out) if mask is None else np.all(out[mask])
|
||||
if image_is_trivial:
|
||||
out[:] = False
|
||||
if mask is not None:
|
||||
# isolated pixels in masked area are returned as peaks
|
||||
isolated_px = np.logical_xor(mask, ndi.binary_opening(mask))
|
||||
out[isolated_px] = True
|
||||
|
||||
out &= image > threshold
|
||||
return out
|
||||
|
||||
|
||||
def _exclude_border(label, border_width):
|
||||
"""Set label border values to 0."""
|
||||
# zero out label borders
|
||||
for i, width in enumerate(border_width):
|
||||
if width == 0:
|
||||
continue
|
||||
label[(slice(None),) * i + (slice(None, width),)] = 0
|
||||
label[(slice(None),) * i + (slice(-width, None),)] = 0
|
||||
return label
|
||||
|
||||
|
||||
def _get_threshold(image, threshold_abs, threshold_rel):
|
||||
"""Return the threshold value according to an absolute and a relative
|
||||
value.
|
||||
|
||||
"""
|
||||
threshold = threshold_abs if threshold_abs is not None else image.min()
|
||||
|
||||
if threshold_rel is not None:
|
||||
threshold = max(threshold, threshold_rel * image.max())
|
||||
|
||||
return threshold
|
||||
|
||||
|
||||
def _get_excluded_border_width(image, min_distance, exclude_border):
|
||||
"""Return border_width values relative to a min_distance if requested."""
|
||||
|
||||
if isinstance(exclude_border, bool):
|
||||
border_width = (min_distance if exclude_border else 0,) * image.ndim
|
||||
elif isinstance(exclude_border, int):
|
||||
if exclude_border < 0:
|
||||
raise ValueError("`exclude_border` cannot be a negative value")
|
||||
border_width = (exclude_border,) * image.ndim
|
||||
elif isinstance(exclude_border, tuple):
|
||||
if len(exclude_border) != image.ndim:
|
||||
raise ValueError(
|
||||
"`exclude_border` should have the same length as the "
|
||||
"dimensionality of the image."
|
||||
)
|
||||
for exclude in exclude_border:
|
||||
if not isinstance(exclude, int):
|
||||
raise ValueError(
|
||||
"`exclude_border`, when expressed as a tuple, must only "
|
||||
"contain ints."
|
||||
)
|
||||
if exclude < 0:
|
||||
raise ValueError("`exclude_border` can not be a negative value")
|
||||
border_width = exclude_border
|
||||
else:
|
||||
raise TypeError(
|
||||
"`exclude_border` must be bool, int, or tuple with the same "
|
||||
"length as the dimensionality of the image."
|
||||
)
|
||||
|
||||
return border_width
|
||||
|
||||
|
||||
def peak_local_max(
|
||||
image,
|
||||
min_distance=1,
|
||||
threshold_abs=None,
|
||||
threshold_rel=None,
|
||||
exclude_border=True,
|
||||
num_peaks=np.inf,
|
||||
footprint=None,
|
||||
labels=None,
|
||||
num_peaks_per_label=np.inf,
|
||||
p_norm=np.inf,
|
||||
):
|
||||
"""Find peaks in an image as coordinate list.
|
||||
|
||||
Peaks are the local maxima in a region of `2 * min_distance + 1`
|
||||
(i.e. peaks are separated by at least `min_distance`).
|
||||
|
||||
If both `threshold_abs` and `threshold_rel` are provided, the maximum
|
||||
of the two is chosen as the minimum intensity threshold of peaks.
|
||||
|
||||
.. versionchanged:: 0.18
|
||||
Prior to version 0.18, peaks of the same height within a radius of
|
||||
`min_distance` were all returned, but this could cause unexpected
|
||||
behaviour. From 0.18 onwards, an arbitrary peak within the region is
|
||||
returned. See issue gh-2592.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Input image.
|
||||
min_distance : int, optional
|
||||
The minimal allowed distance separating peaks. To find the
|
||||
maximum number of peaks, use `min_distance=1`.
|
||||
threshold_abs : float or None, optional
|
||||
Minimum intensity of peaks. By default, the absolute threshold is
|
||||
the minimum intensity of the image.
|
||||
threshold_rel : float or None, optional
|
||||
Minimum intensity of peaks, calculated as
|
||||
``max(image) * threshold_rel``.
|
||||
exclude_border : int, tuple of ints, or bool, optional
|
||||
If positive integer, `exclude_border` excludes peaks from within
|
||||
`exclude_border`-pixels of the border of the image.
|
||||
If tuple of non-negative ints, the length of the tuple must match the
|
||||
input array's dimensionality. Each element of the tuple will exclude
|
||||
peaks from within `exclude_border`-pixels of the border of the image
|
||||
along that dimension.
|
||||
If True, takes the `min_distance` parameter as value.
|
||||
If zero or False, peaks are identified regardless of their distance
|
||||
from the border.
|
||||
num_peaks : int, optional
|
||||
Maximum number of peaks. When the number of peaks exceeds `num_peaks`,
|
||||
return `num_peaks` peaks based on highest peak intensity.
|
||||
footprint : ndarray of bools, optional
|
||||
If provided, `footprint == 1` represents the local region within which
|
||||
to search for peaks at every point in `image`.
|
||||
labels : ndarray of ints, optional
|
||||
If provided, each unique region `labels == value` represents a unique
|
||||
region to search for peaks. Zero is reserved for background.
|
||||
num_peaks_per_label : int, optional
|
||||
Maximum number of peaks for each label.
|
||||
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
|
||||
The coordinates of the peaks.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The peak local maximum function returns the coordinates of local peaks
|
||||
(maxima) in an image. Internally, a maximum filter is used for finding
|
||||
local maxima. This operation dilates the original image. After comparison
|
||||
of the dilated and original images, this function returns the coordinates
|
||||
of the peaks where the dilated image equals the original image.
|
||||
|
||||
See also
|
||||
--------
|
||||
skimage.feature.corner_peaks
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> img1 = np.zeros((7, 7))
|
||||
>>> img1[3, 4] = 1
|
||||
>>> img1[3, 2] = 1.5
|
||||
>>> img1
|
||||
array([[0. , 0. , 0. , 0. , 0. , 0. , 0. ],
|
||||
[0. , 0. , 0. , 0. , 0. , 0. , 0. ],
|
||||
[0. , 0. , 0. , 0. , 0. , 0. , 0. ],
|
||||
[0. , 0. , 1.5, 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. ]])
|
||||
|
||||
>>> peak_local_max(img1, min_distance=1)
|
||||
array([[3, 2],
|
||||
[3, 4]])
|
||||
|
||||
>>> peak_local_max(img1, min_distance=2)
|
||||
array([[3, 2]])
|
||||
|
||||
>>> img2 = np.zeros((20, 20, 20))
|
||||
>>> img2[10, 10, 10] = 1
|
||||
>>> img2[15, 15, 15] = 1
|
||||
>>> peak_idx = peak_local_max(img2, exclude_border=0)
|
||||
>>> peak_idx
|
||||
array([[10, 10, 10],
|
||||
[15, 15, 15]])
|
||||
|
||||
>>> peak_mask = np.zeros_like(img2, dtype=bool)
|
||||
>>> peak_mask[tuple(peak_idx.T)] = True
|
||||
>>> np.argwhere(peak_mask)
|
||||
array([[10, 10, 10],
|
||||
[15, 15, 15]])
|
||||
|
||||
"""
|
||||
if (footprint is None or footprint.size == 1) and min_distance < 1:
|
||||
warn(
|
||||
"When min_distance < 1, peak_local_max acts as finding "
|
||||
"image > max(threshold_abs, threshold_rel * max(image)).",
|
||||
RuntimeWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
border_width = _get_excluded_border_width(image, min_distance, exclude_border)
|
||||
|
||||
threshold = _get_threshold(image, threshold_abs, threshold_rel)
|
||||
|
||||
if footprint is None:
|
||||
size = 2 * min_distance + 1
|
||||
footprint = np.ones((size,) * image.ndim, dtype=bool)
|
||||
else:
|
||||
footprint = np.asarray(footprint)
|
||||
|
||||
if labels is None:
|
||||
# Non maximum filter
|
||||
mask = _get_peak_mask(image, footprint, threshold)
|
||||
|
||||
mask = _exclude_border(mask, border_width)
|
||||
|
||||
# Select highest intensities (num_peaks)
|
||||
coordinates = _get_high_intensity_peaks(
|
||||
image, mask, num_peaks, min_distance, p_norm
|
||||
)
|
||||
|
||||
else:
|
||||
_labels = _exclude_border(labels.astype(int, casting="safe"), border_width)
|
||||
|
||||
if np.issubdtype(image.dtype, np.floating):
|
||||
bg_val = np.finfo(image.dtype).min
|
||||
else:
|
||||
bg_val = np.iinfo(image.dtype).min
|
||||
|
||||
# For each label, extract a smaller image enclosing the object of
|
||||
# interest, identify num_peaks_per_label peaks
|
||||
labels_peak_coord = []
|
||||
|
||||
for label_idx, roi in enumerate(ndi.find_objects(_labels)):
|
||||
if roi is None:
|
||||
continue
|
||||
|
||||
# Get roi mask
|
||||
label_mask = labels[roi] == label_idx + 1
|
||||
# Extract image roi
|
||||
img_object = image[roi].copy()
|
||||
# Ensure masked values don't affect roi's local peaks
|
||||
img_object[np.logical_not(label_mask)] = bg_val
|
||||
|
||||
mask = _get_peak_mask(img_object, footprint, threshold, label_mask)
|
||||
|
||||
coordinates = _get_high_intensity_peaks(
|
||||
img_object, mask, num_peaks_per_label, min_distance, p_norm
|
||||
)
|
||||
|
||||
# transform coordinates in global image indices space
|
||||
for idx, s in enumerate(roi):
|
||||
coordinates[:, idx] += s.start
|
||||
|
||||
labels_peak_coord.append(coordinates)
|
||||
|
||||
if labels_peak_coord:
|
||||
coordinates = np.vstack(labels_peak_coord)
|
||||
else:
|
||||
coordinates = np.empty((0, 2), dtype=int)
|
||||
|
||||
if len(coordinates) > num_peaks:
|
||||
out = np.zeros_like(image, dtype=bool)
|
||||
out[tuple(coordinates.T)] = True
|
||||
coordinates = _get_high_intensity_peaks(
|
||||
image, out, num_peaks, min_distance, p_norm
|
||||
)
|
||||
|
||||
return coordinates
|
||||
|
||||
|
||||
def _prominent_peaks(
|
||||
image, min_xdistance=1, min_ydistance=1, threshold=None, num_peaks=np.inf
|
||||
):
|
||||
"""Return peaks with non-maximum suppression.
|
||||
|
||||
Identifies most prominent features separated by certain distances.
|
||||
Non-maximum suppression with different sizes is applied separately
|
||||
in the first and second dimension of the image to identify peaks.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : (M, N) ndarray
|
||||
Input image.
|
||||
min_xdistance : int
|
||||
Minimum distance separating features in the x dimension.
|
||||
min_ydistance : int
|
||||
Minimum distance separating features in the y dimension.
|
||||
threshold : float
|
||||
Minimum intensity of peaks. Default is `0.5 * max(image)`.
|
||||
num_peaks : int
|
||||
Maximum number of peaks. When the number of peaks exceeds `num_peaks`,
|
||||
return `num_peaks` coordinates based on peak intensity.
|
||||
|
||||
Returns
|
||||
-------
|
||||
intensity, xcoords, ycoords : tuple of array
|
||||
Peak intensity values, x and y indices.
|
||||
"""
|
||||
|
||||
img = image.copy()
|
||||
rows, cols = img.shape
|
||||
|
||||
if threshold is None:
|
||||
threshold = 0.5 * np.max(img)
|
||||
|
||||
ycoords_size = 2 * min_ydistance + 1
|
||||
xcoords_size = 2 * min_xdistance + 1
|
||||
img_max = ndi.maximum_filter1d(
|
||||
img, size=ycoords_size, axis=0, mode='constant', cval=0
|
||||
)
|
||||
img_max = ndi.maximum_filter1d(
|
||||
img_max, size=xcoords_size, axis=1, mode='constant', cval=0
|
||||
)
|
||||
mask = img == img_max
|
||||
img *= mask
|
||||
img_t = img > threshold
|
||||
|
||||
label_img = measure.label(img_t)
|
||||
props = measure.regionprops(label_img, img_max)
|
||||
|
||||
# Sort the list of peaks by intensity, not left-right, so larger peaks
|
||||
# in Hough space cannot be arbitrarily suppressed by smaller neighbors
|
||||
props = sorted(props, key=lambda x: x.intensity_max)[::-1]
|
||||
coords = np.array([np.round(p.centroid) for p in props], dtype=int)
|
||||
|
||||
img_peaks = []
|
||||
ycoords_peaks = []
|
||||
xcoords_peaks = []
|
||||
|
||||
# relative coordinate grid for local neighborhood suppression
|
||||
ycoords_ext, xcoords_ext = np.mgrid[
|
||||
-min_ydistance : min_ydistance + 1, -min_xdistance : min_xdistance + 1
|
||||
]
|
||||
|
||||
for ycoords_idx, xcoords_idx in coords:
|
||||
accum = img_max[ycoords_idx, xcoords_idx]
|
||||
if accum > threshold:
|
||||
# absolute coordinate grid for local neighborhood suppression
|
||||
ycoords_nh = ycoords_idx + ycoords_ext
|
||||
xcoords_nh = xcoords_idx + xcoords_ext
|
||||
|
||||
# no reflection for distance neighborhood
|
||||
ycoords_in = np.logical_and(ycoords_nh > 0, ycoords_nh < rows)
|
||||
ycoords_nh = ycoords_nh[ycoords_in]
|
||||
xcoords_nh = xcoords_nh[ycoords_in]
|
||||
|
||||
# reflect xcoords and assume xcoords are continuous,
|
||||
# e.g. for angles:
|
||||
# (..., 88, 89, -90, -89, ..., 89, -90, -89, ...)
|
||||
xcoords_low = xcoords_nh < 0
|
||||
ycoords_nh[xcoords_low] = rows - ycoords_nh[xcoords_low]
|
||||
xcoords_nh[xcoords_low] += cols
|
||||
xcoords_high = xcoords_nh >= cols
|
||||
ycoords_nh[xcoords_high] = rows - ycoords_nh[xcoords_high]
|
||||
xcoords_nh[xcoords_high] -= cols
|
||||
|
||||
# suppress neighborhood
|
||||
img_max[ycoords_nh, xcoords_nh] = 0
|
||||
|
||||
# add current feature to peaks
|
||||
img_peaks.append(accum)
|
||||
ycoords_peaks.append(ycoords_idx)
|
||||
xcoords_peaks.append(xcoords_idx)
|
||||
|
||||
img_peaks = np.array(img_peaks)
|
||||
ycoords_peaks = np.array(ycoords_peaks)
|
||||
xcoords_peaks = np.array(xcoords_peaks)
|
||||
|
||||
if num_peaks < len(img_peaks):
|
||||
idx_maxsort = np.argsort(img_peaks)[::-1][:num_peaks]
|
||||
img_peaks = img_peaks[idx_maxsort]
|
||||
ycoords_peaks = ycoords_peaks[idx_maxsort]
|
||||
xcoords_peaks = xcoords_peaks[idx_maxsort]
|
||||
|
||||
return img_peaks, xcoords_peaks, ycoords_peaks
|
||||
771
.CondaPkg/env/Lib/site-packages/skimage/feature/sift.py
vendored
Normal file
771
.CondaPkg/env/Lib/site-packages/skimage/feature/sift.py
vendored
Normal file
@@ -0,0 +1,771 @@
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
import scipy.ndimage as ndi
|
||||
|
||||
from .._shared.utils import check_nD, _supported_float_type
|
||||
from ..feature.util import DescriptorExtractor, FeatureDetector
|
||||
from .._shared.filters import gaussian
|
||||
from ..transform import rescale
|
||||
from ..util import img_as_float
|
||||
from ._sift import _local_max, _ori_distances, _update_histogram
|
||||
|
||||
|
||||
def _edgeness(hxx, hyy, hxy):
|
||||
"""Compute edgeness (eq. 18 of Otero et. al. IPOL paper)"""
|
||||
trace = hxx + hyy
|
||||
determinant = hxx * hyy - hxy * hxy
|
||||
return (trace * trace) / determinant
|
||||
|
||||
|
||||
def _sparse_gradient(vol, positions):
|
||||
"""Gradient of a 3D volume at the provided `positions`.
|
||||
|
||||
For SIFT we only need the gradient at specific positions and do not need
|
||||
the gradient at the edge positions, so can just use this simple
|
||||
implementation instead of numpy.gradient.
|
||||
"""
|
||||
p0 = positions[..., 0]
|
||||
p1 = positions[..., 1]
|
||||
p2 = positions[..., 2]
|
||||
g0 = vol[p0 + 1, p1, p2] - vol[p0 - 1, p1, p2]
|
||||
g0 *= 0.5
|
||||
g1 = vol[p0, p1 + 1, p2] - vol[p0, p1 - 1, p2]
|
||||
g1 *= 0.5
|
||||
g2 = vol[p0, p1, p2 + 1] - vol[p0, p1, p2 - 1]
|
||||
g2 *= 0.5
|
||||
return g0, g1, g2
|
||||
|
||||
|
||||
def _hessian(d, positions):
|
||||
"""Compute the non-redundant 3D Hessian terms at the requested positions.
|
||||
|
||||
Source: "Anatomy of the SIFT Method" p.380 (13)
|
||||
"""
|
||||
p0 = positions[..., 0]
|
||||
p1 = positions[..., 1]
|
||||
p2 = positions[..., 2]
|
||||
two_d0 = 2 * d[p0, p1, p2]
|
||||
# 0 = row, 1 = col, 2 = octave
|
||||
h00 = d[p0 - 1, p1, p2] + d[p0 + 1, p1, p2] - two_d0
|
||||
h11 = d[p0, p1 - 1, p2] + d[p0, p1 + 1, p2] - two_d0
|
||||
h22 = d[p0, p1, p2 - 1] + d[p0, p1, p2 + 1] - two_d0
|
||||
h01 = 0.25 * (
|
||||
d[p0 + 1, p1 + 1, p2]
|
||||
- d[p0 - 1, p1 + 1, p2]
|
||||
- d[p0 + 1, p1 - 1, p2]
|
||||
+ d[p0 - 1, p1 - 1, p2]
|
||||
)
|
||||
h02 = 0.25 * (
|
||||
d[p0 + 1, p1, p2 + 1]
|
||||
- d[p0 + 1, p1, p2 - 1]
|
||||
+ d[p0 - 1, p1, p2 - 1]
|
||||
- d[p0 - 1, p1, p2 + 1]
|
||||
)
|
||||
h12 = 0.25 * (
|
||||
d[p0, p1 + 1, p2 + 1]
|
||||
- d[p0, p1 + 1, p2 - 1]
|
||||
+ d[p0, p1 - 1, p2 - 1]
|
||||
- d[p0, p1 - 1, p2 + 1]
|
||||
)
|
||||
return (h00, h11, h22, h01, h02, h12)
|
||||
|
||||
|
||||
def _offsets(grad, hess):
|
||||
"""Compute position refinement offsets from gradient and Hessian.
|
||||
|
||||
This is equivalent to np.linalg.solve(-H, J) where H is the Hessian
|
||||
matrix and J is the gradient (Jacobian).
|
||||
|
||||
This analytical solution is adapted from (BSD-licensed) C code by
|
||||
Otero et. al (see SIFT docstring References).
|
||||
"""
|
||||
h00, h11, h22, h01, h02, h12 = hess
|
||||
g0, g1, g2 = grad
|
||||
det = h00 * h11 * h22
|
||||
det -= h00 * h12 * h12
|
||||
det -= h01 * h01 * h22
|
||||
det += 2 * h01 * h02 * h12
|
||||
det -= h02 * h02 * h11
|
||||
aa = (h11 * h22 - h12 * h12) / det
|
||||
ab = (h02 * h12 - h01 * h22) / det
|
||||
ac = (h01 * h12 - h02 * h11) / det
|
||||
bb = (h00 * h22 - h02 * h02) / det
|
||||
bc = (h01 * h02 - h00 * h12) / det
|
||||
cc = (h00 * h11 - h01 * h01) / det
|
||||
offset0 = -aa * g0 - ab * g1 - ac * g2
|
||||
offset1 = -ab * g0 - bb * g1 - bc * g2
|
||||
offset2 = -ac * g0 - bc * g1 - cc * g2
|
||||
return np.stack((offset0, offset1, offset2), axis=-1)
|
||||
|
||||
|
||||
class SIFT(FeatureDetector, DescriptorExtractor):
|
||||
"""SIFT feature detection and descriptor extraction.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
upsampling : int, optional
|
||||
Prior to the feature detection the image is upscaled by a factor
|
||||
of 1 (no upscaling), 2 or 4. Method: Bi-cubic interpolation.
|
||||
n_octaves : int, optional
|
||||
Maximum number of octaves. With every octave the image size is
|
||||
halved and the sigma doubled. The number of octaves will be
|
||||
reduced as needed to keep at least 12 pixels along each dimension
|
||||
at the smallest scale.
|
||||
n_scales : int, optional
|
||||
Maximum number of scales in every octave.
|
||||
sigma_min : float, optional
|
||||
The blur level of the seed image. If upsampling is enabled
|
||||
sigma_min is scaled by factor 1/upsampling
|
||||
sigma_in : float, optional
|
||||
The assumed blur level of the input image.
|
||||
c_dog : float, optional
|
||||
Threshold to discard low contrast extrema in the DoG. It's final
|
||||
value is dependent on n_scales by the relation:
|
||||
final_c_dog = (2^(1/n_scales)-1) / (2^(1/3)-1) * c_dog
|
||||
c_edge : float, optional
|
||||
Threshold to discard extrema that lie in edges. If H is the
|
||||
Hessian of an extremum, its "edgeness" is described by
|
||||
tr(H)²/det(H). If the edgeness is higher than
|
||||
(c_edge + 1)²/c_edge, the extremum is discarded.
|
||||
n_bins : int, optional
|
||||
Number of bins in the histogram that describes the gradient
|
||||
orientations around keypoint.
|
||||
lambda_ori : float, optional
|
||||
The window used to find the reference orientation of a keypoint
|
||||
has a width of 6 * lambda_ori * sigma and is weighted by a
|
||||
standard deviation of 2 * lambda_ori * sigma.
|
||||
c_max : float, optional
|
||||
The threshold at which a secondary peak in the orientation
|
||||
histogram is accepted as orientation
|
||||
lambda_descr : float, optional
|
||||
The window used to define the descriptor of a keypoint has a width
|
||||
of 2 * lambda_descr * sigma * (n_hist+1)/n_hist and is weighted by
|
||||
a standard deviation of lambda_descr * sigma.
|
||||
n_hist : int, optional
|
||||
The window used to define the descriptor of a keypoint consists of
|
||||
n_hist * n_hist histograms.
|
||||
n_ori : int, optional
|
||||
The number of bins in the histograms of the descriptor patch.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
delta_min : float
|
||||
The sampling distance of the first octave. It's final value is
|
||||
1/upsampling.
|
||||
float_dtype : type
|
||||
The datatype of the image.
|
||||
scalespace_sigmas : (n_octaves, n_scales + 3) array
|
||||
The sigma value of all scales in all octaves.
|
||||
keypoints : (N, 2) array
|
||||
Keypoint coordinates as ``(row, col)``.
|
||||
positions : (N, 2) array
|
||||
Subpixel-precision keypoint coordinates as ``(row, col)``.
|
||||
sigmas : (N,) array
|
||||
The corresponding sigma (blur) value of a keypoint.
|
||||
scales : (N,) array
|
||||
The corresponding scale of a keypoint.
|
||||
orientations : (N,) array
|
||||
The orientations of the gradient around every keypoint.
|
||||
octaves : (N,) array
|
||||
The corresponding octave of a keypoint.
|
||||
descriptors : (N, n_hist*n_hist*n_ori) array
|
||||
The descriptors of a keypoint.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The SIFT algorithm was developed by David Lowe [1]_, [2]_ and later
|
||||
patented by the University of British Columbia. Since the patent expired in
|
||||
2020 it's free to use. The implementation here closely follows the
|
||||
detailed description in [3]_, including use of the same default parameters.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] D.G. Lowe. "Object recognition from local scale-invariant
|
||||
features", Proceedings of the Seventh IEEE International
|
||||
Conference on Computer Vision, 1999, vol.2, pp. 1150-1157.
|
||||
:DOI:`10.1109/ICCV.1999.790410`
|
||||
|
||||
.. [2] D.G. Lowe. "Distinctive Image Features from Scale-Invariant
|
||||
Keypoints", International Journal of Computer Vision, 2004,
|
||||
vol. 60, pp. 91–110.
|
||||
:DOI:`10.1023/B:VISI.0000029664.99615.94`
|
||||
|
||||
.. [3] I. R. Otero and M. Delbracio. "Anatomy of the SIFT Method",
|
||||
Image Processing On Line, 4 (2014), pp. 370–396.
|
||||
:DOI:`10.5201/ipol.2014.82`
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.feature import SIFT, match_descriptors
|
||||
>>> from skimage.data import camera
|
||||
>>> from skimage.transform import rotate
|
||||
>>> img1 = camera()
|
||||
>>> img2 = rotate(camera(), 90)
|
||||
>>> detector_extractor1 = SIFT()
|
||||
>>> detector_extractor2 = SIFT()
|
||||
>>> detector_extractor1.detect_and_extract(img1)
|
||||
>>> detector_extractor2.detect_and_extract(img2)
|
||||
>>> matches = match_descriptors(detector_extractor1.descriptors,
|
||||
... detector_extractor2.descriptors,
|
||||
... max_ratio=0.6)
|
||||
>>> matches[10:15]
|
||||
array([[ 10, 412],
|
||||
[ 11, 417],
|
||||
[ 12, 407],
|
||||
[ 13, 411],
|
||||
[ 14, 406]])
|
||||
>>> detector_extractor1.keypoints[matches[10:15, 0]]
|
||||
array([[ 95, 214],
|
||||
[ 97, 211],
|
||||
[ 97, 218],
|
||||
[102, 215],
|
||||
[104, 218]])
|
||||
>>> detector_extractor2.keypoints[matches[10:15, 1]]
|
||||
array([[297, 95],
|
||||
[301, 97],
|
||||
[294, 97],
|
||||
[297, 102],
|
||||
[293, 104]])
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
upsampling=2,
|
||||
n_octaves=8,
|
||||
n_scales=3,
|
||||
sigma_min=1.6,
|
||||
sigma_in=0.5,
|
||||
c_dog=0.04 / 3,
|
||||
c_edge=10,
|
||||
n_bins=36,
|
||||
lambda_ori=1.5,
|
||||
c_max=0.8,
|
||||
lambda_descr=6,
|
||||
n_hist=4,
|
||||
n_ori=8,
|
||||
):
|
||||
if upsampling in [1, 2, 4]:
|
||||
self.upsampling = upsampling
|
||||
else:
|
||||
raise ValueError("upsampling must be 1, 2 or 4")
|
||||
self.n_octaves = n_octaves
|
||||
self.n_scales = n_scales
|
||||
self.sigma_min = sigma_min / upsampling
|
||||
self.sigma_in = sigma_in
|
||||
self.c_dog = (2 ** (1 / n_scales) - 1) / (2 ** (1 / 3) - 1) * c_dog
|
||||
self.c_edge = c_edge
|
||||
self.n_bins = n_bins
|
||||
self.lambda_ori = lambda_ori
|
||||
self.c_max = c_max
|
||||
self.lambda_descr = lambda_descr
|
||||
self.n_hist = n_hist
|
||||
self.n_ori = n_ori
|
||||
self.delta_min = 1 / upsampling
|
||||
self.float_dtype = None
|
||||
self.scalespace_sigmas = None
|
||||
self.keypoints = None
|
||||
self.positions = None
|
||||
self.sigmas = None
|
||||
self.scales = None
|
||||
self.orientations = None
|
||||
self.octaves = None
|
||||
self.descriptors = None
|
||||
|
||||
@property
|
||||
def deltas(self):
|
||||
"""The sampling distances of all octaves"""
|
||||
deltas = self.delta_min * np.power(
|
||||
2, np.arange(self.n_octaves), dtype=self.float_dtype
|
||||
)
|
||||
return deltas
|
||||
|
||||
def _set_number_of_octaves(self, image_shape):
|
||||
size_min = 12 # minimum size of last octave
|
||||
s0 = min(image_shape) * self.upsampling
|
||||
max_octaves = int(math.log2(s0 / size_min) + 1)
|
||||
if max_octaves < self.n_octaves:
|
||||
self.n_octaves = max_octaves
|
||||
|
||||
def _create_scalespace(self, image):
|
||||
"""Source: "Anatomy of the SIFT Method" Alg. 1
|
||||
Construction of the scalespace by gradually blurring (scales) and
|
||||
downscaling (octaves) the image.
|
||||
"""
|
||||
scalespace = []
|
||||
if self.upsampling > 1:
|
||||
image = rescale(image, self.upsampling, order=1)
|
||||
|
||||
# smooth to sigma_min, assuming sigma_in
|
||||
image = gaussian(
|
||||
image,
|
||||
sigma=self.upsampling * math.sqrt(self.sigma_min**2 - self.sigma_in**2),
|
||||
mode='reflect',
|
||||
)
|
||||
|
||||
# Eq. 10: sigmas.shape = (n_octaves, n_scales + 3).
|
||||
# The three extra scales are:
|
||||
# One for the differences needed for DoG and two auxiliary
|
||||
# images (one at either end) for peak_local_max with exclude
|
||||
# border = True (see Fig. 5)
|
||||
# The smoothing doubles after n_scales steps.
|
||||
tmp = np.power(2, np.arange(self.n_scales + 3) / self.n_scales)
|
||||
tmp *= self.sigma_min
|
||||
# all sigmas for the gaussian scalespace
|
||||
sigmas = self.deltas[:, np.newaxis] / self.deltas[0] * tmp[np.newaxis, :]
|
||||
self.scalespace_sigmas = sigmas
|
||||
|
||||
# Eq. 7: Gaussian smoothing depends on difference with previous sigma
|
||||
# gaussian_sigmas.shape = (n_octaves, n_scales + 2)
|
||||
var_diff = np.diff(sigmas * sigmas, axis=1)
|
||||
gaussian_sigmas = np.sqrt(var_diff) / self.deltas[:, np.newaxis]
|
||||
|
||||
# one octave is represented by a 3D image with depth (n_scales+x)
|
||||
for o in range(self.n_octaves):
|
||||
# Temporarily put scales axis first so octave[i] is C-contiguous
|
||||
# (this makes Gaussian filtering faster).
|
||||
octave = np.empty(
|
||||
(self.n_scales + 3,) + image.shape, dtype=self.float_dtype, order='C'
|
||||
)
|
||||
octave[0] = image
|
||||
for s in range(1, self.n_scales + 3):
|
||||
# blur new scale assuming sigma of the last one
|
||||
gaussian(
|
||||
octave[s - 1],
|
||||
sigma=gaussian_sigmas[o, s - 1],
|
||||
mode='reflect',
|
||||
out=octave[s],
|
||||
)
|
||||
# move scales to last axis as expected by other methods
|
||||
scalespace.append(np.moveaxis(octave, 0, -1))
|
||||
if o < self.n_octaves - 1:
|
||||
# downscale the image by taking every second pixel
|
||||
image = octave[self.n_scales][::2, ::2]
|
||||
return scalespace
|
||||
|
||||
def _inrange(self, a, dim):
|
||||
return (
|
||||
(a[:, 0] > 0)
|
||||
& (a[:, 0] < dim[0] - 1)
|
||||
& (a[:, 1] > 0)
|
||||
& (a[:, 1] < dim[1] - 1)
|
||||
)
|
||||
|
||||
def _find_localize_evaluate(self, dogspace, img_shape):
|
||||
"""Source: "Anatomy of the SIFT Method" Alg. 4-9
|
||||
1) first find all extrema of a (3, 3, 3) neighborhood
|
||||
2) use second order Taylor development to refine the positions to
|
||||
sub-pixel precision
|
||||
3) filter out extrema that have low contrast and lie on edges or close
|
||||
to the image borders
|
||||
"""
|
||||
extrema_pos = []
|
||||
extrema_scales = []
|
||||
extrema_sigmas = []
|
||||
threshold = self.c_dog * 0.8
|
||||
for o, (octave, delta) in enumerate(zip(dogspace, self.deltas)):
|
||||
# find extrema
|
||||
keys = _local_max(np.ascontiguousarray(octave), threshold)
|
||||
if keys.size == 0:
|
||||
extrema_pos.append(np.empty((0, 2)))
|
||||
continue
|
||||
|
||||
# localize extrema
|
||||
oshape = octave.shape
|
||||
refinement_iterations = 5
|
||||
offset_max = 0.6
|
||||
for i in range(refinement_iterations):
|
||||
if i > 0:
|
||||
# exclude any keys that have moved out of bounds
|
||||
keys = keys[self._inrange(keys, oshape), :]
|
||||
|
||||
# Jacobian and Hessian of all extrema
|
||||
grad = _sparse_gradient(octave, keys)
|
||||
hess = _hessian(octave, keys)
|
||||
|
||||
# solve for offset of the extremum
|
||||
off = _offsets(grad, hess)
|
||||
if i == refinement_iterations - 1:
|
||||
break
|
||||
# offset is too big and an increase would not bring us out of
|
||||
# bounds
|
||||
wrong_position_pos = np.logical_and(
|
||||
off > offset_max, keys + 1 < tuple([a - 1 for a in oshape])
|
||||
)
|
||||
wrong_position_neg = np.logical_and(off < -offset_max, keys - 1 > 0)
|
||||
if not np.any(np.logical_or(wrong_position_neg, wrong_position_pos)):
|
||||
break
|
||||
keys[wrong_position_pos] += 1
|
||||
keys[wrong_position_neg] -= 1
|
||||
|
||||
# mask for all extrema that have been localized successfully
|
||||
finished = np.all(np.abs(off) < offset_max, axis=1)
|
||||
keys = keys[finished]
|
||||
off = off[finished]
|
||||
grad = [g[finished] for g in grad]
|
||||
|
||||
# value of extremum in octave
|
||||
vals = octave[keys[:, 0], keys[:, 1], keys[:, 2]]
|
||||
# values at interpolated point
|
||||
w = vals
|
||||
for i in range(3):
|
||||
w += 0.5 * grad[i] * off[:, i]
|
||||
|
||||
h00, h11, h01 = hess[0][finished], hess[1][finished], hess[3][finished]
|
||||
|
||||
sigmaratio = self.scalespace_sigmas[0, 1] / self.scalespace_sigmas[0, 0]
|
||||
|
||||
# filter for contrast, edgeness and borders
|
||||
contrast_threshold = self.c_dog
|
||||
contrast_filter = np.abs(w) > contrast_threshold
|
||||
|
||||
edge_threshold = np.square(self.c_edge + 1) / self.c_edge
|
||||
edge_response = _edgeness(
|
||||
h00[contrast_filter], h11[contrast_filter], h01[contrast_filter]
|
||||
)
|
||||
edge_filter = np.abs(edge_response) <= edge_threshold
|
||||
|
||||
keys = keys[contrast_filter][edge_filter]
|
||||
off = off[contrast_filter][edge_filter]
|
||||
yx = ((keys[:, :2] + off[:, :2]) * delta).astype(self.float_dtype)
|
||||
|
||||
sigmas = self.scalespace_sigmas[o, keys[:, 2]] * np.power(
|
||||
sigmaratio, off[:, 2]
|
||||
)
|
||||
border_filter = np.all(
|
||||
np.logical_and(
|
||||
(yx - sigmas[:, np.newaxis]) > 0.0,
|
||||
(yx + sigmas[:, np.newaxis]) < img_shape,
|
||||
),
|
||||
axis=1,
|
||||
)
|
||||
extrema_pos.append(yx[border_filter])
|
||||
extrema_scales.append(keys[border_filter, 2])
|
||||
extrema_sigmas.append(sigmas[border_filter])
|
||||
|
||||
octave_indices = np.concatenate(
|
||||
[np.full(len(p), i) for i, p in enumerate(extrema_pos)]
|
||||
)
|
||||
|
||||
if len(octave_indices) == 0:
|
||||
raise RuntimeError(
|
||||
"SIFT found no features. Try passing in an image containing "
|
||||
"greater intensity contrasts between adjacent pixels."
|
||||
)
|
||||
|
||||
extrema_pos = np.concatenate(extrema_pos)
|
||||
extrema_scales = np.concatenate(extrema_scales)
|
||||
extrema_sigmas = np.concatenate(extrema_sigmas)
|
||||
return extrema_pos, extrema_scales, extrema_sigmas, octave_indices
|
||||
|
||||
def _fit(self, h):
|
||||
"""Refine the position of the peak by fitting it to a parabola"""
|
||||
return (h[0] - h[2]) / (2 * (h[0] + h[2] - 2 * h[1]))
|
||||
|
||||
def _compute_orientation(
|
||||
self, positions_oct, scales_oct, sigmas_oct, octaves, gaussian_scalespace
|
||||
):
|
||||
"""Source: "Anatomy of the SIFT Method" Alg. 11
|
||||
Calculates the orientation of the gradient around every keypoint
|
||||
"""
|
||||
gradient_space = []
|
||||
# list for keypoints that have more than one reference orientation
|
||||
keypoint_indices = []
|
||||
keypoint_angles = []
|
||||
keypoint_octave = []
|
||||
orientations = np.zeros_like(sigmas_oct, dtype=self.float_dtype)
|
||||
key_count = 0
|
||||
for o, (octave, delta) in enumerate(zip(gaussian_scalespace, self.deltas)):
|
||||
gradient_space.append(np.gradient(octave))
|
||||
|
||||
in_oct = octaves == o
|
||||
if not np.any(in_oct):
|
||||
continue
|
||||
positions = positions_oct[in_oct]
|
||||
scales = scales_oct[in_oct]
|
||||
sigmas = sigmas_oct[in_oct]
|
||||
|
||||
oshape = octave.shape[:2]
|
||||
# convert to octave's dimensions
|
||||
yx = positions / delta
|
||||
sigma = sigmas / delta
|
||||
|
||||
# dimensions of the patch
|
||||
radius = 3 * self.lambda_ori * sigma
|
||||
p_min = np.maximum(0, yx - radius[:, np.newaxis] + 0.5).astype(int)
|
||||
p_max = np.minimum(
|
||||
yx + radius[:, np.newaxis] + 0.5, (oshape[0] - 1, oshape[1] - 1)
|
||||
).astype(int)
|
||||
# orientation histogram
|
||||
hist = np.empty(self.n_bins, dtype=self.float_dtype)
|
||||
avg_kernel = np.full((3,), 1 / 3, dtype=self.float_dtype)
|
||||
for k in range(len(yx)):
|
||||
hist[:] = 0
|
||||
|
||||
# use the patch coordinates to get the gradient and then
|
||||
# normalize them
|
||||
r, c = np.meshgrid(
|
||||
np.arange(p_min[k, 0], p_max[k, 0] + 1),
|
||||
np.arange(p_min[k, 1], p_max[k, 1] + 1),
|
||||
indexing='ij',
|
||||
sparse=True,
|
||||
)
|
||||
gradient_row = gradient_space[o][0][r, c, scales[k]]
|
||||
gradient_col = gradient_space[o][1][r, c, scales[k]]
|
||||
r = r.astype(self.float_dtype, copy=False)
|
||||
c = c.astype(self.float_dtype, copy=False)
|
||||
r -= yx[k, 0]
|
||||
c -= yx[k, 1]
|
||||
|
||||
# gradient magnitude and angles
|
||||
magnitude = np.sqrt(np.square(gradient_row) + np.square(gradient_col))
|
||||
theta = np.mod(np.arctan2(gradient_col, gradient_row), 2 * np.pi)
|
||||
|
||||
# more weight to center values
|
||||
kernel = np.exp(
|
||||
np.divide(r * r + c * c, -2 * (self.lambda_ori * sigma[k]) ** 2)
|
||||
)
|
||||
|
||||
# fill the histogram
|
||||
bins = np.floor(
|
||||
(theta / (2 * np.pi) * self.n_bins + 0.5) % self.n_bins
|
||||
).astype(int)
|
||||
np.add.at(hist, bins, kernel * magnitude)
|
||||
|
||||
# smooth the histogram and find the maximum
|
||||
hist = np.concatenate((hist[-6:], hist, hist[:6]))
|
||||
for _ in range(6): # number of smoothings
|
||||
hist = np.convolve(hist, avg_kernel, mode='same')
|
||||
hist = hist[6:-6]
|
||||
max_filter = ndi.maximum_filter(hist, [3], mode='wrap')
|
||||
|
||||
# if an angle is in 80% percent range of the maximum, a
|
||||
# new keypoint is created for it
|
||||
maxima = np.nonzero(
|
||||
np.logical_and(
|
||||
hist >= (self.c_max * np.max(hist)), max_filter == hist
|
||||
)
|
||||
)
|
||||
|
||||
# save the angles
|
||||
for c, m in enumerate(maxima[0]):
|
||||
neigh = np.arange(m - 1, m + 2) % len(hist)
|
||||
# use neighbors to fit a parabola, to get more accurate
|
||||
# result
|
||||
ori = (m + self._fit(hist[neigh]) + 0.5) * 2 * np.pi / self.n_bins
|
||||
if ori > np.pi:
|
||||
ori -= 2 * np.pi
|
||||
if c == 0:
|
||||
orientations[key_count] = ori
|
||||
else:
|
||||
keypoint_indices.append(key_count)
|
||||
keypoint_angles.append(ori)
|
||||
keypoint_octave.append(o)
|
||||
key_count += 1
|
||||
self.positions = np.concatenate(
|
||||
(positions_oct, positions_oct[keypoint_indices])
|
||||
)
|
||||
self.scales = np.concatenate((scales_oct, scales_oct[keypoint_indices]))
|
||||
self.sigmas = np.concatenate((sigmas_oct, sigmas_oct[keypoint_indices]))
|
||||
self.orientations = np.concatenate((orientations, keypoint_angles))
|
||||
self.octaves = np.concatenate((octaves, keypoint_octave))
|
||||
# return the gradient_space to reuse it to find the descriptor
|
||||
return gradient_space
|
||||
|
||||
def _rotate(self, row, col, angle):
|
||||
c = math.cos(angle)
|
||||
s = math.sin(angle)
|
||||
rot_row = c * row + s * col
|
||||
rot_col = -s * row + c * col
|
||||
return rot_row, rot_col
|
||||
|
||||
def _compute_descriptor(self, gradient_space):
|
||||
"""Source: "Anatomy of the SIFT Method" Alg. 12
|
||||
Calculates the descriptor for every keypoint
|
||||
"""
|
||||
n_key = len(self.scales)
|
||||
self.descriptors = np.empty(
|
||||
(n_key, self.n_hist**2 * self.n_ori), dtype=np.uint8
|
||||
)
|
||||
|
||||
# indices of the histograms
|
||||
hists = np.arange(1, self.n_hist + 1, dtype=self.float_dtype)
|
||||
# indices of the bins
|
||||
bins = np.arange(1, self.n_ori + 1, dtype=self.float_dtype)
|
||||
|
||||
key_numbers = np.arange(n_key)
|
||||
for o, (gradient, delta) in enumerate(zip(gradient_space, self.deltas)):
|
||||
in_oct = self.octaves == o
|
||||
if not np.any(in_oct):
|
||||
continue
|
||||
positions = self.positions[in_oct]
|
||||
scales = self.scales[in_oct]
|
||||
sigmas = self.sigmas[in_oct]
|
||||
orientations = self.orientations[in_oct]
|
||||
numbers = key_numbers[in_oct]
|
||||
|
||||
dim = gradient[0].shape[:2]
|
||||
center_pos = positions / delta
|
||||
sigma = sigmas / delta
|
||||
|
||||
# dimensions of the patch
|
||||
radius = self.lambda_descr * (1 + 1 / self.n_hist) * sigma
|
||||
radius_patch = math.sqrt(2) * radius
|
||||
p_min = np.asarray(
|
||||
np.maximum(0, center_pos - radius_patch[:, np.newaxis] + 0.5), dtype=int
|
||||
)
|
||||
p_max = np.asarray(
|
||||
np.minimum(
|
||||
center_pos + radius_patch[:, np.newaxis] + 0.5,
|
||||
(dim[0] - 1, dim[1] - 1),
|
||||
),
|
||||
dtype=int,
|
||||
)
|
||||
|
||||
for k in range(len(p_max)):
|
||||
rad_k = float(radius[k])
|
||||
ori = float(orientations[k])
|
||||
histograms = np.zeros(
|
||||
(self.n_hist, self.n_hist, self.n_ori), dtype=self.float_dtype
|
||||
)
|
||||
# the patch
|
||||
r, c = np.meshgrid(
|
||||
np.arange(p_min[k, 0], p_max[k, 0]),
|
||||
np.arange(p_min[k, 1], p_max[k, 1]),
|
||||
indexing='ij',
|
||||
sparse=True,
|
||||
)
|
||||
# normalized coordinates
|
||||
r_norm = np.subtract(r, center_pos[k, 0], dtype=self.float_dtype)
|
||||
c_norm = np.subtract(c, center_pos[k, 1], dtype=self.float_dtype)
|
||||
r_norm, c_norm = self._rotate(r_norm, c_norm, ori)
|
||||
|
||||
# select coordinates and gradient values within the patch
|
||||
inside = np.maximum(np.abs(r_norm), np.abs(c_norm)) < rad_k
|
||||
r_norm, c_norm = r_norm[inside], c_norm[inside]
|
||||
r_idx, c_idx = np.nonzero(inside)
|
||||
r = r[r_idx, 0]
|
||||
c = c[0, c_idx]
|
||||
gradient_row = gradient[0][r, c, scales[k]]
|
||||
gradient_col = gradient[1][r, c, scales[k]]
|
||||
# compute the (relative) gradient orientation
|
||||
theta = np.arctan2(gradient_col, gradient_row) - ori
|
||||
lam_sig = self.lambda_descr * float(sigma[k])
|
||||
# Gaussian weighted kernel magnitude
|
||||
kernel = np.exp((r_norm * r_norm + c_norm * c_norm) / (-2 * lam_sig**2))
|
||||
magnitude = (
|
||||
np.sqrt(gradient_row * gradient_row + gradient_col * gradient_col)
|
||||
* kernel
|
||||
)
|
||||
|
||||
lam_sig_ratio = 2 * lam_sig / self.n_hist
|
||||
rc_bins = (hists - (1 + self.n_hist) / 2) * lam_sig_ratio
|
||||
rc_bin_spacing = lam_sig_ratio
|
||||
ori_bins = (2 * np.pi * bins) / self.n_ori
|
||||
|
||||
# distances to the histograms and bins
|
||||
dist_r = np.abs(np.subtract.outer(rc_bins, r_norm))
|
||||
dist_c = np.abs(np.subtract.outer(rc_bins, c_norm))
|
||||
|
||||
# the orientation histograms/bins that get the contribution
|
||||
near_t, near_t_val = _ori_distances(ori_bins, theta)
|
||||
|
||||
# create the histogram
|
||||
_update_histogram(
|
||||
histograms,
|
||||
near_t,
|
||||
near_t_val,
|
||||
magnitude,
|
||||
dist_r,
|
||||
dist_c,
|
||||
rc_bin_spacing,
|
||||
)
|
||||
|
||||
# convert the histograms to a 1d descriptor
|
||||
histograms = histograms.reshape(-1)
|
||||
# saturate the descriptor
|
||||
histograms = np.minimum(histograms, 0.2 * np.linalg.norm(histograms))
|
||||
# normalize the descriptor
|
||||
descriptor = (512 * histograms) / np.linalg.norm(histograms)
|
||||
# quantize the descriptor
|
||||
descriptor = np.minimum(np.floor(descriptor), 255)
|
||||
self.descriptors[numbers[k], :] = descriptor
|
||||
|
||||
def _preprocess(self, image):
|
||||
check_nD(image, 2)
|
||||
image = img_as_float(image)
|
||||
self.float_dtype = _supported_float_type(image.dtype)
|
||||
image = image.astype(self.float_dtype, copy=False)
|
||||
|
||||
self._set_number_of_octaves(image.shape)
|
||||
return image
|
||||
|
||||
def detect(self, image):
|
||||
"""Detect the keypoints.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : 2D array
|
||||
Input image.
|
||||
|
||||
"""
|
||||
image = self._preprocess(image)
|
||||
|
||||
gaussian_scalespace = self._create_scalespace(image)
|
||||
|
||||
dog_scalespace = [np.diff(layer, axis=2) for layer in gaussian_scalespace]
|
||||
|
||||
positions, scales, sigmas, octaves = self._find_localize_evaluate(
|
||||
dog_scalespace, image.shape
|
||||
)
|
||||
|
||||
self._compute_orientation(
|
||||
positions, scales, sigmas, octaves, gaussian_scalespace
|
||||
)
|
||||
|
||||
self.keypoints = self.positions.round().astype(int)
|
||||
|
||||
def extract(self, image):
|
||||
"""Extract the descriptors for all keypoints in the image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : 2D array
|
||||
Input image.
|
||||
|
||||
"""
|
||||
image = self._preprocess(image)
|
||||
|
||||
gaussian_scalespace = self._create_scalespace(image)
|
||||
|
||||
gradient_space = [np.gradient(octave) for octave in gaussian_scalespace]
|
||||
|
||||
self._compute_descriptor(gradient_space)
|
||||
|
||||
def detect_and_extract(self, image):
|
||||
"""Detect the keypoints and extract their descriptors.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : 2D array
|
||||
Input image.
|
||||
|
||||
"""
|
||||
image = self._preprocess(image)
|
||||
|
||||
gaussian_scalespace = self._create_scalespace(image)
|
||||
|
||||
dog_scalespace = [np.diff(layer, axis=2) for layer in gaussian_scalespace]
|
||||
|
||||
positions, scales, sigmas, octaves = self._find_localize_evaluate(
|
||||
dog_scalespace, image.shape
|
||||
)
|
||||
|
||||
gradient_space = self._compute_orientation(
|
||||
positions, scales, sigmas, octaves, gaussian_scalespace
|
||||
)
|
||||
|
||||
self._compute_descriptor(gradient_space)
|
||||
|
||||
self.keypoints = self.positions.round().astype(int)
|
||||
186
.CondaPkg/env/Lib/site-packages/skimage/feature/template.py
vendored
Normal file
186
.CondaPkg/env/Lib/site-packages/skimage/feature/template.py
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
from scipy.signal import fftconvolve
|
||||
|
||||
from .._shared.utils import check_nD, _supported_float_type
|
||||
|
||||
|
||||
def _window_sum_2d(image, window_shape):
|
||||
window_sum = np.cumsum(image, axis=0)
|
||||
window_sum = window_sum[window_shape[0] : -1] - window_sum[: -window_shape[0] - 1]
|
||||
|
||||
window_sum = np.cumsum(window_sum, axis=1)
|
||||
window_sum = (
|
||||
window_sum[:, window_shape[1] : -1] - window_sum[:, : -window_shape[1] - 1]
|
||||
)
|
||||
|
||||
return window_sum
|
||||
|
||||
|
||||
def _window_sum_3d(image, window_shape):
|
||||
window_sum = _window_sum_2d(image, window_shape)
|
||||
|
||||
window_sum = np.cumsum(window_sum, axis=2)
|
||||
window_sum = (
|
||||
window_sum[:, :, window_shape[2] : -1]
|
||||
- window_sum[:, :, : -window_shape[2] - 1]
|
||||
)
|
||||
|
||||
return window_sum
|
||||
|
||||
|
||||
def match_template(
|
||||
image, template, pad_input=False, mode='constant', constant_values=0
|
||||
):
|
||||
"""Match a template to a 2-D or 3-D image using normalized correlation.
|
||||
|
||||
The output is an array with values between -1.0 and 1.0. The value at a
|
||||
given position corresponds to the correlation coefficient between the image
|
||||
and the template.
|
||||
|
||||
For `pad_input=True` matches correspond to the center and otherwise to the
|
||||
top-left corner of the template. To find the best match you must search for
|
||||
peaks in the response (output) image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : (M, N[, P]) array
|
||||
2-D or 3-D input image.
|
||||
template : (m, n[, p]) array
|
||||
Template to locate. It must be `(m <= M, n <= N[, p <= P])`.
|
||||
pad_input : bool
|
||||
If True, pad `image` so that output is the same size as the image, and
|
||||
output values correspond to the template center. Otherwise, the output
|
||||
is an array with shape `(M - m + 1, N - n + 1)` for an `(M, N)` image
|
||||
and an `(m, n)` template, and matches correspond to origin
|
||||
(top-left corner) of the template.
|
||||
mode : see `numpy.pad`, optional
|
||||
Padding mode.
|
||||
constant_values : see `numpy.pad`, optional
|
||||
Constant values used in conjunction with ``mode='constant'``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
output : array
|
||||
Response image with correlation coefficients.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Details on the cross-correlation are presented in [1]_. This implementation
|
||||
uses FFT convolutions of the image and the template. Reference [2]_
|
||||
presents similar derivations but the approximation presented in this
|
||||
reference is not used in our implementation.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] J. P. Lewis, "Fast Normalized Cross-Correlation", Industrial Light
|
||||
and Magic.
|
||||
.. [2] Briechle and Hanebeck, "Template Matching using Fast Normalized
|
||||
Cross Correlation", Proceedings of the SPIE (2001).
|
||||
:DOI:`10.1117/12.421129`
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> template = np.zeros((3, 3))
|
||||
>>> template[1, 1] = 1
|
||||
>>> template
|
||||
array([[0., 0., 0.],
|
||||
[0., 1., 0.],
|
||||
[0., 0., 0.]])
|
||||
>>> image = np.zeros((6, 6))
|
||||
>>> image[1, 1] = 1
|
||||
>>> image[4, 4] = -1
|
||||
>>> image
|
||||
array([[ 0., 0., 0., 0., 0., 0.],
|
||||
[ 0., 1., 0., 0., 0., 0.],
|
||||
[ 0., 0., 0., 0., 0., 0.],
|
||||
[ 0., 0., 0., 0., 0., 0.],
|
||||
[ 0., 0., 0., 0., -1., 0.],
|
||||
[ 0., 0., 0., 0., 0., 0.]])
|
||||
>>> result = match_template(image, template)
|
||||
>>> np.round(result, 3)
|
||||
array([[ 1. , -0.125, 0. , 0. ],
|
||||
[-0.125, -0.125, 0. , 0. ],
|
||||
[ 0. , 0. , 0.125, 0.125],
|
||||
[ 0. , 0. , 0.125, -1. ]])
|
||||
>>> result = match_template(image, template, pad_input=True)
|
||||
>>> np.round(result, 3)
|
||||
array([[-0.125, -0.125, -0.125, 0. , 0. , 0. ],
|
||||
[-0.125, 1. , -0.125, 0. , 0. , 0. ],
|
||||
[-0.125, -0.125, -0.125, 0. , 0. , 0. ],
|
||||
[ 0. , 0. , 0. , 0.125, 0.125, 0.125],
|
||||
[ 0. , 0. , 0. , 0.125, -1. , 0.125],
|
||||
[ 0. , 0. , 0. , 0.125, 0.125, 0.125]])
|
||||
"""
|
||||
check_nD(image, (2, 3))
|
||||
|
||||
if image.ndim < template.ndim:
|
||||
raise ValueError(
|
||||
"Dimensionality of template must be less than or "
|
||||
"equal to the dimensionality of image."
|
||||
)
|
||||
if np.any(np.less(image.shape, template.shape)):
|
||||
raise ValueError("Image must be larger than template.")
|
||||
|
||||
image_shape = image.shape
|
||||
|
||||
float_dtype = _supported_float_type(image.dtype)
|
||||
image = image.astype(float_dtype, copy=False)
|
||||
|
||||
pad_width = tuple((width, width) for width in template.shape)
|
||||
if mode == 'constant':
|
||||
image = np.pad(
|
||||
image, pad_width=pad_width, mode=mode, constant_values=constant_values
|
||||
)
|
||||
else:
|
||||
image = np.pad(image, pad_width=pad_width, mode=mode)
|
||||
|
||||
# Use special case for 2-D images for much better performance in
|
||||
# computation of integral images
|
||||
if image.ndim == 2:
|
||||
image_window_sum = _window_sum_2d(image, template.shape)
|
||||
image_window_sum2 = _window_sum_2d(image**2, template.shape)
|
||||
elif image.ndim == 3:
|
||||
image_window_sum = _window_sum_3d(image, template.shape)
|
||||
image_window_sum2 = _window_sum_3d(image**2, template.shape)
|
||||
|
||||
template_mean = template.mean()
|
||||
template_volume = math.prod(template.shape)
|
||||
template_ssd = np.sum((template - template_mean) ** 2)
|
||||
|
||||
if image.ndim == 2:
|
||||
xcorr = fftconvolve(image, template[::-1, ::-1], mode="valid")[1:-1, 1:-1]
|
||||
elif image.ndim == 3:
|
||||
xcorr = fftconvolve(image, template[::-1, ::-1, ::-1], mode="valid")[
|
||||
1:-1, 1:-1, 1:-1
|
||||
]
|
||||
|
||||
numerator = xcorr - image_window_sum * template_mean
|
||||
|
||||
denominator = image_window_sum2
|
||||
np.multiply(image_window_sum, image_window_sum, out=image_window_sum)
|
||||
np.divide(image_window_sum, template_volume, out=image_window_sum)
|
||||
denominator -= image_window_sum
|
||||
denominator *= template_ssd
|
||||
np.maximum(denominator, 0, out=denominator) # sqrt of negative number not allowed
|
||||
np.sqrt(denominator, out=denominator)
|
||||
|
||||
response = np.zeros_like(xcorr, dtype=float_dtype)
|
||||
|
||||
# avoid zero-division
|
||||
mask = denominator > np.finfo(float_dtype).eps
|
||||
|
||||
response[mask] = numerator[mask] / denominator[mask]
|
||||
|
||||
slices = []
|
||||
for i in range(template.ndim):
|
||||
if pad_input:
|
||||
d0 = (template.shape[i] - 1) // 2
|
||||
d1 = d0 + image_shape[i]
|
||||
else:
|
||||
d0 = template.shape[i] - 1
|
||||
d1 = d0 + image_shape[i] - template.shape[i] + 1
|
||||
slices.append(slice(d0, d1))
|
||||
|
||||
return response[tuple(slices)]
|
||||
0
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__init__.py
vendored
Normal file
0
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__init__.py
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/__init__.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/__init__.cpython-312.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_blob.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_blob.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_brief.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_brief.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_canny.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_canny.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_cascade.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_cascade.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_censure.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_censure.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_corner.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_corner.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_daisy.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_daisy.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_fisher_vector.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_fisher_vector.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_haar.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_haar.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_hog.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_hog.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_match.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_match.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_orb.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_orb.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_peak.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_peak.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_sift.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_sift.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_template.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_template.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_texture.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_texture.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_util.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/__pycache__/test_util.cpython-312.pyc
vendored
Normal file
Binary file not shown.
62
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_basic_features.py
vendored
Normal file
62
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_basic_features.py
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
import pytest
|
||||
import numpy as np
|
||||
|
||||
from skimage.feature import multiscale_basic_features
|
||||
|
||||
|
||||
@pytest.mark.parametrize('edges', (False, True))
|
||||
@pytest.mark.parametrize('texture', (False, True))
|
||||
def test_multiscale_basic_features_gray(edges, texture):
|
||||
img = np.zeros((20, 20))
|
||||
img[:10] = 1
|
||||
img += 0.05 * np.random.randn(*img.shape)
|
||||
features = multiscale_basic_features(img, edges=edges, texture=texture)
|
||||
|
||||
n_sigmas = 6
|
||||
intensity = True
|
||||
assert features.shape[-1] == (
|
||||
n_sigmas * (int(intensity) + int(edges) + 2 * int(texture))
|
||||
)
|
||||
assert features.shape[:-1] == img.shape[:]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('edges', (False, True))
|
||||
@pytest.mark.parametrize('texture', (False, True))
|
||||
def test_multiscale_basic_features_rgb(edges, texture):
|
||||
img = np.zeros((20, 20, 3))
|
||||
img[:10] = 1
|
||||
img += 0.05 * np.random.randn(*img.shape)
|
||||
features = multiscale_basic_features(
|
||||
img, edges=edges, texture=texture, channel_axis=-1
|
||||
)
|
||||
|
||||
n_sigmas = 6
|
||||
intensity = True
|
||||
assert features.shape[-1] == (
|
||||
3 * n_sigmas * (int(intensity) + int(edges) + 2 * int(texture))
|
||||
)
|
||||
assert features.shape[:-1] == img.shape[:-1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('channel_axis', [0, 1, 2, -1, -2])
|
||||
def test_multiscale_basic_features_channel_axis(channel_axis):
|
||||
num_channels = 5
|
||||
shape_spatial = (10, 10)
|
||||
ndim = len(shape_spatial)
|
||||
shape = tuple(np.insert(shape_spatial, channel_axis % (ndim + 1), num_channels))
|
||||
img = np.zeros(shape)
|
||||
img[:10] = 1
|
||||
img += 0.05 * np.random.randn(*img.shape)
|
||||
n_sigmas = 2
|
||||
|
||||
# features for all channels are concatenated along the last axis
|
||||
features = multiscale_basic_features(
|
||||
img, sigma_min=1, sigma_max=2, channel_axis=channel_axis
|
||||
)
|
||||
assert features.shape[-1] == 5 * n_sigmas * 4
|
||||
assert features.shape[:-1] == np.moveaxis(img, channel_axis, -1).shape[:-1]
|
||||
|
||||
# Consider channel_axis as spatial dimension
|
||||
features = multiscale_basic_features(img, sigma_min=1, sigma_max=2)
|
||||
assert features.shape[-1] == n_sigmas * 5
|
||||
assert features.shape[:-1] == img.shape
|
||||
588
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_blob.py
vendored
Normal file
588
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_blob.py
vendored
Normal file
@@ -0,0 +1,588 @@
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy.testing import assert_almost_equal
|
||||
|
||||
from skimage import feature
|
||||
from skimage.draw import disk
|
||||
from skimage.draw.draw3d import ellipsoid
|
||||
from skimage.feature import blob_dog, blob_doh, blob_log
|
||||
from skimage.feature.blob import _blob_overlap
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.uint8, np.float16, np.float32, np.float64])
|
||||
@pytest.mark.parametrize('threshold_type', ['absolute', 'relative'])
|
||||
def test_blob_dog(dtype, threshold_type):
|
||||
r2 = math.sqrt(2)
|
||||
img = np.ones((512, 512), dtype=dtype)
|
||||
|
||||
xs, ys = disk((400, 130), 5)
|
||||
img[xs, ys] = 255
|
||||
|
||||
xs, ys = disk((100, 300), 25)
|
||||
img[xs, ys] = 255
|
||||
|
||||
xs, ys = disk((200, 350), 45)
|
||||
img[xs, ys] = 255
|
||||
|
||||
if threshold_type == 'absolute':
|
||||
threshold = 2.0
|
||||
if img.dtype.kind != 'f':
|
||||
# account for internal scaling to [0, 1] by img_as_float
|
||||
threshold /= np.ptp(img)
|
||||
threshold_rel = None
|
||||
elif threshold_type == 'relative':
|
||||
threshold = None
|
||||
threshold_rel = 0.5
|
||||
|
||||
blobs = blob_dog(
|
||||
img,
|
||||
min_sigma=4,
|
||||
max_sigma=50,
|
||||
threshold=threshold,
|
||||
threshold_rel=threshold_rel,
|
||||
)
|
||||
|
||||
def radius(x):
|
||||
return r2 * x[2]
|
||||
|
||||
s = sorted(blobs, key=radius)
|
||||
thresh = 5
|
||||
ratio_thresh = 0.25
|
||||
|
||||
b = s[0]
|
||||
assert abs(b[0] - 400) <= thresh
|
||||
assert abs(b[1] - 130) <= thresh
|
||||
assert abs(radius(b) - 5) <= ratio_thresh * 5
|
||||
|
||||
b = s[1]
|
||||
assert abs(b[0] - 100) <= thresh
|
||||
assert abs(b[1] - 300) <= thresh
|
||||
assert abs(radius(b) - 25) <= ratio_thresh * 25
|
||||
|
||||
b = s[2]
|
||||
assert abs(b[0] - 200) <= thresh
|
||||
assert abs(b[1] - 350) <= thresh
|
||||
assert abs(radius(b) - 45) <= ratio_thresh * 45
|
||||
|
||||
# Testing no peaks
|
||||
img_empty = np.zeros((100, 100), dtype=dtype)
|
||||
assert blob_dog(img_empty).size == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.uint8, np.float16, np.float32, np.float64])
|
||||
@pytest.mark.parametrize('threshold_type', ['absolute', 'relative'])
|
||||
def test_blob_dog_3d(dtype, threshold_type):
|
||||
# Testing 3D
|
||||
r = 10
|
||||
pad = 10
|
||||
im3 = ellipsoid(r, r, r)
|
||||
im3 = np.pad(im3, pad, mode='constant')
|
||||
|
||||
if threshold_type == 'absolute':
|
||||
threshold = 0.001
|
||||
threshold_rel = 0
|
||||
elif threshold_type == 'relative':
|
||||
threshold = 0
|
||||
threshold_rel = 0.5
|
||||
|
||||
blobs = blob_dog(
|
||||
im3,
|
||||
min_sigma=3,
|
||||
max_sigma=10,
|
||||
sigma_ratio=1.2,
|
||||
threshold=threshold,
|
||||
threshold_rel=threshold_rel,
|
||||
)
|
||||
b = blobs[0]
|
||||
|
||||
assert b.shape == (4,)
|
||||
assert b[0] == r + pad + 1
|
||||
assert b[1] == r + pad + 1
|
||||
assert b[2] == r + pad + 1
|
||||
assert abs(math.sqrt(3) * b[3] - r) < 1.1
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.uint8, np.float16, np.float32, np.float64])
|
||||
@pytest.mark.parametrize('threshold_type', ['absolute', 'relative'])
|
||||
def test_blob_dog_3d_anisotropic(dtype, threshold_type):
|
||||
# Testing 3D anisotropic
|
||||
r = 10
|
||||
pad = 10
|
||||
im3 = ellipsoid(r / 2, r, r)
|
||||
im3 = np.pad(im3, pad, mode='constant')
|
||||
|
||||
if threshold_type == 'absolute':
|
||||
threshold = 0.001
|
||||
threshold_rel = None
|
||||
elif threshold_type == 'relative':
|
||||
threshold = None
|
||||
threshold_rel = 0.5
|
||||
|
||||
blobs = blob_dog(
|
||||
im3.astype(dtype, copy=False),
|
||||
min_sigma=[1.5, 3, 3],
|
||||
max_sigma=[5, 10, 10],
|
||||
sigma_ratio=1.2,
|
||||
threshold=threshold,
|
||||
threshold_rel=threshold_rel,
|
||||
)
|
||||
b = blobs[0]
|
||||
|
||||
assert b.shape == (6,)
|
||||
assert b[0] == r / 2 + pad + 1
|
||||
assert b[1] == r + pad + 1
|
||||
assert b[2] == r + pad + 1
|
||||
assert abs(math.sqrt(3) * b[3] - r / 2) < 1.1
|
||||
assert abs(math.sqrt(3) * b[4] - r) < 1.1
|
||||
assert abs(math.sqrt(3) * b[5] - r) < 1.1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("disc_center", [(5, 5), (5, 20)])
|
||||
@pytest.mark.parametrize("exclude_border", [6, (6, 6), (4, 15)])
|
||||
def test_blob_dog_exclude_border(disc_center, exclude_border):
|
||||
# Testing exclude border
|
||||
|
||||
# image where blob is disc_center px from borders, radius 5
|
||||
img = np.ones((512, 512))
|
||||
xs, ys = disk(disc_center, 5)
|
||||
img[xs, ys] = 255
|
||||
blobs = blob_dog(
|
||||
img,
|
||||
min_sigma=1.5,
|
||||
max_sigma=5,
|
||||
sigma_ratio=1.2,
|
||||
)
|
||||
assert blobs.shape[0] == 1, "one blob should have been detected"
|
||||
b = blobs[0]
|
||||
assert b[0] == disc_center[0], f"blob should be {disc_center[0]} px from x border"
|
||||
assert b[1] == disc_center[1], f"blob should be {disc_center[1]} px from y border"
|
||||
|
||||
blobs = blob_dog(
|
||||
img,
|
||||
min_sigma=1.5,
|
||||
max_sigma=5,
|
||||
sigma_ratio=1.2,
|
||||
exclude_border=exclude_border,
|
||||
)
|
||||
|
||||
if disc_center == (5, 20) and exclude_border == (4, 15):
|
||||
assert blobs.shape[0] == 1, "one blob should have been detected"
|
||||
b = blobs[0]
|
||||
assert (
|
||||
b[0] == disc_center[0]
|
||||
), f"blob should be {disc_center[0]} px from x border"
|
||||
assert (
|
||||
b[1] == disc_center[1]
|
||||
), f"blob should be {disc_center[1]} px from y border"
|
||||
else:
|
||||
msg = "zero blobs should be detected, as only blob is 5 px from border"
|
||||
assert blobs.shape[0] == 0, msg
|
||||
|
||||
|
||||
@pytest.mark.parametrize('anisotropic', [False, True])
|
||||
@pytest.mark.parametrize('ndim', [1, 2, 3, 4])
|
||||
@pytest.mark.parametrize('function_name', ['blob_dog', 'blob_log'])
|
||||
def test_nd_blob_no_peaks_shape(function_name, ndim, anisotropic):
|
||||
# uniform image so no blobs will be found
|
||||
z = np.zeros((16,) * ndim, dtype=np.float32)
|
||||
if anisotropic:
|
||||
max_sigma = 8 + np.arange(ndim)
|
||||
else:
|
||||
max_sigma = 8
|
||||
blob_func = getattr(feature, function_name)
|
||||
blobs = blob_func(z, max_sigma=max_sigma)
|
||||
# z.ndim + (z.ndim sigmas if anisotropic, only one sigma otherwise)
|
||||
expected_shape = 2 * z.ndim if anisotropic else z.ndim + 1
|
||||
assert blobs.shape == (0, expected_shape)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.uint8, np.float16, np.float32, np.float64])
|
||||
@pytest.mark.parametrize('threshold_type', ['absolute', 'relative'])
|
||||
def test_blob_log(dtype, threshold_type):
|
||||
r2 = math.sqrt(2)
|
||||
img = np.ones((256, 256), dtype=dtype)
|
||||
|
||||
xs, ys = disk((200, 65), 5)
|
||||
img[xs, ys] = 255
|
||||
|
||||
xs, ys = disk((80, 25), 15)
|
||||
img[xs, ys] = 255
|
||||
|
||||
xs, ys = disk((50, 150), 25)
|
||||
img[xs, ys] = 255
|
||||
|
||||
xs, ys = disk((100, 175), 30)
|
||||
img[xs, ys] = 255
|
||||
|
||||
if threshold_type == 'absolute':
|
||||
threshold = 1
|
||||
if img.dtype.kind != 'f':
|
||||
# account for internal scaling to [0, 1] by img_as_float
|
||||
threshold /= np.ptp(img)
|
||||
threshold_rel = None
|
||||
elif threshold_type == 'relative':
|
||||
threshold = None
|
||||
threshold_rel = 0.5
|
||||
|
||||
blobs = blob_log(
|
||||
img, min_sigma=5, max_sigma=20, threshold=threshold, threshold_rel=threshold_rel
|
||||
)
|
||||
|
||||
def radius(x):
|
||||
return r2 * x[2]
|
||||
|
||||
s = sorted(blobs, key=radius)
|
||||
thresh = 3
|
||||
|
||||
b = s[0]
|
||||
assert abs(b[0] - 200) <= thresh
|
||||
assert abs(b[1] - 65) <= thresh
|
||||
assert abs(radius(b) - 5) <= thresh
|
||||
|
||||
b = s[1]
|
||||
assert abs(b[0] - 80) <= thresh
|
||||
assert abs(b[1] - 25) <= thresh
|
||||
assert abs(radius(b) - 15) <= thresh
|
||||
|
||||
b = s[2]
|
||||
assert abs(b[0] - 50) <= thresh
|
||||
assert abs(b[1] - 150) <= thresh
|
||||
assert abs(radius(b) - 25) <= thresh
|
||||
|
||||
b = s[3]
|
||||
assert abs(b[0] - 100) <= thresh
|
||||
assert abs(b[1] - 175) <= thresh
|
||||
assert abs(radius(b) - 30) <= thresh
|
||||
|
||||
# Testing log scale
|
||||
blobs = blob_log(
|
||||
img,
|
||||
min_sigma=5,
|
||||
max_sigma=20,
|
||||
threshold=threshold,
|
||||
threshold_rel=threshold_rel,
|
||||
log_scale=True,
|
||||
)
|
||||
|
||||
b = s[0]
|
||||
assert abs(b[0] - 200) <= thresh
|
||||
assert abs(b[1] - 65) <= thresh
|
||||
assert abs(radius(b) - 5) <= thresh
|
||||
|
||||
b = s[1]
|
||||
assert abs(b[0] - 80) <= thresh
|
||||
assert abs(b[1] - 25) <= thresh
|
||||
assert abs(radius(b) - 15) <= thresh
|
||||
|
||||
b = s[2]
|
||||
assert abs(b[0] - 50) <= thresh
|
||||
assert abs(b[1] - 150) <= thresh
|
||||
assert abs(radius(b) - 25) <= thresh
|
||||
|
||||
b = s[3]
|
||||
assert abs(b[0] - 100) <= thresh
|
||||
assert abs(b[1] - 175) <= thresh
|
||||
assert abs(radius(b) - 30) <= thresh
|
||||
|
||||
# Testing no peaks
|
||||
img_empty = np.zeros((100, 100))
|
||||
assert blob_log(img_empty).size == 0
|
||||
|
||||
|
||||
def test_blob_log_no_warnings():
|
||||
img = np.ones((11, 11))
|
||||
|
||||
xs, ys = disk((5, 5), 2)
|
||||
img[xs, ys] = 255
|
||||
|
||||
xs, ys = disk((7, 6), 2)
|
||||
img[xs, ys] = 255
|
||||
|
||||
blob_log(img, max_sigma=20, num_sigma=10, threshold=0.1)
|
||||
|
||||
|
||||
def test_blob_log_3d():
|
||||
# Testing 3D
|
||||
r = 6
|
||||
pad = 10
|
||||
im3 = ellipsoid(r, r, r)
|
||||
im3 = np.pad(im3, pad, mode='constant')
|
||||
|
||||
blobs = blob_log(im3, min_sigma=3, max_sigma=10)
|
||||
b = blobs[0]
|
||||
|
||||
assert b.shape == (4,)
|
||||
assert b[0] == r + pad + 1
|
||||
assert b[1] == r + pad + 1
|
||||
assert b[2] == r + pad + 1
|
||||
assert abs(math.sqrt(3) * b[3] - r) < 1
|
||||
|
||||
|
||||
def test_blob_log_3d_anisotropic():
|
||||
# Testing 3D anisotropic
|
||||
r = 6
|
||||
pad = 10
|
||||
im3 = ellipsoid(r / 2, r, r)
|
||||
im3 = np.pad(im3, pad, mode='constant')
|
||||
|
||||
blobs = blob_log(
|
||||
im3,
|
||||
min_sigma=[1, 2, 2],
|
||||
max_sigma=[5, 10, 10],
|
||||
)
|
||||
|
||||
b = blobs[0]
|
||||
assert b.shape == (6,)
|
||||
assert b[0] == r / 2 + pad + 1
|
||||
assert b[1] == r + pad + 1
|
||||
assert b[2] == r + pad + 1
|
||||
assert abs(math.sqrt(3) * b[3] - r / 2) < 1
|
||||
assert abs(math.sqrt(3) * b[4] - r) < 1
|
||||
assert abs(math.sqrt(3) * b[5] - r) < 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("disc_center", [(5, 5), (5, 20)])
|
||||
@pytest.mark.parametrize("exclude_border", [6, (6, 6), (4, 15)])
|
||||
def test_blob_log_exclude_border(disc_center, exclude_border):
|
||||
# image where blob is disc_center px from borders, radius 5
|
||||
img = np.ones((512, 512))
|
||||
xs, ys = disk(disc_center, 5)
|
||||
img[xs, ys] = 255
|
||||
|
||||
blobs = blob_log(
|
||||
img,
|
||||
min_sigma=1.5,
|
||||
max_sigma=5,
|
||||
)
|
||||
assert blobs.shape[0] == 1
|
||||
b = blobs[0]
|
||||
assert b[0] == disc_center[0], f"blob should be {disc_center[0]} px from x border"
|
||||
assert b[1] == disc_center[1], f"blob should be {disc_center[1]} px from y border"
|
||||
|
||||
blobs = blob_log(
|
||||
img,
|
||||
min_sigma=1.5,
|
||||
max_sigma=5,
|
||||
exclude_border=exclude_border,
|
||||
)
|
||||
|
||||
if disc_center == (5, 20) and exclude_border == (4, 15):
|
||||
assert blobs.shape[0] == 1, "one blob should have been detected"
|
||||
b = blobs[0]
|
||||
assert (
|
||||
b[0] == disc_center[0]
|
||||
), f"blob should be {disc_center[0]} px from x border"
|
||||
assert (
|
||||
b[1] == disc_center[1]
|
||||
), f"blob should be {disc_center[1]} px from y border"
|
||||
else:
|
||||
msg = "zero blobs should be detected, as only blob is 5 px from border"
|
||||
assert blobs.shape[0] == 0, msg
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dtype", [np.uint8, np.float16, np.float32])
|
||||
@pytest.mark.parametrize('threshold_type', ['absolute', 'relative'])
|
||||
def test_blob_doh(dtype, threshold_type):
|
||||
img = np.ones((512, 512), dtype=dtype)
|
||||
|
||||
xs, ys = disk((400, 130), 20)
|
||||
img[xs, ys] = 255
|
||||
|
||||
xs, ys = disk((460, 50), 30)
|
||||
img[xs, ys] = 255
|
||||
|
||||
xs, ys = disk((100, 300), 40)
|
||||
img[xs, ys] = 255
|
||||
|
||||
xs, ys = disk((200, 350), 50)
|
||||
img[xs, ys] = 255
|
||||
|
||||
if threshold_type == 'absolute':
|
||||
# Note: have to either scale up threshold or rescale the image to the
|
||||
# range [0, 1] internally.
|
||||
threshold = 0.05
|
||||
if img.dtype.kind == 'f':
|
||||
# account for lack of internal scaling to [0, 1] by img_as_float
|
||||
ptp = np.ptp(img)
|
||||
threshold *= ptp**2
|
||||
threshold_rel = None
|
||||
elif threshold_type == 'relative':
|
||||
threshold = None
|
||||
threshold_rel = 0.5
|
||||
|
||||
blobs = blob_doh(
|
||||
img,
|
||||
min_sigma=1,
|
||||
max_sigma=60,
|
||||
num_sigma=10,
|
||||
threshold=threshold,
|
||||
threshold_rel=threshold_rel,
|
||||
)
|
||||
|
||||
def radius(x):
|
||||
return x[2]
|
||||
|
||||
s = sorted(blobs, key=radius)
|
||||
thresh = 4
|
||||
|
||||
b = s[0]
|
||||
assert abs(b[0] - 400) <= thresh
|
||||
assert abs(b[1] - 130) <= thresh
|
||||
assert abs(radius(b) - 20) <= thresh
|
||||
|
||||
b = s[1]
|
||||
assert abs(b[0] - 460) <= thresh
|
||||
assert abs(b[1] - 50) <= thresh
|
||||
assert abs(radius(b) - 30) <= thresh
|
||||
|
||||
b = s[2]
|
||||
assert abs(b[0] - 100) <= thresh
|
||||
assert abs(b[1] - 300) <= thresh
|
||||
assert abs(radius(b) - 40) <= thresh
|
||||
|
||||
b = s[3]
|
||||
assert abs(b[0] - 200) <= thresh
|
||||
assert abs(b[1] - 350) <= thresh
|
||||
assert abs(radius(b) - 50) <= thresh
|
||||
|
||||
|
||||
def test_blob_doh_log_scale():
|
||||
img = np.ones((512, 512), dtype=np.uint8)
|
||||
|
||||
xs, ys = disk((400, 130), 20)
|
||||
img[xs, ys] = 255
|
||||
|
||||
xs, ys = disk((460, 50), 30)
|
||||
img[xs, ys] = 255
|
||||
|
||||
xs, ys = disk((100, 300), 40)
|
||||
img[xs, ys] = 255
|
||||
|
||||
xs, ys = disk((200, 350), 50)
|
||||
img[xs, ys] = 255
|
||||
|
||||
blobs = blob_doh(
|
||||
img, min_sigma=1, max_sigma=60, num_sigma=10, log_scale=True, threshold=0.05
|
||||
)
|
||||
|
||||
def radius(x):
|
||||
return x[2]
|
||||
|
||||
s = sorted(blobs, key=radius)
|
||||
thresh = 10
|
||||
|
||||
b = s[0]
|
||||
assert abs(b[0] - 400) <= thresh
|
||||
assert abs(b[1] - 130) <= thresh
|
||||
assert abs(radius(b) - 20) <= thresh
|
||||
|
||||
b = s[2]
|
||||
assert abs(b[0] - 460) <= thresh
|
||||
assert abs(b[1] - 50) <= thresh
|
||||
assert abs(radius(b) - 30) <= thresh
|
||||
|
||||
b = s[1]
|
||||
assert abs(b[0] - 100) <= thresh
|
||||
assert abs(b[1] - 300) <= thresh
|
||||
assert abs(radius(b) - 40) <= thresh
|
||||
|
||||
b = s[3]
|
||||
assert abs(b[0] - 200) <= thresh
|
||||
assert abs(b[1] - 350) <= thresh
|
||||
assert abs(radius(b) - 50) <= thresh
|
||||
|
||||
|
||||
def test_blob_doh_no_peaks():
|
||||
# Testing no peaks
|
||||
img_empty = np.zeros((100, 100))
|
||||
assert blob_doh(img_empty).size == 0
|
||||
|
||||
|
||||
def test_blob_doh_overlap():
|
||||
img = np.ones((256, 256), dtype=np.uint8)
|
||||
|
||||
xs, ys = disk((100, 100), 20)
|
||||
img[xs, ys] = 255
|
||||
|
||||
xs, ys = disk((120, 100), 30)
|
||||
img[xs, ys] = 255
|
||||
|
||||
blobs = blob_doh(img, min_sigma=1, max_sigma=60, num_sigma=10, threshold=0.05)
|
||||
|
||||
assert len(blobs) == 1
|
||||
|
||||
|
||||
def test_blob_log_overlap_3d():
|
||||
r1, r2 = 7, 6
|
||||
pad1, pad2 = 11, 12
|
||||
blob1 = ellipsoid(r1, r1, r1)
|
||||
blob1 = np.pad(blob1, pad1, mode='constant')
|
||||
blob2 = ellipsoid(r2, r2, r2)
|
||||
blob2 = np.pad(
|
||||
blob2, [(pad2, pad2), (pad2 - 9, pad2 + 9), (pad2, pad2)], mode='constant'
|
||||
)
|
||||
im3 = np.logical_or(blob1, blob2)
|
||||
|
||||
blobs = blob_log(im3, min_sigma=2, max_sigma=10, overlap=0.1)
|
||||
assert len(blobs) == 1
|
||||
|
||||
|
||||
def test_blob_overlap_3d_anisotropic():
|
||||
# Two spheres with distance between centers equal to radius
|
||||
# One sphere is much smaller than the other so about half of it is within
|
||||
# the bigger sphere.
|
||||
s3 = math.sqrt(3)
|
||||
overlap = _blob_overlap(
|
||||
np.array([0, 0, 0, 2 / s3, 10 / s3, 10 / s3]),
|
||||
np.array([0, 0, 10, 0.2 / s3, 1 / s3, 1 / s3]),
|
||||
sigma_dim=3,
|
||||
)
|
||||
assert_almost_equal(overlap, 0.48125)
|
||||
overlap = _blob_overlap(
|
||||
np.array([0, 0, 0, 2 / s3, 10 / s3, 10 / s3]),
|
||||
np.array([2, 0, 0, 0.2 / s3, 1 / s3, 1 / s3]),
|
||||
sigma_dim=3,
|
||||
)
|
||||
assert_almost_equal(overlap, 0.48125)
|
||||
|
||||
|
||||
def test_blob_log_anisotropic():
|
||||
image = np.zeros((50, 50))
|
||||
image[20, 10:20] = 1
|
||||
isotropic_blobs = blob_log(image, min_sigma=0.5, max_sigma=2, num_sigma=3)
|
||||
assert len(isotropic_blobs) > 1 # many small blobs found in line
|
||||
ani_blobs = blob_log(
|
||||
image, min_sigma=[0.5, 5], max_sigma=[2, 20], num_sigma=3
|
||||
) # 10x anisotropy, line is 1x10
|
||||
assert len(ani_blobs) == 1 # single anisotropic blob found
|
||||
|
||||
|
||||
def test_blob_log_overlap_3d_anisotropic():
|
||||
r1, r2 = 7, 6
|
||||
pad1, pad2 = 11, 12
|
||||
blob1 = ellipsoid(r1, r1, r1)
|
||||
blob1 = np.pad(blob1, pad1, mode='constant')
|
||||
blob2 = ellipsoid(r2, r2, r2)
|
||||
blob2 = np.pad(
|
||||
blob2, [(pad2, pad2), (pad2 - 9, pad2 + 9), (pad2, pad2)], mode='constant'
|
||||
)
|
||||
im3 = np.logical_or(blob1, blob2)
|
||||
|
||||
blobs = blob_log(im3, min_sigma=[2, 2.01, 2.005], max_sigma=10, overlap=0.1)
|
||||
assert len(blobs) == 1
|
||||
|
||||
# Two circles with distance between centers equal to radius
|
||||
overlap = _blob_overlap(
|
||||
np.array([0, 0, 10 / math.sqrt(2)]), np.array([0, 10, 10 / math.sqrt(2)])
|
||||
)
|
||||
assert_almost_equal(
|
||||
overlap, 1.0 / math.pi * (2 * math.acos(1.0 / 2) - math.sqrt(3) / 2.0)
|
||||
)
|
||||
|
||||
|
||||
def test_no_blob():
|
||||
im = np.zeros((10, 10))
|
||||
blobs = blob_log(im, min_sigma=2, max_sigma=5, num_sigma=4)
|
||||
assert len(blobs) == 0
|
||||
110
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_brief.py
vendored
Normal file
110
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_brief.py
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
import pytest
|
||||
import copy
|
||||
|
||||
import numpy as np
|
||||
|
||||
from skimage._shared.testing import assert_array_equal
|
||||
from skimage import data
|
||||
from skimage.feature import BRIEF, corner_peaks, corner_harris
|
||||
from skimage._shared import testing
|
||||
|
||||
|
||||
def test_color_image_unsupported_error():
|
||||
"""Brief descriptors can be evaluated on gray-scale images only."""
|
||||
img = np.zeros((20, 20, 3))
|
||||
keypoints = np.asarray([[7, 5], [11, 13]])
|
||||
with testing.raises(ValueError):
|
||||
BRIEF().extract(img, keypoints)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', ['float32', 'float64', 'uint8', 'int'])
|
||||
def test_normal_mode(dtype):
|
||||
"""Verify the computed BRIEF descriptors with expected for normal mode."""
|
||||
img = data.coins().astype(dtype)
|
||||
|
||||
keypoints = corner_peaks(
|
||||
corner_harris(img), min_distance=5, threshold_abs=0, threshold_rel=0.1
|
||||
)
|
||||
|
||||
extractor = BRIEF(descriptor_size=8, sigma=2)
|
||||
|
||||
extractor.extract(img, keypoints[:8])
|
||||
|
||||
expected = np.array(
|
||||
[
|
||||
[1, 1, 1, 0, 1, 1, 0, 1],
|
||||
[0, 1, 1, 0, 1, 1, 0, 0],
|
||||
[1, 1, 1, 0, 1, 1, 0, 1],
|
||||
[0, 0, 0, 1, 0, 0, 1, 0],
|
||||
[0, 1, 1, 0, 1, 1, 0, 0],
|
||||
[0, 1, 1, 0, 1, 1, 1, 0],
|
||||
[1, 1, 1, 0, 1, 1, 0, 1],
|
||||
[1, 0, 1, 0, 0, 1, 1, 0],
|
||||
],
|
||||
dtype=bool,
|
||||
)
|
||||
|
||||
assert_array_equal(extractor.descriptors, expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', ['float32', 'float64', 'uint8', 'int'])
|
||||
def test_uniform_mode(dtype):
|
||||
"""Verify the computed BRIEF descriptors with expected for uniform mode."""
|
||||
img = data.coins().astype(dtype)
|
||||
|
||||
keypoints = corner_peaks(
|
||||
corner_harris(img), min_distance=5, threshold_abs=0, threshold_rel=0.1
|
||||
)
|
||||
|
||||
extractor = BRIEF(descriptor_size=8, sigma=2, mode='uniform', rng=1)
|
||||
BRIEF(descriptor_size=8, sigma=2, mode='uniform', rng=1)
|
||||
|
||||
extractor.extract(img, keypoints[:8])
|
||||
|
||||
expected = np.array(
|
||||
[
|
||||
[0, 1, 0, 1, 0, 1, 1, 0],
|
||||
[0, 1, 0, 0, 0, 1, 0, 1],
|
||||
[0, 1, 0, 0, 0, 1, 1, 1],
|
||||
[1, 0, 1, 0, 1, 0, 1, 1],
|
||||
[0, 0, 1, 0, 0, 1, 0, 1],
|
||||
[0, 1, 0, 1, 0, 1, 0, 1],
|
||||
[0, 1, 0, 0, 0, 1, 1, 1],
|
||||
[1, 0, 1, 1, 1, 0, 0, 1],
|
||||
],
|
||||
dtype=bool,
|
||||
)
|
||||
|
||||
assert_array_equal(extractor.descriptors, expected)
|
||||
|
||||
|
||||
def test_unsupported_mode():
|
||||
with testing.raises(ValueError):
|
||||
BRIEF(mode='foobar')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', ['float32', 'float64', 'uint8', 'int'])
|
||||
def test_border(dtype):
|
||||
img = np.zeros((100, 100), dtype=dtype)
|
||||
keypoints = np.array([[1, 1], [20, 20], [50, 50], [80, 80]])
|
||||
|
||||
extractor = BRIEF(patch_size=41, rng=1)
|
||||
extractor.extract(img, keypoints)
|
||||
|
||||
assert extractor.descriptors.shape[0] == 3
|
||||
assert_array_equal(extractor.mask, (False, True, True, True))
|
||||
|
||||
|
||||
def test_independent_rng():
|
||||
img = np.zeros((100, 100), dtype=int)
|
||||
keypoints = np.array([[1, 1], [20, 20], [50, 50], [80, 80]])
|
||||
|
||||
rng = np.random.default_rng()
|
||||
extractor = BRIEF(patch_size=41, rng=rng)
|
||||
|
||||
x = copy.deepcopy(extractor.rng).random()
|
||||
rng.random()
|
||||
extractor.extract(img, keypoints)
|
||||
z = copy.deepcopy(extractor.rng).random()
|
||||
|
||||
assert x == z
|
||||
185
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_canny.py
vendored
Normal file
185
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_canny.py
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
import unittest
|
||||
import numpy as np
|
||||
import pytest
|
||||
from skimage._shared.testing import assert_equal
|
||||
from scipy.ndimage import binary_dilation, binary_erosion
|
||||
from skimage import data, feature
|
||||
from skimage.util import img_as_float
|
||||
|
||||
|
||||
class TestCanny(unittest.TestCase):
|
||||
def test_00_00_zeros(self):
|
||||
'''Test that the Canny filter finds no points for a blank field'''
|
||||
result = feature.canny(np.zeros((20, 20)), 4, 0, 0, np.ones((20, 20), bool))
|
||||
self.assertFalse(np.any(result))
|
||||
|
||||
def test_00_01_zeros_mask(self):
|
||||
'''Test that the Canny filter finds no points in a masked image'''
|
||||
result = feature.canny(
|
||||
np.random.uniform(size=(20, 20)), 4, 0, 0, np.zeros((20, 20), bool)
|
||||
)
|
||||
self.assertFalse(np.any(result))
|
||||
|
||||
def test_01_01_circle(self):
|
||||
'''Test that the Canny filter finds the outlines of a circle'''
|
||||
i, j = np.mgrid[-200:200, -200:200].astype(float) / 200
|
||||
c = np.abs(np.sqrt(i * i + j * j) - 0.5) < 0.02
|
||||
result = feature.canny(c.astype(float), 4, 0, 0, np.ones(c.shape, bool))
|
||||
#
|
||||
# erode and dilate the circle to get rings that should contain the
|
||||
# outlines
|
||||
#
|
||||
cd = binary_dilation(c, iterations=3)
|
||||
ce = binary_erosion(c, iterations=3)
|
||||
cde = np.logical_and(cd, np.logical_not(ce))
|
||||
self.assertTrue(np.all(cde[result]))
|
||||
#
|
||||
# The circle has a radius of 100. There are two rings here, one
|
||||
# for the inside edge and one for the outside. So that's
|
||||
# 100 * 2 * 2 * 3 for those places where pi is still 3.
|
||||
# The edge contains both pixels if there's a tie, so we
|
||||
# bump the count a little.
|
||||
point_count = np.sum(result)
|
||||
self.assertTrue(point_count > 1200)
|
||||
self.assertTrue(point_count < 1600)
|
||||
|
||||
def test_01_02_circle_with_noise(self):
|
||||
'''Test that the Canny filter finds the circle outlines
|
||||
in a noisy image'''
|
||||
np.random.seed(0)
|
||||
i, j = np.mgrid[-200:200, -200:200].astype(float) / 200
|
||||
c = np.abs(np.sqrt(i * i + j * j) - 0.5) < 0.02
|
||||
cf = c.astype(float) * 0.5 + np.random.uniform(size=c.shape) * 0.5
|
||||
result = feature.canny(cf, 4, 0.1, 0.2, np.ones(c.shape, bool))
|
||||
#
|
||||
# erode and dilate the circle to get rings that should contain the
|
||||
# outlines
|
||||
#
|
||||
cd = binary_dilation(c, iterations=4)
|
||||
ce = binary_erosion(c, iterations=4)
|
||||
cde = np.logical_and(cd, np.logical_not(ce))
|
||||
self.assertTrue(np.all(cde[result]))
|
||||
point_count = np.sum(result)
|
||||
self.assertTrue(point_count > 1200)
|
||||
self.assertTrue(point_count < 1600)
|
||||
|
||||
def test_image_shape(self):
|
||||
self.assertRaises(ValueError, feature.canny, np.zeros((20, 20, 20)), 4, 0, 0)
|
||||
|
||||
def test_mask_none(self):
|
||||
result1 = feature.canny(np.zeros((20, 20)), 4, 0, 0, np.ones((20, 20), bool))
|
||||
result2 = feature.canny(np.zeros((20, 20)), 4, 0, 0)
|
||||
self.assertTrue(np.all(result1 == result2))
|
||||
|
||||
def test_use_quantiles(self):
|
||||
image = img_as_float(data.camera()[::100, ::100])
|
||||
|
||||
# Correct output produced manually with quantiles
|
||||
# of 0.8 and 0.6 for high and low respectively
|
||||
correct_output = np.array(
|
||||
[
|
||||
[False, False, False, False, False, False],
|
||||
[False, True, True, True, False, False],
|
||||
[False, False, False, True, False, False],
|
||||
[False, False, False, True, False, False],
|
||||
[False, False, True, True, False, False],
|
||||
[False, False, False, False, False, False],
|
||||
]
|
||||
)
|
||||
|
||||
result = feature.canny(
|
||||
image, low_threshold=0.6, high_threshold=0.8, use_quantiles=True
|
||||
)
|
||||
|
||||
assert_equal(result, correct_output)
|
||||
|
||||
def test_img_all_ones(self):
|
||||
image = np.ones((10, 10))
|
||||
assert np.all(feature.canny(image) == 0)
|
||||
|
||||
def test_invalid_use_quantiles(self):
|
||||
image = img_as_float(data.camera()[::50, ::50])
|
||||
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
feature.canny,
|
||||
image,
|
||||
use_quantiles=True,
|
||||
low_threshold=0.5,
|
||||
high_threshold=3.6,
|
||||
)
|
||||
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
feature.canny,
|
||||
image,
|
||||
use_quantiles=True,
|
||||
low_threshold=-5,
|
||||
high_threshold=0.5,
|
||||
)
|
||||
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
feature.canny,
|
||||
image,
|
||||
use_quantiles=True,
|
||||
low_threshold=99,
|
||||
high_threshold=0.9,
|
||||
)
|
||||
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
feature.canny,
|
||||
image,
|
||||
use_quantiles=True,
|
||||
low_threshold=0.5,
|
||||
high_threshold=-100,
|
||||
)
|
||||
|
||||
# Example from issue #4282
|
||||
image = data.camera()
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
feature.canny,
|
||||
image,
|
||||
use_quantiles=True,
|
||||
low_threshold=50,
|
||||
high_threshold=150,
|
||||
)
|
||||
|
||||
def test_dtype(self):
|
||||
"""Check that the same output is produced regardless of image dtype."""
|
||||
image_uint8 = data.camera()
|
||||
image_float = img_as_float(image_uint8)
|
||||
|
||||
result_uint8 = feature.canny(image_uint8)
|
||||
result_float = feature.canny(image_float)
|
||||
|
||||
assert_equal(result_uint8, result_float)
|
||||
|
||||
low = 0.1
|
||||
high = 0.2
|
||||
|
||||
assert_equal(
|
||||
feature.canny(image_float, 1.0, low, high),
|
||||
feature.canny(image_uint8, 1.0, 255 * low, 255 * high),
|
||||
)
|
||||
|
||||
def test_full_mask_matches_no_mask(self):
|
||||
"""The masked and unmasked algorithms should return the same result."""
|
||||
image = data.camera()
|
||||
|
||||
for mode in ('constant', 'nearest', 'reflect'):
|
||||
assert_equal(
|
||||
feature.canny(image, mode=mode),
|
||||
feature.canny(image, mode=mode, mask=np.ones_like(image, dtype=bool)),
|
||||
)
|
||||
|
||||
def test_unsupported_int64(self):
|
||||
for dtype in (np.int64, np.uint64):
|
||||
image = np.zeros((10, 10), dtype=dtype)
|
||||
image[3, 3] = np.iinfo(dtype).max
|
||||
with pytest.raises(
|
||||
ValueError, match="64-bit integer images are not supported"
|
||||
):
|
||||
feature.canny(image)
|
||||
18
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_cascade.py
vendored
Normal file
18
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_cascade.py
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import skimage.data as data
|
||||
from skimage.feature import Cascade
|
||||
|
||||
|
||||
def test_detector_astronaut():
|
||||
# Load the trained file from the module root.
|
||||
trained_file = data.lbp_frontal_face_cascade_filename()
|
||||
|
||||
# Initialize the detector cascade.
|
||||
detector = Cascade(trained_file)
|
||||
|
||||
img = data.astronaut()
|
||||
|
||||
detected = detector.detect_multi_scale(
|
||||
img=img, scale_factor=1.2, step_ratio=1, min_size=(60, 60), max_size=(123, 123)
|
||||
)
|
||||
|
||||
assert len(detected) == 1, 'One face should be detected.'
|
||||
95
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_censure.py
vendored
Normal file
95
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_censure.py
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
import numpy as np
|
||||
from skimage._shared.testing import assert_array_equal
|
||||
from skimage.data import moon
|
||||
from skimage.feature import CENSURE
|
||||
from skimage._shared.testing import run_in_parallel
|
||||
from skimage._shared import testing
|
||||
from skimage.transform import rescale
|
||||
|
||||
|
||||
img = moon()
|
||||
np.random.seed(0)
|
||||
|
||||
|
||||
def test_censure_on_rectangular_images():
|
||||
"""Censure feature detector should work on 2D image of any shape."""
|
||||
rect_image = np.random.rand(300, 200)
|
||||
square_image = np.random.rand(200, 200)
|
||||
CENSURE().detect(square_image)
|
||||
CENSURE().detect(rect_image)
|
||||
|
||||
|
||||
def test_keypoints_censure_color_image_unsupported_error():
|
||||
"""Censure keypoints can be extracted from gray-scale images only."""
|
||||
with testing.raises(ValueError):
|
||||
CENSURE().detect(np.zeros((20, 20, 3)))
|
||||
|
||||
|
||||
def test_keypoints_censure_mode_validity_error():
|
||||
"""Mode argument in keypoints_censure can be either DoB, Octagon or
|
||||
STAR."""
|
||||
with testing.raises(ValueError):
|
||||
CENSURE(mode='dummy')
|
||||
|
||||
|
||||
def test_keypoints_censure_scale_range_error():
|
||||
"""Difference between the the max_scale and min_scale parameters in
|
||||
keypoints_censure should be greater than or equal to two."""
|
||||
with testing.raises(ValueError):
|
||||
CENSURE(min_scale=1, max_scale=2)
|
||||
|
||||
|
||||
def test_keypoints_censure_moon_image_dob():
|
||||
"""Verify the actual Censure keypoints and their corresponding scale with
|
||||
the expected values for DoB filter."""
|
||||
detector = CENSURE()
|
||||
detector.detect(img)
|
||||
expected_keypoints = np.array(
|
||||
[
|
||||
[21, 497],
|
||||
[36, 46],
|
||||
[119, 350],
|
||||
[185, 177],
|
||||
[287, 250],
|
||||
[357, 239],
|
||||
[463, 116],
|
||||
[464, 132],
|
||||
[467, 260],
|
||||
]
|
||||
)
|
||||
expected_scales = np.array([3, 4, 4, 2, 2, 3, 2, 2, 2])
|
||||
|
||||
assert_array_equal(expected_keypoints, detector.keypoints)
|
||||
assert_array_equal(expected_scales, detector.scales)
|
||||
|
||||
|
||||
@run_in_parallel()
|
||||
def test_keypoints_censure_moon_image_octagon():
|
||||
"""Verify the actual Censure keypoints and their corresponding scale with
|
||||
the expected values for Octagon filter."""
|
||||
|
||||
detector = CENSURE(mode='octagon')
|
||||
# quarter scale image for speed
|
||||
detector.detect(rescale(img, 0.25, anti_aliasing=False, mode='constant'))
|
||||
expected_keypoints = np.array([[23, 27], [29, 89], [31, 87], [106, 59], [111, 67]])
|
||||
|
||||
expected_scales = np.array([3, 2, 5, 2, 4])
|
||||
|
||||
assert_array_equal(expected_keypoints, detector.keypoints)
|
||||
assert_array_equal(expected_scales, detector.scales)
|
||||
|
||||
|
||||
def test_keypoints_censure_moon_image_star():
|
||||
"""Verify the actual Censure keypoints and their corresponding scale with
|
||||
the expected values for STAR filter."""
|
||||
detector = CENSURE(mode='star')
|
||||
# quarter scale image for speed
|
||||
detector.detect(rescale(img, 0.25, anti_aliasing=False, mode='constant'))
|
||||
expected_keypoints = np.array(
|
||||
[[23, 27], [29, 89], [30, 86], [107, 59], [109, 64], [111, 67], [113, 70]]
|
||||
)
|
||||
|
||||
expected_scales = np.array([3, 2, 4, 2, 5, 3, 2])
|
||||
|
||||
assert_array_equal(expected_keypoints, detector.keypoints)
|
||||
assert_array_equal(expected_scales, detector.scales)
|
||||
818
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_corner.py
vendored
Normal file
818
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_corner.py
vendored
Normal file
@@ -0,0 +1,818 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy.testing import assert_almost_equal, assert_array_equal, assert_equal
|
||||
|
||||
from skimage import data, draw, img_as_float
|
||||
from skimage._shared._warnings import expected_warnings
|
||||
from skimage._shared.testing import run_in_parallel
|
||||
from skimage._shared.utils import _supported_float_type
|
||||
from skimage.color import rgb2gray
|
||||
from skimage.feature import (
|
||||
corner_fast,
|
||||
corner_foerstner,
|
||||
corner_harris,
|
||||
corner_kitchen_rosenfeld,
|
||||
corner_moravec,
|
||||
corner_orientations,
|
||||
corner_peaks,
|
||||
corner_shi_tomasi,
|
||||
corner_subpix,
|
||||
hessian_matrix,
|
||||
hessian_matrix_det,
|
||||
hessian_matrix_eigvals,
|
||||
peak_local_max,
|
||||
shape_index,
|
||||
structure_tensor,
|
||||
structure_tensor_eigenvalues,
|
||||
)
|
||||
from skimage.morphology import cube, octagon
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def im3d():
|
||||
r = 10
|
||||
pad = 10
|
||||
im3 = draw.ellipsoid(r, r, r)
|
||||
im3 = np.pad(im3, pad, mode='constant').astype(np.uint8)
|
||||
return im3
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_structure_tensor(dtype):
|
||||
square = np.zeros((5, 5), dtype=dtype)
|
||||
square[2, 2] = 1
|
||||
Arr, Arc, Acc = structure_tensor(square, sigma=0.1, order='rc')
|
||||
out_dtype = _supported_float_type(dtype)
|
||||
assert all(a.dtype == out_dtype for a in (Arr, Arc, Acc))
|
||||
assert_array_equal(
|
||||
Acc,
|
||||
np.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],
|
||||
]
|
||||
),
|
||||
)
|
||||
assert_array_equal(
|
||||
Arc,
|
||||
np.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],
|
||||
]
|
||||
),
|
||||
)
|
||||
assert_array_equal(
|
||||
Arr,
|
||||
np.array(
|
||||
[
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 1, 4, 1, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 1, 4, 1, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_structure_tensor_3d(dtype):
|
||||
cube = np.zeros((5, 5, 5), dtype=dtype)
|
||||
cube[2, 2, 2] = 1
|
||||
A_elems = structure_tensor(cube, sigma=0.1)
|
||||
assert all(a.dtype == _supported_float_type(dtype) for a in A_elems)
|
||||
assert_equal(len(A_elems), 6)
|
||||
assert_array_equal(
|
||||
A_elems[0][:, 1, :],
|
||||
np.array(
|
||||
[
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 1, 4, 1, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 1, 4, 1, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
]
|
||||
),
|
||||
)
|
||||
assert_array_equal(
|
||||
A_elems[0][1],
|
||||
np.array(
|
||||
[
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 1, 4, 1, 0],
|
||||
[0, 4, 16, 4, 0],
|
||||
[0, 1, 4, 1, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
]
|
||||
),
|
||||
)
|
||||
assert_array_equal(
|
||||
A_elems[3][2],
|
||||
np.array(
|
||||
[
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 4, 16, 4, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 4, 16, 4, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def test_structure_tensor_3d_rc_only():
|
||||
cube = np.zeros((5, 5, 5))
|
||||
with pytest.raises(ValueError):
|
||||
structure_tensor(cube, sigma=0.1, order='xy')
|
||||
A_elems_rc = structure_tensor(cube, sigma=0.1, order='rc')
|
||||
A_elems_none = structure_tensor(cube, sigma=0.1)
|
||||
assert_array_equal(A_elems_rc, A_elems_none)
|
||||
|
||||
|
||||
def test_structure_tensor_orders():
|
||||
square = np.zeros((5, 5))
|
||||
square[2, 2] = 1
|
||||
A_elems_default = structure_tensor(square, sigma=0.1)
|
||||
A_elems_xy = structure_tensor(square, sigma=0.1, order='xy')
|
||||
A_elems_rc = structure_tensor(square, sigma=0.1, order='rc')
|
||||
assert_array_equal(A_elems_rc, A_elems_default)
|
||||
assert_array_equal(A_elems_xy, A_elems_default[::-1])
|
||||
|
||||
|
||||
@pytest.mark.parametrize('ndim', [2, 3])
|
||||
def test_structure_tensor_sigma(ndim):
|
||||
img = np.zeros((5,) * ndim)
|
||||
img[[2] * ndim] = 1
|
||||
A_default = structure_tensor(img, sigma=0.1, order='rc')
|
||||
A_tuple = structure_tensor(img, sigma=(0.1,) * ndim, order='rc')
|
||||
A_list = structure_tensor(img, sigma=[0.1] * ndim, order='rc')
|
||||
assert_array_equal(A_tuple, A_default)
|
||||
assert_array_equal(A_list, A_default)
|
||||
with pytest.raises(ValueError):
|
||||
structure_tensor(img, sigma=(0.1,) * (ndim - 1), order='rc')
|
||||
with pytest.raises(ValueError):
|
||||
structure_tensor(img, sigma=[0.1] * (ndim + 1), order='rc')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_hessian_matrix(dtype):
|
||||
square = np.zeros((5, 5), dtype=dtype)
|
||||
square[2, 2] = 4
|
||||
Hrr, Hrc, Hcc = hessian_matrix(
|
||||
square, sigma=0.1, order='rc', use_gaussian_derivatives=False
|
||||
)
|
||||
|
||||
out_dtype = _supported_float_type(dtype)
|
||||
assert all(a.dtype == out_dtype for a in (Hrr, Hrc, Hcc))
|
||||
assert_almost_equal(
|
||||
Hrr,
|
||||
np.array(
|
||||
[
|
||||
[0, 0, 2, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, -2, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 2, 0, 0],
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
assert_almost_equal(
|
||||
Hrc,
|
||||
np.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],
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
assert_almost_equal(
|
||||
Hcc,
|
||||
np.array(
|
||||
[
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[2, 0, -2, 0, 2],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
with expected_warnings(["use_gaussian_derivatives currently defaults"]):
|
||||
# FutureWarning warning when use_gaussian_derivatives is not
|
||||
# specified.
|
||||
hessian_matrix(square, sigma=0.1, order='rc')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('use_gaussian_derivatives', [False, True])
|
||||
def test_hessian_matrix_order(use_gaussian_derivatives):
|
||||
square = np.zeros((5, 5), dtype=float)
|
||||
square[2, 2] = 4
|
||||
|
||||
Hxx, Hxy, Hyy = hessian_matrix(
|
||||
square, sigma=0.1, order="xy", use_gaussian_derivatives=use_gaussian_derivatives
|
||||
)
|
||||
|
||||
Hrr, Hrc, Hcc = hessian_matrix(
|
||||
square, sigma=0.1, order="rc", use_gaussian_derivatives=use_gaussian_derivatives
|
||||
)
|
||||
|
||||
# verify results are equivalent, just reversed in order
|
||||
assert_array_equal(Hxx, Hcc)
|
||||
assert_array_equal(Hxy, Hrc)
|
||||
assert_array_equal(Hyy, Hrr)
|
||||
|
||||
|
||||
def test_hessian_matrix_3d():
|
||||
cube = np.zeros((5, 5, 5))
|
||||
cube[2, 2, 2] = 4
|
||||
Hs = hessian_matrix(cube, sigma=0.1, order='rc', use_gaussian_derivatives=False)
|
||||
assert len(Hs) == 6, f"incorrect number of Hessian images ({len(Hs)}) for 3D"
|
||||
# This test didn't catch the fix in gh-6624 (passes with and without) ...
|
||||
assert_almost_equal(
|
||||
Hs[2][:, 2, :],
|
||||
np.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],
|
||||
]
|
||||
),
|
||||
)
|
||||
# ... so we add another test that fails for the not-fixed hessian_matrix
|
||||
assert_almost_equal(
|
||||
Hs[0][:, 2, :],
|
||||
np.array(
|
||||
[
|
||||
[0, 0, 2, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, -2, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 2, 0, 0],
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('use_gaussian_derivatives', [False, True])
|
||||
def test_hessian_matrix_3d_xy(use_gaussian_derivatives):
|
||||
img = np.ones((5, 5, 5))
|
||||
|
||||
# order="xy" is only permitted for 2D
|
||||
with pytest.raises(ValueError):
|
||||
hessian_matrix(
|
||||
img,
|
||||
sigma=0.1,
|
||||
order="xy",
|
||||
use_gaussian_derivatives=use_gaussian_derivatives,
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
hessian_matrix(
|
||||
img,
|
||||
sigma=0.1,
|
||||
order='nonexistant',
|
||||
use_gaussian_derivatives=use_gaussian_derivatives,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_structure_tensor_eigenvalues(dtype):
|
||||
square = np.zeros((5, 5), dtype=dtype)
|
||||
square[2, 2] = 1
|
||||
A_elems = structure_tensor(square, sigma=0.1, order='rc')
|
||||
l1, l2 = structure_tensor_eigenvalues(A_elems)
|
||||
out_dtype = _supported_float_type(dtype)
|
||||
assert all(a.dtype == out_dtype for a in (l1, l2))
|
||||
assert_array_equal(
|
||||
l1,
|
||||
np.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],
|
||||
]
|
||||
),
|
||||
)
|
||||
assert_array_equal(
|
||||
l2,
|
||||
np.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],
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def test_structure_tensor_eigenvalues_3d():
|
||||
image = np.pad(cube(9, dtype=np.int64), 5, mode='constant') * 1000
|
||||
boundary = (
|
||||
np.pad(cube(9), 5, mode='constant') - np.pad(cube(7), 6, mode='constant')
|
||||
).astype(bool)
|
||||
A_elems = structure_tensor(image, sigma=0.1)
|
||||
e0, e1, e2 = structure_tensor_eigenvalues(A_elems)
|
||||
# e0 should detect facets
|
||||
assert np.all(e0[boundary] != 0)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_hessian_matrix_eigvals(dtype):
|
||||
square = np.zeros((5, 5), dtype=dtype)
|
||||
square[2, 2] = 4
|
||||
H = hessian_matrix(square, sigma=0.1, order='rc', use_gaussian_derivatives=False)
|
||||
l1, l2 = hessian_matrix_eigvals(H)
|
||||
out_dtype = _supported_float_type(dtype)
|
||||
assert all(a.dtype == out_dtype for a in (l1, l2))
|
||||
assert_almost_equal(
|
||||
l1,
|
||||
np.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],
|
||||
]
|
||||
),
|
||||
)
|
||||
assert_almost_equal(
|
||||
l2,
|
||||
np.array(
|
||||
[
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, -1, 0, -1, 0],
|
||||
[0, 0, -2, 0, 0],
|
||||
[0, -1, 0, -1, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_hessian_matrix_eigvals_3d(im3d, dtype):
|
||||
im3d = im3d.astype(dtype, copy=False)
|
||||
H = hessian_matrix(im3d, use_gaussian_derivatives=False)
|
||||
E = hessian_matrix_eigvals(H)
|
||||
out_dtype = _supported_float_type(dtype)
|
||||
assert all(a.dtype == out_dtype for a in E)
|
||||
|
||||
# test descending order:
|
||||
e0, e1, e2 = E
|
||||
assert np.all(e0 >= e1) and np.all(e1 >= e2)
|
||||
|
||||
E0, E1, E2 = E[:, E.shape[1] // 2] # cross section
|
||||
row_center, col_center = np.array(E0.shape) // 2
|
||||
circles = [
|
||||
draw.circle_perimeter(row_center, col_center, radius, shape=E0.shape)
|
||||
for radius in range(1, E0.shape[1] // 2 - 1)
|
||||
]
|
||||
response0 = np.array([np.mean(E0[c]) for c in circles])
|
||||
response2 = np.array([np.mean(E2[c]) for c in circles])
|
||||
# eigenvalues are negative just inside the sphere, positive just outside
|
||||
assert np.argmin(response2) < np.argmax(response0)
|
||||
assert np.min(response2) < 0
|
||||
assert np.max(response0) > 0
|
||||
|
||||
|
||||
@run_in_parallel()
|
||||
def test_hessian_matrix_det():
|
||||
image = np.zeros((5, 5))
|
||||
image[2, 2] = 1
|
||||
det = hessian_matrix_det(image, 5)
|
||||
assert_almost_equal(det, 0, decimal=3)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_hessian_matrix_det_3d(im3d, dtype):
|
||||
im3d = im3d.astype(dtype, copy=False)
|
||||
D = hessian_matrix_det(im3d)
|
||||
assert D.dtype == _supported_float_type(dtype)
|
||||
D0 = D[D.shape[0] // 2]
|
||||
row_center, col_center = np.array(D0.shape) // 2
|
||||
# testing in 3D is hard. We test this by showing that you get the
|
||||
# expected flat-then-low-then-high 2nd derivative response in a circle
|
||||
# around the midplane of the sphere.
|
||||
circles = [
|
||||
draw.circle_perimeter(row_center, col_center, r, shape=D0.shape)
|
||||
for r in range(1, D0.shape[1] // 2 - 1)
|
||||
]
|
||||
response = np.array([np.mean(D0[c]) for c in circles])
|
||||
lowest = np.argmin(response)
|
||||
highest = np.argmax(response)
|
||||
assert lowest < highest
|
||||
assert response[lowest] < 0
|
||||
assert response[highest] > 0
|
||||
|
||||
|
||||
def test_shape_index():
|
||||
# software floating point arm doesn't raise a warning on divide by zero
|
||||
# https://github.com/scikit-image/scikit-image/issues/3335
|
||||
square = np.zeros((5, 5))
|
||||
square[2, 2] = 4
|
||||
with expected_warnings([r'divide by zero|\A\Z', r'invalid value|\A\Z']):
|
||||
s = shape_index(square, sigma=0.1)
|
||||
assert_almost_equal(
|
||||
s,
|
||||
np.array(
|
||||
[
|
||||
[np.nan, np.nan, -0.5, np.nan, np.nan],
|
||||
[np.nan, 0, np.nan, 0, np.nan],
|
||||
[-0.5, np.nan, -1, np.nan, -0.5],
|
||||
[np.nan, 0, np.nan, 0, np.nan],
|
||||
[np.nan, np.nan, -0.5, np.nan, np.nan],
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@run_in_parallel()
|
||||
def test_square_image():
|
||||
im = np.zeros((50, 50)).astype(float)
|
||||
im[:25, :25] = 1.0
|
||||
|
||||
# Moravec
|
||||
results = corner_moravec(im) > 0
|
||||
# interest points along edge
|
||||
assert np.count_nonzero(results) == 92
|
||||
|
||||
# Harris
|
||||
results = peak_local_max(
|
||||
corner_harris(im, method='k'), min_distance=10, threshold_rel=0
|
||||
)
|
||||
# interest at corner
|
||||
assert len(results) == 1
|
||||
|
||||
results = peak_local_max(
|
||||
corner_harris(im, method='eps'), min_distance=10, threshold_rel=0
|
||||
)
|
||||
# interest at corner
|
||||
assert len(results) == 1
|
||||
|
||||
# Shi-Tomasi
|
||||
results = peak_local_max(corner_shi_tomasi(im), min_distance=10, threshold_rel=0)
|
||||
# interest at corner
|
||||
assert len(results) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
@pytest.mark.parametrize(
|
||||
'func',
|
||||
[
|
||||
corner_moravec,
|
||||
corner_harris,
|
||||
corner_shi_tomasi,
|
||||
corner_kitchen_rosenfeld,
|
||||
],
|
||||
)
|
||||
def test_corner_dtype(dtype, func):
|
||||
im = np.zeros((50, 50), dtype=dtype)
|
||||
im[:25, :25] = 1.0
|
||||
out_dtype = _supported_float_type(dtype)
|
||||
corners = func(im)
|
||||
assert corners.dtype == out_dtype
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_corner_foerstner_dtype(dtype):
|
||||
im = np.zeros((50, 50), dtype=dtype)
|
||||
im[:25, :25] = 1.0
|
||||
out_dtype = _supported_float_type(dtype)
|
||||
assert all(arr.dtype == out_dtype for arr in corner_foerstner(im))
|
||||
|
||||
|
||||
def test_noisy_square_image():
|
||||
im = np.zeros((50, 50)).astype(float)
|
||||
im[:25, :25] = 1.0
|
||||
rng = np.random.default_rng(1234)
|
||||
im = im + rng.uniform(size=im.shape) * 0.2
|
||||
|
||||
# Moravec
|
||||
results = peak_local_max(corner_moravec(im), min_distance=10, threshold_rel=0)
|
||||
# undefined number of interest points
|
||||
assert results.any()
|
||||
|
||||
# Harris
|
||||
results = peak_local_max(
|
||||
corner_harris(im, method='k'), min_distance=10, threshold_rel=0
|
||||
)
|
||||
assert len(results) == 1
|
||||
results = peak_local_max(
|
||||
corner_harris(im, method='eps'), min_distance=10, threshold_rel=0
|
||||
)
|
||||
assert len(results) == 1
|
||||
|
||||
# Shi-Tomasi
|
||||
results = peak_local_max(
|
||||
corner_shi_tomasi(im, sigma=1.5), min_distance=10, threshold_rel=0
|
||||
)
|
||||
assert len(results) == 1
|
||||
|
||||
|
||||
def test_squared_dot():
|
||||
im = np.zeros((50, 50))
|
||||
im[4:8, 4:8] = 1
|
||||
im = img_as_float(im)
|
||||
|
||||
# Moravec fails
|
||||
|
||||
# Harris
|
||||
results = peak_local_max(corner_harris(im), min_distance=10, threshold_rel=0)
|
||||
assert (results == np.array([[6, 6]])).all()
|
||||
|
||||
# Shi-Tomasi
|
||||
results = peak_local_max(corner_shi_tomasi(im), min_distance=10, threshold_rel=0)
|
||||
assert (results == np.array([[6, 6]])).all()
|
||||
|
||||
|
||||
def test_rotated_img():
|
||||
"""
|
||||
The harris filter should yield the same results with an image and it's
|
||||
rotation.
|
||||
"""
|
||||
im = img_as_float(data.astronaut().mean(axis=2))
|
||||
im_rotated = im.T
|
||||
|
||||
# Moravec
|
||||
results = np.nonzero(corner_moravec(im))
|
||||
results_rotated = np.nonzero(corner_moravec(im_rotated))
|
||||
assert (np.sort(results[0]) == np.sort(results_rotated[1])).all()
|
||||
assert (np.sort(results[1]) == np.sort(results_rotated[0])).all()
|
||||
|
||||
# Harris
|
||||
results = np.nonzero(corner_harris(im))
|
||||
results_rotated = np.nonzero(corner_harris(im_rotated))
|
||||
assert (np.sort(results[0]) == np.sort(results_rotated[1])).all()
|
||||
assert (np.sort(results[1]) == np.sort(results_rotated[0])).all()
|
||||
|
||||
# Shi-Tomasi
|
||||
results = np.nonzero(corner_shi_tomasi(im))
|
||||
results_rotated = np.nonzero(corner_shi_tomasi(im_rotated))
|
||||
assert (np.sort(results[0]) == np.sort(results_rotated[1])).all()
|
||||
assert (np.sort(results[1]) == np.sort(results_rotated[0])).all()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_subpix_edge(dtype):
|
||||
img = np.zeros((50, 50), dtype=dtype)
|
||||
img[:25, :25] = 255
|
||||
img[25:, 25:] = 255
|
||||
corner = peak_local_max(
|
||||
corner_harris(img), min_distance=10, threshold_rel=0, num_peaks=1
|
||||
)
|
||||
subpix = corner_subpix(img, corner)
|
||||
assert subpix.dtype == _supported_float_type(dtype)
|
||||
assert_array_equal(subpix[0], (24.5, 24.5))
|
||||
|
||||
|
||||
def test_subpix_dot():
|
||||
img = np.zeros((50, 50))
|
||||
img[25, 25] = 255
|
||||
corner = peak_local_max(
|
||||
corner_harris(img), min_distance=10, threshold_rel=0, num_peaks=1
|
||||
)
|
||||
subpix = corner_subpix(img, corner)
|
||||
assert_array_equal(subpix[0], (25, 25))
|
||||
|
||||
|
||||
def test_subpix_no_class():
|
||||
img = np.zeros((50, 50))
|
||||
subpix = corner_subpix(img, np.array([[25, 25]]))
|
||||
assert_array_equal(subpix[0], (np.nan, np.nan))
|
||||
|
||||
img[25, 25] = 1e-10
|
||||
corner = peak_local_max(
|
||||
corner_harris(img), min_distance=10, threshold_rel=0, num_peaks=1
|
||||
)
|
||||
subpix = corner_subpix(img, corner)
|
||||
assert_array_equal(subpix[0], (np.nan, np.nan))
|
||||
|
||||
|
||||
def test_subpix_border():
|
||||
img = np.zeros((50, 50))
|
||||
img[1:25, 1:25] = 255
|
||||
img[25:-1, 25:-1] = 255
|
||||
corner = corner_peaks(corner_harris(img), threshold_rel=0)
|
||||
subpix = corner_subpix(img, corner, window_size=11)
|
||||
ref = np.array(
|
||||
[
|
||||
[24.5, 24.5],
|
||||
[0.52040816, 0.52040816],
|
||||
[0.52040816, 24.47959184],
|
||||
[24.47959184, 0.52040816],
|
||||
[24.52040816, 48.47959184],
|
||||
[48.47959184, 24.52040816],
|
||||
[48.47959184, 48.47959184],
|
||||
]
|
||||
)
|
||||
|
||||
assert_almost_equal(subpix, ref)
|
||||
|
||||
|
||||
def test_num_peaks():
|
||||
"""For a bunch of different values of num_peaks, check that
|
||||
peak_local_max returns exactly the right amount of peaks. Test
|
||||
is run on the astronaut image in order to produce a sufficient number of
|
||||
corners.
|
||||
"""
|
||||
|
||||
img_corners = corner_harris(rgb2gray(data.astronaut()))
|
||||
|
||||
for i in range(20):
|
||||
n = np.random.randint(1, 21)
|
||||
results = peak_local_max(
|
||||
img_corners, min_distance=10, threshold_rel=0, num_peaks=n
|
||||
)
|
||||
assert results.shape[0] == n
|
||||
|
||||
|
||||
def test_corner_peaks():
|
||||
response = np.zeros((10, 10))
|
||||
response[2:5, 2:5] = 1
|
||||
response[8:10, 0:2] = 1
|
||||
|
||||
corners = corner_peaks(
|
||||
response, exclude_border=False, min_distance=10, threshold_rel=0
|
||||
)
|
||||
assert corners.shape == (1, 2)
|
||||
|
||||
corners = corner_peaks(
|
||||
response, exclude_border=False, min_distance=5, threshold_rel=0
|
||||
)
|
||||
assert corners.shape == (2, 2)
|
||||
|
||||
corners = corner_peaks(response, exclude_border=False, min_distance=1)
|
||||
assert corners.shape == (5, 2)
|
||||
|
||||
corners = corner_peaks(
|
||||
response, exclude_border=False, min_distance=1, indices=False
|
||||
)
|
||||
assert np.sum(corners) == 5
|
||||
|
||||
|
||||
def test_blank_image_nans():
|
||||
"""Some of the corner detectors had a weakness in terms of returning
|
||||
NaN when presented with regions of constant intensity. This should
|
||||
be fixed by now. We test whether each detector returns something
|
||||
finite in the case of constant input"""
|
||||
|
||||
detectors = [
|
||||
corner_moravec,
|
||||
corner_harris,
|
||||
corner_shi_tomasi,
|
||||
corner_kitchen_rosenfeld,
|
||||
corner_foerstner,
|
||||
]
|
||||
constant_image = np.zeros((20, 20))
|
||||
|
||||
for det in detectors:
|
||||
response = det(constant_image)
|
||||
assert np.all(np.isfinite(response))
|
||||
|
||||
|
||||
def test_corner_fast_image_unsupported_error():
|
||||
img = np.zeros((20, 20, 3))
|
||||
with pytest.raises(ValueError):
|
||||
corner_fast(img)
|
||||
|
||||
|
||||
@run_in_parallel()
|
||||
def test_corner_fast_astronaut():
|
||||
img = rgb2gray(data.astronaut())
|
||||
expected = np.array(
|
||||
[
|
||||
[444, 310],
|
||||
[374, 171],
|
||||
[249, 171],
|
||||
[492, 139],
|
||||
[403, 162],
|
||||
[496, 266],
|
||||
[362, 328],
|
||||
[476, 250],
|
||||
[353, 172],
|
||||
[346, 279],
|
||||
[494, 169],
|
||||
[177, 156],
|
||||
[413, 181],
|
||||
[213, 117],
|
||||
[390, 149],
|
||||
[140, 205],
|
||||
[232, 266],
|
||||
[489, 155],
|
||||
[387, 195],
|
||||
[101, 198],
|
||||
[363, 192],
|
||||
[364, 147],
|
||||
[300, 244],
|
||||
[325, 245],
|
||||
[141, 242],
|
||||
[401, 197],
|
||||
[197, 148],
|
||||
[339, 242],
|
||||
[188, 113],
|
||||
[362, 252],
|
||||
[379, 183],
|
||||
[358, 307],
|
||||
[245, 137],
|
||||
[369, 159],
|
||||
[464, 251],
|
||||
[305, 57],
|
||||
[223, 375],
|
||||
]
|
||||
)
|
||||
actual = corner_peaks(corner_fast(img, 12, 0.3), min_distance=10, threshold_rel=0)
|
||||
assert_array_equal(actual, expected)
|
||||
|
||||
|
||||
def test_corner_orientations_image_unsupported_error():
|
||||
img = np.zeros((20, 20, 3))
|
||||
with pytest.raises(ValueError):
|
||||
corner_orientations(img, np.asarray([[7, 7]]), np.ones((3, 3)))
|
||||
|
||||
|
||||
def test_corner_orientations_even_shape_error():
|
||||
img = np.zeros((20, 20))
|
||||
with pytest.raises(ValueError):
|
||||
corner_orientations(img, np.asarray([[7, 7]]), np.ones((4, 4)))
|
||||
|
||||
|
||||
@run_in_parallel()
|
||||
def test_corner_orientations_astronaut():
|
||||
img = rgb2gray(data.astronaut())
|
||||
corners = corner_peaks(
|
||||
corner_fast(img, 11, 0.35), min_distance=10, threshold_abs=0, threshold_rel=0.1
|
||||
)
|
||||
expected = np.array(
|
||||
[
|
||||
-4.40598471e-01,
|
||||
-1.46554357e00,
|
||||
2.39291733e00,
|
||||
-1.63869275e00,
|
||||
1.45931342e00,
|
||||
-1.64397304e00,
|
||||
-1.76069982e00,
|
||||
1.09650167e00,
|
||||
-1.65449964e00,
|
||||
1.19134149e00,
|
||||
5.46905279e-02,
|
||||
2.17103132e00,
|
||||
8.12701702e-01,
|
||||
-1.22091334e-01,
|
||||
-2.01162417e00,
|
||||
1.25854853e00,
|
||||
3.05330950e00,
|
||||
2.01197383e00,
|
||||
1.07812134e00,
|
||||
3.09780364e00,
|
||||
-3.49561988e-01,
|
||||
2.43573659e00,
|
||||
3.14918803e-01,
|
||||
-9.88548213e-01,
|
||||
-1.88247204e-01,
|
||||
2.47305654e00,
|
||||
-2.99143370e00,
|
||||
1.47154532e00,
|
||||
-6.61151410e-01,
|
||||
-1.68885773e00,
|
||||
-3.09279990e-01,
|
||||
-2.81524886e00,
|
||||
-1.75220190e00,
|
||||
-1.69230287e00,
|
||||
-7.52950306e-04,
|
||||
]
|
||||
)
|
||||
|
||||
actual = corner_orientations(img, corners, octagon(3, 2))
|
||||
assert_almost_equal(actual, expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_corner_orientations_square(dtype):
|
||||
square = np.zeros((12, 12), dtype=dtype)
|
||||
square[3:9, 3:9] = 1
|
||||
corners = corner_peaks(corner_fast(square, 9), min_distance=1, threshold_rel=0)
|
||||
actual_orientations = corner_orientations(square, corners, octagon(3, 2))
|
||||
assert actual_orientations.dtype == _supported_float_type(dtype)
|
||||
actual_orientations_degrees = np.rad2deg(actual_orientations)
|
||||
expected_orientations_degree = np.array([45, 135, -45, -135])
|
||||
assert_array_equal(actual_orientations_degrees, expected_orientations_degree)
|
||||
103
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_daisy.py
vendored
Normal file
103
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_daisy.py
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy import sqrt, ceil
|
||||
from numpy.testing import assert_almost_equal
|
||||
|
||||
from skimage import data
|
||||
from skimage import img_as_float
|
||||
from skimage.feature import daisy
|
||||
|
||||
|
||||
def test_daisy_color_image_unsupported_error():
|
||||
img = np.zeros((20, 20, 3))
|
||||
with pytest.raises(ValueError):
|
||||
daisy(img)
|
||||
|
||||
|
||||
def test_daisy_desc_dims():
|
||||
img = img_as_float(data.astronaut()[:128, :128].mean(axis=2))
|
||||
rings = 2
|
||||
histograms = 4
|
||||
orientations = 3
|
||||
descs = daisy(img, rings=rings, histograms=histograms, orientations=orientations)
|
||||
assert descs.shape[2] == (rings * histograms + 1) * orientations
|
||||
|
||||
rings = 4
|
||||
histograms = 5
|
||||
orientations = 13
|
||||
descs = daisy(img, rings=rings, histograms=histograms, orientations=orientations)
|
||||
assert descs.shape[2] == (rings * histograms + 1) * orientations
|
||||
|
||||
|
||||
def test_descs_shape():
|
||||
img = img_as_float(data.astronaut()[:256, :256].mean(axis=2))
|
||||
radius = 20
|
||||
step = 8
|
||||
descs = daisy(img, radius=radius, step=step)
|
||||
assert descs.shape[0] == ceil((img.shape[0] - radius * 2) / float(step))
|
||||
assert descs.shape[1] == ceil((img.shape[1] - radius * 2) / float(step))
|
||||
|
||||
img = img[:-1, :-2]
|
||||
radius = 5
|
||||
step = 3
|
||||
descs = daisy(img, radius=radius, step=step)
|
||||
assert descs.shape[0] == ceil((img.shape[0] - radius * 2) / float(step))
|
||||
assert descs.shape[1] == ceil((img.shape[1] - radius * 2) / float(step))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float32, np.float64])
|
||||
def test_daisy_sigmas_and_radii(dtype):
|
||||
img = data.astronaut()[:64, :64].mean(axis=2).astype(dtype, copy=False)
|
||||
sigmas = [1, 2, 3]
|
||||
radii = [1, 2]
|
||||
descs = daisy(img, sigmas=sigmas, ring_radii=radii)
|
||||
assert descs.dtype == img.dtype
|
||||
|
||||
|
||||
def test_daisy_incompatible_sigmas_and_radii():
|
||||
img = img_as_float(data.astronaut()[:64, :64].mean(axis=2))
|
||||
sigmas = [1, 2]
|
||||
radii = [1, 2]
|
||||
with pytest.raises(ValueError):
|
||||
daisy(img, sigmas=sigmas, ring_radii=radii)
|
||||
|
||||
|
||||
def test_daisy_normalization():
|
||||
img = img_as_float(data.astronaut()[:64, :64].mean(axis=2))
|
||||
|
||||
descs = daisy(img, normalization='l1')
|
||||
for i in range(descs.shape[0]):
|
||||
for j in range(descs.shape[1]):
|
||||
assert_almost_equal(np.sum(descs[i, j, :]), 1)
|
||||
descs_ = daisy(img)
|
||||
assert_almost_equal(descs, descs_)
|
||||
|
||||
descs = daisy(img, normalization='l2')
|
||||
for i in range(descs.shape[0]):
|
||||
for j in range(descs.shape[1]):
|
||||
assert_almost_equal(sqrt(np.sum(descs[i, j, :] ** 2)), 1)
|
||||
|
||||
orientations = 8
|
||||
descs = daisy(img, orientations=orientations, normalization='daisy')
|
||||
desc_dims = descs.shape[2]
|
||||
for i in range(descs.shape[0]):
|
||||
for j in range(descs.shape[1]):
|
||||
for k in range(0, desc_dims, orientations):
|
||||
assert_almost_equal(
|
||||
sqrt(np.sum(descs[i, j, k : k + orientations] ** 2)), 1
|
||||
)
|
||||
|
||||
img = np.zeros((50, 50))
|
||||
descs = daisy(img, normalization='off')
|
||||
for i in range(descs.shape[0]):
|
||||
for j in range(descs.shape[1]):
|
||||
assert_almost_equal(np.sum(descs[i, j, :]), 0)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
daisy(img, normalization='does_not_exist')
|
||||
|
||||
|
||||
def test_daisy_visualization():
|
||||
img = img_as_float(data.astronaut()[:32, :32].mean(axis=2))
|
||||
descs, descs_img = daisy(img, visualize=True)
|
||||
assert descs_img.shape == (32, 32, 3)
|
||||
191
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_fisher_vector.py
vendored
Normal file
191
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_fisher_vector.py
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
import pytest
|
||||
|
||||
import numpy as np
|
||||
|
||||
pytest.importorskip('sklearn')
|
||||
|
||||
from skimage.feature._fisher_vector import ( # noqa: E402
|
||||
learn_gmm,
|
||||
fisher_vector,
|
||||
FisherVectorException,
|
||||
DescriptorException,
|
||||
)
|
||||
|
||||
|
||||
def test_gmm_wrong_descriptor_format_1():
|
||||
"""Test that DescriptorException is raised when wrong type for descriptions
|
||||
is passed.
|
||||
"""
|
||||
|
||||
with pytest.raises(DescriptorException):
|
||||
learn_gmm('completely wrong test', n_modes=1)
|
||||
|
||||
|
||||
def test_gmm_wrong_descriptor_format_2():
|
||||
"""Test that DescriptorException is raised when descriptors are of
|
||||
different dimensionality.
|
||||
"""
|
||||
|
||||
with pytest.raises(DescriptorException):
|
||||
learn_gmm([np.zeros((5, 11)), np.zeros((4, 10))], n_modes=1)
|
||||
|
||||
|
||||
def test_gmm_wrong_descriptor_format_3():
|
||||
"""Test that DescriptorException is raised when not all descriptors are of
|
||||
rank 2.
|
||||
"""
|
||||
|
||||
with pytest.raises(DescriptorException):
|
||||
learn_gmm([np.zeros((5, 10)), np.zeros((4, 10, 1))], n_modes=1)
|
||||
|
||||
|
||||
def test_gmm_wrong_descriptor_format_4():
|
||||
"""Test that DescriptorException is raised when elements of descriptor list
|
||||
are of the incorrect type (i.e. not a NumPy ndarray).
|
||||
"""
|
||||
|
||||
with pytest.raises(DescriptorException):
|
||||
learn_gmm([[1, 2, 3], [1, 2, 3]], n_modes=1)
|
||||
|
||||
|
||||
def test_gmm_wrong_num_modes_format_1():
|
||||
"""Test that FisherVectorException is raised when incorrect type for
|
||||
n_modes is passed into the learn_gmm function.
|
||||
"""
|
||||
|
||||
with pytest.raises(FisherVectorException):
|
||||
learn_gmm([np.zeros((5, 10)), np.zeros((4, 10))], n_modes='not_valid')
|
||||
|
||||
|
||||
def test_gmm_wrong_num_modes_format_2():
|
||||
"""Test that FisherVectorException is raised when a number that is not a
|
||||
positive integer is passed into the n_modes argument of learn_gmm.
|
||||
"""
|
||||
|
||||
with pytest.raises(FisherVectorException):
|
||||
learn_gmm([np.zeros((5, 10)), np.zeros((4, 10))], n_modes=-1)
|
||||
|
||||
|
||||
def test_gmm_wrong_covariance_type():
|
||||
"""Test that FisherVectorException is raised when wrong covariance type is
|
||||
passed in as a keyword argument.
|
||||
"""
|
||||
|
||||
with pytest.raises(FisherVectorException):
|
||||
learn_gmm(
|
||||
np.random.random((10, 10)), n_modes=2, gm_args={'covariance_type': 'full'}
|
||||
)
|
||||
|
||||
|
||||
def test_gmm_correct_covariance_type():
|
||||
"""Test that GMM estimation is successful when the correct covariance type
|
||||
is passed in as a keyword argument.
|
||||
"""
|
||||
|
||||
gmm = learn_gmm(
|
||||
np.random.random((10, 10)), n_modes=2, gm_args={'covariance_type': 'diag'}
|
||||
)
|
||||
|
||||
assert gmm.means_ is not None
|
||||
assert gmm.covariances_ is not None
|
||||
assert gmm.weights_ is not None
|
||||
|
||||
|
||||
def test_gmm_e2e():
|
||||
"""
|
||||
Test the GMM estimation. Since this is essentially a wrapper for the
|
||||
scikit-learn GaussianMixture class, the testing of the actual inner
|
||||
workings of the GMM estimation is left to scikit-learn and its
|
||||
dependencies.
|
||||
|
||||
We instead simply assert that the estimation was successful based on the
|
||||
fact that the GMM object will have associated mixture weights, means, and
|
||||
variances after estimation is successful/complete.
|
||||
"""
|
||||
|
||||
gmm = learn_gmm(np.random.random((100, 64)), n_modes=5)
|
||||
|
||||
assert gmm.means_ is not None
|
||||
assert gmm.covariances_ is not None
|
||||
assert gmm.weights_ is not None
|
||||
|
||||
|
||||
def test_fv_wrong_descriptor_types():
|
||||
"""
|
||||
Test that DescriptorException is raised when the incorrect type for the
|
||||
descriptors is passed into the fisher_vector function.
|
||||
"""
|
||||
try:
|
||||
from sklearn.mixture import GaussianMixture
|
||||
except ImportError:
|
||||
print(
|
||||
'scikit-learn is not installed. Please ensure it is installed in '
|
||||
'order to use the Fisher vector functionality.'
|
||||
)
|
||||
|
||||
with pytest.raises(DescriptorException):
|
||||
fisher_vector([[1, 2, 3, 4]], GaussianMixture())
|
||||
|
||||
|
||||
def test_fv_wrong_gmm_type():
|
||||
"""
|
||||
Test that FisherVectorException is raised when a GMM not of type
|
||||
sklearn.mixture.GaussianMixture is passed into the fisher_vector
|
||||
function.
|
||||
"""
|
||||
|
||||
class MyDifferentGaussianMixture:
|
||||
pass
|
||||
|
||||
with pytest.raises(FisherVectorException):
|
||||
fisher_vector(np.zeros((10, 10)), MyDifferentGaussianMixture())
|
||||
|
||||
|
||||
def test_fv_e2e():
|
||||
"""
|
||||
Test the Fisher vector computation given a GMM returned from the learn_gmm
|
||||
function. We simply assert that the dimensionality of the resulting Fisher
|
||||
vector is correct.
|
||||
|
||||
The dimensionality of a Fisher vector is given by 2KD + K, where K is the
|
||||
number of Gaussians specified in the associated GMM, and D is the
|
||||
dimensionality of the descriptors using to estimate the GMM.
|
||||
"""
|
||||
|
||||
dim = 128
|
||||
num_modes = 8
|
||||
|
||||
expected_dim = 2 * num_modes * dim + num_modes
|
||||
|
||||
descriptors = [np.random.random((np.random.randint(5, 30), dim)) for _ in range(10)]
|
||||
|
||||
gmm = learn_gmm(descriptors, n_modes=num_modes)
|
||||
|
||||
fisher_vec = fisher_vector(descriptors[0], gmm)
|
||||
|
||||
assert len(fisher_vec) == expected_dim
|
||||
|
||||
|
||||
def test_fv_e2e_improved():
|
||||
"""
|
||||
Test the improved Fisher vector computation given a GMM returned from the
|
||||
learn_gmm function. We simply assert that the dimensionality of the
|
||||
resulting Fisher vector is correct.
|
||||
|
||||
The dimensionality of a Fisher vector is given by 2KD + K, where K is the
|
||||
number of Gaussians specified in the associated GMM, and D is the
|
||||
dimensionality of the descriptors using to estimate the GMM.
|
||||
"""
|
||||
|
||||
dim = 128
|
||||
num_modes = 8
|
||||
|
||||
expected_dim = 2 * num_modes * dim + num_modes
|
||||
|
||||
descriptors = [np.random.random((np.random.randint(5, 30), dim)) for _ in range(10)]
|
||||
|
||||
gmm = learn_gmm(descriptors, n_modes=num_modes)
|
||||
|
||||
fisher_vec = fisher_vector(descriptors[0], gmm, improved=True)
|
||||
|
||||
assert len(fisher_vec) == expected_dim
|
||||
182
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_haar.py
vendored
Normal file
182
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_haar.py
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
from random import shuffle
|
||||
|
||||
import pytest
|
||||
|
||||
import numpy as np
|
||||
from numpy.testing import assert_allclose
|
||||
from numpy.testing import assert_array_equal
|
||||
|
||||
from skimage.transform import integral_image
|
||||
from skimage.feature import haar_like_feature
|
||||
from skimage.feature import haar_like_feature_coord
|
||||
from skimage.feature import draw_haar_like_feature
|
||||
|
||||
|
||||
def test_haar_like_feature_error():
|
||||
img = np.ones((5, 5), dtype=np.float32)
|
||||
img_ii = integral_image(img)
|
||||
|
||||
feature_type = 'unknown_type'
|
||||
with pytest.raises(ValueError):
|
||||
haar_like_feature(img_ii, 0, 0, 5, 5, feature_type=feature_type)
|
||||
haar_like_feature_coord(5, 5, feature_type=feature_type)
|
||||
draw_haar_like_feature(img, 0, 0, 5, 5, feature_type=feature_type)
|
||||
|
||||
feat_coord, feat_type = haar_like_feature_coord(5, 5, 'type-2-x')
|
||||
with pytest.raises(ValueError):
|
||||
haar_like_feature(
|
||||
img_ii, 0, 0, 5, 5, feature_type=feat_type[:3], feature_coord=feat_coord
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dtype", [np.uint8, np.int8, np.float32, np.float64])
|
||||
@pytest.mark.parametrize(
|
||||
"feature_type,shape_feature,expected_feature_value",
|
||||
[
|
||||
('type-2-x', (84,), [0.0]),
|
||||
('type-2-y', (84,), [0.0]),
|
||||
('type-3-x', (42,), [-5, -4.0, -3.0, -2.0, -1.0]),
|
||||
('type-3-y', (42,), [-5, -4.0, -3.0, -2.0, -1.0]),
|
||||
('type-4', (36,), [0.0]),
|
||||
],
|
||||
)
|
||||
def test_haar_like_feature(feature_type, shape_feature, expected_feature_value, dtype):
|
||||
# test Haar-like feature on a basic one image
|
||||
img = np.ones((5, 5), dtype=dtype)
|
||||
img_ii = integral_image(img)
|
||||
haar_feature = haar_like_feature(img_ii, 0, 0, 5, 5, feature_type=feature_type)
|
||||
assert_allclose(np.sort(np.unique(haar_feature)), expected_feature_value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dtype", [np.uint8, np.int8, np.float32, np.float64])
|
||||
@pytest.mark.parametrize(
|
||||
"feature_type", ['type-2-x', 'type-2-y', 'type-3-x', 'type-3-y', 'type-4']
|
||||
)
|
||||
def test_haar_like_feature_fused_type(dtype, feature_type):
|
||||
# check that the input type is kept
|
||||
img = np.ones((5, 5), dtype=dtype)
|
||||
img_ii = integral_image(img)
|
||||
expected_dtype = img_ii.dtype
|
||||
# to avoid overflow, unsigned type are converted to signed
|
||||
if 'uint' in expected_dtype.name:
|
||||
expected_dtype = np.dtype(expected_dtype.name.replace('u', ''))
|
||||
haar_feature = haar_like_feature(img_ii, 0, 0, 5, 5, feature_type=feature_type)
|
||||
assert haar_feature.dtype == expected_dtype
|
||||
|
||||
|
||||
def test_haar_like_feature_list():
|
||||
img = np.ones((5, 5), dtype=np.int8)
|
||||
img_ii = integral_image(img)
|
||||
feature_type = ['type-2-x', 'type-2-y', 'type-3-x', 'type-3-y', 'type-4']
|
||||
haar_list = haar_like_feature(img_ii, 0, 0, 5, 5, feature_type=feature_type)
|
||||
haar_all = haar_like_feature(img_ii, 0, 0, 5, 5)
|
||||
assert_array_equal(haar_list, haar_all)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"feature_type",
|
||||
[
|
||||
'type-2-x',
|
||||
'type-2-y',
|
||||
'type-3-x',
|
||||
'type-3-y',
|
||||
'type-4',
|
||||
['type-2-y', 'type-3-x', 'type-4'],
|
||||
],
|
||||
)
|
||||
def test_haar_like_feature_precomputed(feature_type):
|
||||
img = np.ones((5, 5), dtype=np.int8)
|
||||
img_ii = integral_image(img)
|
||||
if isinstance(feature_type, list):
|
||||
# shuffle the index of the feature to be sure that we are output
|
||||
# the features in the same order
|
||||
shuffle(feature_type)
|
||||
feat_coord, feat_type = zip(
|
||||
*[haar_like_feature_coord(5, 5, feat_t) for feat_t in feature_type]
|
||||
)
|
||||
feat_coord = np.concatenate(feat_coord)
|
||||
feat_type = np.concatenate(feat_type)
|
||||
else:
|
||||
feat_coord, feat_type = haar_like_feature_coord(5, 5, feature_type)
|
||||
haar_feature_precomputed = haar_like_feature(
|
||||
img_ii, 0, 0, 5, 5, feature_type=feat_type, feature_coord=feat_coord
|
||||
)
|
||||
haar_feature = haar_like_feature(img_ii, 0, 0, 5, 5, feature_type)
|
||||
assert_array_equal(haar_feature_precomputed, haar_feature)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"feature_type,height,width,expected_coord",
|
||||
[
|
||||
(
|
||||
'type-2-x',
|
||||
2,
|
||||
2,
|
||||
[
|
||||
[[(0, 0), (0, 0)], [(0, 1), (0, 1)]],
|
||||
[[(0, 0), (1, 0)], [(0, 1), (1, 1)]],
|
||||
[[(1, 0), (1, 0)], [(1, 1), (1, 1)]],
|
||||
],
|
||||
),
|
||||
(
|
||||
'type-2-y',
|
||||
2,
|
||||
2,
|
||||
[
|
||||
[[(0, 0), (0, 0)], [(1, 0), (1, 0)]],
|
||||
[[(0, 0), (0, 1)], [(1, 0), (1, 1)]],
|
||||
[[(0, 1), (0, 1)], [(1, 1), (1, 1)]],
|
||||
],
|
||||
),
|
||||
(
|
||||
'type-3-x',
|
||||
3,
|
||||
3,
|
||||
[
|
||||
[[(0, 0), (0, 0)], [(0, 1), (0, 1)], [(0, 2), (0, 2)]],
|
||||
[[(0, 0), (1, 0)], [(0, 1), (1, 1)], [(0, 2), (1, 2)]],
|
||||
[[(0, 0), (2, 0)], [(0, 1), (2, 1)], [(0, 2), (2, 2)]],
|
||||
[[(1, 0), (1, 0)], [(1, 1), (1, 1)], [(1, 2), (1, 2)]],
|
||||
[[(1, 0), (2, 0)], [(1, 1), (2, 1)], [(1, 2), (2, 2)]],
|
||||
[[(2, 0), (2, 0)], [(2, 1), (2, 1)], [(2, 2), (2, 2)]],
|
||||
],
|
||||
),
|
||||
(
|
||||
'type-3-y',
|
||||
3,
|
||||
3,
|
||||
[
|
||||
[[(0, 0), (0, 0)], [(1, 0), (1, 0)], [(2, 0), (2, 0)]],
|
||||
[[(0, 0), (0, 1)], [(1, 0), (1, 1)], [(2, 0), (2, 1)]],
|
||||
[[(0, 0), (0, 2)], [(1, 0), (1, 2)], [(2, 0), (2, 2)]],
|
||||
[[(0, 1), (0, 1)], [(1, 1), (1, 1)], [(2, 1), (2, 1)]],
|
||||
[[(0, 1), (0, 2)], [(1, 1), (1, 2)], [(2, 1), (2, 2)]],
|
||||
[[(0, 2), (0, 2)], [(1, 2), (1, 2)], [(2, 2), (2, 2)]],
|
||||
],
|
||||
),
|
||||
(
|
||||
'type-4',
|
||||
2,
|
||||
2,
|
||||
[[[(0, 0), (0, 0)], [(0, 1), (0, 1)], [(1, 1), (1, 1)], [(1, 0), (1, 0)]]],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_haar_like_feature_coord(feature_type, height, width, expected_coord):
|
||||
feat_coord, feat_type = haar_like_feature_coord(width, height, feature_type)
|
||||
# convert the output to a full numpy array just for comparison
|
||||
feat_coord = np.array([hf for hf in feat_coord])
|
||||
assert_array_equal(feat_coord, expected_coord)
|
||||
assert np.all(feat_type == feature_type)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("max_n_features,nnz_values", [(None, 46), (1, 4)])
|
||||
def test_draw_haar_like_feature(max_n_features, nnz_values):
|
||||
img = np.zeros((5, 5), dtype=np.float32)
|
||||
coord, _ = haar_like_feature_coord(5, 5, 'type-4')
|
||||
image = draw_haar_like_feature(
|
||||
img, 0, 0, 5, 5, coord, max_n_features=max_n_features, rng=0
|
||||
)
|
||||
draw_haar_like_feature(img, 0, 0, 5, 5, coord, max_n_features=max_n_features, rng=0)
|
||||
assert image.shape == (5, 5, 3)
|
||||
assert np.count_nonzero(image) == nnz_values
|
||||
360
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_hog.py
vendored
Normal file
360
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_hog.py
vendored
Normal file
@@ -0,0 +1,360 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy.testing import assert_almost_equal
|
||||
|
||||
from skimage import color, data, draw, feature, img_as_float
|
||||
from skimage._shared import filters
|
||||
from skimage._shared.testing import fetch
|
||||
from skimage._shared.utils import _supported_float_type
|
||||
|
||||
|
||||
def test_hog_output_size():
|
||||
img = img_as_float(data.astronaut()[:256, :].mean(axis=2))
|
||||
|
||||
fd = feature.hog(
|
||||
img,
|
||||
orientations=9,
|
||||
pixels_per_cell=(8, 8),
|
||||
cells_per_block=(1, 1),
|
||||
block_norm='L1',
|
||||
)
|
||||
|
||||
assert len(fd) == 9 * (256 // 8) * (512 // 8)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float32, np.float64])
|
||||
def test_hog_output_correctness_l1_norm(dtype):
|
||||
img = color.rgb2gray(data.astronaut()).astype(dtype=dtype, copy=False)
|
||||
correct_output = np.load(fetch('data/astronaut_GRAY_hog_L1.npy'))
|
||||
|
||||
output = feature.hog(
|
||||
img,
|
||||
orientations=9,
|
||||
pixels_per_cell=(8, 8),
|
||||
cells_per_block=(3, 3),
|
||||
block_norm='L1',
|
||||
feature_vector=True,
|
||||
transform_sqrt=False,
|
||||
visualize=False,
|
||||
)
|
||||
float_dtype = _supported_float_type(dtype)
|
||||
assert output.dtype == float_dtype
|
||||
decimal = 7 if float_dtype == np.float64 else 5
|
||||
assert_almost_equal(output, correct_output, decimal=decimal)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float32, np.float64])
|
||||
def test_hog_output_correctness_l2hys_norm(dtype):
|
||||
img = color.rgb2gray(data.astronaut()).astype(dtype=dtype, copy=False)
|
||||
correct_output = np.load(fetch('data/astronaut_GRAY_hog_L2-Hys.npy'))
|
||||
|
||||
output = feature.hog(
|
||||
img,
|
||||
orientations=9,
|
||||
pixels_per_cell=(8, 8),
|
||||
cells_per_block=(3, 3),
|
||||
block_norm='L2-Hys',
|
||||
feature_vector=True,
|
||||
transform_sqrt=False,
|
||||
visualize=False,
|
||||
)
|
||||
float_dtype = _supported_float_type(dtype)
|
||||
assert output.dtype == float_dtype
|
||||
decimal = 7 if float_dtype == np.float64 else 5
|
||||
assert_almost_equal(output, correct_output, decimal=decimal)
|
||||
|
||||
|
||||
def test_hog_image_size_cell_size_mismatch():
|
||||
image = data.camera()[:150, :200]
|
||||
fd = feature.hog(
|
||||
image,
|
||||
orientations=9,
|
||||
pixels_per_cell=(8, 8),
|
||||
cells_per_block=(1, 1),
|
||||
block_norm='L1',
|
||||
)
|
||||
assert len(fd) == 9 * (150 // 8) * (200 // 8)
|
||||
|
||||
|
||||
def test_hog_odd_cell_size():
|
||||
img = np.zeros((3, 3))
|
||||
img[2, 2] = 1
|
||||
|
||||
correct_output = np.zeros((9,))
|
||||
correct_output[0] = 0.5
|
||||
correct_output[4] = 0.5
|
||||
|
||||
output = feature.hog(
|
||||
img, pixels_per_cell=(3, 3), cells_per_block=(1, 1), block_norm='L1'
|
||||
)
|
||||
|
||||
assert_almost_equal(output, correct_output, decimal=1)
|
||||
|
||||
|
||||
def test_hog_basic_orientations_and_data_types():
|
||||
# scenario:
|
||||
# 1) create image (with float values) where upper half is filled by
|
||||
# zeros, bottom half by 100
|
||||
# 2) create unsigned integer version of this image
|
||||
# 3) calculate feature.hog() for both images, both with 'transform_sqrt'
|
||||
# option enabled and disabled
|
||||
# 4) verify that all results are equal where expected
|
||||
# 5) verify that computed feature vector is as expected
|
||||
# 6) repeat the scenario for 90, 180 and 270 degrees rotated images
|
||||
|
||||
# size of testing image
|
||||
width = height = 35
|
||||
|
||||
image0 = np.zeros((height, width), dtype='float')
|
||||
image0[height // 2 :] = 100
|
||||
|
||||
for rot in range(4):
|
||||
# rotate by 0, 90, 180 and 270 degrees
|
||||
image_float = np.rot90(image0, rot)
|
||||
|
||||
# create uint8 image from image_float
|
||||
image_uint8 = image_float.astype('uint8')
|
||||
|
||||
(hog_float, hog_img_float) = feature.hog(
|
||||
image_float,
|
||||
orientations=4,
|
||||
pixels_per_cell=(8, 8),
|
||||
cells_per_block=(1, 1),
|
||||
visualize=True,
|
||||
transform_sqrt=False,
|
||||
block_norm='L1',
|
||||
)
|
||||
(hog_uint8, hog_img_uint8) = feature.hog(
|
||||
image_uint8,
|
||||
orientations=4,
|
||||
pixels_per_cell=(8, 8),
|
||||
cells_per_block=(1, 1),
|
||||
visualize=True,
|
||||
transform_sqrt=False,
|
||||
block_norm='L1',
|
||||
)
|
||||
(hog_float_norm, hog_img_float_norm) = feature.hog(
|
||||
image_float,
|
||||
orientations=4,
|
||||
pixels_per_cell=(8, 8),
|
||||
cells_per_block=(1, 1),
|
||||
visualize=True,
|
||||
transform_sqrt=True,
|
||||
block_norm='L1',
|
||||
)
|
||||
(hog_uint8_norm, hog_img_uint8_norm) = feature.hog(
|
||||
image_uint8,
|
||||
orientations=4,
|
||||
pixels_per_cell=(8, 8),
|
||||
cells_per_block=(1, 1),
|
||||
visualize=True,
|
||||
transform_sqrt=True,
|
||||
block_norm='L1',
|
||||
)
|
||||
|
||||
# set to True to enable manual debugging with graphical output,
|
||||
# must be False for automatic testing
|
||||
if False:
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
plt.figure()
|
||||
plt.subplot(2, 3, 1)
|
||||
plt.imshow(image_float)
|
||||
plt.colorbar()
|
||||
plt.title('image')
|
||||
plt.subplot(2, 3, 2)
|
||||
plt.imshow(hog_img_float)
|
||||
plt.colorbar()
|
||||
plt.title('HOG result visualisation (float img)')
|
||||
plt.subplot(2, 3, 5)
|
||||
plt.imshow(hog_img_uint8)
|
||||
plt.colorbar()
|
||||
plt.title('HOG result visualisation (uint8 img)')
|
||||
plt.subplot(2, 3, 3)
|
||||
plt.imshow(hog_img_float_norm)
|
||||
plt.colorbar()
|
||||
plt.title('HOG result (transform_sqrt) visualisation (float img)')
|
||||
plt.subplot(2, 3, 6)
|
||||
plt.imshow(hog_img_uint8_norm)
|
||||
plt.colorbar()
|
||||
plt.title('HOG result (transform_sqrt) visualisation (uint8 img)')
|
||||
plt.show()
|
||||
|
||||
# results (features and visualisation) for float and uint8 images must
|
||||
# be almost equal
|
||||
assert_almost_equal(hog_float, hog_uint8)
|
||||
assert_almost_equal(hog_img_float, hog_img_uint8)
|
||||
|
||||
# resulting features should be almost equal
|
||||
# when 'transform_sqrt' is enabled
|
||||
# or disabled (for current simple testing image)
|
||||
assert_almost_equal(hog_float, hog_float_norm, decimal=4)
|
||||
assert_almost_equal(hog_float, hog_uint8_norm, decimal=4)
|
||||
|
||||
# reshape resulting feature vector to matrix with 4 columns (each
|
||||
# corresponding to one of 4 directions); only one direction should
|
||||
# contain nonzero values (this is manually determined for testing
|
||||
# image)
|
||||
actual = np.max(hog_float.reshape(-1, 4), axis=0)
|
||||
|
||||
if rot in [0, 2]:
|
||||
# image is rotated by 0 and 180 degrees
|
||||
desired = [0, 0, 1, 0]
|
||||
elif rot in [1, 3]:
|
||||
# image is rotated by 90 and 270 degrees
|
||||
desired = [1, 0, 0, 0]
|
||||
else:
|
||||
raise Exception('Result is not determined for this rotation.')
|
||||
|
||||
assert_almost_equal(actual, desired, decimal=2)
|
||||
|
||||
|
||||
def test_hog_orientations_circle():
|
||||
# scenario:
|
||||
# 1) create image with blurred circle in the middle
|
||||
# 2) calculate feature.hog()
|
||||
# 3) verify that the resulting feature vector contains uniformly
|
||||
# distributed values for all orientations, i.e. no orientation is
|
||||
# lost or emphasized
|
||||
# 4) repeat the scenario for other 'orientations' option
|
||||
|
||||
# size of testing image
|
||||
width = height = 100
|
||||
|
||||
image = np.zeros((height, width))
|
||||
rr, cc = draw.disk((int(height / 2), int(width / 2)), int(width / 3))
|
||||
image[rr, cc] = 100
|
||||
image = filters.gaussian(image, sigma=2, mode='reflect')
|
||||
|
||||
for orientations in range(2, 15):
|
||||
(hog, hog_img) = feature.hog(
|
||||
image,
|
||||
orientations=orientations,
|
||||
pixels_per_cell=(8, 8),
|
||||
cells_per_block=(1, 1),
|
||||
visualize=True,
|
||||
transform_sqrt=False,
|
||||
block_norm='L1',
|
||||
)
|
||||
|
||||
# set to True to enable manual debugging with graphical output,
|
||||
# must be False for automatic testing
|
||||
if False:
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
plt.figure()
|
||||
plt.subplot(1, 2, 1)
|
||||
plt.imshow(image)
|
||||
plt.colorbar()
|
||||
plt.title('image_float')
|
||||
plt.subplot(1, 2, 2)
|
||||
plt.imshow(hog_img)
|
||||
plt.colorbar()
|
||||
plt.title('HOG result visualisation, ' f'orientations={orientations}')
|
||||
plt.show()
|
||||
|
||||
# reshape resulting feature vector to matrix with N columns (each
|
||||
# column corresponds to one direction),
|
||||
hog_matrix = hog.reshape(-1, orientations)
|
||||
|
||||
# compute mean values in the resulting feature vector for each
|
||||
# direction, these values should be almost equal to the global mean
|
||||
# value (since the image contains a circle), i.e., all directions have
|
||||
# same contribution to the result
|
||||
actual = np.mean(hog_matrix, axis=0)
|
||||
desired = np.mean(hog_matrix)
|
||||
assert_almost_equal(actual, desired, decimal=1)
|
||||
|
||||
|
||||
def test_hog_visualization_orientation():
|
||||
"""Test that the visualization produces a line with correct orientation
|
||||
|
||||
The hog visualization is expected to draw line segments perpendicular to
|
||||
the midpoints of orientation bins. This example verifies that when
|
||||
orientations=3 and the gradient is entirely in the middle bin (bisected
|
||||
by the y-axis), the line segment drawn by the visualization is horizontal.
|
||||
"""
|
||||
|
||||
width = height = 11
|
||||
|
||||
image = np.zeros((height, width), dtype='float')
|
||||
image[height // 2 :] = 1
|
||||
|
||||
_, hog_image = feature.hog(
|
||||
image,
|
||||
orientations=3,
|
||||
pixels_per_cell=(width, height),
|
||||
cells_per_block=(1, 1),
|
||||
visualize=True,
|
||||
block_norm='L1',
|
||||
)
|
||||
|
||||
middle_index = height // 2
|
||||
indices_excluding_middle = [x for x in range(height) if x != middle_index]
|
||||
|
||||
assert (hog_image[indices_excluding_middle, :] == 0).all()
|
||||
assert (hog_image[middle_index, 1:-1] > 0).all()
|
||||
|
||||
|
||||
def test_hog_block_normalization_incorrect_error():
|
||||
img = np.eye(4)
|
||||
with pytest.raises(ValueError):
|
||||
feature.hog(img, block_norm='Linf')
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"shape,channel_axis",
|
||||
[
|
||||
((3, 3, 3), None),
|
||||
((3, 3), -1),
|
||||
((3, 3, 3, 3), -1),
|
||||
],
|
||||
)
|
||||
def test_hog_incorrect_dimensions(shape, channel_axis):
|
||||
img = np.zeros(shape)
|
||||
with pytest.raises(ValueError):
|
||||
feature.hog(img, channel_axis=channel_axis, block_norm='L1')
|
||||
|
||||
|
||||
def test_hog_output_equivariance_deprecated_multichannel():
|
||||
img = data.astronaut()
|
||||
img[:, :, (1, 2)] = 0
|
||||
hog_ref = feature.hog(img, channel_axis=-1, block_norm='L1')
|
||||
|
||||
for n in (1, 2):
|
||||
hog_fact = feature.hog(
|
||||
np.roll(img, n, axis=2), channel_axis=-1, block_norm='L1'
|
||||
)
|
||||
assert_almost_equal(hog_ref, hog_fact)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('channel_axis', [0, 1, -1, -2])
|
||||
def test_hog_output_equivariance_channel_axis(channel_axis):
|
||||
img = data.astronaut()[:64, :32]
|
||||
img[:, :, (1, 2)] = 0
|
||||
img = np.moveaxis(img, -1, channel_axis)
|
||||
hog_ref = feature.hog(img, channel_axis=channel_axis, block_norm='L1')
|
||||
|
||||
for n in (1, 2):
|
||||
hog_fact = feature.hog(
|
||||
np.roll(img, n, axis=channel_axis),
|
||||
channel_axis=channel_axis,
|
||||
block_norm='L1',
|
||||
)
|
||||
assert_almost_equal(hog_ref, hog_fact)
|
||||
|
||||
|
||||
def test_hog_small_image():
|
||||
"""Test that an exception is thrown whenever the input image is
|
||||
too small for the given parameters.
|
||||
"""
|
||||
img = np.zeros((24, 24))
|
||||
feature.hog(img, pixels_per_cell=(8, 8), cells_per_block=(3, 3))
|
||||
|
||||
img = np.zeros((23, 23))
|
||||
with pytest.raises(ValueError, match=".*image is too small given"):
|
||||
feature.hog(
|
||||
img,
|
||||
pixels_per_cell=(8, 8),
|
||||
cells_per_block=(3, 3),
|
||||
)
|
||||
321
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_match.py
vendored
Normal file
321
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_match.py
vendored
Normal file
@@ -0,0 +1,321 @@
|
||||
import numpy as np
|
||||
from skimage._shared.testing import assert_equal
|
||||
from skimage import data
|
||||
from skimage import transform
|
||||
from skimage.color import rgb2gray
|
||||
from skimage.feature import BRIEF, match_descriptors, corner_peaks, corner_harris
|
||||
from skimage._shared import testing
|
||||
|
||||
|
||||
def test_binary_descriptors_unequal_descriptor_sizes_error():
|
||||
"""Sizes of descriptors of keypoints to be matched should be equal."""
|
||||
descs1 = np.array([[True, True, False, True], [False, True, False, True]])
|
||||
descs2 = np.array(
|
||||
[[True, False, False, True, False], [False, True, True, True, False]]
|
||||
)
|
||||
with testing.raises(ValueError):
|
||||
match_descriptors(descs1, descs2)
|
||||
|
||||
|
||||
def test_binary_descriptors():
|
||||
descs1 = np.array(
|
||||
[[True, True, False, True, True], [False, True, False, True, True]]
|
||||
)
|
||||
descs2 = np.array(
|
||||
[[True, False, False, True, False], [False, False, True, True, True]]
|
||||
)
|
||||
matches = match_descriptors(descs1, descs2)
|
||||
assert_equal(matches, [[0, 0], [1, 1]])
|
||||
|
||||
|
||||
def test_binary_descriptors_rotation_crosscheck_false():
|
||||
"""Verify matched keypoints and their corresponding masks results between
|
||||
image and its rotated version with the expected keypoint pairs with
|
||||
cross_check disabled."""
|
||||
img = data.astronaut()
|
||||
img = rgb2gray(img)
|
||||
tform = transform.SimilarityTransform(scale=1, rotation=0.15, translation=(0, 0))
|
||||
rotated_img = transform.warp(img, tform, clip=False)
|
||||
|
||||
extractor = BRIEF(descriptor_size=512)
|
||||
|
||||
keypoints1 = corner_peaks(
|
||||
corner_harris(img), min_distance=5, threshold_abs=0, threshold_rel=0.1
|
||||
)
|
||||
extractor.extract(img, keypoints1)
|
||||
descriptors1 = extractor.descriptors
|
||||
|
||||
keypoints2 = corner_peaks(
|
||||
corner_harris(rotated_img), min_distance=5, threshold_abs=0, threshold_rel=0.1
|
||||
)
|
||||
extractor.extract(rotated_img, keypoints2)
|
||||
descriptors2 = extractor.descriptors
|
||||
|
||||
matches = match_descriptors(descriptors1, descriptors2, cross_check=False)
|
||||
|
||||
exp_matches1 = np.arange(47)
|
||||
exp_matches2 = np.array(
|
||||
[
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
7,
|
||||
8,
|
||||
14,
|
||||
9,
|
||||
11,
|
||||
13,
|
||||
23,
|
||||
15,
|
||||
16,
|
||||
22,
|
||||
17,
|
||||
19,
|
||||
37,
|
||||
18,
|
||||
24,
|
||||
27,
|
||||
30,
|
||||
25,
|
||||
26,
|
||||
32,
|
||||
28,
|
||||
35,
|
||||
37,
|
||||
42,
|
||||
29,
|
||||
38,
|
||||
33,
|
||||
40,
|
||||
36,
|
||||
39,
|
||||
10,
|
||||
36,
|
||||
43,
|
||||
15,
|
||||
35,
|
||||
41,
|
||||
6,
|
||||
37,
|
||||
32,
|
||||
24,
|
||||
8,
|
||||
]
|
||||
)
|
||||
|
||||
assert_equal(matches[:, 0], exp_matches1)
|
||||
assert_equal(matches[:, 1], exp_matches2)
|
||||
|
||||
# minkowski takes a different code path, therefore we test it explicitly
|
||||
matches = match_descriptors(
|
||||
descriptors1, descriptors2, metric='minkowski', cross_check=False
|
||||
)
|
||||
assert_equal(matches[:, 0], exp_matches1)
|
||||
assert_equal(matches[:, 1], exp_matches2)
|
||||
|
||||
# it also has an extra parameter
|
||||
matches = match_descriptors(
|
||||
descriptors1, descriptors2, metric='minkowski', p=4, cross_check=False
|
||||
)
|
||||
assert_equal(matches[:, 0], exp_matches1)
|
||||
assert_equal(matches[:, 1], exp_matches2)
|
||||
|
||||
|
||||
def test_binary_descriptors_rotation_crosscheck_true():
|
||||
"""Verify matched keypoints and their corresponding masks results between
|
||||
image and its rotated version with the expected keypoint pairs with
|
||||
cross_check enabled."""
|
||||
img = data.astronaut()
|
||||
img = rgb2gray(img)
|
||||
tform = transform.SimilarityTransform(scale=1, rotation=0.15, translation=(0, 0))
|
||||
rotated_img = transform.warp(img, tform, clip=False)
|
||||
|
||||
extractor = BRIEF(descriptor_size=512)
|
||||
|
||||
keypoints1 = corner_peaks(
|
||||
corner_harris(img), min_distance=5, threshold_abs=0, threshold_rel=0.1
|
||||
)
|
||||
extractor.extract(img, keypoints1)
|
||||
descriptors1 = extractor.descriptors
|
||||
|
||||
keypoints2 = corner_peaks(
|
||||
corner_harris(rotated_img), min_distance=5, threshold_abs=0, threshold_rel=0.1
|
||||
)
|
||||
extractor.extract(rotated_img, keypoints2)
|
||||
descriptors2 = extractor.descriptors
|
||||
|
||||
matches = match_descriptors(descriptors1, descriptors2, cross_check=True)
|
||||
|
||||
exp_matches1 = np.array(
|
||||
[
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31,
|
||||
32,
|
||||
33,
|
||||
34,
|
||||
38,
|
||||
41,
|
||||
42,
|
||||
]
|
||||
)
|
||||
exp_matches2 = np.array(
|
||||
[
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
7,
|
||||
8,
|
||||
14,
|
||||
9,
|
||||
11,
|
||||
13,
|
||||
23,
|
||||
15,
|
||||
16,
|
||||
22,
|
||||
17,
|
||||
19,
|
||||
18,
|
||||
24,
|
||||
27,
|
||||
30,
|
||||
25,
|
||||
26,
|
||||
28,
|
||||
35,
|
||||
37,
|
||||
42,
|
||||
29,
|
||||
38,
|
||||
33,
|
||||
40,
|
||||
36,
|
||||
43,
|
||||
41,
|
||||
6,
|
||||
]
|
||||
)
|
||||
assert_equal(matches[:, 0], exp_matches1)
|
||||
assert_equal(matches[:, 1], exp_matches2)
|
||||
|
||||
|
||||
def test_max_distance():
|
||||
descs1 = np.zeros((10, 128))
|
||||
descs2 = np.zeros((15, 128))
|
||||
|
||||
descs1[0, :] = 1
|
||||
|
||||
matches = match_descriptors(
|
||||
descs1, descs2, metric='euclidean', max_distance=0.1, cross_check=False
|
||||
)
|
||||
assert len(matches) == 9
|
||||
|
||||
matches = match_descriptors(
|
||||
descs1,
|
||||
descs2,
|
||||
metric='euclidean',
|
||||
max_distance=np.sqrt(128.1),
|
||||
cross_check=False,
|
||||
)
|
||||
assert len(matches) == 10
|
||||
|
||||
matches = match_descriptors(
|
||||
descs1, descs2, metric='euclidean', max_distance=0.1, cross_check=True
|
||||
)
|
||||
assert_equal(matches, [[1, 0]])
|
||||
|
||||
matches = match_descriptors(
|
||||
descs1,
|
||||
descs2,
|
||||
metric='euclidean',
|
||||
max_distance=np.sqrt(128.1),
|
||||
cross_check=True,
|
||||
)
|
||||
assert_equal(matches, [[1, 0]])
|
||||
|
||||
|
||||
def test_max_ratio():
|
||||
descs1 = 10 * np.arange(10)[:, None].astype(np.float32)
|
||||
descs2 = 10 * np.arange(15)[:, None].astype(np.float32)
|
||||
|
||||
descs2[0] = 5.0
|
||||
|
||||
matches = match_descriptors(
|
||||
descs1, descs2, metric='euclidean', max_ratio=1.0, cross_check=False
|
||||
)
|
||||
assert_equal(len(matches), 10)
|
||||
|
||||
matches = match_descriptors(
|
||||
descs1, descs2, metric='euclidean', max_ratio=0.6, cross_check=False
|
||||
)
|
||||
assert_equal(len(matches), 10)
|
||||
|
||||
matches = match_descriptors(
|
||||
descs1, descs2, metric='euclidean', max_ratio=0.5, cross_check=False
|
||||
)
|
||||
assert_equal(len(matches), 9)
|
||||
|
||||
descs1[0] = 7.5
|
||||
|
||||
matches = match_descriptors(
|
||||
descs1, descs2, metric='euclidean', max_ratio=0.5, cross_check=False
|
||||
)
|
||||
assert_equal(len(matches), 9)
|
||||
|
||||
descs2 = 10 * np.arange(1)[:, None].astype(np.float32)
|
||||
|
||||
matches = match_descriptors(
|
||||
descs1, descs2, metric='euclidean', max_ratio=1.0, cross_check=False
|
||||
)
|
||||
assert_equal(len(matches), 10)
|
||||
|
||||
matches = match_descriptors(
|
||||
descs1, descs2, metric='euclidean', max_ratio=0.5, cross_check=False
|
||||
)
|
||||
assert_equal(len(matches), 10)
|
||||
|
||||
descs1 = 10 * np.arange(1)[:, None].astype(np.float32)
|
||||
|
||||
matches = match_descriptors(
|
||||
descs1, descs2, metric='euclidean', max_ratio=1.0, cross_check=False
|
||||
)
|
||||
assert_equal(len(matches), 1)
|
||||
|
||||
matches = match_descriptors(
|
||||
descs1, descs2, metric='euclidean', max_ratio=0.5, cross_check=False
|
||||
)
|
||||
assert_equal(len(matches), 1)
|
||||
180
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_orb.py
vendored
Normal file
180
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_orb.py
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy.testing import assert_almost_equal, assert_equal
|
||||
|
||||
from skimage import data
|
||||
from skimage._shared.testing import run_in_parallel, xfail, arch32
|
||||
from skimage.feature import ORB
|
||||
from skimage.util.dtype import _convert
|
||||
|
||||
|
||||
img = data.coins()
|
||||
|
||||
|
||||
@run_in_parallel()
|
||||
@pytest.mark.parametrize('dtype', ['float32', 'float64', 'uint8', 'uint16', 'int64'])
|
||||
def test_keypoints_orb_desired_no_of_keypoints(dtype):
|
||||
_img = _convert(img, dtype)
|
||||
detector_extractor = ORB(n_keypoints=10, fast_n=12, fast_threshold=0.20)
|
||||
detector_extractor.detect(_img)
|
||||
|
||||
exp_rows = np.array(
|
||||
[141.0, 108.0, 214.56, 131.0, 214.272, 67.0, 206.0, 177.0, 108.0, 141.0]
|
||||
)
|
||||
exp_cols = np.array(
|
||||
[323.0, 328.0, 282.24, 292.0, 281.664, 85.0, 260.0, 284.0, 328.8, 267.0]
|
||||
)
|
||||
|
||||
exp_scales = np.array([1, 1, 1.44, 1, 1.728, 1, 1, 1, 1.2, 1])
|
||||
|
||||
exp_orientations = np.array(
|
||||
[
|
||||
-53.97446153,
|
||||
59.5055285,
|
||||
-96.01885186,
|
||||
-149.70789506,
|
||||
-94.70171899,
|
||||
-45.76429535,
|
||||
-51.49752849,
|
||||
113.57081195,
|
||||
63.30428063,
|
||||
-79.56091118,
|
||||
]
|
||||
)
|
||||
exp_response = np.array(
|
||||
[
|
||||
1.01168357,
|
||||
0.82934145,
|
||||
0.67784179,
|
||||
0.57176438,
|
||||
0.56637459,
|
||||
0.52248355,
|
||||
0.43696175,
|
||||
0.42992376,
|
||||
0.37700486,
|
||||
0.36126832,
|
||||
]
|
||||
)
|
||||
|
||||
if np.dtype(dtype) == np.float32:
|
||||
assert detector_extractor.scales.dtype == np.float32
|
||||
assert detector_extractor.responses.dtype == np.float32
|
||||
assert detector_extractor.orientations.dtype == np.float32
|
||||
else:
|
||||
assert detector_extractor.scales.dtype == np.float64
|
||||
assert detector_extractor.responses.dtype == np.float64
|
||||
assert detector_extractor.orientations.dtype == np.float64
|
||||
|
||||
assert_almost_equal(exp_rows, detector_extractor.keypoints[:, 0])
|
||||
assert_almost_equal(exp_cols, detector_extractor.keypoints[:, 1])
|
||||
assert_almost_equal(exp_scales, detector_extractor.scales)
|
||||
assert_almost_equal(exp_response, detector_extractor.responses, 5)
|
||||
assert_almost_equal(
|
||||
exp_orientations, np.rad2deg(detector_extractor.orientations), 4
|
||||
)
|
||||
|
||||
detector_extractor.detect_and_extract(img)
|
||||
assert_almost_equal(exp_rows, detector_extractor.keypoints[:, 0])
|
||||
assert_almost_equal(exp_cols, detector_extractor.keypoints[:, 1])
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', ['float32', 'float64', 'uint8', 'uint16', 'int64'])
|
||||
def test_keypoints_orb_less_than_desired_no_of_keypoints(dtype):
|
||||
_img = _convert(img, dtype)
|
||||
detector_extractor = ORB(
|
||||
n_keypoints=15, fast_n=12, fast_threshold=0.33, downscale=2, n_scales=2
|
||||
)
|
||||
detector_extractor.detect(_img)
|
||||
|
||||
exp_rows = np.array([108.0, 203.0, 140.0, 65.0, 58.0])
|
||||
exp_cols = np.array([293.0, 267.0, 202.0, 130.0, 291.0])
|
||||
|
||||
exp_scales = np.array([1.0, 1.0, 1.0, 1.0, 1.0])
|
||||
|
||||
exp_orientations = np.array(
|
||||
[151.93906, -56.90052, -79.46341, -59.42996, -158.26941]
|
||||
)
|
||||
|
||||
exp_response = np.array([-0.1764169, 0.2652126, -0.0324343, 0.0400902, 0.2667641])
|
||||
|
||||
assert_almost_equal(exp_rows, detector_extractor.keypoints[:, 0])
|
||||
assert_almost_equal(exp_cols, detector_extractor.keypoints[:, 1])
|
||||
assert_almost_equal(exp_scales, detector_extractor.scales)
|
||||
assert_almost_equal(exp_response, detector_extractor.responses)
|
||||
assert_almost_equal(
|
||||
exp_orientations, np.rad2deg(detector_extractor.orientations), 3
|
||||
)
|
||||
|
||||
detector_extractor.detect_and_extract(img)
|
||||
assert_almost_equal(exp_rows, detector_extractor.keypoints[:, 0])
|
||||
assert_almost_equal(exp_cols, detector_extractor.keypoints[:, 1])
|
||||
|
||||
|
||||
@xfail(
|
||||
condition=arch32,
|
||||
reason=(
|
||||
'Known test failure on 32-bit platforms. See links for '
|
||||
'details: '
|
||||
'https://github.com/scikit-image/scikit-image/issues/3091 '
|
||||
'https://github.com/scikit-image/scikit-image/issues/2529'
|
||||
),
|
||||
)
|
||||
def test_descriptor_orb():
|
||||
detector_extractor = ORB(fast_n=12, fast_threshold=0.20)
|
||||
exp_descriptors = np.array(
|
||||
[
|
||||
[0, 0, 0, 1, 0, 0, 0, 1, 0, 1],
|
||||
[1, 1, 0, 1, 0, 0, 0, 1, 0, 1],
|
||||
[1, 1, 0, 0, 1, 0, 0, 0, 1, 1],
|
||||
[1, 1, 1, 0, 0, 0, 1, 1, 1, 0],
|
||||
[0, 0, 0, 1, 0, 1, 1, 1, 1, 1],
|
||||
[1, 0, 0, 1, 1, 0, 0, 0, 1, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[1, 1, 1, 0, 1, 1, 1, 1, 0, 0],
|
||||
[1, 1, 1, 1, 0, 0, 0, 1, 1, 1],
|
||||
[0, 1, 1, 0, 0, 1, 1, 0, 1, 1],
|
||||
[1, 1, 0, 0, 0, 0, 0, 0, 1, 1],
|
||||
[1, 0, 0, 0, 0, 1, 0, 1, 1, 1],
|
||||
[1, 0, 1, 1, 1, 0, 1, 0, 1, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 0, 1, 1],
|
||||
[0, 1, 1, 0, 0, 0, 1, 0, 0, 1],
|
||||
[0, 1, 1, 0, 0, 0, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 1, 1, 1, 1, 0, 1, 1, 0],
|
||||
[0, 0, 1, 1, 1, 0, 1, 0, 0, 1],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 1, 0],
|
||||
],
|
||||
dtype=bool,
|
||||
)
|
||||
|
||||
detector_extractor.detect(img)
|
||||
detector_extractor.extract(
|
||||
img,
|
||||
detector_extractor.keypoints,
|
||||
detector_extractor.scales,
|
||||
detector_extractor.orientations,
|
||||
)
|
||||
|
||||
assert_equal(exp_descriptors, detector_extractor.descriptors[100:120, 10:20])
|
||||
|
||||
detector_extractor.detect_and_extract(img)
|
||||
assert_equal(exp_descriptors, detector_extractor.descriptors[100:120, 10:20])
|
||||
keypoints_count = detector_extractor.keypoints.shape[0]
|
||||
assert keypoints_count == detector_extractor.descriptors.shape[0]
|
||||
assert keypoints_count == detector_extractor.orientations.shape[0]
|
||||
assert keypoints_count == detector_extractor.responses.shape[0]
|
||||
assert keypoints_count == detector_extractor.scales.shape[0]
|
||||
|
||||
|
||||
def test_no_descriptors_extracted_orb():
|
||||
img = np.ones((128, 128))
|
||||
detector_extractor = ORB()
|
||||
with pytest.raises(RuntimeError):
|
||||
detector_extractor.detect_and_extract(img)
|
||||
|
||||
|
||||
def test_img_too_small_orb():
|
||||
img = data.brick()[:64, :64]
|
||||
detector_extractor = ORB(downscale=2, n_scales=8)
|
||||
detector_extractor.detect(img)
|
||||
detector_extractor.detect_and_extract(img)
|
||||
646
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_peak.py
vendored
Normal file
646
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_peak.py
vendored
Normal file
@@ -0,0 +1,646 @@
|
||||
import itertools
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy.testing import assert_array_almost_equal, assert_array_equal, assert_equal
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
from skimage._shared._warnings import expected_warnings
|
||||
from skimage.feature import peak
|
||||
|
||||
|
||||
np.random.seed(21)
|
||||
|
||||
|
||||
class TestPeakLocalMax:
|
||||
def test_trivial_case(self):
|
||||
trivial = np.zeros((25, 25))
|
||||
peak_indices = peak.peak_local_max(trivial, min_distance=1)
|
||||
assert type(peak_indices) is np.ndarray
|
||||
assert peak_indices.size == 0
|
||||
|
||||
def test_noisy_peaks(self):
|
||||
peak_locations = [(7, 7), (7, 13), (13, 7), (13, 13)]
|
||||
|
||||
# image with noise of amplitude 0.8 and peaks of amplitude 1
|
||||
image = 0.8 * np.random.rand(20, 20)
|
||||
for r, c in peak_locations:
|
||||
image[r, c] = 1
|
||||
|
||||
peaks_detected = peak.peak_local_max(image, min_distance=5)
|
||||
|
||||
assert len(peaks_detected) == len(peak_locations)
|
||||
for loc in peaks_detected:
|
||||
assert tuple(loc) in peak_locations
|
||||
|
||||
def test_relative_threshold(self):
|
||||
image = np.zeros((5, 5), dtype=np.uint8)
|
||||
image[1, 1] = 10
|
||||
image[3, 3] = 20
|
||||
peaks = peak.peak_local_max(image, min_distance=1, threshold_rel=0.5)
|
||||
assert len(peaks) == 1
|
||||
assert_array_almost_equal(peaks, [(3, 3)])
|
||||
|
||||
def test_absolute_threshold(self):
|
||||
image = np.zeros((5, 5), dtype=np.uint8)
|
||||
image[1, 1] = 10
|
||||
image[3, 3] = 20
|
||||
peaks = peak.peak_local_max(image, min_distance=1, threshold_abs=10)
|
||||
assert len(peaks) == 1
|
||||
assert_array_almost_equal(peaks, [(3, 3)])
|
||||
|
||||
def test_constant_image(self):
|
||||
image = np.full((20, 20), 128, dtype=np.uint8)
|
||||
peaks = peak.peak_local_max(image, min_distance=1)
|
||||
assert len(peaks) == 0
|
||||
|
||||
def test_flat_peak(self):
|
||||
image = np.zeros((5, 5), dtype=np.uint8)
|
||||
image[1:3, 1:3] = 10
|
||||
peaks = peak.peak_local_max(image, min_distance=1)
|
||||
assert len(peaks) == 4
|
||||
|
||||
def test_sorted_peaks(self):
|
||||
image = np.zeros((5, 5), dtype=np.uint8)
|
||||
image[1, 1] = 20
|
||||
image[3, 3] = 10
|
||||
peaks = peak.peak_local_max(image, min_distance=1)
|
||||
assert peaks.tolist() == [[1, 1], [3, 3]]
|
||||
|
||||
image = np.zeros((3, 10))
|
||||
image[1, (1, 3, 5, 7)] = (1, 2, 3, 4)
|
||||
peaks = peak.peak_local_max(image, min_distance=1)
|
||||
assert peaks.tolist() == [[1, 7], [1, 5], [1, 3], [1, 1]]
|
||||
|
||||
def test_num_peaks(self):
|
||||
image = np.zeros((7, 7), dtype=np.uint8)
|
||||
image[1, 1] = 10
|
||||
image[1, 3] = 11
|
||||
image[1, 5] = 12
|
||||
image[3, 5] = 8
|
||||
image[5, 3] = 7
|
||||
assert len(peak.peak_local_max(image, min_distance=1, threshold_abs=0)) == 5
|
||||
peaks_limited = peak.peak_local_max(
|
||||
image, min_distance=1, threshold_abs=0, num_peaks=2
|
||||
)
|
||||
assert len(peaks_limited) == 2
|
||||
assert (1, 3) in peaks_limited
|
||||
assert (1, 5) in peaks_limited
|
||||
peaks_limited = peak.peak_local_max(
|
||||
image, min_distance=1, threshold_abs=0, num_peaks=4
|
||||
)
|
||||
assert len(peaks_limited) == 4
|
||||
assert (1, 3) in peaks_limited
|
||||
assert (1, 5) in peaks_limited
|
||||
assert (1, 1) in peaks_limited
|
||||
assert (3, 5) in peaks_limited
|
||||
|
||||
def test_num_peaks_and_labels(self):
|
||||
image = np.zeros((7, 7), dtype=np.uint8)
|
||||
labels = np.zeros((7, 7), dtype=np.uint8) + 20
|
||||
image[1, 1] = 10
|
||||
image[1, 3] = 11
|
||||
image[1, 5] = 12
|
||||
image[3, 5] = 8
|
||||
image[5, 3] = 7
|
||||
peaks_limited = peak.peak_local_max(
|
||||
image, min_distance=1, threshold_abs=0, labels=labels
|
||||
)
|
||||
assert len(peaks_limited) == 5
|
||||
peaks_limited = peak.peak_local_max(
|
||||
image, min_distance=1, threshold_abs=0, labels=labels, num_peaks=2
|
||||
)
|
||||
assert len(peaks_limited) == 2
|
||||
|
||||
def test_num_peaks_tot_vs_labels_4quadrants(self):
|
||||
np.random.seed(21)
|
||||
image = np.random.uniform(size=(20, 30))
|
||||
i, j = np.mgrid[0:20, 0:30]
|
||||
labels = 1 + (i >= 10) + (j >= 15) * 2
|
||||
result = peak.peak_local_max(
|
||||
image,
|
||||
labels=labels,
|
||||
min_distance=1,
|
||||
threshold_rel=0,
|
||||
num_peaks=np.inf,
|
||||
num_peaks_per_label=2,
|
||||
)
|
||||
assert len(result) == 8
|
||||
result = peak.peak_local_max(
|
||||
image,
|
||||
labels=labels,
|
||||
min_distance=1,
|
||||
threshold_rel=0,
|
||||
num_peaks=np.inf,
|
||||
num_peaks_per_label=1,
|
||||
)
|
||||
assert len(result) == 4
|
||||
result = peak.peak_local_max(
|
||||
image,
|
||||
labels=labels,
|
||||
min_distance=1,
|
||||
threshold_rel=0,
|
||||
num_peaks=2,
|
||||
num_peaks_per_label=2,
|
||||
)
|
||||
assert len(result) == 2
|
||||
|
||||
def test_num_peaks3D(self):
|
||||
# Issue 1354: the old code only hold for 2D arrays
|
||||
# and this code would die with IndexError
|
||||
image = np.zeros((10, 10, 100))
|
||||
image[5, 5, ::5] = np.arange(20)
|
||||
peaks_limited = peak.peak_local_max(image, min_distance=1, num_peaks=2)
|
||||
assert len(peaks_limited) == 2
|
||||
|
||||
def test_reorder_labels(self):
|
||||
image = np.random.uniform(size=(40, 60))
|
||||
i, j = np.mgrid[0:40, 0:60]
|
||||
labels = 1 + (i >= 20) + (j >= 30) * 2
|
||||
labels[labels == 4] = 5
|
||||
i, j = np.mgrid[-3:4, -3:4]
|
||||
footprint = i * i + j * j <= 9
|
||||
expected = np.zeros(image.shape, float)
|
||||
for imin, imax in ((0, 20), (20, 40)):
|
||||
for jmin, jmax in ((0, 30), (30, 60)):
|
||||
expected[imin:imax, jmin:jmax] = ndi.maximum_filter(
|
||||
image[imin:imax, jmin:jmax], footprint=footprint
|
||||
)
|
||||
expected = expected == image
|
||||
peak_idx = peak.peak_local_max(
|
||||
image,
|
||||
labels=labels,
|
||||
min_distance=1,
|
||||
threshold_rel=0,
|
||||
footprint=footprint,
|
||||
exclude_border=False,
|
||||
)
|
||||
result = np.zeros_like(expected, dtype=bool)
|
||||
result[tuple(peak_idx.T)] = True
|
||||
assert (result == expected).all()
|
||||
|
||||
def test_indices_with_labels(self):
|
||||
image = np.random.uniform(size=(40, 60))
|
||||
i, j = np.mgrid[0:40, 0:60]
|
||||
labels = 1 + (i >= 20) + (j >= 30) * 2
|
||||
i, j = np.mgrid[-3:4, -3:4]
|
||||
footprint = i * i + j * j <= 9
|
||||
expected = np.zeros(image.shape, float)
|
||||
for imin, imax in ((0, 20), (20, 40)):
|
||||
for jmin, jmax in ((0, 30), (30, 60)):
|
||||
expected[imin:imax, jmin:jmax] = ndi.maximum_filter(
|
||||
image[imin:imax, jmin:jmax], footprint=footprint
|
||||
)
|
||||
expected = np.stack(np.nonzero(expected == image), axis=-1)
|
||||
expected = expected[np.argsort(image[tuple(expected.T)])[::-1]]
|
||||
result = peak.peak_local_max(
|
||||
image,
|
||||
labels=labels,
|
||||
min_distance=1,
|
||||
threshold_rel=0,
|
||||
footprint=footprint,
|
||||
exclude_border=False,
|
||||
)
|
||||
result = result[np.argsort(image[tuple(result.T)])[::-1]]
|
||||
assert (result == expected).all()
|
||||
|
||||
def test_ndarray_exclude_border(self):
|
||||
nd_image = np.zeros((5, 5, 5))
|
||||
nd_image[[1, 0, 0], [0, 1, 0], [0, 0, 1]] = 1
|
||||
nd_image[3, 0, 0] = 1
|
||||
nd_image[2, 2, 2] = 1
|
||||
expected = np.array([[2, 2, 2]], dtype=int)
|
||||
expectedNoBorder = np.array([[0, 0, 1], [2, 2, 2], [3, 0, 0]], dtype=int)
|
||||
result = peak.peak_local_max(nd_image, min_distance=2, exclude_border=2)
|
||||
assert_array_equal(result, expected)
|
||||
# Check that bools work as expected
|
||||
assert_array_equal(
|
||||
peak.peak_local_max(nd_image, min_distance=2, exclude_border=2),
|
||||
peak.peak_local_max(nd_image, min_distance=2, exclude_border=True),
|
||||
)
|
||||
assert_array_equal(
|
||||
peak.peak_local_max(nd_image, min_distance=2, exclude_border=0),
|
||||
peak.peak_local_max(nd_image, min_distance=2, exclude_border=False),
|
||||
)
|
||||
|
||||
# Check both versions with no border
|
||||
result = peak.peak_local_max(nd_image, min_distance=2, exclude_border=0)
|
||||
assert_array_equal(result, expectedNoBorder)
|
||||
peak_idx = peak.peak_local_max(nd_image, exclude_border=False)
|
||||
result = np.zeros_like(nd_image, dtype=bool)
|
||||
result[tuple(peak_idx.T)] = True
|
||||
assert_array_equal(result, nd_image.astype(bool))
|
||||
|
||||
def test_empty(self):
|
||||
image = np.zeros((10, 20))
|
||||
labels = np.zeros((10, 20), int)
|
||||
result = peak.peak_local_max(
|
||||
image,
|
||||
labels=labels,
|
||||
footprint=np.ones((3, 3), bool),
|
||||
min_distance=1,
|
||||
threshold_rel=0,
|
||||
exclude_border=False,
|
||||
)
|
||||
assert result.shape == (0, image.ndim)
|
||||
|
||||
def test_empty_non2d_indices(self):
|
||||
image = np.zeros((10, 10, 10))
|
||||
result = peak.peak_local_max(
|
||||
image,
|
||||
footprint=np.ones((3, 3, 3), bool),
|
||||
min_distance=1,
|
||||
threshold_rel=0,
|
||||
exclude_border=False,
|
||||
)
|
||||
assert result.shape == (0, image.ndim)
|
||||
|
||||
def test_one_point(self):
|
||||
image = np.zeros((10, 20))
|
||||
labels = np.zeros((10, 20), int)
|
||||
image[5, 5] = 1
|
||||
labels[5, 5] = 1
|
||||
peak_idx = peak.peak_local_max(
|
||||
image,
|
||||
labels=labels,
|
||||
footprint=np.ones((3, 3), bool),
|
||||
min_distance=1,
|
||||
threshold_rel=0,
|
||||
exclude_border=False,
|
||||
)
|
||||
result = np.zeros_like(image, dtype=bool)
|
||||
result[tuple(peak_idx.T)] = True
|
||||
assert np.all(result == (labels == 1))
|
||||
|
||||
def test_adjacent_and_same(self):
|
||||
image = np.zeros((10, 20))
|
||||
labels = np.zeros((10, 20), int)
|
||||
image[5, 5:6] = 1
|
||||
labels[5, 5:6] = 1
|
||||
expected = np.stack(np.where(labels == 1), axis=-1)
|
||||
result = peak.peak_local_max(
|
||||
image,
|
||||
labels=labels,
|
||||
footprint=np.ones((3, 3), bool),
|
||||
min_distance=1,
|
||||
threshold_rel=0,
|
||||
exclude_border=False,
|
||||
)
|
||||
assert_array_equal(result, expected)
|
||||
|
||||
def test_adjacent_and_different(self):
|
||||
image = np.zeros((10, 20))
|
||||
labels = np.zeros((10, 20), int)
|
||||
image[5, 5] = 1
|
||||
image[5, 6] = 0.5
|
||||
labels[5, 5:6] = 1
|
||||
expected = np.stack(np.where(image == 1), axis=-1)
|
||||
result = peak.peak_local_max(
|
||||
image,
|
||||
labels=labels,
|
||||
footprint=np.ones((3, 3), bool),
|
||||
min_distance=1,
|
||||
threshold_rel=0,
|
||||
exclude_border=False,
|
||||
)
|
||||
assert_array_equal(result, expected)
|
||||
result = peak.peak_local_max(
|
||||
image, labels=labels, min_distance=1, threshold_rel=0, exclude_border=False
|
||||
)
|
||||
assert_array_equal(result, expected)
|
||||
|
||||
def test_not_adjacent_and_different(self):
|
||||
image = np.zeros((10, 20))
|
||||
labels = np.zeros((10, 20), int)
|
||||
image[5, 5] = 1
|
||||
image[5, 8] = 0.5
|
||||
labels[image > 0] = 1
|
||||
expected = np.stack(np.where(labels == 1), axis=-1)
|
||||
result = peak.peak_local_max(
|
||||
image,
|
||||
labels=labels,
|
||||
footprint=np.ones((3, 3), bool),
|
||||
min_distance=1,
|
||||
threshold_rel=0,
|
||||
exclude_border=False,
|
||||
)
|
||||
assert_array_equal(result, expected)
|
||||
|
||||
def test_two_objects(self):
|
||||
image = np.zeros((10, 20))
|
||||
labels = np.zeros((10, 20), int)
|
||||
image[5, 5] = 1
|
||||
image[5, 15] = 0.5
|
||||
labels[5, 5] = 1
|
||||
labels[5, 15] = 2
|
||||
expected = np.stack(np.where(labels > 0), axis=-1)
|
||||
result = peak.peak_local_max(
|
||||
image,
|
||||
labels=labels,
|
||||
footprint=np.ones((3, 3), bool),
|
||||
min_distance=1,
|
||||
threshold_rel=0,
|
||||
exclude_border=False,
|
||||
)
|
||||
assert_array_equal(result, expected)
|
||||
|
||||
def test_adjacent_different_objects(self):
|
||||
image = np.zeros((10, 20))
|
||||
labels = np.zeros((10, 20), int)
|
||||
image[5, 5] = 1
|
||||
image[5, 6] = 0.5
|
||||
labels[5, 5] = 1
|
||||
labels[5, 6] = 2
|
||||
expected = np.stack(np.where(labels > 0), axis=-1)
|
||||
result = peak.peak_local_max(
|
||||
image,
|
||||
labels=labels,
|
||||
footprint=np.ones((3, 3), bool),
|
||||
min_distance=1,
|
||||
threshold_rel=0,
|
||||
exclude_border=False,
|
||||
)
|
||||
assert_array_equal(result, expected)
|
||||
|
||||
def test_four_quadrants(self):
|
||||
image = np.random.uniform(size=(20, 30))
|
||||
i, j = np.mgrid[0:20, 0:30]
|
||||
labels = 1 + (i >= 10) + (j >= 15) * 2
|
||||
i, j = np.mgrid[-3:4, -3:4]
|
||||
footprint = i * i + j * j <= 9
|
||||
expected = np.zeros(image.shape, float)
|
||||
for imin, imax in ((0, 10), (10, 20)):
|
||||
for jmin, jmax in ((0, 15), (15, 30)):
|
||||
expected[imin:imax, jmin:jmax] = ndi.maximum_filter(
|
||||
image[imin:imax, jmin:jmax], footprint=footprint
|
||||
)
|
||||
expected = expected == image
|
||||
peak_idx = peak.peak_local_max(
|
||||
image,
|
||||
labels=labels,
|
||||
footprint=footprint,
|
||||
min_distance=1,
|
||||
threshold_rel=0,
|
||||
exclude_border=False,
|
||||
)
|
||||
result = np.zeros_like(image, dtype=bool)
|
||||
result[tuple(peak_idx.T)] = True
|
||||
assert np.all(result == expected)
|
||||
|
||||
def test_disk(self):
|
||||
'''regression test of img-1194, footprint = [1]
|
||||
Test peak.peak_local_max when every point is a local maximum
|
||||
'''
|
||||
image = np.random.uniform(size=(10, 20))
|
||||
footprint = np.array([[1]])
|
||||
peak_idx = peak.peak_local_max(
|
||||
image,
|
||||
labels=np.ones((10, 20), int),
|
||||
footprint=footprint,
|
||||
min_distance=1,
|
||||
threshold_rel=0,
|
||||
threshold_abs=-1,
|
||||
exclude_border=False,
|
||||
)
|
||||
result = np.zeros_like(image, dtype=bool)
|
||||
result[tuple(peak_idx.T)] = True
|
||||
assert np.all(result)
|
||||
peak_idx = peak.peak_local_max(
|
||||
image, footprint=footprint, threshold_abs=-1, exclude_border=False
|
||||
)
|
||||
result = np.zeros_like(image, dtype=bool)
|
||||
result[tuple(peak_idx.T)] = True
|
||||
assert np.all(result)
|
||||
|
||||
def test_3D(self):
|
||||
image = np.zeros((30, 30, 30))
|
||||
image[15, 15, 15] = 1
|
||||
image[5, 5, 5] = 1
|
||||
assert_array_equal(
|
||||
peak.peak_local_max(image, min_distance=10, threshold_rel=0), [[15, 15, 15]]
|
||||
)
|
||||
assert_array_equal(
|
||||
peak.peak_local_max(image, min_distance=6, threshold_rel=0), [[15, 15, 15]]
|
||||
)
|
||||
assert sorted(
|
||||
peak.peak_local_max(
|
||||
image, min_distance=10, threshold_rel=0, exclude_border=False
|
||||
).tolist()
|
||||
) == [[5, 5, 5], [15, 15, 15]]
|
||||
assert sorted(
|
||||
peak.peak_local_max(image, min_distance=5, threshold_rel=0).tolist()
|
||||
) == [[5, 5, 5], [15, 15, 15]]
|
||||
|
||||
def test_4D(self):
|
||||
image = np.zeros((30, 30, 30, 30))
|
||||
image[15, 15, 15, 15] = 1
|
||||
image[5, 5, 5, 5] = 1
|
||||
assert_array_equal(
|
||||
peak.peak_local_max(image, min_distance=10, threshold_rel=0),
|
||||
[[15, 15, 15, 15]],
|
||||
)
|
||||
assert_array_equal(
|
||||
peak.peak_local_max(image, min_distance=6, threshold_rel=0),
|
||||
[[15, 15, 15, 15]],
|
||||
)
|
||||
assert sorted(
|
||||
peak.peak_local_max(
|
||||
image, min_distance=10, threshold_rel=0, exclude_border=False
|
||||
).tolist()
|
||||
) == [[5, 5, 5, 5], [15, 15, 15, 15]]
|
||||
assert sorted(
|
||||
peak.peak_local_max(image, min_distance=5, threshold_rel=0).tolist()
|
||||
) == [[5, 5, 5, 5], [15, 15, 15, 15]]
|
||||
|
||||
def test_threshold_rel_default(self):
|
||||
image = np.ones((5, 5))
|
||||
|
||||
image[2, 2] = 1
|
||||
assert len(peak.peak_local_max(image)) == 0
|
||||
|
||||
image[2, 2] = 2
|
||||
assert_array_equal(peak.peak_local_max(image), [[2, 2]])
|
||||
|
||||
image[2, 2] = 0
|
||||
with expected_warnings(["When min_distance < 1"]):
|
||||
assert len(peak.peak_local_max(image, min_distance=0)) == image.size - 1
|
||||
|
||||
def test_peak_at_border(self):
|
||||
image = np.full((10, 10), -2)
|
||||
image[2, 4] = -1
|
||||
image[3, 0] = -1
|
||||
|
||||
peaks = peak.peak_local_max(image, min_distance=3)
|
||||
assert peaks.size == 0
|
||||
|
||||
peaks = peak.peak_local_max(image, min_distance=3, exclude_border=0)
|
||||
assert len(peaks) == 2
|
||||
assert [2, 4] in peaks
|
||||
assert [3, 0] in peaks
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["indices"],
|
||||
[[indices] for indices in itertools.product(range(5), range(5))],
|
||||
)
|
||||
def test_exclude_border(indices):
|
||||
image = np.zeros((5, 5))
|
||||
image[indices] = 1
|
||||
|
||||
# exclude_border = False, means it will always be found.
|
||||
assert len(peak.peak_local_max(image, exclude_border=False)) == 1
|
||||
|
||||
# exclude_border = 0, means it will always be found.
|
||||
assert len(peak.peak_local_max(image, exclude_border=0)) == 1
|
||||
|
||||
# exclude_border = True, min_distance=1 means it will be found unless it's
|
||||
# on the edge.
|
||||
if indices[0] in (0, 4) or indices[1] in (0, 4):
|
||||
expected_peaks = 0
|
||||
else:
|
||||
expected_peaks = 1
|
||||
assert (
|
||||
len(peak.peak_local_max(image, min_distance=1, exclude_border=True))
|
||||
== expected_peaks
|
||||
)
|
||||
|
||||
# exclude_border = (1, 0) means it will be found unless it's on the edge of
|
||||
# the first dimension.
|
||||
if indices[0] in (0, 4):
|
||||
expected_peaks = 0
|
||||
else:
|
||||
expected_peaks = 1
|
||||
assert len(peak.peak_local_max(image, exclude_border=(1, 0))) == expected_peaks
|
||||
|
||||
# exclude_border = (0, 1) means it will be found unless it's on the edge of
|
||||
# the second dimension.
|
||||
if indices[1] in (0, 4):
|
||||
expected_peaks = 0
|
||||
else:
|
||||
expected_peaks = 1
|
||||
assert len(peak.peak_local_max(image, exclude_border=(0, 1))) == expected_peaks
|
||||
|
||||
|
||||
def test_exclude_border_errors():
|
||||
image = np.zeros((5, 5))
|
||||
|
||||
# exclude_border doesn't have the right cardinality.
|
||||
with pytest.raises(ValueError):
|
||||
assert peak.peak_local_max(image, exclude_border=(1,))
|
||||
|
||||
# exclude_border doesn't have the right type
|
||||
with pytest.raises(TypeError):
|
||||
assert peak.peak_local_max(image, exclude_border=1.0)
|
||||
|
||||
# exclude_border is a tuple of the right cardinality but contains
|
||||
# non-integer values.
|
||||
with pytest.raises(ValueError):
|
||||
assert peak.peak_local_max(image, exclude_border=(1, 'a'))
|
||||
|
||||
# exclude_border is a tuple of the right cardinality but contains a
|
||||
# negative value.
|
||||
with pytest.raises(ValueError):
|
||||
assert peak.peak_local_max(image, exclude_border=(1, -1))
|
||||
|
||||
# exclude_border is a negative value.
|
||||
with pytest.raises(ValueError):
|
||||
assert peak.peak_local_max(image, exclude_border=-1)
|
||||
|
||||
|
||||
def test_input_values_with_labels():
|
||||
# Issue #5235: input values may be modified when labels are used
|
||||
|
||||
img = np.random.rand(128, 128)
|
||||
labels = np.zeros((128, 128), int)
|
||||
|
||||
labels[10:20, 10:20] = 1
|
||||
labels[12:16, 12:16] = 0
|
||||
|
||||
img_before = img.copy()
|
||||
|
||||
_ = peak.peak_local_max(img, labels=labels)
|
||||
|
||||
assert_array_equal(img, img_before)
|
||||
|
||||
|
||||
class TestProminentPeaks:
|
||||
def test_isolated_peaks(self):
|
||||
image = np.zeros((15, 15))
|
||||
x0, y0, i0 = (12, 8, 1)
|
||||
x1, y1, i1 = (2, 2, 1)
|
||||
x2, y2, i2 = (5, 13, 1)
|
||||
image[y0, x0] = i0
|
||||
image[y1, x1] = i1
|
||||
image[y2, x2] = i2
|
||||
out = peak._prominent_peaks(image)
|
||||
assert len(out[0]) == 3
|
||||
for i, x, y in zip(out[0], out[1], out[2]):
|
||||
assert i in (i0, i1, i2)
|
||||
assert x in (x0, x1, x2)
|
||||
assert y in (y0, y1, y2)
|
||||
|
||||
def test_threshold(self):
|
||||
image = np.zeros((15, 15))
|
||||
x0, y0, i0 = (12, 8, 10)
|
||||
x1, y1, i1 = (2, 2, 8)
|
||||
x2, y2, i2 = (5, 13, 10)
|
||||
image[y0, x0] = i0
|
||||
image[y1, x1] = i1
|
||||
image[y2, x2] = i2
|
||||
out = peak._prominent_peaks(image, threshold=None)
|
||||
assert len(out[0]) == 3
|
||||
for i, x, y in zip(out[0], out[1], out[2]):
|
||||
assert i in (i0, i1, i2)
|
||||
assert x in (x0, x1, x2)
|
||||
out = peak._prominent_peaks(image, threshold=9)
|
||||
assert len(out[0]) == 2
|
||||
for i, x, y in zip(out[0], out[1], out[2]):
|
||||
assert i in (i0, i2)
|
||||
assert x in (x0, x2)
|
||||
assert y in (y0, y2)
|
||||
|
||||
def test_peaks_in_contact(self):
|
||||
image = np.zeros((15, 15))
|
||||
x0, y0, i0 = (8, 8, 1)
|
||||
x1, y1, i1 = (7, 7, 1) # prominent peak
|
||||
x2, y2, i2 = (6, 6, 1)
|
||||
image[y0, x0] = i0
|
||||
image[y1, x1] = i1
|
||||
image[y2, x2] = i2
|
||||
out = peak._prominent_peaks(
|
||||
image,
|
||||
min_xdistance=3,
|
||||
min_ydistance=3,
|
||||
)
|
||||
assert_equal(out[0], np.array((i1,)))
|
||||
assert_equal(out[1], np.array((x1,)))
|
||||
assert_equal(out[2], np.array((y1,)))
|
||||
|
||||
def test_input_labels_unmodified(self):
|
||||
image = np.zeros((10, 20))
|
||||
labels = np.zeros((10, 20), int)
|
||||
image[5, 5] = 1
|
||||
labels[5, 5] = 3
|
||||
labelsin = labels.copy()
|
||||
peak.peak_local_max(
|
||||
image,
|
||||
labels=labels,
|
||||
footprint=np.ones((3, 3), bool),
|
||||
min_distance=1,
|
||||
threshold_rel=0,
|
||||
exclude_border=False,
|
||||
)
|
||||
assert np.all(labels == labelsin)
|
||||
|
||||
def test_many_objects(self):
|
||||
mask = np.zeros([500, 500], dtype=bool)
|
||||
x, y = np.indices((500, 500))
|
||||
x_c = x // 20 * 20 + 10
|
||||
y_c = y // 20 * 20 + 10
|
||||
mask[(x - x_c) ** 2 + (y - y_c) ** 2 < 8**2] = True
|
||||
labels, num_objs = ndi.label(mask)
|
||||
dist = ndi.distance_transform_edt(mask)
|
||||
local_max = peak.peak_local_max(
|
||||
dist, min_distance=20, exclude_border=False, labels=labels
|
||||
)
|
||||
assert len(local_max) == 625
|
||||
179
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_sift.py
vendored
Normal file
179
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_sift.py
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy.testing import assert_almost_equal, assert_equal
|
||||
|
||||
from skimage import data
|
||||
from skimage._shared.testing import run_in_parallel
|
||||
from skimage.feature import SIFT
|
||||
from skimage.util.dtype import _convert
|
||||
|
||||
img = data.coins()
|
||||
|
||||
|
||||
@run_in_parallel()
|
||||
@pytest.mark.parametrize('dtype', ['float32', 'float64', 'uint8', 'uint16', 'int64'])
|
||||
def test_keypoints_sift(dtype):
|
||||
_img = _convert(img, dtype)
|
||||
detector_extractor = SIFT()
|
||||
detector_extractor.detect_and_extract(_img)
|
||||
|
||||
exp_keypoint_rows = np.array([18, 18, 19, 22, 26, 26, 30, 31, 31, 32])
|
||||
exp_keypoint_cols = np.array([331, 331, 325, 330, 310, 330, 205, 323, 149, 338])
|
||||
|
||||
exp_octaves = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
||||
|
||||
exp_position_rows = np.array(
|
||||
[
|
||||
17.81909936,
|
||||
17.81909936,
|
||||
19.05454661,
|
||||
21.85933727,
|
||||
25.54800708,
|
||||
26.25710504,
|
||||
29.90826307,
|
||||
30.78713806,
|
||||
30.87953572,
|
||||
31.72557969,
|
||||
]
|
||||
)
|
||||
|
||||
exp_position_cols = np.array(
|
||||
[
|
||||
331.49693187,
|
||||
331.49693187,
|
||||
325.24476016,
|
||||
330.44616424,
|
||||
310.33932904,
|
||||
330.46155224,
|
||||
204.74535177,
|
||||
322.84100812,
|
||||
149.43192282,
|
||||
337.89643013,
|
||||
]
|
||||
)
|
||||
|
||||
exp_orientations = np.array(
|
||||
[
|
||||
0.26391655,
|
||||
0.26391655,
|
||||
0.39134262,
|
||||
1.77063053,
|
||||
0.98637565,
|
||||
1.37997279,
|
||||
0.4919992,
|
||||
1.48615988,
|
||||
0.33753212,
|
||||
1.64859617,
|
||||
]
|
||||
)
|
||||
|
||||
exp_scales = np.array([2, 2, 1, 3, 3, 1, 2, 1, 1, 1])
|
||||
|
||||
exp_sigmas = np.array(
|
||||
[
|
||||
1.35160379,
|
||||
1.35160379,
|
||||
0.94551567,
|
||||
1.52377498,
|
||||
1.55173233,
|
||||
0.93973722,
|
||||
1.37594124,
|
||||
1.06663786,
|
||||
1.04827034,
|
||||
1.0378916,
|
||||
]
|
||||
)
|
||||
|
||||
exp_scalespace_sigmas = np.array(
|
||||
[
|
||||
[0.8, 1.00793684, 1.26992084, 1.6, 2.01587368, 2.53984168],
|
||||
[1.6, 2.01587368, 2.53984168, 3.2, 4.03174736, 5.07968337],
|
||||
[3.2, 4.03174736, 5.07968337, 6.4, 8.06349472, 10.15936673],
|
||||
[6.4, 8.06349472, 10.15936673, 12.8, 16.12698944, 20.31873347],
|
||||
[12.8, 16.12698944, 20.31873347, 25.6, 32.25397888, 40.63746693],
|
||||
[25.6, 32.25397888, 40.63746693, 51.2, 64.50795775, 81.27493386],
|
||||
]
|
||||
)
|
||||
|
||||
assert_almost_equal(exp_keypoint_rows, detector_extractor.keypoints[:10, 0])
|
||||
assert_almost_equal(exp_keypoint_cols, detector_extractor.keypoints[:10, 1])
|
||||
assert_almost_equal(exp_octaves, detector_extractor.octaves[:10])
|
||||
assert_almost_equal(
|
||||
exp_position_rows, detector_extractor.positions[:10, 0], decimal=4
|
||||
)
|
||||
assert_almost_equal(
|
||||
exp_position_cols, detector_extractor.positions[:10, 1], decimal=4
|
||||
)
|
||||
assert_almost_equal(
|
||||
exp_orientations, detector_extractor.orientations[:10], decimal=4
|
||||
)
|
||||
assert_almost_equal(exp_scales, detector_extractor.scales[:10])
|
||||
assert_almost_equal(exp_sigmas, detector_extractor.sigmas[:10], decimal=4)
|
||||
assert_almost_equal(
|
||||
exp_scalespace_sigmas, detector_extractor.scalespace_sigmas, decimal=4
|
||||
)
|
||||
|
||||
detector_extractor2 = SIFT()
|
||||
detector_extractor2.detect(img)
|
||||
detector_extractor2.extract(img)
|
||||
assert_almost_equal(
|
||||
detector_extractor.keypoints[:10, 0], detector_extractor2.keypoints[:10, 0]
|
||||
)
|
||||
assert_almost_equal(
|
||||
detector_extractor.keypoints[:10, 0], detector_extractor2.keypoints[:10, 0]
|
||||
)
|
||||
|
||||
|
||||
def test_descriptor_sift():
|
||||
detector_extractor = SIFT(n_hist=2, n_ori=4)
|
||||
exp_descriptors = np.array(
|
||||
[
|
||||
[173, 30, 55, 32, 173, 16, 45, 82, 173, 154, 170, 173, 173, 169, 65, 110],
|
||||
[173, 30, 55, 32, 173, 16, 45, 82, 173, 154, 170, 173, 173, 169, 65, 110],
|
||||
[189, 52, 18, 18, 189, 11, 21, 55, 189, 75, 173, 91, 189, 65, 189, 162],
|
||||
[172, 156, 185, 66, 92, 76, 78, 185, 185, 87, 88, 82, 98, 56, 96, 185],
|
||||
[216, 19, 40, 9, 196, 7, 57, 36, 216, 56, 158, 29, 216, 42, 144, 154],
|
||||
[169, 120, 169, 91, 129, 108, 169, 67, 169, 142, 111, 95, 169, 120, 69, 41],
|
||||
[199, 10, 138, 44, 178, 11, 161, 34, 199, 113, 73, 64, 199, 82, 31, 178],
|
||||
[154, 56, 154, 49, 144, 154, 154, 78, 154, 51, 154, 83, 154, 154, 154, 72],
|
||||
[230, 46, 47, 21, 230, 15, 65, 95, 230, 52, 72, 51, 230, 19, 59, 130],
|
||||
[
|
||||
155,
|
||||
117,
|
||||
154,
|
||||
102,
|
||||
155,
|
||||
155,
|
||||
90,
|
||||
110,
|
||||
145,
|
||||
127,
|
||||
155,
|
||||
50,
|
||||
57,
|
||||
155,
|
||||
155,
|
||||
70,
|
||||
],
|
||||
],
|
||||
dtype=np.uint8,
|
||||
)
|
||||
|
||||
detector_extractor.detect_and_extract(img)
|
||||
|
||||
assert_equal(exp_descriptors, detector_extractor.descriptors[:10])
|
||||
|
||||
keypoints_count = detector_extractor.keypoints.shape[0]
|
||||
assert keypoints_count == detector_extractor.descriptors.shape[0]
|
||||
assert keypoints_count == detector_extractor.orientations.shape[0]
|
||||
assert keypoints_count == detector_extractor.octaves.shape[0]
|
||||
assert keypoints_count == detector_extractor.positions.shape[0]
|
||||
assert keypoints_count == detector_extractor.scales.shape[0]
|
||||
assert keypoints_count == detector_extractor.scales.shape[0]
|
||||
|
||||
|
||||
def test_no_descriptors_extracted_sift():
|
||||
img = np.ones((128, 128))
|
||||
detector_extractor = SIFT()
|
||||
with pytest.raises(RuntimeError):
|
||||
detector_extractor.detect_and_extract(img)
|
||||
189
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_template.py
vendored
Normal file
189
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_template.py
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
import numpy as np
|
||||
from skimage._shared.testing import assert_almost_equal, assert_equal
|
||||
|
||||
from skimage import data, img_as_float
|
||||
from skimage.morphology import diamond
|
||||
from skimage.feature import match_template, peak_local_max
|
||||
from skimage._shared import testing
|
||||
|
||||
|
||||
@testing.parametrize('dtype', [np.float32, np.float64])
|
||||
def test_template(dtype):
|
||||
size = 100
|
||||
# Float prefactors ensure that image range is between 0 and 1
|
||||
image = np.full((400, 400), 0.5, dtype=dtype)
|
||||
target = 0.1 * (np.tri(size) + np.tri(size)[::-1])
|
||||
target = target.astype(dtype, copy=False)
|
||||
target_positions = [(50, 50), (200, 200)]
|
||||
for x, y in target_positions:
|
||||
image[x : x + size, y : y + size] = target
|
||||
np.random.seed(1)
|
||||
image += 0.1 * np.random.uniform(size=(400, 400)).astype(dtype, copy=False)
|
||||
|
||||
result = match_template(image, target)
|
||||
assert result.dtype == dtype
|
||||
delta = 5
|
||||
|
||||
positions = peak_local_max(result, min_distance=delta)
|
||||
|
||||
if len(positions) > 2:
|
||||
# Keep the two maximum peaks.
|
||||
intensities = result[tuple(positions.T)]
|
||||
i_maxsort = np.argsort(intensities)[::-1]
|
||||
positions = positions[i_maxsort][:2]
|
||||
|
||||
# Sort so that order matches `target_positions`.
|
||||
positions = positions[np.argsort(positions[:, 0])]
|
||||
|
||||
for xy_target, xy in zip(target_positions, positions):
|
||||
assert_almost_equal(xy, xy_target)
|
||||
|
||||
|
||||
def test_normalization():
|
||||
"""Test that `match_template` gives the correct normalization.
|
||||
|
||||
Normalization gives 1 for a perfect match and -1 for an inverted-match.
|
||||
This test adds positive and negative squares to a zero-array and matches
|
||||
the array with a positive template.
|
||||
"""
|
||||
n = 5
|
||||
N = 20
|
||||
ipos, jpos = (2, 3)
|
||||
ineg, jneg = (12, 11)
|
||||
image = np.full((N, N), 0.5)
|
||||
image[ipos : ipos + n, jpos : jpos + n] = 1
|
||||
image[ineg : ineg + n, jneg : jneg + n] = 0
|
||||
|
||||
# white square with a black border
|
||||
template = np.zeros((n + 2, n + 2))
|
||||
template[1 : 1 + n, 1 : 1 + n] = 1
|
||||
|
||||
result = match_template(image, template)
|
||||
|
||||
# get the max and min results.
|
||||
sorted_result = np.argsort(result.flat)
|
||||
iflat_min = sorted_result[0]
|
||||
iflat_max = sorted_result[-1]
|
||||
min_result = np.unravel_index(iflat_min, result.shape)
|
||||
max_result = np.unravel_index(iflat_max, result.shape)
|
||||
|
||||
# shift result by 1 because of template border
|
||||
assert np.all((np.array(min_result) + 1) == (ineg, jneg))
|
||||
assert np.all((np.array(max_result) + 1) == (ipos, jpos))
|
||||
|
||||
assert np.allclose(result.flat[iflat_min], -1)
|
||||
assert np.allclose(result.flat[iflat_max], 1)
|
||||
|
||||
|
||||
def test_no_nans():
|
||||
"""Test that `match_template` doesn't return NaN values.
|
||||
|
||||
When image values are only slightly different, floating-point errors can
|
||||
cause a subtraction inside of a square root to go negative (without an
|
||||
explicit check that was added to `match_template`).
|
||||
"""
|
||||
np.random.seed(1)
|
||||
image = 0.5 + 1e-9 * np.random.normal(size=(20, 20))
|
||||
template = np.ones((6, 6))
|
||||
template[:3, :] = 0
|
||||
result = match_template(image, template)
|
||||
assert not np.any(np.isnan(result))
|
||||
|
||||
|
||||
def test_switched_arguments():
|
||||
image = np.ones((5, 5))
|
||||
template = np.ones((3, 3))
|
||||
with testing.raises(ValueError):
|
||||
match_template(template, image)
|
||||
|
||||
|
||||
def test_pad_input():
|
||||
"""Test `match_template` when `pad_input=True`.
|
||||
|
||||
This test places two full templates (one with values lower than the image
|
||||
mean, the other higher) and two half templates, which are on the edges of
|
||||
the image. The two full templates should score the top (positive and
|
||||
negative) matches and the centers of the half templates should score 2nd.
|
||||
"""
|
||||
# Float prefactors ensure that image range is between 0 and 1
|
||||
template = 0.5 * diamond(2)
|
||||
image = 0.5 * np.ones((9, 19))
|
||||
mid = slice(2, 7)
|
||||
image[mid, :3] -= template[:, -3:] # half min template centered at 0
|
||||
image[mid, 4:9] += template # full max template centered at 6
|
||||
image[mid, -9:-4] -= template # full min template centered at 12
|
||||
image[mid, -3:] += template[:, :3] # half max template centered at 18
|
||||
|
||||
result = match_template(
|
||||
image, template, pad_input=True, constant_values=image.mean()
|
||||
)
|
||||
|
||||
# get the max and min results.
|
||||
sorted_result = np.argsort(result.flat)
|
||||
i, j = np.unravel_index(sorted_result[:2], result.shape)
|
||||
assert_equal(j, (12, 0))
|
||||
i, j = np.unravel_index(sorted_result[-2:], result.shape)
|
||||
assert_equal(j, (18, 6))
|
||||
|
||||
|
||||
def test_3d():
|
||||
np.random.seed(1)
|
||||
template = np.random.rand(3, 3, 3)
|
||||
image = np.zeros((12, 12, 12))
|
||||
|
||||
image[3:6, 5:8, 4:7] = template
|
||||
|
||||
result = match_template(image, template)
|
||||
|
||||
assert_equal(result.shape, (10, 10, 10))
|
||||
assert_equal(np.unravel_index(result.argmax(), result.shape), (3, 5, 4))
|
||||
|
||||
|
||||
def test_3d_pad_input():
|
||||
np.random.seed(1)
|
||||
template = np.random.rand(3, 3, 3)
|
||||
image = np.zeros((12, 12, 12))
|
||||
|
||||
image[3:6, 5:8, 4:7] = template
|
||||
|
||||
result = match_template(image, template, pad_input=True)
|
||||
|
||||
assert_equal(result.shape, (12, 12, 12))
|
||||
assert_equal(np.unravel_index(result.argmax(), result.shape), (4, 6, 5))
|
||||
|
||||
|
||||
def test_padding_reflect():
|
||||
template = diamond(2)
|
||||
image = np.zeros((10, 10))
|
||||
image[2:7, :3] = template[:, -3:]
|
||||
|
||||
result = match_template(image, template, pad_input=True, mode='reflect')
|
||||
|
||||
assert_equal(np.unravel_index(result.argmax(), result.shape), (4, 0))
|
||||
|
||||
|
||||
def test_wrong_input():
|
||||
image = np.ones((5, 5, 1))
|
||||
template = np.ones((3, 3))
|
||||
with testing.raises(ValueError):
|
||||
match_template(template, image)
|
||||
|
||||
image = np.ones((5, 5))
|
||||
template = np.ones((3, 3, 2))
|
||||
with testing.raises(ValueError):
|
||||
match_template(template, image)
|
||||
|
||||
image = np.ones((5, 5, 3, 3))
|
||||
template = np.ones((3, 3, 2))
|
||||
with testing.raises(ValueError):
|
||||
match_template(template, image)
|
||||
|
||||
|
||||
def test_bounding_values():
|
||||
image = img_as_float(data.page())
|
||||
template = np.zeros((3, 3))
|
||||
template[1, 1] = 1
|
||||
result = match_template(image, template)
|
||||
print(result.max())
|
||||
assert result.max() < 1 + 1e-7
|
||||
assert result.min() > -1 - 1e-7
|
||||
328
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_texture.py
vendored
Normal file
328
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_texture.py
vendored
Normal file
@@ -0,0 +1,328 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from skimage._shared.testing import expected_warnings, run_in_parallel
|
||||
from skimage.feature import (
|
||||
graycomatrix,
|
||||
graycoprops,
|
||||
local_binary_pattern,
|
||||
multiblock_lbp,
|
||||
)
|
||||
from skimage.transform import integral_image
|
||||
|
||||
|
||||
class TestGLCM:
|
||||
def setup_method(self):
|
||||
self.image = np.array(
|
||||
[[0, 0, 1, 1], [0, 0, 1, 1], [0, 2, 2, 2], [2, 2, 3, 3]], dtype=np.uint8
|
||||
)
|
||||
|
||||
@run_in_parallel()
|
||||
def test_output_angles(self):
|
||||
result = graycomatrix(
|
||||
self.image, [1], [0, np.pi / 4, np.pi / 2, 3 * np.pi / 4], 4
|
||||
)
|
||||
assert result.shape == (4, 4, 1, 4)
|
||||
expected1 = np.array(
|
||||
[[2, 2, 1, 0], [0, 2, 0, 0], [0, 0, 3, 1], [0, 0, 0, 1]], dtype=np.uint32
|
||||
)
|
||||
np.testing.assert_array_equal(result[:, :, 0, 0], expected1)
|
||||
expected2 = np.array(
|
||||
[[1, 1, 3, 0], [0, 1, 1, 0], [0, 0, 0, 2], [0, 0, 0, 0]], dtype=np.uint32
|
||||
)
|
||||
np.testing.assert_array_equal(result[:, :, 0, 1], expected2)
|
||||
expected3 = np.array(
|
||||
[[3, 0, 2, 0], [0, 2, 2, 0], [0, 0, 1, 2], [0, 0, 0, 0]], dtype=np.uint32
|
||||
)
|
||||
np.testing.assert_array_equal(result[:, :, 0, 2], expected3)
|
||||
expected4 = np.array(
|
||||
[[2, 0, 0, 0], [1, 1, 2, 0], [0, 0, 2, 1], [0, 0, 0, 0]], dtype=np.uint32
|
||||
)
|
||||
np.testing.assert_array_equal(result[:, :, 0, 3], expected4)
|
||||
|
||||
def test_output_symmetric_1(self):
|
||||
result = graycomatrix(self.image, [1], [np.pi / 2], 4, symmetric=True)
|
||||
assert result.shape == (4, 4, 1, 1)
|
||||
expected = np.array(
|
||||
[[6, 0, 2, 0], [0, 4, 2, 0], [2, 2, 2, 2], [0, 0, 2, 0]], dtype=np.uint32
|
||||
)
|
||||
np.testing.assert_array_equal(result[:, :, 0, 0], expected)
|
||||
|
||||
def test_error_raise_float(self):
|
||||
for dtype in [float, np.double, np.float16, np.float32, np.float64]:
|
||||
with pytest.raises(ValueError):
|
||||
graycomatrix(self.image.astype(dtype), [1], [np.pi], 4)
|
||||
|
||||
def test_error_raise_int_types(self):
|
||||
for dtype in [np.int16, np.int32, np.int64, np.uint16, np.uint32, np.uint64]:
|
||||
with pytest.raises(ValueError):
|
||||
graycomatrix(self.image.astype(dtype), [1], [np.pi])
|
||||
|
||||
def test_error_raise_negative(self):
|
||||
with pytest.raises(ValueError):
|
||||
graycomatrix(self.image.astype(np.int16) - 1, [1], [np.pi], 4)
|
||||
|
||||
def test_error_raise_levels_smaller_max(self):
|
||||
with pytest.raises(ValueError):
|
||||
graycomatrix(self.image - 1, [1], [np.pi], 3)
|
||||
|
||||
def test_image_data_types(self):
|
||||
for dtype in [np.uint16, np.uint32, np.uint64, np.int16, np.int32, np.int64]:
|
||||
img = self.image.astype(dtype)
|
||||
result = graycomatrix(img, [1], [np.pi / 2], 4, symmetric=True)
|
||||
assert result.shape == (4, 4, 1, 1)
|
||||
expected = np.array(
|
||||
[[6, 0, 2, 0], [0, 4, 2, 0], [2, 2, 2, 2], [0, 0, 2, 0]],
|
||||
dtype=np.uint32,
|
||||
)
|
||||
np.testing.assert_array_equal(result[:, :, 0, 0], expected)
|
||||
|
||||
return
|
||||
|
||||
def test_output_distance(self):
|
||||
im = np.array(
|
||||
[[0, 0, 0, 0], [1, 0, 0, 1], [2, 0, 0, 2], [3, 0, 0, 3]], dtype=np.uint8
|
||||
)
|
||||
result = graycomatrix(im, [3], [0], 4, symmetric=False)
|
||||
expected = np.array(
|
||||
[[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], dtype=np.uint32
|
||||
)
|
||||
np.testing.assert_array_equal(result[:, :, 0, 0], expected)
|
||||
|
||||
def test_output_combo(self):
|
||||
im = np.array([[0], [1], [2], [3]], dtype=np.uint8)
|
||||
result = graycomatrix(im, [1, 2], [0, np.pi / 2], 4)
|
||||
assert result.shape == (4, 4, 2, 2)
|
||||
|
||||
z = np.zeros((4, 4), dtype=np.uint32)
|
||||
e1 = np.array(
|
||||
[[0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], [0, 0, 0, 0]], dtype=np.uint32
|
||||
)
|
||||
e2 = np.array(
|
||||
[[0, 0, 1, 0], [0, 0, 0, 1], [0, 0, 0, 0], [0, 0, 0, 0]], dtype=np.uint32
|
||||
)
|
||||
|
||||
np.testing.assert_array_equal(result[:, :, 0, 0], z)
|
||||
np.testing.assert_array_equal(result[:, :, 1, 0], z)
|
||||
np.testing.assert_array_equal(result[:, :, 0, 1], e1)
|
||||
np.testing.assert_array_equal(result[:, :, 1, 1], e2)
|
||||
|
||||
def test_output_empty(self):
|
||||
result = graycomatrix(self.image, [10], [0], 4)
|
||||
np.testing.assert_array_equal(
|
||||
result[:, :, 0, 0], np.zeros((4, 4), dtype=np.uint32)
|
||||
)
|
||||
result = graycomatrix(self.image, [10], [0], 4, normed=True)
|
||||
np.testing.assert_array_equal(
|
||||
result[:, :, 0, 0], np.zeros((4, 4), dtype=np.uint32)
|
||||
)
|
||||
|
||||
def test_normed_symmetric(self):
|
||||
result = graycomatrix(
|
||||
self.image, [1, 2, 3], [0, np.pi / 2, np.pi], 4, normed=True, symmetric=True
|
||||
)
|
||||
for d in range(result.shape[2]):
|
||||
for a in range(result.shape[3]):
|
||||
np.testing.assert_almost_equal(result[:, :, d, a].sum(), 1.0)
|
||||
np.testing.assert_array_equal(
|
||||
result[:, :, d, a], result[:, :, d, a].transpose()
|
||||
)
|
||||
|
||||
def test_contrast(self):
|
||||
result = graycomatrix(self.image, [1, 2], [0], 4, normed=True, symmetric=True)
|
||||
result = np.round(result, 3)
|
||||
contrast = graycoprops(result, 'contrast')
|
||||
np.testing.assert_almost_equal(contrast[0, 0], 0.585, decimal=3)
|
||||
|
||||
def test_dissimilarity(self):
|
||||
result = graycomatrix(
|
||||
self.image, [1], [0, np.pi / 2], 4, normed=True, symmetric=True
|
||||
)
|
||||
result = np.round(result, 3)
|
||||
dissimilarity = graycoprops(result, 'dissimilarity')
|
||||
np.testing.assert_almost_equal(dissimilarity[0, 0], 0.418, decimal=3)
|
||||
|
||||
def test_dissimilarity_2(self):
|
||||
result = graycomatrix(
|
||||
self.image, [1, 3], [np.pi / 2], 4, normed=True, symmetric=True
|
||||
)
|
||||
result = np.round(result, 3)
|
||||
dissimilarity = graycoprops(result, 'dissimilarity')[0, 0]
|
||||
np.testing.assert_almost_equal(dissimilarity, 0.665, decimal=3)
|
||||
|
||||
def test_non_normalized_glcm(self):
|
||||
img = (np.random.random((100, 100)) * 8).astype(np.uint8)
|
||||
p = graycomatrix(img, [1, 2, 4, 5], [0, 0.25, 1, 1.5], levels=8)
|
||||
np.testing.assert_(np.max(graycoprops(p, 'correlation')) < 1.0)
|
||||
|
||||
def test_invalid_property(self):
|
||||
result = graycomatrix(self.image, [1], [0], 4)
|
||||
with pytest.raises(ValueError):
|
||||
graycoprops(result, 'ABC')
|
||||
|
||||
def test_homogeneity(self):
|
||||
result = graycomatrix(self.image, [1], [0, 6], 4, normed=True, symmetric=True)
|
||||
homogeneity = graycoprops(result, 'homogeneity')[0, 0]
|
||||
np.testing.assert_almost_equal(homogeneity, 0.80833333)
|
||||
|
||||
def test_energy(self):
|
||||
result = graycomatrix(self.image, [1], [0, 4], 4, normed=True, symmetric=True)
|
||||
energy = graycoprops(result, 'energy')[0, 0]
|
||||
np.testing.assert_almost_equal(energy, 0.38188131)
|
||||
|
||||
def test_correlation(self):
|
||||
result = graycomatrix(self.image, [1, 2], [0], 4, normed=True, symmetric=True)
|
||||
energy = graycoprops(result, 'correlation')
|
||||
np.testing.assert_almost_equal(energy[0, 0], 0.71953255)
|
||||
np.testing.assert_almost_equal(energy[1, 0], 0.41176470)
|
||||
|
||||
def test_uniform_properties(self):
|
||||
im = np.ones((4, 4), dtype=np.uint8)
|
||||
result = graycomatrix(
|
||||
im, [1, 2, 8], [0, np.pi / 2], 4, normed=True, symmetric=True
|
||||
)
|
||||
for prop in [
|
||||
'contrast',
|
||||
'dissimilarity',
|
||||
'homogeneity',
|
||||
'energy',
|
||||
'correlation',
|
||||
'ASM',
|
||||
]:
|
||||
graycoprops(result, prop)
|
||||
|
||||
|
||||
class TestLBP:
|
||||
def setup_method(self):
|
||||
self.image = np.array(
|
||||
[
|
||||
[255, 6, 255, 0, 141, 0],
|
||||
[48, 250, 204, 166, 223, 63],
|
||||
[8, 0, 159, 50, 255, 30],
|
||||
[167, 255, 63, 40, 128, 255],
|
||||
[0, 255, 30, 34, 255, 24],
|
||||
[146, 241, 255, 0, 189, 126],
|
||||
],
|
||||
dtype=np.uint8,
|
||||
)
|
||||
|
||||
@run_in_parallel()
|
||||
def test_default(self):
|
||||
lbp = local_binary_pattern(self.image, 8, 1, 'default')
|
||||
ref = np.array(
|
||||
[
|
||||
[0, 251, 0, 255, 96, 255],
|
||||
[143, 0, 20, 153, 64, 56],
|
||||
[238, 255, 12, 191, 0, 252],
|
||||
[129, 64.0, 62, 159, 199, 0],
|
||||
[255, 4, 255, 175, 0, 254],
|
||||
[3, 5, 0, 255, 4, 24],
|
||||
]
|
||||
)
|
||||
np.testing.assert_array_equal(lbp, ref)
|
||||
|
||||
def test_ror(self):
|
||||
lbp = local_binary_pattern(self.image, 8, 1, 'ror')
|
||||
ref = np.array(
|
||||
[
|
||||
[0, 127, 0, 255, 3, 255],
|
||||
[31, 0, 5, 51, 1, 7],
|
||||
[119, 255, 3, 127, 0, 63],
|
||||
[3, 1, 31, 63, 31, 0],
|
||||
[255, 1, 255, 95, 0, 127],
|
||||
[3, 5, 0, 255, 1, 3],
|
||||
]
|
||||
)
|
||||
np.testing.assert_array_equal(lbp, ref)
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_float_warning(self, dtype):
|
||||
image = self.image.astype(dtype)
|
||||
msg = "Applying `local_binary_pattern` to floating-point images"
|
||||
with expected_warnings([msg]):
|
||||
lbp = local_binary_pattern(image, 8, 1, 'ror')
|
||||
ref = np.array(
|
||||
[
|
||||
[0, 127, 0, 255, 3, 255],
|
||||
[31, 0, 5, 51, 1, 7],
|
||||
[119, 255, 3, 127, 0, 63],
|
||||
[3, 1, 31, 63, 31, 0],
|
||||
[255, 1, 255, 95, 0, 127],
|
||||
[3, 5, 0, 255, 1, 3],
|
||||
]
|
||||
)
|
||||
np.testing.assert_array_equal(lbp, ref)
|
||||
|
||||
def test_uniform(self):
|
||||
lbp = local_binary_pattern(self.image, 8, 1, 'uniform')
|
||||
ref = np.array(
|
||||
[
|
||||
[0, 7, 0, 8, 2, 8],
|
||||
[5, 0, 9, 9, 1, 3],
|
||||
[9, 8, 2, 7, 0, 6],
|
||||
[2, 1, 5, 6, 5, 0],
|
||||
[8, 1, 8, 9, 0, 7],
|
||||
[2, 9, 0, 8, 1, 2],
|
||||
]
|
||||
)
|
||||
np.testing.assert_array_equal(lbp, ref)
|
||||
|
||||
def test_var(self):
|
||||
# Test idea: mean of variance is estimate of overall variance.
|
||||
|
||||
# Fix random seed for test stability.
|
||||
np.random.seed(13141516)
|
||||
|
||||
# Create random image with known variance.
|
||||
image = np.random.rand(500, 500)
|
||||
target_std = 0.3
|
||||
image = image / image.std() * target_std
|
||||
|
||||
# Use P=4 to avoid interpolation effects
|
||||
P, R = 4, 1
|
||||
msg = "Applying `local_binary_pattern` to floating-point images"
|
||||
with expected_warnings([msg]):
|
||||
lbp = local_binary_pattern(image, P, R, 'var')
|
||||
|
||||
# Take central part to avoid border effect.
|
||||
lbp = lbp[5:-5, 5:-5]
|
||||
|
||||
# The LBP variance is biased (ddof=0), correct for that.
|
||||
expected = target_std**2 * (P - 1) / P
|
||||
|
||||
np.testing.assert_almost_equal(lbp.mean(), expected, 4)
|
||||
|
||||
def test_nri_uniform(self):
|
||||
lbp = local_binary_pattern(self.image, 8, 1, 'nri_uniform')
|
||||
ref = np.array(
|
||||
[
|
||||
[0, 54, 0, 57, 12, 57],
|
||||
[34, 0, 58, 58, 3, 22],
|
||||
[58, 57, 15, 50, 0, 47],
|
||||
[10, 3, 40, 42, 35, 0],
|
||||
[57, 7, 57, 58, 0, 56],
|
||||
[9, 58, 0, 57, 7, 14],
|
||||
]
|
||||
)
|
||||
np.testing.assert_array_almost_equal(lbp, ref)
|
||||
|
||||
|
||||
class TestMBLBP:
|
||||
def test_single_mblbp(self):
|
||||
# Create dummy matrix where first and fifth rectangles have greater
|
||||
# value than the central one. Therefore, the following bits
|
||||
# should be 1.
|
||||
test_img = np.zeros((9, 9), dtype='uint8')
|
||||
test_img[3:6, 3:6] = 1
|
||||
test_img[:3, :3] = 255
|
||||
test_img[6:, 6:] = 255
|
||||
|
||||
# MB-LBP is filled in reverse order. So the first and fifth bits from
|
||||
# the end should be filled.
|
||||
correct_answer = 0b10001000
|
||||
|
||||
int_img = integral_image(test_img)
|
||||
|
||||
lbp_code = multiblock_lbp(int_img, 0, 0, 3, 3)
|
||||
|
||||
np.testing.assert_equal(lbp_code, correct_answer)
|
||||
177
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_util.py
vendored
Normal file
177
.CondaPkg/env/Lib/site-packages/skimage/feature/tests/test_util.py
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from skimage._shared._dependency_checks import has_mpl
|
||||
from skimage.feature.util import (
|
||||
FeatureDetector,
|
||||
DescriptorExtractor,
|
||||
_prepare_grayscale_input_2D,
|
||||
_mask_border_keypoints,
|
||||
plot_matches,
|
||||
plot_matched_features,
|
||||
)
|
||||
|
||||
|
||||
def test_feature_detector():
|
||||
with pytest.raises(NotImplementedError):
|
||||
FeatureDetector().detect(None)
|
||||
|
||||
|
||||
def test_descriptor_extractor():
|
||||
with pytest.raises(NotImplementedError):
|
||||
DescriptorExtractor().extract(None, None)
|
||||
|
||||
|
||||
def test_prepare_grayscale_input_2D():
|
||||
with pytest.raises(ValueError):
|
||||
_prepare_grayscale_input_2D(np.zeros((3, 3, 3)))
|
||||
with pytest.raises(ValueError):
|
||||
_prepare_grayscale_input_2D(np.zeros((3, 1)))
|
||||
with pytest.raises(ValueError):
|
||||
_prepare_grayscale_input_2D(np.zeros((3, 1, 1)))
|
||||
_prepare_grayscale_input_2D(np.zeros((3, 3)))
|
||||
_prepare_grayscale_input_2D(np.zeros((3, 3, 1)))
|
||||
_prepare_grayscale_input_2D(np.zeros((1, 3, 3)))
|
||||
|
||||
|
||||
def test_mask_border_keypoints():
|
||||
keypoints = np.array([[0, 0], [1, 1], [2, 2], [3, 3], [4, 4]])
|
||||
np.testing.assert_equal(
|
||||
_mask_border_keypoints((10, 10), keypoints, 0), [1, 1, 1, 1, 1]
|
||||
)
|
||||
np.testing.assert_equal(
|
||||
_mask_border_keypoints((10, 10), keypoints, 2), [0, 0, 1, 1, 1]
|
||||
)
|
||||
np.testing.assert_equal(
|
||||
_mask_border_keypoints((4, 4), keypoints, 2), [0, 0, 1, 0, 0]
|
||||
)
|
||||
np.testing.assert_equal(
|
||||
_mask_border_keypoints((10, 10), keypoints, 5), [0, 0, 0, 0, 0]
|
||||
)
|
||||
np.testing.assert_equal(
|
||||
_mask_border_keypoints((10, 10), keypoints, 4), [0, 0, 0, 0, 1]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(not has_mpl, reason="Matplotlib not installed")
|
||||
@pytest.mark.parametrize(
|
||||
"shapes",
|
||||
[
|
||||
((10, 10), (10, 10)),
|
||||
((10, 10), (12, 10)),
|
||||
((10, 10), (10, 12)),
|
||||
((10, 10), (12, 12)),
|
||||
((12, 10), (10, 10)),
|
||||
((10, 12), (10, 10)),
|
||||
((12, 12), (10, 10)),
|
||||
],
|
||||
)
|
||||
def test_plot_matches(shapes):
|
||||
from matplotlib import pyplot as plt
|
||||
from matplotlib import use
|
||||
|
||||
use('Agg')
|
||||
|
||||
fig, ax = plt.subplots(nrows=1, ncols=1)
|
||||
|
||||
keypoints1 = 10 * np.random.rand(10, 2)
|
||||
keypoints2 = 10 * np.random.rand(10, 2)
|
||||
idxs1 = np.random.randint(10, size=10)
|
||||
idxs2 = np.random.randint(10, size=10)
|
||||
matches = np.column_stack((idxs1, idxs2))
|
||||
|
||||
shape1, shape2 = shapes
|
||||
img1 = np.zeros(shape1)
|
||||
img2 = np.zeros(shape2)
|
||||
with pytest.warns(FutureWarning):
|
||||
plot_matches(ax, img1, img2, keypoints1, keypoints2, matches)
|
||||
with pytest.warns(FutureWarning):
|
||||
plot_matches(ax, img1, img2, keypoints1, keypoints2, matches, only_matches=True)
|
||||
with pytest.warns(FutureWarning):
|
||||
plot_matches(
|
||||
ax, img1, img2, keypoints1, keypoints2, matches, keypoints_color='r'
|
||||
)
|
||||
with pytest.warns(FutureWarning):
|
||||
plot_matches(ax, img1, img2, keypoints1, keypoints2, matches, matches_color='r')
|
||||
with pytest.warns(FutureWarning):
|
||||
plot_matches(
|
||||
ax, img1, img2, keypoints1, keypoints2, matches, alignment='vertical'
|
||||
)
|
||||
plt.close()
|
||||
|
||||
|
||||
@pytest.mark.skipif(not has_mpl, reason="Matplotlib not installed")
|
||||
@pytest.mark.parametrize(
|
||||
"shapes",
|
||||
[
|
||||
((10, 10), (10, 10)),
|
||||
((10, 10), (12, 10)),
|
||||
((10, 10), (10, 12)),
|
||||
((10, 10), (12, 12)),
|
||||
((12, 10), (10, 10)),
|
||||
((10, 12), (10, 10)),
|
||||
((12, 12), (10, 10)),
|
||||
],
|
||||
)
|
||||
def test_plot_matched_features(shapes):
|
||||
from matplotlib import pyplot as plt
|
||||
from matplotlib import use
|
||||
|
||||
use('Agg')
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
|
||||
keypoints0 = 10 * np.random.rand(10, 2)
|
||||
keypoints1 = 10 * np.random.rand(10, 2)
|
||||
idxs0 = np.random.randint(10, size=10)
|
||||
idxs1 = np.random.randint(10, size=10)
|
||||
matches = np.column_stack((idxs0, idxs1))
|
||||
|
||||
shape0, shape1 = shapes
|
||||
img0 = np.zeros(shape0)
|
||||
img1 = np.zeros(shape1)
|
||||
plot_matched_features(
|
||||
img0,
|
||||
img1,
|
||||
keypoints0=keypoints0,
|
||||
keypoints1=keypoints1,
|
||||
matches=matches,
|
||||
ax=ax,
|
||||
)
|
||||
plot_matched_features(
|
||||
img0,
|
||||
img1,
|
||||
ax=ax,
|
||||
keypoints0=keypoints0,
|
||||
keypoints1=keypoints1,
|
||||
matches=matches,
|
||||
only_matches=True,
|
||||
)
|
||||
plot_matched_features(
|
||||
img0,
|
||||
img1,
|
||||
ax=ax,
|
||||
keypoints0=keypoints0,
|
||||
keypoints1=keypoints1,
|
||||
matches=matches,
|
||||
keypoints_color='r',
|
||||
)
|
||||
plot_matched_features(
|
||||
img0,
|
||||
img1,
|
||||
ax=ax,
|
||||
keypoints0=keypoints0,
|
||||
keypoints1=keypoints1,
|
||||
matches=matches,
|
||||
matches_color='r',
|
||||
)
|
||||
plot_matched_features(
|
||||
img0,
|
||||
img1,
|
||||
ax=ax,
|
||||
keypoints0=keypoints0,
|
||||
keypoints1=keypoints1,
|
||||
matches=matches,
|
||||
alignment='vertical',
|
||||
)
|
||||
plt.close()
|
||||
537
.CondaPkg/env/Lib/site-packages/skimage/feature/texture.py
vendored
Normal file
537
.CondaPkg/env/Lib/site-packages/skimage/feature/texture.py
vendored
Normal file
@@ -0,0 +1,537 @@
|
||||
"""
|
||||
Methods to characterize image textures.
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .._shared.utils import check_nD
|
||||
from ..color import gray2rgb
|
||||
from ..util import img_as_float
|
||||
from ._texture import _glcm_loop, _local_binary_pattern, _multiblock_lbp
|
||||
|
||||
|
||||
def graycomatrix(image, distances, angles, levels=None, symmetric=False, normed=False):
|
||||
"""Calculate the gray-level co-occurrence matrix.
|
||||
|
||||
A gray level co-occurrence matrix is a histogram of co-occurring
|
||||
grayscale values at a given offset over an image.
|
||||
|
||||
.. versionchanged:: 0.19
|
||||
`greymatrix` was renamed to `graymatrix` in 0.19.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : array_like
|
||||
Integer typed input image. Only positive valued images are supported.
|
||||
If type is other than uint8, the argument `levels` needs to be set.
|
||||
distances : array_like
|
||||
List of pixel pair distance offsets.
|
||||
angles : array_like
|
||||
List of pixel pair angles in radians.
|
||||
levels : int, optional
|
||||
The input image should contain integers in [0, `levels`-1],
|
||||
where levels indicate the number of gray-levels counted
|
||||
(typically 256 for an 8-bit image). This argument is required for
|
||||
16-bit images or higher and is typically the maximum of the image.
|
||||
As the output matrix is at least `levels` x `levels`, it might
|
||||
be preferable to use binning of the input image rather than
|
||||
large values for `levels`.
|
||||
symmetric : bool, optional
|
||||
If True, the output matrix `P[:, :, d, theta]` is symmetric. This
|
||||
is accomplished by ignoring the order of value pairs, so both
|
||||
(i, j) and (j, i) are accumulated when (i, j) is encountered
|
||||
for a given offset. The default is False.
|
||||
normed : bool, optional
|
||||
If True, normalize each matrix `P[:, :, d, theta]` by dividing
|
||||
by the total number of accumulated co-occurrences for the given
|
||||
offset. The elements of the resulting matrix sum to 1. The
|
||||
default is False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
P : 4-D ndarray
|
||||
The gray-level co-occurrence histogram. The value
|
||||
`P[i,j,d,theta]` is the number of times that gray-level `j`
|
||||
occurs at a distance `d` and at an angle `theta` from
|
||||
gray-level `i`. If `normed` is `False`, the output is of
|
||||
type uint32, otherwise it is float64. The dimensions are:
|
||||
levels x levels x number of distances x number of angles.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] M. Hall-Beyer, 2007. GLCM Texture: A Tutorial
|
||||
https://prism.ucalgary.ca/handle/1880/51900
|
||||
DOI:`10.11575/PRISM/33280`
|
||||
.. [2] R.M. Haralick, K. Shanmugam, and I. Dinstein, "Textural features for
|
||||
image classification", IEEE Transactions on Systems, Man, and
|
||||
Cybernetics, vol. SMC-3, no. 6, pp. 610-621, Nov. 1973.
|
||||
:DOI:`10.1109/TSMC.1973.4309314`
|
||||
.. [3] M. Nadler and E.P. Smith, Pattern Recognition Engineering,
|
||||
Wiley-Interscience, 1993.
|
||||
.. [4] Wikipedia, https://en.wikipedia.org/wiki/Co-occurrence_matrix
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
Compute 2 GLCMs: One for a 1-pixel offset to the right, and one
|
||||
for a 1-pixel offset upwards.
|
||||
|
||||
>>> image = np.array([[0, 0, 1, 1],
|
||||
... [0, 0, 1, 1],
|
||||
... [0, 2, 2, 2],
|
||||
... [2, 2, 3, 3]], dtype=np.uint8)
|
||||
>>> result = graycomatrix(image, [1], [0, np.pi/4, np.pi/2, 3*np.pi/4],
|
||||
... levels=4)
|
||||
>>> result[:, :, 0, 0]
|
||||
array([[2, 2, 1, 0],
|
||||
[0, 2, 0, 0],
|
||||
[0, 0, 3, 1],
|
||||
[0, 0, 0, 1]], dtype=uint32)
|
||||
>>> result[:, :, 0, 1]
|
||||
array([[1, 1, 3, 0],
|
||||
[0, 1, 1, 0],
|
||||
[0, 0, 0, 2],
|
||||
[0, 0, 0, 0]], dtype=uint32)
|
||||
>>> result[:, :, 0, 2]
|
||||
array([[3, 0, 2, 0],
|
||||
[0, 2, 2, 0],
|
||||
[0, 0, 1, 2],
|
||||
[0, 0, 0, 0]], dtype=uint32)
|
||||
>>> result[:, :, 0, 3]
|
||||
array([[2, 0, 0, 0],
|
||||
[1, 1, 2, 0],
|
||||
[0, 0, 2, 1],
|
||||
[0, 0, 0, 0]], dtype=uint32)
|
||||
|
||||
"""
|
||||
check_nD(image, 2)
|
||||
check_nD(distances, 1, 'distances')
|
||||
check_nD(angles, 1, 'angles')
|
||||
|
||||
image = np.ascontiguousarray(image)
|
||||
|
||||
image_max = image.max()
|
||||
|
||||
if np.issubdtype(image.dtype, np.floating):
|
||||
raise ValueError(
|
||||
"Float images are not supported by graycomatrix. "
|
||||
"Convert the image to an unsigned integer type."
|
||||
)
|
||||
|
||||
# for image type > 8bit, levels must be set.
|
||||
if image.dtype not in (np.uint8, np.int8) and levels is None:
|
||||
raise ValueError(
|
||||
"The levels argument is required for data types "
|
||||
"other than uint8. The resulting matrix will be at "
|
||||
"least levels ** 2 in size."
|
||||
)
|
||||
|
||||
if np.issubdtype(image.dtype, np.signedinteger) and np.any(image < 0):
|
||||
raise ValueError("Negative-valued images are not supported.")
|
||||
|
||||
if levels is None:
|
||||
levels = 256
|
||||
|
||||
if image_max >= levels:
|
||||
raise ValueError(
|
||||
"The maximum grayscale value in the image should be "
|
||||
"smaller than the number of levels."
|
||||
)
|
||||
|
||||
distances = np.ascontiguousarray(distances, dtype=np.float64)
|
||||
angles = np.ascontiguousarray(angles, dtype=np.float64)
|
||||
|
||||
P = np.zeros(
|
||||
(levels, levels, len(distances), len(angles)), dtype=np.uint32, order='C'
|
||||
)
|
||||
|
||||
# count co-occurences
|
||||
_glcm_loop(image, distances, angles, levels, P)
|
||||
|
||||
# make each GLMC symmetric
|
||||
if symmetric:
|
||||
Pt = np.transpose(P, (1, 0, 2, 3))
|
||||
P = P + Pt
|
||||
|
||||
# normalize each GLCM
|
||||
if normed:
|
||||
P = P.astype(np.float64)
|
||||
glcm_sums = np.sum(P, axis=(0, 1), keepdims=True)
|
||||
glcm_sums[glcm_sums == 0] = 1
|
||||
P /= glcm_sums
|
||||
|
||||
return P
|
||||
|
||||
|
||||
def graycoprops(P, prop='contrast'):
|
||||
"""Calculate texture properties of a GLCM.
|
||||
|
||||
Compute a feature of a gray level co-occurrence matrix to serve as
|
||||
a compact summary of the matrix. The properties are computed as
|
||||
follows:
|
||||
|
||||
- 'contrast': :math:`\\sum_{i,j=0}^{levels-1} P_{i,j}(i-j)^2`
|
||||
- 'dissimilarity': :math:`\\sum_{i,j=0}^{levels-1}P_{i,j}|i-j|`
|
||||
- 'homogeneity': :math:`\\sum_{i,j=0}^{levels-1}\\frac{P_{i,j}}{1+(i-j)^2}`
|
||||
- 'ASM': :math:`\\sum_{i,j=0}^{levels-1} P_{i,j}^2`
|
||||
- 'energy': :math:`\\sqrt{ASM}`
|
||||
- 'correlation':
|
||||
.. math:: \\sum_{i,j=0}^{levels-1} P_{i,j}\\left[\\frac{(i-\\mu_i) \\
|
||||
(j-\\mu_j)}{\\sqrt{(\\sigma_i^2)(\\sigma_j^2)}}\\right]
|
||||
|
||||
Each GLCM is normalized to have a sum of 1 before the computation of
|
||||
texture properties.
|
||||
|
||||
.. versionchanged:: 0.19
|
||||
`greycoprops` was renamed to `graycoprops` in 0.19.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
P : ndarray
|
||||
Input array. `P` is the gray-level co-occurrence histogram
|
||||
for which to compute the specified property. The value
|
||||
`P[i,j,d,theta]` is the number of times that gray-level j
|
||||
occurs at a distance d and at an angle theta from
|
||||
gray-level i.
|
||||
prop : {'contrast', 'dissimilarity', 'homogeneity', 'energy', \
|
||||
'correlation', 'ASM'}, optional
|
||||
The property of the GLCM to compute. The default is 'contrast'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
results : 2-D ndarray
|
||||
2-dimensional array. `results[d, a]` is the property 'prop' for
|
||||
the d'th distance and the a'th angle.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] M. Hall-Beyer, 2007. GLCM Texture: A Tutorial v. 1.0 through 3.0.
|
||||
The GLCM Tutorial Home Page,
|
||||
https://prism.ucalgary.ca/handle/1880/51900
|
||||
DOI:`10.11575/PRISM/33280`
|
||||
|
||||
Examples
|
||||
--------
|
||||
Compute the contrast for GLCMs with distances [1, 2] and angles
|
||||
[0 degrees, 90 degrees]
|
||||
|
||||
>>> image = np.array([[0, 0, 1, 1],
|
||||
... [0, 0, 1, 1],
|
||||
... [0, 2, 2, 2],
|
||||
... [2, 2, 3, 3]], dtype=np.uint8)
|
||||
>>> g = graycomatrix(image, [1, 2], [0, np.pi/2], levels=4,
|
||||
... normed=True, symmetric=True)
|
||||
>>> contrast = graycoprops(g, 'contrast')
|
||||
>>> contrast
|
||||
array([[0.58333333, 1. ],
|
||||
[1.25 , 2.75 ]])
|
||||
|
||||
"""
|
||||
check_nD(P, 4, 'P')
|
||||
|
||||
(num_level, num_level2, num_dist, num_angle) = P.shape
|
||||
if num_level != num_level2:
|
||||
raise ValueError('num_level and num_level2 must be equal.')
|
||||
if num_dist <= 0:
|
||||
raise ValueError('num_dist must be positive.')
|
||||
if num_angle <= 0:
|
||||
raise ValueError('num_angle must be positive.')
|
||||
|
||||
# normalize each GLCM
|
||||
P = P.astype(np.float64)
|
||||
glcm_sums = np.sum(P, axis=(0, 1), keepdims=True)
|
||||
glcm_sums[glcm_sums == 0] = 1
|
||||
P /= glcm_sums
|
||||
|
||||
# create weights for specified property
|
||||
I, J = np.ogrid[0:num_level, 0:num_level]
|
||||
if prop == 'contrast':
|
||||
weights = (I - J) ** 2
|
||||
elif prop == 'dissimilarity':
|
||||
weights = np.abs(I - J)
|
||||
elif prop == 'homogeneity':
|
||||
weights = 1.0 / (1.0 + (I - J) ** 2)
|
||||
elif prop in ['ASM', 'energy', 'correlation']:
|
||||
pass
|
||||
else:
|
||||
raise ValueError(f'{prop} is an invalid property')
|
||||
|
||||
# compute property for each GLCM
|
||||
if prop == 'energy':
|
||||
asm = np.sum(P**2, axis=(0, 1))
|
||||
results = np.sqrt(asm)
|
||||
elif prop == 'ASM':
|
||||
results = np.sum(P**2, axis=(0, 1))
|
||||
elif prop == 'correlation':
|
||||
results = np.zeros((num_dist, num_angle), dtype=np.float64)
|
||||
I = np.array(range(num_level)).reshape((num_level, 1, 1, 1))
|
||||
J = np.array(range(num_level)).reshape((1, num_level, 1, 1))
|
||||
diff_i = I - np.sum(I * P, axis=(0, 1))
|
||||
diff_j = J - np.sum(J * P, axis=(0, 1))
|
||||
|
||||
std_i = np.sqrt(np.sum(P * (diff_i) ** 2, axis=(0, 1)))
|
||||
std_j = np.sqrt(np.sum(P * (diff_j) ** 2, axis=(0, 1)))
|
||||
cov = np.sum(P * (diff_i * diff_j), axis=(0, 1))
|
||||
|
||||
# handle the special case of standard deviations near zero
|
||||
mask_0 = std_i < 1e-15
|
||||
mask_0[std_j < 1e-15] = True
|
||||
results[mask_0] = 1
|
||||
|
||||
# handle the standard case
|
||||
mask_1 = ~mask_0
|
||||
results[mask_1] = cov[mask_1] / (std_i[mask_1] * std_j[mask_1])
|
||||
elif prop in ['contrast', 'dissimilarity', 'homogeneity']:
|
||||
weights = weights.reshape((num_level, num_level, 1, 1))
|
||||
results = np.sum(P * weights, axis=(0, 1))
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def local_binary_pattern(image, P, R, method='default'):
|
||||
"""Compute the local binary patterns (LBP) of an image.
|
||||
|
||||
LBP is a visual descriptor often used in texture classification.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : (M, N) array
|
||||
2D grayscale image.
|
||||
P : int
|
||||
Number of circularly symmetric neighbor set points (quantization of
|
||||
the angular space).
|
||||
R : float
|
||||
Radius of circle (spatial resolution of the operator).
|
||||
method : str {'default', 'ror', 'uniform', 'nri_uniform', 'var'}, optional
|
||||
Method to determine the pattern:
|
||||
|
||||
``default``
|
||||
Original local binary pattern which is grayscale invariant but not
|
||||
rotation invariant.
|
||||
``ror``
|
||||
Extension of default pattern which is grayscale invariant and
|
||||
rotation invariant.
|
||||
``uniform``
|
||||
Uniform pattern which is grayscale invariant and rotation
|
||||
invariant, offering finer quantization of the angular space.
|
||||
For details, see [1]_.
|
||||
``nri_uniform``
|
||||
Variant of uniform pattern which is grayscale invariant but not
|
||||
rotation invariant. For details, see [2]_ and [3]_.
|
||||
``var``
|
||||
Variance of local image texture (related to contrast)
|
||||
which is rotation invariant but not grayscale invariant.
|
||||
|
||||
Returns
|
||||
-------
|
||||
output : (M, N) array
|
||||
LBP image.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] T. Ojala, M. Pietikainen, T. Maenpaa, "Multiresolution gray-scale
|
||||
and rotation invariant texture classification with local binary
|
||||
patterns", IEEE Transactions on Pattern Analysis and Machine
|
||||
Intelligence, vol. 24, no. 7, pp. 971-987, July 2002
|
||||
:DOI:`10.1109/TPAMI.2002.1017623`
|
||||
.. [2] T. Ahonen, A. Hadid and M. Pietikainen. "Face recognition with
|
||||
local binary patterns", in Proc. Eighth European Conf. Computer
|
||||
Vision, Prague, Czech Republic, May 11-14, 2004, pp. 469-481, 2004.
|
||||
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.214.6851
|
||||
:DOI:`10.1007/978-3-540-24670-1_36`
|
||||
.. [3] T. Ahonen, A. Hadid and M. Pietikainen, "Face Description with
|
||||
Local Binary Patterns: Application to Face Recognition",
|
||||
IEEE Transactions on Pattern Analysis and Machine Intelligence,
|
||||
vol. 28, no. 12, pp. 2037-2041, Dec. 2006
|
||||
:DOI:`10.1109/TPAMI.2006.244`
|
||||
"""
|
||||
check_nD(image, 2)
|
||||
|
||||
methods = {
|
||||
'default': ord('D'),
|
||||
'ror': ord('R'),
|
||||
'uniform': ord('U'),
|
||||
'nri_uniform': ord('N'),
|
||||
'var': ord('V'),
|
||||
}
|
||||
if np.issubdtype(image.dtype, np.floating):
|
||||
warnings.warn(
|
||||
"Applying `local_binary_pattern` to floating-point images may "
|
||||
"give unexpected results when small numerical differences between "
|
||||
"adjacent pixels are present. It is recommended to use this "
|
||||
"function with images of integer dtype."
|
||||
)
|
||||
image = np.ascontiguousarray(image, dtype=np.float64)
|
||||
output = _local_binary_pattern(image, P, R, methods[method.lower()])
|
||||
return output
|
||||
|
||||
|
||||
def multiblock_lbp(int_image, r, c, width, height):
|
||||
"""Multi-block local binary pattern (MB-LBP).
|
||||
|
||||
The features are calculated similarly to local binary patterns (LBPs),
|
||||
(See :py:meth:`local_binary_pattern`) except that summed blocks are
|
||||
used instead of individual pixel values.
|
||||
|
||||
MB-LBP is an extension of LBP that can be computed on multiple scales
|
||||
in constant time using the integral image. Nine equally-sized rectangles
|
||||
are used to compute a feature. For each rectangle, the sum of the pixel
|
||||
intensities is computed. Comparisons of these sums to that of the central
|
||||
rectangle determine the feature, similarly to LBP.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
int_image : (N, M) array
|
||||
Integral image.
|
||||
r : int
|
||||
Row-coordinate of top left corner of a rectangle containing feature.
|
||||
c : int
|
||||
Column-coordinate of top left corner of a rectangle containing feature.
|
||||
width : int
|
||||
Width of one of the 9 equal rectangles that will be used to compute
|
||||
a feature.
|
||||
height : int
|
||||
Height of one of the 9 equal rectangles that will be used to compute
|
||||
a feature.
|
||||
|
||||
Returns
|
||||
-------
|
||||
output : int
|
||||
8-bit MB-LBP feature descriptor.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] L. Zhang, R. Chu, S. Xiang, S. Liao, S.Z. Li. "Face Detection Based
|
||||
on Multi-Block LBP Representation", In Proceedings: Advances in
|
||||
Biometrics, International Conference, ICB 2007, Seoul, Korea.
|
||||
http://www.cbsr.ia.ac.cn/users/scliao/papers/Zhang-ICB07-MBLBP.pdf
|
||||
:DOI:`10.1007/978-3-540-74549-5_2`
|
||||
"""
|
||||
|
||||
int_image = np.ascontiguousarray(int_image, dtype=np.float32)
|
||||
lbp_code = _multiblock_lbp(int_image, r, c, width, height)
|
||||
return lbp_code
|
||||
|
||||
|
||||
def draw_multiblock_lbp(
|
||||
image,
|
||||
r,
|
||||
c,
|
||||
width,
|
||||
height,
|
||||
lbp_code=0,
|
||||
color_greater_block=(1, 1, 1),
|
||||
color_less_block=(0, 0.69, 0.96),
|
||||
alpha=0.5,
|
||||
):
|
||||
"""Multi-block local binary pattern visualization.
|
||||
|
||||
Blocks with higher sums are colored with alpha-blended white rectangles,
|
||||
whereas blocks with lower sums are colored alpha-blended cyan. Colors
|
||||
and the `alpha` parameter can be changed.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray of float or uint
|
||||
Image on which to visualize the pattern.
|
||||
r : int
|
||||
Row-coordinate of top left corner of a rectangle containing feature.
|
||||
c : int
|
||||
Column-coordinate of top left corner of a rectangle containing feature.
|
||||
width : int
|
||||
Width of one of 9 equal rectangles that will be used to compute
|
||||
a feature.
|
||||
height : int
|
||||
Height of one of 9 equal rectangles that will be used to compute
|
||||
a feature.
|
||||
lbp_code : int
|
||||
The descriptor of feature to visualize. If not provided, the
|
||||
descriptor with 0 value will be used.
|
||||
color_greater_block : tuple of 3 floats
|
||||
Floats specifying the color for the block that has greater
|
||||
intensity value. They should be in the range [0, 1].
|
||||
Corresponding values define (R, G, B) values. Default value
|
||||
is white (1, 1, 1).
|
||||
color_greater_block : tuple of 3 floats
|
||||
Floats specifying the color for the block that has greater intensity
|
||||
value. They should be in the range [0, 1]. Corresponding values define
|
||||
(R, G, B) values. Default value is cyan (0, 0.69, 0.96).
|
||||
alpha : float
|
||||
Value in the range [0, 1] that specifies opacity of visualization.
|
||||
1 - fully transparent, 0 - opaque.
|
||||
|
||||
Returns
|
||||
-------
|
||||
output : ndarray of float
|
||||
Image with MB-LBP visualization.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] L. Zhang, R. Chu, S. Xiang, S. Liao, S.Z. Li. "Face Detection Based
|
||||
on Multi-Block LBP Representation", In Proceedings: Advances in
|
||||
Biometrics, International Conference, ICB 2007, Seoul, Korea.
|
||||
http://www.cbsr.ia.ac.cn/users/scliao/papers/Zhang-ICB07-MBLBP.pdf
|
||||
:DOI:`10.1007/978-3-540-74549-5_2`
|
||||
"""
|
||||
|
||||
# Default colors for regions.
|
||||
# White is for the blocks that are brighter.
|
||||
# Cyan is for the blocks that has less intensity.
|
||||
color_greater_block = np.asarray(color_greater_block, dtype=np.float64)
|
||||
color_less_block = np.asarray(color_less_block, dtype=np.float64)
|
||||
|
||||
# Copy array to avoid the changes to the original one.
|
||||
output = np.copy(image)
|
||||
|
||||
# As the visualization uses RGB color we need 3 bands.
|
||||
if len(image.shape) < 3:
|
||||
output = gray2rgb(image)
|
||||
|
||||
# Colors are specified in floats.
|
||||
output = img_as_float(output)
|
||||
|
||||
# Offsets of neighbor rectangles relative to central one.
|
||||
# It has order starting from top left and going clockwise.
|
||||
neighbor_rect_offsets = (
|
||||
(-1, -1),
|
||||
(-1, 0),
|
||||
(-1, 1),
|
||||
(0, 1),
|
||||
(1, 1),
|
||||
(1, 0),
|
||||
(1, -1),
|
||||
(0, -1),
|
||||
)
|
||||
|
||||
# Pre-multiply the offsets with width and height.
|
||||
neighbor_rect_offsets = np.array(neighbor_rect_offsets)
|
||||
neighbor_rect_offsets[:, 0] *= height
|
||||
neighbor_rect_offsets[:, 1] *= width
|
||||
|
||||
# Top-left coordinates of central rectangle.
|
||||
central_rect_r = r + height
|
||||
central_rect_c = c + width
|
||||
|
||||
for element_num, offset in enumerate(neighbor_rect_offsets):
|
||||
offset_r, offset_c = offset
|
||||
|
||||
curr_r = central_rect_r + offset_r
|
||||
curr_c = central_rect_c + offset_c
|
||||
|
||||
has_greater_value = lbp_code & (1 << (7 - element_num))
|
||||
|
||||
# Mix-in the visualization colors.
|
||||
if has_greater_value:
|
||||
new_value = (1 - alpha) * output[
|
||||
curr_r : curr_r + height, curr_c : curr_c + width
|
||||
] + alpha * color_greater_block
|
||||
output[curr_r : curr_r + height, curr_c : curr_c + width] = new_value
|
||||
else:
|
||||
new_value = (1 - alpha) * output[
|
||||
curr_r : curr_r + height, curr_c : curr_c + width
|
||||
] + alpha * color_less_block
|
||||
output[curr_r : curr_r + height, curr_c : curr_c + width] = new_value
|
||||
|
||||
return output
|
||||
341
.CondaPkg/env/Lib/site-packages/skimage/feature/util.py
vendored
Normal file
341
.CondaPkg/env/Lib/site-packages/skimage/feature/util.py
vendored
Normal file
@@ -0,0 +1,341 @@
|
||||
import numpy as np
|
||||
|
||||
from ..util import img_as_float
|
||||
from .._shared.utils import (
|
||||
_supported_float_type,
|
||||
check_nD,
|
||||
deprecate_func,
|
||||
)
|
||||
|
||||
|
||||
class FeatureDetector:
|
||||
def __init__(self):
|
||||
self.keypoints_ = np.array([])
|
||||
|
||||
def detect(self, image):
|
||||
"""Detect keypoints in image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : 2D array
|
||||
Input image.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class DescriptorExtractor:
|
||||
def __init__(self):
|
||||
self.descriptors_ = np.array([])
|
||||
|
||||
def extract(self, image, keypoints):
|
||||
"""Extract feature descriptors in image for given keypoints.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : 2D array
|
||||
Input image.
|
||||
keypoints : (N, 2) array
|
||||
Keypoint locations as ``(row, col)``.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@deprecate_func(
|
||||
deprecated_version="0.23",
|
||||
removed_version="0.25",
|
||||
hint="Use `skimage.feature.plot_matched_features` instead.",
|
||||
)
|
||||
def plot_matches(
|
||||
ax,
|
||||
image1,
|
||||
image2,
|
||||
keypoints1,
|
||||
keypoints2,
|
||||
matches,
|
||||
keypoints_color='k',
|
||||
matches_color=None,
|
||||
only_matches=False,
|
||||
alignment='horizontal',
|
||||
):
|
||||
"""Plot matched features.
|
||||
|
||||
.. deprecated:: 0.23
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ax : matplotlib.axes.Axes
|
||||
Matches and image are drawn in this ax.
|
||||
image1 : (N, M [, 3]) array
|
||||
First grayscale or color image.
|
||||
image2 : (N, M [, 3]) array
|
||||
Second grayscale or color image.
|
||||
keypoints1 : (K1, 2) array
|
||||
First keypoint coordinates as ``(row, col)``.
|
||||
keypoints2 : (K2, 2) array
|
||||
Second keypoint coordinates as ``(row, col)``.
|
||||
matches : (Q, 2) array
|
||||
Indices of corresponding matches in first and second set of
|
||||
descriptors, where ``matches[:, 0]`` denote the indices in the first
|
||||
and ``matches[:, 1]`` the indices in the second set of descriptors.
|
||||
keypoints_color : matplotlib color, optional
|
||||
Color for keypoint locations.
|
||||
matches_color : matplotlib color, optional
|
||||
Color for lines which connect keypoint matches. By default the
|
||||
color is chosen randomly.
|
||||
only_matches : bool, optional
|
||||
Whether to only plot matches and not plot the keypoint locations.
|
||||
alignment : {'horizontal', 'vertical'}, optional
|
||||
Whether to show images side by side, ``'horizontal'``, or one above
|
||||
the other, ``'vertical'``.
|
||||
|
||||
"""
|
||||
image1 = img_as_float(image1)
|
||||
image2 = img_as_float(image2)
|
||||
|
||||
new_shape1 = list(image1.shape)
|
||||
new_shape2 = list(image2.shape)
|
||||
|
||||
if image1.shape[0] < image2.shape[0]:
|
||||
new_shape1[0] = image2.shape[0]
|
||||
elif image1.shape[0] > image2.shape[0]:
|
||||
new_shape2[0] = image1.shape[0]
|
||||
|
||||
if image1.shape[1] < image2.shape[1]:
|
||||
new_shape1[1] = image2.shape[1]
|
||||
elif image1.shape[1] > image2.shape[1]:
|
||||
new_shape2[1] = image1.shape[1]
|
||||
|
||||
if new_shape1 != image1.shape:
|
||||
new_image1 = np.zeros(new_shape1, dtype=image1.dtype)
|
||||
new_image1[: image1.shape[0], : image1.shape[1]] = image1
|
||||
image1 = new_image1
|
||||
|
||||
if new_shape2 != image2.shape:
|
||||
new_image2 = np.zeros(new_shape2, dtype=image2.dtype)
|
||||
new_image2[: image2.shape[0], : image2.shape[1]] = image2
|
||||
image2 = new_image2
|
||||
|
||||
offset = np.array(image1.shape)
|
||||
if alignment == 'horizontal':
|
||||
image = np.concatenate([image1, image2], axis=1)
|
||||
offset[0] = 0
|
||||
elif alignment == 'vertical':
|
||||
image = np.concatenate([image1, image2], axis=0)
|
||||
offset[1] = 0
|
||||
else:
|
||||
mesg = (
|
||||
f"plot_matches accepts either 'horizontal' or 'vertical' for "
|
||||
f"alignment, but '{alignment}' was given. See "
|
||||
f"https://scikit-image.org/docs/dev/api/skimage.feature.html#skimage.feature.plot_matches " # noqa
|
||||
f"for details."
|
||||
)
|
||||
raise ValueError(mesg)
|
||||
|
||||
if not only_matches:
|
||||
ax.scatter(
|
||||
keypoints1[:, 1],
|
||||
keypoints1[:, 0],
|
||||
facecolors='none',
|
||||
edgecolors=keypoints_color,
|
||||
)
|
||||
ax.scatter(
|
||||
keypoints2[:, 1] + offset[1],
|
||||
keypoints2[:, 0] + offset[0],
|
||||
facecolors='none',
|
||||
edgecolors=keypoints_color,
|
||||
)
|
||||
|
||||
ax.imshow(image, cmap='gray')
|
||||
ax.axis((0, image1.shape[1] + offset[1], image1.shape[0] + offset[0], 0))
|
||||
|
||||
rng = np.random.default_rng()
|
||||
|
||||
for i in range(matches.shape[0]):
|
||||
idx1 = matches[i, 0]
|
||||
idx2 = matches[i, 1]
|
||||
|
||||
if matches_color is None:
|
||||
color = rng.random(3)
|
||||
else:
|
||||
color = matches_color
|
||||
|
||||
ax.plot(
|
||||
(keypoints1[idx1, 1], keypoints2[idx2, 1] + offset[1]),
|
||||
(keypoints1[idx1, 0], keypoints2[idx2, 0] + offset[0]),
|
||||
'-',
|
||||
color=color,
|
||||
)
|
||||
|
||||
|
||||
def plot_matched_features(
|
||||
image0,
|
||||
image1,
|
||||
*,
|
||||
keypoints0,
|
||||
keypoints1,
|
||||
matches,
|
||||
ax,
|
||||
keypoints_color='k',
|
||||
matches_color=None,
|
||||
only_matches=False,
|
||||
alignment='horizontal',
|
||||
):
|
||||
"""Plot matched features between two images.
|
||||
|
||||
.. versionadded:: 0.23
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image0 : (N, M [, 3]) array
|
||||
First image.
|
||||
image1 : (N, M [, 3]) array
|
||||
Second image.
|
||||
keypoints0 : (K1, 2) array
|
||||
First keypoint coordinates as ``(row, col)``.
|
||||
keypoints1 : (K2, 2) array
|
||||
Second keypoint coordinates as ``(row, col)``.
|
||||
matches : (Q, 2) array
|
||||
Indices of corresponding matches in first and second sets of
|
||||
descriptors, where `matches[:, 0]` (resp. `matches[:, 1]`) contains
|
||||
the indices in the first (resp. second) set of descriptors.
|
||||
ax : matplotlib.axes.Axes
|
||||
The Axes object where the images and their matched features are drawn.
|
||||
keypoints_color : matplotlib color, optional
|
||||
Color for keypoint locations.
|
||||
matches_color : matplotlib color, optional
|
||||
Color for lines which connect keypoint matches. By default the
|
||||
color is chosen randomly.
|
||||
only_matches : bool, optional
|
||||
Set to True to plot matches only and not the keypoint locations.
|
||||
alignment : {'horizontal', 'vertical'}, optional
|
||||
Whether to show the two images side by side (`'horizontal'`), or one above
|
||||
the other (`'vertical'`).
|
||||
|
||||
"""
|
||||
image0 = img_as_float(image0)
|
||||
image1 = img_as_float(image1)
|
||||
|
||||
new_shape0 = list(image0.shape)
|
||||
new_shape1 = list(image1.shape)
|
||||
|
||||
if image0.shape[0] < image1.shape[0]:
|
||||
new_shape0[0] = image1.shape[0]
|
||||
elif image0.shape[0] > image1.shape[0]:
|
||||
new_shape1[0] = image0.shape[0]
|
||||
|
||||
if image0.shape[1] < image1.shape[1]:
|
||||
new_shape0[1] = image1.shape[1]
|
||||
elif image0.shape[1] > image1.shape[1]:
|
||||
new_shape1[1] = image0.shape[1]
|
||||
|
||||
if new_shape0 != image0.shape:
|
||||
new_image0 = np.zeros(new_shape0, dtype=image0.dtype)
|
||||
new_image0[: image0.shape[0], : image0.shape[1]] = image0
|
||||
image0 = new_image0
|
||||
|
||||
if new_shape1 != image1.shape:
|
||||
new_image1 = np.zeros(new_shape1, dtype=image1.dtype)
|
||||
new_image1[: image1.shape[0], : image1.shape[1]] = image1
|
||||
image1 = new_image1
|
||||
|
||||
offset = np.array(image0.shape)
|
||||
if alignment == 'horizontal':
|
||||
image = np.concatenate([image0, image1], axis=1)
|
||||
offset[0] = 0
|
||||
elif alignment == 'vertical':
|
||||
image = np.concatenate([image0, image1], axis=0)
|
||||
offset[1] = 0
|
||||
else:
|
||||
mesg = (
|
||||
f"`plot_matched_features` accepts either 'horizontal' or 'vertical' for "
|
||||
f"alignment, but '{alignment}' was given. See "
|
||||
f"https://scikit-image.org/docs/dev/api/skimage.feature.html#skimage.feature.plot_matched_features " # noqa
|
||||
f"for details."
|
||||
)
|
||||
raise ValueError(mesg)
|
||||
|
||||
if not only_matches:
|
||||
ax.scatter(
|
||||
keypoints0[:, 1],
|
||||
keypoints0[:, 0],
|
||||
facecolors='none',
|
||||
edgecolors=keypoints_color,
|
||||
)
|
||||
ax.scatter(
|
||||
keypoints1[:, 1] + offset[1],
|
||||
keypoints1[:, 0] + offset[0],
|
||||
facecolors='none',
|
||||
edgecolors=keypoints_color,
|
||||
)
|
||||
|
||||
ax.imshow(image, cmap='gray')
|
||||
ax.axis((0, image0.shape[1] + offset[1], image0.shape[0] + offset[0], 0))
|
||||
|
||||
rng = np.random.default_rng()
|
||||
|
||||
for i in range(matches.shape[0]):
|
||||
idx0 = matches[i, 0]
|
||||
idx1 = matches[i, 1]
|
||||
|
||||
if matches_color is None:
|
||||
color = rng.random(3)
|
||||
else:
|
||||
color = matches_color
|
||||
|
||||
ax.plot(
|
||||
(keypoints0[idx0, 1], keypoints1[idx1, 1] + offset[1]),
|
||||
(keypoints0[idx0, 0], keypoints1[idx1, 0] + offset[0]),
|
||||
'-',
|
||||
color=color,
|
||||
)
|
||||
|
||||
|
||||
def _prepare_grayscale_input_2D(image):
|
||||
image = np.squeeze(image)
|
||||
check_nD(image, 2)
|
||||
image = img_as_float(image)
|
||||
float_dtype = _supported_float_type(image.dtype)
|
||||
return image.astype(float_dtype, copy=False)
|
||||
|
||||
|
||||
def _prepare_grayscale_input_nD(image):
|
||||
image = np.squeeze(image)
|
||||
check_nD(image, range(2, 6))
|
||||
image = img_as_float(image)
|
||||
float_dtype = _supported_float_type(image.dtype)
|
||||
return image.astype(float_dtype, copy=False)
|
||||
|
||||
|
||||
def _mask_border_keypoints(image_shape, keypoints, distance):
|
||||
"""Mask coordinates that are within certain distance from the image border.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image_shape : (2,) array_like
|
||||
Shape of the image as ``(rows, cols)``.
|
||||
keypoints : (N, 2) array
|
||||
Keypoint coordinates as ``(rows, cols)``.
|
||||
distance : int
|
||||
Image border distance.
|
||||
|
||||
Returns
|
||||
-------
|
||||
mask : (N,) bool array
|
||||
Mask indicating if pixels are within the image (``True``) or in the
|
||||
border region of the image (``False``).
|
||||
|
||||
"""
|
||||
|
||||
rows = image_shape[0]
|
||||
cols = image_shape[1]
|
||||
|
||||
mask = (
|
||||
((distance - 1) < keypoints[:, 0])
|
||||
& (keypoints[:, 0] < (rows - distance + 1))
|
||||
& ((distance - 1) < keypoints[:, 1])
|
||||
& (keypoints[:, 1] < (cols - distance + 1))
|
||||
)
|
||||
|
||||
return mask
|
||||
Reference in New Issue
Block a user