Skip to content

Commit

Permalink
Merge branch 'main' into dr-est_snr
Browse files Browse the repository at this point in the history
  • Loading branch information
tmiw committed Jan 7, 2025
2 parents 7535fa0 + 4257358 commit 745686f
Show file tree
Hide file tree
Showing 14 changed files with 276 additions and 159 deletions.
48 changes: 42 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ add_test(NAME radae_tx_basic
./inference.sh model19_check3/checkpoints/checkpoint_epoch_100.pth wav/brian_g8sez.wav /dev/null \
--rate_Fs --pilots --pilot_eq --eq_ls --cp 0.004 --bottleneck 3 --auxdata --write_rx rx.f32 --correct_freq_offset; \
cat features_in.f32 | python3 radae_txe.py --model model19_check3/checkpoints/checkpoint_epoch_100.pth --txbpf > rx.f32
cat rx.f32 | python3 radae_rxe.py --model model19_check3/checkpoints/checkpoint_epoch_100.pth -v 1 > features_txs_out.f32; \
cat rx.f32 | python3 radae_rxe.py --model model19_check3/checkpoints/checkpoint_epoch_100.pth -v 2 > features_txs_out.f32; \
python3 loss.py features_in.f32 features_txs_out.f32 --loss_test 0.15 --acq_time_test 0.5 --clip_start 5")
set_tests_properties(radae_tx_basic PROPERTIES PASS_REGULAR_EXPRESSION "PASS")

Expand Down Expand Up @@ -291,10 +291,10 @@ add_test(NAME rx_streaming
# basic test of streaming rx, run rx in vanilla and streaming, compare
add_test(NAME radae_rx_basic
COMMAND sh -c "cd ${CMAKE_SOURCE_DIR}; \
./inference.sh model17/checkpoints/checkpoint_epoch_100.pth wav/brian_g8sez.wav /dev/null \
--EbNodB 10 --freq_offset 11 \
./inference.sh model19_check3/checkpoints/checkpoint_epoch_100.pth wav/brian_g8sez.wav /dev/null \
--EbNodB 10 --freq_offset 11 --prepend_noise 1 --append_noise 1 --end_of_over --auxdata \
--rate_Fs --pilots --pilot_eq --eq_ls --cp 0.004 --bottleneck 3 --write_rx rx.f32 --correct_freq_offset; \
cat rx.f32 | PYTHONPATH='../' python3 radae_rxe.py --model model17/checkpoints/checkpoint_epoch_100.pth -v 1 --noauxdata > features_rxs_out.f32; \
cat rx.f32 | PYTHONPATH='../' python3 radae_rxe.py -v 2 > features_rxs_out.f32; \
python3 loss.py features_in.f32 features_rxs_out.f32 --loss_test 0.15 --acq_time_test 0.5")
set_tests_properties(radae_rx_basic PROPERTIES PASS_REGULAR_EXPRESSION "PASS")

Expand All @@ -309,7 +309,7 @@ add_test(NAME radae_rx_awgn
--rate_Fs --pilots --pilot_eq --eq_ls --cp 0.004 --bottleneck 3 --time_offset -16 --write_rx rx.f32 \
--prepend_noise 1 --append_noise 3 --end_of_over --auxdata --correct_freq_offset; \
cat rx.f32 | python3 radae_rxe.py --model model19_check3/checkpoints/checkpoint_epoch_100.pth -v 2 > features_rx_out.f32; \
python3 loss.py features_in.f32 features_rx_out.f32 --loss 0.3 --acq_time_test 1.0 --clip_end 100")
python3 loss.py features_in.f32 features_rx_out.f32 --loss 0.3 --acq_time_test 1.0 --clip_end 300")
set_tests_properties(radae_rx_awgn PROPERTIES PASS_REGULAR_EXPRESSION "PASS")

# SNR=0dB MPP
Expand Down Expand Up @@ -488,7 +488,8 @@ add_test(NAME radae_tx_embed_c
add_test(NAME radae_rx_embed_c
COMMAND sh -c "cd ${CMAKE_SOURCE_DIR}; \
./inference.sh model19_check3/checkpoints/checkpoint_epoch_100.pth wav/brian_g8sez.wav /dev/null \
--rate_Fs --pilots --pilot_eq --eq_ls --cp 0.004 --bottleneck 3 --auxdata --write_rx rx.f32 --correct_freq_offset; \
--EbNodB 100 --freq_offset 0 --append_noise 1 --end_of_over --auxdata \
--rate_Fs --pilots --pilot_eq --eq_ls --cp 0.004 --bottleneck 3 --write_rx rx.f32 --correct_freq_offset; \
cat rx.f32 | PYTHONPATH='.' ${CMAKE_CURRENT_BINARY_DIR}/src/radae_rx > features_out.f32;
python3 loss.py features_in.f32 features_out.f32 --loss_test 0.15 --acq_time_test 0.5")
set_tests_properties(radae_rx_embed_c PROPERTIES PASS_REGULAR_EXPRESSION "PASS")
Expand Down Expand Up @@ -551,6 +552,41 @@ add_test(NAME c_decoder_aux_mpp
python3 loss.py features_in.f32 features_c.f32 --loss 0.3 --clip_start 300")
set_tests_properties(c_decoder_aux_mpp PROPERTIES PASS_REGULAR_EXPRESSION "PASS")

# EOO data -------------------------------------------------------------------------------------------

# Pythion Tx & Rx, no noise
add_test(NAME radae_eoo_data_py
COMMAND sh -c "cd ${CMAKE_SOURCE_DIR}; \
${CMAKE_CURRENT_BINARY_DIR}/src/lpcnet_demo -features wav/brian_g8sez.wav features_in.f32; \
cat features_in.f32 | python3 radae_txe.py --eoo_data_test > rx.f32; \
cat rx.f32 | python3 radae_rxe.py -v 2 --eoo_data_test > /dev/null")
set_tests_properties(radae_eoo_data_py PROPERTIES PASS_REGULAR_EXPRESSION "PASS")

# C tx & rx, no noise. Note Python radae_txe.py just generates eoo_tx.f32 for C radae_tx
add_test(NAME radae_eoo_data_c
COMMAND sh -c "cd ${CMAKE_SOURCE_DIR}; \
${CMAKE_CURRENT_BINARY_DIR}/src/lpcnet_demo -features wav/brian_g8sez.wav features_in.f32; \
cat features_in.f32 | python3 radae_txe.py --eoo_data_test > /dev/null; \
cat features_in.f32 | PYTHONPATH='.' ${CMAKE_CURRENT_BINARY_DIR}/src/radae_tx > rx.f32; \
cat rx.f32 | PYTHONPATH='.' ${CMAKE_CURRENT_BINARY_DIR}/src/radae_rx > /dev/null; \
python3 eoo_ber.py eoo_tx.f32 eoo_rx.f32")
set_tests_properties(radae_eoo_data_c PROPERTIES PASS_REGULAR_EXPRESSION "PASS")

# C tx & rx, over a multipath channel, we set up 5 "overs", each with an EOO chunk of data. Just one of them needs
# to have a BER < 5% for a PASS. About 6dB SNR
add_test(NAME radae_eoo_data_mpp
COMMAND sh -c "cd ${CMAKE_SOURCE_DIR}; test/make_g.sh; \
${CMAKE_CURRENT_BINARY_DIR}/src/lpcnet_demo -features wav/brian_g8sez.wav features_in.f32; \
cat features_in.f32 | python3 radae_txe.py --eoo_data_test > /dev/null; \
cat features_in.f32 | PYTHONPATH='.' ${CMAKE_CURRENT_BINARY_DIR}/src/radae_tx > tx.f32; \
cat tx.f32 tx.f32 tx.f32 tx.f32 tx.f32 > tx_2.f32;
cat tx_2.f32 | python3 f32toint16.py --real --scale 8192 > tx.raw; \
${CODEC2_DEV_BUILD_DIR}/src/ch tx.raw rx.raw --No -24 --mpp --fading_dir .; \
cat rx.raw | python3 int16tof32.py --zeropad > rx.f32; \
cat rx.f32 | PYTHONPATH='.' ${CMAKE_CURRENT_BINARY_DIR}/src/radae_rx > /dev/null; \
python3 eoo_ber.py eoo_tx.f32 eoo_rx.f32")
set_tests_properties(radae_eoo_data_mpp PROPERTIES PASS_REGULAR_EXPRESSION "PASS")

# BBFM -----------------------------------------------------------------------------------------------

# single carrier modem internal (inside single_carrier class) tests
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ The RDOVAE derived Python source code is released under the two-clause BSD licen
| stateful_encoder.[py,sh] | Inference test that compares stateful to vanilla encoder |
| radae_tx.[py,sh] | streaming RADAE encoder and helper script |
| radae_rx.[py,sh] | streaming RADAE decoder and helper script |
| resource_est.py | WIP estimate CPU/memory resources |
| radae_base.py | Shared ML code between models |
| radae/bbfm.py | Baseband FM PyTorch model |
| train_bbfm.py | Training for BBFM model |
Expand Down
18 changes: 18 additions & 0 deletions eoo_ber.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import sys
import numpy as np

# bits are in float form, e.g. +/-1 or +/-1000
tx_bits = np.fromfile(sys.argv[1], dtype=np.float32)
rx_bits = np.fromfile(sys.argv[2], dtype=np.float32)
bits_per_frame = len(tx_bits)
n_frames = len(rx_bits)//bits_per_frame
n_ok_frames = 0
for f in range(n_frames):
n_errors = sum(rx_bits[f*bits_per_frame:(f+1)*bits_per_frame]*tx_bits < 0)
ber = n_errors/bits_per_frame
print(f"frame received! BER: {ber:5.2f}")
if ber < 0.05:
n_ok_frames += 1
print(f"EOO frames received: {n_frames} n_ok_frames: {n_ok_frames}", file=sys.stderr)
if n_ok_frames:
print("PASS", file=sys.stderr)
12 changes: 10 additions & 2 deletions inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
parser.add_argument('--rx_gain', type=float, default=1.0, help='gain to apply to --write_rx samples (default 1.0)')
parser.add_argument('--write_tx', type=str, default="", help='path to output file of rate Fs tx samples in ..IQIQ...f32 format')
parser.add_argument('--phase_offset', type=float, default=0, help='phase offset in rads')
parser.add_argument('--freq_offset', type=float, help='freq offset in Hz')
parser.add_argument('--freq_offset', type=float, default=0, help='freq offset in Hz')
parser.add_argument('--time_offset', type=int, default=0, help='sampling time offset in samples')
parser.add_argument('--df_dt', type=float, default=0, help='rate of change of freq offset in Hz/s')
parser.add_argument('--gain', type=float, default=1.0, help='rx gain (defaul 1.0)')
Expand Down Expand Up @@ -240,7 +240,7 @@
n_errors = int(torch.sum(x < 0))
n_bits = int(torch.numel(x))
BER = n_errors/n_bits
print(f"loss: {loss:5.3f} BER: {BER:5.3f}")
print(f"loss: {loss:5.3f} Auxdata BER: {BER:5.3f}")
else:
print(f"loss: {loss:5.3f}")
if args.loss_test > 0.0:
Expand All @@ -264,6 +264,14 @@
# appends a frame containing a final pilot so the last RADAE frame
# has a good phase reference, and two "end of over" symbols
eoo = model.eoo

# this is messy! - continue phase, freq and dF/dt track from inside forward()
freq = torch.zeros_like(eoo)
freq[:,] = model.freq_offset*torch.ones_like(eoo) + model.df_dt*torch.arange(eoo.shape[1])/model.Fs
omega = freq*2*torch.pi/model.Fs
lin_phase = torch.cumsum(omega,dim=1)
lin_phase = torch.exp(1j*lin_phase)
eoo = eoo*lin_phase*model.final_phase
eoo = eoo + sigma*torch.randn_like(eoo)
rx = torch.concatenate([rx,eoo],dim=1)
if args.prepend_noise > 0.0:
Expand Down
40 changes: 26 additions & 14 deletions radae/dsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ def transmitter_one(self, z, num_timesteps_at_rate_Rs):

# Single modem frame streaming receiver. TODO: is there a better way to pass a bunch of constants around?
class receiver_one():
def __init__(self,latent_dim,Fs,M,Ncp,Wfwd,Nc,Ns,w,P,bottleneck,pilot_gain,time_offset,coarse_mag):
def __init__(self,latent_dim,Fs,M,Ncp,Wfwd,Nc,Ns,w,P,Pend,bottleneck,pilot_gain,time_offset,coarse_mag):
self.latent_dim = latent_dim
self.Fs = Fs
self.M = M
Expand All @@ -354,6 +354,7 @@ def __init__(self,latent_dim,Fs,M,Ncp,Wfwd,Nc,Ns,w,P,bottleneck,pilot_gain,time_
self.Ns = Ns
self.w = w
self.P = P
self.Pend = Pend
self.bottleneck = bottleneck
self.pilot_gain = pilot_gain
self.time_offset = time_offset
Expand Down Expand Up @@ -446,7 +447,7 @@ def do_pilot_eq_one(self, num_modem_frames, rx_sym_pilots):
return rx_sym_pilots

# One frame version of rate Fs receiver for streaming implementation
def receiver_one(self, rx):
def receiver_one(self, rx, endofover):
Ns = self.Ns + 1

# we expect: Pilots - data symbols - Pilots
Expand All @@ -462,21 +463,32 @@ def receiver_one(self, rx):
# DFT to transform M time domain samples to Nc carriers
rx_sym = torch.matmul(rx_dash, self.Wfwd)

# Pilot based EQ
rx_sym_pilots = torch.reshape(rx_sym,(1, num_modem_frames, num_timesteps_at_rate_Rs, self.Nc))
rx_sym_pilots = self.do_pilot_eq_one(num_modem_frames,rx_sym_pilots)
rx_sym = torch.ones(1, num_modem_frames, self.Ns, self.Nc, dtype=torch.complex64)
rx_sym = rx_sym_pilots[:,:,1:self.Ns+1,:]

# demap QPSK symbols
rx_sym = torch.reshape(rx_sym, (1, -1, self.latent_dim//2))
z_hat = torch.zeros(1,rx_sym.shape[1], self.latent_dim)

z_hat[:,:,::2] = rx_sym.real
z_hat[:,:,1::2] = rx_sym.imag

if not endofover:
# Pilot based least squares EQ
rx_sym_pilots = self.do_pilot_eq_one(num_modem_frames,rx_sym_pilots)
rx_sym = rx_sym_pilots[:,:,1:self.Ns+1,:]
rx_sym = torch.reshape(rx_sym, (1, -1, self.latent_dim//2))
z_hat = torch.zeros(1,rx_sym.shape[1], self.latent_dim)

z_hat[:,:,::2] = rx_sym.real
z_hat[:,:,1::2] = rx_sym.imag
else:
# Simpler (but lower performance) EQ as average of pilots, as LS set up for PDDDDP, rather than out PEDDDE
for c in range(self.Nc):
phase_offset = torch.angle(rx_sym_pilots[0,0,0,c]/self.P[c] +
rx_sym_pilots[0,0,1,c]/self.Pend[c] +
rx_sym_pilots[0,0,Ns,c]/self.Pend[c])
rx_sym_pilots[:,:,:Ns+1,c] *= torch.exp(-1j*phase_offset)
rx_sym = torch.reshape(rx_sym_pilots[:,:,2:Ns,:],(1,(Ns-2)*self.Nc))
z_hat = torch.zeros(1,(Ns-2)*self.Nc*2)

z_hat[:,::2] = rx_sym.real
z_hat[:,1::2] = rx_sym.imag

return z_hat


# Generate root raised cosine (Root Nyquist) filter coefficients
# thanks http://www.dsplog.com/db-install/wp-content/uploads/2008/05/raised_cosine_filter.m

Expand Down
25 changes: 23 additions & 2 deletions radae/radae.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,12 +199,12 @@ def __init__(self,
self.pilot_gain = pilot_backoff*self.M/(Nc**0.5)

self.d_samples = int(self.multipath_delay * self.Fs) # multipath delay in samples
self.Ncp = int(cyclic_prefix*self.Fs)

# set up End Of Over sequence
# Normal frame ...PDDDDP...
# EOO frame ...PE000E...
# Key: P = self.p_cp, D = data symbols, E = self.pend_cp, 0 = zeros

if self.Ncp:
M = self.M
Ncp = self.Ncp
Expand All @@ -217,7 +217,10 @@ def __init__(self,
if self.bottleneck == 3:
eoo = torch.tanh(torch.abs(eoo)) * torch.exp(1j*torch.angle(eoo))
self.eoo = eoo


# experimental EOO data symbols (quick and dirty supplimentary txt channel)
self.Nseoo = (Ns-1)*Nc # number of EOO data symbols

print(f"Rs: {Rs:5.2f} Rs': {Rs_dash:5.2f} Ts': {Ts_dash:5.3f} Nsmf: {Nsmf:3d} Ns: {Ns:3d} Nc: {Nc:3d} M: {self.M:d} Ncp: {self.Ncp:d}", file=sys.stderr)

self.Tmf = Tmf
Expand Down Expand Up @@ -435,6 +438,22 @@ def est_snr(self, r, time_offset=0):
SNR_est = Ct/(torch.dot(torch.conj(p),p) - Ct)
return SNR_est.real

def set_eoo_bits(self, eoo_bits):
Ns = self.Ns; Ncp = self.Ncp; M = self.M; Nc = self.Nc; Nmf = int((Ns+1)*(M+Ncp))

eoo_syms = eoo_bits[::2] + 1j*eoo_bits[1::2]
eoo_syms = torch.reshape(eoo_syms,(1,Ns-1,Nc))

eoo_tx = torch.matmul(eoo_syms,self.Winv)
if self.Ncp:
eoo_tx_cp = torch.zeros((1,Ns-1,self.M+Ncp),dtype=torch.complex64)
eoo_tx_cp[:,:,Ncp:] = eoo_tx
eoo_tx_cp[:,:,:Ncp] = eoo_tx_cp[:,:,-Ncp:]
eoo_tx = torch.reshape(eoo_tx_cp,(1,(Ns-1)*(self.M+Ncp)))*self.pilot_gain
if self.bottleneck == 3:
eoo_tx = torch.tanh(torch.abs(eoo_tx)) * torch.exp(1j*torch.angle(eoo_tx))
self.eoo[0,2*(M+Ncp):Nmf] = eoo_tx

def forward(self, features, H, G=None):

(num_batches, num_ten_ms_timesteps, num_features) = features.shape
Expand Down Expand Up @@ -482,6 +501,7 @@ def forward(self, features, H, G=None):

tx_before_channel = None
rx = None
self.final_phase = torch.tensor(1,dtype=torch.complex64)
if self.rate_Fs:
num_timesteps_at_rate_Fs = num_timesteps_at_rate_Rs*self.M

Expand Down Expand Up @@ -530,6 +550,7 @@ def forward(self, features, H, G=None):
lin_phase = torch.cumsum(omega,dim=1)
lin_phase = torch.exp(1j*lin_phase)
tx = tx*lin_phase
self.final_phase = lin_phase[:,-1]

# insert per sequence random phase and freq offset (training time)
if self.freq_rand:
Expand Down
8 changes: 4 additions & 4 deletions radae/radae_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def __init__(self, feature_dim, output_dim, bottleneck = 1):
self.z_dense = nn.Linear(864, self.output_dim)

nb_params = sum(p.numel() for p in self.parameters())
print(f"encoder: {nb_params} weights", file=sys.stderr)
#print(f"encoder: {nb_params} weights", file=sys.stderr)

# initialize weights
self.apply(init_weights)
Expand Down Expand Up @@ -251,7 +251,7 @@ def __init__(self, feature_dim, output_dim, bottleneck = 1):
self.z_dense = nn.Linear(864, self.output_dim)

nb_params = sum(p.numel() for p in self.parameters())
print(f"encoder: {nb_params} weights", file=sys.stderr)
#print(f"encoder: {nb_params} weights", file=sys.stderr)

# initialize weights
self.apply(init_weights)
Expand Down Expand Up @@ -326,7 +326,7 @@ def __init__(self, input_dim, output_dim):
self.glu5 = GLU(96)

nb_params = sum(p.numel() for p in self.parameters())
print(f"decoder: {nb_params} weights", file=sys.stderr)
#print(f"decoder: {nb_params} weights", file=sys.stderr)
# initialize weights
self.apply(init_weights)

Expand Down Expand Up @@ -393,7 +393,7 @@ def __init__(self, input_dim, output_dim):
self.glu5 = GLU(96)

nb_params = sum(p.numel() for p in self.parameters())
print(f"decoder: {nb_params} weights", file=sys.stderr)
#print(f"decoder: {nb_params} weights", file=sys.stderr)
# initialize weights
self.apply(init_weights)

Expand Down
Loading

0 comments on commit 745686f

Please sign in to comment.