kopia lustrzana https://github.com/kgoba/ft8_lib
Update Python reference
rodzic
d46d46edaf
commit
2f2396c3df
462
utils/decode.py
462
utils/decode.py
|
@ -1,9 +1,15 @@
|
|||
import argparse
|
||||
from pathlib import Path
|
||||
import logging
|
||||
|
||||
import scipy.io.wavfile as wavfile
|
||||
from scipy import signal
|
||||
import numpy as np
|
||||
import sys
|
||||
import ldpc
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
FT8_NUM_TONES = 8
|
||||
FT8_NUM_SYMBOLS = 79
|
||||
FT8_TONE_DEVIATION = 6.25
|
||||
|
@ -17,11 +23,14 @@ FT8_PAYLOAD_BITS = 77
|
|||
MIN_FREQ = 300
|
||||
MAX_FREQ = 3000
|
||||
|
||||
|
||||
def lin_to_db(x, eps=1e-12):
|
||||
return 20*np.log10(x + eps)
|
||||
return 20 * np.log10(x + eps)
|
||||
|
||||
|
||||
def db_to_lin(x):
|
||||
return 10**(x/20)
|
||||
return 10 ** (x / 20)
|
||||
|
||||
|
||||
def load_wav(path):
|
||||
rate, samples = wavfile.read(path)
|
||||
|
@ -29,23 +38,29 @@ def load_wav(path):
|
|||
samples = np.array(samples / 32768.0)
|
||||
return (rate, samples)
|
||||
|
||||
|
||||
def quantize(H, mag_db_step=0.5, phase_divs=256):
|
||||
mag_db = lin_to_db(np.abs(H))
|
||||
mag_db = mag_db_step * np.ceil(mag_db / mag_db_step)
|
||||
|
||||
phase = np.angle(H)
|
||||
phase = np.ceil(0.5 + phase * phase_divs / (2*np.pi)) / phase_divs * (2*np.pi)
|
||||
phase = np.ceil(0.5 + phase * phase_divs / (2 * np.pi)) / phase_divs * (2 * np.pi)
|
||||
|
||||
return db_to_lin(mag_db) * np.exp(1j * phase)
|
||||
|
||||
|
||||
class Waterfall:
|
||||
def __init__(self, freq_osr=2, time_osr=2, freq_min=300, freq_max=3000):
|
||||
self.H = None
|
||||
self.freq_osr = freq_osr
|
||||
self.time_osr = time_osr
|
||||
self.window_type = 'hann'
|
||||
self.freq_step = FT8_TONE_DEVIATION / self.freq_osr # frequency step corresponding to one bin, Hz
|
||||
self.time_step = FT8_SYMBOL_PERIOD / self.time_osr # time step corresponding to one STFT position, seconds
|
||||
self.window_type = "hann"
|
||||
self.freq_step = (
|
||||
FT8_TONE_DEVIATION / self.freq_osr
|
||||
) # frequency step corresponding to one bin, Hz
|
||||
self.time_step = (
|
||||
FT8_SYMBOL_PERIOD / self.time_osr
|
||||
) # time step corresponding to one STFT position, seconds
|
||||
self.bin_min = int(freq_min / self.freq_step)
|
||||
self.bin_max = int(freq_max / self.freq_step) + 1
|
||||
# self.freq_first = self.bin_min * self.freq_step
|
||||
|
@ -54,51 +69,94 @@ class Waterfall:
|
|||
def load_signal(self, sig, fs):
|
||||
sym_size = int(fs * FT8_SYMBOL_PERIOD)
|
||||
nfft = sym_size * self.freq_osr
|
||||
_, _, H = signal.stft(sig, window=self.window_type, nfft=nfft, nperseg=nfft, noverlap=nfft - (sym_size//self.time_osr), boundary=None, padded=None)
|
||||
_, _, H = signal.stft(
|
||||
sig,
|
||||
window=self.window_type,
|
||||
nfft=nfft,
|
||||
nperseg=nfft,
|
||||
noverlap=nfft - (sym_size // self.time_osr),
|
||||
boundary=None,
|
||||
padded=None,
|
||||
)
|
||||
self.H = quantize(H)
|
||||
A = np.abs(H)
|
||||
self.Apow = A**2
|
||||
self.Adb = lin_to_db(A)
|
||||
print(f'Max magnitude {self.Adb[:, self.bin_min:self.bin_max].max(axis=(0, 1)):.1f} dB')
|
||||
print(f'Waterfall shape {H.shape}')
|
||||
print(
|
||||
f"Max magnitude {self.Adb[:, self.bin_min:self.bin_max].max(axis=(0, 1)):.1f} dB"
|
||||
)
|
||||
print(f"Waterfall shape {H.shape}")
|
||||
|
||||
|
||||
def search_sync_coarse(wf, min_score=2.5, max_cand=30, snr_mode=2):
|
||||
print(f'Using bins {wf.bin_min}..{wf.bin_max} ({wf.bin_max - wf.bin_min})')
|
||||
logger.info(f"Using bins {wf.bin_min}..{wf.bin_max} ({wf.bin_max - wf.bin_min})")
|
||||
score_map = dict()
|
||||
for freq_sub in range(wf.freq_osr):
|
||||
for bin_first in range(wf.bin_min + freq_sub, wf.bin_max - FT8_NUM_TONES * wf.freq_osr, wf.freq_osr):
|
||||
for time_sub in range(time_osr):
|
||||
for time_start in range(-10 * wf.time_osr + time_sub, 21 * wf.time_osr + time_sub, wf.time_osr):
|
||||
for bin_first in range(
|
||||
wf.bin_min + freq_sub, wf.bin_max - FT8_NUM_TONES * wf.freq_osr, wf.freq_osr
|
||||
):
|
||||
for time_sub in range(wf.time_osr):
|
||||
for time_start in range(
|
||||
-10 * wf.time_osr + time_sub,
|
||||
21 * wf.time_osr + time_sub,
|
||||
wf.time_osr,
|
||||
):
|
||||
# calc sync score at (bin_first, time_start)
|
||||
score = []
|
||||
snr_sig = snr_noise = 0
|
||||
for sync_start in FT8_SYNC_POS:
|
||||
for sync_pos, sync_tone in enumerate(FT8_SYNC_SYMS, start=sync_start):
|
||||
for sync_pos, sync_tone in enumerate(
|
||||
FT8_SYNC_SYMS, start=sync_start
|
||||
):
|
||||
pos = time_start + sync_pos * wf.time_osr
|
||||
if pos >= 0 and pos < wf.Adb.shape[1]:
|
||||
if snr_mode == 0:
|
||||
snr_sig += wf.Apow[bin_first + sync_tone * wf.freq_osr, pos]
|
||||
snr_sig += wf.Apow[
|
||||
bin_first + sync_tone * wf.freq_osr, pos
|
||||
]
|
||||
for noise_tone in range(7):
|
||||
if noise_tone != sync_tone:
|
||||
snr_noise += wf.Apow[bin_first + noise_tone * wf.freq_osr, pos]
|
||||
snr_noise += wf.Apow[
|
||||
bin_first + noise_tone * wf.freq_osr,
|
||||
pos,
|
||||
]
|
||||
else:
|
||||
sym_db = wf.Adb[bin_first + sync_tone * wf.freq_osr, pos]
|
||||
if bin_first + (sync_tone - 1) * freq_osr >= wf.bin_min:
|
||||
sym_down_db = wf.Adb[bin_first + (sync_tone - 1) * wf.freq_osr, pos]
|
||||
sym_db = wf.Adb[
|
||||
bin_first + sync_tone * wf.freq_osr, pos
|
||||
]
|
||||
if (
|
||||
bin_first + (sync_tone - 1) * wf.freq_osr
|
||||
>= wf.bin_min
|
||||
):
|
||||
sym_down_db = wf.Adb[
|
||||
bin_first + (sync_tone - 1) * wf.freq_osr,
|
||||
pos,
|
||||
]
|
||||
score.append(sym_db - sym_down_db)
|
||||
if bin_first + (sync_tone + 1) * wf.freq_osr < wf.bin_max:
|
||||
sym_up_db = wf.Adb[bin_first + (sync_tone + 1) * wf.freq_osr, pos]
|
||||
if (
|
||||
bin_first + (sync_tone + 1) * wf.freq_osr
|
||||
< wf.bin_max
|
||||
):
|
||||
sym_up_db = wf.Adb[
|
||||
bin_first + (sync_tone + 1) * wf.freq_osr,
|
||||
pos,
|
||||
]
|
||||
score.append(sym_db - sym_up_db)
|
||||
if snr_mode == 2:
|
||||
if pos - 1 >= 0:
|
||||
sym_prev_db = wf.Adb[bin_first + sync_tone * wf.freq_osr, pos - 1]
|
||||
sym_prev_db = wf.Adb[
|
||||
bin_first + sync_tone * wf.freq_osr,
|
||||
pos - 1,
|
||||
]
|
||||
score.append(sym_db - sym_prev_db)
|
||||
if pos + 1 < wf.Adb.shape[1]:
|
||||
sym_next_db = wf.Adb[bin_first + sync_tone * wf.freq_osr, pos + 1]
|
||||
sym_next_db = wf.Adb[
|
||||
bin_first + sync_tone * wf.freq_osr,
|
||||
pos + 1,
|
||||
]
|
||||
score.append(sym_db - sym_next_db)
|
||||
if snr_mode == 0:
|
||||
score_avg = 10*np.log10(snr_sig / (snr_noise / 6))
|
||||
score_avg = 10 * np.log10(snr_sig / (snr_noise / 6))
|
||||
else:
|
||||
score_avg = np.mean(score)
|
||||
|
||||
|
@ -118,65 +176,96 @@ def search_sync_coarse(wf, min_score=2.5, max_cand=30, snr_mode=2):
|
|||
if is_better:
|
||||
score_map[(bin_first, time_start)] = score_avg
|
||||
|
||||
top_keys = sorted(score_map.keys(), key=lambda x: score_map[x], reverse=True)[:max_cand]
|
||||
top_keys = sorted(score_map.keys(), key=lambda x: score_map[x], reverse=True)[
|
||||
:max_cand
|
||||
]
|
||||
for idx, (bin, pos) in enumerate(sorted(top_keys)):
|
||||
print(f'{idx+1}: {wf.freq_step * bin:.2f}\t{wf.time_step * pos:+.02f}\t{score_map[(bin, pos)]:.2f}')
|
||||
logger.debug(
|
||||
f"{idx+1}: {wf.freq_step * bin:.2f}\t{wf.time_step * pos:+.02f}\t{score_map[(bin, pos)]:.2f}"
|
||||
)
|
||||
time_offset = FT8_SYMBOL_PERIOD / 4
|
||||
return [(wf.freq_step * bin, wf.time_step * pos - time_offset) for (bin, pos) in sorted(top_keys)]
|
||||
return [
|
||||
(wf.freq_step * bin, wf.time_step * pos - time_offset)
|
||||
for (bin, pos) in sorted(top_keys)
|
||||
]
|
||||
|
||||
|
||||
def downsample_fft(H, bin_f0, fs2=100, freq_osr=1, time_osr=1):
|
||||
def downsample_fft(H, bin_f0, fs2=100, freq_osr=1, time_osr=1, taper_width=1):
|
||||
sym_size2 = int(fs2 * FT8_SYMBOL_PERIOD)
|
||||
nfft2 = sym_size2 * freq_osr
|
||||
freq_step2 = fs2 / nfft2
|
||||
taper_width = 4
|
||||
pad_width = ((nfft2 - 2*taper_width - freq_osr*FT8_NUM_TONES) // 2)
|
||||
H2 = H[bin_f0 - taper_width - pad_width: bin_f0 + freq_osr*FT8_NUM_TONES + taper_width + pad_width, :]
|
||||
pad_width = (nfft2 - 2 * taper_width - freq_osr * FT8_NUM_TONES) // 2
|
||||
H2 = H[
|
||||
bin_f0 - taper_width - pad_width : bin_f0
|
||||
+ freq_osr * FT8_NUM_TONES
|
||||
+ taper_width
|
||||
+ pad_width,
|
||||
:,
|
||||
]
|
||||
W_taper = np.linspace(0, 1, taper_width)
|
||||
W_pad = [0] * pad_width
|
||||
W = np.concatenate( (W_pad, W_taper, [1]*freq_osr*FT8_NUM_TONES, np.flipud(W_taper), W_pad) )
|
||||
W = np.concatenate(
|
||||
(W_pad, W_taper, [1] * freq_osr * FT8_NUM_TONES, np.flipud(W_taper), W_pad)
|
||||
)
|
||||
H2 = np.multiply(H2, np.expand_dims(W, W.ndim))
|
||||
|
||||
shift = taper_width + pad_width
|
||||
H2 = np.roll(H2, -shift, axis=0)
|
||||
_, sig2 = signal.istft(H2, window='hann', nperseg=nfft2, noverlap=nfft2 - (sym_size2//time_osr), input_onesided=False)
|
||||
_, sig_down = signal.istft(
|
||||
H2,
|
||||
window="hann",
|
||||
nperseg=nfft2,
|
||||
noverlap=nfft2 - (sym_size2 // time_osr),
|
||||
input_onesided=False,
|
||||
)
|
||||
|
||||
f0_down = (taper_width + pad_width - shift) * freq_step2
|
||||
return sig2, f0_down
|
||||
return sig_down, f0_down
|
||||
|
||||
|
||||
def search_sync_fine(sig2, fs2, f0_down, pos_start):
|
||||
def search_sync_fine(sig_down, fs2, f0_down, pos_start):
|
||||
sym_size2 = int(fs2 * FT8_SYMBOL_PERIOD)
|
||||
n = np.arange(sym_size2)
|
||||
|
||||
f_tones = np.arange(f0_down, f0_down + FT8_NUM_TONES*FT8_TONE_DEVIATION, FT8_TONE_DEVIATION)
|
||||
ctones_conj = np.exp(-1j * 2*np.pi * np.expand_dims(n, n.ndim) * np.expand_dims(f_tones/fs2, 0))
|
||||
ctweak_plus_tone = np.exp(-1j * 2*np.pi * n * FT8_TONE_DEVIATION/fs2)
|
||||
ctweak_minus_tone = np.exp(1j * 2*np.pi * n * FT8_TONE_DEVIATION/fs2)
|
||||
f_tones = np.arange(
|
||||
f0_down, f0_down + FT8_NUM_TONES * FT8_TONE_DEVIATION, FT8_TONE_DEVIATION
|
||||
)
|
||||
ctones_conj = np.exp(
|
||||
-1j * 2 * np.pi * np.expand_dims(n, n.ndim) * np.expand_dims(f_tones / fs2, 0)
|
||||
)
|
||||
ctweak_plus_tone = np.exp(-1j * 2 * np.pi * n * FT8_TONE_DEVIATION / fs2)
|
||||
ctweak_minus_tone = np.exp(1j * 2 * np.pi * n * FT8_TONE_DEVIATION / fs2)
|
||||
|
||||
max_power, max_freq_offset, max_pos_offset = None, None, None
|
||||
max_power, fine_freq_offset, fine_pos_offset = None, None, None
|
||||
all_powers = []
|
||||
win = signal.windows.kaiser(sym_size2, beta=2.0)
|
||||
for freq_offset in np.linspace(-3.2, 3.2, 21):
|
||||
# win = signal.windows.boxcar(sym_size2)
|
||||
for freq_offset in np.linspace(-3.2, 3.2, 33):
|
||||
power_time = []
|
||||
ctweak = np.exp(-1j * 2*np.pi * n * freq_offset/fs2)
|
||||
for pos_offset in range(-sym_size2//2, sym_size2//2 + 1):
|
||||
ctweak = np.exp(-1j * 2 * np.pi * n * freq_offset / fs2)
|
||||
for pos_offset in range(-sym_size2 // 2, sym_size2 // 2 + 1):
|
||||
power_sig = 0
|
||||
power_nse = 1e-12
|
||||
for sync_start in FT8_SYNC_POS:
|
||||
for sync_pos, sync_tone in enumerate(FT8_SYNC_SYMS):
|
||||
pos1 = pos_start + pos_offset + sym_size2 * (sync_start + sync_pos)
|
||||
|
||||
if pos1 >= 0 and pos1 + sym_size2 < len(sig2):
|
||||
demod = win * sig2[pos1:pos1 + sym_size2] * ctones_conj[:, sync_tone] * ctweak
|
||||
mag2_sym = np.abs(np.sum(demod))**2
|
||||
mag2_minus = np.abs(np.sum(demod * ctweak_minus_tone))**2
|
||||
mag2_plus = np.abs(np.sum(demod * ctweak_plus_tone))**2
|
||||
if pos1 >= 0 and pos1 + sym_size2 < len(sig_down):
|
||||
demod = (
|
||||
win
|
||||
* sig_down[pos1 : pos1 + sym_size2]
|
||||
* ctones_conj[:, sync_tone]
|
||||
* ctweak
|
||||
* np.exp(-1j * 2 * np.pi * pos1 * freq_offset / fs2)
|
||||
)
|
||||
mag2_sym = np.abs(np.sum(demod)) ** 2
|
||||
mag2_minus = np.abs(np.sum(demod * ctweak_minus_tone)) ** 2
|
||||
mag2_plus = np.abs(np.sum(demod * ctweak_plus_tone)) ** 2
|
||||
power_sig += mag2_sym
|
||||
power_nse += (mag2_minus + mag2_plus)/2
|
||||
power_nse += (mag2_minus + mag2_plus) / 2
|
||||
|
||||
# demod_prev = win * sig2[pos1 - sym_size2:pos1] * ctones_conj[:, sync_tone] * ctweak
|
||||
# demod_next = win * sig2[pos1 + sym_size2:pos1 + 2*sym_size2] * ctones_conj[:, sync_tone] * ctweak
|
||||
# demod_prev = win * sig_down[pos1 - sym_size2:pos1] * ctones_conj[:, sync_tone] * ctweak
|
||||
# demod_next = win * sig_down[pos1 + sym_size2:pos1 + 2*sym_size2] * ctones_conj[:, sync_tone] * ctweak
|
||||
# mag2_prev = np.abs(np.sum(demod_prev))**2
|
||||
# mag2_next = np.abs(np.sum(demod_next))**2
|
||||
# power += 2*mag2_sym - mag2_prev - mag2_next
|
||||
|
@ -185,20 +274,26 @@ def search_sync_fine(sig2, fs2, f0_down, pos_start):
|
|||
power_time.append(power)
|
||||
if max_power is None or power > max_power:
|
||||
max_power = power
|
||||
max_freq_offset = freq_offset
|
||||
max_pos_offset = pos_offset
|
||||
|
||||
fine_freq_offset = freq_offset
|
||||
fine_pos_offset = pos_offset
|
||||
|
||||
# print(f'{freq_offset:.1f}, {(np.argmax(power_time) - sym_size2//2)/fs2:.3f}, {np.max(power_time)}')
|
||||
all_powers.append(power_time)
|
||||
|
||||
return max_freq_offset, max_pos_offset
|
||||
return fine_freq_offset, fine_pos_offset
|
||||
|
||||
|
||||
def extract_logl_db(A2db):
|
||||
# FT8 bits -> channel symbols 0, 1, 3, 2, 5, 6, 4, 7
|
||||
A2db_bit0 = np.max(A2db[[5, 6, 4, 7], :], axis=0) - np.max(A2db[[0, 1, 3, 2], :], axis=0) # 4/5/6/7 - 0/1/2/3
|
||||
A2db_bit1 = np.max(A2db[[3, 2, 4, 7], :], axis=0) - np.max(A2db[[0, 1, 5, 6], :], axis=0) # 2/3/6/7 - 0/1/4/5
|
||||
A2db_bit2 = np.max(A2db[[1, 2, 6, 7], :], axis=0) - np.max(A2db[[0, 3, 5, 4], :], axis=0) # 1/3/5/7 - 0/2/4/6
|
||||
A2db_bit0 = np.max(A2db[[5, 6, 4, 7], :], axis=0) - np.max(
|
||||
A2db[[0, 1, 3, 2], :], axis=0
|
||||
) # 4/5/6/7 - 0/1/2/3
|
||||
A2db_bit1 = np.max(A2db[[3, 2, 4, 7], :], axis=0) - np.max(
|
||||
A2db[[0, 1, 5, 6], :], axis=0
|
||||
) # 2/3/6/7 - 0/1/4/5
|
||||
A2db_bit2 = np.max(A2db[[1, 2, 6, 7], :], axis=0) - np.max(
|
||||
A2db[[0, 3, 5, 4], :], axis=0
|
||||
) # 1/3/5/7 - 0/2/4/6
|
||||
A2db_bits = np.stack((A2db_bit0, A2db_bit1, A2db_bit2)).transpose()
|
||||
|
||||
# a = [
|
||||
|
@ -216,95 +311,198 @@ def extract_logl_db(A2db):
|
|||
# [ 42., 1., 40., 5., 35., -28., -29., -30.]])/34/6
|
||||
# A2db_bits = np.matmul(W, a).transpose()
|
||||
|
||||
bits_logl = np.concatenate((A2db_bits[7:36], A2db_bits[43:72])).flatten() * 0.6
|
||||
return bits_logl, A2db_bits
|
||||
bits_logl = np.concatenate((A2db_bits[7:36], A2db_bits[43:72])).flatten()
|
||||
return 1.0 * bits_logl, A2db_bits
|
||||
|
||||
|
||||
fs, sig = load_wav(sys.argv[1])
|
||||
print(f'Sample rate {fs} Hz')
|
||||
def show_plots(A2db, A2db_bits, bits_logl):
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
freq_osr = 2
|
||||
time_osr = 2
|
||||
# import matplotlib.ticker as plticker
|
||||
import matplotlib.colors as pltcolors
|
||||
|
||||
wf = Waterfall(freq_osr=freq_osr, time_osr=time_osr, freq_min=MIN_FREQ, freq_max=MAX_FREQ)
|
||||
wf.load_signal(sig, fs)
|
||||
fig, ax = plt.subplots(4, figsize=(10.0, 8.0))
|
||||
|
||||
use_downsample = True
|
||||
if len(sys.argv) > 2:
|
||||
f0 = float(sys.argv[2])
|
||||
time_start = float(sys.argv[3])
|
||||
candidates = [(f0, time_start)]
|
||||
else:
|
||||
candidates = search_sync_coarse(wf)
|
||||
plt.colorbar(
|
||||
ax[0].imshow(
|
||||
A2db,
|
||||
cmap="inferno",
|
||||
norm=pltcolors.Normalize(-30, 0, clip=True),
|
||||
origin="lower",
|
||||
),
|
||||
orientation="horizontal",
|
||||
ax=ax[0],
|
||||
)
|
||||
plt.colorbar(
|
||||
ax[1].imshow(
|
||||
A2db_bits.transpose(),
|
||||
cmap="bwr",
|
||||
norm=pltcolors.Normalize(-10, 10, clip=True),
|
||||
origin="lower",
|
||||
),
|
||||
orientation="horizontal",
|
||||
ax=ax[1],
|
||||
)
|
||||
# ax[2].imshow(A2db_bits2, cmap='bwr', norm=pltcolors.Normalize(-10, 10, clip=True))
|
||||
ax[2].hist(bits_logl, bins=25)
|
||||
# ax[3].plot(np.arange(len(sig3))/sym_size2, np.real(sig3))
|
||||
# ax[3].plot(np.arange(len(sig3))/sym_size2, np.abs(sig3))
|
||||
ax[3].margins(0, 0)
|
||||
# loc = plticker.MultipleLocator(base=32.0) # this locator puts ticks at regular intervals
|
||||
# ax[1].xaxis.set_major_locator(loc)
|
||||
# ax[0].plot(np.array(all_powers).transpose())
|
||||
|
||||
num_decoded = 0
|
||||
for f0, time_start in candidates:
|
||||
bin_f0 = int(0.5 + f0 / wf.freq_step)
|
||||
f0_real = bin_f0 * wf.freq_step
|
||||
print(f'Frequency {f0:.2f} Hz (bin {bin_f0}), coarse {f0_real:.2f} Hz')
|
||||
plt.grid()
|
||||
plt.show()
|
||||
|
||||
if use_downsample:
|
||||
fs2 = 100
|
||||
env_alpha = 0.06
|
||||
|
||||
sig2, f0_down = downsample_fft(wf.H[:, ::time_osr], bin_f0, fs2=fs2, freq_osr=freq_osr, time_osr=1)
|
||||
print(f'Downsampled signal to {fs2} Hz sample rate, freq shift {f0_real} Hz -> {f0_down} Hz')
|
||||
def main(wave, downsample, time, freq, noise, freq_osr=2, time_osr=2):
|
||||
fs, sig = load_wav(wave)
|
||||
logger.info(f"Sample rate: {fs} Hz")
|
||||
|
||||
pos_start = int(0.5 + time_start * fs2)
|
||||
max_freq_offset, max_pos_offset = search_sync_fine(sig2, fs2, f0_down, pos_start)
|
||||
f0_down_fine, pos_fine = max_freq_offset + f0_down, pos_start + max_pos_offset
|
||||
print(f'Fine sync at {f0_real:.2f} + {max_freq_offset:.2f} = {f0_real + max_freq_offset:.2f} Hz, {pos_start/fs2:.3f} + {max_pos_offset/fs2:.3f} = {pos_fine/fs2:.3f} s')
|
||||
if noise is not None:
|
||||
sig_rms = np.std(sig)
|
||||
logger.info(f"Signal RMS: {sig_rms}")
|
||||
np.random.seed(1337)
|
||||
sig += np.random.normal(0, sig_rms * np.pow(10, noise / 20), len(sig))
|
||||
|
||||
env = signal.filtfilt(env_alpha, [1, -(1-env_alpha)], np.abs(sig2))
|
||||
wf = Waterfall(
|
||||
freq_osr=freq_osr, time_osr=time_osr, freq_min=MIN_FREQ, freq_max=MAX_FREQ
|
||||
)
|
||||
wf.load_signal(sig, fs)
|
||||
|
||||
sym_size2 = int(fs2 * FT8_SYMBOL_PERIOD)
|
||||
ctweak = np.exp(-1j * 2*np.pi * np.arange(len(sig2)) * f0_down_fine/fs2)
|
||||
slice_pos = pos_start + max_pos_offset
|
||||
slice_length = int(FT8_NUM_SYMBOLS*FT8_SYMBOL_PERIOD*fs2)
|
||||
pad_left = pad_right = 0
|
||||
if slice_pos < 0:
|
||||
pad_left = -slice_pos
|
||||
slice_pos = 0
|
||||
if slice_pos + slice_length > len(sig2) + pad_left:
|
||||
pad_right = slice_pos + slice_length - (len(sig2) + pad_left)
|
||||
sig3 = np.pad(sig2*ctweak, (pad_left, pad_right), mode='constant', constant_values=(0, 0))[slice_pos:slice_pos + slice_length]
|
||||
_, _, H2 = signal.stft(sig3, window='boxcar', nperseg=sym_size2, noverlap=0, return_onesided=False, boundary=None, padded=False)
|
||||
A2db = lin_to_db(np.abs(H2[0:FT8_NUM_TONES, :]))
|
||||
if freq is not None and time is not None:
|
||||
candidates = [(freq, time)]
|
||||
else:
|
||||
time_offset = FT8_SYMBOL_PERIOD / 4
|
||||
pos_start = int(0.5 + (time_start + time_offset) / wf.time_step)
|
||||
print(f'Start time {time_start:.3f} s (pos {pos_start}), coarse {pos_start * wf.time_step - time_offset:.3f} s')
|
||||
# TODO: zero padding for time axis
|
||||
A2db = wf.Adb[bin_f0:bin_f0+freq_osr*FT8_NUM_TONES:freq_osr, pos_start:pos_start+FT8_NUM_SYMBOLS*time_osr:time_osr]
|
||||
candidates = search_sync_coarse(wf, snr_mode=1, max_cand=50)
|
||||
|
||||
A2db -= np.max(A2db, axis=0)
|
||||
logger.info(f"Coarse candidates: {len(candidates)}")
|
||||
|
||||
bits_logl, A2db_bits = extract_logl_db(A2db)
|
||||
(num_errors, bits) = ldpc.bp_solve(bits_logl, max_iters=30, max_no_improvement=15)
|
||||
print(f'LDPC decode: {num_errors} errors')
|
||||
if num_errors == 0:
|
||||
print(f'Payload bits: {"".join([str(x) for x in bits[:FT8_PAYLOAD_BITS]])}')
|
||||
print(f'CRC bits : {"".join([str(x) for x in bits[FT8_PAYLOAD_BITS:FT8_LDPC_PAYLOAD_BITS]])}')
|
||||
print(f'Parity bits : {"".join([str(x) for x in bits[FT8_LDPC_PAYLOAD_BITS:]])}')
|
||||
num_decoded += 1
|
||||
num_decoded = 0
|
||||
for f0, time_start in candidates:
|
||||
bin_f0 = int(0.5 + f0 / wf.freq_step)
|
||||
f0_coarse = bin_f0 * wf.freq_step
|
||||
|
||||
print(f'Total decoded: {num_decoded}')
|
||||
if downsample:
|
||||
fs2 = 100
|
||||
# env_alpha = 0.06
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.ticker as plticker
|
||||
import matplotlib.colors as pltcolors
|
||||
fig, ax = plt.subplots(4)
|
||||
sig_down, f0_down = downsample_fft(
|
||||
wf.H[:, ::time_osr], bin_f0, fs2=fs2, freq_osr=freq_osr, time_osr=1
|
||||
)
|
||||
|
||||
plt.colorbar(ax[0].imshow(A2db, cmap='inferno', norm=pltcolors.Normalize(-30, 0, clip=True)), orientation='horizontal', ax=ax[0])
|
||||
plt.colorbar(ax[1].imshow(A2db_bits.transpose(), cmap='bwr', norm=pltcolors.Normalize(-10, 10, clip=True)), orientation='horizontal', ax=ax[1])
|
||||
# ax[2].imshow(A2db_bits2, cmap='bwr', norm=pltcolors.Normalize(-10, 10, clip=True))
|
||||
ax[2].hist(bits_logl, bins=25)
|
||||
# ax[3].plot(np.arange(len(sig3))/sym_size2, np.real(sig3))
|
||||
# ax[3].plot(np.arange(len(sig3))/sym_size2, np.abs(sig3))
|
||||
ax[3].margins(0, 0)
|
||||
# loc = plticker.MultipleLocator(base=32.0) # this locator puts ticks at regular intervals
|
||||
# ax[1].xaxis.set_major_locator(loc)
|
||||
# ax[0].plot(np.array(all_powers).transpose())
|
||||
pos_start = int(0.5 + time_start * fs2)
|
||||
fine_freq_offset, fine_pos_offset = search_sync_fine(
|
||||
sig_down, fs2, f0_down, pos_start
|
||||
)
|
||||
f0_down_fine, pos_fine = (
|
||||
fine_freq_offset + f0_down,
|
||||
pos_start + fine_pos_offset,
|
||||
)
|
||||
|
||||
plt.grid()
|
||||
plt.show()
|
||||
# logger.info(
|
||||
# f"Downsampled signal to {fs2} Hz sample rate, freq shift {f0_coarse} Hz -> {f0_down} Hz"
|
||||
# )
|
||||
print(
|
||||
f"Fine sync at {f0_coarse:.2f} + {fine_freq_offset:.2f} = {f0_coarse + fine_freq_offset:.2f} Hz, {pos_start/fs2:.3f} + {fine_pos_offset/fs2:.3f} = {pos_fine/fs2:.3f} s"
|
||||
)
|
||||
|
||||
# env = signal.filtfilt(env_alpha, [1, -(1 - env_alpha)], np.abs(sig_down))
|
||||
|
||||
sym_size2 = int(fs2 * FT8_SYMBOL_PERIOD)
|
||||
ctweak = np.exp(
|
||||
-1j * 2 * np.pi * np.arange(len(sig_down)) * f0_down_fine / fs2
|
||||
)
|
||||
slice_pos = pos_start + fine_pos_offset
|
||||
slice_length = int(FT8_NUM_SYMBOLS * FT8_SYMBOL_PERIOD * fs2)
|
||||
pad_left = pad_right = 0
|
||||
if slice_pos < 0:
|
||||
pad_left = -slice_pos
|
||||
slice_pos = 0
|
||||
if slice_pos + slice_length > len(sig_down) + pad_left:
|
||||
pad_right = slice_pos + slice_length - (len(sig_down) + pad_left)
|
||||
sig3 = np.pad(
|
||||
sig_down * ctweak,
|
||||
(pad_left, pad_right),
|
||||
mode="constant",
|
||||
constant_values=(0, 0),
|
||||
)[slice_pos : slice_pos + slice_length]
|
||||
|
||||
# win = signal.windows.kaiser(sym_size2, beta=1.6)
|
||||
win = signal.windows.boxcar(sym_size2)
|
||||
_, _, H2 = signal.stft(
|
||||
sig3,
|
||||
window=win,
|
||||
nperseg=sym_size2,
|
||||
noverlap=0,
|
||||
return_onesided=False,
|
||||
boundary=None,
|
||||
padded=False,
|
||||
)
|
||||
A2db = lin_to_db(np.abs(H2[0:FT8_NUM_TONES, :]))
|
||||
else:
|
||||
time_offset = FT8_SYMBOL_PERIOD / 4
|
||||
pos_start = int(0.5 + (time_start + time_offset) / wf.time_step)
|
||||
# print(
|
||||
# f"Start time {time_start:.3f} s (pos {pos_start}), coarse {pos_start * wf.time_step - time_offset:.3f} s"
|
||||
# )
|
||||
# TODO: zero padding for time axis
|
||||
pad_left, pad_right = 0, 0
|
||||
if pos_start < 0:
|
||||
pad_left = -pos_start
|
||||
pos_start = 0
|
||||
if pos_start + wf.Adb.shape[1] > FT8_NUM_SYMBOLS * time_osr:
|
||||
pad_right = pos_start + wf.Adb.shape[1] - (FT8_NUM_SYMBOLS * time_osr)
|
||||
|
||||
Adb = np.pad(
|
||||
wf.Adb,
|
||||
(pad_left, pad_right),
|
||||
mode="constant",
|
||||
constant_values=(0, 0),
|
||||
)
|
||||
|
||||
A2db = Adb[
|
||||
bin_f0 : bin_f0 + freq_osr * FT8_NUM_TONES : freq_osr,
|
||||
pos_start : pos_start + FT8_NUM_SYMBOLS * time_osr : time_osr,
|
||||
]
|
||||
|
||||
A2db -= np.max(A2db, axis=0)
|
||||
|
||||
bits_logl, A2db_bits = extract_logl_db(A2db)
|
||||
(bits, num_errors, iters) = ldpc.bp_solve(
|
||||
bits_logl, max_iters=30, max_no_improvement=15
|
||||
)
|
||||
if num_errors == 0:
|
||||
logger.info(
|
||||
f"Frequency {f0:.2f} Hz (bin {bin_f0}), coarse {f0_coarse:.2f} Hz"
|
||||
)
|
||||
|
||||
print(f"LDPC decode: {num_errors} errors, {iters} iterations")
|
||||
|
||||
print(f'Payload bits: {"".join([str(x) for x in bits[:FT8_PAYLOAD_BITS]])}')
|
||||
print(
|
||||
f'CRC bits : {"".join([str(x) for x in bits[FT8_PAYLOAD_BITS:FT8_LDPC_PAYLOAD_BITS]])}'
|
||||
)
|
||||
print(
|
||||
f'Parity bits : {"".join([str(x) for x in bits[FT8_LDPC_PAYLOAD_BITS:]])}'
|
||||
)
|
||||
num_decoded += 1
|
||||
|
||||
show_plots(A2db, A2db_bits, bits_logl)
|
||||
break
|
||||
|
||||
logger.info(f"Total decoded: {num_decoded}")
|
||||
# show_plots(A2db, A2db_bits, bits_logl)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("wave", type=Path)
|
||||
parser.add_argument("-d", "--downsample", action="store_true")
|
||||
parser.add_argument("-t", "--time", nargs="?", type=float)
|
||||
parser.add_argument("-f", "--freq", nargs="?", type=float)
|
||||
parser.add_argument("-n", "--noise", nargs="?", type=float)
|
||||
args = parser.parse_args()
|
||||
|
||||
main(**vars(args))
|
||||
|
|
610
utils/ldpc.py
610
utils/ldpc.py
|
@ -9,7 +9,7 @@ def ldpc_check(codeword):
|
|||
for m in range(FTX_LDPC_M):
|
||||
x = False
|
||||
for i in range(kFTX_LDPC_Num_rows[m]):
|
||||
x ^= (codeword[kFTX_LDPC_Nm[m][i] - 1] > 0)
|
||||
x ^= codeword[kFTX_LDPC_Nm[m][i] - 1] > 0
|
||||
if x:
|
||||
errors += 1
|
||||
|
||||
|
@ -35,7 +35,7 @@ def bp_solve(codeword, max_iters=25, max_no_improvement=5):
|
|||
|
||||
# Check to see if we have a codeword (check before we do any iter)
|
||||
errors = ldpc_check(plain)
|
||||
print(f'iter {iter}, errors {errors}')
|
||||
# print(f"iter {iter}, errors {errors}")
|
||||
|
||||
if errors < last_errors:
|
||||
no_improvement = 1
|
||||
|
@ -74,282 +74,360 @@ def bp_solve(codeword, max_iters=25, max_no_improvement=5):
|
|||
Tmn *= toc[m][n_idx]
|
||||
tov[n][m_idx] = -2 * np.arctanh(Tmn)
|
||||
|
||||
return (min_errors, plain)
|
||||
return (plain, min_errors, iter)
|
||||
|
||||
|
||||
# Each row describes one LDPC parity check.
|
||||
# Each number is an index into the codeword (1-origin).
|
||||
# The codeword bits mentioned in each row must XOR to zero.
|
||||
kFTX_LDPC_Nm = [
|
||||
[ 4, 31, 59, 91, 92, 96, 153 ],
|
||||
[ 5, 32, 60, 93, 115, 146, 0 ],
|
||||
[ 6, 24, 61, 94, 122, 151, 0 ],
|
||||
[ 7, 33, 62, 95, 96, 143, 0 ],
|
||||
[ 8, 25, 63, 83, 93, 96, 148 ],
|
||||
[ 6, 32, 64, 97, 126, 138, 0 ],
|
||||
[ 5, 34, 65, 78, 98, 107, 154 ],
|
||||
[ 9, 35, 66, 99, 139, 146, 0 ],
|
||||
[ 10, 36, 67, 100, 107, 126, 0 ],
|
||||
[ 11, 37, 67, 87, 101, 139, 158 ],
|
||||
[ 12, 38, 68, 102, 105, 155, 0 ],
|
||||
[ 13, 39, 69, 103, 149, 162, 0 ],
|
||||
[ 8, 40, 70, 82, 104, 114, 145 ],
|
||||
[ 14, 41, 71, 88, 102, 123, 156 ],
|
||||
[ 15, 42, 59, 106, 123, 159, 0 ],
|
||||
[ 1, 33, 72, 106, 107, 157, 0 ],
|
||||
[ 16, 43, 73, 108, 141, 160, 0 ],
|
||||
[ 17, 37, 74, 81, 109, 131, 154 ],
|
||||
[ 11, 44, 75, 110, 121, 166, 0 ],
|
||||
[ 45, 55, 64, 111, 130, 161, 173 ],
|
||||
[ 8, 46, 71, 112, 119, 166, 0 ],
|
||||
[ 18, 36, 76, 89, 113, 114, 143 ],
|
||||
[ 19, 38, 77, 104, 116, 163, 0 ],
|
||||
[ 20, 47, 70, 92, 138, 165, 0 ],
|
||||
[ 2, 48, 74, 113, 128, 160, 0 ],
|
||||
[ 21, 45, 78, 83, 117, 121, 151 ],
|
||||
[ 22, 47, 58, 118, 127, 164, 0 ],
|
||||
[ 16, 39, 62, 112, 134, 158, 0 ],
|
||||
[ 23, 43, 79, 120, 131, 145, 0 ],
|
||||
[ 19, 35, 59, 73, 110, 125, 161 ],
|
||||
[ 20, 36, 63, 94, 136, 161, 0 ],
|
||||
[ 14, 31, 79, 98, 132, 164, 0 ],
|
||||
[ 3, 44, 80, 124, 127, 169, 0 ],
|
||||
[ 19, 46, 81, 117, 135, 167, 0 ],
|
||||
[ 7, 49, 58, 90, 100, 105, 168 ],
|
||||
[ 12, 50, 61, 118, 119, 144, 0 ],
|
||||
[ 13, 51, 64, 114, 118, 157, 0 ],
|
||||
[ 24, 52, 76, 129, 148, 149, 0 ],
|
||||
[ 25, 53, 69, 90, 101, 130, 156 ],
|
||||
[ 20, 46, 65, 80, 120, 140, 170 ],
|
||||
[ 21, 54, 77, 100, 140, 171, 0 ],
|
||||
[ 35, 82, 133, 142, 171, 174, 0 ],
|
||||
[ 14, 30, 83, 113, 125, 170, 0 ],
|
||||
[ 4, 29, 68, 120, 134, 173, 0 ],
|
||||
[ 1, 4, 52, 57, 86, 136, 152 ],
|
||||
[ 26, 51, 56, 91, 122, 137, 168 ],
|
||||
[ 52, 84, 110, 115, 145, 168, 0 ],
|
||||
[ 7, 50, 81, 99, 132, 173, 0 ],
|
||||
[ 23, 55, 67, 95, 172, 174, 0 ],
|
||||
[ 26, 41, 77, 109, 141, 148, 0 ],
|
||||
[ 2, 27, 41, 61, 62, 115, 133 ],
|
||||
[ 27, 40, 56, 124, 125, 126, 0 ],
|
||||
[ 18, 49, 55, 124, 141, 167, 0 ],
|
||||
[ 6, 33, 85, 108, 116, 156, 0 ],
|
||||
[ 28, 48, 70, 85, 105, 129, 158 ],
|
||||
[ 9, 54, 63, 131, 147, 155, 0 ],
|
||||
[ 22, 53, 68, 109, 121, 174, 0 ],
|
||||
[ 3, 13, 48, 78, 95, 123, 0 ],
|
||||
[ 31, 69, 133, 150, 155, 169, 0 ],
|
||||
[ 12, 43, 66, 89, 97, 135, 159 ],
|
||||
[ 5, 39, 75, 102, 136, 167, 0 ],
|
||||
[ 2, 54, 86, 101, 135, 164, 0 ],
|
||||
[ 15, 56, 87, 108, 119, 171, 0 ],
|
||||
[ 10, 44, 82, 91, 111, 144, 149 ],
|
||||
[ 23, 34, 71, 94, 127, 153, 0 ],
|
||||
[ 11, 49, 88, 92, 142, 157, 0 ],
|
||||
[ 29, 34, 87, 97, 147, 162, 0 ],
|
||||
[ 30, 50, 60, 86, 137, 142, 162 ],
|
||||
[ 10, 53, 66, 84, 112, 128, 165 ],
|
||||
[ 22, 57, 85, 93, 140, 159, 0 ],
|
||||
[ 28, 32, 72, 103, 132, 166, 0 ],
|
||||
[ 28, 29, 84, 88, 117, 143, 150 ],
|
||||
[ 1, 26, 45, 80, 128, 147, 0 ],
|
||||
[ 17, 27, 89, 103, 116, 153, 0 ],
|
||||
[ 51, 57, 98, 163, 165, 172, 0 ],
|
||||
[ 21, 37, 73, 138, 152, 169, 0 ],
|
||||
[ 16, 47, 76, 130, 137, 154, 0 ],
|
||||
[ 3, 24, 30, 72, 104, 139, 0 ],
|
||||
[ 9, 40, 90, 106, 134, 151, 0 ],
|
||||
[ 15, 58, 60, 74, 111, 150, 163 ],
|
||||
[ 18, 42, 79, 144, 146, 152, 0 ],
|
||||
[ 25, 38, 65, 99, 122, 160, 0 ],
|
||||
[ 17, 42, 75, 129, 170, 172, 0 ]
|
||||
[4, 31, 59, 91, 92, 96, 153],
|
||||
[5, 32, 60, 93, 115, 146, 0],
|
||||
[6, 24, 61, 94, 122, 151, 0],
|
||||
[7, 33, 62, 95, 96, 143, 0],
|
||||
[8, 25, 63, 83, 93, 96, 148],
|
||||
[6, 32, 64, 97, 126, 138, 0],
|
||||
[5, 34, 65, 78, 98, 107, 154],
|
||||
[9, 35, 66, 99, 139, 146, 0],
|
||||
[10, 36, 67, 100, 107, 126, 0],
|
||||
[11, 37, 67, 87, 101, 139, 158],
|
||||
[12, 38, 68, 102, 105, 155, 0],
|
||||
[13, 39, 69, 103, 149, 162, 0],
|
||||
[8, 40, 70, 82, 104, 114, 145],
|
||||
[14, 41, 71, 88, 102, 123, 156],
|
||||
[15, 42, 59, 106, 123, 159, 0],
|
||||
[1, 33, 72, 106, 107, 157, 0],
|
||||
[16, 43, 73, 108, 141, 160, 0],
|
||||
[17, 37, 74, 81, 109, 131, 154],
|
||||
[11, 44, 75, 110, 121, 166, 0],
|
||||
[45, 55, 64, 111, 130, 161, 173],
|
||||
[8, 46, 71, 112, 119, 166, 0],
|
||||
[18, 36, 76, 89, 113, 114, 143],
|
||||
[19, 38, 77, 104, 116, 163, 0],
|
||||
[20, 47, 70, 92, 138, 165, 0],
|
||||
[2, 48, 74, 113, 128, 160, 0],
|
||||
[21, 45, 78, 83, 117, 121, 151],
|
||||
[22, 47, 58, 118, 127, 164, 0],
|
||||
[16, 39, 62, 112, 134, 158, 0],
|
||||
[23, 43, 79, 120, 131, 145, 0],
|
||||
[19, 35, 59, 73, 110, 125, 161],
|
||||
[20, 36, 63, 94, 136, 161, 0],
|
||||
[14, 31, 79, 98, 132, 164, 0],
|
||||
[3, 44, 80, 124, 127, 169, 0],
|
||||
[19, 46, 81, 117, 135, 167, 0],
|
||||
[7, 49, 58, 90, 100, 105, 168],
|
||||
[12, 50, 61, 118, 119, 144, 0],
|
||||
[13, 51, 64, 114, 118, 157, 0],
|
||||
[24, 52, 76, 129, 148, 149, 0],
|
||||
[25, 53, 69, 90, 101, 130, 156],
|
||||
[20, 46, 65, 80, 120, 140, 170],
|
||||
[21, 54, 77, 100, 140, 171, 0],
|
||||
[35, 82, 133, 142, 171, 174, 0],
|
||||
[14, 30, 83, 113, 125, 170, 0],
|
||||
[4, 29, 68, 120, 134, 173, 0],
|
||||
[1, 4, 52, 57, 86, 136, 152],
|
||||
[26, 51, 56, 91, 122, 137, 168],
|
||||
[52, 84, 110, 115, 145, 168, 0],
|
||||
[7, 50, 81, 99, 132, 173, 0],
|
||||
[23, 55, 67, 95, 172, 174, 0],
|
||||
[26, 41, 77, 109, 141, 148, 0],
|
||||
[2, 27, 41, 61, 62, 115, 133],
|
||||
[27, 40, 56, 124, 125, 126, 0],
|
||||
[18, 49, 55, 124, 141, 167, 0],
|
||||
[6, 33, 85, 108, 116, 156, 0],
|
||||
[28, 48, 70, 85, 105, 129, 158],
|
||||
[9, 54, 63, 131, 147, 155, 0],
|
||||
[22, 53, 68, 109, 121, 174, 0],
|
||||
[3, 13, 48, 78, 95, 123, 0],
|
||||
[31, 69, 133, 150, 155, 169, 0],
|
||||
[12, 43, 66, 89, 97, 135, 159],
|
||||
[5, 39, 75, 102, 136, 167, 0],
|
||||
[2, 54, 86, 101, 135, 164, 0],
|
||||
[15, 56, 87, 108, 119, 171, 0],
|
||||
[10, 44, 82, 91, 111, 144, 149],
|
||||
[23, 34, 71, 94, 127, 153, 0],
|
||||
[11, 49, 88, 92, 142, 157, 0],
|
||||
[29, 34, 87, 97, 147, 162, 0],
|
||||
[30, 50, 60, 86, 137, 142, 162],
|
||||
[10, 53, 66, 84, 112, 128, 165],
|
||||
[22, 57, 85, 93, 140, 159, 0],
|
||||
[28, 32, 72, 103, 132, 166, 0],
|
||||
[28, 29, 84, 88, 117, 143, 150],
|
||||
[1, 26, 45, 80, 128, 147, 0],
|
||||
[17, 27, 89, 103, 116, 153, 0],
|
||||
[51, 57, 98, 163, 165, 172, 0],
|
||||
[21, 37, 73, 138, 152, 169, 0],
|
||||
[16, 47, 76, 130, 137, 154, 0],
|
||||
[3, 24, 30, 72, 104, 139, 0],
|
||||
[9, 40, 90, 106, 134, 151, 0],
|
||||
[15, 58, 60, 74, 111, 150, 163],
|
||||
[18, 42, 79, 144, 146, 152, 0],
|
||||
[25, 38, 65, 99, 122, 160, 0],
|
||||
[17, 42, 75, 129, 170, 172, 0],
|
||||
]
|
||||
|
||||
# Each row corresponds to a codeword bit.
|
||||
# The numbers indicate which three LDPC parity checks (rows in Nm) refer to the codeword bit.
|
||||
# 1-origin.
|
||||
kFTX_LDPC_Mn = [
|
||||
[ 16, 45, 73 ],
|
||||
[ 25, 51, 62 ],
|
||||
[ 33, 58, 78 ],
|
||||
[ 1, 44, 45 ],
|
||||
[ 2, 7, 61 ],
|
||||
[ 3, 6, 54 ],
|
||||
[ 4, 35, 48 ],
|
||||
[ 5, 13, 21 ],
|
||||
[ 8, 56, 79 ],
|
||||
[ 9, 64, 69 ],
|
||||
[ 10, 19, 66 ],
|
||||
[ 11, 36, 60 ],
|
||||
[ 12, 37, 58 ],
|
||||
[ 14, 32, 43 ],
|
||||
[ 15, 63, 80 ],
|
||||
[ 17, 28, 77 ],
|
||||
[ 18, 74, 83 ],
|
||||
[ 22, 53, 81 ],
|
||||
[ 23, 30, 34 ],
|
||||
[ 24, 31, 40 ],
|
||||
[ 26, 41, 76 ],
|
||||
[ 27, 57, 70 ],
|
||||
[ 29, 49, 65 ],
|
||||
[ 3, 38, 78 ],
|
||||
[ 5, 39, 82 ],
|
||||
[ 46, 50, 73 ],
|
||||
[ 51, 52, 74 ],
|
||||
[ 55, 71, 72 ],
|
||||
[ 44, 67, 72 ],
|
||||
[ 43, 68, 78 ],
|
||||
[ 1, 32, 59 ],
|
||||
[ 2, 6, 71 ],
|
||||
[ 4, 16, 54 ],
|
||||
[ 7, 65, 67 ],
|
||||
[ 8, 30, 42 ],
|
||||
[ 9, 22, 31 ],
|
||||
[ 10, 18, 76 ],
|
||||
[ 11, 23, 82 ],
|
||||
[ 12, 28, 61 ],
|
||||
[ 13, 52, 79 ],
|
||||
[ 14, 50, 51 ],
|
||||
[ 15, 81, 83 ],
|
||||
[ 17, 29, 60 ],
|
||||
[ 19, 33, 64 ],
|
||||
[ 20, 26, 73 ],
|
||||
[ 21, 34, 40 ],
|
||||
[ 24, 27, 77 ],
|
||||
[ 25, 55, 58 ],
|
||||
[ 35, 53, 66 ],
|
||||
[ 36, 48, 68 ],
|
||||
[ 37, 46, 75 ],
|
||||
[ 38, 45, 47 ],
|
||||
[ 39, 57, 69 ],
|
||||
[ 41, 56, 62 ],
|
||||
[ 20, 49, 53 ],
|
||||
[ 46, 52, 63 ],
|
||||
[ 45, 70, 75 ],
|
||||
[ 27, 35, 80 ],
|
||||
[ 1, 15, 30 ],
|
||||
[ 2, 68, 80 ],
|
||||
[ 3, 36, 51 ],
|
||||
[ 4, 28, 51 ],
|
||||
[ 5, 31, 56 ],
|
||||
[ 6, 20, 37 ],
|
||||
[ 7, 40, 82 ],
|
||||
[ 8, 60, 69 ],
|
||||
[ 9, 10, 49 ],
|
||||
[ 11, 44, 57 ],
|
||||
[ 12, 39, 59 ],
|
||||
[ 13, 24, 55 ],
|
||||
[ 14, 21, 65 ],
|
||||
[ 16, 71, 78 ],
|
||||
[ 17, 30, 76 ],
|
||||
[ 18, 25, 80 ],
|
||||
[ 19, 61, 83 ],
|
||||
[ 22, 38, 77 ],
|
||||
[ 23, 41, 50 ],
|
||||
[ 7, 26, 58 ],
|
||||
[ 29, 32, 81 ],
|
||||
[ 33, 40, 73 ],
|
||||
[ 18, 34, 48 ],
|
||||
[ 13, 42, 64 ],
|
||||
[ 5, 26, 43 ],
|
||||
[ 47, 69, 72 ],
|
||||
[ 54, 55, 70 ],
|
||||
[ 45, 62, 68 ],
|
||||
[ 10, 63, 67 ],
|
||||
[ 14, 66, 72 ],
|
||||
[ 22, 60, 74 ],
|
||||
[ 35, 39, 79 ],
|
||||
[ 1, 46, 64 ],
|
||||
[ 1, 24, 66 ],
|
||||
[ 2, 5, 70 ],
|
||||
[ 3, 31, 65 ],
|
||||
[ 4, 49, 58 ],
|
||||
[ 1, 4, 5 ],
|
||||
[ 6, 60, 67 ],
|
||||
[ 7, 32, 75 ],
|
||||
[ 8, 48, 82 ],
|
||||
[ 9, 35, 41 ],
|
||||
[ 10, 39, 62 ],
|
||||
[ 11, 14, 61 ],
|
||||
[ 12, 71, 74 ],
|
||||
[ 13, 23, 78 ],
|
||||
[ 11, 35, 55 ],
|
||||
[ 15, 16, 79 ],
|
||||
[ 7, 9, 16 ],
|
||||
[ 17, 54, 63 ],
|
||||
[ 18, 50, 57 ],
|
||||
[ 19, 30, 47 ],
|
||||
[ 20, 64, 80 ],
|
||||
[ 21, 28, 69 ],
|
||||
[ 22, 25, 43 ],
|
||||
[ 13, 22, 37 ],
|
||||
[ 2, 47, 51 ],
|
||||
[ 23, 54, 74 ],
|
||||
[ 26, 34, 72 ],
|
||||
[ 27, 36, 37 ],
|
||||
[ 21, 36, 63 ],
|
||||
[ 29, 40, 44 ],
|
||||
[ 19, 26, 57 ],
|
||||
[ 3, 46, 82 ],
|
||||
[ 14, 15, 58 ],
|
||||
[ 33, 52, 53 ],
|
||||
[ 30, 43, 52 ],
|
||||
[ 6, 9, 52 ],
|
||||
[ 27, 33, 65 ],
|
||||
[ 25, 69, 73 ],
|
||||
[ 38, 55, 83 ],
|
||||
[ 20, 39, 77 ],
|
||||
[ 18, 29, 56 ],
|
||||
[ 32, 48, 71 ],
|
||||
[ 42, 51, 59 ],
|
||||
[ 28, 44, 79 ],
|
||||
[ 34, 60, 62 ],
|
||||
[ 31, 45, 61 ],
|
||||
[ 46, 68, 77 ],
|
||||
[ 6, 24, 76 ],
|
||||
[ 8, 10, 78 ],
|
||||
[ 40, 41, 70 ],
|
||||
[ 17, 50, 53 ],
|
||||
[ 42, 66, 68 ],
|
||||
[ 4, 22, 72 ],
|
||||
[ 36, 64, 81 ],
|
||||
[ 13, 29, 47 ],
|
||||
[ 2, 8, 81 ],
|
||||
[ 56, 67, 73 ],
|
||||
[ 5, 38, 50 ],
|
||||
[ 12, 38, 64 ],
|
||||
[ 59, 72, 80 ],
|
||||
[ 3, 26, 79 ],
|
||||
[ 45, 76, 81 ],
|
||||
[ 1, 65, 74 ],
|
||||
[ 7, 18, 77 ],
|
||||
[ 11, 56, 59 ],
|
||||
[ 14, 39, 54 ],
|
||||
[ 16, 37, 66 ],
|
||||
[ 10, 28, 55 ],
|
||||
[ 15, 60, 70 ],
|
||||
[ 17, 25, 82 ],
|
||||
[ 20, 30, 31 ],
|
||||
[ 12, 67, 68 ],
|
||||
[ 23, 75, 80 ],
|
||||
[ 27, 32, 62 ],
|
||||
[ 24, 69, 75 ],
|
||||
[ 19, 21, 71 ],
|
||||
[ 34, 53, 61 ],
|
||||
[ 35, 46, 47 ],
|
||||
[ 33, 59, 76 ],
|
||||
[ 40, 43, 83 ],
|
||||
[ 41, 42, 63 ],
|
||||
[ 49, 75, 83 ],
|
||||
[ 20, 44, 48 ],
|
||||
[ 42, 49, 57 ]
|
||||
[16, 45, 73],
|
||||
[25, 51, 62],
|
||||
[33, 58, 78],
|
||||
[1, 44, 45],
|
||||
[2, 7, 61],
|
||||
[3, 6, 54],
|
||||
[4, 35, 48],
|
||||
[5, 13, 21],
|
||||
[8, 56, 79],
|
||||
[9, 64, 69],
|
||||
[10, 19, 66],
|
||||
[11, 36, 60],
|
||||
[12, 37, 58],
|
||||
[14, 32, 43],
|
||||
[15, 63, 80],
|
||||
[17, 28, 77],
|
||||
[18, 74, 83],
|
||||
[22, 53, 81],
|
||||
[23, 30, 34],
|
||||
[24, 31, 40],
|
||||
[26, 41, 76],
|
||||
[27, 57, 70],
|
||||
[29, 49, 65],
|
||||
[3, 38, 78],
|
||||
[5, 39, 82],
|
||||
[46, 50, 73],
|
||||
[51, 52, 74],
|
||||
[55, 71, 72],
|
||||
[44, 67, 72],
|
||||
[43, 68, 78],
|
||||
[1, 32, 59],
|
||||
[2, 6, 71],
|
||||
[4, 16, 54],
|
||||
[7, 65, 67],
|
||||
[8, 30, 42],
|
||||
[9, 22, 31],
|
||||
[10, 18, 76],
|
||||
[11, 23, 82],
|
||||
[12, 28, 61],
|
||||
[13, 52, 79],
|
||||
[14, 50, 51],
|
||||
[15, 81, 83],
|
||||
[17, 29, 60],
|
||||
[19, 33, 64],
|
||||
[20, 26, 73],
|
||||
[21, 34, 40],
|
||||
[24, 27, 77],
|
||||
[25, 55, 58],
|
||||
[35, 53, 66],
|
||||
[36, 48, 68],
|
||||
[37, 46, 75],
|
||||
[38, 45, 47],
|
||||
[39, 57, 69],
|
||||
[41, 56, 62],
|
||||
[20, 49, 53],
|
||||
[46, 52, 63],
|
||||
[45, 70, 75],
|
||||
[27, 35, 80],
|
||||
[1, 15, 30],
|
||||
[2, 68, 80],
|
||||
[3, 36, 51],
|
||||
[4, 28, 51],
|
||||
[5, 31, 56],
|
||||
[6, 20, 37],
|
||||
[7, 40, 82],
|
||||
[8, 60, 69],
|
||||
[9, 10, 49],
|
||||
[11, 44, 57],
|
||||
[12, 39, 59],
|
||||
[13, 24, 55],
|
||||
[14, 21, 65],
|
||||
[16, 71, 78],
|
||||
[17, 30, 76],
|
||||
[18, 25, 80],
|
||||
[19, 61, 83],
|
||||
[22, 38, 77],
|
||||
[23, 41, 50],
|
||||
[7, 26, 58],
|
||||
[29, 32, 81],
|
||||
[33, 40, 73],
|
||||
[18, 34, 48],
|
||||
[13, 42, 64],
|
||||
[5, 26, 43],
|
||||
[47, 69, 72],
|
||||
[54, 55, 70],
|
||||
[45, 62, 68],
|
||||
[10, 63, 67],
|
||||
[14, 66, 72],
|
||||
[22, 60, 74],
|
||||
[35, 39, 79],
|
||||
[1, 46, 64],
|
||||
[1, 24, 66],
|
||||
[2, 5, 70],
|
||||
[3, 31, 65],
|
||||
[4, 49, 58],
|
||||
[1, 4, 5],
|
||||
[6, 60, 67],
|
||||
[7, 32, 75],
|
||||
[8, 48, 82],
|
||||
[9, 35, 41],
|
||||
[10, 39, 62],
|
||||
[11, 14, 61],
|
||||
[12, 71, 74],
|
||||
[13, 23, 78],
|
||||
[11, 35, 55],
|
||||
[15, 16, 79],
|
||||
[7, 9, 16],
|
||||
[17, 54, 63],
|
||||
[18, 50, 57],
|
||||
[19, 30, 47],
|
||||
[20, 64, 80],
|
||||
[21, 28, 69],
|
||||
[22, 25, 43],
|
||||
[13, 22, 37],
|
||||
[2, 47, 51],
|
||||
[23, 54, 74],
|
||||
[26, 34, 72],
|
||||
[27, 36, 37],
|
||||
[21, 36, 63],
|
||||
[29, 40, 44],
|
||||
[19, 26, 57],
|
||||
[3, 46, 82],
|
||||
[14, 15, 58],
|
||||
[33, 52, 53],
|
||||
[30, 43, 52],
|
||||
[6, 9, 52],
|
||||
[27, 33, 65],
|
||||
[25, 69, 73],
|
||||
[38, 55, 83],
|
||||
[20, 39, 77],
|
||||
[18, 29, 56],
|
||||
[32, 48, 71],
|
||||
[42, 51, 59],
|
||||
[28, 44, 79],
|
||||
[34, 60, 62],
|
||||
[31, 45, 61],
|
||||
[46, 68, 77],
|
||||
[6, 24, 76],
|
||||
[8, 10, 78],
|
||||
[40, 41, 70],
|
||||
[17, 50, 53],
|
||||
[42, 66, 68],
|
||||
[4, 22, 72],
|
||||
[36, 64, 81],
|
||||
[13, 29, 47],
|
||||
[2, 8, 81],
|
||||
[56, 67, 73],
|
||||
[5, 38, 50],
|
||||
[12, 38, 64],
|
||||
[59, 72, 80],
|
||||
[3, 26, 79],
|
||||
[45, 76, 81],
|
||||
[1, 65, 74],
|
||||
[7, 18, 77],
|
||||
[11, 56, 59],
|
||||
[14, 39, 54],
|
||||
[16, 37, 66],
|
||||
[10, 28, 55],
|
||||
[15, 60, 70],
|
||||
[17, 25, 82],
|
||||
[20, 30, 31],
|
||||
[12, 67, 68],
|
||||
[23, 75, 80],
|
||||
[27, 32, 62],
|
||||
[24, 69, 75],
|
||||
[19, 21, 71],
|
||||
[34, 53, 61],
|
||||
[35, 46, 47],
|
||||
[33, 59, 76],
|
||||
[40, 43, 83],
|
||||
[41, 42, 63],
|
||||
[49, 75, 83],
|
||||
[20, 44, 48],
|
||||
[42, 49, 57],
|
||||
]
|
||||
|
||||
kFTX_LDPC_Num_rows = [
|
||||
7, 6, 6, 6, 7, 6, 7, 6, 6, 7, 6, 6, 7, 7, 6, 6,
|
||||
6, 7, 6, 7, 6, 7, 6, 6, 6, 7, 6, 6, 6, 7, 6, 6,
|
||||
6, 6, 7, 6, 6, 6, 7, 7, 6, 6, 6, 6, 7, 7, 6, 6,
|
||||
6, 6, 7, 6, 6, 6, 7, 6, 6, 6, 6, 7, 6, 6, 6, 7,
|
||||
6, 6, 6, 7, 7, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 7,
|
||||
6, 6, 6
|
||||
7,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
6,
|
||||
7,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
7,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
6,
|
||||
7,
|
||||
6,
|
||||
7,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
7,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
7,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
7,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
]
|
||||
|
|
|
@ -2,62 +2,75 @@
|
|||
|
||||
import sys, os, subprocess
|
||||
|
||||
|
||||
def parse(line):
|
||||
fields = line.strip().split()
|
||||
freq = fields[3]
|
||||
dest = fields[5] if len(fields) > 5 else ''
|
||||
source = fields[6] if len(fields) > 6 else ''
|
||||
report = fields[7] if len(fields) > 7 else ''
|
||||
if dest and dest[0] == '<' and dest[-1] == '>':
|
||||
dest = '<...>'
|
||||
if source and source[0] == '<' and source[-1] == '>':
|
||||
source = '<...>'
|
||||
return ' '.join([dest, source, report])
|
||||
dest = fields[5] if len(fields) > 5 else ""
|
||||
source = fields[6] if len(fields) > 6 else ""
|
||||
report = fields[7] if len(fields) > 7 else ""
|
||||
if dest and dest[0] == "<" and dest[-1] == ">":
|
||||
dest = "<...>"
|
||||
if source and source[0] == "<" and source[-1] == ">":
|
||||
source = "<...>"
|
||||
return " ".join([dest, source, report])
|
||||
|
||||
|
||||
wav_dir = sys.argv[1]
|
||||
wav_files = [os.path.join(wav_dir, f) for f in os.listdir(wav_dir)]
|
||||
wav_files = [f for f in wav_files if os.path.isfile(f) and os.path.splitext(f)[1] == '.wav']
|
||||
txt_files = [os.path.splitext(f)[0] + '.txt' for f in wav_files]
|
||||
wav_files = [
|
||||
f for f in wav_files if os.path.isfile(f) and os.path.splitext(f)[1] == ".wav"
|
||||
]
|
||||
txt_files = [os.path.splitext(f)[0] + ".txt" for f in wav_files]
|
||||
|
||||
is_ft4 = False
|
||||
if len(sys.argv) > 2 and sys.argv[2] == '-ft4':
|
||||
if len(sys.argv) > 2 and sys.argv[2] == "-ft4":
|
||||
is_ft4 = True
|
||||
|
||||
n_extra = 0
|
||||
n_missed = 0
|
||||
n_total = 0
|
||||
for wav_file, txt_file in zip(wav_files, txt_files):
|
||||
if not os.path.isfile(txt_file): continue
|
||||
if not os.path.isfile(txt_file):
|
||||
continue
|
||||
print(wav_file)
|
||||
cmd_args = ['./decode_ft8', wav_file]
|
||||
cmd_args = ["./decode_ft8", wav_file]
|
||||
if is_ft4:
|
||||
cmd_args.append('-ft4')
|
||||
cmd_args.append("-ft4")
|
||||
result = subprocess.run(cmd_args, stdout=subprocess.PIPE)
|
||||
result = result.stdout.decode('utf-8').split('\n')
|
||||
result = result.stdout.decode("utf-8").split("\n")
|
||||
result = [parse(x) for x in result if len(x) > 0]
|
||||
#print(result[0])
|
||||
# print(result[0])
|
||||
result = set(result)
|
||||
|
||||
expected = open(txt_file).read().split('\n')
|
||||
|
||||
expected = open(txt_file).read().split("\n")
|
||||
expected = [parse(x) for x in expected if len(x) > 0]
|
||||
#print(expected[0])
|
||||
# print(expected[0])
|
||||
expected = set(expected)
|
||||
|
||||
|
||||
extra_decodes = result - expected
|
||||
missed_decodes = expected - result
|
||||
print(len(result), '/', len(expected))
|
||||
print(len(result), "/", len(expected))
|
||||
if len(extra_decodes) > 0:
|
||||
print('Extra decodes: ', list(extra_decodes))
|
||||
print("Extra decodes: ", list(extra_decodes))
|
||||
if len(missed_decodes) > 0:
|
||||
print('Missed decodes: ', list(missed_decodes))
|
||||
|
||||
print("Missed decodes: ", list(missed_decodes))
|
||||
|
||||
n_total += len(expected)
|
||||
n_extra += len(extra_decodes)
|
||||
n_missed += len(missed_decodes)
|
||||
|
||||
#break
|
||||
|
||||
print('Total: %d, extra: %d (%.1f%%), missed: %d (%.1f%%)' %
|
||||
(n_total, n_extra, 100.0*n_extra/n_total, n_missed, 100.0*n_missed/n_total))
|
||||
# break
|
||||
|
||||
print(
|
||||
"Total: %d, extra: %d (%.1f%%), missed: %d (%.1f%%)"
|
||||
% (
|
||||
n_total,
|
||||
n_extra,
|
||||
100.0 * n_extra / n_total,
|
||||
n_missed,
|
||||
100.0 * n_missed / n_total,
|
||||
)
|
||||
)
|
||||
recall = (n_total - n_missed) / float(n_total)
|
||||
print('Recall: %.1f%%' % (100*recall, ))
|
||||
print("Recall: %.1f%%" % (100 * recall,))
|
||||
|
|
Ładowanie…
Reference in New Issue