Skip to content

fft

Classes:

Functions:

  • fft3d

    Applies FFT3DFilter, a 3D frequency-domain filter used for strong denoising and mild sharpening.

Attributes:

FilterTypeT module-attribute

Frequency module-attribute

Frequency: TypeAlias = float

SLocBoundT module-attribute

SLocBoundT = TypeVar('SLocBoundT', bound=SLocation)

SLocT module-attribute

SLocT = SLocationT | MultiDim

SLocationT module-attribute

Sigma module-attribute

Sigma: TypeAlias = float

SynthesisTypeT module-attribute

BackendInfo

BackendInfo(backend: Backend, **kwargs: Any)

Bases: KwargsT

Methods:

Attributes:

Source code
353
354
355
356
def __init__(self, backend: DFTTest.Backend, **kwargs: Any) -> None:
    super().__init__(**kwargs)

    self.backend = backend

backend instance-attribute

backend: Backend = backend

num_streams instance-attribute

num_streams: int

resolved_backend property

resolved_backend: Backend

__call__

__call__(
    clip: VideoNode,
    sloc: SLocT | None = None,
    ftype: FilterTypeT | None = None,
    block_size: int | None = None,
    overlap: int | None = None,
    tr: int | None = None,
    tr_overlap: int | None = None,
    swin: SynthesisTypeT | None = None,
    twin: SynthesisTypeT | None = None,
    zmean: bool | None = None,
    alpha: float | None = None,
    ssystem: int | None = None,
    blockwise: bool = True,
    planes: PlanesT = None,
    *,
    func: FuncExceptT | None = None,
    default_args: KwargsT | None = None,
    **dkwargs: Any
) -> VideoNode
Source code
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
def __call__(
    self, clip: vs.VideoNode, sloc: SLocT | None = None,
    ftype: FilterTypeT | None = None,
    block_size: int | None = None, overlap: int | None = None,
    tr: int | None = None, tr_overlap: int | None = None,
    swin: SynthesisTypeT | None = None,
    twin: SynthesisTypeT | None = None,
    zmean: bool | None = None, alpha: float | None = None, ssystem: int | None = None,
    blockwise: bool = True, planes: PlanesT = None, *, func: FuncExceptT | None = None,
    default_args: KwargsT | None = None, **dkwargs: Any
) -> vs.VideoNode:
    func = func or self.__class__

    assert check_variable(clip, func)

    Backend = DFTTest.Backend

    kwargs: KwargsT = KwargsT(
        ftype=ftype, swin=swin, sbsize=block_size, sosize=overlap, tbsize=((tr or 0) * 2) + 1,
        tosize=tr_overlap, twin=twin, zmean=zmean, alpha=alpha, ssystem=ssystem,
        planes=planes, smode=int(blockwise)
    )

    if isinstance(sloc, SLocation.MultiDim):
        kwargs |= KwargsT(ssx=sloc._horizontal, ssy=sloc._vertical, sst=sloc._temporal)
    else:
        kwargs |= KwargsT(slocation=SLocation.from_param(sloc))

    clean_dft_args = KwargsNotNone(default_args or {}) | KwargsNotNone(kwargs)

    dft_args: KwargsT = KwargsT()

    for key, value in clean_dft_args.items():
        if isinstance(value, Enum) and callable(value):
            value = value()

        if isinstance(value, SynthesisTypeWithInfo):
            value = value.to_dict(key[0])

        if isinstance(value, dict):
            dft_args |= value
            continue

        if key == 'nlocation' and value:
            value = cast(Sequence[NLocation | int], value)
            value = list[int](flatten(value))

        if isinstance(value, SLocation):
            value = list(value)

        if isinstance(value, Enum):
            value = value.value

        dft_args[key] = value

    backend = self.resolved_backend

    if backend.is_dfttest2:
        from dfttest2 import Backend as DFTBackend
        from dfttest2 import DFTTest as DFTTest2  # noqa

        dft2_backend = None

        if backend is Backend.NVRTC:
            num_streams = dkwargs.pop('num_streams', self.num_streams if hasattr(self, 'num_streams') else 1)
            dft2_backend = DFTBackend.NVRTC(**(dict(**self) | dict(num_streams=num_streams)))
        elif backend is Backend.cuFFT:
            dft2_backend = DFTBackend.cuFFT(**self)
        elif backend is Backend.CPU:
            dft2_backend = DFTBackend.CPU(**self)
        elif backend is Backend.GCC:
            dft2_backend = DFTBackend.GCC(**self)

        if dft_args.get('tmode') is not None:
            raise CustomValueError('{backend} doesn\'t support tmode', func, backend=backend)

        if dft_args.pop('tosize'):
            raise CustomValueError('{backend} doesn\'t support tosize', func, backend=backend)

        return DFTTest2(clip, **dft_args | dkwargs, backend=dft2_backend)

    dft_args |= self

    if backend is Backend.OLD:
        return core.dfttest.DFTTest(clip, **dft_args)

    if backend is Backend.NEO:
        return core.neo_dfttest.DFTTest(clip, **dft_args)  # type: ignore

    if any(hasattr(core, x) for x in ('dfttest2_cpu', 'dfttest2_cuda', 'dfttest2_nvrtc')):
        raise CustomRuntimeError(
            'dfttest2 plugin is installed but is missing the python package, please install it.', self.__class__
        )

    raise CustomRuntimeError(
        'No implementation of DFTTest could be found, please install one. dfttest2 is recommended.', self.__class__
    )

from_param classmethod

from_param(value: Backend | BackendInfo) -> BackendInfo
Source code
358
359
360
@classmethod
def from_param(cls, value: DFTTest.Backend | BackendInfo) -> BackendInfo:
    return value() if isinstance(value, DFTTest.Backend) else value

DFTTest

DFTTest(
    clip: VideoNode | None = None,
    plugin: Backend | BackendInfo = AUTO,
    sloc: SLocT | None = None,
    **kwargs: Any
)

2D/3D frequency domain denoiser.

Classes:

Methods:

Attributes:

Source code
540
541
542
543
544
545
546
547
548
549
def __init__(
    self, clip: vs.VideoNode | None = None, plugin: Backend | BackendInfo = Backend.AUTO,
    sloc: SLocT | None = None, **kwargs: Any
) -> None:
    self.clip = clip

    self.plugin = BackendInfo.from_param(plugin)

    self.default_args = kwargs.copy()
    self.default_slocation = sloc

clip instance-attribute

clip = clip

default_args instance-attribute

default_args = copy()

default_slocation instance-attribute

default_slocation = sloc

plugin instance-attribute

plugin = from_param(plugin)

Backend

Bases: CustomIntEnum

Methods:

Attributes:

AUTO class-attribute instance-attribute

AUTO = auto()

CPU class-attribute instance-attribute

CPU = auto()

GCC class-attribute instance-attribute

GCC = auto()

NEO class-attribute instance-attribute

NEO = auto()

NVRTC class-attribute instance-attribute

NVRTC = auto()

OLD class-attribute instance-attribute

OLD = auto()

cuFFT class-attribute instance-attribute

cuFFT = auto()

is_dfttest2 property

is_dfttest2: bool

__call__

__call__(*, opt: int = ...) -> BackendInfo
__call__(
    *,
    threads: int = ...,
    fft_threads: int = ...,
    opt: int = ...,
    dither: int = ...
) -> BackendInfo
__call__(*, device_id: int = 0, in_place: bool = True) -> BackendInfo
__call__(*, device_id: int = 0, num_streams: int = 1) -> BackendInfo
__call__() -> BackendInfo
__call__(**kwargs: Any) -> BackendInfo
__call__(**kwargs: Any) -> BackendInfo
Source code
533
534
def __call__(self, **kwargs: Any) -> BackendInfo:
    return BackendInfo(self, **kwargs)

denoise

denoise(
    ref: VideoNode,
    sloc: SLocT | None = None,
    /,
    ftype: FilterTypeT = WIENER,
    tr: int = 0,
    tr_overlap: int = 0,
    swin: SynthesisTypeT = HANNING,
    twin: SynthesisTypeT = RECTANGULAR,
    block_size: int = 16,
    overlap: int = 12,
    zmean: bool = True,
    alpha: float | None = None,
    ssystem: int = 0,
    blockwise: bool = True,
    planes: PlanesT = None,
    func: FuncExceptT | None = None,
    **kwargs: Any,
) -> VideoNode
denoise(
    sloc: SLocT,
    /,
    *,
    ftype: FilterTypeT = WIENER,
    tr: int = 0,
    tr_overlap: int = 0,
    swin: SynthesisTypeT = HANNING,
    twin: SynthesisTypeT = RECTANGULAR,
    block_size: int = 16,
    overlap: int = 12,
    zmean: bool = True,
    alpha: float | None = None,
    ssystem: int = 0,
    blockwise: bool = True,
    planes: PlanesT = None,
    func: FuncExceptT | None = None,
    **kwargs: Any,
) -> VideoNode
denoise(
    ref_or_sloc: VideoNode | SLocT,
    sloc: SLocT | None = None,
    /,
    ftype: FilterTypeT = WIENER,
    tr: int = 0,
    tr_overlap: int = 0,
    swin: SynthesisTypeT = HANNING,
    twin: SynthesisTypeT = RECTANGULAR,
    block_size: int = 16,
    overlap: int = 12,
    zmean: bool = True,
    alpha: float | None = None,
    ssystem: int = 0,
    blockwise: bool = True,
    planes: PlanesT = None,
    func: FuncExceptT | None = None,
    **kwargs: Any,
) -> VideoNode
Source code
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
@inject_self
def denoise(
    self, ref_or_sloc: vs.VideoNode | SLocT, sloc: SLocT | None = None, /,
    ftype: FilterTypeT = FilterType.WIENER,
    tr: int = 0, tr_overlap: int = 0,
    swin: SynthesisTypeT = SynthesisType.HANNING,
    twin: SynthesisTypeT = SynthesisType.RECTANGULAR,
    block_size: int = 16, overlap: int = 12,
    zmean: bool = True, alpha: float | None = None, ssystem: int = 0,
    blockwise: bool = True, planes: PlanesT = None, func: FuncExceptT | None = None, **kwargs: Any
) -> vs.VideoNode:
    func = func or self.denoise

    clip = self.clip
    nsloc = self.default_slocation

    if isinstance(ref_or_sloc, vs.VideoNode):
        clip = ref_or_sloc
    else:
        nsloc = ref_or_sloc

    if sloc is not None:
        nsloc = sloc

    if clip is None:
        raise CustomValueError('You must pass a clip!', func)

    assert check_progressive(clip, func)

    return self.plugin(
        clip, nsloc, func=func, **(self.default_args | dict(
            ftype=ftype, block_size=block_size, overlap=overlap, tr=tr, tr_overlap=tr_overlap, swin=swin,
            twin=twin, zmean=zmean, alpha=alpha, ssystem=ssystem, blockwise=blockwise, planes=planes
        ) | kwargs)
    )

extract_freq

extract_freq(clip: VideoNode, sloc: SLocT, **kwargs: Any) -> VideoNode
Source code
616
617
618
619
@inject_self
def extract_freq(self, clip: vs.VideoNode, sloc: SLocT, **kwargs: Any) -> vs.VideoNode:
    kwargs = dict(func=self.extract_freq) | kwargs
    return clip.std.MakeDiff(self.denoise(clip, sloc, **kwargs))

insert_freq

insert_freq(
    low: VideoNode, high: VideoNode, sloc: SLocT, **kwargs: Any
) -> VideoNode
Source code
621
622
623
@inject_self
def insert_freq(self, low: vs.VideoNode, high: vs.VideoNode, sloc: SLocT, **kwargs: Any) -> vs.VideoNode:
    return low.std.MergeDiff(self.extract_freq(high, sloc, **dict(func=self.insert_freq) | kwargs))

merge_freq

merge_freq(
    low: VideoNode, high: VideoNode, sloc: SLocT, **kwargs: Any
) -> VideoNode
Source code
625
626
627
628
629
@inject_self
def merge_freq(self, low: vs.VideoNode, high: vs.VideoNode, sloc: SLocT, **kwargs: Any) -> vs.VideoNode:
    return self.insert_freq(
        self.denoise(low, sloc, **kwargs), high, sloc, **dict(func=self.merge_freq) | kwargs
    )

FilterType

Bases: CustomIntEnum

Filtering types for DFTTest.

Methods:

Attributes:

  • MULT

    mult = sigma

  • MULT_PSD

    mult = (psd >= pmin && psd <= pmax) ? sigma : sigma2

  • MULT_RANGE

    mult = sigma * sqrt((psd * pmax) / ((psd + pmin) * (psd + pmax)))

  • THR

    mult = psd < sigma ? 0.0 : 1.0

  • WIENER

    mult = max((psd - sigma) / psd, 0) ^ f0beta

MULT class-attribute instance-attribute

MULT = 2

mult = sigma

MULT_PSD class-attribute instance-attribute

MULT_PSD = 3

mult = (psd >= pmin && psd <= pmax) ? sigma : sigma2

MULT_RANGE class-attribute instance-attribute

MULT_RANGE = 4

mult = sigma * sqrt((psd * pmax) / ((psd + pmin) * (psd + pmax)))

THR class-attribute instance-attribute

THR = 1

mult = psd < sigma ? 0.0 : 1.0

WIENER class-attribute instance-attribute

WIENER = 0

mult = max((psd - sigma) / psd, 0) ^ f0beta

__call__

__call__(**kwargs: Any) -> FilterTypeWithInfo
Source code
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
def __call__(self, **kwargs: Any) -> FilterTypeWithInfo:
    if self is FilterType.WIENER:
        def_kwargs = KwargsT(sigma=8.0, beta=1.0, nlocation=None)
    elif self in {FilterType.THR, FilterType.MULT}:
        def_kwargs = KwargsT(sigma=8.0, nlocation=None)
    elif self is FilterType.MULT_PSD:
        def_kwargs = KwargsT(sigma=8.0, pmin=0.0, sigma2=16.0, pmax=500.0)
    elif self is FilterType.MULT_RANGE:
        def_kwargs = KwargsT(sigma=8.0, pmin=0.0, pmax=500.0)
    else:
        def_kwargs = KwargsT()

    kwargs = def_kwargs | kwargs

    if 'beta' in kwargs:
        kwargs['f0beta'] = kwargs.pop('beta')

    return FilterTypeWithInfo(ftype=self.value, **kwargs)

FilterTypeWithInfo

Bases: KwargsT

NLocation

Bases: NamedTuple

Attributes:

frame_number instance-attribute

frame_number: int

plane instance-attribute

plane: int

xpos instance-attribute

xpos: int

ypos instance-attribute

ypos: int

SInterMode

Bases: CustomEnum

SLocation interpolation mode.

Methods:

Attributes:

CUBIC class-attribute instance-attribute

CUBIC = 'cubic'

LINEAR class-attribute instance-attribute

LINEAR = 'linear'

NEAREST class-attribute instance-attribute

NEAREST = 'nearest'

NEAREST_UP class-attribute instance-attribute

NEAREST_UP = 'nearest-up'

QUADRATIC class-attribute instance-attribute

QUADRATIC = 'quadratic'

SPLINE class-attribute instance-attribute

SPLINE = 1

SPLINE_LINEAR class-attribute instance-attribute

SPLINE_LINEAR = 'slinear'

ZERO class-attribute instance-attribute

ZERO = 'zero'

__call__

__call__(location: SLocationT, /, res: int = 20, digits: int = 3) -> SLocation
__call__(
    h_loc: SLocationT | None = None,
    v_loc: SLocationT | None = None,
    t_loc: SLocationT | None = None,
    /,
    res: int = 20,
    digits: int = 3,
) -> MultiDim
__call__(
    *location: SLocationT, res: int = 20, digits: int = 3
) -> SLocation | MultiDim
__call__(
    *locations: SLocationT, res: int = 20, digits: int = 3
) -> SLocation | MultiDim
Source code
65
66
67
68
69
70
71
def __call__(  # type: ignore
    self, *locations: SLocationT, res: int = 20, digits: int = 3
) -> SLocation | SLocation.MultiDim:
    if len(locations) == 1:
        return SLocation.from_param(locations[0]).interpolate(self, res, digits)

    return SLocation.MultiDim(*(self(x, res, digits) for x in locations))

SLocation

SLocation(
    locations: (
        Sequence[Frequency | Sigma]
        | Sequence[tuple[Frequency, Sigma]]
        | Mapping[Frequency, Sigma]
    ),
    interpolate: SInterMode | None = None,
    strict: bool = True,
)

Specify a range of frequencies to target.

Classes:

Methods:

Attributes:

Source code
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
def __init__(
    self, locations: Sequence[Frequency | Sigma] | Sequence[tuple[Frequency, Sigma]] | Mapping[Frequency, Sigma],
    interpolate: SInterMode | None = None, strict: bool = True
) -> None:
    if isinstance(locations, Mapping):
        frequencies, sigmas = list(locations.keys()), list(locations.values())
    else:
        locations = list[float](flatten(locations))

        if len(locations) % 2:
            raise CustomValueError(
                "slocations must resolve to an even number of items, pairing frequency and sigma respectively",
                self.__class__
            )

        frequencies, sigmas = list(locations[0::2]), list(locations[1::2])

    frequencies = self.boundsCheck(frequencies, (0, 1), strict)
    sigmas = self.boundsCheck(sigmas, (0, None), strict)

    self.frequencies, self.sigmas = (t for t in zip(*sorted(zip(frequencies, sigmas))))

    if interpolate:
        interpolated = self.interpolate(interpolate)

        self.frequencies, self.sigmas = interpolated.frequencies, interpolated.sigmas

NoProcess instance-attribute

NoProcess: SLocation

frequencies instance-attribute

frequencies: tuple[float, ...]

sigmas instance-attribute

sigmas: tuple[float, ...]

MultiDim dataclass

MultiDim(
    horizontal: SLocationT | Literal[False] | None = None,
    vertical: SLocationT | Literal[False] | None = None,
    temporal: SLocationT | Literal[False] | None = None,
)

Attributes:

horizontal class-attribute instance-attribute

horizontal: SLocationT | Literal[False] | None = None

temporal class-attribute instance-attribute

temporal: SLocationT | Literal[False] | None = None

vertical class-attribute instance-attribute

vertical: SLocationT | Literal[False] | None = None

boundsCheck classmethod

boundsCheck(
    values: list[float],
    bounds: tuple[float | None, float | None],
    strict: bool = False,
) -> list[float]
Source code
 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
@classmethod
def boundsCheck(
    cls, values: list[float], bounds: tuple[float | None, float | None], strict: bool = False
) -> list[float]:
    if not values:
        raise CustomValueError('"values" can\'t be empty!', cls)

    values = values.copy()

    bounds_str = iter('inf' if x is None else x for x in bounds)
    of_error = CustomOverflowError("Invalid value at index {i}, not in ({bounds})", cls, bounds=bounds_str)

    low_bound, up_bound = bounds

    for i, value in enumerate(values):
        if low_bound is not None and value < low_bound:
            if strict:
                raise of_error(i=i)

            values[i] = low_bound

        if up_bound is not None and value > up_bound:
            if strict:
                raise of_error(i=i)

            values[i] = up_bound

    return values

from_param classmethod

from_param(location: SLocationT | Literal[False]) -> SLocBoundT
from_param(location: SLocationT | Literal[False] | None) -> SLocBoundT | None
from_param(location: SLocationT | Literal[False] | None) -> SLocBoundT | None
Source code
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
@classmethod
def from_param(cls: type[SLocBoundT], location: SLocationT | Literal[False] | None) -> SLocBoundT | None:
    if isinstance(location, SupportsFloatOrIndex) and location is not False:
        location = float(location)
        location = {0: location, 1: location}

    if location is None:
        return None

    if location is False:
        location = SLocation.NoProcess

    if isinstance(location, SLocation):
        return cls(list(location))

    return cls(location)

interpolate

interpolate(
    method: SInterMode = LINEAR, res: int = 20, digits: int = 3
) -> SLocation
Source code
173
174
175
176
177
178
179
180
181
def interpolate(self, method: SInterMode = SInterMode.LINEAR, res: int = 20, digits: int = 3) -> SLocation:
    frequencies = list({round(x / (res - 1), digits) for x in range(res)})
    sigmas = interp1d(
        list(self.frequencies), list(self.sigmas), method.value, fill_value='extrapolate'
    )(frequencies)

    return SLocation(
        dict(zip(frequencies, sigmas)) | dict(zip(self.frequencies, self.sigmas)), strict=False
    )

SynthesisType

Bases: CustomIntEnum

Synthesis type for spatial processing.

Methods:

Attributes:

BARLETT class-attribute instance-attribute

BARLETT = 8

BARLETT_HANN class-attribute instance-attribute

BARLETT_HANN = 9

BLACKMAN class-attribute instance-attribute

BLACKMAN = 2

BLACKMAN_HARRIS_4TERM class-attribute instance-attribute

BLACKMAN_HARRIS_4TERM = 3

BLACKMAN_HARRIS_7TERM class-attribute instance-attribute

BLACKMAN_HARRIS_7TERM = 5

BLACKMAN_NUTTALL class-attribute instance-attribute

BLACKMAN_NUTTALL = 11

FLAT_TOP class-attribute instance-attribute

FLAT_TOP = 6

HAMMING class-attribute instance-attribute

HAMMING = 1

HANNING class-attribute instance-attribute

HANNING = 0

KAISER_BESSEL class-attribute instance-attribute

KAISER_BESSEL = 4

NUTTALL class-attribute instance-attribute

NUTTALL = 10

RECTANGULAR class-attribute instance-attribute

RECTANGULAR = 7

__call__

__call__(**kwargs: Any) -> SynthesisTypeWithInfo
Source code
339
340
341
342
343
def __call__(self, **kwargs: Any) -> SynthesisTypeWithInfo:
    if self is SynthesisType.KAISER_BESSEL and 'beta' not in kwargs:
        kwargs['beta'] = 2.5

    return SynthesisTypeWithInfo(win=self.value, **kwargs)

SynthesisTypeWithInfo

Bases: KwargsT

Methods:

to_dict

to_dict(otype: str) -> KwargsT
Source code
295
296
297
298
299
300
301
302
303
304
def to_dict(self, otype: str) -> KwargsT:
    value = self.copy()

    if 'beta' in value:
        value = value.copy()
        value[f'{otype}beta'] = value.pop('beta')

    value[f'{otype}win'] = value.pop('win')

    return value

fft3d

fft3d(clip: VideoNode, **kwargs: Any) -> ConstantFormatVideoNode

Applies FFT3DFilter, a 3D frequency-domain filter used for strong denoising and mild sharpening.

This filter processes frames using the Fast Fourier Transform (FFT) in the frequency domain. Unlike local filters, FFT3DFilter performs block-based, non-local processing.

Official documentation: https://github.com/myrsloik/VapourSynth-FFT3DFilter/blob/master/doc/fft3dfilter.md

Possibly faster implementation: https://github.com/AmusementClub/VapourSynth-FFT3DFilter/releases

Note: Sigma values are internally scaled according to bit depth, unlike when using the plugin directly.

Parameters:

  • clip

    (VideoNode) –

    Input video clip.

  • **kwargs

    (Any, default: {} ) –

    Additional parameters passed to the FFT3DFilter plugin.

Returns:

  • ConstantFormatVideoNode

    A heavily degraded version of DFTTest, with added banding and color shifts.

Source code
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
@deprecated("`fft3d` is permanently deprecated and known to contain many bugs. Use with caution.")
def fft3d(clip: vs.VideoNode, **kwargs: Any) -> ConstantFormatVideoNode:
    """
    Applies FFT3DFilter, a 3D frequency-domain filter used for strong denoising and mild sharpening.

    This filter processes frames using the Fast Fourier Transform (FFT) in the frequency domain.
    Unlike local filters, FFT3DFilter performs block-based, non-local processing.

    Official documentation:
    https://github.com/myrsloik/VapourSynth-FFT3DFilter/blob/master/doc/fft3dfilter.md

    Possibly faster implementation:
    https://github.com/AmusementClub/VapourSynth-FFT3DFilter/releases

    Note: Sigma values are internally scaled according to bit depth, unlike when using the plugin directly.

    :param clip:        Input video clip.
    :param **kwargs:    Additional parameters passed to the FFT3DFilter plugin.
    :return:            A heavily degraded version of DFTTest, with added banding and color shifts.
    """
    kwargs |= dict(interlaced=FieldBased.from_video(clip, False, fft3d).is_inter)

    # fft3dfilter requires sigma values to be scaled to bit depth
    # https://github.com/myrsloik/VapourSynth-FFT3DFilter/blob/master/doc/fft3dfilter.md#scaling-parameters-according-to-bit-depth
    sigma_multiplier = 1.0 / 256.0 if get_sample_type(clip) is vs.FLOAT else 1 << (get_depth(clip) - 8)

    for sigma in ['sigma', 'sigma2', 'sigma3', 'sigma4', 'smin ', 'smax']:
        if sigma in kwargs:
            kwargs[sigma] *= sigma_multiplier

    return core.fft3dfilter.FFT3DFilter(clip, **kwargs)