File size: 7,189 Bytes
1e3b872
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
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