dfttest2 10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dfttest2/__init__.py +17 -0
- dfttest2/_dfttest2.py +851 -0
- dfttest2/py.typed +0 -0
- dfttest2-10.dist-info/METADATA +103 -0
- dfttest2-10.dist-info/RECORD +7 -0
- dfttest2-10.dist-info/WHEEL +4 -0
- dfttest2-10.dist-info/licenses/vs-dfttest2/LICENSE +674 -0
dfttest2/_dfttest2.py
ADDED
|
@@ -0,0 +1,851 @@
|
|
|
1
|
+
__version__ = "0.4.0"
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import math
|
|
5
|
+
from string import Template
|
|
6
|
+
import typing
|
|
7
|
+
|
|
8
|
+
import vapoursynth as vs
|
|
9
|
+
from vapoursynth import core
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
__all__ = ["DFTTest", "DFTTest2", "Backend"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Backend:
|
|
16
|
+
@dataclass(frozen=False)
|
|
17
|
+
class cuFFT:
|
|
18
|
+
device_id: int = 0
|
|
19
|
+
in_place: bool = True
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=False)
|
|
22
|
+
class NVRTC:
|
|
23
|
+
device_id: int = 0
|
|
24
|
+
num_streams: int = 1
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=False)
|
|
27
|
+
class CPU:
|
|
28
|
+
opt: int = 0
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=False)
|
|
31
|
+
class GCC:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=False)
|
|
35
|
+
class hipFFT:
|
|
36
|
+
device_id: int = 0
|
|
37
|
+
in_place: bool = True
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=False)
|
|
40
|
+
class HIPRTC:
|
|
41
|
+
device_id: int = 0
|
|
42
|
+
num_streams: int = 1
|
|
43
|
+
|
|
44
|
+
backendT = typing.Union[Backend.cuFFT, Backend.NVRTC, Backend.CPU, Backend.GCC, Backend.hipFFT, Backend.HIPRTC]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def init_backend(backend: backendT) -> backendT:
|
|
48
|
+
if backend is Backend.cuFFT: # type: ignore
|
|
49
|
+
backend = Backend.cuFFT()
|
|
50
|
+
elif backend is Backend.NVRTC: # type: ignore
|
|
51
|
+
backend = Backend.NVRTC()
|
|
52
|
+
elif backend is Backend.CPU: # type: ignore
|
|
53
|
+
backend = Backend.CPU()
|
|
54
|
+
elif backend is Backend.GCC: # type: ignore
|
|
55
|
+
backend = Backend.GCC()
|
|
56
|
+
elif backend is Backend.hipFFT: # type: ignore
|
|
57
|
+
backend = Backend.hipFFT()
|
|
58
|
+
elif backend is Backend.HIPRTC: # type: ignore
|
|
59
|
+
backend = Backend.HIPRTC()
|
|
60
|
+
return backend
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# https://github.com/HomeOfVapourSynthEvolution/VapourSynth-DFTTest/blob/
|
|
64
|
+
# bc5e0186a7f309556f20a8e9502f2238e39179b8/DFTTest/DFTTest.cpp#L518
|
|
65
|
+
def normalize(
|
|
66
|
+
window: typing.Sequence[float],
|
|
67
|
+
size: int,
|
|
68
|
+
step: int
|
|
69
|
+
) -> typing.List[float]:
|
|
70
|
+
|
|
71
|
+
nw = [0.0] * size
|
|
72
|
+
for q in range(size):
|
|
73
|
+
for h in range(q, -1, -step):
|
|
74
|
+
nw[q] += window[h] ** 2
|
|
75
|
+
for h in range(q + step, size, step):
|
|
76
|
+
nw[q] += window[h] ** 2
|
|
77
|
+
return [window[q] / math.sqrt(nw[q]) for q in range(size)]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# https://github.com/HomeOfVapourSynthEvolution/VapourSynth-DFTTest/blob/
|
|
81
|
+
# bc5e0186a7f309556f20a8e9502f2238e39179b8/DFTTest/DFTTest.cpp#L462
|
|
82
|
+
def get_window_value(location: float, size: int, mode: int, beta: float) -> float:
|
|
83
|
+
temp = math.pi * location / size
|
|
84
|
+
if mode == 0: # hanning
|
|
85
|
+
return 0.5 * (1 - math.cos(2 * temp))
|
|
86
|
+
elif mode == 1: # hamming
|
|
87
|
+
return 0.53836 - 0.46164 * math.cos(2 * temp)
|
|
88
|
+
elif mode == 2: # blackman
|
|
89
|
+
return 0.42 - 0.5 * math.cos(2 * temp) + 0.08 * math.cos(4 * temp)
|
|
90
|
+
elif mode == 3: # 4 term blackman-harris
|
|
91
|
+
return (
|
|
92
|
+
0.35875
|
|
93
|
+
- 0.48829 * math.cos(2 * temp)
|
|
94
|
+
+ 0.14128 * math.cos(4 * temp)
|
|
95
|
+
- 0.01168 * math.cos(6 * temp)
|
|
96
|
+
)
|
|
97
|
+
elif mode == 4: # kaiser-bessel
|
|
98
|
+
def i0(p: float) -> float:
|
|
99
|
+
p /= 2
|
|
100
|
+
n = t = d = 1.0
|
|
101
|
+
k = 1
|
|
102
|
+
while True:
|
|
103
|
+
n *= p
|
|
104
|
+
d *= k
|
|
105
|
+
v = n / d
|
|
106
|
+
t += v * v
|
|
107
|
+
k += 1
|
|
108
|
+
if k >= 15 or v <= 1e-8:
|
|
109
|
+
break
|
|
110
|
+
return t
|
|
111
|
+
v = 2 * location / size - 1
|
|
112
|
+
return i0(math.pi * beta * math.sqrt(1 - v * v)) / i0(math.pi * beta)
|
|
113
|
+
elif mode == 5: # 7 term blackman-harris
|
|
114
|
+
return (
|
|
115
|
+
0.27105140069342415
|
|
116
|
+
- 0.433297939234486060 * math.cos(2 * temp)
|
|
117
|
+
+ 0.218122999543110620 * math.cos(4 * temp)
|
|
118
|
+
- 0.065925446388030898 * math.cos(6 * temp)
|
|
119
|
+
+ 0.010811742098372268 * math.cos(8 * temp)
|
|
120
|
+
- 7.7658482522509342e-4 * math.cos(10 * temp)
|
|
121
|
+
+ 1.3887217350903198e-5 * math.cos(12 * temp)
|
|
122
|
+
)
|
|
123
|
+
elif mode == 6: # flat top
|
|
124
|
+
return (
|
|
125
|
+
0.2810639
|
|
126
|
+
- 0.5208972 * math.cos(2 * temp)
|
|
127
|
+
+ 0.1980399 * math.cos(4 * temp)
|
|
128
|
+
)
|
|
129
|
+
elif mode == 7: # rectangular
|
|
130
|
+
return 1.0
|
|
131
|
+
elif mode == 8: # Bartlett
|
|
132
|
+
return 1 - 2 * abs(location - size / 2) / size
|
|
133
|
+
elif mode == 9: # bartlett-hann
|
|
134
|
+
return 0.62 - 0.48 * (location / size - 0.5) - 0.38 * math.cos(2 * temp)
|
|
135
|
+
elif mode == 10: # nuttall
|
|
136
|
+
return (
|
|
137
|
+
0.355768
|
|
138
|
+
- 0.487396 * math.cos(2 * temp)
|
|
139
|
+
+ 0.144232 * math.cos(4 * temp)
|
|
140
|
+
- 0.012604 * math.cos(6 * temp)
|
|
141
|
+
)
|
|
142
|
+
elif mode == 11: # blackman-nuttall
|
|
143
|
+
return (
|
|
144
|
+
0.3635819
|
|
145
|
+
- 0.4891775 * math.cos(2 * temp)
|
|
146
|
+
+ 0.1365995 * math.cos(4 * temp)
|
|
147
|
+
- 0.0106411 * math.cos(6 * temp)
|
|
148
|
+
)
|
|
149
|
+
else:
|
|
150
|
+
raise ValueError("unknown window")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# https://github.com/HomeOfVapourSynthEvolution/VapourSynth-DFTTest/blob/
|
|
154
|
+
# bc5e0186a7f309556f20a8e9502f2238e39179b8/DFTTest/DFTTest.cpp#L461
|
|
155
|
+
def get_window(
|
|
156
|
+
radius: int,
|
|
157
|
+
block_size: int,
|
|
158
|
+
block_step: int,
|
|
159
|
+
spatial_window_mode: int,
|
|
160
|
+
spatial_beta: float,
|
|
161
|
+
temporal_window_mode: int,
|
|
162
|
+
temporal_beta: float
|
|
163
|
+
) -> typing.List[float]:
|
|
164
|
+
|
|
165
|
+
temporal_window = [
|
|
166
|
+
get_window_value(
|
|
167
|
+
location = i + 0.5,
|
|
168
|
+
size = 2 * radius + 1,
|
|
169
|
+
mode = temporal_window_mode,
|
|
170
|
+
beta = temporal_beta
|
|
171
|
+
) for i in range(2 * radius + 1)
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
spatial_window = [
|
|
175
|
+
get_window_value(
|
|
176
|
+
location = i + 0.5,
|
|
177
|
+
size = block_size,
|
|
178
|
+
mode = spatial_window_mode,
|
|
179
|
+
beta = spatial_beta
|
|
180
|
+
) for i in range(block_size)
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
spatial_window = normalize(
|
|
184
|
+
window=spatial_window,
|
|
185
|
+
size=block_size,
|
|
186
|
+
step=block_step
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
window = []
|
|
190
|
+
for t_val in temporal_window:
|
|
191
|
+
for s_val1 in spatial_window:
|
|
192
|
+
for s_val2 in spatial_window:
|
|
193
|
+
value = t_val * s_val1 * s_val2
|
|
194
|
+
|
|
195
|
+
# normalize for unnormalized FFT implementation
|
|
196
|
+
value /= math.sqrt(2 * radius + 1) * block_size
|
|
197
|
+
|
|
198
|
+
window.append(value)
|
|
199
|
+
|
|
200
|
+
return window
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# https://github.com/HomeOfVapourSynthEvolution/VapourSynth-DFTTest/blob/
|
|
204
|
+
# bc5e0186a7f309556f20a8e9502f2238e39179b8/DFTTest/DFTTest.cpp#L581
|
|
205
|
+
def get_location(
|
|
206
|
+
position: float,
|
|
207
|
+
length: int
|
|
208
|
+
) -> float:
|
|
209
|
+
|
|
210
|
+
if length == 1:
|
|
211
|
+
return 0.0
|
|
212
|
+
elif position > length // 2:
|
|
213
|
+
return (length - position) / (length // 2)
|
|
214
|
+
else:
|
|
215
|
+
return position / (length // 2)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# https://github.com/HomeOfVapourSynthEvolution/VapourSynth-DFTTest/blob/
|
|
219
|
+
# bc5e0186a7f309556f20a8e9502f2238e39179b8/DFTTest/DFTTest.cpp#L581
|
|
220
|
+
def get_sigma(
|
|
221
|
+
position: float,
|
|
222
|
+
length: int,
|
|
223
|
+
func: typing.Callable[[float], float]
|
|
224
|
+
) -> float:
|
|
225
|
+
|
|
226
|
+
if length == 1:
|
|
227
|
+
return 1.0
|
|
228
|
+
else:
|
|
229
|
+
return func(get_location(position, length))
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def DFTTest2(
|
|
233
|
+
clip: vs.VideoNode,
|
|
234
|
+
ftype: typing.Literal[0, 1, 2, 3, 4] = 0,
|
|
235
|
+
sigma: typing.Union[float, typing.Sequence[typing.Callable[[float], float]]] = 8.0,
|
|
236
|
+
sigma2: float = 8.0,
|
|
237
|
+
pmin: float = 0.0,
|
|
238
|
+
pmax: float = 500.0,
|
|
239
|
+
sbsize: int = 16,
|
|
240
|
+
sosize: int = 12,
|
|
241
|
+
tbsize: int = 3,
|
|
242
|
+
swin: typing.Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] = 0,
|
|
243
|
+
twin: typing.Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] = 7,
|
|
244
|
+
sbeta: float = 2.5,
|
|
245
|
+
tbeta: float = 2.5,
|
|
246
|
+
zmean: bool = True,
|
|
247
|
+
f0beta: float = 1.0,
|
|
248
|
+
ssystem: typing.Literal[0, 1] = 0,
|
|
249
|
+
planes: typing.Optional[typing.Union[int, typing.Sequence[int]]] = None,
|
|
250
|
+
backend: backendT = Backend.cuFFT()
|
|
251
|
+
) -> vs.VideoNode:
|
|
252
|
+
""" this interface is not stable """
|
|
253
|
+
|
|
254
|
+
# translate parameters
|
|
255
|
+
if ftype == 0:
|
|
256
|
+
if abs(f0beta - 1) < 0.00005:
|
|
257
|
+
filter_type = 0
|
|
258
|
+
elif abs(f0beta - 0.5) < 0.0005:
|
|
259
|
+
filter_type = 6
|
|
260
|
+
else:
|
|
261
|
+
filter_type = 5
|
|
262
|
+
else:
|
|
263
|
+
filter_type = ftype
|
|
264
|
+
|
|
265
|
+
radius = (tbsize - 1) // 2
|
|
266
|
+
block_size = sbsize
|
|
267
|
+
block_step = sbsize - sosize
|
|
268
|
+
spatial_window_mode = swin
|
|
269
|
+
temporal_window_mode = twin
|
|
270
|
+
spatial_beta = sbeta
|
|
271
|
+
temporal_beta = tbeta
|
|
272
|
+
zero_mean = zmean
|
|
273
|
+
backend = init_backend(backend)
|
|
274
|
+
|
|
275
|
+
if isinstance(backend, (Backend.CPU, Backend.NVRTC, Backend.GCC, Backend.HIPRTC)):
|
|
276
|
+
if radius not in range(4):
|
|
277
|
+
raise ValueError("invalid radius (tbsize)")
|
|
278
|
+
if block_size != 16:
|
|
279
|
+
raise ValueError("invalid block_size (sbsize)")
|
|
280
|
+
|
|
281
|
+
# compute constants
|
|
282
|
+
try:
|
|
283
|
+
sigma_scalar = float(sigma) # type: ignore
|
|
284
|
+
sigma_is_scalar = True
|
|
285
|
+
except:
|
|
286
|
+
# compute sigma_array
|
|
287
|
+
|
|
288
|
+
sigma_is_scalar = False
|
|
289
|
+
|
|
290
|
+
sigma_funcs = typing.cast(typing.Sequence[typing.Callable[[float], float]], sigma)
|
|
291
|
+
if callable(sigma_funcs):
|
|
292
|
+
sigma_funcs = [sigma_funcs]
|
|
293
|
+
else:
|
|
294
|
+
sigma_funcs = list(sigma_funcs)
|
|
295
|
+
sigma_funcs.extend([sigma_funcs[-1]] * 3)
|
|
296
|
+
sigma_func_x, sigma_func_y, sigma_func_t = sigma_funcs[:3]
|
|
297
|
+
|
|
298
|
+
sigma_array = []
|
|
299
|
+
|
|
300
|
+
if ssystem == 0:
|
|
301
|
+
for t in range(2 * radius + 1):
|
|
302
|
+
sigma_t = get_sigma(position=t, length=2*radius+1, func=sigma_func_t)
|
|
303
|
+
for y in range(block_size):
|
|
304
|
+
sigma_y = get_sigma(position=y, length=block_size, func=sigma_func_y)
|
|
305
|
+
for x in range(block_size // 2 + 1):
|
|
306
|
+
sigma_x = get_sigma(position=x, length=block_size, func=sigma_func_x)
|
|
307
|
+
|
|
308
|
+
sigma = sigma_t * sigma_y * sigma_x
|
|
309
|
+
sigma_array.append(sigma)
|
|
310
|
+
else:
|
|
311
|
+
for t in range(2 * radius + 1):
|
|
312
|
+
loc_t = get_location(position=t, length=2*radius+1)
|
|
313
|
+
for y in range(block_size):
|
|
314
|
+
loc_y = get_location(position=y, length=block_size)
|
|
315
|
+
for x in range(block_size // 2 + 1):
|
|
316
|
+
loc_x = get_location(position=x, length=block_size)
|
|
317
|
+
|
|
318
|
+
ndim = 3 if radius > 0 else 2
|
|
319
|
+
location = math.sqrt((loc_t * loc_t + loc_y * loc_y + loc_x * loc_x) / ndim)
|
|
320
|
+
sigma = sigma_func_t(location)
|
|
321
|
+
sigma_array.append(sigma)
|
|
322
|
+
|
|
323
|
+
window = get_window(
|
|
324
|
+
radius=radius,
|
|
325
|
+
block_size=block_size,
|
|
326
|
+
block_step=block_step,
|
|
327
|
+
spatial_window_mode=spatial_window_mode,
|
|
328
|
+
temporal_window_mode=temporal_window_mode,
|
|
329
|
+
spatial_beta=spatial_beta,
|
|
330
|
+
temporal_beta=temporal_beta
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
wscale = math.fsum(w * w for w in window)
|
|
334
|
+
|
|
335
|
+
if ftype < 2:
|
|
336
|
+
if sigma_is_scalar:
|
|
337
|
+
sigma_scalar *= wscale
|
|
338
|
+
else:
|
|
339
|
+
sigma_array = [s * wscale for s in sigma_array]
|
|
340
|
+
sigma2 *= wscale
|
|
341
|
+
|
|
342
|
+
pmin *= wscale
|
|
343
|
+
pmax *= wscale
|
|
344
|
+
|
|
345
|
+
if isinstance(backend, Backend.cuFFT):
|
|
346
|
+
rdft = core.dfttest2_cuda.RDFT
|
|
347
|
+
elif isinstance(backend, Backend.NVRTC):
|
|
348
|
+
rdft = core.dfttest2_nvrtc.RDFT
|
|
349
|
+
elif isinstance(backend, Backend.CPU):
|
|
350
|
+
rdft = core.dfttest2_cpu.RDFT
|
|
351
|
+
elif isinstance(backend, Backend.GCC):
|
|
352
|
+
rdft = core.dfttest2_gcc.RDFT
|
|
353
|
+
elif isinstance(backend, Backend.hipFFT):
|
|
354
|
+
rdft = core.dfttest2_hip.RDFT
|
|
355
|
+
elif isinstance(backend, Backend.HIPRTC):
|
|
356
|
+
rdft = core.dfttest2_hiprtc.RDFT
|
|
357
|
+
else:
|
|
358
|
+
raise TypeError("unknown backend")
|
|
359
|
+
|
|
360
|
+
if radius == 0:
|
|
361
|
+
window_freq = rdft(
|
|
362
|
+
data=[w * 255 for w in window],
|
|
363
|
+
shape=(block_size, block_size)
|
|
364
|
+
)
|
|
365
|
+
else:
|
|
366
|
+
window_freq = rdft(
|
|
367
|
+
data=[w * 255 for w in window],
|
|
368
|
+
shape=(2 * radius + 1, block_size, block_size)
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
if isinstance(backend, Backend.CPU):
|
|
372
|
+
return core.dfttest2_cpu.DFTTest(
|
|
373
|
+
clip,
|
|
374
|
+
window=window,
|
|
375
|
+
sigma=[sigma_scalar] * (2 * radius + 1) * block_size * (block_size // 2 + 1) if sigma_is_scalar else sigma_array,
|
|
376
|
+
sigma2=sigma2,
|
|
377
|
+
pmin=pmin,
|
|
378
|
+
pmax=pmax,
|
|
379
|
+
radius=radius,
|
|
380
|
+
block_size=block_size,
|
|
381
|
+
block_step=block_step,
|
|
382
|
+
planes=planes,
|
|
383
|
+
filter_type=filter_type,
|
|
384
|
+
window_freq=window_freq,
|
|
385
|
+
opt=backend.opt
|
|
386
|
+
)
|
|
387
|
+
elif isinstance(backend, Backend.GCC):
|
|
388
|
+
return core.dfttest2_gcc.DFTTest(
|
|
389
|
+
clip,
|
|
390
|
+
window=window,
|
|
391
|
+
sigma=[sigma_scalar] * (2 * radius + 1) * block_size * (block_size // 2 + 1) if sigma_is_scalar else sigma_array,
|
|
392
|
+
sigma2=sigma2,
|
|
393
|
+
pmin=pmin,
|
|
394
|
+
pmax=pmax,
|
|
395
|
+
radius=radius,
|
|
396
|
+
block_size=block_size,
|
|
397
|
+
block_step=block_step,
|
|
398
|
+
planes=planes,
|
|
399
|
+
filter_type=filter_type,
|
|
400
|
+
window_freq=window_freq
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
if isinstance(backend, Backend.cuFFT):
|
|
404
|
+
to_single = core.dfttest2_cuda.ToSingle
|
|
405
|
+
elif isinstance(backend, Backend.NVRTC):
|
|
406
|
+
to_single = core.dfttest2_nvrtc.ToSingle
|
|
407
|
+
elif isinstance(backend, Backend.hipFFT):
|
|
408
|
+
to_single = core.dfttest2_hip.ToSingle
|
|
409
|
+
elif isinstance(backend, Backend.HIPRTC):
|
|
410
|
+
to_single = core.dfttest2_hiprtc.ToSingle
|
|
411
|
+
else:
|
|
412
|
+
raise TypeError("unknown backend")
|
|
413
|
+
|
|
414
|
+
kernel = Template(
|
|
415
|
+
"""
|
|
416
|
+
#define FILTER_TYPE ${filter_type}
|
|
417
|
+
#define ZERO_MEAN ${zero_mean}
|
|
418
|
+
#define SIGMA_IS_SCALAR ${sigma_is_scalar}
|
|
419
|
+
|
|
420
|
+
#if ZERO_MEAN
|
|
421
|
+
__device__ static const float window_freq[] { ${window_freq} };
|
|
422
|
+
#endif // ZERO_MEAN
|
|
423
|
+
|
|
424
|
+
__device__ static const float window[] { ${window} };
|
|
425
|
+
|
|
426
|
+
__device__
|
|
427
|
+
static void filter(float2 & value, int x, int y, int t) {
|
|
428
|
+
#if SIGMA_IS_SCALAR
|
|
429
|
+
float sigma = static_cast<float>(${sigma});
|
|
430
|
+
#else // SIGMA_IS_SCALAR
|
|
431
|
+
__device__ static const float sigma_array[] { ${sigma} };
|
|
432
|
+
float sigma = sigma_array[(t * BLOCK_SIZE + y) * (BLOCK_SIZE / 2 + 1) + x];
|
|
433
|
+
#endif // SIGMA_IS_SCALAR
|
|
434
|
+
[[maybe_unused]] float sigma2 = static_cast<float>(${sigma2});
|
|
435
|
+
[[maybe_unused]] float pmin = static_cast<float>(${pmin});
|
|
436
|
+
[[maybe_unused]] float pmax = static_cast<float>(${pmax});
|
|
437
|
+
[[maybe_unused]] float multiplier {};
|
|
438
|
+
|
|
439
|
+
#if FILTER_TYPE == 2
|
|
440
|
+
value.x *= sigma;
|
|
441
|
+
value.y *= sigma;
|
|
442
|
+
return ;
|
|
443
|
+
#endif
|
|
444
|
+
|
|
445
|
+
float psd = value.x * value.x + value.y * value.y;
|
|
446
|
+
|
|
447
|
+
#if FILTER_TYPE == 1
|
|
448
|
+
if (psd < sigma) {
|
|
449
|
+
value.x = 0.0f;
|
|
450
|
+
value.y = 0.0f;
|
|
451
|
+
}
|
|
452
|
+
return ;
|
|
453
|
+
#elif FILTER_TYPE == 0
|
|
454
|
+
multiplier = fmaxf((psd - sigma) / (psd + 1e-15f), 0.0f);
|
|
455
|
+
#elif FILTER_TYPE == 3
|
|
456
|
+
if (psd >= pmin && psd <= pmax) {
|
|
457
|
+
multiplier = sigma;
|
|
458
|
+
} else {
|
|
459
|
+
multiplier = sigma2;
|
|
460
|
+
}
|
|
461
|
+
#elif FILTER_TYPE == 4
|
|
462
|
+
multiplier = sigma * sqrtf(psd * (pmax / ((psd + pmin) * (psd + pmax) + 1e-15f)));
|
|
463
|
+
#elif FILTER_TYPE == 5
|
|
464
|
+
multiplier = powf(fmaxf((psd - sigma) / (psd + 1e-15f), 0.0f), pmin);
|
|
465
|
+
#else
|
|
466
|
+
multiplier = sqrtf(fmaxf((psd - sigma) / (psd + 1e-15f), 0.0f));
|
|
467
|
+
#endif
|
|
468
|
+
|
|
469
|
+
value.x *= multiplier;
|
|
470
|
+
value.y *= multiplier;
|
|
471
|
+
}
|
|
472
|
+
"""
|
|
473
|
+
).substitute(
|
|
474
|
+
sigma_is_scalar=int(sigma_is_scalar),
|
|
475
|
+
sigma=(
|
|
476
|
+
to_single(sigma_scalar)
|
|
477
|
+
if sigma_is_scalar
|
|
478
|
+
else ','.join(str(to_single(x)) for x in sigma_array)
|
|
479
|
+
),
|
|
480
|
+
sigma2=to_single(sigma2),
|
|
481
|
+
pmin=to_single(pmin),
|
|
482
|
+
pmax=to_single(pmax),
|
|
483
|
+
filter_type=int(filter_type),
|
|
484
|
+
window_freq=','.join(str(to_single(x)) for x in window_freq),
|
|
485
|
+
zero_mean=int(zero_mean),
|
|
486
|
+
window=','.join(str(to_single(x)) for x in window),
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
if isinstance(backend, Backend.cuFFT):
|
|
490
|
+
return core.dfttest2_cuda.DFTTest(
|
|
491
|
+
clip,
|
|
492
|
+
kernel=kernel,
|
|
493
|
+
radius=radius,
|
|
494
|
+
block_size=block_size,
|
|
495
|
+
block_step=block_step,
|
|
496
|
+
planes=planes,
|
|
497
|
+
in_place=backend.in_place,
|
|
498
|
+
device_id=backend.device_id
|
|
499
|
+
)
|
|
500
|
+
elif isinstance(backend, Backend.NVRTC):
|
|
501
|
+
return core.dfttest2_nvrtc.DFTTest(
|
|
502
|
+
clip,
|
|
503
|
+
kernel=kernel,
|
|
504
|
+
radius=radius,
|
|
505
|
+
block_size=block_size,
|
|
506
|
+
block_step=block_step,
|
|
507
|
+
planes=planes,
|
|
508
|
+
in_place=False,
|
|
509
|
+
device_id=backend.device_id,
|
|
510
|
+
num_streams=backend.num_streams
|
|
511
|
+
)
|
|
512
|
+
if isinstance(backend, Backend.hipFFT):
|
|
513
|
+
return core.dfttest2_hip.DFTTest(
|
|
514
|
+
clip,
|
|
515
|
+
kernel=kernel,
|
|
516
|
+
radius=radius,
|
|
517
|
+
block_size=block_size,
|
|
518
|
+
block_step=block_step,
|
|
519
|
+
planes=planes,
|
|
520
|
+
in_place=backend.in_place,
|
|
521
|
+
device_id=backend.device_id
|
|
522
|
+
)
|
|
523
|
+
elif isinstance(backend, Backend.HIPRTC):
|
|
524
|
+
return core.dfttest2_hiprtc.DFTTest(
|
|
525
|
+
clip,
|
|
526
|
+
kernel=kernel,
|
|
527
|
+
radius=radius,
|
|
528
|
+
block_size=block_size,
|
|
529
|
+
block_step=block_step,
|
|
530
|
+
planes=planes,
|
|
531
|
+
in_place=False,
|
|
532
|
+
device_id=backend.device_id,
|
|
533
|
+
num_streams=backend.num_streams
|
|
534
|
+
)
|
|
535
|
+
else:
|
|
536
|
+
raise TypeError("unknown backend")
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def select_backend(
|
|
540
|
+
backend: typing.Optional[backendT],
|
|
541
|
+
sbsize: int,
|
|
542
|
+
tbsize: int
|
|
543
|
+
) -> backendT:
|
|
544
|
+
|
|
545
|
+
if backend is not None:
|
|
546
|
+
return backend
|
|
547
|
+
|
|
548
|
+
if sbsize == 16 and tbsize in [1, 3, 5, 7]:
|
|
549
|
+
if hasattr(core, "dfttest2_nvrtc"):
|
|
550
|
+
return Backend.NVRTC()
|
|
551
|
+
elif hasattr(core, "dfttest2_hiprtc"):
|
|
552
|
+
return Backend.HIPRTC()
|
|
553
|
+
elif hasattr(core, "dfttest2_cuda"):
|
|
554
|
+
return Backend.cuFFT()
|
|
555
|
+
elif hasattr(core, "dfttest2_hip"):
|
|
556
|
+
return Backend.hipFFT()
|
|
557
|
+
elif hasattr(core, "dfttest2_cpu"):
|
|
558
|
+
return Backend.CPU()
|
|
559
|
+
else:
|
|
560
|
+
return Backend.GCC()
|
|
561
|
+
else:
|
|
562
|
+
if hasattr(core, "dfttest2_cuda"):
|
|
563
|
+
return Backend.cuFFT()
|
|
564
|
+
else:
|
|
565
|
+
return Backend.hipFFT()
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
FREQ = float
|
|
569
|
+
SIGMA = float
|
|
570
|
+
def flatten(
|
|
571
|
+
data: typing.Optional[typing.Union[
|
|
572
|
+
typing.Sequence[typing.Tuple[FREQ, SIGMA]],
|
|
573
|
+
typing.Sequence[float]
|
|
574
|
+
]]
|
|
575
|
+
) -> typing.Optional[typing.List[float]]:
|
|
576
|
+
|
|
577
|
+
import itertools as it
|
|
578
|
+
import numbers
|
|
579
|
+
|
|
580
|
+
if data is None:
|
|
581
|
+
return None
|
|
582
|
+
elif isinstance(data[0], numbers.Real):
|
|
583
|
+
return data
|
|
584
|
+
else:
|
|
585
|
+
data = typing.cast(typing.Sequence[typing.Tuple[FREQ, SIGMA]], data)
|
|
586
|
+
return list(it.chain.from_iterable(data))
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def to_func(
|
|
590
|
+
data: typing.Optional[typing.Sequence[float]],
|
|
591
|
+
norm: typing.Callable[[float], float],
|
|
592
|
+
sigma: float
|
|
593
|
+
) -> typing.Callable[[float], float]:
|
|
594
|
+
|
|
595
|
+
if data is None:
|
|
596
|
+
return lambda _: norm(sigma)
|
|
597
|
+
|
|
598
|
+
locations = data[::2]
|
|
599
|
+
sigmas = data[1::2]
|
|
600
|
+
packs = list(zip(locations, sigmas))
|
|
601
|
+
packs = sorted(packs, key=lambda group: group[0])
|
|
602
|
+
|
|
603
|
+
def func(x: float) -> float:
|
|
604
|
+
length = len(packs)
|
|
605
|
+
for i in range(length - 1):
|
|
606
|
+
if x <= packs[i + 1][0]:
|
|
607
|
+
weight = (x - packs[i][0]) / (packs[i + 1][0] - packs[i][0])
|
|
608
|
+
return (1 - weight) * norm(packs[i][1]) + weight * norm(packs[i + 1][1])
|
|
609
|
+
raise ValueError()
|
|
610
|
+
|
|
611
|
+
return func
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def DFTTest(
|
|
615
|
+
clip: vs.VideoNode,
|
|
616
|
+
ftype: typing.Literal[0, 1, 2, 3, 4] = 0,
|
|
617
|
+
sigma: float = 8.0,
|
|
618
|
+
sigma2: float = 8.0,
|
|
619
|
+
pmin: float = 0.0,
|
|
620
|
+
pmax: float = 500.0,
|
|
621
|
+
sbsize: int = 16,
|
|
622
|
+
smode: typing.Literal[0, 1] = 1,
|
|
623
|
+
sosize: int = 12,
|
|
624
|
+
tbsize: int = 3,
|
|
625
|
+
# tmode=0, tosize=0
|
|
626
|
+
swin: typing.Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] = 0,
|
|
627
|
+
twin: typing.Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] = 7,
|
|
628
|
+
sbeta: float = 2.5,
|
|
629
|
+
tbeta: float = 2.5,
|
|
630
|
+
zmean: bool = True,
|
|
631
|
+
f0beta: float = 1.0,
|
|
632
|
+
nlocation: typing.Optional[typing.Sequence[int]] = None,
|
|
633
|
+
alpha: typing.Optional[float] = None,
|
|
634
|
+
slocation: typing.Optional[typing.Union[
|
|
635
|
+
typing.Sequence[typing.Tuple[FREQ, SIGMA]],
|
|
636
|
+
typing.Sequence[float]
|
|
637
|
+
]] = None,
|
|
638
|
+
ssx: typing.Optional[typing.Union[
|
|
639
|
+
typing.Sequence[typing.Tuple[FREQ, SIGMA]],
|
|
640
|
+
typing.Sequence[float]
|
|
641
|
+
]] = None,
|
|
642
|
+
ssy: typing.Optional[typing.Union[
|
|
643
|
+
typing.Sequence[typing.Tuple[FREQ, SIGMA]],
|
|
644
|
+
typing.Sequence[float]
|
|
645
|
+
]] = None,
|
|
646
|
+
sst: typing.Optional[typing.Union[
|
|
647
|
+
typing.Sequence[typing.Tuple[FREQ, SIGMA]],
|
|
648
|
+
typing.Sequence[float]
|
|
649
|
+
]] = None,
|
|
650
|
+
ssystem: typing.Literal[0, 1] = 0,
|
|
651
|
+
planes: typing.Optional[typing.Union[int, typing.Sequence[int]]] = None,
|
|
652
|
+
backend: typing.Optional[backendT] = None
|
|
653
|
+
) -> vs.VideoNode:
|
|
654
|
+
""" 2D/3D frequency domain denoiser
|
|
655
|
+
|
|
656
|
+
The interface is compatible with core.dfttest.DFTTest by HolyWu.
|
|
657
|
+
|
|
658
|
+
Args:
|
|
659
|
+
clip: Clip to process.
|
|
660
|
+
|
|
661
|
+
Any format with either integer sample type of 8-16 bit depth
|
|
662
|
+
or float sample type of 32 bit depth is supported.
|
|
663
|
+
|
|
664
|
+
ftype: Controls the filter type.
|
|
665
|
+
|
|
666
|
+
Possible settings are:
|
|
667
|
+
0: generalized wiener filter
|
|
668
|
+
mult = max((psd - sigma) / psd, 0) ^ f0beta
|
|
669
|
+
|
|
670
|
+
1: hard threshold
|
|
671
|
+
mult = psd < sigma ? 0.0 : 1.0
|
|
672
|
+
|
|
673
|
+
2: multiplier
|
|
674
|
+
mult = sigma
|
|
675
|
+
|
|
676
|
+
3: multiplier switched based on psd value
|
|
677
|
+
mult = (psd >= pmin && psd <= pmax) ? sigma : sigma2
|
|
678
|
+
|
|
679
|
+
4: multiplier modified based on psd value and range
|
|
680
|
+
mult = sigma * sqrt((psd * pmax) / ((psd + pmin) * (psd + pmax)))
|
|
681
|
+
|
|
682
|
+
The real and imaginary parts of each complex dft coefficient are multiplied
|
|
683
|
+
by the corresponding 'mult' value.
|
|
684
|
+
|
|
685
|
+
** psd = magnitude squared = real*real + imag*imag
|
|
686
|
+
|
|
687
|
+
sigma, sigma2: Value of sigma and sigma2.
|
|
688
|
+
If using the slocation parameter then the sigma parameter is ignored.
|
|
689
|
+
|
|
690
|
+
pmin, pmax: Used as described in the ftype parameter description.
|
|
691
|
+
|
|
692
|
+
sbsize: Sets the length of the sides of the spatial window.
|
|
693
|
+
Must be 1 or greater. Must be odd if using smode=0.
|
|
694
|
+
|
|
695
|
+
smode: Sets the mode for spatial operation.
|
|
696
|
+
Currently only tmode=1 is implemented.
|
|
697
|
+
|
|
698
|
+
sosize: Sets the spatial overlap amount.
|
|
699
|
+
Must be in the range 0 to sbsize-1 (inclusive).
|
|
700
|
+
If sosize is greater than sbsize>>1, then sbsize%(sbsize-sosize) must equal 0.
|
|
701
|
+
In other words, overlap greater than 50% requires that sbsize-sosize be a divisor of sbsize.
|
|
702
|
+
|
|
703
|
+
tbsize: Sets the length of the temporal dimension (i.e. number of frames).
|
|
704
|
+
Must be at least 1. Must be odd if using tmode=0.
|
|
705
|
+
|
|
706
|
+
tmode: Sets the mode for temporal operation.
|
|
707
|
+
Currently only tmode=0 is implemented.
|
|
708
|
+
|
|
709
|
+
tosize: Sets the temporal overlap amount.
|
|
710
|
+
Must be in the range 0 to tbsize-1 (inclusive).
|
|
711
|
+
If tosize is greater than tbsize>>1, then tbsize%(tbsize-tosize) must equal 0.
|
|
712
|
+
In other words, overlap greater than 50% requires that tbsize-tosize be a divisor of tbsize.
|
|
713
|
+
|
|
714
|
+
swin, twin: Sets the type of analysis/synthesis window to be used for spatial (swin) and
|
|
715
|
+
temporal (twin) processing. Possible settings:
|
|
716
|
+
|
|
717
|
+
0: hanning
|
|
718
|
+
1: hamming
|
|
719
|
+
2: blackman
|
|
720
|
+
3: 4 term blackman-harris
|
|
721
|
+
4: kaiser-bessel
|
|
722
|
+
5: 7 term blackman-harris
|
|
723
|
+
6: flat top
|
|
724
|
+
7: rectangular
|
|
725
|
+
8: Bartlett
|
|
726
|
+
9: Bartlett-Hann
|
|
727
|
+
10: Nuttall
|
|
728
|
+
11: Blackman-Nuttall
|
|
729
|
+
|
|
730
|
+
sbeta,tbeta: Sets the beta value for kaiser-bessel window type.
|
|
731
|
+
sbeta goes with swin, tbeta goes with twin.
|
|
732
|
+
Not used unless the corresponding window value is set to 4.
|
|
733
|
+
|
|
734
|
+
zmean: Controls whether the window mean is subtracted out (zero'd)
|
|
735
|
+
prior to filtering in the frequency domain.
|
|
736
|
+
|
|
737
|
+
f0beta: Power term in ftype=0.
|
|
738
|
+
|
|
739
|
+
nlocation: Currently not implemented.
|
|
740
|
+
|
|
741
|
+
slocation/ssx/ssy/sst: Used to specify functions of sigma based on frequency.
|
|
742
|
+
Check the original documentation for details.
|
|
743
|
+
|
|
744
|
+
Note that in current implementation,
|
|
745
|
+
"slocation = [(0.0, 1.0), (1.0, 10.0)]"
|
|
746
|
+
is equivalent to
|
|
747
|
+
"slocation = [0.0, 1.0, 1.0, 10.0]"
|
|
748
|
+
|
|
749
|
+
ssystem: Method of sigma computation.
|
|
750
|
+
Check the original documentation for details.
|
|
751
|
+
|
|
752
|
+
planes: Sets which planes will be processed.
|
|
753
|
+
Any unprocessed planes will be simply copied.
|
|
754
|
+
|
|
755
|
+
backend: Backend implementation to use.
|
|
756
|
+
All available backends can be found in the dfttest2.Backend "namespace":
|
|
757
|
+
dfttest2.Backend.{CPU, cuFFT, NVRTC, GCC, hipFFT, HIPRTC}
|
|
758
|
+
|
|
759
|
+
The CPU, NVRTC and GCC backends require sbsize=16.
|
|
760
|
+
The cuFFT and NVRTC backends require a CUDA-enabled system.
|
|
761
|
+
The hipFFT and HIPRTC backends require a CUDA-enabled system.
|
|
762
|
+
|
|
763
|
+
Speed: NVRTC == HIPRTC >> cuFFT > hipFFT > CPU == GCC
|
|
764
|
+
"""
|
|
765
|
+
|
|
766
|
+
if (
|
|
767
|
+
not isinstance(clip, vs.VideoNode) or
|
|
768
|
+
clip.width == 0 or
|
|
769
|
+
clip.height == 0 or
|
|
770
|
+
clip.format is None or
|
|
771
|
+
(clip.format.sample_type == vs.INTEGER and clip.format.bits_per_sample > 16) or
|
|
772
|
+
(clip.format.sample_type == vs.FLOAT and clip.format.bits_per_sample != 32)
|
|
773
|
+
):
|
|
774
|
+
raise ValueError("only constant format 8-16 bit integer and 32 bit float input supported")
|
|
775
|
+
|
|
776
|
+
if ftype < 0 or ftype > 4:
|
|
777
|
+
raise ValueError("ftype must be 0, 1, 2, 3, or 4")
|
|
778
|
+
|
|
779
|
+
if sbsize < 1:
|
|
780
|
+
raise ValueError("sbsize must be greater than or equal to 1")
|
|
781
|
+
|
|
782
|
+
if smode != 1:
|
|
783
|
+
raise ValueError('"smode" must be 1')
|
|
784
|
+
|
|
785
|
+
if sosize > sbsize // 2 and (sbsize % (sbsize - sosize) != 0):
|
|
786
|
+
raise ValueError("spatial overlap greater than 50% requires that sbsize-sosize is a divisor of sbsize")
|
|
787
|
+
|
|
788
|
+
if tbsize < 1:
|
|
789
|
+
raise ValueError('"tbsize" must be at least 1')
|
|
790
|
+
|
|
791
|
+
if swin < 0 or swin > 11:
|
|
792
|
+
raise ValueError("swin must be between 0 and 11 (inclusive)")
|
|
793
|
+
|
|
794
|
+
if twin < 0 or twin > 11:
|
|
795
|
+
raise ValueError("twin must be between 0 and 11 (inclusive)")
|
|
796
|
+
|
|
797
|
+
if nlocation is not None:
|
|
798
|
+
raise ValueError('"nlocation" must be None')
|
|
799
|
+
|
|
800
|
+
if slocation and len(slocation) % 2 != 0:
|
|
801
|
+
raise ValueError("number of elements in slocation must be a multiple of 2")
|
|
802
|
+
|
|
803
|
+
if ssx and len(ssx) % 2 != 0:
|
|
804
|
+
raise ValueError("number of elements in ssx must be a multiple of 2")
|
|
805
|
+
|
|
806
|
+
if ssy and len(ssy) % 2 != 0:
|
|
807
|
+
raise ValueError("number of elements in ssy must be a multiple of 2")
|
|
808
|
+
|
|
809
|
+
if sst and len(sst) % 2 != 0:
|
|
810
|
+
raise ValueError("number of elements in sst must be a multiple of 2")
|
|
811
|
+
|
|
812
|
+
if ssystem < 0 or ssystem > 1:
|
|
813
|
+
raise ValueError("ssystem must be 0 or 1")
|
|
814
|
+
|
|
815
|
+
def norm(x: float) -> float:
|
|
816
|
+
if slocation is not None and ssystem == 1:
|
|
817
|
+
return x
|
|
818
|
+
elif tbsize == 1:
|
|
819
|
+
return math.sqrt(x)
|
|
820
|
+
else:
|
|
821
|
+
return x ** (1 / 3)
|
|
822
|
+
|
|
823
|
+
_sigma: typing.Union[float, typing.Sequence[typing.Callable[[float], float]]]
|
|
824
|
+
|
|
825
|
+
if slocation is not None:
|
|
826
|
+
_sigma = [to_func(flatten(slocation), norm, sigma)] * 3
|
|
827
|
+
elif any(ss is not None for ss in (ssx, ssy, sst)):
|
|
828
|
+
_sigma = [to_func(flatten(ss), norm, sigma) for ss in (ssx, ssy, sst)]
|
|
829
|
+
else:
|
|
830
|
+
_sigma = sigma
|
|
831
|
+
|
|
832
|
+
return DFTTest2(
|
|
833
|
+
clip = clip,
|
|
834
|
+
ftype = ftype,
|
|
835
|
+
sigma = _sigma,
|
|
836
|
+
sigma2 = sigma2,
|
|
837
|
+
pmin = pmin,
|
|
838
|
+
pmax = pmax,
|
|
839
|
+
sbsize = sbsize,
|
|
840
|
+
sosize = sosize,
|
|
841
|
+
tbsize = tbsize,
|
|
842
|
+
swin = swin,
|
|
843
|
+
twin = twin,
|
|
844
|
+
sbeta = sbeta,
|
|
845
|
+
tbeta = tbeta,
|
|
846
|
+
zmean = zmean,
|
|
847
|
+
f0beta = f0beta,
|
|
848
|
+
ssystem = ssystem,
|
|
849
|
+
planes = planes,
|
|
850
|
+
backend = select_backend(backend, sbsize, tbsize)
|
|
851
|
+
)
|