Skip to content

Commit 6e27a5b

Browse files
authored
Merge pull request #1014 from eshaz/ld-wow-smoothing
add option to smooth wow brightness correction
2 parents df1bafe + e4615b2 commit 6e27a5b

3 files changed

Lines changed: 75 additions & 31 deletions

File tree

lddecode/core.py

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1462,7 +1462,9 @@ def __init__(
14621462
initphase=False,
14631463
fields_written=0,
14641464
readloc=0,
1465-
use_threads=True
1465+
use_threads=True,
1466+
wow_level_adjust_smoothing=0,
1467+
wow_interpolation_method="linear"
14661468
):
14671469
self.rawdata = decode["input"]
14681470
self.data = decode
@@ -1497,6 +1499,9 @@ def __init__(
14971499

14981500
self.use_threads = use_threads
14991501

1502+
self.wow_level_adjust_smoothing = wow_level_adjust_smoothing
1503+
self.wow_interpolation_method = wow_interpolation_method
1504+
15001505
@profile
15011506
def process(self):
15021507
self.linelocs1, self.linebad, self.nextfieldoffset = self.compute_linelocs()
@@ -2452,38 +2457,37 @@ def fix_badlines(self, linelocs_in, linelocs_backup_in=None):
24522457

24532458
return linelocs
24542459

2455-
def computewow_scaled(self, kind='linear'):
2460+
def computewow_scaled(self):
24562461
"""Compute how much the line deviates fron expected,
24572462
and scale input samples to output samples
24582463
"""
2459-
if self.interpolated_pixel_locs is None:
2460-
actual_linelocs = np.array(self.linelocs, dtype=np.float64)
2461-
expected_linelocs = np.array([i * self.inlinelen for i in range(len(actual_linelocs))], dtype=np.float64)
2462-
2463-
outscale = self.inlinelen / self.outlinelen
2464-
outsamples = self.outlinecount * self.outlinelen
2465-
outline_offset = (self.lineoffset + 1) * self.outlinelen
2466-
2467-
if kind == 'linear':
2468-
k=1
2469-
bc_type=None
2470-
elif kind == 'quadratic':
2471-
k=2
2472-
bc_type=None
2473-
elif kind == 'cubic':
2474-
k=3
2475-
bc_type='natural'
2476-
2477-
# create a spline that interpolates the exact sample value based on expected vs. actual line locations
2478-
spl = interpolate.make_interp_spline(expected_linelocs, actual_linelocs, k=k, bc_type=bc_type, check_finite=False)
2479-
2480-
# scale up to compute where the output pixel would fall on the interpolated line loc
2481-
scaled_pixel_locs = np.arange(outsamples + outline_offset) * outscale
2482-
2483-
# interpolate the expected pixel location
2484-
self.interpolated_pixel_locs = spl(scaled_pixel_locs)
2485-
# amount of wow for each scaled pixel
2486-
self.wowfactors = spl(scaled_pixel_locs, 1)
2464+
actual_linelocs = np.array(self.linelocs, dtype=np.float64)
2465+
expected_linelocs = np.array([i * self.inlinelen for i in range(len(actual_linelocs))], dtype=np.float64)
2466+
2467+
outscale = self.inlinelen / self.outlinelen
2468+
outsamples = self.outlinecount * self.outlinelen
2469+
outline_offset = (self.lineoffset + 1) * self.outlinelen
2470+
2471+
if self.wow_interpolation_method == 'linear':
2472+
k=1
2473+
bc_type=None
2474+
elif self.wow_interpolation_method == 'quadratic':
2475+
k=2
2476+
bc_type=None
2477+
elif self.wow_interpolation_method == 'cubic':
2478+
k=3
2479+
bc_type='natural'
2480+
2481+
# create a spline that interpolates the exact sample value based on expected vs. actual line locations
2482+
spl = interpolate.make_interp_spline(expected_linelocs, actual_linelocs, k=k, bc_type=bc_type, check_finite=False)
2483+
2484+
# scale up to compute where the output pixel would fall on the interpolated line loc
2485+
scaled_pixel_locs = np.arange(outsamples + outline_offset) * outscale
2486+
2487+
# interpolate the expected pixel location
2488+
self.interpolated_pixel_locs = spl(scaled_pixel_locs)
2489+
# amount of wow for each scaled pixel
2490+
self.wowfactors = spl(scaled_pixel_locs, 1)
24872491

24882492
return self.interpolated_pixel_locs, self.wowfactors
24892493

@@ -2562,6 +2566,7 @@ def downscale(
25622566
wowfactors,
25632567
self.lineoffset,
25642568
outwidth,
2569+
wow_level_adjust_smoothing=self.wow_level_adjust_smoothing
25652570
)
25662571

25672572
if self.rf.decode_digital_audio:
@@ -3510,6 +3515,8 @@ def __init__(
35103515

35113516
self.autoMTF = True
35123517
self.useAGC = extra_options.get("useAGC", True)
3518+
self.wow_level_adjust_smoothing = extra_options.get("wow_level_adjust_smoothing", 0)
3519+
self.wow_interpolation_method = extra_options.get("wow_interpolation_method", "linear")
35133520

35143521
self.verboseVITS = False
35153522

@@ -3893,6 +3900,8 @@ def decodefield(self, start, mtf_level, prevfield=None, initphase=False, redo=Fa
38933900
initphase=initphase,
38943901
fields_written=self.fields_written,
38953902
readloc=rawdecode["startloc"],
3903+
wow_level_adjust_smoothing=self.wow_level_adjust_smoothing,
3904+
wow_interpolation_method=self.wow_interpolation_method
38963905
)
38973906

38983907
# set an object-level variable to make notebook debugging easier

lddecode/main.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def main(args=None):
2222
parser = argparse.ArgumentParser(
2323
description="Extracts audio and video from raw RF laserdisc captures",
2424
epilog=options_epilog,
25+
formatter_class=argparse.RawTextHelpFormatter
2526
)
2627
parser.add_argument("infile", metavar="infile", type=str, help="source file")
2728
parser.add_argument(
@@ -210,6 +211,29 @@ def main(args=None):
210211
help="Strength of deemphasis (default 1.0)",
211212
)
212213

214+
parser.add_argument(
215+
"--wow_level_adjust_smoothing",
216+
type=float,
217+
default=0,
218+
help=(
219+
"Adjusts the amount of smoothing in lines that is performed when compensating for brightness variations caused by wow. (default 0)"
220+
"\nWow calculation is based on position of hsync pulses which is affected by the accuracy of the TBC. "
221+
"\nIf you see vertical brightness variations (banding), setting to a value larger than 0 will smooth the wow adjustment."
222+
)
223+
)
224+
parser.add_argument(
225+
"--wow_interpolation_method",
226+
type=str,
227+
default="linear",
228+
choices=["linear", "quadratic", "cubic"],
229+
help=(
230+
"Sets the type of interpolation spline used to correct wow."
231+
"\n linear [default]"
232+
"\n quadratic"
233+
"\n cubic"
234+
)
235+
)
236+
213237
parser.add_argument(
214238
"-t",
215239
"--threads",
@@ -330,6 +354,8 @@ def main(args=None):
330354
"audio_filterwidth": args.audio_filterwidth,
331355
"AC3": args.AC3,
332356
"use_profiler": args.use_profiler,
357+
"wow_level_adjust_smoothing": args.wow_level_adjust_smoothing,
358+
"wow_interpolation_method": args.wow_interpolation_method
333359
}
334360

335361
if vid_standard == "NTSC" and args.NTSC_color_notch_filter:

lddecode/utils.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def scale(buf, begin, end, tgtlen, mult=1):
7171

7272
# Scales and compensates for wow-induced playback-speed variations
7373
@njit(nogil=True, cache=True, fastmath=True)
74-
def scale_field(buf, dsout, interpolated_pixel_locs, wowfactors, lineoffset, outwidth, level_adjust_threshold = 15):
74+
def scale_field(buf, dsout, interpolated_pixel_locs, wowfactors, lineoffset, outwidth, wow_level_adjust_smoothing = 0, level_adjust_threshold = 15):
7575
# Constants preserved as float32
7676
point_5 = np.float32(0.5)
7777
two = np.float32(2)
@@ -95,6 +95,15 @@ def scale_field(buf, dsout, interpolated_pixel_locs, wowfactors, lineoffset, out
9595
wowfactors
9696
)
9797

98+
if wow_level_adjust_smoothing > 0:
99+
# removes oscillating brightness variations for video with lots of noise around the hsync pulses, i.e. noisy line locations result in noisy wow calculations
100+
# applies a low pass filter that smooths any sudden brightness variations while still being reactive enough to compensate for low frequency wow
101+
alpha = 1 / (wow_level_adjust_smoothing * outwidth)
102+
one_minus_alpha = 1 - alpha
103+
104+
for i in range(1, len(level_adjusts)):
105+
level_adjusts[i] = alpha * level_adjusts[i] + one_minus_alpha * level_adjusts[i-1]
106+
98107
for i in range(lineoffset_out_samples, len(dsout) + lineoffset_out_samples):
99108
# compensates for the amplitude/frequency shift caused by FM demodulation under varying playback speed.
100109
level_adjust = level_adjusts[i]

0 commit comments

Comments
 (0)