Team Ai
Apppublic

Dynamatrix/DiffBIR-OpenXLab

sourceHugging Faceapache-2.0updated 2y agoView on Hugging Face
0likes
degradation.py766 linesDownload Raw Back to utils
1# https://github.com/XPixelGroup/BasicSR/blob/master/basicsr/data/degradations.py2import cv23import math4import numpy as np5import random6import torch7from scipy import special8from scipy.stats import multivariate_normal9from torchvision.transforms.functional_tensor import rgb_to_grayscale10 11# -------------------------------------------------------------------- #12# --------------------------- blur kernels --------------------------- #13# -------------------------------------------------------------------- #14 15 16# --------------------------- util functions --------------------------- #17def sigma_matrix2(sig_x, sig_y, theta):18    """Calculate the rotated sigma matrix (two dimensional matrix).19 20    Args:21        sig_x (float):22        sig_y (float):23        theta (float): Radian measurement.24 25    Returns:26        ndarray: Rotated sigma matrix.27    """28    d_matrix = np.array([[sig_x**2, 0], [0, sig_y**2]])29    u_matrix = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]])30    return np.dot(u_matrix, np.dot(d_matrix, u_matrix.T))31 32 33def mesh_grid(kernel_size):34    """Generate the mesh grid, centering at zero.35 36    Args:37        kernel_size (int):38 39    Returns:40        xy (ndarray): with the shape (kernel_size, kernel_size, 2)41        xx (ndarray): with the shape (kernel_size, kernel_size)42        yy (ndarray): with the shape (kernel_size, kernel_size)43    """44    ax = np.arange(-kernel_size // 2 + 1., kernel_size // 2 + 1.)45    xx, yy = np.meshgrid(ax, ax)46    xy = np.hstack((xx.reshape((kernel_size * kernel_size, 1)), yy.reshape(kernel_size * kernel_size,47                                                                           1))).reshape(kernel_size, kernel_size, 2)48    return xy, xx, yy49 50 51def pdf2(sigma_matrix, grid):52    """Calculate PDF of the bivariate Gaussian distribution.53 54    Args:55        sigma_matrix (ndarray): with the shape (2, 2)56        grid (ndarray): generated by :func:`mesh_grid`,57            with the shape (K, K, 2), K is the kernel size.58 59    Returns:60        kernel (ndarrray): un-normalized kernel.61    """62    inverse_sigma = np.linalg.inv(sigma_matrix)63    kernel = np.exp(-0.5 * np.sum(np.dot(grid, inverse_sigma) * grid, 2))64    return kernel65 66 67def cdf2(d_matrix, grid):68    """Calculate the CDF of the standard bivariate Gaussian distribution.69        Used in skewed Gaussian distribution.70 71    Args:72        d_matrix (ndarrasy): skew matrix.73        grid (ndarray): generated by :func:`mesh_grid`,74            with the shape (K, K, 2), K is the kernel size.75 76    Returns:77        cdf (ndarray): skewed cdf.78    """79    rv = multivariate_normal([0, 0], [[1, 0], [0, 1]])80    grid = np.dot(grid, d_matrix)81    cdf = rv.cdf(grid)82    return cdf83 84 85def bivariate_Gaussian(kernel_size, sig_x, sig_y, theta, grid=None, isotropic=True):86    """Generate a bivariate isotropic or anisotropic Gaussian kernel.87 88    In the isotropic mode, only `sig_x` is used. `sig_y` and `theta` is ignored.89 90    Args:91        kernel_size (int):92        sig_x (float):93        sig_y (float):94        theta (float): Radian measurement.95        grid (ndarray, optional): generated by :func:`mesh_grid`,96            with the shape (K, K, 2), K is the kernel size. Default: None97        isotropic (bool):98 99    Returns:100        kernel (ndarray): normalized kernel.101    """102    if grid is None:103        grid, _, _ = mesh_grid(kernel_size)104    if isotropic:105        sigma_matrix = np.array([[sig_x**2, 0], [0, sig_x**2]])106    else:107        sigma_matrix = sigma_matrix2(sig_x, sig_y, theta)108    kernel = pdf2(sigma_matrix, grid)109    kernel = kernel / np.sum(kernel)110    return kernel111 112 113def bivariate_generalized_Gaussian(kernel_size, sig_x, sig_y, theta, beta, grid=None, isotropic=True):114    """Generate a bivariate generalized Gaussian kernel.115 116    ``Paper: Parameter Estimation For Multivariate Generalized Gaussian Distributions``117 118    In the isotropic mode, only `sig_x` is used. `sig_y` and `theta` is ignored.119 120    Args:121        kernel_size (int):122        sig_x (float):123        sig_y (float):124        theta (float): Radian measurement.125        beta (float): shape parameter, beta = 1 is the normal distribution.126        grid (ndarray, optional): generated by :func:`mesh_grid`,127            with the shape (K, K, 2), K is the kernel size. Default: None128 129    Returns:130        kernel (ndarray): normalized kernel.131    """132    if grid is None:133        grid, _, _ = mesh_grid(kernel_size)134    if isotropic:135        sigma_matrix = np.array([[sig_x**2, 0], [0, sig_x**2]])136    else:137        sigma_matrix = sigma_matrix2(sig_x, sig_y, theta)138    inverse_sigma = np.linalg.inv(sigma_matrix)139    kernel = np.exp(-0.5 * np.power(np.sum(np.dot(grid, inverse_sigma) * grid, 2), beta))140    kernel = kernel / np.sum(kernel)141    return kernel142 143 144def bivariate_plateau(kernel_size, sig_x, sig_y, theta, beta, grid=None, isotropic=True):145    """Generate a plateau-like anisotropic kernel.146 147    1 / (1+x^(beta))148 149    Reference: https://stats.stackexchange.com/questions/203629/is-there-a-plateau-shaped-distribution150 151    In the isotropic mode, only `sig_x` is used. `sig_y` and `theta` is ignored.152 153    Args:154        kernel_size (int):155        sig_x (float):156        sig_y (float):157        theta (float): Radian measurement.158        beta (float): shape parameter, beta = 1 is the normal distribution.159        grid (ndarray, optional): generated by :func:`mesh_grid`,160            with the shape (K, K, 2), K is the kernel size. Default: None161 162    Returns:163        kernel (ndarray): normalized kernel.164    """165    if grid is None:166        grid, _, _ = mesh_grid(kernel_size)167    if isotropic:168        sigma_matrix = np.array([[sig_x**2, 0], [0, sig_x**2]])169    else:170        sigma_matrix = sigma_matrix2(sig_x, sig_y, theta)171    inverse_sigma = np.linalg.inv(sigma_matrix)172    kernel = np.reciprocal(np.power(np.sum(np.dot(grid, inverse_sigma) * grid, 2), beta) + 1)173    kernel = kernel / np.sum(kernel)174    return kernel175 176 177def random_bivariate_Gaussian(kernel_size,178                              sigma_x_range,179                              sigma_y_range,180                              rotation_range,181                              noise_range=None,182                              isotropic=True):183    """Randomly generate bivariate isotropic or anisotropic Gaussian kernels.184 185    In the isotropic mode, only `sigma_x_range` is used. `sigma_y_range` and `rotation_range` is ignored.186 187    Args:188        kernel_size (int):189        sigma_x_range (tuple): [0.6, 5]190        sigma_y_range (tuple): [0.6, 5]191        rotation range (tuple): [-math.pi, math.pi]192        noise_range(tuple, optional): multiplicative kernel noise,193            [0.75, 1.25]. Default: None194 195    Returns:196        kernel (ndarray):197    """198    assert kernel_size % 2 == 1, 'Kernel size must be an odd number.'199    assert sigma_x_range[0] < sigma_x_range[1], 'Wrong sigma_x_range.'200    sigma_x = np.random.uniform(sigma_x_range[0], sigma_x_range[1])201    if isotropic is False:202        assert sigma_y_range[0] < sigma_y_range[1], 'Wrong sigma_y_range.'203        assert rotation_range[0] < rotation_range[1], 'Wrong rotation_range.'204        sigma_y = np.random.uniform(sigma_y_range[0], sigma_y_range[1])205        rotation = np.random.uniform(rotation_range[0], rotation_range[1])206    else:207        sigma_y = sigma_x208        rotation = 0209 210    kernel = bivariate_Gaussian(kernel_size, sigma_x, sigma_y, rotation, isotropic=isotropic)211 212    # add multiplicative noise213    if noise_range is not None:214        assert noise_range[0] < noise_range[1], 'Wrong noise range.'215        noise = np.random.uniform(noise_range[0], noise_range[1], size=kernel.shape)216        kernel = kernel * noise217    kernel = kernel / np.sum(kernel)218    return kernel219 220 221def random_bivariate_generalized_Gaussian(kernel_size,222                                          sigma_x_range,223                                          sigma_y_range,224                                          rotation_range,225                                          beta_range,226                                          noise_range=None,227                                          isotropic=True):228    """Randomly generate bivariate generalized Gaussian kernels.229 230    In the isotropic mode, only `sigma_x_range` is used. `sigma_y_range` and `rotation_range` is ignored.231 232    Args:233        kernel_size (int):234        sigma_x_range (tuple): [0.6, 5]235        sigma_y_range (tuple): [0.6, 5]236        rotation range (tuple): [-math.pi, math.pi]237        beta_range (tuple): [0.5, 8]238        noise_range(tuple, optional): multiplicative kernel noise,239            [0.75, 1.25]. Default: None240 241    Returns:242        kernel (ndarray):243    """244    assert kernel_size % 2 == 1, 'Kernel size must be an odd number.'245    assert sigma_x_range[0] < sigma_x_range[1], 'Wrong sigma_x_range.'246    sigma_x = np.random.uniform(sigma_x_range[0], sigma_x_range[1])247    if isotropic is False:248        assert sigma_y_range[0] < sigma_y_range[1], 'Wrong sigma_y_range.'249        assert rotation_range[0] < rotation_range[1], 'Wrong rotation_range.'250        sigma_y = np.random.uniform(sigma_y_range[0], sigma_y_range[1])251        rotation = np.random.uniform(rotation_range[0], rotation_range[1])252    else:253        sigma_y = sigma_x254        rotation = 0255 256    # assume beta_range[0] < 1 < beta_range[1]257    if np.random.uniform() < 0.5:258        beta = np.random.uniform(beta_range[0], 1)259    else:260        beta = np.random.uniform(1, beta_range[1])261 262    kernel = bivariate_generalized_Gaussian(kernel_size, sigma_x, sigma_y, rotation, beta, isotropic=isotropic)263 264    # add multiplicative noise265    if noise_range is not None:266        assert noise_range[0] < noise_range[1], 'Wrong noise range.'267        noise = np.random.uniform(noise_range[0], noise_range[1], size=kernel.shape)268        kernel = kernel * noise269    kernel = kernel / np.sum(kernel)270    return kernel271 272 273def random_bivariate_plateau(kernel_size,274                             sigma_x_range,275                             sigma_y_range,276                             rotation_range,277                             beta_range,278                             noise_range=None,279                             isotropic=True):280    """Randomly generate bivariate plateau kernels.281 282    In the isotropic mode, only `sigma_x_range` is used. `sigma_y_range` and `rotation_range` is ignored.283 284    Args:285        kernel_size (int):286        sigma_x_range (tuple): [0.6, 5]287        sigma_y_range (tuple): [0.6, 5]288        rotation range (tuple): [-math.pi/2, math.pi/2]289        beta_range (tuple): [1, 4]290        noise_range(tuple, optional): multiplicative kernel noise,291            [0.75, 1.25]. Default: None292 293    Returns:294        kernel (ndarray):295    """296    assert kernel_size % 2 == 1, 'Kernel size must be an odd number.'297    assert sigma_x_range[0] < sigma_x_range[1], 'Wrong sigma_x_range.'298    sigma_x = np.random.uniform(sigma_x_range[0], sigma_x_range[1])299    if isotropic is False:300        assert sigma_y_range[0] < sigma_y_range[1], 'Wrong sigma_y_range.'301        assert rotation_range[0] < rotation_range[1], 'Wrong rotation_range.'302        sigma_y = np.random.uniform(sigma_y_range[0], sigma_y_range[1])303        rotation = np.random.uniform(rotation_range[0], rotation_range[1])304    else:305        sigma_y = sigma_x306        rotation = 0307 308    # TODO: this may be not proper309    if np.random.uniform() < 0.5:310        beta = np.random.uniform(beta_range[0], 1)311    else:312        beta = np.random.uniform(1, beta_range[1])313 314    kernel = bivariate_plateau(kernel_size, sigma_x, sigma_y, rotation, beta, isotropic=isotropic)315    # add multiplicative noise316    if noise_range is not None:317        assert noise_range[0] < noise_range[1], 'Wrong noise range.'318        noise = np.random.uniform(noise_range[0], noise_range[1], size=kernel.shape)319        kernel = kernel * noise320    kernel = kernel / np.sum(kernel)321 322    return kernel323 324 325def random_mixed_kernels(kernel_list,326                         kernel_prob,327                         kernel_size=21,328                         sigma_x_range=(0.6, 5),329                         sigma_y_range=(0.6, 5),330                         rotation_range=(-math.pi, math.pi),331                         betag_range=(0.5, 8),332                         betap_range=(0.5, 8),333                         noise_range=None):334    """Randomly generate mixed kernels.335 336    Args:337        kernel_list (tuple): a list name of kernel types,338            support ['iso', 'aniso', 'skew', 'generalized', 'plateau_iso',339            'plateau_aniso']340        kernel_prob (tuple): corresponding kernel probability for each341            kernel type342        kernel_size (int):343        sigma_x_range (tuple): [0.6, 5]344        sigma_y_range (tuple): [0.6, 5]345        rotation range (tuple): [-math.pi, math.pi]346        beta_range (tuple): [0.5, 8]347        noise_range(tuple, optional): multiplicative kernel noise,348            [0.75, 1.25]. Default: None349 350    Returns:351        kernel (ndarray):352    """353    kernel_type = random.choices(kernel_list, kernel_prob)[0]354    if kernel_type == 'iso':355        kernel = random_bivariate_Gaussian(356            kernel_size, sigma_x_range, sigma_y_range, rotation_range, noise_range=noise_range, isotropic=True)357    elif kernel_type == 'aniso':358        kernel = random_bivariate_Gaussian(359            kernel_size, sigma_x_range, sigma_y_range, rotation_range, noise_range=noise_range, isotropic=False)360    elif kernel_type == 'generalized_iso':361        kernel = random_bivariate_generalized_Gaussian(362            kernel_size,363            sigma_x_range,364            sigma_y_range,365            rotation_range,366            betag_range,367            noise_range=noise_range,368            isotropic=True)369    elif kernel_type == 'generalized_aniso':370        kernel = random_bivariate_generalized_Gaussian(371            kernel_size,372            sigma_x_range,373            sigma_y_range,374            rotation_range,375            betag_range,376            noise_range=noise_range,377            isotropic=False)378    elif kernel_type == 'plateau_iso':379        kernel = random_bivariate_plateau(380            kernel_size, sigma_x_range, sigma_y_range, rotation_range, betap_range, noise_range=None, isotropic=True)381    elif kernel_type == 'plateau_aniso':382        kernel = random_bivariate_plateau(383            kernel_size, sigma_x_range, sigma_y_range, rotation_range, betap_range, noise_range=None, isotropic=False)384    return kernel385 386 387np.seterr(divide='ignore', invalid='ignore')388 389 390def circular_lowpass_kernel(cutoff, kernel_size, pad_to=0):391    """2D sinc filter392 393    Reference: https://dsp.stackexchange.com/questions/58301/2-d-circularly-symmetric-low-pass-filter394 395    Args:396        cutoff (float): cutoff frequency in radians (pi is max)397        kernel_size (int): horizontal and vertical size, must be odd.398        pad_to (int): pad kernel size to desired size, must be odd or zero.399    """400    assert kernel_size % 2 == 1, 'Kernel size must be an odd number.'401    kernel = np.fromfunction(402        lambda x, y: cutoff * special.j1(cutoff * np.sqrt(403            (x - (kernel_size - 1) / 2)**2 + (y - (kernel_size - 1) / 2)**2)) / (2 * np.pi * np.sqrt(404                (x - (kernel_size - 1) / 2)**2 + (y - (kernel_size - 1) / 2)**2)), [kernel_size, kernel_size])405    kernel[(kernel_size - 1) // 2, (kernel_size - 1) // 2] = cutoff**2 / (4 * np.pi)406    kernel = kernel / np.sum(kernel)407    if pad_to > kernel_size:408        pad_size = (pad_to - kernel_size) // 2409        kernel = np.pad(kernel, ((pad_size, pad_size), (pad_size, pad_size)))410    return kernel411 412 413# ------------------------------------------------------------- #414# --------------------------- noise --------------------------- #415# ------------------------------------------------------------- #416 417# ----------------------- Gaussian Noise ----------------------- #418 419 420def generate_gaussian_noise(img, sigma=10, gray_noise=False):421    """Generate Gaussian noise.422 423    Args:424        img (Numpy array): Input image, shape (h, w, c), range [0, 1], float32.425        sigma (float): Noise scale (measured in range 255). Default: 10.426 427    Returns:428        (Numpy array): Returned noisy image, shape (h, w, c), range[0, 1],429            float32.430    """431    if gray_noise:432        noise = np.float32(np.random.randn(*(img.shape[0:2]))) * sigma / 255.433        noise = np.expand_dims(noise, axis=2).repeat(3, axis=2)434    else:435        noise = np.float32(np.random.randn(*(img.shape))) * sigma / 255.436    return noise437 438 439def add_gaussian_noise(img, sigma=10, clip=True, rounds=False, gray_noise=False):440    """Add Gaussian noise.441 442    Args:443        img (Numpy array): Input image, shape (h, w, c), range [0, 1], float32.444        sigma (float): Noise scale (measured in range 255). Default: 10.445 446    Returns:447        (Numpy array): Returned noisy image, shape (h, w, c), range[0, 1],448            float32.449    """450    noise = generate_gaussian_noise(img, sigma, gray_noise)451    out = img + noise452    if clip and rounds:453        out = np.clip((out * 255.0).round(), 0, 255) / 255.454    elif clip:455        out = np.clip(out, 0, 1)456    elif rounds:457        out = (out * 255.0).round() / 255.458    return out459 460 461def generate_gaussian_noise_pt(img, sigma=10, gray_noise=0):462    """Add Gaussian noise (PyTorch version).463 464    Args:465        img (Tensor): Shape (b, c, h, w), range[0, 1], float32.466        scale (float | Tensor): Noise scale. Default: 1.0.467 468    Returns:469        (Tensor): Returned noisy image, shape (b, c, h, w), range[0, 1],470            float32.471    """472    b, _, h, w = img.size()473    if not isinstance(sigma, (float, int)):474        sigma = sigma.view(img.size(0), 1, 1, 1)475    if isinstance(gray_noise, (float, int)):476        cal_gray_noise = gray_noise > 0477    else:478        gray_noise = gray_noise.view(b, 1, 1, 1)479        cal_gray_noise = torch.sum(gray_noise) > 0480 481    if cal_gray_noise:482        noise_gray = torch.randn(*img.size()[2:4], dtype=img.dtype, device=img.device) * sigma / 255.483        noise_gray = noise_gray.view(b, 1, h, w)484 485    # always calculate color noise486    noise = torch.randn(*img.size(), dtype=img.dtype, device=img.device) * sigma / 255.487 488    if cal_gray_noise:489        noise = noise * (1 - gray_noise) + noise_gray * gray_noise490    return noise491 492 493def add_gaussian_noise_pt(img, sigma=10, gray_noise=0, clip=True, rounds=False):494    """Add Gaussian noise (PyTorch version).495 496    Args:497        img (Tensor): Shape (b, c, h, w), range[0, 1], float32.498        scale (float | Tensor): Noise scale. Default: 1.0.499 500    Returns:501        (Tensor): Returned noisy image, shape (b, c, h, w), range[0, 1],502            float32.503    """504    noise = generate_gaussian_noise_pt(img, sigma, gray_noise)505    out = img + noise506    if clip and rounds:507        out = torch.clamp((out * 255.0).round(), 0, 255) / 255.508    elif clip:509        out = torch.clamp(out, 0, 1)510    elif rounds:511        out = (out * 255.0).round() / 255.512    return out513 514 515# ----------------------- Random Gaussian Noise ----------------------- #516def random_generate_gaussian_noise(img, sigma_range=(0, 10), gray_prob=0):517    sigma = np.random.uniform(sigma_range[0], sigma_range[1])518    if np.random.uniform() < gray_prob:519        gray_noise = True520    else:521        gray_noise = False522    return generate_gaussian_noise(img, sigma, gray_noise)523 524 525def random_add_gaussian_noise(img, sigma_range=(0, 1.0), gray_prob=0, clip=True, rounds=False):526    noise = random_generate_gaussian_noise(img, sigma_range, gray_prob)527    out = img + noise528    if clip and rounds:529        out = np.clip((out * 255.0).round(), 0, 255) / 255.530    elif clip:531        out = np.clip(out, 0, 1)532    elif rounds:533        out = (out * 255.0).round() / 255.534    return out535 536 537def random_generate_gaussian_noise_pt(img, sigma_range=(0, 10), gray_prob=0):538    sigma = torch.rand(539        img.size(0), dtype=img.dtype, device=img.device) * (sigma_range[1] - sigma_range[0]) + sigma_range[0]540    gray_noise = torch.rand(img.size(0), dtype=img.dtype, device=img.device)541    gray_noise = (gray_noise < gray_prob).float()542    return generate_gaussian_noise_pt(img, sigma, gray_noise)543 544 545def random_add_gaussian_noise_pt(img, sigma_range=(0, 1.0), gray_prob=0, clip=True, rounds=False):546    noise = random_generate_gaussian_noise_pt(img, sigma_range, gray_prob)547    out = img + noise548    if clip and rounds:549        out = torch.clamp((out * 255.0).round(), 0, 255) / 255.550    elif clip:551        out = torch.clamp(out, 0, 1)552    elif rounds:553        out = (out * 255.0).round() / 255.554    return out555 556 557# ----------------------- Poisson (Shot) Noise ----------------------- #558 559 560def generate_poisson_noise(img, scale=1.0, gray_noise=False):561    """Generate poisson noise.562 563    Reference: https://github.com/scikit-image/scikit-image/blob/main/skimage/util/noise.py#L37-L219564 565    Args:566        img (Numpy array): Input image, shape (h, w, c), range [0, 1], float32.567        scale (float): Noise scale. Default: 1.0.568        gray_noise (bool): Whether generate gray noise. Default: False.569 570    Returns:571        (Numpy array): Returned noisy image, shape (h, w, c), range[0, 1],572            float32.573    """574    if gray_noise:575        img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)576    # round and clip image for counting vals correctly577    img = np.clip((img * 255.0).round(), 0, 255) / 255.578    vals = len(np.unique(img))579    vals = 2**np.ceil(np.log2(vals))580    out = np.float32(np.random.poisson(img * vals) / float(vals))581    noise = out - img582    if gray_noise:583        noise = np.repeat(noise[:, :, np.newaxis], 3, axis=2)584    return noise * scale585 586 587def add_poisson_noise(img, scale=1.0, clip=True, rounds=False, gray_noise=False):588    """Add poisson noise.589 590    Args:591        img (Numpy array): Input image, shape (h, w, c), range [0, 1], float32.592        scale (float): Noise scale. Default: 1.0.593        gray_noise (bool): Whether generate gray noise. Default: False.594 595    Returns:596        (Numpy array): Returned noisy image, shape (h, w, c), range[0, 1],597            float32.598    """599    noise = generate_poisson_noise(img, scale, gray_noise)600    out = img + noise601    if clip and rounds:602        out = np.clip((out * 255.0).round(), 0, 255) / 255.603    elif clip:604        out = np.clip(out, 0, 1)605    elif rounds:606        out = (out * 255.0).round() / 255.607    return out608 609 610def generate_poisson_noise_pt(img, scale=1.0, gray_noise=0):611    """Generate a batch of poisson noise (PyTorch version)612 613    Args:614        img (Tensor): Input image, shape (b, c, h, w), range [0, 1], float32.615        scale (float | Tensor): Noise scale. Number or Tensor with shape (b).616            Default: 1.0.617        gray_noise (float | Tensor): 0-1 number or Tensor with shape (b).618            0 for False, 1 for True. Default: 0.619 620    Returns:621        (Tensor): Returned noisy image, shape (b, c, h, w), range[0, 1],622            float32.623    """624    b, _, h, w = img.size()625    if isinstance(gray_noise, (float, int)):626        cal_gray_noise = gray_noise > 0627    else:628        gray_noise = gray_noise.view(b, 1, 1, 1)629        cal_gray_noise = torch.sum(gray_noise) > 0630    if cal_gray_noise:631        img_gray = rgb_to_grayscale(img, num_output_channels=1)632        # round and clip image for counting vals correctly633        img_gray = torch.clamp((img_gray * 255.0).round(), 0, 255) / 255.634        # use for-loop to get the unique values for each sample635        vals_list = [len(torch.unique(img_gray[i, :, :, :])) for i in range(b)]636        vals_list = [2**np.ceil(np.log2(vals)) for vals in vals_list]637        vals = img_gray.new_tensor(vals_list).view(b, 1, 1, 1)638        out = torch.poisson(img_gray * vals) / vals639        noise_gray = out - img_gray640        noise_gray = noise_gray.expand(b, 3, h, w)641 642    # always calculate color noise643    # round and clip image for counting vals correctly644    img = torch.clamp((img * 255.0).round(), 0, 255) / 255.645    # use for-loop to get the unique values for each sample646    vals_list = [len(torch.unique(img[i, :, :, :])) for i in range(b)]647    vals_list = [2**np.ceil(np.log2(vals)) for vals in vals_list]648    vals = img.new_tensor(vals_list).view(b, 1, 1, 1)649    out = torch.poisson(img * vals) / vals650    noise = out - img651    if cal_gray_noise:652        noise = noise * (1 - gray_noise) + noise_gray * gray_noise653    if not isinstance(scale, (float, int)):654        scale = scale.view(b, 1, 1, 1)655    return noise * scale656 657 658def add_poisson_noise_pt(img, scale=1.0, clip=True, rounds=False, gray_noise=0):659    """Add poisson noise to a batch of images (PyTorch version).660 661    Args:662        img (Tensor): Input image, shape (b, c, h, w), range [0, 1], float32.663        scale (float | Tensor): Noise scale. Number or Tensor with shape (b).664            Default: 1.0.665        gray_noise (float | Tensor): 0-1 number or Tensor with shape (b).666            0 for False, 1 for True. Default: 0.667 668    Returns:669        (Tensor): Returned noisy image, shape (b, c, h, w), range[0, 1],670            float32.671    """672    noise = generate_poisson_noise_pt(img, scale, gray_noise)673    out = img + noise674    if clip and rounds:675        out = torch.clamp((out * 255.0).round(), 0, 255) / 255.676    elif clip:677        out = torch.clamp(out, 0, 1)678    elif rounds:679        out = (out * 255.0).round() / 255.680    return out681 682 683# ----------------------- Random Poisson (Shot) Noise ----------------------- #684 685 686def random_generate_poisson_noise(img, scale_range=(0, 1.0), gray_prob=0):687    scale = np.random.uniform(scale_range[0], scale_range[1])688    if np.random.uniform() < gray_prob:689        gray_noise = True690    else:691        gray_noise = False692    return generate_poisson_noise(img, scale, gray_noise)693 694 695def random_add_poisson_noise(img, scale_range=(0, 1.0), gray_prob=0, clip=True, rounds=False):696    noise = random_generate_poisson_noise(img, scale_range, gray_prob)697    out = img + noise698    if clip and rounds:699        out = np.clip((out * 255.0).round(), 0, 255) / 255.700    elif clip:701        out = np.clip(out, 0, 1)702    elif rounds:703        out = (out * 255.0).round() / 255.704    return out705 706 707def random_generate_poisson_noise_pt(img, scale_range=(0, 1.0), gray_prob=0):708    scale = torch.rand(709        img.size(0), dtype=img.dtype, device=img.device) * (scale_range[1] - scale_range[0]) + scale_range[0]710    gray_noise = torch.rand(img.size(0), dtype=img.dtype, device=img.device)711    gray_noise = (gray_noise < gray_prob).float()712    return generate_poisson_noise_pt(img, scale, gray_noise)713 714 715def random_add_poisson_noise_pt(img, scale_range=(0, 1.0), gray_prob=0, clip=True, rounds=False):716    noise = random_generate_poisson_noise_pt(img, scale_range, gray_prob)717    out = img + noise718    if clip and rounds:719        out = torch.clamp((out * 255.0).round(), 0, 255) / 255.720    elif clip:721        out = torch.clamp(out, 0, 1)722    elif rounds:723        out = (out * 255.0).round() / 255.724    return out725 726 727# ------------------------------------------------------------------------ #728# --------------------------- JPEG compression --------------------------- #729# ------------------------------------------------------------------------ #730 731 732def add_jpg_compression(img, quality=90):733    """Add JPG compression artifacts.734 735    Args:736        img (Numpy array): Input image, shape (h, w, c), range [0, 1], float32.737        quality (float): JPG compression quality. 0 for lowest quality, 100 for738            best quality. Default: 90.739 740    Returns:741        (Numpy array): Returned image after JPG, shape (h, w, c), range[0, 1],742            float32.743    """744    img = np.clip(img, 0, 1)745    encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), quality]746    _, encimg = cv2.imencode('.jpg', img * 255., encode_param)747    img = np.float32(cv2.imdecode(encimg, 1)) / 255.748    return img749 750 751def random_add_jpg_compression(img, quality_range=(90, 100)):752    """Randomly add JPG compression artifacts.753 754    Args:755        img (Numpy array): Input image, shape (h, w, c), range [0, 1], float32.756        quality_range (tuple[float] | list[float]): JPG compression quality757            range. 0 for lowest quality, 100 for best quality.758            Default: (90, 100).759 760    Returns:761        (Numpy array): Returned image after JPG, shape (h, w, c), range[0, 1],762            float32.763    """764    quality = np.random.uniform(quality_range[0], quality_range[1])765    return add_jpg_compression(img, int(quality))766