Source code for detectree.filters
"""Utilities to produce filters."""
import numpy as np
from skimage.filters import gabor_kernel
__all__ = ["get_texture_kernel", "get_gabor_filter_bank"]
def _gaussian_kernel1d(sigma, order, radius):
"""
Compute a 1-D Gaussian convolution kernel.
From https://github.com/scipy/scipy/blob/v1.9.2/scipy/ndimage/_filters.py#L179-L207
Copying it here since it is not part of scipy's public API.
See https://github.com/martibosch/detectree/issues/12
"""
if order < 0:
raise ValueError("order must be non-negative")
exponent_range = np.arange(order + 1)
sigma2 = sigma * sigma
x = np.arange(-radius, radius + 1)
phi_x = np.exp(-0.5 / sigma2 * x**2)
phi_x = phi_x / phi_x.sum()
if order == 0:
return phi_x
else:
# f(x) = q(x) * phi(x) = q(x) * exp(p(x))
# f'(x) = (q'(x) + q(x) * p'(x)) * phi(x)
# p'(x) = -1 / sigma ** 2
# Implement q'(x) + q(x) * p'(x) as a matrix operator and apply to the
# coefficients of q(x)
q = np.zeros(order + 1)
q[0] = 1
D = np.diag(exponent_range[1:], 1) # D @ q(x) = q'(x)
P = np.diag(np.ones(order) / -sigma2, -1) # P @ q(x) = q(x) * p'(x)
Q_deriv = D + P
for _ in range(order):
q = Q_deriv.dot(q)
q = (x[:, None] ** exponent_range).dot(q)
return q * phi_x
def _get_gaussian_kernel1d(sigma, *, order=0, truncate=4.0):
"""Based on scipy.ndimage.filters.gaussian_filter1d."""
sd = float(sigma)
# make the radius of the filter equal to truncate standard deviations
lw = int(truncate * sd + 0.5)
# # Since we are calling correlate, not convolve, revert the kernel
# weights = _gaussian_kernel1d(sigma, order, lw)[::-1]
weights = _gaussian_kernel1d(sigma, order, lw)
return weights
[docs]
def get_texture_kernel(sigma):
"""
Get a texture kernel based on Yang et al. (2009).
Parameters
----------
sigma : numeric
Scale parameter to build a texture kernel, based on a Gaussian on the
X dimension and a second-derivative Gaussian in the Y dimension
Returns
-------
texture_kernel : array-like
"""
g0_kernel_arr = _get_gaussian_kernel1d(sigma, order=0)
g2_kernel_arr = _get_gaussian_kernel1d(sigma, order=2)
return np.dot(g2_kernel_arr.reshape(1, -1).T, g0_kernel_arr.reshape(1, -1))
[docs]
def get_gabor_filter_bank(frequencies, num_orientations):
"""
Get a Gabor filter bank with different frequencies and orientations.
Parameters
----------
frequencies : list-like
Set of frequencies used to build the Gabor filter bank.
num_orientations : int or list-like
Number of orientations used to build the Gabor filter bank. If an
integer is provided, the corresponding number of orientations will be
used for each scale (determined by `gabor_frequencies`). If a tuple is
provided, each element will determine the number of orientations that
must be used at its matching scale (determined by `gabor_frequencies`)
- thus the tuple must match the length of `frequencies`.
Returns
-------
kernels : list-like
List of kernel 2-D arrays that correspond to the filter bank.
"""
kernels = []
for frequency, _num_orientations in zip(frequencies, num_orientations):
for orientation_i in range(_num_orientations):
theta = orientation_i / _num_orientations * np.pi
kernel = np.real(gabor_kernel(frequency, theta=theta))
kernels.append(kernel)
return kernels