Welcome to Doom9's Forum, THE in-place to be for everyone interested in DVD conversion.

Before you start posting please read the forum rules. By posting to this forum you agree to abide by the rules.

 

Go Back   Doom9's Forum > Capturing and Editing Video > VapourSynth

Reply
 
Thread Tools Search this Thread Display Modes
Old 30th September 2022, 08:23   #1  |  Link
Selur
Registered User
 
Selur's Avatar
 
Join Date: Oct 2001
Location: Germany
Posts: 7,259
SpotLess in Vapoursynth

There are multiple Vapoursynth ports of SpotLess:

One from MysteryX:
Code:
# 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.p...70#post1955170

and one from ChaosCoder:
Code:
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-Plugi...41Fun.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):
Code:
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/Vapoursynth...er/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
__________________
Hybrid here in the forum, homepage
Selur is offline   Reply With Quote
Old 30th September 2022, 08:51   #2  |  Link
ChaosKing
Registered User
 
Join Date: Dec 2005
Location: Germany
Posts: 1,795
I belive the SpotLess() function in lostfunc is actually KillerSpots(). Not sure why I named it spotless ^^"
This would explain the difference.
__________________
AVSRepoGUI // VSRepoGUI - Package Manager for AviSynth // VapourSynth
VapourSynth Portable FATPACK || VapourSynth Database

Last edited by ChaosKing; 30th September 2022 at 08:54.
ChaosKing is offline   Reply With Quote
Old 30th September 2022, 09:23   #3  |  Link
Selur
Registered User
 
Selur's Avatar
 
Join Date: Oct 2001
Location: Germany
Posts: 7,259
You might be right, I got a KillerSpots version:
Code:
import vapoursynth as vs
core = vs.core

def SpotLess(clip: vs.VideoNode,
             radT: int = 1,
             thsad: int = 10000,
             thsad2: int = None,
             pel: int = None,
             chroma: bool = True,
             ablksize: int = None,
             aoverlap: int = None,
             asearch: int = None,
             ssharp: int = None,
             pglobal: bool = True,
             rec: bool = False,
             rblksize: int = None, 
             roverlap: int = None,
             rsearch: int = None,
             truemotion: bool = True,
             rfilter: int = None,
             blur: bool = False,
             
             ref: vs.VideoNode = None
            ) -> vs.VideoNode:
            
    """
      Args:
          radT   (int)  - Temporal radius in frames
          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).
          thsad2 - mvtools ThSAD that will be used for all calculations with radT > 1
          pel = mvtools Motion estimation accuracy. Value can only be 1, 2 or 4.
            1 : means precision to the pixel.
            2 : means precision to half a pixel.
            4 : means precision to quarter of a pixel, produced by spatial interpolation (more accurate but slower and not always better due to big level scale step). 
          chroma (bool) - Whether to process chroma.
          ablksize   - 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).
          pglobal: mkvootls apply relative penalty (scaled to 256) to SAD cost for global predictor vector.
          rec    (bool) - Recalculate the motion vectors to obtain more precision.
          rblksize/roverlap/rsearch, same as axxx but for the recalculation (rec=True)
          ssharp (int - mvtools Super sharp parameter. Sub-pixel interpolation method for when pel == 2 || 4.
            0 : soft interpolation (bilinear).
            1 : bicubic interpolation (4 tap Catmull-Rom)
            2 : sharper Wiener interpolation (6 tap, similar to Lanczos). 
          rfilter (int) - mvtools Super rfilter. Hierarchical levels smoothing and reducing (halving) filter.
            0 : simple 4 pixels averaging like unfiltered SimpleResize (old method)
            1 : triangle (shifted) filter like ReduceBy2 for more smoothing (decreased aliasing)
            2 : triangle filter like BilinearResize for even more smoothing
            3 : quadratic filter for even more smoothing
            4 : cubic filter like BicubicResize(b=1,c=0) for even more smoothing 
          blur (bool): apply additional bluring during mvtools Super
          truemotion: mvtools Analyse and Recalculate truemotion parameter.
            
    """     
    # Init variables and check sanity
    if radT < 1 or radT > 10:
      raise ValueError("Spotless: radT must be between 1 and 10 (inclusive)")
    if pel==None:
      pel = 1 if clip.width > 960 else 2
    if not pel in [1, 2, 4]:
      raise ValueError("Spotless: pel must be 1, 2 or 4")
    
    thsad2 = thsad2 or thsad
    thsad2 = (thsad + thsad2)/2 if radT>=3 else thsad2
    
    if not isinstance(clip, vs.VideoNode) or clip.format.color_family not in [vs.GRAY, vs.YUV]:
      raise ValueError("SpotLess: This is not a GRAY or YUV clip!")
          
    if ablksize is None:
      ablksize = 32 if clip.width > 2400 else 16 if clip.width > 960 else 8
    if aoverlap is None:
      aoverlap = ablksize//2
    aoverlap = min(aoverlap, ablksize//2)
    if asearch is None:
      asearch = 5  
    if asearch < 0 or asearch > 7:
      raise ValueError("Spotless: search must be between 0 and 7 (inclusive)!")      
    if rfilter is None:
      rfilter = 2
    if rfilter < 0 or rfilter > 4:
      raise ValueError("Spotless: rfilter must be between 0 and 4 (inclusive)")
        
    if ssharp is None:
      ssharp = 1
    if not ssharp in range(2):
      raise ValueError("Spotless: ssharp must be between 0 and 2 (inclusive)")

    if rec:
      if rblksize is None:
        rblksize = ablksize
      if rsearch is None:
        rsearch = asearch
      if rsearch < 0 or rsearch > 7:
        raise ValueError("Spotless: rsearch must between 0 and 7 (inclusive)!")  
      if roverlap is None:
        roverlap = aoverlap
      roverlap = min(roverlap, rblksize/2)
      
    # init functions
    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
    
    # Super
    pad = max(ablksize, 8)
    sup = ref or (c.std.Convolution(matrix=[1, 2, 1, 2, 4, 2, 1, 2, 1]) if blur else clip)
    sup = S(sup, hpad=pad, vpad=pad, pel=pel, sharp=ssharp, rfilter=rfilter)
    sup_rend = S(clip, pel=pel, sharp=ssharp, rfilter=rfilter, levels=1) if ref or blur else sup

    bv=[]
    fv=[]
    def doAnalysis(bv, fv, delta, search, blksize, overlap, chroma, truemotion, pglobal):
       bv.append(A(sup, isb=True, delta=delta, search=asearch, blksize=ablksize, overlap=aoverlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal))
       fv.append(A(sup, isb=False, delta=delta, search=asearch, blksize=ablksize, overlap=aoverlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal))

    # Analyse
    for delta in range(1, radT+1):
      doAnalysis(bv, fv, delta=delta, search=asearch, blksize=ablksize, overlap=aoverlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
    
    def doRecalculate(bv, fv, delta, blksize, overlap, search, truemotion):
       bv[delta-1] = R(sup, bv[delta-1], blksize=blksize, overlap=overlap, search=search, truemotion=truemotion)
       fv[delta-1] = R(sup, fv[delta-1], blksize=blksize, overlap=overlap, search=search, truemotion=truemotion)
    
    if rec:
      for delta in range(1, radT+1):
        doRecalculate(bv, fv, delta, rblksize, roverlap, rsearch, truemotion=truemotion)

    bc=[]
    fc=[]
    def doCompensate(bc, fc, bv, fv, delta, thsad, thsad2):
      if delta != 1:
        thsad = thsad2

      bc.append(C(clip, sup_rend, bv[delta-1], thsad=thsad))
      fc.append(C(clip, sup_rend, fv[delta-1], thsad=thsad))
      
    # Compensate
    for delta in range(1, radT+1):
      doCompensate(bc, fc, bv, fv, delta, thsad, thsad2)
    
    ic =  core.std.Interleave(bc + [clip] + fc)
    output = core.tmedian.TemporalMedian(ic, radius=radT)
    return output.std.SelectEvery(radT*2+1, radT)  # Return middle frame
not at a computer with Vapoursynth on it to test whether I messed something up there when trying to combine these -> seems to work now

Cu Selur
__________________
Hybrid here in the forum, homepage

Last edited by Selur; 30th September 2022 at 14:43. Reason: refactored the merged SpotLess to allow ratT 0-10
Selur is offline   Reply With Quote
Reply

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT +1. The time now is 21:21.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.