Selur
30th September 2022, 08:23
There are multiple Vapoursynth ports of SpotLess:
One from MysteryX:
# SpotLess denoising method (m1=4) EXPERIMENTAL
def SpotLess(c: vs.VideoNode, radt: int = 1, thsad: int = 10000, thsad2: Optional[int] = None, pel: int = 2, chroma: bool = True, blksize: int = 8,
overlap: Optional[int] = None, truemotion: bool = True, pglobal: bool = True, blur: float = 0.0, ref: Optional[vs.VideoNode] = None) -> vs.VideoNode:
if radt < 1 or radt > 3:
raise ValueError("Spotless: radt must be between 1 and 3")
if not pel in [1, 2, 4]:
raise ValueError("Spotless: pel must be 1, 2 or 4")
thsad2 = thsad2 or thsad
icalc = c.format.bits_per_sample < 32
S = core.mv.Super if icalc else core.mvsf.Super
A = core.mv.Analyse if icalc else core.mvsf.Analyse
C = core.mv.Compensate if icalc else core.mvsf.Compensate
pad = max(blksize, 8)
sup = ref or (c.std.Convolution(matrix=[1, 2, 1, 2, 4, 2, 1, 2, 1]) if blur > 0.0 else c)
sup = S(sup, hpad=pad, vpad=pad, pel=pel, sharp=2)
sup_rend = S(c, hpad=pad, vpad=pad, pel=pel, sharp=2, levels=1) if ref or blur > 0.0 else sup
th1 = thsad
th2 = (thsad + thsad2)/2 if radt==3 else thsad2
th3 = thsad2
bv1 = A(sup, isb=True, delta=1, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
fv1 = A(sup, isb=False, delta=1, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
if radt >= 2:
bv2 = A(sup, isb=True, delta=2, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
fv2 = A(sup, isb=False, delta=2, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
if radt >= 3:
bv3 = A(sup, isb=True, delta=3, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
fv3 = A(sup, isb=False, delta=3, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
bc1 = C(c, sup_rend, bv1, thsad=th1)
fc1 = C(c, sup_rend, fv1, thsad=th1)
if radt >= 2:
bc2 = C(c, sup_rend, bv2, thsad=th2)
fc2 = C(c, sup_rend, fv2, thsad=th2)
if radt >= 3:
bc3 = C(c, sup_rend, bv3, thsad=th3)
fc3 = C(c, sup_rend, fv3, thsad=th3)
ic = core.std.Interleave([bc1, c, fc1]) if radt == 1 else \
core.std.Interleave([bc2, bc1, c, fc1, fc2]) if radt == 2 else \
core.std.Interleave([bc3, bc2, bc1, c, fc1, fc2, fc3])
output = core.tmedian.TemporalMedian(ic, radius=radt)
return output.std.SelectEvery(radt*2+1, radt) # Return middle frame
source: https://forum.doom9.org/showthread.php?p=1955170#post1955170
and one from ChaosCoder:
def SpotLess(clip, chroma=True, rec=False, analyse_args=None, recalculate_args=None):
"""
From: https://forum.doom9.org/showthread.php?p=1402690 by Didée
ChaosKing:
In my experience this filter works very good as a prefilter for SMDegrain().
Filtering only luma seems to help to avoid ghost artifacts.
Args:
chroma (bool) - Whether to process chroma.
rec (bool) - Recalculate the motion vectors to obtain more precision.
"""
# modified from lostfunc: https://github.com/theChaosCoder/lostfunc/blob/v1/lostfunc.py#L10
if not isinstance(clip, vs.VideoNode) or clip.format.color_family not in [vs.GRAY, vs.YUV]:
raise TypeError("SpotLess: This is not a GRAY or YUV clip!")
isFLOAT = clip.format.sample_type == vs.FLOAT
isGRAY = clip.format.color_family == vs.GRAY
chroma = False if isGRAY else chroma
planes = [0, 1, 2] if chroma else [0]
A = core.mvsf.Analyse if isFLOAT else core.mv.Analyse
C = core.mvsf.Compensate if isFLOAT else core.mv.Compensate
S = core.mvsf.Super if isFLOAT else core.mv.Super
R = core.mvsf.Recalculate if isFLOAT else core.mv.Recalculate
bs = 32 if clip.width > 2400 else 16 if clip.width > 960 else 8
pel = 1 if clip.width > 960 else 2
sup = S(clip, pel=pel, sharp=1, rfilter=4)
if analyse_args is None:
analyse_args = dict(blksize=bs, overlap=bs//2, search=5)
if recalculate_args is None:
recalculate_args = dict(blksize=bs//2, overlap=bs//4, search=5)
bv1 = A(sup, isb=True, delta=1, **analyse_args)
fv1 = A(sup, isb=False, delta=1, **analyse_args)
if rec:
bv1 = R(sup, bv1, **recalculate_args)
fv1 = R(sup, fv1, **recalculate_args)
bc1 = C(clip, sup, bv1)
fc1 = C(clip, sup, fv1)
fcb = core.std.Interleave([fc1, clip, bc1])
return fcb.tmedian.TemporalMedian(1, planes)[1::3]
source: https://github.com/Vapoursynth-Plugins-Gitify/G41Fun/blob/c395f098e3d5b7d78a6accfd3f478997653aeca8/G41Fun.py#L3237
I also got another slightly different version, which I use in Hybrid (not sure where it came from originally, I suspect it was based on an older version by ChaosCoder):
import vapoursynth as vs
core = vs.core
#analyse_args= dict(blksize=bs, overlap=bs//2, search=5)
#analyse_args= dict(blksize=bs//2, overlap=bs//4, search=5)
def SpotLess(clip, chroma=True, rec=False, radT=1, ablksz=None, aoverlap=None, asearch=5, pel=None, rblksz=None, roverlap=None, rsearch=None, thsad=10000, thsad2=10000):
"""
Args:
chroma (bool) - Whether to process chroma.
rec (bool) - Recalculate the motion vectors to obtain more precision.
radT (int) - Temporal radius in frames
ablksz - mvtools Analyse blocksize, if not set ablksz = 32 if clip.width > 2400 else 16 if clip.width > 960 else 8, otherwise 4/8/16/32/64
aoverlap - mvtools Analyse overlap, if not set ablksz/2, otherwise 4/8/16/32/64
asearch - mvtools Analyse search, it not set 5, other wise 1-7
search = 0 : 'OneTimeSearch'. searchparam is the step between each vectors tried ( if searchparam is superior to 1, step will be progressively refined ).
search = 1 : 'NStepSearch'. N is set by searchparam. It's the most well known of the MV search algorithm.
search = 2 : Logarithmic search, also named Diamond Search. searchparam is the initial step search, there again, it is refined progressively.
search = 3 : Exhaustive search, searchparam is the radius (square side is 2*radius+1). It is slow, but it gives the best results, SAD-wise.
search = 4 : Hexagon search, searchparam is the range. (similar to x264).
search = 5 : Uneven Multi Hexagon (UMH) search, searchparam is the range. (similar to x264).
search = 6 : pure Horizontal exhaustive search, searchparam is the radius (width is 2*radius+1).
search = 7 : pure Vertical exhaustive search, searchparam is the radius (height is 2*radius+1).
rblksz/roverlap/rsearch, same as axxx but for the recalculation
thsad - mvtools ThSAD is SAD threshold for safe (dummy) compensation. (10000)
If block SAD is above the thSAD, the block is bad, and we use source block instead of the compensated block. Default is 10000 (practically disabled).
"""
# modified from lostfunc: https://github.com/theChaosCoder/lostfunc/blob/v1/lostfunc.py#L10
if not isinstance(clip, vs.VideoNode) or clip.format.color_family not in [vs.GRAY, vs.YUV]:
raise TypeError("SpotLess: This is not a GRAY or YUV clip!")
isFLOAT = clip.format.sample_type == vs.FLOAT
isGRAY = clip.format.color_family == vs.GRAY
chroma = False if isGRAY else chroma
planes = [0, 1, 2] if chroma else [0]
A = core.mvsf.Analyse if isFLOAT else core.mv.Analyse
C = core.mvsf.Compensate if isFLOAT else core.mv.Compensate
S = core.mvsf.Super if isFLOAT else core.mv.Super
R = core.mvsf.Recalculate if isFLOAT else core.mv.Recalculate
if ablksz == None:
ablksz = 32 if clip.width > 2400 else 16 if clip.width > 960 else 8
if aoverlap is None:
aoverlap = ablksz//2
if asearch is None:
asearch = 5
if pel is None:
pel = 1 if clip.width > 960 else 2
if rsearch is None:
rsearch = asearch
if roverlap is None:
roverlap = aoverlap
if rsearch is None:
rsearch = asearch
sup = S(clip, pel=pel, sharp=1, rfilter=4)
bv1 = A(sup, isb=True, delta=radT, blksize=ablksz, overlap=aoverlap, search=asearch)
fv1 = A(sup, isb=False, delta=radT, blksize=ablksz, overlap=aoverlap, search=asearch)
if rec:
bv1 = R(sup, bv1, blksize=rblksz, overlap=roverlap, search=rsearch)
fv1 = R(sup, fv1, blksize=rblksz, overlap=roverlap, search=rsearch)
bc1 = C(clip, sup, bv1, thsad=thsad, thsad2=thsad2)
fc1 = C(clip, sup, fv1, thsad=thsad, thsad2=thsad2)
fcb = core.std.Interleave([fc1, clip, bc1])
return fcb.tmedian.TemporalMedian(1, planes)[1::3]
source: https://github.com/Selur/VapoursynthScriptsInHybrid/blob/master/SpotLess.py
Since they seem to be slightly different, I'm wondering:
a. is there another version, maybe combining them all into one?
b. is one of them better than the other?
Cu Selur
One from MysteryX:
# SpotLess denoising method (m1=4) EXPERIMENTAL
def SpotLess(c: vs.VideoNode, radt: int = 1, thsad: int = 10000, thsad2: Optional[int] = None, pel: int = 2, chroma: bool = True, blksize: int = 8,
overlap: Optional[int] = None, truemotion: bool = True, pglobal: bool = True, blur: float = 0.0, ref: Optional[vs.VideoNode] = None) -> vs.VideoNode:
if radt < 1 or radt > 3:
raise ValueError("Spotless: radt must be between 1 and 3")
if not pel in [1, 2, 4]:
raise ValueError("Spotless: pel must be 1, 2 or 4")
thsad2 = thsad2 or thsad
icalc = c.format.bits_per_sample < 32
S = core.mv.Super if icalc else core.mvsf.Super
A = core.mv.Analyse if icalc else core.mvsf.Analyse
C = core.mv.Compensate if icalc else core.mvsf.Compensate
pad = max(blksize, 8)
sup = ref or (c.std.Convolution(matrix=[1, 2, 1, 2, 4, 2, 1, 2, 1]) if blur > 0.0 else c)
sup = S(sup, hpad=pad, vpad=pad, pel=pel, sharp=2)
sup_rend = S(c, hpad=pad, vpad=pad, pel=pel, sharp=2, levels=1) if ref or blur > 0.0 else sup
th1 = thsad
th2 = (thsad + thsad2)/2 if radt==3 else thsad2
th3 = thsad2
bv1 = A(sup, isb=True, delta=1, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
fv1 = A(sup, isb=False, delta=1, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
if radt >= 2:
bv2 = A(sup, isb=True, delta=2, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
fv2 = A(sup, isb=False, delta=2, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
if radt >= 3:
bv3 = A(sup, isb=True, delta=3, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
fv3 = A(sup, isb=False, delta=3, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
bc1 = C(c, sup_rend, bv1, thsad=th1)
fc1 = C(c, sup_rend, fv1, thsad=th1)
if radt >= 2:
bc2 = C(c, sup_rend, bv2, thsad=th2)
fc2 = C(c, sup_rend, fv2, thsad=th2)
if radt >= 3:
bc3 = C(c, sup_rend, bv3, thsad=th3)
fc3 = C(c, sup_rend, fv3, thsad=th3)
ic = core.std.Interleave([bc1, c, fc1]) if radt == 1 else \
core.std.Interleave([bc2, bc1, c, fc1, fc2]) if radt == 2 else \
core.std.Interleave([bc3, bc2, bc1, c, fc1, fc2, fc3])
output = core.tmedian.TemporalMedian(ic, radius=radt)
return output.std.SelectEvery(radt*2+1, radt) # Return middle frame
source: https://forum.doom9.org/showthread.php?p=1955170#post1955170
and one from ChaosCoder:
def SpotLess(clip, chroma=True, rec=False, analyse_args=None, recalculate_args=None):
"""
From: https://forum.doom9.org/showthread.php?p=1402690 by Didée
ChaosKing:
In my experience this filter works very good as a prefilter for SMDegrain().
Filtering only luma seems to help to avoid ghost artifacts.
Args:
chroma (bool) - Whether to process chroma.
rec (bool) - Recalculate the motion vectors to obtain more precision.
"""
# modified from lostfunc: https://github.com/theChaosCoder/lostfunc/blob/v1/lostfunc.py#L10
if not isinstance(clip, vs.VideoNode) or clip.format.color_family not in [vs.GRAY, vs.YUV]:
raise TypeError("SpotLess: This is not a GRAY or YUV clip!")
isFLOAT = clip.format.sample_type == vs.FLOAT
isGRAY = clip.format.color_family == vs.GRAY
chroma = False if isGRAY else chroma
planes = [0, 1, 2] if chroma else [0]
A = core.mvsf.Analyse if isFLOAT else core.mv.Analyse
C = core.mvsf.Compensate if isFLOAT else core.mv.Compensate
S = core.mvsf.Super if isFLOAT else core.mv.Super
R = core.mvsf.Recalculate if isFLOAT else core.mv.Recalculate
bs = 32 if clip.width > 2400 else 16 if clip.width > 960 else 8
pel = 1 if clip.width > 960 else 2
sup = S(clip, pel=pel, sharp=1, rfilter=4)
if analyse_args is None:
analyse_args = dict(blksize=bs, overlap=bs//2, search=5)
if recalculate_args is None:
recalculate_args = dict(blksize=bs//2, overlap=bs//4, search=5)
bv1 = A(sup, isb=True, delta=1, **analyse_args)
fv1 = A(sup, isb=False, delta=1, **analyse_args)
if rec:
bv1 = R(sup, bv1, **recalculate_args)
fv1 = R(sup, fv1, **recalculate_args)
bc1 = C(clip, sup, bv1)
fc1 = C(clip, sup, fv1)
fcb = core.std.Interleave([fc1, clip, bc1])
return fcb.tmedian.TemporalMedian(1, planes)[1::3]
source: https://github.com/Vapoursynth-Plugins-Gitify/G41Fun/blob/c395f098e3d5b7d78a6accfd3f478997653aeca8/G41Fun.py#L3237
I also got another slightly different version, which I use in Hybrid (not sure where it came from originally, I suspect it was based on an older version by ChaosCoder):
import vapoursynth as vs
core = vs.core
#analyse_args= dict(blksize=bs, overlap=bs//2, search=5)
#analyse_args= dict(blksize=bs//2, overlap=bs//4, search=5)
def SpotLess(clip, chroma=True, rec=False, radT=1, ablksz=None, aoverlap=None, asearch=5, pel=None, rblksz=None, roverlap=None, rsearch=None, thsad=10000, thsad2=10000):
"""
Args:
chroma (bool) - Whether to process chroma.
rec (bool) - Recalculate the motion vectors to obtain more precision.
radT (int) - Temporal radius in frames
ablksz - mvtools Analyse blocksize, if not set ablksz = 32 if clip.width > 2400 else 16 if clip.width > 960 else 8, otherwise 4/8/16/32/64
aoverlap - mvtools Analyse overlap, if not set ablksz/2, otherwise 4/8/16/32/64
asearch - mvtools Analyse search, it not set 5, other wise 1-7
search = 0 : 'OneTimeSearch'. searchparam is the step between each vectors tried ( if searchparam is superior to 1, step will be progressively refined ).
search = 1 : 'NStepSearch'. N is set by searchparam. It's the most well known of the MV search algorithm.
search = 2 : Logarithmic search, also named Diamond Search. searchparam is the initial step search, there again, it is refined progressively.
search = 3 : Exhaustive search, searchparam is the radius (square side is 2*radius+1). It is slow, but it gives the best results, SAD-wise.
search = 4 : Hexagon search, searchparam is the range. (similar to x264).
search = 5 : Uneven Multi Hexagon (UMH) search, searchparam is the range. (similar to x264).
search = 6 : pure Horizontal exhaustive search, searchparam is the radius (width is 2*radius+1).
search = 7 : pure Vertical exhaustive search, searchparam is the radius (height is 2*radius+1).
rblksz/roverlap/rsearch, same as axxx but for the recalculation
thsad - mvtools ThSAD is SAD threshold for safe (dummy) compensation. (10000)
If block SAD is above the thSAD, the block is bad, and we use source block instead of the compensated block. Default is 10000 (practically disabled).
"""
# modified from lostfunc: https://github.com/theChaosCoder/lostfunc/blob/v1/lostfunc.py#L10
if not isinstance(clip, vs.VideoNode) or clip.format.color_family not in [vs.GRAY, vs.YUV]:
raise TypeError("SpotLess: This is not a GRAY or YUV clip!")
isFLOAT = clip.format.sample_type == vs.FLOAT
isGRAY = clip.format.color_family == vs.GRAY
chroma = False if isGRAY else chroma
planes = [0, 1, 2] if chroma else [0]
A = core.mvsf.Analyse if isFLOAT else core.mv.Analyse
C = core.mvsf.Compensate if isFLOAT else core.mv.Compensate
S = core.mvsf.Super if isFLOAT else core.mv.Super
R = core.mvsf.Recalculate if isFLOAT else core.mv.Recalculate
if ablksz == None:
ablksz = 32 if clip.width > 2400 else 16 if clip.width > 960 else 8
if aoverlap is None:
aoverlap = ablksz//2
if asearch is None:
asearch = 5
if pel is None:
pel = 1 if clip.width > 960 else 2
if rsearch is None:
rsearch = asearch
if roverlap is None:
roverlap = aoverlap
if rsearch is None:
rsearch = asearch
sup = S(clip, pel=pel, sharp=1, rfilter=4)
bv1 = A(sup, isb=True, delta=radT, blksize=ablksz, overlap=aoverlap, search=asearch)
fv1 = A(sup, isb=False, delta=radT, blksize=ablksz, overlap=aoverlap, search=asearch)
if rec:
bv1 = R(sup, bv1, blksize=rblksz, overlap=roverlap, search=rsearch)
fv1 = R(sup, fv1, blksize=rblksz, overlap=roverlap, search=rsearch)
bc1 = C(clip, sup, bv1, thsad=thsad, thsad2=thsad2)
fc1 = C(clip, sup, fv1, thsad=thsad, thsad2=thsad2)
fcb = core.std.Interleave([fc1, clip, bc1])
return fcb.tmedian.TemporalMedian(1, planes)[1::3]
source: https://github.com/Selur/VapoursynthScriptsInHybrid/blob/master/SpotLess.py
Since they seem to be slightly different, I'm wondering:
a. is there another version, maybe combining them all into one?
b. is one of them better than the other?
Cu Selur