| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- 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,
- 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.
- 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=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,
- 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.
- 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,
- workers=workers,
- )
- for dim in range(image.shape[-1])
- )
- features = list(itertools.chain.from_iterable(all_results))
- out = np.stack(features, axis=-1)
- return out
|