Commit Graph

9 Commits

Author SHA1 Message Date
Smittix
da2a700bcc Fix SSTV slant correction wedge artifact 2026-02-19 12:18:20 +00:00
Smittix
f7fad076c2 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>
2026-02-19 11:04:06 +00:00
Smittix
a397271553 fix: Slant correction via post-processing shear, not in-decoder sync fixup
Previous attempts to correct slant by altering R-channel placement and
buffer consumption caused cascading failures: a false positive in B pixel
data would misplace R, then the wrong consumed value misaligned the next
line's G, and the error compounded across all 256 lines.

New approach (safe by design):
- Sync search is measurement-only: never touches pos or consumed, so
  a noisy or wrong measurement cannot corrupt the current or future lines.
- Per-line deviation (measured sync position minus expected) is recorded
  in self._sync_deviations throughout the decode.
- get_image() fits a line through the deviations (linear regression) to
  estimate the per-line SDR clock drift rate, then applies a horizontal
  shear to the assembled PIL image: each row is shifted by
  -round(row × drift_rate × width / channel_samples) pixels.
- Worst case (all measurements fail): no correction applied, image
  quality identical to the pre-change baseline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 10:43:16 +00:00
Smittix
83a54ccb20 fix: Replace coarse Scottie sync search with vectorised fine scan
The step-49 coarse scan introduced up to ±24 sample uncertainty in R
channel placement. When accumulated SDR clock drift pushed the actual
sync 35+ samples early in the search region, the step-49 windows could
land on the B-pixel tail and return position 0, misplacing R by ~50
samples (~16 pixel colour shift) — worse than no correction at all.

Replace with a vectorised goertzel_batch sliding-window scan at step=1
over a short window (sync_duration / 3 ≈ 3 ms), giving single-sample
accuracy. Use consumed=pos (instead of max(pos,line_samples)) when the
sync is found, so the next line starts at its correct separator and
per-line timing errors stop accumulating entirely.

Falls back to the fixed-offset path whenever the sync is not found
(e.g. noisy signal), preserving the pre-change baseline quality.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 10:29:19 +00:00
Smittix
2e9bab75b1 fix: Correct Scottie sync search to prevent decoder stall
The previous sync search used search_margin = line_samples/10 (~306
samples for Scottie2), reaching deep into B channel pixel data behind
pos and well past the expected sync end ahead of pos.

When _find_sync returned a position in the late portion of that wide
region, pos + R_channel_samples exceeded the buffer length. The
buffer-too-short guard in _decode_line then returned early without
consuming data or advancing the line counter, causing the stall guard
in feed() to permanently break the decode loop.

Fix: use a 50-sample backward margin (covers >130 ppm SDR drift) and
a forward margin capped to whatever the current buffer can safely
support for the R channel. A final candidate-position check before
committing pos ensures no overflow is possible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 10:01:28 +00:00
Smittix
0dc40bbea3 fix: Correct Scottie SSTV image slant by resyncing to mid-line sync pulse
Scottie modes place their horizontal sync pulse between the Blue and Red
channels. The decoder was using a fixed offset to skip over it, so any
SDR clock error accumulated line-by-line and produced a visible diagonal
slant in the decoded image.

Fix: search for the actual 1200 Hz sync pulse in a ±10% window around
the expected position before decoding the Red channel, then align to the
real pulse. This resets accumulated clock drift on every scanline, the
same way Martin and Robot modes already handle their front-of-line sync.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 09:54:28 +00:00
Smittix
17f6947648 fix: Correct SSTV VIS codes and replace Goertzel pixel decoder with Hilbert transform
Fix wrong VIS codes for PD90 (96→99), PD120 (93→95), PD180 (95→97),
PD240 (113→96), and ScottieDX (55→76). This caused PD180 to be detected
as PD90 and PD120 to fail entirely.

Replace batch Goertzel pixel decoding with analytic signal (Hilbert
transform) FM demodulation. The Goertzel approach used 96-sample windows
with ~500 Hz resolution — wider than the 800 Hz pixel frequency range —
making accurate pixel decoding impossible for fast modes like Martin2
and Scottie2. The Hilbert method computes per-sample instantaneous
frequency, matching the approach used by QSSTV and other professional
SSTV decoders.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 09:23:15 +00:00
Smittix
1ee64efc81 Fix PD120 SSTV decode hang and false leader tone detection
Fix infinite CPU spin in PD120 decoding caused by a 1-sample rounding
mismatch between line_samples (24407) and the sum of sub-component
samples (24408). The feed() while loop would re-enter _decode_line()
endlessly when the buffer was too short by 1 sample. Added a stall
guard that breaks the loop when no progress is made.

Fix false "leader tone detected" in the signal monitor by requiring
the detected tone to dominate the other tone by 2x, matching the
approach already used by the VIS detector.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 14:55:22 +00:00
Smittix
ef7d8cca9f Replace broken slowrx dependency with pure Python SSTV decoder
slowrx is a GTK GUI app that doesn't support CLI usage, so the SSTV
decoder was silently failing. This replaces it with a pure Python
implementation using numpy and Pillow that supports Robot36/72,
Martin1/2, Scottie1/2, and PD120/180 modes via VIS header auto-detection.

Key implementation details:
- Generalized Goertzel (DTFT) for exact-frequency tone detection
- Vectorized batch Goertzel for real-time pixel decoding performance
- Overlapping analysis windows for short-window frequency estimation
- VIS header detection state machine with parity validation
- Per-line sync re-synchronization for drift tolerance

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 19:47:02 +00:00