Skip to content

alpha

This module implements functions based on the famous dehalo_alpha.

Functions:

  • dehalo_alpha

    Reduce halo artifacts by aggressively processing the edges and their surroundings.

IterArr

IterArr = T | list[T] | tuple[T | list[T], ...]

dehalo_alpha

dehalo_alpha(
    clip: VideoNode,
    blur: IterArr[float]
    | VSFunctionPlanesArgs
    | tuple[float | list[float] | VSFunctionPlanesArgs, ...] = GAUSS(sigma=1.4),
    lowsens: IterArr[float] = 50.0,
    highsens: IterArr[float] = 50.0,
    ss: float | tuple[float, ...] = 1.5,
    darkstr: IterArr[float] = 0.0,
    brightstr: IterArr[float] = 1.0,
    planes: Planes = 0,
    attach_masks: bool = False,
    func: FuncExcept | None = None,
    **kwargs: Any,
) -> VideoNode

Reduce halo artifacts by aggressively processing the edges and their surroundings.

The parameter ss can be configured per iteration while blur, lowsens, highsens, darkstr and brightstr can be configured per plane and per iteration. You can specify:

  • A single value: applies to all iterations and all planes.
  • A tuple of values: interpreted as iteration-wise.
  • A list inside the tuple: interpreted as per-plane for a specific iteration.
For example

blur=(1.4, [1.4, 1.65], [1.5, 1.4, 1.45]) implies 3 iterations:

  • 1st: 1.4 for all planes
  • 2nd: 1.4 for luma, 1.65 for both chroma planes
  • 3rd: 1.5 for luma, 1.4 for U, 1.45 for V

Parameters:

  • clip

    (VideoNode) –

    Source clip.

  • blur

    (IterArr[float] | VSFunctionPlanesArgs | tuple[float | list[float] | VSFunctionPlanesArgs, ...], default: GAUSS(sigma=1.4) ) –

    Standard deviation of the Gaussian kernel if float or custom blurring function to use in place of the default implementation.

  • lowsens

    (IterArr[float], default: 50.0 ) –

    Lower sensitivity threshold — dehalo is fully applied below this value. Setting both lowsens and highsens to -1 disables mask-based processing entirely.

  • highsens

    (IterArr[float], default: 50.0 ) –

    Upper sensitivity threshold — dehalo is completely skipped above this value. Setting both lowsens and highsens to -1 disables mask-based processing entirely.

  • ss

    (float | tuple[float, ...], default: 1.5 ) –

    Supersampling factor to reduce aliasing artifacts.

  • darkstr

    (IterArr[float], default: 0.0 ) –

    Strength factor for suppressing dark halos.

  • brightstr

    (IterArr[float], default: 1.0 ) –

    Strength factor for suppressing bright halos.

  • planes

    (Planes, default: 0 ) –

    Planes to process. Default to 0.

  • attach_masks

    (bool, default: False ) –

    Stores generated masks as frame properties in the output clip. The prop name is DehaloAlphaMask_{i}, where i is the iteration index.

  • func

    (FuncExcept | None, default: None ) –

    An optional function to use for error handling.

  • **kwargs

    (Any, default: {} ) –

    Additional advanced parameters.

Raises:

  • CustomIndexError

    If lowsens or highsens are not between 0 and 100 (inclusive).

Returns:

  • VideoNode

    Dehaloed clip.

Source code in vsdehalo/alpha.py
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
def dehalo_alpha(
    clip: vs.VideoNode,
    # Blur param
    blur: IterArr[float]
    | VSFunctionPlanesArgs
    | tuple[float | list[float] | VSFunctionPlanesArgs, ...] = Prefilter.GAUSS(sigma=1.4),
    # Mask params
    lowsens: IterArr[float] = 50.0,
    highsens: IterArr[float] = 50.0,
    # Supersampling clamp params
    ss: float | tuple[float, ...] = 1.5,
    # Limiting params
    darkstr: IterArr[float] = 0.0,
    brightstr: IterArr[float] = 1.0,
    # Misc params
    planes: Planes = 0,
    attach_masks: bool = False,
    func: FuncExcept | None = None,
    **kwargs: Any,
) -> vs.VideoNode:
    """
    Reduce halo artifacts by aggressively processing the edges and their surroundings.

    The parameter `ss` can be configured per iteration while `blur`, `lowsens`, `highsens`, `darkstr` and `brightstr`
    can be configured per plane and per iteration. You can specify:

    - A single value: applies to all iterations and all planes.
    - A tuple of values: interpreted as iteration-wise.
    - A list inside the tuple: interpreted as per-plane for a specific iteration.

    For example:
        `blur=(1.4, [1.4, 1.65], [1.5, 1.4, 1.45])` implies 3 iterations:

        - 1st: 1.4 for all planes
        - 2nd: 1.4 for luma, 1.65 for both chroma planes
        - 3rd: 1.5 for luma, 1.4 for U, 1.45 for V

    Args:
        clip: Source clip.
        blur: Standard deviation of the Gaussian kernel if float or custom blurring function
            to use in place of the default implementation.
        lowsens: Lower sensitivity threshold — dehalo is fully applied below this value.
            Setting both `lowsens` and `highsens` to `-1` disables mask-based processing entirely.
        highsens: Upper sensitivity threshold — dehalo is completely skipped above this value.
            Setting both `lowsens` and `highsens` to `-1` disables mask-based processing entirely.
        ss: Supersampling factor to reduce aliasing artifacts.
        darkstr: Strength factor for suppressing dark halos.
        brightstr: Strength factor for suppressing bright halos.
        planes: Planes to process. Default to 0.
        attach_masks: Stores generated masks as frame properties in the output clip.
            The prop name is `DehaloAlphaMask_{i}`, where `i` is the iteration index.
        func: An optional function to use for error handling.
        **kwargs: Additional advanced parameters.

    Raises:
        CustomIndexError: If `lowsens` or `highsens` are not between 0 and 100 (inclusive).

    Returns:
        Dehaloed clip.
    """
    util = FunctionUtil(clip, func or dehalo_alpha, planes)

    assert check_progressive(clip, util.func)

    values = _normalize_iter_arr_t(
        blur,
        lowsens,
        highsens,
        ss,
        darkstr,
        brightstr,
        kwargs.get("supersampler", Lanczos()),
        kwargs.get("supersampler_ref", Mitchell()),
    )
    masks_to_prop = list[vs.VideoNode]()

    work_clip = util.work_clip

    for (
        blur_i,
        lowsens_i,
        highsens_i,
        (ss_i, *_),
        darkstr_i,
        brightstr_i,
        (sser_i, *_),
        (sser_ref_i, *_),
    ) in values:
        # Applying the blur function
        dehalo = (
            blur_i[0](work_clip, planes=planes)
            if _is_callable(blur_i[0])
            else gauss_blur(work_clip, blur_i, planes=planes)
        )

        # Building the mask
        if all(0 <= x <= 100 for x in (*lowsens_i, *highsens_i)):
            mask = norm_expr(
                [
                    Morpho.gradient(work_clip, planes=planes, func=util.func),
                    Morpho.gradient(dehalo, planes=planes, func=util.func),
                ],
                "x 0 = x y - dup x / ? mask_max * {lowsens} - x range_size + range_size 2 * / {highsens} + *",
                planes,
                func=util.func,
                lowsens=(scale_delta(x, 8, clip) for x in lowsens_i),
                highsens=(x / 100 for x in highsens_i),
            )

            if attach_masks:
                masks_to_prop.append(core.std.SetFrameProps(mask, lowsens=lowsens_i, highsens=highsens_i))

            dehalo = core.std.MaskedMerge(
                dehalo, work_clip, limiter(mask, mask=True, planes=planes, func=util.func), planes
            )

        elif lowsens_i.count(-1) == len(lowsens_i) and highsens_i.count(-1) == len(highsens_i):
            pass
        else:
            raise CustomIndexError("lowsens and highsens must be between 0 and 100!", func)

        # Clamping with supersampling clips to reduce aliasing
        if ss_i == 1:
            dehalo = repair.Mode.MINMAX_SQUARE1(work_clip, dehalo, planes)
        else:
            ss_width = mod_x(work_clip.width * ss_i, 2**work_clip.format.subsampling_w)
            ss_height = mod_x(work_clip.height * ss_i, 2**work_clip.format.subsampling_h)

            sser_i = Scaler.ensure_obj(sser_i)
            sser_ref_i = Scaler.ensure_obj(sser_ref_i)

            clip_ss = sser_i.scale(work_clip, ss_width, ss_height)
            inpand = sser_ref_i.scale(Morpho.minimum(dehalo, planes=planes), ss_width, ss_height)
            expand = sser_ref_i.scale(Morpho.maximum(dehalo, planes=planes), ss_width, ss_height)
            dehalo = sser_i.scale(
                ExprOp.CLAMP(clip_ss, inpand, expand, planes=planes, func=util.func),
                work_clip.width,
                work_clip.height,
            )

        # Limiting the dehalo clip to control the bright and dark halos
        work_clip = dehalo = norm_expr(
            [work_clip, dehalo],
            "x x y - dup {brightstr} * dup1 {darkstr} * ? -",
            planes,
            func=util.func,
            darkstr=darkstr_i,
            brightstr=brightstr_i,
        )

    out = util.return_clip(dehalo)  # pyright: ignore[reportPossiblyUnboundVariable]

    for i, mask in enumerate(masks_to_prop):
        out = out.std.ClipToProp(mask, f"DehaloAlphaMask_{i}")

    return out