from __future__ import annotations
import math
from math import sqrt
from typing import Any, Optional
import torch
from kornia.core import Device, Dtype, Tensor, concatenate, cos, stack, tensor, where, zeros, zeros_like
from kornia.core.check import KORNIA_CHECK, KORNIA_CHECK_IS_TENSOR, KORNIA_CHECK_SHAPE
from kornia.utils import deprecated
def _check_kernel_size(kernel_size: tuple[int, ...] | int, min_value: int = 0, allow_even: bool = False) -> None:
if isinstance(kernel_size, int):
kernel_size = (kernel_size,)
fmt = "even or odd" if allow_even else "odd"
for size in kernel_size:
KORNIA_CHECK(
isinstance(size, int) and (((size % 2 == 1) or allow_even) and size > min_value),
f"Kernel size must be an {fmt} integer bigger than {min_value}. Gotcha {size} on {kernel_size}",
)
def _unpack_2d_ks(kernel_size: tuple[int, int] | int) -> tuple[int, int]:
if isinstance(kernel_size, int):
ky = kx = kernel_size
else:
KORNIA_CHECK(len(kernel_size) == 2, "2D Kernel size should have a length of 2.")
ky, kx = kernel_size
ky = int(ky)
kx = int(kx)
return (ky, kx)
def _unpack_3d_ks(kernel_size: tuple[int, int, int] | int) -> tuple[int, int, int]:
if isinstance(kernel_size, int):
kz = ky = kx = kernel_size
else:
KORNIA_CHECK(len(kernel_size) == 3, "3D Kernel size should have a length of 3.")
kz, ky, kx = kernel_size
kz = int(kz)
ky = int(ky)
kx = int(kx)
return (kz, ky, kx)
def normalize_kernel2d(input: Tensor) -> Tensor:
r"""Normalize both derivative and smoothing kernel."""
KORNIA_CHECK_SHAPE(input, ["*", "H", "W"])
norm = input.abs().sum(dim=-1).sum(dim=-1)
return input / (norm[..., None, None])
def gaussian(
window_size: int, sigma: Tensor | float, *, device: Optional[Device] = None, dtype: Optional[Dtype] = None
) -> Tensor:
"""Compute the gaussian values based on the window and sigma values.
Args:
window_size: the size which drives the filter amount.
sigma: gaussian standard deviation. If a tensor, should be in a shape :math:`(B, 1)`
device: This value will be used if sigma is a float. Device desired to compute.
dtype: This value will be used if sigma is a float. Dtype desired for compute.
Returns:
A tensor withshape :math:`(B, \text{kernel_size})`, with Gaussian values.
"""
if isinstance(sigma, float):
sigma = tensor([[sigma]], device=device, dtype=dtype)
KORNIA_CHECK_IS_TENSOR(sigma)
KORNIA_CHECK_SHAPE(sigma, ["B", "1"])
batch_size = sigma.shape[0]
x = (torch.arange(window_size, device=sigma.device, dtype=sigma.dtype) - window_size // 2).expand(batch_size, -1)
if window_size % 2 == 0:
x = x + 0.5
gauss = torch.exp(-x.pow(2.0) / (2 * sigma.pow(2.0)))
return gauss / gauss.sum(-1, keepdim=True)
def gaussian_discrete_erf(
window_size: int, sigma: Tensor | float, *, device: Optional[Device] = None, dtype: Optional[Dtype] = None
) -> Tensor:
r"""Discrete Gaussian by interpolating the error function.
Adapted from: https://github.com/Project-MONAI/MONAI/blob/master/monai/networks/layers/convutils.py
Args:
window_size: the size which drives the filter amount.
sigma: gaussian standard deviation. If a tensor, should be in a shape :math:`(B, 1)`
device: This value will be used if sigma is a float. Device desired to compute.
dtype: This value will be used if sigma is a float. Dtype desired for compute.
Returns:
A tensor withshape :math:`(B, \text{kernel_size})`, with discrete Gaussian values computed by approximation of
the error function.
"""
if isinstance(sigma, float):
sigma = tensor([[sigma]], device=device, dtype=dtype)
KORNIA_CHECK_SHAPE(sigma, ["B", "1"])
batch_size = sigma.shape[0]
x = (torch.arange(window_size, device=sigma.device, dtype=sigma.dtype) - window_size // 2).expand(batch_size, -1)
t = 0.70710678 / sigma.abs()
# t = tensor(2, device=sigma.device, dtype=sigma.dtype).sqrt() / (sigma.abs() * 2)
gauss = 0.5 * ((t * (x + 0.5)).erf() - (t * (x - 0.5)).erf())
gauss = gauss.clamp(min=0)
return gauss / gauss.sum(-1, keepdim=True)
def _modified_bessel_0(x: Tensor) -> Tensor:
r"""Adapted from:
https://github.com/Project-MONAI/MONAI/blob/master/monai/networks/layers/convutils.py
"""
ax = torch.abs(x)
out = zeros_like(x)
idx_a = ax < 3.75
if idx_a.any():
y = (x[idx_a] / 3.75) * (x[idx_a] / 3.75)
out[idx_a] = 1.0 + y * (
3.5156229 + y * (3.0899424 + y * (1.2067492 + y * (0.2659732 + y * (0.360768e-1 + y * 0.45813e-2))))
)
idx_b = ~idx_a
if idx_b.any():
y = 3.75 / ax[idx_b]
ans = 0.916281e-2 + y * (-0.2057706e-1 + y * (0.2635537e-1 + y * (-0.1647633e-1 + y * 0.392377e-2)))
coef = 0.39894228 + y * (0.1328592e-1 + y * (0.225319e-2 + y * (-0.157565e-2 + y * ans)))
out[idx_b] = (ax[idx_b].exp() / ax[idx_b].sqrt()) * coef
return out
def _modified_bessel_1(x: Tensor) -> Tensor:
r"""Adapted from:
https://github.com/Project-MONAI/MONAI/blob/master/monai/networks/layers/convutils.py
"""
ax = torch.abs(x)
out = zeros_like(x)
idx_a = ax < 3.75
if idx_a.any():
y = (x[idx_a] / 3.75) * (x[idx_a] / 3.75)
ans = 0.51498869 + y * (0.15084934 + y * (0.2658733e-1 + y * (0.301532e-2 + y * 0.32411e-3)))
out[idx_a] = ax[idx_a] * (0.5 + y * (0.87890594 + y * ans))
idx_b = ~idx_a
if idx_b.any():
y = 3.75 / ax[idx_b]
ans = 0.2282967e-1 + y * (-0.2895312e-1 + y * (0.1787654e-1 - y * 0.420059e-2))
ans = 0.39894228 + y * (-0.3988024e-1 + y * (-0.362018e-2 + y * (0.163801e-2 + y * (-0.1031555e-1 + y * ans))))
ans = ans * ax[idx_b].exp() / ax[idx_b].sqrt()
out[idx_b] = where(x[idx_b] < 0, -ans, ans)
return out
def _modified_bessel_i(n: int, x: Tensor) -> Tensor:
r"""Adapted from:
https://github.com/Project-MONAI/MONAI/blob/master/monai/networks/layers/convutils.py
"""
KORNIA_CHECK(n >= 2, "n must be greater than 1.99")
if (x == 0.0).all():
return x
batch_size = x.shape[0]
tox = 2.0 / x.abs()
ans = zeros(batch_size, 1, device=x.device, dtype=x.dtype)
bip = zeros(batch_size, 1, device=x.device, dtype=x.dtype)
bi = torch.ones(batch_size, 1, device=x.device, dtype=x.dtype)
m = int(2 * (n + int(sqrt(40.0 * n))))
for j in range(m, 0, -1):
bim = bip + float(j) * tox * bi
bip = bi
bi = bim
idx = bi.abs() > 1.0e10
if idx.any():
ans[idx] = ans[idx] * 1.0e-10
bi[idx] = bi[idx] * 1.0e-10
bip[idx] = bip[idx] * 1.0e-10
if j == n:
ans = bip
out = ans * _modified_bessel_0(x) / bi
if (n % 2) == 1:
out = where(x < 0.0, -out, out)
# TODO: skip the previous computation for x == 0, instead of forcing here
out = where(x == 0.0, x, out)
return out
def gaussian_discrete(
window_size: int, sigma: Tensor | float, *, device: Optional[Device] = None, dtype: Optional[Dtype] = None
) -> Tensor:
r"""Discrete Gaussian kernel based on the modified Bessel functions.
Adapted from: https://github.com/Project-MONAI/MONAI/blob/master/monai/networks/layers/convutils.py
Args:
window_size: the size which drives the filter amount.
sigma: gaussian standard deviation. If a tensor, should be in a shape :math:`(B, 1)`
device: This value will be used if sigma is a float. Device desired to compute.
dtype: This value will be used if sigma is a float. Dtype desired for compute.
Returns:
A tensor withshape :math:`(B, \text{kernel_size})`, with discrete Gaussian values computed by modified Bessel
function.
"""
if isinstance(sigma, float):
sigma = tensor([[sigma]], device=device, dtype=dtype)
KORNIA_CHECK_SHAPE(sigma, ["B", "1"])
sigma2 = sigma * sigma
tail = int(window_size // 2) + 1
bessels = [
_modified_bessel_0(sigma2),
_modified_bessel_1(sigma2),
*(_modified_bessel_i(k, sigma2) for k in range(2, tail)),
]
# NOTE: on monain is exp(-sig)
# https://github.com/Project-MONAI/MONAI/blob/dev/monai/networks/layers/convutils.py#L128
out = concatenate(bessels[:0:-1] + bessels, -1) * sigma2.exp()
return out / out.sum(-1, keepdim=True)
def laplacian_1d(window_size: int, *, device: Optional[Device] = None, dtype: Dtype = torch.float32) -> Tensor:
"""One could also use the Laplacian of Gaussian formula to design the filter."""
# TODO: add default dtype as None when kornia relies on torch > 1.12
filter_1d = torch.ones(window_size, device=device, dtype=dtype)
middle = window_size // 2
filter_1d[middle] = 1 - window_size
return filter_1d
def get_box_kernel1d(kernel_size: int, *, device: Optional[Device] = None, dtype: Optional[Dtype] = None) -> Tensor:
r"""Utility function that returns a 1-D box filter.
Args:
kernel_size: the size of the kernel.
device: the desired device of returned tensor.
dtype: the desired data type of returned tensor.
Returns:
A tensor with shape :math:`(1, \text{kernel\_size})`, filled with the value
:math:`\frac{1}{\text{kernel\_size}}`.
"""
scale = tensor(1.0 / kernel_size, device=device, dtype=dtype)
return scale.expand(1, kernel_size)
def get_box_kernel2d(
kernel_size: tuple[int, int] | int, *, device: Optional[Device] = None, dtype: Optional[Dtype] = None
) -> Tensor:
r"""Utility function that returns a 2-D box filter.
Args:
kernel_size: the size of the kernel.
device: the desired device of returned tensor.
dtype: the desired data type of returned tensor.
Returns:
A tensor with shape :math:`(1, \text{kernel\_size}[0], \text{kernel\_size}[1])`,
filled with the value :math:`\frac{1}{\text{kernel\_size}[0] \times \text{kernel\_size}[1]}`.
"""
ky, kx = _unpack_2d_ks(kernel_size)
scale = tensor(1.0 / (kx * ky), device=device, dtype=dtype)
return scale.expand(1, ky, kx)
def get_binary_kernel2d(
window_size: tuple[int, int] | int, *, device: Optional[Device] = None, dtype: Dtype = torch.float32
) -> Tensor:
"""Create a binary kernel to extract the patches.
If the window size is HxW will create a (H*W)x1xHxW kernel.
"""
# TODO: add default dtype as None when kornia relies on torch > 1.12
ky, kx = _unpack_2d_ks(window_size)
window_range = kx * ky
kernel = zeros((window_range, window_range), device=device, dtype=dtype)
idx = torch.arange(window_range, device=device)
kernel[idx, idx] += 1.0
return kernel.view(window_range, 1, ky, kx)
def get_sobel_kernel_3x3(*, device: Optional[Device] = None, dtype: Optional[Dtype] = None) -> Tensor:
"""Utility function that returns a sobel kernel of 3x3."""
return tensor([[-1.0, 0.0, 1.0], [-2.0, 0.0, 2.0], [-1.0, 0.0, 1.0]], device=device, dtype=dtype)
def get_sobel_kernel_5x5_2nd_order(*, device: Optional[Device] = None, dtype: Optional[Dtype] = None) -> Tensor:
"""Utility function that returns a 2nd order sobel kernel of 5x5."""
return tensor(
[
[-1.0, 0.0, 2.0, 0.0, -1.0],
[-4.0, 0.0, 8.0, 0.0, -4.0],
[-6.0, 0.0, 12.0, 0.0, -6.0],
[-4.0, 0.0, 8.0, 0.0, -4.0],
[-1.0, 0.0, 2.0, 0.0, -1.0],
],
device=device,
dtype=dtype,
)
def _get_sobel_kernel_5x5_2nd_order_xy(*, device: Optional[Device] = None, dtype: Optional[Dtype] = None) -> Tensor:
"""Utility function that returns a 2nd order sobel kernel of 5x5."""
return tensor(
[
[-1.0, -2.0, 0.0, 2.0, 1.0],
[-2.0, -4.0, 0.0, 4.0, 2.0],
[0.0, 0.0, 0.0, 0.0, 0.0],
[2.0, 4.0, 0.0, -4.0, -2.0],
[1.0, 2.0, 0.0, -2.0, -1.0],
],
device=device,
dtype=dtype,
)
def get_diff_kernel_3x3(*, device: Optional[Device] = None, dtype: Optional[Dtype] = None) -> Tensor:
"""Utility function that returns a first order derivative kernel of 3x3."""
return tensor([[-0.0, 0.0, 0.0], [-1.0, 0.0, 1.0], [-0.0, 0.0, 0.0]], device=device, dtype=dtype)
def get_diff_kernel3d(device: Optional[Device] = None, dtype: Optional[Dtype] = None) -> Tensor:
"""Utility function that returns a first order derivative kernel of 3x3x3."""
kernel = tensor(
[
[
[[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.5, 0.0, 0.5], [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.5, 0.0], [0.0, 0.0, 0.0], [0.0, 0.5, 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.5, 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.5, 0.0], [0.0, 0.0, 0.0]],
],
],
device=device,
dtype=dtype,
)
return kernel[:, None, ...]
def get_diff_kernel3d_2nd_order(device: Optional[Device] = None, dtype: Optional[Dtype] = None) -> Tensor:
"""Utility function that returns a first order derivative kernel of 3x3x3."""
kernel = tensor(
[
[
[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
[[0.0, 0.0, 0.0], [1.0, -2.0, 1.0], [0.0, 0.0, 0.0]],
[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
],
[
[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
[[0.0, 1.0, 0.0], [0.0, -2.0, 0.0], [0.0, 1.0, 0.0]],
[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
],
[
[[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 0.0]],
[[0.0, 0.0, 0.0], [0.0, -2.0, 0.0], [0.0, 0.0, 0.0]],
[[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 0.0]],
],
[
[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
[[1.0, 0.0, -1.0], [0.0, 0.0, 0.0], [-1.0, 0.0, 1.0]],
[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
],
[
[[0.0, 1.0, 0.0], [0.0, 0.0, 0.0], [0.0, -1.0, 0.0]],
[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
[[0.0, -1.0, 0.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
],
[
[[0.0, 0.0, 0.0], [1.0, 0.0, -1.0], [0.0, 0.0, 0.0]],
[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
[[0.0, 0.0, 0.0], [-1.0, 0.0, 1.0], [0.0, 0.0, 0.0]],
],
],
device=device,
dtype=dtype,
)
return kernel[:, None, ...]
def get_sobel_kernel2d(*, device: Optional[Device] = None, dtype: Optional[Dtype] = None) -> Tensor:
kernel_x = get_sobel_kernel_3x3(device=device, dtype=dtype)
kernel_y = kernel_x.transpose(0, 1)
return stack([kernel_x, kernel_y])
def get_diff_kernel2d(*, device: Optional[Device] = None, dtype: Optional[Dtype] = None) -> Tensor:
kernel_x = get_diff_kernel_3x3(device=device, dtype=dtype)
kernel_y = kernel_x.transpose(0, 1)
return stack([kernel_x, kernel_y])
def get_sobel_kernel2d_2nd_order(*, device: Optional[Device] = None, dtype: Optional[Dtype] = None) -> Tensor:
gxx = get_sobel_kernel_5x5_2nd_order(device=device, dtype=dtype)
gyy = gxx.transpose(0, 1)
gxy = _get_sobel_kernel_5x5_2nd_order_xy(device=device, dtype=dtype)
return stack([gxx, gxy, gyy])
def get_diff_kernel2d_2nd_order(*, device: Optional[Device] = None, dtype: Optional[Dtype] = None) -> Tensor:
gxx = tensor([[0.0, 0.0, 0.0], [1.0, -2.0, 1.0], [0.0, 0.0, 0.0]], device=device, dtype=dtype)
gyy = gxx.transpose(0, 1)
gxy = tensor([[-1.0, 0.0, 1.0], [0.0, 0.0, 0.0], [1.0, 0.0, -1.0]], device=device, dtype=dtype)
return stack([gxx, gxy, gyy])
def get_spatial_gradient_kernel2d(
mode: str, order: int, *, device: Optional[Device] = None, dtype: Optional[Dtype] = None
) -> Tensor:
r"""Function that returns kernel for 1st or 2nd order image gradients, using one of the following operators:
sobel, diff.
"""
KORNIA_CHECK(mode.lower() in {"sobel", "diff"}, f"Mode should be `sobel` or `diff`. Got {mode}")
KORNIA_CHECK(order in {1, 2}, f"Order should be 1 or 2. Got {order}")
if mode == "sobel" and order == 1:
kernel: Tensor = get_sobel_kernel2d(device=device, dtype=dtype)
elif mode == "sobel" and order == 2:
kernel = get_sobel_kernel2d_2nd_order(device=device, dtype=dtype)
elif mode == "diff" and order == 1:
kernel = get_diff_kernel2d(device=device, dtype=dtype)
elif mode == "diff" and order == 2:
kernel = get_diff_kernel2d_2nd_order(device=device, dtype=dtype)
else:
raise NotImplementedError(f"Not implemented for order {order} on mode {mode}")
return kernel
def get_spatial_gradient_kernel3d(
mode: str, order: int, device: Optional[Device] = None, dtype: Optional[Dtype] = None
) -> Tensor:
r"""Function that returns kernel for 1st or 2nd order scale pyramid gradients, using one of the following
operators: sobel, diff."""
KORNIA_CHECK(mode.lower() in {"sobel", "diff"}, f"Mode should be `sobel` or `diff`. Got {mode}")
KORNIA_CHECK(order in {1, 2}, f"Order should be 1 or 2. Got {order}")
if mode == "diff" and order == 1:
kernel = get_diff_kernel3d(device=device, dtype=dtype)
elif mode == "diff" and order == 2:
kernel = get_diff_kernel3d_2nd_order(device=device, dtype=dtype)
else:
raise NotImplementedError(f"Not implemented 3d gradient kernel for order {order} on mode {mode}")
return kernel
[docs]def get_gaussian_kernel1d(
kernel_size: int,
sigma: float | Tensor,
force_even: bool = False,
*,
device: Optional[Device] = None,
dtype: Optional[Dtype] = None,
) -> Tensor:
r"""Function that returns Gaussian filter coefficients.
Args:
kernel_size: filter size. It should be odd and positive.
sigma: gaussian standard deviation.
force_even: overrides requirement for odd kernel size.
device: This value will be used if sigma is a float. Device desired to compute.
dtype: This value will be used if sigma is a float. Dtype desired for compute.
Returns:
gaussian filter coefficients with shape :math:`(B, \text{kernel_size})`.
Examples:
>>> get_gaussian_kernel1d(3, 2.5)
tensor([[0.3243, 0.3513, 0.3243]])
>>> get_gaussian_kernel1d(5, 1.5)
tensor([[0.1201, 0.2339, 0.2921, 0.2339, 0.1201]])
>>> get_gaussian_kernel1d(5, torch.tensor([[1.5], [0.7]]))
tensor([[0.1201, 0.2339, 0.2921, 0.2339, 0.1201],
[0.0096, 0.2054, 0.5699, 0.2054, 0.0096]])
"""
_check_kernel_size(kernel_size, allow_even=force_even)
return gaussian(kernel_size, sigma, device=device, dtype=dtype)
[docs]def get_gaussian_discrete_kernel1d(
kernel_size: int,
sigma: float | Tensor,
force_even: bool = False,
*,
device: Optional[Device] = None,
dtype: Optional[Dtype] = None,
) -> Tensor:
"""Function that returns Gaussian filter coefficients based on the modified Bessel functions.
Adapted from: https://github.com/Project-MONAI/MONAI/blob/master/monai/networks/layers/convutils.py.
Args:
kernel_size: filter size. It should be odd and positive.
sigma: gaussian standard deviation. If a tensor, should be in a shape :math:`(B, 1)`
force_even: overrides requirement for odd kernel size.
device: This value will be used if sigma is a float. Device desired to compute.
dtype: This value will be used if sigma is a float. Dtype desired for compute.
Returns:
1D tensor with gaussian filter coefficients. With shape :math:`(B, \text{kernel_size})`
Examples:
>>> get_gaussian_discrete_kernel1d(3, 2.5)
tensor([[0.3235, 0.3531, 0.3235]])
>>> get_gaussian_discrete_kernel1d(5, 1.5)
tensor([[0.1096, 0.2323, 0.3161, 0.2323, 0.1096]])
>>> get_gaussian_discrete_kernel1d(5, torch.tensor([[1.5],[2.4]]))
tensor([[0.1096, 0.2323, 0.3161, 0.2323, 0.1096],
[0.1635, 0.2170, 0.2389, 0.2170, 0.1635]])
"""
_check_kernel_size(kernel_size, allow_even=force_even)
return gaussian_discrete(kernel_size, sigma, device=device, dtype=dtype)
[docs]def get_gaussian_erf_kernel1d(
kernel_size: int,
sigma: float | Tensor,
force_even: bool = False,
*,
device: Optional[Device] = None,
dtype: Optional[Dtype] = None,
) -> Tensor:
"""Function that returns Gaussian filter coefficients by interpolating the error function.
Adapted from: https://github.com/Project-MONAI/MONAI/blob/master/monai/networks/layers/convutils.py.
Args:
kernel_size: filter size. It should be odd and positive.
sigma: gaussian standard deviation. If a tensor, should be in a shape :math:`(B, 1)`
force_even: overrides requirement for odd kernel size.
device: This value will be used if sigma is a float. Device desired to compute.
dtype: This value will be used if sigma is a float. Dtype desired for compute.
Returns:
1D tensor with gaussian filter coefficients. Shape :math:`(B, \text{kernel_size})`
Examples:
>>> get_gaussian_erf_kernel1d(3, 2.5)
tensor([[0.3245, 0.3511, 0.3245]])
>>> get_gaussian_erf_kernel1d(5, 1.5)
tensor([[0.1226, 0.2331, 0.2887, 0.2331, 0.1226]])
>>> get_gaussian_erf_kernel1d(5, torch.tensor([[1.5], [2.1]]))
tensor([[0.1226, 0.2331, 0.2887, 0.2331, 0.1226],
[0.1574, 0.2198, 0.2456, 0.2198, 0.1574]])
"""
_check_kernel_size(kernel_size, allow_even=force_even)
return gaussian_discrete_erf(kernel_size, sigma, device=device, dtype=dtype)
[docs]def get_gaussian_kernel2d(
kernel_size: tuple[int, int] | int,
sigma: tuple[float, float] | Tensor,
force_even: bool = False,
*,
device: Optional[Device] = None,
dtype: Optional[Dtype] = None,
) -> Tensor:
r"""Function that returns Gaussian filter matrix coefficients.
Args:
kernel_size: filter sizes in the y and x direction. Sizes should be odd and positive.
sigma: gaussian standard deviation in the y and x.
force_even: overrides requirement for odd kernel size.
device: This value will be used if sigma is a float. Device desired to compute.
dtype: This value will be used if sigma is a float. Dtype desired for compute.
Returns:
2D tensor with gaussian filter matrix coefficients.
Shape:
- Output: :math:`(B, \text{kernel_size}_x, \text{kernel_size}_y)`
Examples:
>>> get_gaussian_kernel2d((5, 5), (1.5, 1.5))
tensor([[[0.0144, 0.0281, 0.0351, 0.0281, 0.0144],
[0.0281, 0.0547, 0.0683, 0.0547, 0.0281],
[0.0351, 0.0683, 0.0853, 0.0683, 0.0351],
[0.0281, 0.0547, 0.0683, 0.0547, 0.0281],
[0.0144, 0.0281, 0.0351, 0.0281, 0.0144]]])
>>> get_gaussian_kernel2d((3, 5), (1.5, 1.5))
tensor([[[0.0370, 0.0720, 0.0899, 0.0720, 0.0370],
[0.0462, 0.0899, 0.1123, 0.0899, 0.0462],
[0.0370, 0.0720, 0.0899, 0.0720, 0.0370]]])
>>> get_gaussian_kernel2d((5, 5), torch.tensor([[1.5, 1.5]]))
tensor([[[0.0144, 0.0281, 0.0351, 0.0281, 0.0144],
[0.0281, 0.0547, 0.0683, 0.0547, 0.0281],
[0.0351, 0.0683, 0.0853, 0.0683, 0.0351],
[0.0281, 0.0547, 0.0683, 0.0547, 0.0281],
[0.0144, 0.0281, 0.0351, 0.0281, 0.0144]]])
"""
if isinstance(sigma, tuple):
sigma = tensor([sigma], device=device, dtype=dtype)
KORNIA_CHECK_IS_TENSOR(sigma)
KORNIA_CHECK_SHAPE(sigma, ["B", "2"])
ksize_y, ksize_x = _unpack_2d_ks(kernel_size)
sigma_y, sigma_x = sigma[:, 0, None], sigma[:, 1, None]
kernel_y = get_gaussian_kernel1d(ksize_y, sigma_y, force_even, device=device, dtype=dtype)[..., None]
kernel_x = get_gaussian_kernel1d(ksize_x, sigma_x, force_even, device=device, dtype=dtype)[..., None]
return kernel_y * kernel_x.view(-1, 1, ksize_x)
def get_gaussian_kernel3d(
kernel_size: tuple[int, int, int] | int,
sigma: tuple[float, float, float] | Tensor,
force_even: bool = False,
*,
device: Optional[Device] = None,
dtype: Optional[Dtype] = None,
) -> Tensor:
r"""Function that returns Gaussian filter matrix coefficients.
Args:
kernel_size: filter sizes in the z, y and x direction. Sizes should be odd and positive.
sigma: gaussian standard deviation in the z, y and x direction.
force_even: overrides requirement for odd kernel size.
device: This value will be used if sigma is a float. Device desired to compute.
dtype: This value will be used if sigma is a float. Dtype desired for compute.
Returns:
3D tensor with gaussian filter matrix coefficients.
Shape:
- Output: :math:`(B, \text{kernel_size}_x, \text{kernel_size}_y, \text{kernel_size}_z)`
Examples:
>>> get_gaussian_kernel3d((3, 3, 3), (1.5, 1.5, 1.5))
tensor([[[[0.0292, 0.0364, 0.0292],
[0.0364, 0.0455, 0.0364],
[0.0292, 0.0364, 0.0292]],
<BLANKLINE>
[[0.0364, 0.0455, 0.0364],
[0.0455, 0.0568, 0.0455],
[0.0364, 0.0455, 0.0364]],
<BLANKLINE>
[[0.0292, 0.0364, 0.0292],
[0.0364, 0.0455, 0.0364],
[0.0292, 0.0364, 0.0292]]]])
>>> get_gaussian_kernel3d((3, 3, 3), (1.5, 1.5, 1.5)).sum()
tensor(1.)
>>> get_gaussian_kernel3d((3, 3, 3), (1.5, 1.5, 1.5)).shape
torch.Size([1, 3, 3, 3])
>>> get_gaussian_kernel3d((3, 7, 5), torch.tensor([[1.5, 1.5, 1.5]])).shape
torch.Size([1, 3, 7, 5])
"""
if isinstance(sigma, tuple):
sigma = tensor([sigma], device=device, dtype=dtype)
KORNIA_CHECK_IS_TENSOR(sigma)
KORNIA_CHECK_SHAPE(sigma, ["B", "3"])
ksize_z, ksize_y, ksize_x = _unpack_3d_ks(kernel_size)
sigma_z, sigma_y, sigma_x = sigma[:, 0, None], sigma[:, 1, None], sigma[:, 2, None]
kernel_z = get_gaussian_kernel1d(ksize_z, sigma_z, force_even, device=device, dtype=dtype)
kernel_y = get_gaussian_kernel1d(ksize_y, sigma_y, force_even, device=device, dtype=dtype)
kernel_x = get_gaussian_kernel1d(ksize_x, sigma_x, force_even, device=device, dtype=dtype)
return kernel_z.view(-1, ksize_z, 1, 1) * kernel_y.view(-1, 1, ksize_y, 1) * kernel_x.view(-1, 1, 1, ksize_x)
[docs]def get_laplacian_kernel1d(
kernel_size: int, *, device: Optional[Device] = None, dtype: Dtype = torch.float32
) -> Tensor:
r"""Function that returns the coefficients of a 1D Laplacian filter.
Args:
kernel_size: filter size. It should be odd and positive.
device: tensor device desired to create the kernel
dtype: tensor dtype desired to create the kernel
Returns:
1D tensor with laplacian filter coefficients.
Shape:
- Output: math:`(\text{kernel_size})`
Examples:
>>> get_laplacian_kernel1d(3)
tensor([ 1., -2., 1.])
>>> get_laplacian_kernel1d(5)
tensor([ 1., 1., -4., 1., 1.])
"""
# TODO: add default dtype as None when kornia relies on torch > 1.12
_check_kernel_size(kernel_size)
return laplacian_1d(kernel_size, device=device, dtype=dtype)
[docs]def get_laplacian_kernel2d(
kernel_size: tuple[int, int] | int, *, device: Optional[Device] = None, dtype: Dtype = torch.float32
) -> Tensor:
r"""Function that returns Gaussian filter matrix coefficients.
Args:
kernel_size: filter size should be odd.
device: tensor device desired to create the kernel
dtype: tensor dtype desired to create the kernel
Returns:
2D tensor with laplacian filter matrix coefficients.
Shape:
- Output: :math:`(\text{kernel_size}_x, \text{kernel_size}_y)`
Examples:
>>> get_laplacian_kernel2d(3)
tensor([[ 1., 1., 1.],
[ 1., -8., 1.],
[ 1., 1., 1.]])
>>> get_laplacian_kernel2d(5)
tensor([[ 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1.],
[ 1., 1., -24., 1., 1.],
[ 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1.]])
"""
# TODO: add default dtype as None when kornia relies on torch > 1.12
ky, kx = _unpack_2d_ks(kernel_size)
_check_kernel_size((ky, kx))
kernel = torch.ones((ky, kx), device=device, dtype=dtype)
mid_x = kx // 2
mid_y = ky // 2
kernel[mid_y, mid_x] = 1 - kernel.sum()
return kernel
def get_pascal_kernel_2d(
kernel_size: tuple[int, int] | int,
norm: bool = True,
*,
device: Optional[Device] = None,
dtype: Optional[Dtype] = None,
) -> Tensor:
"""Generate pascal filter kernel by kernel size.
Args:
kernel_size: height and width of the kernel.
norm: if to normalize the kernel or not. Default: True.
device: tensor device desired to create the kernel
dtype: tensor dtype desired to create the kernel
Returns:
if kernel_size is an integer the kernel will be shaped as :math:`(kernel_size, kernel_size)`
otherwise the kernel will be shaped as :math: `kernel_size`
Examples:
>>> get_pascal_kernel_2d(1)
tensor([[1.]])
>>> get_pascal_kernel_2d(4)
tensor([[0.0156, 0.0469, 0.0469, 0.0156],
[0.0469, 0.1406, 0.1406, 0.0469],
[0.0469, 0.1406, 0.1406, 0.0469],
[0.0156, 0.0469, 0.0469, 0.0156]])
>>> get_pascal_kernel_2d(4, norm=False)
tensor([[1., 3., 3., 1.],
[3., 9., 9., 3.],
[3., 9., 9., 3.],
[1., 3., 3., 1.]])
"""
ky, kx = _unpack_2d_ks(kernel_size)
ax = get_pascal_kernel_1d(kx, device=device, dtype=dtype)
ay = get_pascal_kernel_1d(ky, device=device, dtype=dtype)
filt = ay[:, None] * ax[None, :]
if norm:
filt = filt / torch.sum(filt)
return filt
def get_pascal_kernel_1d(
kernel_size: int, norm: bool = False, *, device: Optional[Device] = None, dtype: Optional[Dtype] = None
) -> Tensor:
"""Generate Yang Hui triangle (Pascal's triangle) by a given number.
Args:
kernel_size: height and width of the kernel.
norm: if to normalize the kernel or not. Default: False.
device: tensor device desired to create the kernel
dtype: tensor dtype desired to create the kernel
Returns:
kernel shaped as :math:`(kernel_size,)`
Examples:
>>> get_pascal_kernel_1d(1)
tensor([1.])
>>> get_pascal_kernel_1d(2)
tensor([1., 1.])
>>> get_pascal_kernel_1d(3)
tensor([1., 2., 1.])
>>> get_pascal_kernel_1d(4)
tensor([1., 3., 3., 1.])
>>> get_pascal_kernel_1d(5)
tensor([1., 4., 6., 4., 1.])
>>> get_pascal_kernel_1d(6)
tensor([ 1., 5., 10., 10., 5., 1.])
"""
pre: list[float] = []
cur: list[float] = []
for i in range(kernel_size):
cur = [1.0] * (i + 1)
for j in range(1, i // 2 + 1):
value = pre[j - 1] + pre[j]
cur[j] = value
if i != 2 * j:
cur[-j - 1] = value
pre = cur
out = tensor(cur, device=device, dtype=dtype)
if norm:
out = out / out.sum()
return out
def get_canny_nms_kernel(device: Optional[Device] = None, dtype: Optional[Dtype] = None) -> Tensor:
"""Utility function that returns 3x3 kernels for the Canny Non-maximal suppression."""
return tensor(
[
[[[0.0, 0.0, 0.0], [0.0, 1.0, -1.0], [0.0, 0.0, 0.0]]],
[[[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, -1.0]]],
[[[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, -1.0, 0.0]]],
[[[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [-1.0, 0.0, 0.0]]],
[[[0.0, 0.0, 0.0], [-1.0, 1.0, 0.0], [0.0, 0.0, 0.0]]],
[[[-1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 0.0]]],
[[[0.0, -1.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 0.0]]],
[[[0.0, 0.0, -1.0], [0.0, 1.0, 0.0], [0.0, 0.0, 0.0]]],
],
device=device,
dtype=dtype,
)
def get_hysteresis_kernel(device: Optional[Device] = None, dtype: Optional[Dtype] = None) -> Tensor:
"""Utility function that returns the 3x3 kernels for the Canny hysteresis."""
return tensor(
[
[[[0.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 0.0, 0.0]]],
[[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]],
[[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]]],
[[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [1.0, 0.0, 0.0]]],
[[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 0.0]]],
[[[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]],
[[[0.0, 1.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]],
[[[0.0, 0.0, 1.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]],
],
device=device,
dtype=dtype,
)
[docs]def get_hanning_kernel1d(kernel_size: int, device: Optional[Device] = None, dtype: Optional[Dtype] = None) -> Tensor:
"""Returns Hanning (also known as Hann) kernel, used in signal processing and KCF tracker.
.. math:: w(n) = 0.5 - 0.5cos\\left(\\frac{2\\pi{n}}{M-1}\\right)
\\qquad 0 \\leq n \\leq M-1
See further in numpy docs https://numpy.org/doc/stable/reference/generated/numpy.hanning.html
Args:
kernel_size: The size the of the kernel. It should be positive.
device: tensor device desired to create the kernel
dtype: tensor dtype desired to create the kernel
Returns:
1D tensor with Hanning filter coefficients. Shape math:`(\text{kernel_size})`
.. math:: w(n) = 0.5 - 0.5cos\\left(\\frac{2\\pi{n}}{M-1}\\right)
Examples:
>>> get_hanning_kernel1d(4)
tensor([0.0000, 0.7500, 0.7500, 0.0000])
"""
_check_kernel_size(kernel_size, 2, allow_even=True)
x = torch.arange(kernel_size, device=device, dtype=dtype)
x = 0.5 - 0.5 * cos(2.0 * math.pi * x / float(kernel_size - 1))
return x
[docs]def get_hanning_kernel2d(
kernel_size: tuple[int, int] | int, device: Optional[Device] = None, dtype: Optional[Dtype] = None
) -> Tensor:
"""Returns 2d Hanning kernel, used in signal processing and KCF tracker.
Args:
kernel_size: The size of the kernel for the filter. It should be positive.
device: tensor device desired to create the kernel
dtype: tensor dtype desired to create the kernel
Returns:
2D tensor with Hanning filter coefficients. Shape: math:`(\text{kernel_size[0], kernel_size[1]})`
.. math:: w(n) = 0.5 - 0.5cos\\left(\\frac{2\\pi{n}}{M-1}\\right)
"""
kernel_size = _unpack_2d_ks(kernel_size)
_check_kernel_size(kernel_size, 2, allow_even=True)
ky = get_hanning_kernel1d(kernel_size[0], device, dtype)[None].T
kx = get_hanning_kernel1d(kernel_size[1], device, dtype)[None]
kernel2d = ky @ kx
return kernel2d
@deprecated(replace_with="get_gaussian_kernel1d", version="6.9.10")
def get_gaussian_kernel1d_t(*args: Any, **kwargs: Any) -> Tensor:
return get_gaussian_kernel1d(*args, **kwargs)
@deprecated(replace_with="get_gaussian_kernel2d", version="6.9.10")
def get_gaussian_kernel2d_t(*args: Any, **kwargs: Any) -> Tensor:
return get_gaussian_kernel2d(*args, **kwargs)
@deprecated(replace_with="get_gaussian_kernel3d", version="6.9.10")
def get_gaussian_kernel3d_t(*args: Any, **kwargs: Any) -> Tensor:
return get_gaussian_kernel3d(*args, **kwargs)