Log in

View Full Version : Soft thresholded median sharpening function


*.mp4 guy
6th March 2010, 10:01
Function MedSharp(Clip C, int "thresh", int "rad", int "mode", bool "lp", bool "hp", bool "nr", float "str", int "kernel")
{

_Thr = Default(thresh, 256)
_Rad = Default(rad, 1)
Mode = Default(mode, 1)
str = Default(str, 1.5)
THR = string(_Thr)
RAD = string(_Rad)
RAD2 = string(_Rad)

lp = Default(lp, false)
hp = Default(hp, false)
nr = Default(nr, false)
kernel = Default(kernel, 1)


Lowpass = C
Lowpass = lp == true ? _Rad >= 2 ? Lowpass.NLLV(rad=1).NLLH(rad=1) : Lowpass : Lowpass
Lowpass = lp == true ? _Rad >= 4 ? Lowpass.NLLV(rad=2).NLLH(rad=2) : Lowpass : Lowpass
Lowpass = lp == true ? _Rad == 6 ? Lowpass.NLLV(rad=3).NLLH(rad=3) : Lowpass : Lowpass
Lowpass = lp == true ? _Rad >= 8 ? Lowpass.NLLV(rad=4).NLLH(rad=4) : Lowpass : Lowpass
Lowpass = lp == true ? _Rad == 10 ? Lowpass.NLLV(rad=4).NLLH(rad=5) : Lowpass : Lowpass
Lowpass = lp == true ? _Rad == 12 ? Lowpass.NLLV(rad=4).NLLH(rad=6) : Lowpass : Lowpass
Lowpass = lp == true ? _Rad == 14 ? Lowpass.NLLV(rad=4).NLLH(rad=7) : Lowpass : Lowpass
Lowpass = lp == true ? _Rad >= 16 ? Lowpass.NLLV(rad=4).NLLH(rad=8) : Lowpass : Lowpass
Lowpass = nr == true ? Lowpass.TMed2(rad=_Rad, thresh=thresh, Kernel=Kernel) : Lowpass

Diff = Mode==0 ? MT_Luts(C, Lowpass, mode="med", pixels = "0 "+RAD+" "+RAD+" 0 -"+RAD+" 0 0 -"+RAD ,\
expr = "X Y - X Y - X Y - abs 1 + * X Y - abs 1 + "+THR+" 1 >= "+THR+" 0.5 ^ "+THR+" ? + / - 128 +", u=1,v=1)\
: MT_Luts(C, Lowpass, mode="med", pixels = "0 "+RAD+" "+RAD+" 0 -"+RAD+" 0 0 -"+RAD+" 0 0" ,\
expr = "X Y - X Y - X Y - abs 1 + * X Y - abs 1 + "+THR+" 1 >= "+THR+" 0.5 ^ "+THR+" ? + / - 128 +", u=1,v=1)

Diff = Kernel > 1 ? MT_Luts(C, Lowpass, mode="med", pixels = "-"+RAD2+" -"+RAD2+"-"+RAD2+" "+RAD2+" "+RAD2+" -"+RAD2+" "+RAD2+" "+RAD2+" 0 "+RAD+" "+RAD+" 0 -"+RAD+" 0 0 -"+RAD ,\
expr = "X Y - X Y - X Y - abs 1 + * X Y - abs 1 + "+THR+" 1 >= "+THR+" 0.5 ^ "+THR+" ? + / - 128 +", u=1,v=1) : Diff

Diff = Diff.mt_Lut("X 128 - "+string(str)+" * 128 +")

Diff2 = Diff.NLLV(rad=_Rad).NLLH(rad=_Rad)
Diff = hp == true ? Mt_Makediff(diff, diff2) : Diff
#Diff = hp == true ? Diff.lanczosresize(c.width*2, c.height*2, 0.25, 0.25, taps=3).NLHH().NLHV().pointresize(c.width, c.height) : Diff
#Diff = hp == true ? Diff.NLHH2().NLHV2() : Diff


MT_adddiff(C, Diff, u=2, v=2)

Return(Last)
}

Dependencies
Function TMed2(Clip C, int "thresh", int "rad", int "mode", int "kernel")
{

_Thr = Default(thresh, 256)
_Rad = Default(rad, 1)
Mode = Default(mode, 1)
THR = string(_Thr)
RAD = string(_Rad)
RAD2 = string(_Rad)
kernel = Default(kernel, 1)

Diff = Mode==0 ? MT_Luts(C, C, mode="med", pixels = "0 "+RAD+" "+RAD+" 0 -"+RAD+" 0 0 -"+RAD ,\
expr = "X Y - X Y - X Y - abs 1 + * X Y - abs 1 + "+THR+" 1 >= "+THR+" 0.5 ^ "+THR+" ? + / - 128 +", u=1,v=1)\
: MT_Luts(C, C, mode="med", pixels = "0 "+RAD+" "+RAD+" 0 -"+RAD+" 0 0 -"+RAD+" 0 0" ,\
expr = "X Y - X Y - X Y - abs 1 + * X Y - abs 1 + "+THR+" 1 >= "+THR+" 0.5 ^ "+THR+" ? + / - 128 +", u=1,v=1)
Diff = Kernel > 1 ? MT_Luts(C, C, mode="med", pixels = "-"+RAD2+" -"+RAD2+"-"+RAD2+" "+RAD2+" "+RAD2+" -"+RAD2+" "+RAD2+" "+RAD2+" 0 "+RAD+" "+RAD+" 0 -"+RAD+" 0 0 -"+RAD ,\
expr = "X Y - X Y - X Y - abs 1 + * X Y - abs 1 + "+THR+" 1 >= "+THR+" 0.5 ^ "+THR+" ? + / - 128 +", u=1,v=1) : Diff



MT_Makediff(C, Diff, u=2, v=2)

Return(Last)
}

Function NLHH2(Clip C)
{


B1 = C.BlurH(1, 0.833)
B2 = C.BlurH(2, 0.750)
B3 = C.BlurH(3, 0.944)
B4= C.BlurH(4, 0.981)

B1_D = Mt_Makediff(c, b1, u=1, v=1)
B2_D = Mt_MakeDiff(b2, c, u=1, v=1)
B3_D = Mt_MakeDiff(c, b3, u=1, v=1)
B4_D = Mt_MakeDiff(b4, c, u=1, v=1)

B2_DT = Mt_LutXY(B1_D, B2_D, " X 128 - Y 128 - X 128 - abs Y 128 - abs * 1 + * X 128 - abs Y 128 - abs * Y 128 - abs 0.75 * X 128 - abs - 0 > \
Y 128 - abs 0.75 * X 128 - abs - 0 ? X 128 - abs 0 > X 128 - abs -1 X 128 - abs 0 > X 128 - 1 ? / ^ 1 ? / 1 * + 1 + / + 128 + ", u=1, v=1)
B3_DT = Mt_LutXY(B2_DT, B3_D, " X 128 - Y 128 - X 128 - abs Y 128 - abs * 1 + * X 128 - abs Y 128 - abs * Y 128 - abs 2.25 * X 128 - abs - 0 > \
Y 128 - abs 2.25 * X 128 - abs - 0 ? X 128 - abs 0 > X 128 - abs -1 X 128 - abs 0 > X 128 - 1 ? / ^ 1 ? / 1 * + 1 + / + 128 + ", u=1, v=1)
B4_DT = Mt_LutXY(B3_DT, B4_D, " X 128 - Y 128 - X 128 - abs Y 128 - abs * 1 + * X 128 - abs Y 128 - abs * Y 128 - abs 6.75 * X 128 - abs - 0 > \
Y 128 - abs 6.75 * X 128 - abs - 0 ? X 128 - abs 0 > X 128 - abs -1 X 128 - abs 0 > X 128 - 1 ? / ^ 1 ? / 1 * + 1 + / + 128 + ", u=1, v=1)

Mt_AddDiff(B4_DT, C)

Return(last)
}

Function NLHV2(Clip C)
{


B1 = C.BlurH(1, 0.833)
B2 = C.BlurH(2, 0.750)
B3 = C.BlurH(3, 0.944)
B4= C.BlurH(4, 0.981)

B1_D = Mt_Makediff(c, b1, u=1, v=1)
B2_D = Mt_MakeDiff(b2, c, u=1, v=1)
B3_D = Mt_MakeDiff(c, b3, u=1, v=1)
B4_D = Mt_MakeDiff(b4, c, u=1, v=1)

B2_DT = Mt_LutXY(B1_D, B2_D, " X 128 - Y 128 - X 128 - abs Y 128 - abs * 1 + * X 128 - abs Y 128 - abs * Y 128 - abs 0.75 * X 128 - abs - 0 > \
Y 128 - abs 0.75 * X 128 - abs - 0 ? X 128 - abs 0 > X 128 - abs -1 X 128 - abs 0 > X 128 - 1 ? / ^ 1 ? / 1 * + 1 + / + 128 + ", u=1, v=1)
B3_DT = Mt_LutXY(B2_DT, B3_D, " X 128 - Y 128 - X 128 - abs Y 128 - abs * 1 + * X 128 - abs Y 128 - abs * Y 128 - abs 2.25 * X 128 - abs - 0 > \
Y 128 - abs 2.25 * X 128 - abs - 0 ? X 128 - abs 0 > X 128 - abs -1 X 128 - abs 0 > X 128 - 1 ? / ^ 1 ? / 1 * + 1 + / + 128 + ", u=1, v=1)
B4_DT = Mt_LutXY(B3_DT, B4_D, " X 128 - Y 128 - X 128 - abs Y 128 - abs * 1 + * X 128 - abs Y 128 - abs * Y 128 - abs 6.75 * X 128 - abs - 0 > \
Y 128 - abs 6.75 * X 128 - abs - 0 ? X 128 - abs 0 > X 128 - abs -1 X 128 - abs 0 > X 128 - 1 ? / ^ 1 ? / 1 * + 1 + / + 128 + ", u=1, v=1)

Mt_AddDiff(B4_DT, C)

Return(last)
}


Function NLLH(Clip C, int "rad")
{


Rad = Default(Rad, 1)

B1 = C.BlurH(1*rad, 0.439)
B2 = C.BlurH(3*rad, 0.833)
B3 = C.BlurH(5*rad, 0.934)
B4= C.BlurH(7*rad, 0.983)

B1_D = Mt_Makediff(B1, C, u=1, v=1)
B2_D = Mt_MakeDiff(C, B2, u=1, v=1)
B3_D = Mt_MakeDiff(B3, C, u=1, v=1)
B4_D = Mt_MakeDiff(C, B4, u=1, v=1)

B2_DT = Mt_LutXY(B1_D, B2_D, " X 128 - Y 128 - X 128 - abs Y 128 - abs * 1 + * X 128 - abs Y 128 - abs * Y 128 - abs 1.915 * X 128 - abs - 0 > \
Y 128 - abs 1.915 * X 128 - abs - 0 ? X 128 - abs 0 > X 128 - abs -1 X 128 - abs 0 > X 128 - 1 ? / ^ 1 ? / 1 * + 1 + / + 128 + ", u=1, v=1)
B3_DT = Mt_LutXY(B2_DT, B3_D, " X 128 - Y 128 - X 128 - abs Y 128 - abs * 1 + * X 128 - abs Y 128 - abs * Y 128 - abs 2.025 * X 128 - abs - 0 > \
Y 128 - abs 2.025 * X 128 - abs - 0 ? X 128 - abs 0 > X 128 - abs -1 X 128 - abs 0 > X 128 - 1 ? / ^ 1 ? / 1 * + 1 + / + 128 + ", u=1, v=1)
B4_DT = Mt_LutXY(B3_DT, B4_D, " X 128 - Y 128 - X 128 - abs Y 128 - abs * 1 + * X 128 - abs Y 128 - abs * Y 128 - abs 2.077 * X 128 - abs - 0 > \
Y 128 - abs 2.077 * X 128 - abs - 0 ? X 128 - abs 0 > X 128 - abs -1 X 128 - abs 0 > X 128 - 1 ? / ^ 1 ? / 1 * + 1 + / + 128 + ", u=1, v=1)

Mt_AddDiff(B4_DT, C)

Return(last)
}

Function NLLV(Clip C, int "rad")
{


Rad = Default(Rad, 1)

B1 = C.BlurV(1*rad, 0.439)
B2 = C.BlurV(3*rad, 0.833)
B3 = C.BlurV(5*rad, 0.934)
B4= C.BlurV(7*rad, 0.983)

B1_D = Mt_Makediff(B1, C, u=1, v=1)
B2_D = Mt_MakeDiff(C, B2, u=1, v=1)
B3_D = Mt_MakeDiff(B3, C, u=1, v=1)
B4_D = Mt_MakeDiff(C, B4, u=1, v=1)

B2_DT = Mt_LutXY(B1_D, B2_D, " X 128 - Y 128 - X 128 - abs Y 128 - abs * 1 + * X 128 - abs Y 128 - abs * Y 128 - abs 1.915 * X 128 - abs - 0 > \
Y 128 - abs 1.915 * X 128 - abs - 0 ? X 128 - abs 0 > X 128 - abs -1 X 128 - abs 0 > X 128 - 1 ? / ^ 1 ? / 1 * + 1 + / + 128 + ", u=1, v=1)
B3_DT = Mt_LutXY(B2_DT, B3_D, " X 128 - Y 128 - X 128 - abs Y 128 - abs * 1 + * X 128 - abs Y 128 - abs * Y 128 - abs 2.025 * X 128 - abs - 0 > \
Y 128 - abs 2.025 * X 128 - abs - 0 ? X 128 - abs 0 > X 128 - abs -1 X 128 - abs 0 > X 128 - 1 ? / ^ 1 ? / 1 * + 1 + / + 128 + ", u=1, v=1)
B4_DT = Mt_LutXY(B3_DT, B4_D, " X 128 - Y 128 - X 128 - abs Y 128 - abs * 1 + * X 128 - abs Y 128 - abs * Y 128 - abs 2.077 * X 128 - abs - 0 > \
Y 128 - abs 2.077 * X 128 - abs - 0 ? X 128 - abs 0 > X 128 - abs -1 X 128 - abs 0 > X 128 - 1 ? / ^ 1 ? / 1 * + 1 + / + 128 + ", u=1, v=1)

Mt_AddDiff(B4_DT, C)

Return(last)
}

Function blurH(clip c, int "rad", Float "CW")
{

Rad = Default(rad, 1)
CW = Default(CW, 0.5)

Center = C
Left = C.PointResize(C.width, C.height, -rad, 0, C.width, C.height)
Right = C.PointResize(C.width, C.height, rad, 0, C.width, C.height)

Average(Center, CW/2, Left, (1-CW)/2, Right, (1-CW)/2, Center, CW/2)

Return(last)
}

Function BlurV(clip c, int "rad", Float "CW")
{

Rad = Default(rad, 1)
CW = Default(CW, 0.5)

Center = C
Down = C.PointResize(C.width, C.height, 0, -rad, C.width, C.height)
Up = C.PointResize(C.width, C.height, 0, rad, C.width, C.height)

Average(Center, CW/2, Down, (1-CW)/2, Up, (1-CW)/2, Center, CW/2)

Return(last)
}

Quick tests look promising, it appears to completely avoid aliasing the way normal median sharpening does, but I have tested very little. Str is exponential, 4 is twice as strong as 1, etc. This was hacked together out of a bit of something else, so some of the internals are a little odd (like str) but everything looks like its working well.

[edit] forgive the wideness, will fix later
[edit] fixed (mostly, still too wide, but I don't want to split the equations)

Didée
6th March 2010, 13:54
Hey, that's a nice idea! Alas, the available means in Avisynth are somewhat restricted, you know.

With "thresholded median", I rather understand that the thresholdig (be it soft or hard) is applied to each single neighborhood pixel while doing the median calculation. What we have here is a plain median, where the median's result is furtheron mix-calculated with the original pixel. That's a non-trivial difference.
However, the former way can not be done at all with MaskTools, any access "into" the basic operation is not possible. The latter way, as you are doing, is infact the only way that's open from the script level. (I'm only noting, not complaining!)

But I wonder a bit about the median neighborhood. E.g. with rad=2, you're using only the following pixels from a 5x5 neighborhood:

x x x x x
x x x x x
x x x x x
x x x x x
x x x x x

With increasing radii, the results get less and less precise. rad=1 is a bit rough, rad=2 is pretty rough, and with rad=3 (or at latest with 4), the result starts to be purely random (too little pixels, from too far away, to produce a meaningful result).

Shouldn't there be mt_square used instead, to take all pixels into account? Of course, things will get pretty slow then, especially with larger radii, but that's how median and mt_luts are.


A possible addition: make the treshold adaptive to the actual min-max span of the used neighborhood. Idea? ;)

*.mp4 guy
7th March 2010, 21:36
Oddly enough, adding in the corners doesn't really help (at least not at small radii).

Also, the median is calculated after the limiting, iirc. From documentation:
mt_luts takes two clips into account. It'll compute the "mode" operation over the
result of the lut expression, with the pixel of the first clip as x, and the neighbouring
pixel of the second clip as y.

So it goes.

1 calculate difference between center pixel and median neighborhood pixels

2 soft-limit each difference

3 find median of differences

4 add median difference to source

Also, if the source is blurry at radii X (and needs to be sharpened) then there is correlation at radii X, even excluding the extra pixels, its a bit non-optimal, but there are other problems with using median sharpening at high radii that this avoids besides the speed problem.

Didée
8th March 2010, 10:47
Jeeze .... my last post clearly was a case of "unwrap candy, throw candy into dustbin, put paper into mouth." :D

Thanks for the reminder on the order-of-calculation in mt_luts. It's a more powerful tool than it looks at first glance. ;)

Even more funnily, somehow I had stopped reading after "Soft thresholded median|..." - thought it was just a kind of soft-median, but didn't see that it's actually a sharpener. [ouch]

I'm still a bit puzzeled about "precision" - if only a few random pixels out of a rather big neighborhood are evaluated, it just doesn't seem right. But hey, apart from all theorizing, the empiric test is a strong argument, too. And after simply giving a try on that function, conclusion is:

This baby seems to work pretty good. Period. :)

Didée
9th March 2010, 13:04
To compensate for my silliness above, here are two suggestions. (Hopefully, more constructive this time.);)

Firstly, just about boring syntax:
[[edit] forgive the wideness, will fix later
[edit] fixed (mostly, still too wide, but I don't want to split the equations)
Quite a few characters can be saved by not calling "string(var)" several times. Using a string variable makes things shorter. Example how I did locally:
(Took the freedom to also delete leading-blanks, end-blanks, and double-blanks from the strings.)
_Thr = Default(thresh, 256)
_Rad = Default(rad, 1)
Mode = Default(mode, 1)

THR = string(_Thr)
RAD = string(_Rad)

Diff = Mode==0 ? MT_Luts(C, C, mode="med", pixels = "0 "+RAD+" "+RAD+" 0 -"+RAD+" 0 0 -"+RAD ,\
expr = "X Y - X Y - X Y - abs 1 + * X Y - abs 1 + "+THR+" 1 >= "+THR+" 0.5 ^ "+THR+" ? + / - 128 +", u=1,v=1)\
: MT_Luts(C, C, mode="med", pixels = "0 "+RAD+" "+RAD+" 0 -"+RAD+" 0 0 -"+RAD+" 0 0" ,\
expr = "X Y - X Y - X Y - abs 1 + * X Y - abs 1 + "+THR+" 1 >= "+THR+" 0.5 ^ "+THR+" ? + / - 128 +", u=1,v=1)
If that's still too wide, use . ;)


Ah, boring, isn't it. More interestingly, a technical change. I found that using only 4 pixels from the neighborhood can indeed cause a drawback: if there is any strictly-horizontal or strictly-vertical detail, then most of it will not get any sharpening. (On a straight line, 3 of the 5 samples are on the line, so the median won't bite.)

Surely that's by intention:D, but it could be interpreted as a shortcoming. The issue can be pretty much avoided by extending the kernel to 8 neighbors, using 4 additional pixels from the diagonals:

(Now the strings get awfully long again....)

_Thr = Default(thresh, 256)
_Rad = Default(rad, 1)
Mode = Default(mode, 1)

THR = string(_Thr)
RAD = string(_Rad)
RAD2 = string( round(_Rad/1.4142) )

Diff = Mode == 0 ? MT_Luts(C, C, mode="med", pixels = "-"+RAD2+" -"+RAD2+"-"+RAD2+" "+RAD2+" "+RAD2+" -"+RAD2+" "+RAD2+" "+RAD2+" 0 "+RAD+" "+RAD+" 0 -"+RAD+" 0 0 -"+RAD ,\
expr = "X Y - X Y - X Y - abs 1 + * X Y - abs 1 + "+THR+" 1 >= "+THR+" 0.5 ^ "+THR+" ? + / - 128 +", u=1,v=1)\
: MT_Luts(C, C, mode="med", pixels = "-"+RAD2+" -"+RAD2+"-"+RAD2+" "+RAD2+" "+RAD2+" -"+RAD2+" "+RAD2+" "+RAD2+" 0 "+RAD+" "+RAD+" 0 -"+RAD+" 0 0 -"+RAD+" 0 0" ,\
expr = "X Y - X Y - X Y - abs 1 + * X Y - abs 1 + "+THR+" 1 >= "+THR+" 0.5 ^ "+THR+" ? + / - 128 +", u=1,v=1)
MT_adddiff(C, Diff, u=2, v=2)

Could be integrated by extending the "mode" parameter, or by "fast=true/false", or by "kernel=4/8", or whatever.

*.mp4 guy
9th March 2010, 14:42
Well, the problem with the diagonals, and with large medians in general, is that all the pixels have the same "weight", when they really shouldn't. Anyway, If you want to force sharpening all the time, that is what mode 0 is for, by excluding the center value, you get sharpening no matter what (though its not exactly a median anymore, it works fine).

Expanding on the pixel positioning problem, the basic situation is that you want all of the pixels in your median calculation to have the same frequency relation to the center pixel, furthermore, you want them to all be of the highest correlation with the center pixel (for any given radius X) the correct way to solve this problem is to use all of the pixels needed to accurately represent a given radius, and then weight them to the correct proportions, but this is slow and causes implementation problems in avisynth.

However, I'll see what adding in the diagonals looks like, also, new version in first post, with some experimental features (I know things are really unoptimized, that will be addressed when I know what works and what doesn't)

Didée
9th March 2010, 15:04
Anyway, If you want to force sharpening all the time, that is what mode 0 is for, by excluding the center value, you get sharpening no matter what (though its not exactly a median anymore, it works fine).
- And that's a question indeed: if it is not a median, what IS it, then?

If the "pixel situation" is like this ...
o o o o o o o
o o o o o o o
x x x x x x x
o o o o o o o
o o o o o o o... then it's not quite clear to me what is supposed to happen.


to use all of the pixels needed to accurately represent a given radius, and then weight them to the correct proportions, but this is slow and causes implementation problems in avisynth.
Exactly that. And also, exactly that. ;)


However, I'll see what adding in the diagonals looks like,
Well, the most obvious difference (apart from even stronger enhancement of horizontal/vertical features, compared to mode=0) is that the overall effect is stronger.

*.mp4 guy
9th March 2010, 15:17
What happens with 4 values is that the median two values are found and then averaged, which still works pretty well, though by all rights it shouldn't.

As to the differences when using the corners, well, it definitely is stronger, but in the one test I did, I couldn't call it better. it amplified the median artifacts, and skewed the sharpening to a lower frequency level, which reduced overall high frequency sharpening, which is the opposite of what I want.

[edit]

Did another test, it looked alright this time, it might be faster for really strong sharpening, don't think it will look better though.

[edit2]

The larger kernel is stronger and more regular, while the smaller kernel can get more "sharpness" into the high frequencies, so there is a trade off of sorts. I might have to cobble together a weighted median filter now, since larger kernels do have some clear advantages, meh.

rkalwaitis
29th March 2010, 07:47
*.mp4 guy,

I was interested in seeing what you finally came up with for this sharpening method.

*.mp4 guy
11th August 2010, 07:02
Hopefully this doesn't count as necro posting, but I had forgotten about this thread. When I was working on doing things correctly, I did a test run which took an incredible amount of time to accomplish essentially nothing. Then a bit later I had an idea on how to cheat to get something similar with a still large, but now manageable speed hit, its definitely not "correct" but it works.

Function MedSharp2(Clip C, int "thresh", int "rad", int "mode", bool "lp", bool "hp", bool "nr", float "str", int "kernel")
{

_Thr = Default(thresh, 256)
_Rad = Default(rad, 1)
Mode = Default(mode, 1)
str = Default(str, 1)
THR = string(_Thr)
RAD = string(_Rad)
RAD2 = string(_Rad)

lp = Default(lp, false)
hp = Default(hp, false)
nr = Default(nr, false)
kernel = Default(kernel, 2)


inputclipproperties = c
blank = BlankClip(length=inputclipproperties.framecount, width=inputclipproperties.width,\
height=inputclipproperties.height, pixel_type="rgb32", fps=inputclipproperties.framerate,\
fps_denominator=1, audio_rate=inputclipproperties.Audiorate, stereo=true, sixteen_bit=true,\
color=$828282).converttoyv12()

Lowpass1 = C.NLLV(rad=1).NLLH(rad=1)
Lowpass_Return = lp == true ? _Rad >= 2 ? Lowpass1 : C : C

Lowpass_NR_1 = Lowpass_Return.TMed2(rad=_Rad, thresh=_Thr, Kernel=1)
Lowpass_NR_2 = Lowpass_Return.TMed2(rad=_Rad, thresh=_Thr/4, Kernel=2)

Diff1 = MT_Luts(C, Lowpass_NR_1, mode="med", pixels = "0 "+RAD+" "+RAD+" 0 -"+RAD+" 0 0 -"+RAD+" 0 0" ,\
expr = "X Y - X Y - X Y - abs 1 + * X Y - abs 1 + "+THR+" 1 >= "+THR+" 0.5 ^ "+THR+" ? + / - 128 +", u=1,v=1)
Diff2 = MT_Luts(C, Lowpass_NR_2, mode="med", pixels = "-"+RAD2+" -"+RAD2+"-"+RAD2+" "+RAD2+" "+RAD2+" -"+RAD2+" "+RAD2+" "+RAD2+" 0 "+RAD+" "+RAD+" 0 -"+RAD+" 0 0 -"+RAD ,\
expr = "X Y - X Y - X Y - abs 1 + * X Y - abs 1 + "+THR+" 2 * 1 >= "+THR+" 2 * 0.5 ^ "+THR+" 2 * ? + / - 128 +", u=1,v=1)

mask1 = Mt_Lut(Diff1, " X 128 - 255 * ", u=1, v=1)#.NLLV().NLLH()
#mask2 = Mt_Lut(Diff2, " X 128 - 255 * ", u=1, v=1)#.NLLV().NLLH()

s1 = mt_merge(Diff2, blank, mask1).mt_Lut("X 128 - "+string(str)+" 2 / * 128 +")
s1b = s1.NLLV().NLLH()
s1s = mt_makediff(s1, s1b)
s2 = Diff1.mt_Lut("X 128 - "+string(str)+" * 128 +")
s2b = s1.NLLV().NLLH()
s2s = mt_makediff(s2, s2b)


out = MT_adddiff(C, s1s, u=2, v=2)
out = MT_adddiff(out, s2s, u=2, v=2)
out

Return(Last)
}

This predates ReCon which I consider to be generally better, but I coded the solution a while ago, and it works fine. All it does is use the larger radius filter on pixels that the lower radius doesn't operate on, and the smaller radius the remainder of the time.

The last version I posted also had the following features, but I don't think I explained them. A lowpass is applied to the neighbor pixels when using radius 2 because it reduces noise amplification, while otherwise performing about the same. I didn't code for radii larger then 2, because they never looked acceptable to me, the medians start to do weird things and distort the image. Also, neighborhood pixels are themselves put through a median smoothing operation before being used for sharpening, it reduces noise and does not significantly effect sharpening strength. There is additionally some fiddling with the frequency components of the sharpening before applying them to the picture.

[edit] also it is too wide again, and some of the arguments don't do anything anymore.