Skip to content

blockmatch

Functions:

  • bm3d

    Block-Matching and 3D filtering (BM3D) is a state-of-the-art algorithm for image denoising.

  • wnnm

    Weighted Nuclear Norm Minimization Denoise algorithm.

BM3D

BM3D(bm3d_func: Callable[P, R])

Bases: Generic[P, R]

Class decorator that wraps the bm3d function and extends its functionality.

It is not meant to be used directly.

Classes:

  • Backend

    Enum representing the available backends for running the BM3D plugin.

  • Profile

    Enum representing the available BM3D profiles, each with default parameter settings.

Methods:

Attributes:

Source code
142
143
def __init__(self, bm3d_func: Callable[P, R]) -> None:
    self._func = bm3d_func

matrix_opp2rgb class-attribute instance-attribute

matrix_opp2rgb: list[float] = [1, 1, 2 / 3, 1, 0, -4 / 3, 1, -1, 2 / 3]

Matrix to convert OPP (Opponent) color space back to RGB color space.

matrix_rgb2opp class-attribute instance-attribute

matrix_rgb2opp: list[float] = [
    1 / 3,
    1 / 3,
    1 / 3,
    1 / 2,
    0,
    -1 / 2,
    1 / 4,
    -1 / 2,
    1 / 4,
]

Matrix to convert RGB color space to OPP (Opponent) color space.

Backend

Bases: CustomEnum

Enum representing the available backends for running the BM3D plugin.

Methods:

  • resolve

    Resolves the appropriate BM3D backend to use based on availability and context.

Attributes:

  • AUTO

    Automatically selects the best available backend.

  • CPU

    Optimized CPU implementation using AVX and AVX2 intrinsics.

  • CUDA

    GPU implementation using NVIDIA CUDA.

  • CUDA_RTC

    GPU implementation using NVIDIA CUDA with NVRTC (runtime compilation).

  • HIP

    GPU implementation using AMD HIP.

  • OLD

    Reference VapourSynth-BM3D implementation.

  • SYCL

    GPU implementation using Intel SYCL.

  • plugin (_VSPlugin) –

    Returns the appropriate BM3D plugin based on the current backend.

AUTO class-attribute instance-attribute

AUTO = 'auto'

Automatically selects the best available backend. Selection priority: "CUDA_RTC" → "CUDA" → "HIP" → "SYCL" → "CPU" → "OLD". When the filter chain is executed within vspreview, the priority of "cuda_rtc" and "cuda" is reversed.

CPU class-attribute instance-attribute

CPU = 'bm3dcpu'

Optimized CPU implementation using AVX and AVX2 intrinsics.

CUDA class-attribute instance-attribute

CUDA = 'bm3dcuda'

GPU implementation using NVIDIA CUDA.

CUDA_RTC class-attribute instance-attribute

CUDA_RTC = 'bm3dcuda_rtc'

GPU implementation using NVIDIA CUDA with NVRTC (runtime compilation).

HIP class-attribute instance-attribute

HIP = 'bm3dhip'

GPU implementation using AMD HIP.

OLD class-attribute instance-attribute

OLD = 'bm3d'

Reference VapourSynth-BM3D implementation.

SYCL class-attribute instance-attribute

SYCL = 'bm3dsycl'

GPU implementation using Intel SYCL.

plugin property

plugin: _VSPlugin

Returns the appropriate BM3D plugin based on the current backend.

Returns:

  • _VSPlugin

    The corresponding BM3D plugin for the resolved backend.

resolve cached

resolve() -> Backend

Resolves the appropriate BM3D backend to use based on availability and context.

If the current instance is not BM3D.Backend.AUTO, it returns itself. Otherwise, it attempts to select the best available backend.

Returns:

  • Backend

    The resolved BM3D.Backend to use for processing.

Raises:

  • CustomRuntimeError

    If no supported BM3D implementation is available on the system.

Source code
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
@cache
def resolve(self) -> BM3D.Backend:
    """
    Resolves the appropriate BM3D backend to use based on availability and context.

    If the current instance is not BM3D.Backend.AUTO, it returns itself.
    Otherwise, it attempts to select the best available backend.

    :raises CustomRuntimeError:     If no supported BM3D implementation is available on the system.
    :return:                        The resolved BM3D.Backend to use for processing.
    """
    if self is not BM3D.Backend.AUTO:
        return self

    try:
        from vspreview.api import is_preview
    except ImportError:

        def is_preview() -> bool:
            return False

    if is_preview() and hasattr(core, "bm3dcuda"):
        return BM3D.Backend.CUDA

    for member in list(BM3D.Backend.__members__.values())[1:]:
        if hasattr(core, member.value):
            return BM3D.Backend(member.value)

    raise CustomRuntimeError(
        "No compatible plugin found. Please install one from: "
        "https://github.com/WolframRhodium/VapourSynth-BM3DCUDA "
        "or https://github.com/HomeOfVapourSynthEvolution/VapourSynth-BM3D/"
    )

Profile

Bases: CustomStrEnum

Enum representing the available BM3D profiles, each with default parameter settings.

For more detailed information on these profiles, refer to the original documentation

Methods:

  • basic_args

    Retrieves the arguments for the basic estimate step based on the specified radius.

  • final_args

    Retrieves the arguments for the final estimate step based on the specified radius.

Attributes:

FAST class-attribute instance-attribute

FAST = 'fast'

A profile optimized for maximum processing speed.

HIGH class-attribute instance-attribute

HIGH = 'high'

A profile focused on achieving high-precision denoising.

LOW_COMPLEXITY class-attribute instance-attribute

LOW_COMPLEXITY = 'lc'

A profile designed for content with low-complexity noise.

NORMAL class-attribute instance-attribute

NORMAL = 'np'

A neutral profile.

VERY_NOISY class-attribute instance-attribute

VERY_NOISY = 'vn'

A profile tailored for handling very noisy content.

config cached property

Retrieves the configuration for each BM3D profile.

basic_args

basic_args(radius: int | None) -> dict[str, Any]

Retrieves the arguments for the basic estimate step based on the specified radius.

Parameters:

  • radius
    (int | None) –

    The temporal radius for denoising. If None, a default value is used.

Returns:

  • dict[str, Any]

    A dictionary of arguments for the basic denoising step.

Source code
441
442
443
444
445
446
447
448
def basic_args(self, radius: int | None) -> dict[str, Any]:
    """
    Retrieves the arguments for the basic estimate step based on the specified radius.

    :param radius:  The temporal radius for denoising. If None, a default value is used.
    :return:        A dictionary of arguments for the basic denoising step.
    """
    return self._get_args(radius, "basic")

final_args

final_args(radius: int | None) -> dict[str, Any]

Retrieves the arguments for the final estimate step based on the specified radius.

Parameters:

  • radius
    (int | None) –

    The temporal radius for denoising. If None, a default value is used.

Returns:

  • dict[str, Any]

    A dictionary of arguments for the final denoising step.

Source code
450
451
452
453
454
455
456
457
def final_args(self, radius: int | None) -> dict[str, Any]:
    """
    Retrieves the arguments for the final estimate step based on the specified radius.

    :param radius:  The temporal radius for denoising. If None, a default value is used.
    :return:        A dictionary of arguments for the final denoising step.
    """
    return self._get_args(radius, "final")

__call__

__call__(*args: args, **kwargs: kwargs) -> R
Source code
145
146
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
    return self._func(*args, **kwargs)

UnsupportedProfileError

Bases: CustomValueError

Raised when an unsupported profile is passed.

bm3d

bm3d(
    clip: VideoNode,
    sigma: float | Sequence[float] = 0.5,
    tr: int | Sequence[int | None] | None = None,
    refine: int = 1,
    profile: Profile = FAST,
    pre: VideoNode | None = None,
    ref: VideoNode | None = None,
    backend: Backend = AUTO,
    basic_args: dict[str, Any] | None = None,
    final_args: dict[str, Any] | None = None,
    **kwargs: Any
) -> ConstantFormatVideoNode

Block-Matching and 3D filtering (BM3D) is a state-of-the-art algorithm for image denoising.

More information at: - https://github.com/HomeOfVapourSynthEvolution/VapourSynth-BM3D/ - https://github.com/WolframRhodium/VapourSynth-BM3DCUDA

Example:

denoised = bm3d(clip, 1.25, 1, backend=bm3d.Backend.CUDA_RTC, ...)

Parameters:

  • clip

    (VideoNode) –

    The clip to process. If using BM3D.Backend.OLD, the clip format must be YUV444 or RGB, as filtering is always performed in the OPPonent color space. If using another device type and the clip format is: - RGB -> Processed in OPP format (BM3D algorithm, aka chroma=False). - YUV444 -> Processed in YUV444 format (CBM3D algorithm, aka chroma=True). - GRAY -> Processed as-is. - YUVXXX -> Each plane is processed separately.

  • sigma

    (float | Sequence[float], default: 0.5 ) –

    Strength of denoising. Valid range is [0, +inf). A sequence of up to 3 elements can be used to set different sigma values for the Y, U, and V channels. If fewer than 3 elements are given, the last value is repeated. Defaults to 0.5.

  • tr

    (int | Sequence[int | None] | None, default: None ) –

    The temporal radius for denoising. Valid range is [1, 16]. Defaults to the radius defined by the profile.

  • refine

    (int, default: 1 ) –

    Number of refinement steps. * 0 means basic estimate only. * 1 means basic estimate with one final estimate. * n means basic estimate refined with a final estimate n times.

  • profile

    (Profile, default: FAST ) –

    The preset profile. Defaults to BM3D.Profile.FAST.

  • pre

    (VideoNode | None, default: None ) –

    A pre-filtered clip for the basic estimate. It should be more suitable for block-matching than the input clip, and must be of the same format and dimensions. Either pre or ref can be specified, not both. Defaults to None.

  • ref

    (VideoNode | None, default: None ) –

    A clip to be used as the basic estimate. It replaces BM3D’s internal basic estimate and serves as the reference for the final estimate. Must be of the same format and dimensions as the input clip. Either ref or pre can be specified, not both. Defaults to None.

  • backend

    (Backend, default: AUTO ) –

    The backend to use for processing. Defaults to BM3D.Backend.AUTO.

  • basic_args

    (dict[str, Any] | None, default: None ) –

    Additional arguments to pass to the basic estimate step. Defaults to None.

  • final_args

    (dict[str, Any] | None, default: None ) –

    Additional arguments to pass to the final estimate step. Defaults to None.

  • **kwargs

    (Any, default: {} ) –

    Internal keyword arguments for testing purposes.

Returns:

  • ConstantFormatVideoNode

    Denoised clip.

Raises:

  • CustomValueError

    If both pre and ref are specified at the same time.

  • UnsupportedProfileError

    If the VERY_NOISY profile is not supported by the selected device type.

  • UnsupportedVideoFormatError

    If the video format is not supported when using BM3D.Backend.OLD.

Source code
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
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
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
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
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
@BM3D
def bm3d(
    clip: vs.VideoNode,
    sigma: float | Sequence[float] = 0.5,
    tr: int | Sequence[int | None] | None = None,
    refine: int = 1,
    profile: BM3D.Profile = BM3D.Profile.FAST,
    pre: vs.VideoNode | None = None,
    ref: vs.VideoNode | None = None,
    backend: BM3D.Backend = BM3D.Backend.AUTO,
    basic_args: dict[str, Any] | None = None,
    final_args: dict[str, Any] | None = None,
    **kwargs: Any,
) -> ConstantFormatVideoNode:
    """
    Block-Matching and 3D filtering (BM3D) is a state-of-the-art algorithm for image denoising.

    More information at:
        - https://github.com/HomeOfVapourSynthEvolution/VapourSynth-BM3D/
        - https://github.com/WolframRhodium/VapourSynth-BM3DCUDA

    Example:
        ```py
        denoised = bm3d(clip, 1.25, 1, backend=bm3d.Backend.CUDA_RTC, ...)
        ```

    :param clip:                            The clip to process.
                                            If using BM3D.Backend.OLD, the clip format must be YUV444 or RGB,
                                            as filtering is always performed in the OPPonent color space.
                                            If using another device type and the clip format is:
                                                - RGB       -> Processed in OPP format (BM3D algorithm, aka `chroma=False`).
                                                - YUV444    -> Processed in YUV444 format (CBM3D algorithm, aka `chroma=True`).
                                                - GRAY      -> Processed as-is.
                                                - YUVXXX    -> Each plane is processed separately.

    :param sigma:                           Strength of denoising. Valid range is [0, +inf).
                                            A sequence of up to 3 elements can be used to set different sigma values
                                            for the Y, U, and V channels.
                                            If fewer than 3 elements are given, the last value is repeated.
                                            Defaults to 0.5.

    :param tr:                              The temporal radius for denoising. Valid range is [1, 16].
                                            Defaults to the radius defined by the profile.

    :param refine:                          Number of refinement steps.
                                                * 0 means basic estimate only.
                                                * 1 means basic estimate with one final estimate.
                                                * n means basic estimate refined with a final estimate n times.

    :param profile:                         The preset profile. Defaults to BM3D.Profile.FAST.

    :param pre:                             A pre-filtered clip for the basic estimate.
                                            It should be more suitable for block-matching than the input clip,
                                            and must be of the same format and dimensions.
                                            Either `pre` or `ref` can be specified, not both.
                                            Defaults to None.

    :param ref:                             A clip to be used as the basic estimate.
                                            It replaces BM3D’s internal basic estimate and serves as the reference
                                            for the final estimate.
                                            Must be of the same format and dimensions as the input clip.
                                            Either `ref` or `pre` can be specified, not both.
                                            Defaults to None.

    :param backend:                         The backend to use for processing. Defaults to BM3D.Backend.AUTO.

    :param basic_args:                      Additional arguments to pass to the basic estimate step.
                                            Defaults to None.

    :param final_args:                      Additional arguments to pass to the final estimate step.
                                            Defaults to None.

    :param **kwargs:                        Internal keyword arguments for testing purposes.

    :raises CustomValueError:               If both `pre` and `ref` are specified at the same time.
    :raises UnsupportedProfileError:        If the VERY_NOISY profile is not supported by the selected device type.
    :raises UnsupportedVideoFormatError:    If the video format is not supported when using BM3D.Backend.OLD.

    :return:                                Denoised clip.
    """

    func = kwargs.pop("func", None) or bm3d

    if ref and pre:
        raise CustomValueError("You cannot specify both 'pre' and 'ref' at the same time.", func)

    if pre is not None:
        pre = check_ref_clip(clip, pre, func)

    if ref is not None:
        ref = check_ref_clip(clip, ref, func)

    radius_basic, radius_final = normalize_seq(tr, 2)
    nsigma = normalize_seq(sigma, 3)

    backend = backend.resolve()
    nbasic_args = fallback(basic_args, KwargsT())
    nfinal_args = fallback(final_args, KwargsT())

    matrix_rgb2opp = kwargs.pop("matrix_rgb2opp", BM3D.matrix_rgb2opp)
    matrix_opp2rgb = kwargs.pop("matrix_rgb2opp", BM3D.matrix_opp2rgb)

    if backend != BM3D.Backend.OLD and profile == BM3D.Profile.VERY_NOISY:
        raise UnsupportedProfileError(
            "The VERY_NOISY profile is only supported with BM3D.Backend.OLD.", func
        )

    def _bm3d_wolfram(
        preclip: ConstantFormatVideoNode,
        pre: ConstantFormatVideoNode | None,
        ref: ConstantFormatVideoNode | None,
        chroma: bool = False,
    ) -> ConstantFormatVideoNode:
        """Internal function for WolframRhodium implementation."""

        if not ref:
            b_args = (
                _clean_keywords(profile.basic_args(radius_basic), backend.plugin.BM3Dv2)
                | nbasic_args
                | kwargs
            )
            b_args.update(chroma=chroma)

            basic = backend.plugin.BM3Dv2(preclip, pre, nsigma, **b_args)
        else:
            basic = ref

        if not refine:
            final = basic
        else:
            f_args = (
                _clean_keywords(profile.final_args(radius_final), backend.plugin.BM3Dv2)
                | nfinal_args
                | kwargs
            )
            f_args.update(chroma=chroma)

            final = basic

            for _ in range(refine):
                final = backend.plugin.BM3Dv2(preclip, final, nsigma, **f_args)

        return final

    def _bm3d_mawen(
        preclip: ConstantFormatVideoNode,
        pre: ConstantFormatVideoNode | None,
        ref: ConstantFormatVideoNode | None,
    ) -> ConstantFormatVideoNode:
        """Internal function for mawen1250 implementation."""

        preclip = core.bm3d.RGB2OPP(preclip, preclip.format.sample_type)

        if pre:
            pre = core.bm3d.RGB2OPP(pre, pre.format.sample_type)

        if ref:
            ref = core.bm3d.RGB2OPP(ref, ref.format.sample_type)

        if not ref:
            b_args = profile.basic_args(radius_basic) | nbasic_args | kwargs
            r = b_args["radius"]

            if r > 0:
                basic = core.bm3d.VBasic(
                    preclip, pre, profile, nsigma, matrix=100, **b_args
                ).bm3d.VAggregate(r, preclip.format.sample_type)
            else:
                basic = core.bm3d.Basic(
                    preclip, pre, profile, nsigma, matrix=100, **b_args
                )
        else:
            basic = ref

        if not refine:
            final = basic
        else:
            f_args = profile.final_args(radius_final) | nfinal_args | kwargs
            r = f_args["radius"]

            final = basic

            for _ in range(refine):
                if r > 0:
                    final = core.bm3d.VFinal(
                        preclip, final, profile, nsigma, matrix=100, **f_args
                    ).bm3d.VAggregate(r, preclip.format.sample_type)
                else:
                    final = core.bm3d.Final(
                        preclip, final, profile, nsigma, matrix=100, **f_args
                    )

        if 0 in nsigma:
            final = join({p: preclip if s == 0 else final for p, s in zip(range(3), nsigma)}, vs.YUV)

        return core.bm3d.OPP2RGB(final, preclip.format.sample_type)

    assert check_variable(clip, func)

    if clip.format.color_family == vs.RGB:
        if backend == BM3D.Backend.OLD:
            return _bm3d_mawen(clip, pre, ref)

        coefs = list(interleave_arr(matrix_rgb2opp, [0, 0, 0], 3))

        clip_opp = core.fmtc.matrix(clip, coef=coefs, col_fam=vs.YUV, bits=32)
        pre_opp = core.fmtc.matrix(pre, coef=coefs, col_fam=vs.YUV, bits=32) if pre else pre
        ref_opp = core.fmtc.matrix(ref, coef=coefs, col_fam=vs.YUV, bits=32) if ref else ref

        denoised = _bm3d_wolfram(clip_opp, pre_opp, ref_opp)

        denoised = core.fmtc.matrix(
            denoised,
            coef=list(interleave_arr(matrix_opp2rgb, [0, 0, 0], 3)),
            col_fam=vs.RGB,
        )

        return depth(denoised, clip)

    preclip = depth(clip, 32)

    if clip.format.color_family == vs.YUV and {clip.format.subsampling_w, clip.format.subsampling_h} == {0}:
        if backend == BM3D.Backend.OLD:
            point = Point()
            preclip = point.resample(clip, clip.format.replace(color_family=vs.RGB))

            denoised = bm3d(
                preclip,
                sigma,
                (radius_basic, radius_final),
                refine,
                profile,
                pre,
                ref,
                backend,
                basic_args,
                final_args,
                **kwargs,
            )

            return point.resample(denoised, clip, clip)

        denoised = _bm3d_wolfram(preclip, pre, ref, chroma=True)

        return depth(denoised, clip)

    if backend == BM3D.Backend.OLD:
        raise UnsupportedVideoFormatError(
            "When using `BM3D.Backend.OLD`, the input clip must be in YUV444 or RGB format.",
            func, clip.format.color_family
        )

    denoised = _bm3d_wolfram(preclip, pre, ref)

    return depth(denoised, clip)

wnnm

wnnm(
    clip: VideoNode,
    sigma: float | Sequence[float] = 3.0,
    tr: int = 0,
    refine: int = 0,
    ref: VideoNode | None = None,
    merge_factor: float = 0.1,
    self_refine: bool = False,
    planes: PlanesT = None,
    **kwargs: Any
) -> VideoNode

Weighted Nuclear Norm Minimization Denoise algorithm.

Block matching, which is popularized by BM3D, finds similar blocks and then stacks together in a 3-D group. The similarity between these blocks allows details to be preserved during denoising.

In contrast to BM3D, which denoises the 3-D group based on frequency domain filtering, WNNM utilizes weighted nuclear norm minimization, a kind of low rank matrix approximation. Because of this, WNNM exhibits less blocking and ringing artifact compared to BM3D, but the computational complexity is much higher. This stage is called collaborative filtering in BM3D.

For more information, see the WNNM README.

Parameters:

  • clip

    (VideoNode) –

    Clip to process.

  • sigma

    (float | Sequence[float], default: 3.0 ) –

    Strength of denoising, valid range is [0, +inf]. If a float is passed, this strength will be applied to every plane. Values higher than 4.0 are not recommended. Recommended values are [0.35, 1.0]. Default: 3.0.

  • refine

    (int, default: 0 ) –

    The amount of iterations for iterative regularization. Default: 0.

  • tr

    (int, default: 0 ) –

    Temporal radius. To enable spatial-only denoising, set this to 0. Higher values will rapidly increase filtering time and RAM usage. Default: 0.

  • ref

    (VideoNode | None, default: None ) –

    Reference clip. Must be the same dimensions and format as input clip. Default: None.

  • merge_factor

    (float, default: 0.1 ) –

    Merge amount of the last recalculation into the new one when performing iterative regularization.

  • self_refine

    (bool, default: False ) –

    If True, the iterative recalculation step will use the result from the previous iteration as the reference clip ref instead of the original input. Default: False.

  • planes

    (PlanesT, default: None ) –

    Planes to process. If None, all planes. Default: None.

  • kwargs

    (Any, default: {} ) –

    Additional arguments to be passed to the plugin.

Returns:

  • VideoNode

    Denoised clip.

Source code
 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
def wnnm(
    clip: vs.VideoNode,
    sigma: float | Sequence[float] = 3.0,
    tr: int = 0,
    refine: int = 0,
    ref: vs.VideoNode | None = None,
    merge_factor: float = 0.1,
    self_refine: bool = False,
    planes: PlanesT = None,
    **kwargs: Any
) -> vs.VideoNode:
    """
    Weighted Nuclear Norm Minimization Denoise algorithm.

    Block matching, which is popularized by BM3D, finds similar blocks and then stacks together in a 3-D group.
    The similarity between these blocks allows details to be preserved during denoising.

    In contrast to BM3D, which denoises the 3-D group based on frequency domain filtering,
    WNNM utilizes weighted nuclear norm minimization, a kind of low rank matrix approximation.
    Because of this, WNNM exhibits less blocking and ringing artifact compared to BM3D,
    but the computational complexity is much higher. This stage is called collaborative filtering in BM3D.

    For more information, see the [WNNM README](https://github.com/WolframRhodium/VapourSynth-WNNM).

    :param clip:                    Clip to process.
    :param sigma:                   Strength of denoising, valid range is [0, +inf].
                                    If a float is passed, this strength will be applied to every plane.
                                    Values higher than 4.0 are not recommended. Recommended values are [0.35, 1.0].
                                    Default: 3.0.
    :param refine:                  The amount of iterations for iterative regularization.
                                    Default: 0.
    :param tr:                      Temporal radius. To enable spatial-only denoising, set this to 0.
                                    Higher values will rapidly increase filtering time and RAM usage.
                                    Default: 0.
    :param ref:                     Reference clip. Must be the same dimensions and format as input clip.
                                    Default: None.
    :param merge_factor:            Merge amount of the last recalculation into the new one
                                    when performing iterative regularization.
    :param self_refine:             If True, the iterative recalculation step will use the result
                                    from the previous iteration as the reference clip `ref`
                                    instead of the original input.
                                    Default: False.
    :param planes:                  Planes to process. If None, all planes. Default: None.
    :param kwargs:                  Additional arguments to be passed to the plugin.

    :return:                        Denoised clip.
    """

    assert check_progressive(clip, wnnm)

    func = FunctionUtil(clip, wnnm, planes, bitdepth=32)

    sigma = func.norm_seq(sigma, 0)

    if ref is not None:
        ref = depth(ref, 32)
        ref = get_y(ref) if func.luma_only else ref

    denoised = cast(ConstantFormatVideoNode, None)
    dkwargs = KwargsT(radius=tr, rclip=ref) | kwargs

    for i in range(refine + 1):
        if i == 0:
            previous = func.work_clip
        elif i == 1:
            previous = denoised
        else:
            previous = norm_expr(
                [func.work_clip, previous, denoised],
                'x y - {merge_factor} * z +',
                planes,
                merge_factor=merge_factor,
                func=func.func
            )

        if self_refine and denoised:
            dkwargs.update(rclip=denoised)

        denoised = core.wnnm.WNNM(previous, sigma, **dkwargs)

    return denoised