One of the interesting data protocols developed in the last century is APT (Automatic Picture Transmission). It is used to transmit images of the Earth’s surface from space, and what is much more interesting for us, receiving APT signals is feasible to radio amateurs.

NOAA Satellite © en.wikipedia.org/wiki/NOAA-15

According to international conventions, all meteorological data is open, and anyone can receive NOAA signals. There have already been several articles on Medium about the NOAA reception like this, but they all usually boil down to “plug in the receiver, run the program, get a picture”, without explanation of how it really works. Let’s try to go one logical level down and see how the data processing works under the hood.

Let’s get started.

Good reception when the satellite is high above the horizon occurs about once a day and can be sometimes at 5 am, the actual flight across the sky takes about 10 minutes, so it may be useful to schedule an automatic recording at the proper time.

Of course, you need a receiver to get the radio signal. The cheapest RTL-SDR V3 at $ 35 will do the job, I used a better SDRPlay model with the following settings:

As you can see, I set the decimation value to maximum, which allows getting the maximum dynamic range. The LNA and Gain gain levels should be selected depending on the antenna. Satellites NOAA-15, NOAA-18 and NOAA-19 transmit data on frequencies 137.620, 137.9125 and 137.100 MHz, respectively. The signal itself has a bandwidth of about 50 kHz, and if everything was done correctly, at a given time the signal should appear on the spectrum:

It is interesting to note the slope of the lines due to the Doppler effect — the satellite flies around the globe, and this is another good proof of the curvature of the Earth 😉

Speaking of reception, the great thing about NOAA satellites is that reception is affordable for beginners. The signal can, in principle, be heard on any antenna, even on the simplest one from the TV, but to get a good picture the quality of the antenna is very critical. With a bad antenna (like mine:) the picture will have less contrast, but this is enough to demonstrate the decoder’s operation. Nice 137 MHz antenna can be bought or made from plumbing and copper pipes, an example can be viewed here. However, the topic of the article is digital signal processing and not DIY, those who wish can find the proper antenna design on their own.

So, we should start the recording at the proper time, using the FM mode and a 50 kHz bandwidth. The result should be a WAV file of about 10 minutes length, the periodic pulses should be clearly audible. Now we can start decoding.

import scipy.io.wavfile as wav
import scipy.signal as signal
import numpy as np
import matplotlib.pyplot as plt
fs, data = wav.read('HDSDR_20201227_070306Z_137100kHz_AF.wav')
data_crop = data[20*fs:21*fs]
plt.figure(figsize=(12,4))plt.plot(data_crop)
plt.xlabel("Samples")
plt.ylabel("Amplitude")
plt.title("Signal")
plt.show()

A periodical signal should be visible:

Step 2. To speed up decoding, let’s reduce the sampling rate by 4 times, discarding unnecessary values:

resample = 4
data = data[::resample]
fs = fs//resample

Step 3. The image is transmitting in amplitude modulation, for conversion to AM let’s apply the Hilbert transform:

def hilbert(data):
analytical_signal = signal.hilbert(data)
amplitude_envelope = np.abs(analytical_signal)
return amplitude_envelope
data_am = hilbert(data)

We can display the picture and make sure that we got the signal envelope:

Step 4. The final step. Actually, the decoding was already finished. The data itself is transmitted in analogue format, so the color of each pixel depends on the signal level. We can “reshape” the data into a 2D image, from the format description it is known that one line is transmitted in 0.5 s:

from PIL import Imageframe_width = int(0.5*fs)
w, h = frame_width, data_am.shape[0]//frame_width
image = Image.new('RGB', (w, h))
px, py = 0, 0
for p in range(data_am.shape[0]):
lum = int(data_am[p]//32 - 32)
if lum < 0: lum = 0
if lum > 255: lum = 255
image.putpixel((px, py), (0, lum, 0))
px += 1
if px >= w:
if (py % 50) == 0:
print(f"Line saved {py} of {h}")
px = 0
py += 1
if py >= h:
break

The putpixel function is not the fastest way to work with images, and the code can be sped up 10 times using numpy.reshape and Image.fromarray, but this way looks more clear. To convert the amplitude of the signal to the brightness range 0..255, the values are divided by 32, for another antenna the value may have to be changed.

For ease of viewing, let’s resize the image and display it:

image = image.resize((w, 4*h))
plt.imshow(image)
plt.show()

If everything was done correctly, we should get something like this:

The “old-school” green color was chosen just for fun, those who wish can change the color gamut by changing the parameters of the putpixel method. What do we see on the screen? In APT format, two channels are transmitted. Long-wave IR (10.8 micrometers) is transmitted on one half of the frame, near/mid-wave IR (0.86 or 3.75 micrometers) is transmitted to the other, the mode is selected depending on whether the satellite transmits a night or day image. The data also has timing markers and telemetry, those who wish can see the description of the APT format in more detail. Software decoders can use these markers to adjust the picture, but this may not work on weak signals. The code above never gets out of sync since there is no sync at all, even the weakest signal will be visible, albeit with less contrast.

Of course, it is not necessary to decode NOAA signals only using Python, there are enough software decoders that can do the job, and even have decent features, like placing countries borders and cities labels, rendering pseudo colors, etc. But it can be also interesting to do the decoding from scratch.



Source link