Skip to content

Commit 29ec686

Browse files
happycubeclaude
andcommitted
Add mb_measure.py: coherent-average NTC-7 multiburst level tool
Coherently averages the line-20 waveform across same-parity combination fields before running measure_ntc7_multiburst, so the sine-fit p-p is unbiased by random noise. Gates candidate fields on packet-frequency plausibility (monotonic, near-nominal) to avoid mislocking on content (e.g. Phil Collins CLV disc). Reports per-packet IRE and dB vs 0.5 MHz. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e7ad49b commit 29ec686

1 file changed

Lines changed: 68 additions & 0 deletions

File tree

analysis/mb_measure.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""Coherent-average NTC-7 multiburst level measurement.
2+
3+
Usage: python3 analysis/mb_measure.py <file.tbc> [max_frames]
4+
5+
Collects up to 10 same-parity fields carrying the 6-packet NTC-7 combination
6+
multiburst, coherently averages the line-20 waveform (killing random noise so
7+
the sine-fit p-p is unbiased), then measures per-packet peak-to-peak IRE.
8+
Prints levels and dB relative to the 0.5 MHz packet. Exits non-zero with a
9+
pattern summary if no multiburst is found.
10+
"""
11+
import sys
12+
import copy
13+
import numpy as np
14+
sys.path.insert(0, "analysis")
15+
from tbc_common import (load_video, measure_ntc7_multiburst,
16+
detect_patterns, summarize_patterns)
17+
18+
NOM = [0.5, 1.0, 2.0, 3.0, 3.58, 4.2]
19+
20+
21+
def main():
22+
path = sys.argv[1]
23+
max_frames = int(sys.argv[2]) if len(sys.argv) > 2 else 10
24+
params, fields, _ = load_video(path)
25+
scan = min(len(fields), 2 * max_frames + 20)
26+
27+
# A real combination multiburst has six packets whose measured
28+
# frequencies climb monotonically and sit near the nominal set; content
29+
# or noise can spuriously yield six "packets" at scrambled frequencies
30+
# (seen on the Phil Collins CLV disc), so gate on frequency plausibility.
31+
def is_valid(pk):
32+
if len(pk) != 6:
33+
return False
34+
freqs = [p[1] for p in pk]
35+
if any(b <= a for a, b in zip(freqs, freqs[1:])):
36+
return False
37+
return all(abs(f - nom) < 0.4 for f, nom in zip(freqs, NOM))
38+
39+
# group valid multiburst fields by parity; coherent averaging needs one
40+
groups = {True: [], False: []}
41+
for f in fields[:scan]:
42+
if is_valid(measure_ntc7_multiburst(f)):
43+
groups[f.isFirstField].append(f)
44+
45+
grp = max(groups.values(), key=len)[:10]
46+
if not grp:
47+
det = detect_patterns(params, fields, max_fields=16)
48+
print(f"{path}: NO multiburst found. Patterns present:")
49+
for line in summarize_patterns(det, fields):
50+
print(" " + line)
51+
sys.exit(2)
52+
53+
mean_pic = np.mean([f.dspicture.astype(np.float64) for f in grp], axis=0)
54+
af = copy.copy(grp[0])
55+
af.dspicture = mean_pic
56+
pk = measure_ntc7_multiburst(af)
57+
ref = pk[0][2]
58+
59+
print(f"{path}: {len(grp)} fields "
60+
f"({'first' if grp[0].isFirstField else 'second'} parity), "
61+
f"field indices {[f.field_index for f in grp]}")
62+
for fq, (_, fmeas, pp) in zip(NOM, pk):
63+
print(f" {fq:5.2f} MHz (meas {fmeas:5.3f}) {pp:6.2f} IRE "
64+
f"{20 * np.log10(pp / ref):+6.2f} dB")
65+
66+
67+
if __name__ == "__main__":
68+
main()

0 commit comments

Comments
 (0)