Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
75 changes: 56 additions & 19 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,11 @@ add_test(NAME rade_c_v2_dec
set_tests_properties(rade_c_v2_dec PROPERTIES PASS_REGULAR_EXPRESSION "PASS")

# Test V2 C transmitter: compare IQ output to tx2.py reference using RMS difference
# EOO max diff threshold: 1e-4 (essentially float32 precision)
# EOO max diff threshold: 0.01 -- the SSB BPF is one continuous filter spanning the
# data->EOO boundary, so its memory carries the pre-existing ~1.2% data-side encoder
# float difference into the start of the EOO window as a decaying transient (observed
# peak ~0.006, decaying to ~1e-5 by the end of the 960-sample EOO window); a tight
# near-float32-precision threshold is no longer the right expectation here.
# Data diff_rms/sig_rms threshold: 3% (encoder float differences are ~1.2%)
add_test(NAME rade_c_v2_tx
COMMAND sh -c "cd ${CMAKE_SOURCE_DIR}; \
Expand All @@ -756,7 +760,7 @@ diff_rms=np.sqrt(np.mean(np.abs(data_py-data_c)**2)); \
sig_rms=np.sqrt(np.mean(np.abs(data_py)**2)); \
eoo_max=np.max(np.abs(eoo_py-eoo_c)); \
print(f'diff_rms/sig_rms: {diff_rms/sig_rms:.4f} eoo_max_diff: {eoo_max:.2e}'); \
ok = diff_rms/sig_rms < 0.03 and eoo_max < 1e-4; \
ok = diff_rms/sig_rms < 0.03 and eoo_max < 0.01; \
print('PASS' if ok else 'FAIL')\"")
set_tests_properties(rade_c_v2_tx PROPERTIES PASS_REGULAR_EXPRESSION "PASS")

Expand Down Expand Up @@ -820,17 +824,38 @@ add_test(NAME rade_c_v2_rx_mpp_low
python3 loss.py features_in.f32 features_rx_v2_c.f32 --loss_test 0.40 --clip_start 100 --clip_end 300")
set_tests_properties(rade_c_v2_rx_mpp_low PROPERTIES PASS_REGULAR_EXPRESSION "PASS")

# V2 C receiver EOO detection: MPP channel at EbNodB=8 (mirrors v2_eoo_mpp_low)
# V2 C receiver EOO detection: tx2.py -> ch -> C rx, AWGN channel.
add_test(NAME rade_c_v2_rx_eoo
COMMAND sh -c "cd ${CMAKE_SOURCE_DIR}; \
test/make_g.sh; \
./inference.sh 250725/checkpoints/checkpoint_epoch_200.pth wav/brian_g8sez.wav /dev/null \
--rate_Fs --latent-dim 56 --peak --cp 0.004 --time_offset -16 --correct_time_offset -16 \
--auxdata --w1_dec 128 --end_of_over_v2 --write_rx rx_v2_eoo_test.f32 \
--append_noise 1 --g_file g_mpp.f32 --EbNodB 8; \
cat rx_v2_eoo_test.f32 | ${RADE_C_BUILD_DIR}/src/radae_rx --v2 -v 0 2>&1 >/dev/null")
./tx2.sh 250725/checkpoints/checkpoint_epoch_200.pth wav/brian_g8sez.wav v2_eoo_tx2.f32; \
cat v2_eoo_tx2.f32 | python3 f32toint16.py --real --scale 16383 | \
${CMAKE_BINARY_DIR}/src/ch - - --No -24 | python3 int16tof32.py --zeropad > v2_eoo_rx2.f32; \
cat v2_eoo_rx2.f32 | ${RADE_C_BUILD_DIR}/src/radae_rx --v2 -v 0 2>&1 >/dev/null")
set_tests_properties(rade_c_v2_rx_eoo PROPERTIES PASS_REGULAR_EXPRESSION "End-of-over detected")

# V2 C receiver EOO detection: tx2.py -> ch -> C rx, MPP fading channel.
# scale 8192 (rather than 16383) + No -30 gives the same ~10dB SNR3k as the AWGN
# test above with zero clipping from multipath anti-fade peaks (16383 clips ~0.09%).
add_test(NAME rade_c_v2_rx_eoo_mpp
COMMAND sh -c "cd ${CMAKE_SOURCE_DIR}; \
test/make_g.sh; \
./tx2.sh 250725/checkpoints/checkpoint_epoch_200.pth wav/brian_g8sez.wav v2_eoo_mpp_tx2.f32; \
cat v2_eoo_mpp_tx2.f32 | python3 f32toint16.py --real --scale 8192 | \
${CMAKE_BINARY_DIR}/src/ch - - --No -30 --mpp --fading_dir . | python3 int16tof32.py --zeropad > v2_eoo_mpp_rx2.f32; \
cat v2_eoo_mpp_rx2.f32 | ${RADE_C_BUILD_DIR}/src/radae_rx --v2 -v 0 2>&1 >/dev/null")
set_tests_properties(rade_c_v2_rx_eoo_mpp PROPERTIES PASS_REGULAR_EXPRESSION "End-of-over detected")

# V2 C receiver EOO detection: C tx (radae_tx) -> ch -> C rx, MPP fading channel.
add_test(NAME rade_c_v2_tx_eoo_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 | ${RADE_C_BUILD_DIR}/src/radae_tx --v2 > v2_eoo_ctx.f32; \
cat v2_eoo_ctx.f32 | python3 f32toint16.py --real --scale 8192 | \
${CMAKE_BINARY_DIR}/src/ch - - --No -30 --mpp --fading_dir . | python3 int16tof32.py --zeropad > v2_eoo_ctx_rx.f32; \
cat v2_eoo_ctx_rx.f32 | ${RADE_C_BUILD_DIR}/src/radae_rx --v2 -v 0 2>&1 >/dev/null")
set_tests_properties(rade_c_v2_tx_eoo_mpp PROPERTIES PASS_REGULAR_EXPRESSION "End-of-over detected")

# V2 C receiver SNR estimator: high SNR (EbNodB=12, mirrors v2_snr_est_high)
add_test(NAME rade_c_v2_rx_snr_high
COMMAND sh -c "cd ${CMAKE_SOURCE_DIR}; \
Expand Down Expand Up @@ -1200,21 +1225,33 @@ add_test(NAME v2_eoo_awgn_low
2>&1 | grep 'EOO detected' && echo PASS || echo FAIL")
set_tests_properties(v2_eoo_awgn_low PROPERTIES PASS_REGULAR_EXPRESSION "PASS")

# V2 EOO detection under real MPP fading: tx2.py -> ch -> rx2.py.
# inference.py's --end_of_over_v2 splice only ever applies flat AWGN to the
# EOO tail regardless of channel flags (never real H/G fading, never the SSB
# BPF) -- tx2.py's continuous data+EOO stream through ch fades/filters both
# genuinely. Scale 8192 (not 16383) avoids clipping from multipath anti-fade
# peaks. --No -25 matches inference.py's --EbNodB 8 target C/No (39.46dB-Hz)
# for this file; "high" uses ch's default (no --No) -- both this and
# inference.py's default --EbNodB are so far into the noiseless regime
# (SNR3k 80-96dB) that AWGN is negligible either way and MPP fading is the
# only real impairment in both.
add_test(NAME v2_eoo_mpp_high
COMMAND sh -c "cd ${CMAKE_SOURCE_DIR}; \
./inference.sh 250725/checkpoints/checkpoint_epoch_200.pth wav/brian_g8sez.wav /dev/null --rate_Fs \
--latent-dim 56 --peak --cp 0.004 --time_offset -16 --correct_time_offset -16 --auxdata --w1_dec 128 \
--end_of_over_v2 --write_rx v2_eoo_tx.f32 --append_noise 1 --g_file g_mpp.f32; \
./rx2.sh 250725/checkpoints/checkpoint_epoch_200.pth 250725a_ml_sync v2_eoo_tx.f32 /dev/null \
test/make_g.sh; \
./tx2.sh 250725/checkpoints/checkpoint_epoch_200.pth wav/brian_g8sez.wav v2_eoo_mpp_high_tx2.f32; \
cat v2_eoo_mpp_high_tx2.f32 | python3 f32toint16.py --real --scale 8192 | \
${CMAKE_BINARY_DIR}/src/ch - - --mpp --fading_dir . | python3 int16tof32.py --zeropad > v2_eoo_mpp_high_rx2.f32; \
./rx2.sh 250725/checkpoints/checkpoint_epoch_200.pth 250725a_ml_sync v2_eoo_mpp_high_rx2.f32 /dev/null \
2>&1 | grep 'EOO detected' && echo PASS || echo FAIL")
set_tests_properties(v2_eoo_mpp_high PROPERTIES PASS_REGULAR_EXPRESSION "PASS")

add_test(NAME v2_eoo_mpp_low
COMMAND sh -c "cd ${CMAKE_SOURCE_DIR}; \
./inference.sh 250725/checkpoints/checkpoint_epoch_200.pth wav/brian_g8sez.wav /dev/null --rate_Fs \
--latent-dim 56 --peak --cp 0.004 --time_offset -16 --correct_time_offset -16 --auxdata --w1_dec 128 \
--end_of_over_v2 --write_rx v2_eoo_tx.f32 --append_noise 1 --g_file g_mpp.f32 --EbNodB 8; \
./rx2.sh 250725/checkpoints/checkpoint_epoch_200.pth 250725a_ml_sync v2_eoo_tx.f32 /dev/null \
test/make_g.sh; \
./tx2.sh 250725/checkpoints/checkpoint_epoch_200.pth wav/brian_g8sez.wav v2_eoo_mpp_low_tx2.f32; \
cat v2_eoo_mpp_low_tx2.f32 | python3 f32toint16.py --real --scale 8192 | \
${CMAKE_BINARY_DIR}/src/ch - - --No -25 --mpp --fading_dir . | python3 int16tof32.py --zeropad > v2_eoo_mpp_low_rx2.f32; \
./rx2.sh 250725/checkpoints/checkpoint_epoch_200.pth 250725a_ml_sync v2_eoo_mpp_low_rx2.f32 /dev/null \
2>&1 | grep 'EOO detected' && echo PASS || echo FAIL")
set_tests_properties(v2_eoo_mpp_low PROPERTIES PASS_REGULAR_EXPRESSION "PASS")

Expand Down Expand Up @@ -1274,7 +1311,7 @@ add_test(NAME v2_tx2
./tx2.sh 250725/checkpoints/checkpoint_epoch_200.pth wav/brian_g8sez.wav v2_tx2_rx.f32; \
python3 inference.py 250725/checkpoints/checkpoint_epoch_200.pth features_in.f32 features_out.f32 \
--latent-dim 56 --w1_dec 128 --auxdata --rate_Fs --cp 0.004 \
--time_offset -16 --correct_time_offset -8 --peak; \
--time_offset -16 --correct_time_offset -8 --peak --ssb_bpf; \
./rx2.sh 250725/checkpoints/checkpoint_epoch_200.pth 250725a_ml_sync v2_tx2_rx.f32 /dev/null --quiet; \
python3 loss.py features_in.f32 features_out.f32 --features_hat2 features_out_rx2.f32 \
--clip_start 100 --clip_end 300 --compare --delta 0.05")
Expand All @@ -1291,7 +1328,7 @@ add_test(NAME v2_tx2_ch
./rx2.sh 250725/checkpoints/checkpoint_epoch_200.pth 250725a_ml_sync v2_tx2ch_inf_rx.f32 /dev/null \
--agc --gain 1.22E-4 --quiet; \
cp features_out_rx2.f32 features_out_rx2_inf.f32; \
./tx2.sh 250725/checkpoints/checkpoint_epoch_200.pth wav/brian_g8sez.wav v2_tx2ch_tx.f32 --ssb_bpf; \
./tx2.sh 250725/checkpoints/checkpoint_epoch_200.pth wav/brian_g8sez.wav v2_tx2ch_tx.f32; \
cat v2_tx2ch_tx.f32 | python3 f32toint16.py --real --scale 16383 | ${CMAKE_BINARY_DIR}/src/ch - - --No -20 | python3 int16tof32.py --zeropad > v2_tx2ch_rx.f32; \
./rx2.sh 250725/checkpoints/checkpoint_epoch_200.pth 250725a_ml_sync v2_tx2ch_rx.f32 /dev/null \
--agc --gain 1.22E-4 --quiet; \
Expand Down
16 changes: 0 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,22 +267,6 @@ issues.
The full procedure, including a checklist template for submitting results, is
in [doc/verification/verification_procedure.md](doc/verification/verification_procedure.md).

A software-only loss baseline must be established using the current version
of the code under test — loss values shift slightly between model versions.
We use the reference Python implementation and `wav/all.wav` to establish
the baseline; re-run with the latest version to obtain the current baseline:
```
lpcnet_demo -features wav/all.wav features_in.f32
python3 tx2.py 250725/checkpoints/checkpoint_epoch_200.pth features_in.f32 tx.f32
python3 rx2.py 250725/checkpoints/checkpoint_epoch_200.pth 250725a_ml_sync tx.f32 features_rx.f32 --quiet
python3 loss.py features_in.f32 features_rx.f32 --clip_start 100 --clip_end 300
```
Example output (Python reference, `wav/all.wav`, model `250725`, commit `b549586`):
```
loss: 0.081 start: 224 acq_time: 1.24 s
```
Record the current baseline loss value. A pass is within ±10% of the baseline.

## Stored File Tests

The `ota_test.sh` script supports stored-file over-the-air and over-the-cable testing. It assembles a transmit file containing a chirp reference, compressed SSB, RADE V1, and RADE V2 signals in sequence, which can be sent over a real HF channel or processed through a channel simulator. The script performs a *controlled* test of SSB, RADE V1, and RADE V2 over real-world channels.
Expand Down
26 changes: 4 additions & 22 deletions doc/v2_test_report.tex
Original file line number Diff line number Diff line change
Expand Up @@ -369,33 +369,15 @@ \subsection{End of Over Detection}

This section describes the End of Over (EOO) system performance. When the transmission is about to cease, a special EOO frame is sent to instruct the receiver to immediately stop decoding. This prevents decoding of spurious received signal and unwanted ``R2D2" tones emerging from the decoder at the end of a transmission. The design goal is reliable operation in high SNR benign channels. In poor channels (e.g. low SNR MPP), it is acceptable to have a lower probability of EOO detection, as clean end of over behaviour is a secondary requirement in the low SNR use case. In all channels, should the EOO frame not be detected, the hangover counter in the receiver state machine will eventually stop decoding after a few seconds.

The script \emph{eoo\_detect\_prob.sh} generates $N=20$ by 9 \si{s} duration samples (extracted from \emph{all.wav}) each terminated in an EOO frame. The number of successful EOO detections is counted to determine the probability of EOO detection for each condition. The 9 \si{s} duration is chosen to ensure the alignment shifts between \emph{all.wav} and \emph{g\_mpp.f32} as the trials modulo rotate around each file. Note the sharp knee in the AWGN performance at around 1 \si{dB} SNR.
The script \emph{eoo\_detect\_prob.sh} generates $N=20$ by 9 \si{s} duration samples (extracted from \emph{all.wav}) each terminated in an EOO frame, transmitted through \emph{tx2.py} $\to$ \emph{ch} $\to$ \emph{rx2.py} so that both the payload and the EOO frame are genuinely faded and filtered by the channel (an earlier version of this test spliced an AWGN-only EOO tail onto the channel output, so multipath fading never actually reached the EOO frame -- see Issue \#1). The number of successful EOO detections is counted to determine the probability of EOO detection for each condition. The 9 \si{s} duration is chosen to ensure the alignment shifts between \emph{all.wav} and \emph{g\_mpp.f32} as the trials modulo rotate around each file. AWGN detection probability declines steadily from 65\% at 4.3 \si{dB} SNR to 5\% at 0.3 \si{dB} SNR; MPP is weaker at comparable SNR, declining from 55\% at 8.3 \si{dB} SNR to 10\% at 2.3 \si{dB} SNR, since a real fade can occasionally land on the EOO frame itself.

\begin{table} [h]
\caption{End of Over Probability of Detection over $N=20$ trials}
\label{tab:eoo_p_detetc}
\centering
\begin{tabular}{ c | c | r | r }
\hline
Channel & EbNodB & SNR (\si{dB}) & P(detect) \\
\hline
AWGN & 100 & 100 & 1.00 \\
AWGN & 10 & 3.5 & 1.0 \\
AWGN & 8 & 1.5 & 1.0 \\
AWGN & 6 & -0.5 & 0.0 \\
MPP & 100 & 100 & 1.0 \\
MPP & 10 & 4.5 & 1.0 \\
MPP & 6 & -0.5 & 0.3 \\
MPP & 3 & -3.5 & 0.0 \\
\hline
\end{tabular}
\end{table}
\input{v2_test_report_eoo.tex}

The script \emph{eoo\_false\_prob.sh} counts how many times the EOO detection system detects a EOO frame when none is present (false detection), e.g. due to the regular payload V2 OFDM symbols resembling an EOO frame. This is undesirable as the receiver would stop decoding mid-over and have to re-acquire. The longest source file tested was \emph{librispeech.wav} (used for ASR testing) at 540 seconds with no false detections. The source file \emph{all.wav} was also tested with various channel conditions (e.g. 10 trials over a MPP channel at 3.5 dB SNR) with no false detections.
The script \emph{eoo\_false\_prob.sh} counts how many times the EOO detection system detects a EOO frame when none is present (false detection), e.g. due to the regular payload V2 OFDM symbols resembling an EOO frame. This is undesirable as the receiver would stop decoding mid-over and have to re-acquire. The longest source file tested was \emph{librispeech.wav} (used for ASR testing) at 540 seconds with no false detections. Over $N=20$ trials each, no false detections were observed on \emph{all.wav} at 4.4 \si{dB} SNR over AWGN or 5.4 \si{dB} SNR over MPP.

Sample command lines:
\begin{verbatim}
./test/eoo_detect_prob.sh --channel mpp --EbNodB 10
./test/eoo_detect_prob.sh --channel mpp --No -25
./test/eoo_false_prob.sh
\end{verbatim}

Expand Down
28 changes: 28 additions & 0 deletions doc/v2_test_report_eoo.tex
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
%% Auto-generated by test/eoo_report_data.sh -- N=20 trials per point
%% tx2.py -> ch -> rx2.py, both data and EOO genuinely faded/filtered (see Issue #1)
\begin{table} [h]
\caption{End of Over Probability of Detection over $N=20$ trials}
\label{tab:eoo_p_detect}
\centering
\begin{tabular}{ c | r | r }
\hline
Channel & SNR3k (\si{dB}) & P(detect) \\
\hline
AWGN & 80.3 & 0.95 \\
AWGN & 4.3 & 0.65 \\
AWGN & 2.3 & 0.40 \\
AWGN & 0.3 & 0.05 \\
MPP & 80.3 & 0.85 \\
MPP & 8.3 & 0.55 \\
MPP & 5.3 & 0.25 \\
MPP & 2.3 & 0.10 \\
\hline
\end{tabular}
\end{table}

%% False-trigger results
%% AWGN -24: Mean SNR3k = 4.36dB
%% AWGN -24: Result: 0 false triggers over 20 trials
%% MPP -25: Mean SNR3k = 5.36dB
%% MPP -25: Result: 0 false triggers over 20 trials
%%
Binary file modified doc/verification/loss_clipped.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified doc/verification/loss_unclipped.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading