from typing import Literal, Any import numpy as np import torch import torchvision.transforms as t from PIL import Image as ImageF from PIL.Image import Image as ImageB from torch import Tensor, dtype def tensor_to_image(self): return t.ToPILImage()(self.permute(2, 0, 1)) def image_to_tensor(self): return t.ToTensor()(self).permute(1, 2, 0) def create_rgba_image(width: int, height: int, color=(0, 0, 0, 0)) -> ImageB: return ImageF.new("RGBA", (width, height), color) def get_sampler_by_name(method) -> Literal[0, 1, 2, 3, 4, 5]: if method == "lanczos": return ImageF.LANCZOS elif method == "bicubic": return ImageF.BICUBIC elif method == "hamming": return ImageF.HAMMING elif method == "bilinear": return ImageF.BILINEAR elif method == "box": return ImageF.BOX elif method == "nearest": return ImageF.NEAREST else: raise ValueError("Sampler not found.") def cv2_layer(tensor: Tensor, function) -> Tensor: """ This function applies a given function to each channel of an input tensor and returns the result as a PyTorch tensor. :param tensor: A PyTorch tensor of shape (H, W, C) or (N, H, W, C), where C is the number of channels, H is the height, and W is the width of the image. :param function: A function that takes a numpy array of shape (H, W, C) as input and returns a numpy array of the same shape. :return: A PyTorch tensor of the same shape as the input tensor, where the given function has been applied to each channel of each image in the tensor. """ shape_size = tensor.shape.__len__() def produce(image): channels = image[0, 0, :].shape[0] rgb = image[:, :, 0:3].numpy() result_rgb = function(rgb) if channels <= 3: return torch.from_numpy(result_rgb) elif channels == 4: alpha = image[:, :, 3:4].numpy() result_alpha = function(alpha)[..., np.newaxis] result_rgba = np.concatenate((result_rgb, result_alpha), axis=2) return torch.from_numpy(result_rgba) if shape_size == 3: return torch.from_numpy(produce(tensor)) elif shape_size == 4: return torch.stack([ produce(tensor[i]) for i in range(len(tensor)) ]) else: raise ValueError("Incompatible tensor dimension.") def radialspace_1D( size: int, curvy: float = 1.0, scale: float = 1.0, min_val: float = 0.0, max_val: float = 1.0, normalize: bool = True, dtype: dtype = torch.float32 ): """ Create a 1D tensor with a radial gradient from 0 to 1 from the center to the edges. :param size: Tuple of int Size of the tensor. Can be a tuple of one or two integers. If one integer is given, the tensor will be square. If two integers are given, the tensor will have the given width and height. :param curvy: Float Curviness of the gradient. Higher values result in a sharper transition from 0 to 1. :param scale: Float Scale of the gradient. Higher values result in a larger area with values close to 1. :param min_val: Float Minimum value in the tensor. :param max_val: Float Maximum value in the tensor. :param normalize: Bool Whether to normalize the tensor values to be between min_val and max_val. :param dtype: Torch.dtype Data type of the resulting tensor. :return: Torch.Tensor A 2D tensor with a radial gradient from 0 to 1 from the center to the edges. """ tensor = radialspace_2D((1, size), curvy, scale, "square", min_val, max_val, (0.0, 0.5), None, normalize, dtype).squeeze() if min_val < 0: tensor[:len(tensor) // 2] = -(tensor[:len(tensor) // 2]) if normalize: tensor = ((tensor - tensor.min()) / (tensor.max() - tensor.min())) * (max_val - min_val) + min_val return tensor def radialspace_2D( size: tuple[int] | tuple[int, int], curvy: float = 1.0, scale: float = 1.0, mode: str = "square", min_val: float = 0.0, max_val: float = 1.0, center: tuple[float, float] = (0.5, 0.5), function: Any = None, normalize: bool = True, dtype: dtype = torch.float32 ) -> Tensor: """ Create a 2D tensor with a radial gradient from 0 to 1 from the center to the edges. :param size: Tuple of int Size of the tensor. Can be a tuple of one or two integers. If one integer is given, the tensor will be square. If two integers are given, the tensor will have the given width and height. :param curvy: Float Curviness of the gradient. Higher values result in a sharper transition from 0 to 1. :param scale: Float Scale of the gradient. Higher values result in a larger area with values close to 1. :param mode: Str Shape of the gradient. Can be "square", "circle", "rectangle", or "corners". :param min_val: Float Minimum value in the tensor. :param max_val: Float Maximum value in the tensor. :param center: Tuple of float Coordinates of the center of the gradient. :param function: Callable or None Custom function for computing the distance from the center. If given, this function will be used instead of the built-in modes. The function should take two arguments (xx and yy) and return a tensor of the same shape. :param normalize: Bool Whether to normalize the tensor values to be between min_val and max_val. :param dtype: Torch.dtype Data type of the resulting tensor. :return: Torch.Tensor A 2D tensor with a radial gradient from 0 to 1 from the center to the edges. """ if isinstance(size, tuple): if len(size) == 1: width = height = size[0] elif len(size) == 2: width, height = size else: raise ValueError("Invalid size argument") else: raise TypeError("Size must be a tuple") x = torch.linspace(0, 1, width) y = torch.linspace(0, 1, height) xx, yy = torch.meshgrid(x, y, indexing='ij') if function is not None: d = function(xx, yy) elif mode == "square": xx = (torch.abs(xx - center[0]) ** curvy) yy = (torch.abs(yy - center[1]) ** curvy) d = torch.max(xx, yy) elif mode == "circle": d = torch.sqrt((xx - center[0]) ** 2 + (yy - center[1]) ** 2) d = (d ** curvy) elif mode == "rectangle": xx = (torch.abs(xx - center[0]) ** curvy) yy = (torch.abs(yy - center[1]) ** curvy) d = xx + yy elif mode == "corners": xx = (torch.abs(xx - center[0]) ** curvy) yy = (torch.abs(yy - center[1]) ** curvy) d = torch.min(xx, yy) else: raise ValueError("Not supported mode.") if normalize: d = d / d.max() * scale d = torch.clamp(d, min_val, max_val) return d.to(dtype) Tensor.tensor_to_image = tensor_to_image ImageB.image_to_tensor = image_to_tensor