Skip to content

mask

Functions:

descale_detail_mask

descale_detail_mask(
    clip: VideoNode,
    rescaled: VideoNode,
    thr: float = 0.05,
    inflate: int = 2,
    xxpand: tuple[int, int] = (4, 0),
) -> ConstantFormatVideoNode

Mask non-native resolution detail to prevent detail loss and artifacting.

Descaling without masking is very dangerous, as descaling FHD material often leads to heavy artifacting and fine detail loss.

Parameters:

  • clip

    (VideoNode) –

    Original clip.

  • rescaled

    (VideoNode) –

    Clip rescaled using the presumed native kernel.

  • thr

    (float, default: 0.05 ) –

    Binarizing threshold. Lower will catch more. Assumes float bitdepth input. Default: 0.05.

  • inflate

    (int, default: 2 ) –

    Amount of times to inflate the mask. Default: 2.

  • xxpand

    (tuple[int, int], default: (4, 0) ) –

    Amount of times to Maximum the clip by. The first Maximum is done before inflating, the second after. Default: 4 times pre-inflating, 0 times post-inflating.

Returns:

  • ConstantFormatVideoNode

    Mask containing all the native FHD detail.

Source code in vsscale/mask.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@limiter
def descale_detail_mask(
    clip: vs.VideoNode, rescaled: vs.VideoNode, thr: float = 0.05, inflate: int = 2, xxpand: tuple[int, int] = (4, 0)
) -> ConstantFormatVideoNode:
    """
    Mask non-native resolution detail to prevent detail loss and artifacting.

    Descaling without masking is very dangerous, as descaling FHD material often leads to
    heavy artifacting and fine detail loss.

    Args:
        clip: Original clip.
        rescaled: Clip rescaled using the presumed native kernel.
        thr: Binarizing threshold. Lower will catch more. Assumes float bitdepth input. Default: 0.05.
        inflate: Amount of times to ``inflate`` the mask. Default: 2.
        xxpand: Amount of times to ``Maximum`` the clip by. The first ``Maximum`` is done before inflating, the second
            after. Default: 4 times pre-inflating, 0 times post-inflating.

    Returns:
        Mask containing all the native FHD detail.
    """
    mask = norm_expr([get_y(clip), get_y(rescaled)], "x y - abs", func=descale_detail_mask)

    mask = Morpho.binarize(mask, thr)

    if xxpand[0]:
        mask = iterate(mask, core.std.Maximum if xxpand[0] > 0 else core.std.Minimum, xxpand[0])

    if inflate:
        mask = iterate(mask, core.std.Inflate, inflate)

    if xxpand[1]:
        mask = iterate(mask, core.std.Maximum if xxpand[1] > 0 else core.std.Minimum, xxpand[1])

    return mask

descale_error_mask

descale_error_mask(
    clip: VideoNode,
    rescaled: VideoNode,
    thr: float | list[float] = 0.038,
    expands: int | tuple[int, int, int] = (2, 2, 3),
    blur: int | float = 3,
    bwbias: int = 1,
    tr: int = 0,
) -> ConstantFormatVideoNode

Create an error mask from the original and rescaled clip.

Parameters:

  • clip

    (VideoNode) –

    Original clip.

  • rescaled

    (VideoNode) –

    Rescaled clip.

  • thr

    (float | list[float], default: 0.038 ) –

    Threshold of the minimum difference.

  • expands

    (int | tuple[int, int, int], default: (2, 2, 3) ) –

    Iterations of mask expand at each step (diff, expand, binarize).

  • blur

    (int | float, default: 3 ) –

    How much to blur the clip. If int, it will be a box_blur, else gauss_blur.

  • bwbias

    (int, default: 1 ) –

    Calculate a bias with the clip's chroma.

  • tr

    (int, default: 0 ) –

    Make the error mask temporally stable with a temporal radius.

Returns:

  • ConstantFormatVideoNode

    Descale error mask.

Source code in vsscale/mask.py
 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
@limiter
def descale_error_mask(
    clip: vs.VideoNode,
    rescaled: vs.VideoNode,
    thr: float | list[float] = 0.038,
    expands: int | tuple[int, int, int] = (2, 2, 3),
    blur: int | float = 3,
    bwbias: int = 1,
    tr: int = 0,
) -> ConstantFormatVideoNode:
    """
    Create an error mask from the original and rescaled clip.

    Args:
        clip: Original clip.
        rescaled: Rescaled clip.
        thr: Threshold of the minimum difference.
        expands: Iterations of mask expand at each step (diff, expand, binarize).
        blur: How much to blur the clip. If int, it will be a box_blur, else gauss_blur.
        bwbias: Calculate a bias with the clip's chroma.
        tr: Make the error mask temporally stable with a temporal radius.

    Returns:
        Descale error mask.
    """
    y, *chroma = split(clip)

    error = norm_expr([y, rescaled], "x y - abs", func=descale_error_mask)

    if bwbias > 1 and chroma:
        chroma_abs = norm_expr(chroma, "x neutral - abs y neutral - abs max")
        chroma_abs = core.resize.Bicubic(chroma_abs, y.width, y.height)

        bias = norm_expr([y, chroma_abs], f"x ymax >= x ymin <= or y not and {bwbias} 1 ?", func=descale_error_mask)
        bias = Morpho.expand(bias, 2, func=descale_error_mask)

        error = ExprOp.MUL(error, bias)

    if isinstance(expands, int):
        exp1 = exp2 = exp3 = expands
    else:
        exp1, exp2, exp3 = expands

    if exp1:
        error = Morpho.expand(error, exp1, func=descale_error_mask)

    if exp2:
        error = Morpho.expand(error, exp2, mode=XxpandMode.ELLIPSE, func=descale_error_mask)

    thrs = [thr] if isinstance(thr, (float, int)) else thr

    error = Morpho.binarize(error, thrs[0])

    for scaled_thr in thrs[1:]:
        bin2 = Morpho.binarize(error, scaled_thr)
        error = bin2.hysteresis.Hysteresis(error)

    if exp3:
        error = Morpho.expand(error, exp2, mode=XxpandMode.ELLIPSE, func=descale_error_mask)

    if tr:
        avg = Morpho.binarize(BlurMatrix.MEAN(taps=tr, mode=ConvMode.TEMPORAL)(error), 0.5)

        error = ExprOp.MIN(error, ExprOp.MAX(shift_clip_multi(ExprOp.MIN(error, avg), (-tr, tr))))

    error = box_blur(error, blur) if isinstance(blur, int) else gauss_blur(error, blur)

    return error