Log in

View Full Version : Cleanup single-frame white stripe noise?


shae
1st August 2017, 18:10
What's nowadays a good interlaced-aware plugin to remove random white stripes that last a single frame?

If there are a few options, I'd lean toward leaving lines rather than false positives, and speed rather than 100% detection.

http://i.imgur.com/WVF1KdN.jpg

Mounir
1st August 2017, 19:19
neatvideo (latest version) has such feature; last time i checked it wasn't that bad. Gpu+ cpu work together, you can't expect better than this imo

johnmeyer
1st August 2017, 19:37
I suggest you read through the Decomet thread. I'm surprised that you didn't see it because it has been quite active recently. You'll find it just a few posts down from this one:

New Plugin: Decomet 0.4 (https://forum.doom9.org/showthread.php?t=163049)

shae
1st August 2017, 20:30
Is there an AviSynth version of NeatVideo?

Decomet needs two captures of the video, it seems.

DeSpot (http://avisynth.org.ru/despot/despot.html) and DeScratch (http://avisynth.org.ru/descratch/descratch.html) seem not exactly made for this job.

DeVCR (http://avisynth.nl/index.php/DeVCR) seems the closest, but does collateral changes to the whole frame.

jmac698
1st August 2017, 22:12
Uh.. they all work, and I've tested them all, I believe I posted a script to compare all of them.
Descratch merely needs a turnleft/turnright.
It doesn't matter if something is "made" for one purpose, it still works.
But, the Japanese one worked best, it's called Depulse. It's best because it lets you set a number of parameters to find the white lines such as min/max length etc.
I can also add that for some frames, you can 100% perfectly fix it, if it's film based material with pulldown, by using my decomet with the duplicate fields.

GMJCZP
2nd August 2017, 05:46
I do not know if this is of help, nothing is lost with try:

Here (https://forum.doom9.org/showthread.php?p=1813164#post1813164)

johnmeyer
2nd August 2017, 05:58
I have some motion-estimated code that uses Despot for this purpose. I can post that if you think you can use it. The only issue is that Despot has more settings than almost any plugin, and unlike some settings in other plugins that do much, every setting in Despot affects the outcome, and they all interact. However, it can do a pretty good job with comets.

You might also be able to use the motion compensated version of Removedirt. It is pretty remarkable at removing dirt spots on film. The only issue with comets (and you will find this discussion if you read the thread I linked to above) is that they tend to trail off from a fat head to a really thin tail. Whoever came up with the name "comet" to describe the artifact did a good job of coming up with a descriptor that does a great job describing the problem.

jmac698
2nd August 2017, 11:19
Links to DePulse and mirror
https://forum.doom9.org/showthread.php?p=1800999&highlight=decomet#post1800999

The head looks fat, it's still one pixel high. But yeah, it's a great description, and the tails are what tends to be left over by first attempts to clean.

shae
2nd August 2017, 15:02
Thanks for all the pointers.

The reason I was hoping to find something targeting a very specific artifact was to avoid other changes to the image, like byproduct grain/dirt/spot removal.
Also many parameters is not appealing.

jmac: It's 50i.

GMJCZP, johnmeyer: What's do the motion compensation parts do? I suppose it's not to compare stuff between frames, but the pixel data to use for the fixes?

johnmeyer: Sure, if you think it may work. But a zillion parameters does sound daunting.

BTW, I examined more instances of the artifact. So far it's always 1 pixel high, and always includes some 0xFFFFFF white:
http://i.imgur.com/O20CIpe.png

Anyway, so far DePulse (http://www5e.biglobe.ne.jp/~hoe/dv-and-movie/tipsxxx/index.html) looks like the closest match, but it's not ideal.
To work for my artifacts well there's a need to separate the detection of an artifact line instance, and the decision on its extent.
The way DePulse's threshold settings work either doesn't treat the whole line, or picks up also non-artifacts.
Magenta areas are the detected lines (with debug=true). You can see semi-transparent continuations of the lines that weren't included:

http://i.imgur.com/yyAPhR6.png

Is there a lossless YV12 -> YUY2 -> YV12 with a stock AVS function?
Can you suggest a way/script to get as output just changed frames, or a time-interleave of original/changed?

GMJCZP
2nd August 2017, 17:11
It would be good to consult the whole thread, but read this to see if it works:

Here (https://forum.doom9.org/showthread.php?p=1627449#post1627449)

I think johnmeyer and TinMan (StainlessS) have worked on this for motion detection.

Edit: Did you try my suggestion?

Mounir
2nd August 2017, 18:54
Try the solution of YUP, it requires mvtools & degrainmedian
you can decrease the mode(0 to 5) for degrainmedian to remove more or less lines but beware of artefacts (mode=4 seems ok to me in most cases)

avisource().assumetff()
SeparateFields()
e=SelectEven()
o=selectOdd()

bvo = o.MVAnalyse(isb = true, truemotion=true, delta = 1, idx = 1, overlap=4, dct=1)
fvo = o.MVAnalyse(isb = false, truemotion=true, delta = 1, idx = 1, overlap=4, dct=1)
fco = o.MVFlow(fvo, idx=1, thSCD1=500)
bco = o.MVFlow(bvo, idx=1, thSCD1=500)
interleave(fco, o, bco)
DeGrainMedian(limitY=255,limitUV=255,mode=4, norow=true)
od=selectevery(3,1)

bve = e.MVAnalyse(isb = true, truemotion=true, delta = 1, idx = 2, overlap=4, dct=1)
fve = e.MVAnalyse(isb = false, truemotion=true, delta = 1, idx = 2, overlap=4, dct=1)
fce = e.MVFlow(fve, idx=2, thSCD1=500)
bce = e.MVFlow(bve, idx=2, thSCD1=500)
interleave(fce, e, bce)
DeGrainMedian(limitY=255,limitUV=255,mode=4, norow=true)
ed=selectevery(3,1)

interleave(ed,od)
Weave()

StainlessS
2nd August 2017, 18:54
Can you suggest a way/script to get as output just changed frames, or a time-interleave of original/changed?

I spotted a bug in DePulse, (used same pitch for before, current, and after frames. (pitch can vary in single clip, crop/Interleave/splice).

Am currently fixing but getting compiler errors (error within compiler).

When fixed will provide global variable with optional name Prefix eg "Prefix_Status", set true/false on every frame (true=changed).

EDIT: DePulse does not do frame Global modifications, only Pulse local.
EDIT: Global var change to eg Prefix_PulseLines, of type Int.

shae
2nd August 2017, 20:12
GMJCZP:
Yeah, tried KillerSpots. In limited testing, the lines are gone, but in addition it also removes noise/grain. "limit" doesn't change much, if anything.
Trying "advance=true", it complains about missing "RestoreMotionBlocks". I tried various RemoveGrain 1.0b DLLs. I don't know, maybe they don't load.
Curious behavior: In the frame's bottom edge, in the semi-noise region, it sometimes changes the coloration green<->purple.

Mounir:
It works for the lines, but even mode=5 does some global noise reduction, though much milder than other things. Mild, but I'd rather leave it as is. In mode=5 there's also a mild leftover of the comet lines (say, like 10-20% transparency). It's not too objectionable. BTW, seems slow, but maybe there are better MVtools v1 compiles.

When fixed will provide global variable with optional name Prefix eg "Prefix_Status", set true/false on every frame (true=changed).I.e., to somehow use in AVS to keep just changed frames?

EDIT: DePulse does not do frame Global modifications, only Pulse local.Yeah, that's a good property. It's also fast. Too bad the base algorithm doesn't fully fix the lines in my case.

johnmeyer
2nd August 2017, 20:37
Here is a function that might help. It was "lying around" and I don't know if this is something I wrote, or whether I stole it from someone else. Some of the code looks like something I wrote, so I think it is at least partly mine. I called it "RemoveTears" although, as I stated before, I really like the term "comets" much better. The "RemoveTearsI" is the interlaced version. It calls RemoveTears after separating into fields, and then reassembles things back into interlaced video.function Remove_TearsI(clip source)
{
ml = 100 # mask scale
scene_change = 200 # scene change

fields=source.SeparateFields()
even_fields=selecteven(fields)
odd_fields =selectodd(fields)

even_result_fields = RemoveTears(even_fields,ml,scene_change)
odd_result_fields = RemoveTears(odd_fields, ml,scene_change)

#Put even and odd fields back together into interlaced video
interleave(even_result_fields,odd_result_fields)
weave()
}

function RemoveTears(clip source, int ml, int scene_change)
{
show = 0
tsmooth = 0
p1percent = 2
dilate = 20
maxpts = 0
p2 = 12
mthres = 16
p1 = 24
pwidth = 100
pheight = 12
mwidth = 7
mheight = 5
merode = 33
seg = 1
sign = 0

interlaced = false
ranked = true

super = MSuper(source,hpad=16, vpad=16, pel=2, sharp=1)
vb = MAnalyse(super,isb = true, blksize=8,overlap=4,search=3,dct=0) # backward vectors
vf = MAnalyse(super,isb = false, blksize=8,overlap=4,search=3,dct=0) # forward vectors
cb = MFlow(source,super,vb,thSCD1=scene_change) # next compensated backward
cf = MFlow(source,super,vf,thSCD1=scene_change) # previous compensated forward

sadf = MMask(super,vf, ml=ml,kind=1,gamma=1, thSCD1 = scene_change)
msadf= sadf.Binarize() # binary inverted forward SAD mask
sadb = MMask(super,vb, ml=ml, gamma=1, kind=1, thSCD1 = scene_change) # backward SAD mask
msadb= sadb.Binarize() # binary inverted backward SAD mask
msad = Logic(msadf,msadb,"OR") # combined inverted SAD mask
msad = msad.Expand() # expanded inverted SAD mask
msadi = Interleave(msad, msad, msad) # interleaved 3-frame inverted SAD mask

Interleave(cf,source,cb) # interleave forward compensated, source, and backward compensated

DeSpot(show=show,extmask=msadi,tsmooth=tsmooth,p1percent=p1percent,dilate=dilate,maxpts=maxpts,p2=p2,\
mthres=mthres,p1=p1,pwidth=pwidth,pheight=pheight,mwidth=mwidth,mheight=mheight,merode=merode,\
interlaced=interlaced,seg=seg,sign=sign,ranked=ranked).SelectEvery(3,1)
}

GMJCZP
2nd August 2017, 21:22
GMJCZP:
Yeah, tried KillerSpots. In limited testing, the lines are gone, but in addition it also removes noise/grain. "limit" doesn't change much, if anything.
Trying "advance=true", it complains about missing "RestoreMotionBlocks". I tried various RemoveGrain 1.0b DLLs. I don't know, maybe they don't load.


What happens is that you need, in addition to RGTools, RemoveDirtSSE2.

Enter here (http://avisynth.nl/index.php/RemoveDirt) and download the version recommended here (http://xhmikosr.1f0.de/_old/avisynth/plugins/RemoveDirt_0.9.zip). Search RemoveDirtSSE2.

I will update the KillerSpots requirements.

LemMotlow
2nd August 2017, 21:40
Amazing. No sample of the actual problem except for some tiny Meshuggenah pictures nobody can use. This guessing game could go on for a really long time. Think I'll wait about a month and check back.

StainlessS
3rd August 2017, 03:30
DePulse v1.00 (never previously had a version):- http://www.mediafire.com/file/7p4habjdd92ygie/Depulse_25_dll_v1.0_20170803.zip


DePulse 2005/04/10, 2005/04/11 update

Author Unknown

v1.00 Update StainlessS, 03 Aug 2017. Added Prefix arg.

Function:
Dropout remover, removes white/black noise horizontal streaking in
analogue interlaced captures.

Usage instructions.

Assumes TFF (top field first) field order interlacing.

YUY2 only.

Syntax is: DePulse(clip, "h"=180[0-255], "l"=60[0-255], "d"=30[0-255], "debug"=[true/false],String "Prefix"="")


Pixel brightness values greater than h (high) are targeted as white noise.
If a pixel (t) is target'ed as white noise then surrounding pixels
(above, below, before and after, Not left and right) are tested
and if ALL of them are less than (t - d) the target pixel t is
depulsed.

Pixel brightness values less than l (low) are targeted as black noise.
If a pixel (t) is target'ed as black noise then surrounding pixels
(above, below, before and after, Not left and right) are tested and
if ALL of them are greater than (t + d) the target pixel t is
depulsed.

In debug mode, pixels judged to be black or white noise are coloured pink or green.

Prefix, if Prefix not Default "", then sets a Global Variable for use in Runtime environment eg ScriptClip.
The Global variable is set to the number of scanlines that have depulsed pixels.
Eg, if Prefix="GLB" then sets variable GLB_PulseLines to the count of depulsed scanlines in current frame.


MakePulseClip.avs

# MakePulseClip.avs # Make Test clip
#
# Requires GScript,

FRAMES = 100 # Uses a lot of RAM

clean=colorbars(width=480, height=360, pixel_type="YUY2").Trim(0,-1).KillAudio
YUY2=0
GScript("""
for(i=0,FRAMES-1) {
c=addcomets(clean)
YUY2 = (YUY2.IsClip) ? YUY2++c : c
}
""")
return YUY2 # DONT CONVERT IN HERE, SAVE AS PulseClipYUY2.AVI.

# Below by JMac698

function whiteline(clip template) {
template.blankclip(width=template.width/4, height=1,color_yuv=$EB8080) # $EB=235
}

function blackline(clip template) {
template.blankclip(width=template.width/4, height=1,color_yuv=$108080) # $10=16
}

function addcomets(clip v){
#Add three, randomly positioned, white and black lines to an existing video
v
w=width
h=height
overlay(whiteline,x=rand(w*3/4),y=rand(h))
overlay(blackline,x=rand(w*3/4),y=rand(h))
overlay(whiteline,x=rand(w*3/4),y=rand(h))
overlay(blackline,x=rand(w*3/4),y=rand(h))
overlay(whiteline,x=rand(w*3/4),y=rand(h))
overlay(blackline,x=rand(w*3/4),y=rand(h))
}


DepulseTest.avs

# DepulseTest.avs
# Requires Depulse, RT_Stats.

LoadPlugin("G:\DePulse.dll") # Or in Plugins

VIDEONAME = "G:\PulseClipYUY2.avi" # See MakePulseClip.avs
#VIDEONAME = "F:\V\Cabaret.avi"

### Config ####
H = 180
L = 60
D = 30
Prefix = "GLB"
Show = True
STACK = True
###############
src = AviSource(VIDEONAME) # Original, whatever colorspace
YUY2 = src.ConvertToYUY2 # Original As YUY2 # EDIT: Maybe Add Interlaced=true where required

FIX = YUY2.DePulse(h=H,l=L,d=D)
DBG = YUY2.DePulse(h=H,l=L,d=D,debug=True,prefix=PREFIX)

Frms = 0
TotCnt = 0

SSS = """
cnt = GLB_PulseLines # ScanLines with pulses, this frame
TotCnt= TotCnt + cnt # Total pulsing scalines in clip so far
Frms = Frms + ((cnt>0) ? 1 : 0) # Number of frames with pulsing scanlines
(SHOW) ? RT_Subtitle("%d] Cnt=%d : TotCnt=%d : Frms=%d",current_frame,cnt,TotCnt,Frms) : Last
Return Last
"""

# Important, Need After_Frame=True, need proc frame BEFORE SSS script, else numbers (GLB_PulseLines) off by 1 frame
DBG = DBG.Scriptclip(SSS,After_Frame=True)

LFT=YUY2.RtSub("Original") HMID =LFT.BlankClip(width=8,Color=$FF00FF) RGT=FIX.RtSub("DePulse")
TOP=StackHorizontal(LFT,HMID,RGT)
BOT = StackHorizontal(DBG.RtSub("Debug + Count of pulsing scanlines"),HMID,FIX.ClipDelta(YUY2,amp=True).RtSub("Delta (Depulse-Original)"))
VMID =TOP.BlankClip(height=8,Color=$FF00FF)
STK4=TOP.StackVertical(VMID,BOT)

(STACK) ? STK4 : FIX

Return Last.ConvertToYV12

# Stack Overhead RT_Subtitle Text, with optional FrameNumber shown.
Function RtSub(clip c,string Tit, Bool "ShowFrameNo") {
c.BlankClip(height=20)
(Default(ShowFrameNo,False)) ? ScriptClip("""RT_Subtitle("%d] %s",current_frame,""""+Tit+"""")""") : RT_Subtitle("%s",Tit)
Return StackVertical(c).AudioDubEx(c)
}

# Return Clip Difference of input clips (amp==true = Amplified, show==true = show background)
Function ClipDelta(clip clip1,clip clip2,bool "amp",bool "show") {
amp=Default(amp,false)
show=Default(show,false)
c2=clip1.levels(128-32,1.0,128+32,128-32,128+32).greyscale()
c1=clip1.subtract(clip2)
c1=(amp)?c1.levels(127,1.0,129,0,255):c1
return (show)?c1.Merge(c2):c1
}


Guinea pigs required.

EDIT: @ Shae,

I.e., to somehow use in AVS to keep just changed frames?
Well to tell what frames have been 'touched'.
Int, count of scanlines detected gives a little more info that simple bool, perhaps of greater use, perhaps not.


Yeah, that's a good property. It's also fast. Too bad the base algorithm doesn't fully fix the lines in my case.
Pixels are process individually with no regard to those left or right, if they dont fit with the h, l, and d args they are not touched.

Snippet

int PulseLines = 0;

for (int j=1; j<height-2; j+=2) {
int PulsedPix=0;
for (i=0; i<rowsize; i+=4) {
// SJ, Here bottom field current
// j limit, bot field of second last interlaced pair (last line processed, j=height-3)
// LUMA LEFT
col0=srcp[(j )*pitch + i]; // j bot field this pix
col1=srcp[(j-1)*pitch + i]; // j-1 top field pix above
col2=srcp[(j+1)*pitch + i]; // j+1 top field pix below

col3=srcpaf[(j-1)*pitchaf + i]; // j-1 nxt frame top field pix above
col4=srcpaf[(j+1)*pitchaf + i]; // j+1 nxt frame top field pix below

col5=srcpbf[j*pitchbf + i]; // j prv frame curr field

col6=srcpaf[j*pitchaf + i]; // j nxt frame curr field

col7=srcp[(j-1)*pitch + i - ((j==1) ? 0 : pitch)]; // j-2 curr field above pix (2 above)
col8=srcp[(j+1)*pitch + i + ((j==height-3) ? 0 : pitch)];// j+2 curr field below pix (2 below)

lo = col0 - _d;

// If curr pix > h arg AND all surrounding pixels < (curr pix - d arg) then depulse.
if ( (col0>_h)&&(col1<lo)&&(col2<lo)&&(col3<lo)&&(col4<lo)&&(col5<lo)&&(col6<lo)&&(col7<lo)&&(col8<lo) ) {
dstp[i]=(col1+col2+col3+col4+col5+col6+col7+col8)/8;
if (_debug) {
dstp[i+1]=250; // U
dstp[i+3]=250; // V
}
++PulsedPix; // single pixel on scanline depulsed, if non zero at end of scanline then scanline was depulsed.
}
hi = col0 + _d;

// If curr pix < l arg AND all surrounding pixels > (curr pix + d arg) then depulse.
if ( (col0<_l)&&(col1>hi)&&(col2>hi)&&(col3>hi)&&(col4>hi)&&(col5>hi)&&(col6>hi)&&(col7>hi)&&(col8>hi) ) {
dstp[i]=(col1+col2+col3+col4+col5+col6+col7+col8)/8;
if (_debug) {
dstp[i+1]=10; // U
dstp[i+3]=10; // V
}
++PulsedPix;
}



EDIT: I did try counting contiguous depulsed pixels (both dark and light separately), but showed up dozens of comets where only a few visible, did not seem useful.
EDIT: Would total number of depulsed pixels be better measure than count of scanlines ? EDIT: Probably not.

johnmeyer
3rd August 2017, 05:45
I am supposed to receive, tomorrow, several boxes of tapes that need to be transferred. I'm not sure if they are VHS or 8mm, but if they are VHS, I should be able to supply some nice samples of pulses/comets/tears/bullets, or whatever else we want to call these scan line dropouts. I'll be capturing for a few days and may not get to putting them in the editor until early next week.

StainlessS
3rd August 2017, 06:09
I'll probably not be on-line after maybe tomorrow, till mid next week, so not a disappointment.

Missing you already :)

EDIT: @ Below, Yes I'm missing you too GMJCZP (such an attention seeker).

EDIT: 06:25, gonna get some shut eye, tata.

GMJCZP
3rd August 2017, 06:23
Here we will be waiting as always your contributions :thanks:

shae
3rd August 2017, 15:05
johnmeyer: Remove_TearsI() doesn't alter the image (same binary data). I tried randomly changing ml (mask scale) and scene_change, but visually it stays the same.
Didn't try messing with DeSpot's parameters.

GMJCZP: Oh, I thought it was superceded by RemoveGrain. It indeed works with RemoveDirt.
So, with advanced=true the overall noise removal is almost the same as with advanced=false. Maybe a tiny bit less. The main difference I see is that if the frame is rejeced by limit it stays unaltered, at least visually.

StainlessS: Thanks! For one stats variable, I think detected line count is more informative than pixel count.

The per-pixel approach is really limiting. With two threshold parameters, one for the initial detection, another for the line extent decision, it might both reduce false-positives and more completely remove semi-transparent line parts.
Maybe even a third parameter: minimum number of pixels per line, to ignore things like 1-3 pixel noise.

Might be good to start a new thread dedicated to DePulse? It'd make it more discoverable and discussable. There's some discussion in this thread (https://forum.doom9.org/showthread.php?t=151426), but also a question mark in the title. :)

LemMotlow: The shots (and text) are a complete description of the artifacts. Everyone so far understood it. Most of the discussion was about how to remove just the specific artifacts, and not grain/noise/spots.

BTW: The script Mounir mentioned, from Yup, is discussed here:
https://forum.doom9.org/showthread.php?p=949401#post949401

StainlessS
3rd August 2017, 16:03
Might be good to start a new thread dedicated to DePulse?

I dont really wanny support it, it aint mine, I just did a fix and slight mod (well several small mods since a couple of years ago).

Mod of zip included script, writes list of DePulsed frames.

# DepulseTest.avs
# Requires Depulse, RT_Stats.

LoadPlugin("G:\DePulse.dll") # Or in Plugins

VIDEONAME = "G:\PulseClipYUY2.avi" # See MakePulseClip.avs
#VIDEONAME = "F:\V\Cabaret.avi"

### Config ####
H = 180 # 180
L = 60 # 60
D = 30 # 30
LACED = True # Interlaced YV12 ColorSpace Conversions
STACK = True # True, returns Stack 4 windows
RETDBG = True # True and STACK=False, then return DBG instead of FIX (Frames file).
SHOW = True # True, Show Metrics in Debug Window
FNAME = "DePulsed_Frames.txt" # Frames modified by Depulse when STACK || RETDBG, ("" switches OFF)
###############
Prefix = "GLB" # "GLB" produces Int Global var GLB_PulseLines (best not change, hard coded in SSS script)

src = AviSource(VIDEONAME) # Original, whatever colorspace
src = src.AssumeTFF # DePulse requirement
YUY2 = src.ConvertToYUY2(Interlaced=LACED) # Original As YUY2. NOP if already YUY2

FNAME=(FNAME!="") ? RT_GetFullPathName(FNAME) : FNAME # Get full path, incl drive and path.
(FNAME!="") ? RT_FileDelete(FNAME) : NOP # Delete Frames File

FIX = YUY2.DePulse(h=H,l=L,d=D)
DBG = YUY2.DePulse(h=H,l=L,d=D,debug=True,prefix=PREFIX)

Frms = 0
TotCnt = 0

SSS = """
# RT_AverageLuma(w=1,h=1) # Sample single pixel of source. If this line un-commented, then Scriptclip(After_Frame=True) unnecessary.
cnt = GLB_PulseLines # ScanLines with pulses, this frame
(cnt>0 && FNAME!="") ? RT_WriteFile(FNAME,"%d",current_frame,Append=True) : NOP # Write DePulsed frame number to file
TotCnt= TotCnt + cnt # Total pulsing scanlines in clip so far
Frms = Frms + ((cnt>0) ? 1 : 0) # Number of frames with pulsing scanlines
(SHOW) ? RT_Subtitle("%d] Cnt=%d : TotCnt=%d : Frms=%d",current_frame,cnt,TotCnt,Frms) : Last
Return Last
"""

# Important, Need After_Frame=True, need fetch source DBG frame BEFORE SSS script, else numbers (GLB_PulseLines) off by 1 frame.
# In SSS script, GLB_PulseLines accessed before [RT_Subtitle or final frame return Last] requests a frame from the
# SSS source clip [ie DBG.ScriptClip], After_Frame=True, forces DBG frame to be accessed before first line of SSS script
# is executed. If first line of script was eg RT_AverageLuma, then that would force the current_frame of source clip to
# be fetched (and GLB_PulseLines set), before measuring the RT_AverageLuma, and so After_Frame would not then be an issue (but here it is).
# ScriptClip is very flexible, Ive rarely found any runtime problem that could not be solved in ScriptClip alone (occasionally
# two instances of Scriptclip), and I usually find it best to set After_Frame=True, except perhaps when using multiple
# Conditional filters (eg see docs Conditional Filter, Advenced).
# If DePulse() was used INTERNAL to the ScriptClip script, then so long as it was called before accessing GLB_PulseLines then
# again After_Frame would not be an issue, but it would slow down your script due to an instance of DePulse being Constructed and
# Destructed at every frame. Above, we have constructed the DBG clip outside of ScriptClip and so only a single instance is needed
# and before frameserving begins (but something needs to request a frame from it, otherwise GLB_PulseLines would contain metric for
# previous frame, After_Frame=True ensures that Global var metric is current).
# ALSO NOTE, that on each construction of DePulse filter, the Global Variable name is initialized and memory allocated for it, and the
# value of it initialized to 0. If constructing DePulse within ScriptClip, then Global variable initialzation would occur at each frame
# and allocate memory for the glob variable, so construction outside of ScriptClip is more memory economical. We could check for
# existence of variable of similar name, but as we cannot tell difference between existing Global and Local vars of similar name, so
# we have to perhaps waste more ram to be more behaviour predictable.
# Above we used RT_AverageLuma as standard AverageLuma only for Planar Y (Not YUY2, or RGB[converted to LumaY]).
DBG = DBG.Scriptclip(SSS,After_Frame=True)

LFT=YUY2.RtSub("Original") HMID =LFT.BlankClip(width=8,Color=$FF00FF) RGT=FIX.RtSub("DePulse")
TOP=StackHorizontal(LFT,HMID,RGT)
BOT = StackHorizontal(DBG.RtSub("Debug + Count of pulsing scanlines"),HMID,FIX.ClipDelta(YUY2,amp=True).RtSub("Delta (Depulse-Original)"))
VMID =TOP.BlankClip(height=8,Color=$FF00FF)
STK4=TOP.StackVertical(VMID,BOT)

(STACK) ? STK4 : (RETDBG) ? DBG : FIX

Return Last.ConvertToYV12(Interlaced=LACED)

# Stack Overhead RT_Subtitle Text, with optional FrameNumber shown.
Function RtSub(clip c,string Tit, Bool "ShowFrameNo") {
c.BlankClip(height=20)
(Default(ShowFrameNo,False)) ? ScriptClip("""RT_Subtitle("%d] %s",current_frame,""""+Tit+"""")""") : RT_Subtitle("%s",Tit)
Return StackVertical(c).AudioDubEx(c)
}

# Return Clip Difference of input clips (amp==true = Amplified, show==true = show background)
Function ClipDelta(clip clip1,clip clip2,bool "amp",bool "show") {
amp=Default(amp,false)
show=Default(show,false)
c2=clip1.levels(128-32,1.0,128+32,128-32,128+32).greyscale()
c1=clip1.subtract(clip2)
c1=(amp)?c1.levels(127,1.0,129,0,255):c1
return (show)?c1.Merge(c2):c1
}



Edited: Replaced AverageLuma (Planar only) with RT_AverageLuma

shae
3rd August 2017, 19:45
I didn't mean you should support/develop, just thought concentrating it in a single thread would be more organized for posterity.
If you don't mind, I could post it there. :)

StainlessS
3rd August 2017, 20:07
OK, well maybe I start thread when/if get reports that its working ok.
I guess would best be me if it were to be updated in future, otherwise reliant upon mods constantly appending updates to first post.

vcmohan
4th August 2017, 07:59
Just seen this thread. It is possible that my FanFilter will be able to remove these horizontal streaks. Suggest following script
<code>
crop(.........) # to make frame suitable for yuv
converttoyuv12()
TurnLeft()
Fan(opt = "stop") #try opt = "edge" and "pass" also
turnright()
</code>

shae
4th August 2017, 13:22
if get reports that its working ok.In limited testing, I didn't find problems.

It is possible that my FanFilter will be able to remove these horizontal streaks.
Thanks. Tried a few variations of "opt", and also plus/minus/lambda. It seems to target higher frequency noise, so not the lines.

Mounir
4th August 2017, 16:42
Try this other script i keep under my belt. This is the work of Didée originally topic here (http://forum.videohelp.com/threads/343296-Transfer-of-real-bad-tape-to-pc-and-restore?p=2139402&viewfull=1#post2139402), slightly tweaked for my needs (remove as much lines white/black and chroma noise) as possible / keep as much details as possible. Requires mvtools2 & ccd (vdub plugin)
video sample here (http://uptobox.com/mdcqx2nqo0tn)

separatefields()
a=last
mot=removegrain(8,0).DepanEstimate(range=2)
take2=a.depaninterleave(mot,prev=2,next=2,subpixel=2)
clean1=take2.DeGrainMedian(limitY=255,limitUV=255,mode=5,norow=true,interlaced=false).selectevery(5,2)

sup1 = clean1.minblur(1).removegrain(11,0)
\ .mt_lutxy(clean1,"x 1 + y < x 2 + x 1 - y > x 2 - y ? ?",U=2,V=2)
\ .msuper(pel=2,sharp=0)
sup2 = a.msuper(pel=2,levels=1,sharp=2)
###### Good mvtools values, Do not change, checked##########
bv22=sup1.manalyse(isb=true, truemotion=false,global=true,delta=2,blksize=16,overlap=8,search=5,searchparam=4,DCT=5)
bv21=sup1.manalyse(isb=true, truemotion=false,global=true,delta=1,blksize=16,overlap=8,search=5,searchparam=4,DCT=5)
fv21=sup1.manalyse(isb=false,truemotion=false,global=true,delta=1,blksize=16,overlap=8,search=5,searchparam=4,DCT=5)
fv22=sup1.manalyse(isb=false,truemotion=false,global=true,delta=2,blksize=16,overlap=8,search=5,searchparam=4,DCT=5)

interleave(a.mcompensate(sup2,fv22),a.mcompensate(sup2,fv21),a,a.mcompensate(sup2,bv21),a.mcompensate(sup2,bv22))
mergeluma(TMedian2(),0.9).mergechroma(TMedian2(),1).selectevery(5,2)

sup3 = last.msuper(pel=2,sharp=2)
bv33=sup3.manalyse(isb=true, truemotion=false,global=true,delta=3,blksize=8,overlap=4,search=5,searchparam=4,DCT=5)
bv32=sup3.manalyse(isb=true, truemotion=false,global=true,delta=2,blksize=8,overlap=4,search=5,searchparam=4,DCT=5)
bv31=sup3.manalyse(isb=true, truemotion=false,global=true,delta=1,blksize=8,overlap=4,search=5,searchparam=4,DCT=5)
fv31=sup3.manalyse(isb=false,truemotion=false,global=true,delta=1,blksize=8,overlap=4,search=5,searchparam=4,DCT=5)
fv32=sup3.manalyse(isb=false,truemotion=false,global=true,delta=2,blksize=8,overlap=4,search=5,searchparam=4,DCT=5)
fv33=sup3.manalyse(isb=false,truemotion=false,global=true,delta=3,blksize=8,overlap=4,search=5,searchparam=4,DCT=5)
last
weave()


#################################################################
function EdgeCleaner(clip c, float "strength", bool "rep", int "rmode", int "smode", bool "hot", bool "fix") {

strength = default(strength, 8.0)
rep = default(rep, true)
rmode = default(rmode, 17)
smode = default(smode, 0)
hot = default(hot, false)
fix = default(fix, true)

c = (c.isYV12()) ? c : c.ConvertToYV12()
strength = (smode==0) ? strength : strength+4

main = c.aWarpSharp(strength,1)
main = (rep) ? Repair(main,c,rmode) : main

mask = c.mt_edge("prewitt",4,32,4,32).mt_invert().mt_convolution()

final = (!hot) ? mt_merge(c,main,mask) : Repair(mt_merge(c,main,mask),c,2)
final = (fix) ? Overlay(final,c.ConvertToRGB24().Crop(0,1,-c.width+1,-c.height+2),x=0,y=1) : final
final = (smode != 0) ? mt_merge(final,c,c.StarMask(smode)) : final

return final

}

function StarMask(clip c, int "mode") {

mode = default(mode, 1)

clean = (mode==1) ? c.RemoveGrain(17) : Repair(c.Deen("a3d",4,12,0),c,15).RemoveGrain(21)
diff = (mode==1) ? mt_makediff(c,clean) : NOP

final = (mode==1) ? diff.Greyscale().Levels(40,0.350,168,0,255).removegrain(7,-1).mt_edge("prewitt",4,16,4,16) : \
Subtract(mt_merge(clean,c,c.mt_edge("roberts",0,2,0,2).mt_expand(mode=mt_circle(1)).mt_invert()),c).mt_edge("roberts",0,0,0,0).mt_deflate()

return final

}



function MinBlur(clip clp, int r, int "uv")
{
uv = default(uv,3)
uv2 = (uv==2) ? 1 : uv
rg4 = (uv==3) ? 4 : -1
rg11 = (uv==3) ? 11 : -1
rg20 = (uv==3) ? 20 : -1
medf = (uv==3) ? 1 : -200

RG11D = (r==0) ? mt_makediff(clp,clp.sbr(),U=uv2,V=uv2)
\ : (r==1) ? mt_makediff(clp,clp.removegrain(11,rg11),U=uv2,V=uv2)
\ : (r==2) ? mt_makediff(clp,clp.removegrain(11,rg11).removegrain(20,rg20),U=uv2,V=uv2)
\ : mt_makediff(clp,clp.removegrain(11,rg11).removegrain(20,rg20).removegrain(20,rg20),U=uv2,V=uv2)
RG4D = (r<=1) ? mt_makediff(clp,clp.removegrain(4,rg4),U=uv2,V=uv2)
\ : (r==2) ? mt_makediff(clp,clp.medianblur(2,2*medf,2*medf),U=uv2,V=uv2)
\ : mt_makediff(clp,clp.medianblur(3,3*medf,3*medf),U=uv2,V=uv2)
DD = mt_lutxy(RG11D,RG4D,"x 128 - y 128 - * 0 < 128 x 128 - abs y 128 - abs < x y ? ?",U=uv2,V=uv2)
clp.mt_makediff(DD,U=uv,V=uv)
return(last)
}



# median of 5 clips from Helpers.avs by G-force
Function Median2(clip "input_1", clip "input_2", clip "input_3", clip "input_4", clip "input_5", string "chroma")
{
chroma = default(chroma,"process") #default is "process". Alternates: "copy first" or "copy second"
#MEDIAN(i1,i3,i5)
Interleave(input_1,input_3,input_5)
chroma == "process" ? Clense(reduceflicker=false) : Clense(reduceflicker=false,grey=true)
m1 = selectevery(3,1)
#MAX(MIN(i1,i3,i5),i2)
m2 = input_1.MT_Logic(input_3,"min",chroma=chroma).MT_Logic(input_5,"min",chroma=chroma).MT_Logic(input_2,"max",chroma=chroma)
#MIN(MAX(i1,i3,i5),i4)
m3 = input_1.MT_Logic(input_3,"max",chroma=chroma).MT_Logic(input_5,"max",chroma=chroma).MT_Logic(input_4,"min",chroma=chroma)
Interleave(m1,m2,m3)
chroma == "process" ? Clense(reduceflicker=false) : Clense(reduceflicker=false,grey=true)
selectevery(3,1)
chroma == "copy first" ? last.MergeChroma(input_1) : chroma == "copy second" ? last.MergeChroma(input_2) : last
ConverttoRGB32(matrix="rec601",interlaced=false)
LoadVirtualDubPlugin("C:\Program Files (x86)\VirtualDub\plugins\Camcorder_Color_Denoise_sse2.vdf", "CCD", 1)
CCD(7,1) # de 0 à 100 # Défaut =30 /
converttoyv12(matrix="rec601",interlaced=false)
Return(last)
}

function TMedian2(clip c) {
Median2( c.selectevery(1,-2), c.selectevery(1,-1), c, c.selectevery(1,1), c.selectevery(1,2) ) }


function RemoveDirt(clip input, int limit, bool _grey)
{
clensed=input.Clense(grey=_grey, cache=4)
alt=input.RemoveGrain(2)
return RestoreMotionBlocks(clensed,input,alternative=alt,pthreshold=6,cthreshold=8, gmthreshold=40,dist=3,dmode=2,debug=false,noise=limit,noisy=4, grey=_grey)

# Alternative settings
# return RestoreMotionBlocks(clensed,input,alternative=alt,pthreshold=4,cthreshold=6, gmthreshold=40,dist=1,dmode=2,debug=false,noise=limit,noisy=12,grey=_grey,show=true)
# return RestoreMotionBlocks(clensed,input,alternative=alt,pthreshold=6,cthreshold=8, gmthreshold=40,dist=3,tolerance= 12,dmode=2,debug=false,noise=limit,noisy=12,grey=_grey,show=false)
}

vcmohan
5th August 2017, 12:48
Thanks. Tried a few variations of "opt", and also plus/minus/lambda. It seems to target higher frequency noise, so not the lines.
As I do not have avisynth 2.5.8 dll on my system (I am using avisynth+ recent dll) I am unable to test myself. Hope you used YUV as 7, plus as 0 and minus as 50 for test. If still it does not work please report so that I will look into and recompile.

shae
5th August 2017, 13:57
Mounir:
With YV12 input DePanInterleave complains it wants planar YUV, or YUY2. Converted to YUY2, RemoveGrain then complains it wants planar.
(BTW, annoying that the plugins don't use libfftw3f-3.dll from their local dir.)

vcmohan:
Tried now plus=0, minus=50. Doesn't affect the lines.
yuv=7 adds blocky color artifacts, I guess due to the lower resolution/quality of the chroma.
I am using AVS+. The compile is fine, it loads and functions. As far as I can tell it works as intended, just targets different kind of noise.

Anyway guys, thanks for all the suggestions. I think for now I'll make do with my existing pipeline.

yup
9th August 2017, 13:19
shae!
Try
https://forum.doom9.org/showthread.php?p=943287#post943287
It is very slow, but some video after became watchable.
Written for old Avisynth, need some colour space conversion.
For You recommend after AVISource use ConvertToYV16(interlaced=true).
I am recently testing this script under new Avisynth+ 32 bit and new plugin (excluding variableblur and dffttest).
See link below
https://www.dropbox.com/s/4z5knjl9qg1ybex/removelinetest.avs?dl=0
Need only pinterf plugins and nnedi3 from jpsdr, tested under Avisynth+ 64 bit.
yup.

shae
10th August 2017, 15:06
Thanks. I bet the <2fps you got is going to be even slower here. :)

yup
10th August 2017, 17:06
Thanks. I bet the <2fps you got is going to be even slower here. :)
On 2 core PC (3 years old) I see 10 fps.
yup.

shae
12th August 2017, 14:17
Tried it. No effect on white lines, just mild overall noise reduction.

yup
15th August 2017, 06:25
Tried it. No effect on white lines, just mild overall noise reduction.

shae!
Try change parameters mainly thicknessline (1 to 4) and halflength (up to 150), and see compare between source and filtered video and also difference.
If I have time I try Your source.
yup.

shae
19th August 2017, 00:41
I might experiment more later, though all lines are 1 pixel high.
No need to test it directly for me, but thanks.

vcmohan
25th August 2017, 17:19
I have recoded Fan Filter and now included in modplus plugin as one of the functions. Using it with following script, I could eliminate white streaks
converttoRGB24()
TurnLeft()
Fan(span = 7,edge = true, uv = true, plus = 0.05, minus = 0.05)
turnRight()
plus and minus values can be increased if required. Please note that parameter list has changed from previous FanFilter

kedautinh12
1st September 2021, 12:55
DePulse v1.00 (never previously had a version):- http://www.mediafire.com/file/7p4habjdd92ygie/Depulse_25_dll_v1.0_20170803.zip


DePulse 2005/04/10, 2005/04/11 update

Author Unknown

v1.00 Update StainlessS, 03 Aug 2017. Added Prefix arg.

Function:
Dropout remover, removes white/black noise horizontal streaking in
analogue interlaced captures.

Usage instructions.

Assumes TFF (top field first) field order interlacing.

YUY2 only.

Syntax is: DePulse(clip, "h"=180[0-255], "l"=60[0-255], "d"=30[0-255], "debug"=[true/false],String "Prefix"="")


Pixel brightness values greater than h (high) are targeted as white noise.
If a pixel (t) is target'ed as white noise then surrounding pixels
(above, below, before and after, Not left and right) are tested
and if ALL of them are less than (t - d) the target pixel t is
depulsed.

Pixel brightness values less than l (low) are targeted as black noise.
If a pixel (t) is target'ed as black noise then surrounding pixels
(above, below, before and after, Not left and right) are tested and
if ALL of them are greater than (t + d) the target pixel t is
depulsed.

In debug mode, pixels judged to be black or white noise are coloured pink or green.

Prefix, if Prefix not Default "", then sets a Global Variable for use in Runtime environment eg ScriptClip.
The Global variable is set to the number of scanlines that have depulsed pixels.
Eg, if Prefix="GLB" then sets variable GLB_PulseLines to the count of depulsed scanlines in current frame.


MakePulseClip.avs

# MakePulseClip.avs # Make Test clip
#
# Requires GScript,

FRAMES = 100 # Uses a lot of RAM

clean=colorbars(width=480, height=360, pixel_type="YUY2").Trim(0,-1).KillAudio
YUY2=0
GScript("""
for(i=0,FRAMES-1) {
c=addcomets(clean)
YUY2 = (YUY2.IsClip) ? YUY2++c : c
}
""")
return YUY2 # DONT CONVERT IN HERE, SAVE AS PulseClipYUY2.AVI.

# Below by JMac698

function whiteline(clip template) {
template.blankclip(width=template.width/4, height=1,color_yuv=$EB8080) # $EB=235
}

function blackline(clip template) {
template.blankclip(width=template.width/4, height=1,color_yuv=$108080) # $10=16
}

function addcomets(clip v){
#Add three, randomly positioned, white and black lines to an existing video
v
w=width
h=height
overlay(whiteline,x=rand(w*3/4),y=rand(h))
overlay(blackline,x=rand(w*3/4),y=rand(h))
overlay(whiteline,x=rand(w*3/4),y=rand(h))
overlay(blackline,x=rand(w*3/4),y=rand(h))
overlay(whiteline,x=rand(w*3/4),y=rand(h))
overlay(blackline,x=rand(w*3/4),y=rand(h))
}


DepulseTest.avs

# DepulseTest.avs
# Requires Depulse, RT_Stats.

LoadPlugin("G:\DePulse.dll") # Or in Plugins

VIDEONAME = "G:\PulseClipYUY2.avi" # See MakePulseClip.avs
#VIDEONAME = "F:\V\Cabaret.avi"

### Config ####
H = 180
L = 60
D = 30
Prefix = "GLB"
Show = True
STACK = True
###############
src = AviSource(VIDEONAME) # Original, whatever colorspace
YUY2 = src.ConvertToYUY2 # Original As YUY2 # EDIT: Maybe Add Interlaced=true where required

FIX = YUY2.DePulse(h=H,l=L,d=D)
DBG = YUY2.DePulse(h=H,l=L,d=D,debug=True,prefix=PREFIX)

Frms = 0
TotCnt = 0

SSS = """
cnt = GLB_PulseLines # ScanLines with pulses, this frame
TotCnt= TotCnt + cnt # Total pulsing scalines in clip so far
Frms = Frms + ((cnt>0) ? 1 : 0) # Number of frames with pulsing scanlines
(SHOW) ? RT_Subtitle("%d] Cnt=%d : TotCnt=%d : Frms=%d",current_frame,cnt,TotCnt,Frms) : Last
Return Last
"""

# Important, Need After_Frame=True, need proc frame BEFORE SSS script, else numbers (GLB_PulseLines) off by 1 frame
DBG = DBG.Scriptclip(SSS,After_Frame=True)

LFT=YUY2.RtSub("Original") HMID =LFT.BlankClip(width=8,Color=$FF00FF) RGT=FIX.RtSub("DePulse")
TOP=StackHorizontal(LFT,HMID,RGT)
BOT = StackHorizontal(DBG.RtSub("Debug + Count of pulsing scanlines"),HMID,FIX.ClipDelta(YUY2,amp=True).RtSub("Delta (Depulse-Original)"))
VMID =TOP.BlankClip(height=8,Color=$FF00FF)
STK4=TOP.StackVertical(VMID,BOT)

(STACK) ? STK4 : FIX

Return Last.ConvertToYV12

# Stack Overhead RT_Subtitle Text, with optional FrameNumber shown.
Function RtSub(clip c,string Tit, Bool "ShowFrameNo") {
c.BlankClip(height=20)
(Default(ShowFrameNo,False)) ? ScriptClip("""RT_Subtitle("%d] %s",current_frame,""""+Tit+"""")""") : RT_Subtitle("%s",Tit)
Return StackVertical(c).AudioDubEx(c)
}

# Return Clip Difference of input clips (amp==true = Amplified, show==true = show background)
Function ClipDelta(clip clip1,clip clip2,bool "amp",bool "show") {
amp=Default(amp,false)
show=Default(show,false)
c2=clip1.levels(128-32,1.0,128+32,128-32,128+32).greyscale()
c1=clip1.subtract(clip2)
c1=(amp)?c1.levels(127,1.0,129,0,255):c1
return (show)?c1.Merge(c2):c1
}


Guinea pigs required.

EDIT: @ Shae,


Well to tell what frames have been 'touched'.
Int, count of scanlines detected gives a little more info that simple bool, perhaps of greater use, perhaps not.



Pixels are process individually with no regard to those left or right, if they dont fit with the h, l, and d args they are not touched.

Snippet

int PulseLines = 0;

for (int j=1; j<height-2; j+=2) {
int PulsedPix=0;
for (i=0; i<rowsize; i+=4) {
// SJ, Here bottom field current
// j limit, bot field of second last interlaced pair (last line processed, j=height-3)
// LUMA LEFT
col0=srcp[(j )*pitch + i]; // j bot field this pix
col1=srcp[(j-1)*pitch + i]; // j-1 top field pix above
col2=srcp[(j+1)*pitch + i]; // j+1 top field pix below

col3=srcpaf[(j-1)*pitchaf + i]; // j-1 nxt frame top field pix above
col4=srcpaf[(j+1)*pitchaf + i]; // j+1 nxt frame top field pix below

col5=srcpbf[j*pitchbf + i]; // j prv frame curr field

col6=srcpaf[j*pitchaf + i]; // j nxt frame curr field

col7=srcp[(j-1)*pitch + i - ((j==1) ? 0 : pitch)]; // j-2 curr field above pix (2 above)
col8=srcp[(j+1)*pitch + i + ((j==height-3) ? 0 : pitch)];// j+2 curr field below pix (2 below)

lo = col0 - _d;

// If curr pix > h arg AND all surrounding pixels < (curr pix - d arg) then depulse.
if ( (col0>_h)&&(col1<lo)&&(col2<lo)&&(col3<lo)&&(col4<lo)&&(col5<lo)&&(col6<lo)&&(col7<lo)&&(col8<lo) ) {
dstp[i]=(col1+col2+col3+col4+col5+col6+col7+col8)/8;
if (_debug) {
dstp[i+1]=250; // U
dstp[i+3]=250; // V
}
++PulsedPix; // single pixel on scanline depulsed, if non zero at end of scanline then scanline was depulsed.
}
hi = col0 + _d;

// If curr pix < l arg AND all surrounding pixels > (curr pix + d arg) then depulse.
if ( (col0<_l)&&(col1>hi)&&(col2>hi)&&(col3>hi)&&(col4>hi)&&(col5>hi)&&(col6>hi)&&(col7>hi)&&(col8>hi) ) {
dstp[i]=(col1+col2+col3+col4+col5+col6+col7+col8)/8;
if (_debug) {
dstp[i+1]=10; // U
dstp[i+3]=10; // V
}
++PulsedPix;
}



EDIT: I did try counting contiguous depulsed pixels (both dark and light separately), but showed up dozens of comets where only a few visible, did not seem useful.
EDIT: Would total number of depulsed pixels be better measure than count of scanlines ? EDIT: Probably not.

Thanks but can you compile x64 ver??

StainlessS
1st September 2021, 15:37
Thanks but can you compile x64 ver??
See Here: https://forum.doom9.org/showthread.php?p=1951107#post1951107

EDIT: Also added Version Resource to dll's.

TCmullet
11th May 2024, 22:06
I have recoded Fan Filter and now included in modplus plugin as one of the functions. Using it with following script, I could eliminate white streaks

plus and minus values can be increased if required. Please note that parameter list has changed from previous FanFilter

OH! I am hoping so! DePulse is working wonderfully... to a point. But I have some streaks (video dropouts from using BAD tape brand). And because DePulse is working only on 1-pixel high comets, some of mine are 3-4 pixels. I wasn't sure if your Fan() can work on a wider streak (taller, that is). Oh, and your plugin isn't "modPlus"; it's "manyPlus". (Maybe you changed the name since 2017.)

DePulse has to have interlaced. I hope Fan() can handle progressive.

And YES, a thread dedicated to DePulse would be NICE, IMHO. StainlessS, maybe you could incorporate Fan() logic (if it does what I need) into DePulse, allowing it to be triggered by a parameter "linesize" by which we tell DePulse that my streaks can be "linesize" pixels high. I could see isolating Fan() logic invocation from normal DePulse such that if linesize=1, do normal Depulse logic, but if linesize > 1, ONLY touch streaks that are > 1 and up to linesize high. We'd use two invocations. Just some ideas. Now to go and try out Fan().

Update: I've totally misunderstood Fan(), I think. I don't think it can do anything for me.

Here's the 2 adjacent frames in my video illustrating DePulse's failure to eliminate a streak because it's more than 1 pixel high:
https://i.imgur.com/lkVy7UP.jpg
https://i.imgur.com/VN0cpOG.jpg

DTL
12th May 2024, 06:53
For such small and single frame defect you can start from simple temporal median like Spotless() script.