This program starts a new line when it receives a sync frame (those seven white and black stripes), works well if the signal has clear sync frames.

The first time I recorded a NOAA APT signal the bright parts had lot’s of noise (I think the FM demodulator bandwith was too narrow and had saturation when receiving white), the sync frames were really low quality and the alignment was really bad.

Every decoder I’ve tested, excluding WXtoIMG, has the same problem.


You need the GNU Scientific Library: sudo apt install libgsl0-dev libgsl0.

cargo test

If you get something like a wall of errors because linking with GSL fails, run with the GSLv2 feature:

cargo test --features GSLv2

Things to do

  • Split README into multiple pages and make a better website. Maybe using Jekyll.

  • Improve syncing performance.

  • Option for disabling syncing.

  • Separate thread for GUI.

  • The parameters used for filter design are hardcoded.

  • Add optional lowpass filter before demodulation, there is already one to prevent aliasing but I want to filter noise outside the AM bandwidth.

  • Optionally filter DC component before demodulation, I think it’s useful if the FM demodulation had offset (because of Doppler effect). Looks like otherwise we get bad contrast.

  • Optionally output raw samples as WAV at various steps for debugging, maybe plot the FFT too.

  • Do and fix tests, first I have to fix (or remove) the GSL dependency.

  • Separate GUI and no GUI builds.

  • Check and fix Cargo.toml dev buils.

  • Make OSX binaries, I don’t have a Mac. I should cross-compile or get a virtual machine to work.

  • For some reason the --debug does not work when using the GUI.

How it works


  • Load samples from WAV.

  • Resample to a intermediate sample rate: 20800Hz.

    • Get L (interpolation factor) and M (decimation factor) by looking at the greatest common divisor (GCD) between input and output sample rates.

    • Get interpolating lowpass filter inpulse response by window method.

      • Get kaiser window.

      • Sample and window the function sin(n*cutout)/(n*pi).

    • Do the equivalent of:

      • Insertion of L-1 zeros between samples.

      • Filter by doing the convolution with the impulse response.

      • Decimate by M.

  • Demodulate AM signal to get the APT signal.

    • Iterate over samples, get amplitude by looking at current and previous sample, see below.
  • Find the position of the sync frames of the APT signal (the white and black stripes that you can see in the final image).

    • Calculate the cross correlation between a hardcoded sync frame and the APT signal.

    • The peaks of that cross correlation show the locations of the sync frames in the APT signal.

  • Map the values of the signal to numbers between 0 and 255.

  • Generate the final image starting a new line on every sync frame.

Resampling algorithm

I did something like what you can see here but with a easier (and slower) implementation.

Resampling algorithm

For each output sample, we calculate the sum of the products between input samples and filter coefficients.

AM demodulation

Previously I used a Hilbert filter to get the analytic signal, then the absolute value of the analytic signal is the modulated signal.

Then I found a very fast demodulator implemented on pietern/apt137. For each output sample, you only need the current input sample, the previous one and the carrier frequency:

AM demodulation formula

Where theta is the AM carrier frequency divided by the sample rate.

I couldn’t find the theory behind that method, looks similar to I/Q demodulation. I was able to reach that final expression (which is used by pietern/apt137) by hand and I wrote the steps on extra/demodulation.pdf. I think it only works if the input AM signal is oversampled, maybe that’s why I can’t find anything about it on the web.


  • Modulation:

    • The signal is modulated first on AM and then on FM.

    • FM frequencies:

      • NOAA 15: 137.62MHz.

      • NOAA 18: 137.9125MHz.

      • NOAA 19: 137.1MHz.

    • AM carrier: 2400Hz.

  • APT signal:

    • 8 bits/pixel.

    • The signal amplitude represents the brightness of each pixel.

    • Two lines per second, 4160 pixels per second.

    • 2080 pixels per line, 909 useful pixels per line.

    • Each line has:

      • Sync A: Seven black and seven white pixels.

      • Space A: Some black pixels (periodically white ones too).

      • Image A: Visible/Infrared.

      • Telemetry A: For calibration I think?

      • Sync B: Some white and black pixels but I don’t know the frequency.

      • Space B: Some white pixels (periodically black ones too).

      • Image B: Infrared.

      • Telemetry B: For calibration I think?

Favicons generated using RealFaviconGenerator