From 0dc40bbea3ae8fa3aa9b66dc7692693c2201d1d6 Mon Sep 17 00:00:00 2001 From: Smittix Date: Thu, 19 Feb 2026 09:54:28 +0000 Subject: [PATCH] fix: Correct Scottie SSTV image slant by resyncing to mid-line sync pulse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- utils/sstv/image_decoder.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/utils/sstv/image_decoder.py b/utils/sstv/image_decoder.py index 95246d0..33176a0 100644 --- a/utils/sstv/image_decoder.py +++ b/utils/sstv/image_decoder.py @@ -234,8 +234,23 @@ class SSTVImageDecoder: # Scottie: separator between G and B pos += self._porch_samples else: - # Scottie: sync + porch between B and R - pos += self._sync_samples + self._porch_samples + # Scottie: sync + porch between B and R. + # Search for the actual sync pulse to correct for + # SDR clock drift — without this, any timing error + # accumulates line-by-line producing a visible slant. + search_margin = max(100, self._line_samples // 10) + sync_search_start = max(0, pos - search_margin) + sync_search_end = min( + len(self._buffer), + pos + self._sync_samples + search_margin, + ) + sync_region = self._buffer[sync_search_start:sync_search_end] + sync_found = self._find_sync(sync_region) + if sync_found is not None: + pos = (sync_search_start + sync_found + + self._sync_samples + self._porch_samples) + else: + pos += self._sync_samples + self._porch_samples elif self._separator_samples > 0: # Robot: separator + porch between channels pos += self._separator_samples