Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

End of Over Data #35

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6fb88ba
wip insert some QPSK symbols in EOO frame
drowe67 Nov 22, 2024
91a77f0
bottleneck 3 for data symbols
drowe67 Nov 23, 2024
14fd1c4
wip EOO data basic rx
drowe67 Nov 25, 2024
46569da
inferernce.py wasn't adding phase and freq offsets to EOO!
drowe67 Nov 25, 2024
e364a63
based EOO data proof of concept (ML side) - getting data out with a f…
drowe67 Nov 25, 2024
d851f8f
wip fix failing ctests
drowe67 Nov 25, 2024
0ab0061
Merge branch 'main' into dr-eoo-data
drowe67 Nov 25, 2024
52b412b
used a custom RNG for EOO data bits, which stopped some test fails. …
drowe67 Nov 25, 2024
0bb2a12
comment out #weights prints, to reduce noise when running tests
drowe67 Nov 25, 2024
a006b07
allow for EOO not triggering at low SNR awgn test
drowe67 Nov 25, 2024
2dad457
prototype EOO rade_api.h
drowe67 Nov 26, 2024
8ccd3cb
Merge branch 'dr-eoo-data' of github.com:drowe67/radae into dr-eoo-data
drowe67 Nov 26, 2024
238facf
n_eoo_features out is number of floats rather than symbols
drowe67 Nov 26, 2024
a106dfc
wip EOO ouput data through C API
drowe67 Nov 28, 2024
39bf054
wip EOO data - about to refactor tests
drowe67 Nov 28, 2024
048ecc7
1st pass EOO data unit test framework
drowe67 Nov 28, 2024
3f8a23b
initial attempt at passing in tx EOO bits via function call (Python)
drowe67 Nov 28, 2024
bcb032f
numpy interafce for setting eoo bits
drowe67 Nov 28, 2024
09e9547
first pass of EOO data through C API
drowe67 Nov 28, 2024
1ca79a6
multipath channel test
drowe67 Nov 29, 2024
5c02edf
rade_rx() API change as per Mooneer's suggestion
drowe67 Nov 29, 2024
d294bcb
rm resource_est.py (moved to papers repo)
drowe67 Dec 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -415,7 +416,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 @@ -431,21 +432,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