codeformer / basicsr /data /gaussian_kernels.py
zhangbo2008's picture
Upload folder using huggingface_hub
6c60ccc
raw
history blame
No virus
25.5 kB
import math
import numpy as np
import random
from scipy.ndimage.interpolation import shift
from scipy.stats import multivariate_normal
def sigma_matrix2(sig_x, sig_y, theta):
"""Calculate the rotated sigma matrix (two dimensional matrix).
Args:
sig_x (float):
sig_y (float):
theta (float): Radian measurement.
Returns:
ndarray: Rotated sigma matrix.
"""
D = np.array([[sig_x**2, 0], [0, sig_y**2]])
U = np.array([[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]])
return np.dot(U, np.dot(D, U.T))
def mesh_grid(kernel_size):
"""Generate the mesh grid, centering at zero.
Args:
kernel_size (int):
Returns:
xy (ndarray): with the shape (kernel_size, kernel_size, 2)
xx (ndarray): with the shape (kernel_size, kernel_size)
yy (ndarray): with the shape (kernel_size, kernel_size)
"""
ax = np.arange(-kernel_size // 2 + 1., kernel_size // 2 + 1.)
xx, yy = np.meshgrid(ax, ax)
xy = np.hstack((xx.reshape((kernel_size * kernel_size, 1)),
yy.reshape(kernel_size * kernel_size,
1))).reshape(kernel_size, kernel_size, 2)
return xy, xx, yy
def pdf2(sigma_matrix, grid):
"""Calculate PDF of the bivariate Gaussian distribution.
Args:
sigma_matrix (ndarray): with the shape (2, 2)
grid (ndarray): generated by :func:`mesh_grid`,
with the shape (K, K, 2), K is the kernel size.
Returns:
kernel (ndarrray): un-normalized kernel.
"""
inverse_sigma = np.linalg.inv(sigma_matrix)
kernel = np.exp(-0.5 * np.sum(np.dot(grid, inverse_sigma) * grid, 2))
return kernel
def cdf2(D, grid):
"""Calculate the CDF of the standard bivariate Gaussian distribution.
Used in skewed Gaussian distribution.
Args:
D (ndarrasy): skew matrix.
grid (ndarray): generated by :func:`mesh_grid`,
with the shape (K, K, 2), K is the kernel size.
Returns:
cdf (ndarray): skewed cdf.
"""
rv = multivariate_normal([0, 0], [[1, 0], [0, 1]])
grid = np.dot(grid, D)
cdf = rv.cdf(grid)
return cdf
def bivariate_skew_Gaussian(kernel_size, sig_x, sig_y, theta, D, grid=None):
"""Generate a bivariate skew Gaussian kernel.
Described in `A multivariate skew normal distribution`_ by Shi et. al (2004).
Args:
kernel_size (int):
sig_x (float):
sig_y (float):
theta (float): Radian measurement.
D (ndarrasy): skew matrix.
grid (ndarray, optional): generated by :func:`mesh_grid`,
with the shape (K, K, 2), K is the kernel size. Default: None
Returns:
kernel (ndarray): normalized kernel.
.. _A multivariate skew normal distribution:
https://www.sciencedirect.com/science/article/pii/S0047259X03001313
"""
if grid is None:
grid, _, _ = mesh_grid(kernel_size)
sigma_matrix = sigma_matrix2(sig_x, sig_y, theta)
pdf = pdf2(sigma_matrix, grid)
cdf = cdf2(D, grid)
kernel = pdf * cdf
kernel = kernel / np.sum(kernel)
return kernel
def mass_center_shift(kernel_size, kernel):
"""Calculate the shift of the mass center of a kenrel.
Args:
kernel_size (int):
kernel (ndarray): normalized kernel.
Returns:
delta_h (float):
delta_w (float):
"""
ax = np.arange(-kernel_size // 2 + 1., kernel_size // 2 + 1.)
col_sum, row_sum = np.sum(kernel, axis=0), np.sum(kernel, axis=1)
delta_h = np.dot(row_sum, ax)
delta_w = np.dot(col_sum, ax)
return delta_h, delta_w
def bivariate_skew_Gaussian_center(kernel_size,
sig_x,
sig_y,
theta,
D,
grid=None):
"""Generate a bivariate skew Gaussian kernel at center. Shift with nearest padding.
Args:
kernel_size (int):
sig_x (float):
sig_y (float):
theta (float): Radian measurement.
D (ndarrasy): skew matrix.
grid (ndarray, optional): generated by :func:`mesh_grid`,
with the shape (K, K, 2), K is the kernel size. Default: None
Returns:
kernel (ndarray): centered and normalized kernel.
"""
if grid is None:
grid, _, _ = mesh_grid(kernel_size)
kernel = bivariate_skew_Gaussian(kernel_size, sig_x, sig_y, theta, D, grid)
delta_h, delta_w = mass_center_shift(kernel_size, kernel)
kernel = shift(kernel, [-delta_h, -delta_w], mode='nearest')
kernel = kernel / np.sum(kernel)
return kernel
def bivariate_anisotropic_Gaussian(kernel_size,
sig_x,
sig_y,
theta,
grid=None):
"""Generate a bivariate anisotropic Gaussian kernel.
Args:
kernel_size (int):
sig_x (float):
sig_y (float):
theta (float): Radian measurement.
grid (ndarray, optional): generated by :func:`mesh_grid`,
with the shape (K, K, 2), K is the kernel size. Default: None
Returns:
kernel (ndarray): normalized kernel.
"""
if grid is None:
grid, _, _ = mesh_grid(kernel_size)
sigma_matrix = sigma_matrix2(sig_x, sig_y, theta)
kernel = pdf2(sigma_matrix, grid)
kernel = kernel / np.sum(kernel)
return kernel
def bivariate_isotropic_Gaussian(kernel_size, sig, grid=None):
"""Generate a bivariate isotropic Gaussian kernel.
Args:
kernel_size (int):
sig (float):
grid (ndarray, optional): generated by :func:`mesh_grid`,
with the shape (K, K, 2), K is the kernel size. Default: None
Returns:
kernel (ndarray): normalized kernel.
"""
if grid is None:
grid, _, _ = mesh_grid(kernel_size)
sigma_matrix = np.array([[sig**2, 0], [0, sig**2]])
kernel = pdf2(sigma_matrix, grid)
kernel = kernel / np.sum(kernel)
return kernel
def bivariate_generalized_Gaussian(kernel_size,
sig_x,
sig_y,
theta,
beta,
grid=None):
"""Generate a bivariate generalized Gaussian kernel.
Described in `Parameter Estimation For Multivariate Generalized Gaussian Distributions`_
by Pascal et. al (2013).
Args:
kernel_size (int):
sig_x (float):
sig_y (float):
theta (float): Radian measurement.
beta (float): shape parameter, beta = 1 is the normal distribution.
grid (ndarray, optional): generated by :func:`mesh_grid`,
with the shape (K, K, 2), K is the kernel size. Default: None
Returns:
kernel (ndarray): normalized kernel.
.. _Parameter Estimation For Multivariate Generalized Gaussian Distributions:
https://arxiv.org/abs/1302.6498
"""
if grid is None:
grid, _, _ = mesh_grid(kernel_size)
sigma_matrix = sigma_matrix2(sig_x, sig_y, theta)
inverse_sigma = np.linalg.inv(sigma_matrix)
kernel = np.exp(
-0.5 * np.power(np.sum(np.dot(grid, inverse_sigma) * grid, 2), beta))
kernel = kernel / np.sum(kernel)
return kernel
def bivariate_plateau_type1(kernel_size, sig_x, sig_y, theta, beta, grid=None):
"""Generate a plateau-like anisotropic kernel.
1 / (1+x^(beta))
Args:
kernel_size (int):
sig_x (float):
sig_y (float):
theta (float): Radian measurement.
beta (float): shape parameter, beta = 1 is the normal distribution.
grid (ndarray, optional): generated by :func:`mesh_grid`,
with the shape (K, K, 2), K is the kernel size. Default: None
Returns:
kernel (ndarray): normalized kernel.
"""
if grid is None:
grid, _, _ = mesh_grid(kernel_size)
sigma_matrix = sigma_matrix2(sig_x, sig_y, theta)
inverse_sigma = np.linalg.inv(sigma_matrix)
kernel = np.reciprocal(
np.power(np.sum(np.dot(grid, inverse_sigma) * grid, 2), beta) + 1)
kernel = kernel / np.sum(kernel)
return kernel
def bivariate_plateau_type1_iso(kernel_size, sig, beta, grid=None):
"""Generate a plateau-like isotropic kernel.
1 / (1+x^(beta))
Args:
kernel_size (int):
sig (float):
beta (float): shape parameter, beta = 1 is the normal distribution.
grid (ndarray, optional): generated by :func:`mesh_grid`,
with the shape (K, K, 2), K is the kernel size. Default: None
Returns:
kernel (ndarray): normalized kernel.
"""
if grid is None:
grid, _, _ = mesh_grid(kernel_size)
sigma_matrix = np.array([[sig**2, 0], [0, sig**2]])
inverse_sigma = np.linalg.inv(sigma_matrix)
kernel = np.reciprocal(
np.power(np.sum(np.dot(grid, inverse_sigma) * grid, 2), beta) + 1)
kernel = kernel / np.sum(kernel)
return kernel
def random_bivariate_skew_Gaussian_center(kernel_size,
sigma_x_range,
sigma_y_range,
rotation_range,
noise_range=None,
strict=False):
"""Randomly generate bivariate skew Gaussian kernels at center.
Args:
kernel_size (int):
sigma_x_range (tuple): [0.6, 5]
sigma_y_range (tuple): [0.6, 5]
rotation range (tuple): [-math.pi, math.pi]
noise_range(tuple, optional): multiplicative kernel noise, [0.75, 1.25]. Default: None
Returns:
kernel (ndarray):
"""
assert kernel_size % 2 == 1, 'Kernel size must be an odd number.'
assert sigma_x_range[0] < sigma_x_range[1], 'Wrong sigma_x_range.'
assert sigma_y_range[0] < sigma_y_range[1], 'Wrong sigma_y_range.'
assert rotation_range[0] < rotation_range[1], 'Wrong rotation_range.'
sigma_x = np.random.uniform(sigma_x_range[0], sigma_x_range[1])
sigma_y = np.random.uniform(sigma_y_range[0], sigma_y_range[1])
if strict:
sigma_max = np.max([sigma_x, sigma_y])
sigma_min = np.min([sigma_x, sigma_y])
sigma_x, sigma_y = sigma_max, sigma_min
rotation = np.random.uniform(rotation_range[0], rotation_range[1])
sigma_max = np.max([sigma_x, sigma_y])
thres = 3 / sigma_max
D = [[np.random.uniform(-thres, thres),
np.random.uniform(-thres, thres)],
[np.random.uniform(-thres, thres),
np.random.uniform(-thres, thres)]]
kernel = bivariate_skew_Gaussian_center(kernel_size, sigma_x, sigma_y,
rotation, D)
# add multiplicative noise
if noise_range is not None:
assert noise_range[0] < noise_range[1], 'Wrong noise range.'
noise = np.random.uniform(
noise_range[0], noise_range[1], size=kernel.shape)
kernel = kernel * noise
kernel = kernel / np.sum(kernel)
if strict:
return kernel, sigma_x, sigma_y, rotation, D
else:
return kernel
def random_bivariate_anisotropic_Gaussian(kernel_size,
sigma_x_range,
sigma_y_range,
rotation_range,
noise_range=None,
strict=False):
"""Randomly generate bivariate anisotropic Gaussian kernels.
Args:
kernel_size (int):
sigma_x_range (tuple): [0.6, 5]
sigma_y_range (tuple): [0.6, 5]
rotation range (tuple): [-math.pi, math.pi]
noise_range(tuple, optional): multiplicative kernel noise, [0.75, 1.25]. Default: None
Returns:
kernel (ndarray):
"""
assert kernel_size % 2 == 1, 'Kernel size must be an odd number.'
assert sigma_x_range[0] < sigma_x_range[1], 'Wrong sigma_x_range.'
assert sigma_y_range[0] < sigma_y_range[1], 'Wrong sigma_y_range.'
assert rotation_range[0] < rotation_range[1], 'Wrong rotation_range.'
sigma_x = np.random.uniform(sigma_x_range[0], sigma_x_range[1])
sigma_y = np.random.uniform(sigma_y_range[0], sigma_y_range[1])
if strict:
sigma_max = np.max([sigma_x, sigma_y])
sigma_min = np.min([sigma_x, sigma_y])
sigma_x, sigma_y = sigma_max, sigma_min
rotation = np.random.uniform(rotation_range[0], rotation_range[1])
kernel = bivariate_anisotropic_Gaussian(kernel_size, sigma_x, sigma_y,
rotation)
# add multiplicative noise
if noise_range is not None:
assert noise_range[0] < noise_range[1], 'Wrong noise range.'
noise = np.random.uniform(
noise_range[0], noise_range[1], size=kernel.shape)
kernel = kernel * noise
kernel = kernel / np.sum(kernel)
if strict:
return kernel, sigma_x, sigma_y, rotation
else:
return kernel
def random_bivariate_isotropic_Gaussian(kernel_size,
sigma_range,
noise_range=None,
strict=False):
"""Randomly generate bivariate isotropic Gaussian kernels.
Args:
kernel_size (int):
sigma_range (tuple): [0.6, 5]
noise_range(tuple, optional): multiplicative kernel noise, [0.75, 1.25]. Default: None
Returns:
kernel (ndarray):
"""
assert kernel_size % 2 == 1, 'Kernel size must be an odd number.'
assert sigma_range[0] < sigma_range[1], 'Wrong sigma_x_range.'
sigma = np.random.uniform(sigma_range[0], sigma_range[1])
kernel = bivariate_isotropic_Gaussian(kernel_size, sigma)
# add multiplicative noise
if noise_range is not None:
assert noise_range[0] < noise_range[1], 'Wrong noise range.'
noise = np.random.uniform(
noise_range[0], noise_range[1], size=kernel.shape)
kernel = kernel * noise
kernel = kernel / np.sum(kernel)
if strict:
return kernel, sigma
else:
return kernel
def random_bivariate_generalized_Gaussian(kernel_size,
sigma_x_range,
sigma_y_range,
rotation_range,
beta_range,
noise_range=None,
strict=False):
"""Randomly generate bivariate generalized Gaussian kernels.
Args:
kernel_size (int):
sigma_x_range (tuple): [0.6, 5]
sigma_y_range (tuple): [0.6, 5]
rotation range (tuple): [-math.pi, math.pi]
beta_range (tuple): [0.5, 8]
noise_range(tuple, optional): multiplicative kernel noise, [0.75, 1.25]. Default: None
Returns:
kernel (ndarray):
"""
assert kernel_size % 2 == 1, 'Kernel size must be an odd number.'
assert sigma_x_range[0] < sigma_x_range[1], 'Wrong sigma_x_range.'
assert sigma_y_range[0] < sigma_y_range[1], 'Wrong sigma_y_range.'
assert rotation_range[0] < rotation_range[1], 'Wrong rotation_range.'
sigma_x = np.random.uniform(sigma_x_range[0], sigma_x_range[1])
sigma_y = np.random.uniform(sigma_y_range[0], sigma_y_range[1])
if strict:
sigma_max = np.max([sigma_x, sigma_y])
sigma_min = np.min([sigma_x, sigma_y])
sigma_x, sigma_y = sigma_max, sigma_min
rotation = np.random.uniform(rotation_range[0], rotation_range[1])
if np.random.uniform() < 0.5:
beta = np.random.uniform(beta_range[0], 1)
else:
beta = np.random.uniform(1, beta_range[1])
kernel = bivariate_generalized_Gaussian(kernel_size, sigma_x, sigma_y,
rotation, beta)
# add multiplicative noise
if noise_range is not None:
assert noise_range[0] < noise_range[1], 'Wrong noise range.'
noise = np.random.uniform(
noise_range[0], noise_range[1], size=kernel.shape)
kernel = kernel * noise
kernel = kernel / np.sum(kernel)
if strict:
return kernel, sigma_x, sigma_y, rotation, beta
else:
return kernel
def random_bivariate_plateau_type1(kernel_size,
sigma_x_range,
sigma_y_range,
rotation_range,
beta_range,
noise_range=None,
strict=False):
"""Randomly generate bivariate plateau type1 kernels.
Args:
kernel_size (int):
sigma_x_range (tuple): [0.6, 5]
sigma_y_range (tuple): [0.6, 5]
rotation range (tuple): [-math.pi/2, math.pi/2]
beta_range (tuple): [1, 4]
noise_range(tuple, optional): multiplicative kernel noise, [0.75, 1.25]. Default: None
Returns:
kernel (ndarray):
"""
assert kernel_size % 2 == 1, 'Kernel size must be an odd number.'
assert sigma_x_range[0] < sigma_x_range[1], 'Wrong sigma_x_range.'
assert sigma_y_range[0] < sigma_y_range[1], 'Wrong sigma_y_range.'
assert rotation_range[0] < rotation_range[1], 'Wrong rotation_range.'
sigma_x = np.random.uniform(sigma_x_range[0], sigma_x_range[1])
sigma_y = np.random.uniform(sigma_y_range[0], sigma_y_range[1])
if strict:
sigma_max = np.max([sigma_x, sigma_y])
sigma_min = np.min([sigma_x, sigma_y])
sigma_x, sigma_y = sigma_max, sigma_min
rotation = np.random.uniform(rotation_range[0], rotation_range[1])
if np.random.uniform() < 0.5:
beta = np.random.uniform(beta_range[0], 1)
else:
beta = np.random.uniform(1, beta_range[1])
kernel = bivariate_plateau_type1(kernel_size, sigma_x, sigma_y, rotation,
beta)
# add multiplicative noise
if noise_range is not None:
assert noise_range[0] < noise_range[1], 'Wrong noise range.'
noise = np.random.uniform(
noise_range[0], noise_range[1], size=kernel.shape)
kernel = kernel * noise
kernel = kernel / np.sum(kernel)
if strict:
return kernel, sigma_x, sigma_y, rotation, beta
else:
return kernel
def random_bivariate_plateau_type1_iso(kernel_size,
sigma_range,
beta_range,
noise_range=None,
strict=False):
"""Randomly generate bivariate plateau type1 kernels (iso).
Args:
kernel_size (int):
sigma_range (tuple): [0.6, 5]
beta_range (tuple): [1, 4]
noise_range(tuple, optional): multiplicative kernel noise, [0.75, 1.25]. Default: None
Returns:
kernel (ndarray):
"""
assert kernel_size % 2 == 1, 'Kernel size must be an odd number.'
assert sigma_range[0] < sigma_range[1], 'Wrong sigma_x_range.'
sigma = np.random.uniform(sigma_range[0], sigma_range[1])
beta = np.random.uniform(beta_range[0], beta_range[1])
kernel = bivariate_plateau_type1_iso(kernel_size, sigma, beta)
# add multiplicative noise
if noise_range is not None:
assert noise_range[0] < noise_range[1], 'Wrong noise range.'
noise = np.random.uniform(
noise_range[0], noise_range[1], size=kernel.shape)
kernel = kernel * noise
kernel = kernel / np.sum(kernel)
if strict:
return kernel, sigma, beta
else:
return kernel
def random_mixed_kernels(kernel_list,
kernel_prob,
kernel_size=21,
sigma_x_range=[0.6, 5],
sigma_y_range=[0.6, 5],
rotation_range=[-math.pi, math.pi],
beta_range=[0.5, 8],
noise_range=None):
"""Randomly generate mixed kernels.
Args:
kernel_list (tuple): a list name of kenrel types,
support ['iso', 'aniso', 'skew', 'generalized', 'plateau_iso', 'plateau_aniso']
kernel_prob (tuple): corresponding kernel probability for each kernel type
kernel_size (int):
sigma_x_range (tuple): [0.6, 5]
sigma_y_range (tuple): [0.6, 5]
rotation range (tuple): [-math.pi, math.pi]
beta_range (tuple): [0.5, 8]
noise_range(tuple, optional): multiplicative kernel noise, [0.75, 1.25]. Default: None
Returns:
kernel (ndarray):
"""
kernel_type = random.choices(kernel_list, kernel_prob)[0]
if kernel_type == 'iso':
kernel = random_bivariate_isotropic_Gaussian(
kernel_size, sigma_x_range, noise_range=noise_range)
elif kernel_type == 'aniso':
kernel = random_bivariate_anisotropic_Gaussian(
kernel_size,
sigma_x_range,
sigma_y_range,
rotation_range,
noise_range=noise_range)
elif kernel_type == 'skew':
kernel = random_bivariate_skew_Gaussian_center(
kernel_size,
sigma_x_range,
sigma_y_range,
rotation_range,
noise_range=noise_range)
elif kernel_type == 'generalized':
kernel = random_bivariate_generalized_Gaussian(
kernel_size,
sigma_x_range,
sigma_y_range,
rotation_range,
beta_range,
noise_range=noise_range)
elif kernel_type == 'plateau_iso':
kernel = random_bivariate_plateau_type1_iso(
kernel_size, sigma_x_range, beta_range, noise_range=noise_range)
elif kernel_type == 'plateau_aniso':
kernel = random_bivariate_plateau_type1(
kernel_size,
sigma_x_range,
sigma_y_range,
rotation_range,
beta_range,
noise_range=noise_range)
# add multiplicative noise
if noise_range is not None:
assert noise_range[0] < noise_range[1], 'Wrong noise range.'
noise = np.random.uniform(
noise_range[0], noise_range[1], size=kernel.shape)
kernel = kernel * noise
kernel = kernel / np.sum(kernel)
return kernel
def show_one_kernel():
import matplotlib.pyplot as plt
kernel_size = 21
# bivariate skew Gaussian
D = [[0, 0], [0, 0]]
D = [[3 / 4, 0], [0, 0.5]]
kernel = bivariate_skew_Gaussian_center(kernel_size, 2, 4, -math.pi / 4, D)
# bivariate anisotropic Gaussian
kernel = bivariate_anisotropic_Gaussian(kernel_size, 2, 4, -math.pi / 4)
# bivariate anisotropic Gaussian
kernel = bivariate_isotropic_Gaussian(kernel_size, 1)
# bivariate generalized Gaussian
kernel = bivariate_generalized_Gaussian(
kernel_size, 2, 4, -math.pi / 4, beta=4)
delta_h, delta_w = mass_center_shift(kernel_size, kernel)
print(delta_h, delta_w)
fig, axs = plt.subplots(nrows=2, ncols=2)
# axs.set_axis_off()
ax = axs[0][0]
im = ax.matshow(kernel, cmap='jet', origin='upper')
fig.colorbar(im, ax=ax)
# image
ax = axs[0][1]
kernel_vis = kernel - np.min(kernel)
kernel_vis = kernel_vis / np.max(kernel_vis) * 255.
ax.imshow(kernel_vis, interpolation='nearest')
_, xx, yy = mesh_grid(kernel_size)
# contour
ax = axs[1][0]
CS = ax.contour(xx, yy, kernel, origin='upper')
ax.clabel(CS, inline=1, fontsize=3)
# contourf
ax = axs[1][1]
kernel = kernel / np.max(kernel)
p = ax.contourf(
xx, yy, kernel, origin='upper', levels=np.linspace(-0.05, 1.05, 10))
fig.colorbar(p)
plt.show()
def show_plateau_kernel():
import matplotlib.pyplot as plt
kernel_size = 21
kernel = plateau_type1(kernel_size, 2, 4, -math.pi / 8, 2, grid=None)
kernel_norm = bivariate_isotropic_Gaussian(kernel_size, 5)
kernel_gau = bivariate_generalized_Gaussian(
kernel_size, 2, 4, -math.pi / 8, 2, grid=None)
delta_h, delta_w = mass_center_shift(kernel_size, kernel)
print(delta_h, delta_w)
# kernel_slice = kernel[10, :]
# kernel_gau_slice = kernel_gau[10, :]
# kernel_norm_slice = kernel_norm[10, :]
# fig, ax = plt.subplots()
# t = list(range(1, 22))
# ax.plot(t, kernel_gau_slice)
# ax.plot(t, kernel_slice)
# ax.plot(t, kernel_norm_slice)
# t = np.arange(0, 10, 0.1)
# y = np.exp(-0.5 * t)
# y2 = np.reciprocal(1 + t)
# print(t.shape)
# print(y.shape)
# ax.plot(t, y)
# ax.plot(t, y2)
# plt.show()
fig, axs = plt.subplots(nrows=2, ncols=2)
# axs.set_axis_off()
ax = axs[0][0]
im = ax.matshow(kernel, cmap='jet', origin='upper')
fig.colorbar(im, ax=ax)
# image
ax = axs[0][1]
kernel_vis = kernel - np.min(kernel)
kernel_vis = kernel_vis / np.max(kernel_vis) * 255.
ax.imshow(kernel_vis, interpolation='nearest')
_, xx, yy = mesh_grid(kernel_size)
# contour
ax = axs[1][0]
CS = ax.contour(xx, yy, kernel, origin='upper')
ax.clabel(CS, inline=1, fontsize=3)
# contourf
ax = axs[1][1]
kernel = kernel / np.max(kernel)
p = ax.contourf(
xx, yy, kernel, origin='upper', levels=np.linspace(-0.05, 1.05, 10))
fig.colorbar(p)
plt.show()