Engee documentation
Notebook

Slow motion television

In this example, we will look at an image transmission protocol based on Slow Scan Television (SSTV), which is Slow Scan Television, or small frame television. It is a unique and flexible image transmission protocol that is ideal for narrowband links. The variability of SSTV allows transmission to be tailored to specific conditions, and its ease of implementation makes it popular among radio amateurs. However, due to its low transmission speed, SSTV is not suitable for applications requiring high speed or high image quality.

This model is not tied to any communication standard implemented in practice, and describes primarily technologies and methods that we can apply in Engee for such systems.

The general principle of SSTV is to transmit static images over narrowband communication channels such as radio channels. The process can be divided into several steps: image encoding, modulation, transmission, reception, demodulation and decoding.

image_2.png

Next, we turn to the implemented model.

Analysing the model

The following logic is implemented in the model input: we read an image into the model using the Images library and perform image normalisation.

image_2.png

In [ ]:
using Images

path_img = "$(@__DIR__)/img.jpg"
inp_img = imrotate(Gray.(load(path_img)), deg2rad(-90))

S = size(inp_img)
println("Размер входного изображения: $(S))")
Размер входного изображения: (160, 160))

In this example, all work with the image will be done in a grey shaded representation. If we consider Julia views, it means that pixels are encoded in the range from 0 to 1, where 0 is black and 1 is white.

In [ ]:
Gray.([1,0])
Out[0]:
No description has been provided for this image

Now let's move on to the next block, the interleaving block. Interleaving is a technique used in data transmission systems to improve error resilience. The basic idea is to change the order of the data before transmission, so that neighbouring bits or characters are separated in the stream.

image_2.png

This block is implemented using Engee Function. It has as input the number of interleaving groups and the input signal itself.

Interleaving formula:

index = mod((i - 1) * n, N) + div(i - 1, div(N, n)) + 1

Displacement formula:

index = mod((i - 1), n) * div(N, n) + div(i - 1, n) + 1

These formulas ensure that the index does not go outside the array because the mod operation is used to limit the index within N.

Let's compare the formulas themselves.

  1. There is a difference in calculating the position within the group. In the first formula, the position within the group is calculated as follows: mod((i-1),n). In the second formula, the position within the group is calculated as follows: mod((i-1)×n,N).

2- There are also differences in the calculation of the group number. In the first formula, the group number is calculated as follows: div(i-1,n). In the second formula, the group number is calculated this way: div(i-1,div(N,n)).

That is, as we can see, they are mutually reversible. Next, let us consider a simple example of the interleaving we have implemented.

In [ ]:
L = 4
data_in = collect(1:L)
n = 2 # части массива
println("Исходные данные: ", data_in)

# Функция перемежения
N = length(data_in)
interleaved_data = similar(data_in)

for i in 1:N
    index = mod((i - 1) * n, N) + div(i - 1, div(N, n)) + 1
    interleaved_data[i] = data_in[index]
end

println("Перемеженные данные: ", interleaved_data)

# Функция деперемежения
N = length(interleaved_data)
deinterleaved_data = similar(interleaved_data)

for i in 1:N
    index = mod((i - 1), n) * div(N, n) + div(i - 1, n) + 1
    deinterleaved_data[i] = interleaved_data[index]
end

println("Деперемеженные данные: ", deinterleaved_data)
Исходные данные: [1, 2, 3, 4]
Перемеженные данные: [1, 3, 2, 4]
Деперемеженные данные: [1, 2, 3, 4]

As we can see, the algorithm works correctly. Now let's set the number of groups for our model.

In [ ]:
num = 400 # Количество групп
println("Количество значений в группе: $((S[1]*S[2])/num)")
Количество значений в группе: 64.0

The next block we use is XOR. This operation is reversible and allows us to implement a simple scrambler variant.

image_2.png

Scrambler is a software or hardware device that performs scrambling, i.e. reversible transformation of a digital stream without changing the transmission rate in order to obtain the properties of a random sequence. After scrambling, the occurrence of "1" and "0" in the output sequence is equally likely.

Next, let's set the bit mask for our scrambler.

In [ ]:
bit_mask = 0b01010101 # max 8 bit
println("bit_mask: $bit_mask")
bit_mask: 85

The next blocks we will consider are the 16-FSK modulation and automatic gain control (AGC).

image.png

FSK is a modulation method in which digital data (bits) are transmitted by changing the frequency of the carrier signal. Each bit value (0 or 1) or group of bits corresponds to a specific frequency. 16-FSK is an extension of FSK that uses 16 different frequencies to transmit 4 bits of data simultaneously. Table of bit and number correspondence for 16-FSK:

Bit combination Number
[0, 0, 0, 0] 1
[0, 0, 0, 1] 3
[0, 0, 1, 0] 5
[0, 0, 1, 1] 7
[0, 1, 0, 0] 9
[0, 1, 0, 1] 11
[0, 1, 1, 0] 13
[0, 1, 1, 1] 15
[1, 0, 0, 0] -1
[1, 0, 0, 1] -3
[1, 0, 1, 0] -5
[1, 0, 1, 1] -7
[1, 1, 0, 0] -9
[1, 1, 0, 1] -11
[1, 1, 1, 0] -13
[1, 1, 1, 1] -15

AGC is a system that automatically adjusts the signal gain on the receive side to keep the output signal level constant regardless of changes in the input signal level. In our case, logic is implemented that calculates the output power relative to the data before signal gain, but you can easily replace this approach with a preset value.

image_4.png

Based on the test below, we can clearly conclude that at 16-FSK the desired signal power is 85W.

In [ ]:
norm_img = ((channelview(inp_img))[:])
power_img = sum(norm_img.^2) / length(norm_img)
println("Мощность входного сигнала: ", power_img, " Вт")

values = [1:2:15; -1:-2:-15] 
v = [values[rand(1:length(values))] for _ in 1:length(norm_img)]
power_desired = sum(v .^ 2) / length(v)
println("Желаемая мощность: ", power_desired, " Вт")

K = sqrt(power_desired / power_img) # Используем квадратный корень, так как мощность пропорциональна квадрату амплитуды 
println("Коэффициент усиления: ", K)

amplified = norm_img .* K  # Усиленный сигнал

power_out = sum(amplified .^ 2) / length(amplified) 
println("Мощность выходного сигнала: ", round(power_out, digits=2), " Вт")
Мощность входного сигнала: 0.3573653492647059 Вт
Желаемая мощность: 84.9459375 Вт
Коэффициент усиления: 15.417540102273989
Мощность выходного сигнала: 84.97 Вт

The next blocks we will look at are FM Modulator Baseband and FM Demodulator Baseband. This is a modulation that converts the baseband signal into a frequency modulated (FM) signal.

There are two key parameters used in FM radio broadcasting.

  1. The carrier frequency is the fundamental frequency of the signal that is modulated to transmit information. In FM broadcasting it is between 87.5 MHz and 108 MHz.

  2. The deviation frequency is the maximum deviation of frequency from the carrier frequency when the signal is transmitted. For standard FM broadcasting, the maximum allowable deviation is ±75 kHz.

Thus, if the carrier frequency is, say, 100 MHz, the actual frequency of the transmitted signal will vary between 99.925 and 100.075 MHz.

In [ ]:
fs = 100e3 # Hz
println("Несущая частота: $fs Гц")

deviation_fs = fs * 0.05 # 5% от fs
println("Частота девиации: $deviation_fs Гц")
Несущая частота: 100000.0 Гц
Частота девиации: 5000.0 Гц

The last block we will consider is the communication channel block. It uses the Signal-to-Noise Ratio (SNR) metric, the signal-to-noise ratio, to overlay noise. This metric shows how much stronger the useful signal is than the noise. image.png

The higher the SNR, the higher the signal quality, as noise has less impact on the data. Conversely, the lower the SNR, the lower the signal quality because noise begins to dominate the useful signal.

For example:

  1. if SNR = 20 dB, this means that the signal is 100 times more powerful than the noise (on a linear scale);
  2. if SNR = 0 dB, it means that the power of the signal and noise are equal;
  3. if SNR = -10 dB, it means that the noise is 10 times stronger than the signal.
In [ ]:
snr = 10;
println("SNR: $snr дБ")
snr_linear = 10^(snr / 10)
signal_power = 1
noise_power = 1 / snr_linear
println("Мощность шума: $noise_power")
SNR: 10 дБ
Мощность шума: 0.1

Running the model and analysing the results

Set the simulation parameters and run the model.

In [ ]:
st = 1/fs*S[1]*S[2] # Sample time
println("Время одного отсчёта: $st")
step = st/S[1]/S[2]
println("Шаг решателя: $step")
time_stop = st*2.1
println("Шаг моделирования: $st")
Время одного отсчёта: 0.256
Шаг решателя: 1.0e-5
Шаг моделирования: 0.256
In [ ]:
name_model = "SSTV"
Path = (@__DIR__) * "/" * name_model * ".engee"

if name_model in [m.name for m in engee.get_all_models()] # Проверка условия загрузки модели в ядро
    model = engee.open( name_model ) # Открыть модель
    model_output = engee.run( model, verbose=true ); # Запустить модель
else
    model = engee.load( Path, force=true ) # Загрузить модель
    model_output = engee.run( model, verbose=true ); # Запустить модель
    engee.close( name_model, force=true ); # Закрыть модель
end
sleep(1)
Building...
Progress 0%
Progress 5%
Progress 11%
Progress 16%
Progress 22%
Progress 28%
Progress 34%
Progress 39%
Progress 45%
Progress 50%
Progress 60%
Progress 67%
Progress 72%
Progress 81%
Progress 87%
Progress 93%
Progress 99%
Progress 100%
Progress 100%

Visualise the results of our model and summarise the results.

In [ ]:
println("SNR: $snr дБ")
println()
ber = ((collect(BER)).value)[end] 
println("Всего бит: $(Int(ber[3]))")
println("Кол-во ошибок: $(Int(ber[2]))")
println("BER: $(round(ber[1], digits=2))")

inp = plot(framestyle=:none, grid=false, title = "Вход")
heatmap!( 1:S[2], 1:S[1], permutedims(inp_img, (2, 1)), colorbar=false)

sim_img = imrotate(colorview(Gray, ((collect(img_FM))[3,:].value)), deg2rad(-90))
sim = plot(framestyle=:none, grid=false, title = "Выход")
heatmap!( 1:size(sim_img, 2), 1:size(sim_img, 1), permutedims(sim_img, (2, 1)), colorbar=false)

sim_error = (collect(error_sim)).value 
Average_error = round(sum(abs.(sim_error))/length(sim_error)*100, digits=2)
err = plot(sim_error, title = "Cредняя ошибка равна $Average_error % ")

plot(plot(inp, sim), err, layout = grid(2, 1, heights=[0.7, 0.3]), legend=false) 
SNR: 10 дБ

Всего бит: 430088
Кол-во ошибок: 39938
BER: 0.09
Out[0]:

As we can see from the simulation results, we get good BER values at the output. If we do more detailed tuning of interleaving and scrambling, we can get even better picture quality. For this purpose you are provided with the second script SSTV_test. There is no unnecessary description in it, but only the code of this project.

Conclusion

In this example, we have looked at the possibilities of modelling a slow-motion TV-based image transfer protocol in Engee, and we have explored the many tools that allow us to run such projects in Engee. As you can see from our example, Engee has all the necessary features and tools for these models, and they work correctly.