Skip to content

spat_funcs

Functions:

  • adg_mask

    Generates an adaptive grain mask based on each frame's average luma and pixel value.

  • flat_mask
  • retinex

    Multi-Scale Retinex (MSR) implementation for dynamic range and contrast enhancement.

  • texture_mask

adg_mask

adg_mask(
    clip: VideoNode,
    luma_scaling: float = 8.0,
    relative: bool = False,
    func: FuncExceptT | None = None,
) -> ConstantFormatVideoNode
adg_mask(
    clip: VideoNode,
    luma_scaling: Sequence[float] = ...,
    relative: bool = False,
    func: FuncExceptT | None = None,
) -> list[ConstantFormatVideoNode]
adg_mask(
    clip: VideoNode,
    luma_scaling: float | Sequence[float] = 8.0,
    relative: bool = False,
    func: FuncExceptT | None = None,
) -> ConstantFormatVideoNode | list[ConstantFormatVideoNode]

Generates an adaptive grain mask based on each frame's average luma and pixel value.

This function is primarily used to create masks for adaptive grain applications but can be used in other scenarios requiring luminance-aware masking.

Parameters:

  • clip

    (VideoNode) –

    The clip to process.

  • luma_scaling

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

    Controls the strength of the adaptive mask. Can be a single float or a sequence of floats. Default is 8.0. Negative values invert the mask behavior.

  • relative

    (bool, default: False ) –

    Enables relative computation based on pixel-to-average luminance ratios. Requires the akarin plugin. If True without akarin, an error is raised.

  • func

    (FuncExceptT | None, default: None ) –

    Function returned for custom error handling. This should only be set by VS package developers.

Returns:

  • ConstantFormatVideoNode | list[ConstantFormatVideoNode]

    A single mask or a list of masks (if luma_scaling is a sequence), corresponding to the input clip.

Raises:

  • CustomRuntimeError

    If the akarin plugin is not available and either relative is True or the clip is in fp16 format.

Source code
 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
def adg_mask(
    clip: vs.VideoNode,
    luma_scaling: float | Sequence[float] = 8.0,
    relative: bool = False,
    func: FuncExceptT | None = None
) -> ConstantFormatVideoNode | list[ConstantFormatVideoNode]:
    """
    Generates an adaptive grain mask based on each frame's average luma and pixel value.

    This function is primarily used to create masks for adaptive grain applications but can be used
    in other scenarios requiring luminance-aware masking.

    :param clip:                The clip to process.
    :param luma_scaling:        Controls the strength of the adaptive mask. Can be a single float or a sequence of floats.
                                Default is 8.0. Negative values invert the mask behavior.
    :param relative:            Enables relative computation based on pixel-to-average luminance ratios.
                                Requires the akarin plugin. If True without akarin, an error is raised.
    :param func:                Function returned for custom error handling.
                                This should only be set by VS package developers.
    :raises CustomRuntimeError: If the akarin plugin is not available and either `relative` is True
                                or the clip is in fp16 format.
    :return:                    A single mask or a list of masks (if `luma_scaling` is a sequence),
                                corresponding to the input clip.
    """
    func = func or adg_mask

    assert check_variable(clip, func)

    use_complex = complexpr_available and clip.format.bits_per_sample > 16 or relative

    luma, prop = plane(clip, 0), 'P' if use_complex else None
    y, y_inv = luma.std.PlaneStats(prop=prop), luma.std.Invert().std.PlaneStats(prop=prop)

    if not use_complex and relative:
        raise CustomRuntimeError(
            "You don't have akarin plugin, you can't use this function!", func, 'relative=True'
        )

    if use_complex:
        peak = get_peak_value(y)

        is_integer = y.format.sample_type == vs.INTEGER

        x_string, aft_int = (f'x {peak} / ', f' {peak} * 0.5 +') if is_integer else ('x ', '0 1 clamp')

        if relative:
            x_string += 'Y! Y@ 0.5 < x.PMin 0 max 0.5 / log Y@ * x.PMax 1.0 min 0.5 / log Y@ * ? '

        x_string += '0 0.999 clamp X!'

        def _adgfunc(luma: ConstantFormatVideoNode, ls: float) -> ConstantFormatVideoNode:
            return norm_expr(
                luma, f'{x_string} 1 X@ X@ X@ X@ X@ '
                '18.188 * 45.47 - * 36.624 + * 9.466 - * 1.124 + * - '
                f'x.PAverage 2 pow {ls} * pow {aft_int}',
                func=func
            )
    else:
        def _adgfunc(luma: ConstantFormatVideoNode, ls: float) -> ConstantFormatVideoNode:
            return luma.adg.Mask(ls)

    scaled_clips = [_adgfunc(y_inv if ls < 0 else y, abs(ls)) for ls in to_arr(luma_scaling)]

    if isinstance(luma_scaling, Sequence):
        return scaled_clips

    return scaled_clips[0]

flat_mask

flat_mask(
    src: VideoNode, radius: int = 5, thr: float = 0.011, gauss: bool = False
) -> ConstantFormatVideoNode
Source code
178
179
180
181
182
183
184
185
186
187
def flat_mask(src: vs.VideoNode, radius: int = 5, thr: float = 0.011, gauss: bool = False) -> ConstantFormatVideoNode:
    luma = get_y(src)

    blur = gauss_blur(luma, radius * 0.361083333) if gauss else box_blur(luma, radius)

    blur, mask = depth(blur, 8), depth(luma, 8)

    mask = mask.vszip.AdaptiveBinarize(blur, int(scale_value(thr, 32, blur)))

    return depth(mask, luma, dither_type=DitherType.NONE, range_in=ColorRange.FULL, range_out=ColorRange.FULL)

retinex

retinex(
    clip: VideoNode,
    sigma: Sequence[float] = [25, 80, 250],
    lower_thr: float = 0.001,
    upper_thr: float = 0.001,
    fast: bool = True,
    func: FuncExceptT | None = None,
) -> ConstantFormatVideoNode

Multi-Scale Retinex (MSR) implementation for dynamic range and contrast enhancement.

More information here.

Parameters:

  • clip

    (VideoNode) –

    Input video clip.

  • sigma

    (Sequence[float], default: [25, 80, 250] ) –

    List of Gaussian sigmas for MSR. Using 3 scales (e.g., [25, 80, 250]) balances speed and quality.

  • lower_thr

    (float, default: 0.001 ) –

    Lower threshold percentile for output normalization (0–1, exclusive). Affects shadow contrast.

  • upper_thr

    (float, default: 0.001 ) –

    Upper threshold percentile for output normalization (0–1, exclusive). Affects highlight compression.

  • fast

    (bool, default: True ) –

    Enables fast mode using downscaled approximation and simplifications. Default is True.

  • func

    (FuncExceptT | None, default: None ) –

    Function returned for custom error handling. This should only be set by VS package developers.

Returns:

  • ConstantFormatVideoNode

    Processed luma-enhanced clip.

Source code
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
@limiter
def retinex(
    clip: vs.VideoNode,
    sigma: Sequence[float] = [25, 80, 250],
    lower_thr: float = 0.001,
    upper_thr: float = 0.001,
    fast: bool = True,
    func: FuncExceptT | None = None
) -> ConstantFormatVideoNode:
    """
    Multi-Scale Retinex (MSR) implementation for dynamic range and contrast enhancement.

    More information [here](https://github.com/HomeOfVapourSynthEvolution/VapourSynth-Retinex).

    :param clip:        Input video clip.
    :param sigma:       List of Gaussian sigmas for MSR. Using 3 scales (e.g., [25, 80, 250]) balances speed and quality.
    :param lower_thr:   Lower threshold percentile for output normalization (0–1, exclusive). Affects shadow contrast.
    :param upper_thr:   Upper threshold percentile for output normalization (0–1, exclusive). Affects highlight compression.
    :param fast:        Enables fast mode using downscaled approximation and simplifications. Default is True.
    :param func:        Function returned for custom error handling.
                        This should only be set by VS package developers.
    :return:            Processed luma-enhanced clip.
    """
    func = func or retinex

    assert check_variable(clip, func)

    sigma = sorted(sigma)

    y = get_y(clip)

    y = y.std.PlaneStats()
    is_float = get_sample_type(y) is vs.FLOAT

    if is_float:
        luma_float = norm_expr(y, "x x.PlaneStatsMin - x.PlaneStatsMax x.PlaneStatsMin - /", func=func)
    else:
        luma_float = norm_expr(
            y, "1 x.PlaneStatsMax x.PlaneStatsMin - / x x.PlaneStatsMin - *", None, vs.GRAYS, func=func
        )

    slen, slenm = len(sigma), len(sigma) - 1

    expr_msr = StrList([
        f"{x} 0 <= 1 x {x} / 1 + ? "
        for x in ExprVars(1, slen + (not fast))
    ])

    if fast:
        expr_msr.append("x.PlaneStatsMax 0 <= 1 x x.PlaneStatsMax / 1 + ? ")
        sigma = sigma[:-1]

    expr_msr.extend(ExprOp.ADD * slenm)
    expr_msr.append(f"log {slen} /")

    msr = norm_expr([luma_float, *(gauss_blur(luma_float, i, _fast=fast) for i in sigma)], expr_msr, func=func)

    msr_stats = msr.vszip.PlaneMinMax(lower_thr, upper_thr)

    expr_balance = "x x.psmMin - x.psmMax x.psmMin - /"

    if not is_float:
        expr_balance = f"{expr_balance} {{ymax}} {{ymin}} - * {{ymin}} + round {{ymin}} {{ymax}} clamp"

    return norm_expr(
        msr_stats, expr_balance, None, y,
        ymin=get_lowest_value(y, False),
        ymax=get_peak_value(y, False),
        func=func
    )

texture_mask

texture_mask(
    clip: VideoNode,
    rady: int = 2,
    radc: int | None = None,
    blur: int | float = 8,
    thr: float = 0.2,
    stages: list[tuple[int, int]] = [(60, 2), (40, 4), (20, 2)],
    points: list[tuple[bool, float]] = [
        (False, 1.75),
        (True, 2.5),
        (True, 5),
        (False, 10),
    ],
) -> ConstantFormatVideoNode
Source code
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
def texture_mask(
    clip: vs.VideoNode, rady: int = 2, radc: int | None = None,
    blur: int | float = 8, thr: float = 0.2,
    stages: list[tuple[int, int]] = [(60, 2), (40, 4), (20, 2)],
    points: list[tuple[bool, float]] = [(False, 1.75), (True, 2.5), (True, 5), (False, 10)]
) -> ConstantFormatVideoNode:
    levels = [x for x, _ in points]
    _points = [scale_value(x, 8, clip) for _, x in points]
    thr = scale_value(thr, 8, 32, ColorRange.FULL)

    qm, peak = len(points), get_peak_value(clip)

    rmask = MinMax(rady, fallback(radc, rady)).edgemask(clip, lthr=0)

    emask = clip.std.Prewitt()

    rm_txt = ExprOp.MIN(rmask, (
        Morpho.minimum(Morpho.binarize(emask, thr, 1.0, 0), iterations=it)
        for thr, it in stages
    ))

    expr = [f'x {_points[0]} < x {_points[-1]} > or 0']

    for x in range(len(_points) - 1):
        if _points[x + 1] < _points[-1]:
            expr.append(f'x {_points[x + 1]} <=')

        if levels[x] == levels[x + 1]:
            expr.append(f'{peak if levels[x] else 0}')
        else:
            mean = peak * (levels[x + 1] - levels[x]) / (_points[x + 1] - _points[x])
            expr.append(f'x {_points[x]} - {mean} * {peak * levels[x]} +')

    weighted = norm_expr(rm_txt, [expr, ExprOp.TERN * (qm - 1)], func=texture_mask)

    if isinstance(blur, float):
        weighted = gauss_blur(weighted, blur)
    else:
        weighted = box_blur(weighted, blur)

    return norm_expr(weighted, f'x {peak * thr} - {1 / (1 - thr)} *', func=texture_mask)