mirror of
https://github.com/smittix/intercept.git
synced 2026-07-30 11:38:09 -07:00
fix: Expand Scottie sync deviation search window to fix under-correction
The slant correction was severely under-correcting because bwd=50 caused the sync deviation measurements to saturate after only ~25 lines (for a 2-sample/line SDR clock drift). Lines 25-256 all reported deviation=-50, pulling the linear regression slope toward zero. Increase bwd and fwd to 800 samples each — sufficient to track cumulative drift from up to ~±200 ppm SDR clock offset across the full 256-line image. Also use a full-sync-length (432-sample) Goertzel window instead of 1/3 length, giving ~111 Hz frequency resolution to cleanly separate the 1200 Hz sync tone from 1500 Hz pixel data. Search is stepped at 5 samples (~0.1 ms) for efficiency, keeping the goertzel_batch batch size at ~320 windows/line. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+26
-13
@@ -246,21 +246,33 @@ class SSTVImageDecoder:
|
|||||||
# slant correction without touching pos or consumed —
|
# slant correction without touching pos or consumed —
|
||||||
# so a noisy/false measurement never corrupts the decode.
|
# so a noisy/false measurement never corrupts the decode.
|
||||||
r_samples = self._channel_samples[-1]
|
r_samples = self._channel_samples[-1]
|
||||||
bwd = min(50, pos)
|
# Large window to cover cumulative SDR clock drift over
|
||||||
fwd = max(0, len(self._buffer) - pos
|
# the full image. bwd=50 saturates after ~25 lines for
|
||||||
- self._sync_samples - self._porch_samples
|
# a 2-sample/line drift; 800 samples covers ±200 ppm.
|
||||||
- r_samples)
|
bwd = min(800, pos)
|
||||||
fwd = min(fwd, self._sync_samples)
|
remaining = len(self._buffer) - pos
|
||||||
|
fwd = min(800, max(
|
||||||
|
0,
|
||||||
|
remaining - self._sync_samples
|
||||||
|
- self._porch_samples - r_samples))
|
||||||
deviation: float | None = None
|
deviation: float | None = None
|
||||||
if bwd + fwd > 0:
|
if bwd + fwd > 0:
|
||||||
region_start = pos - bwd
|
region_start = pos - bwd
|
||||||
sync_region = self._buffer[
|
sync_region = self._buffer[
|
||||||
region_start: pos + self._sync_samples + fwd]
|
region_start: pos + self._sync_samples + fwd]
|
||||||
win = max(20, self._sync_samples // 3)
|
# Full-sync-length window gives best freq resolution
|
||||||
n_win = len(sync_region) - win + 1
|
# (~111 Hz at 48 kHz) to cleanly separate 1200 Hz
|
||||||
if n_win > 0:
|
# sync from 1500 Hz pixel data.
|
||||||
windows = np.lib.stride_tricks.sliding_window_view(
|
win = self._sync_samples
|
||||||
sync_region, win)
|
n_raw = len(sync_region) - win + 1
|
||||||
|
if n_raw > 0:
|
||||||
|
# Step 5 samples (~0.1 ms) — enough resolution
|
||||||
|
# for pixel-level drift, keeps batch size small.
|
||||||
|
step = 5
|
||||||
|
all_windows = (
|
||||||
|
np.lib.stride_tricks.sliding_window_view(
|
||||||
|
sync_region, win))
|
||||||
|
windows = all_windows[::step]
|
||||||
energies = goertzel_batch(
|
energies = goertzel_batch(
|
||||||
windows,
|
windows,
|
||||||
np.array([FREQ_SYNC, FREQ_BLACK]),
|
np.array([FREQ_SYNC, FREQ_BLACK]),
|
||||||
@@ -269,9 +281,10 @@ class SSTVImageDecoder:
|
|||||||
black_e = energies[:, 1]
|
black_e = energies[:, 1]
|
||||||
valid_mask = sync_e > black_e * 2
|
valid_mask = sync_e > black_e * 2
|
||||||
if valid_mask.any():
|
if valid_mask.any():
|
||||||
fine_best = int(
|
fine_idx = int(
|
||||||
np.argmax(np.where(valid_mask, sync_e, 0.0)))
|
np.argmax(
|
||||||
deviation = float(fine_best - bwd)
|
np.where(valid_mask, sync_e, 0.0)))
|
||||||
|
deviation = float(fine_idx * step - bwd)
|
||||||
self._sync_deviations.append(deviation)
|
self._sync_deviations.append(deviation)
|
||||||
pos += self._sync_samples + self._porch_samples
|
pos += self._sync_samples + self._porch_samples
|
||||||
elif self._separator_samples > 0:
|
elif self._separator_samples > 0:
|
||||||
|
|||||||
Reference in New Issue
Block a user