JS8Call-Improved master
Loading...
Searching...
No Matches
whitening_processor.h
Go to the documentation of this file.
1
5
6#pragma once
7
8#include <QDebug>
9#include <QLoggingCategory>
10
11#include <algorithm>
12#include <array>
13#include <cmath>
14#include <cstdlib>
15#include <numeric>
16#include <optional>
17#include <sstream>
18#include <vector>
19
20Q_DECLARE_LOGGING_CATEGORY(decoder_js8);
21
22namespace js8 {
31template <int NROWS, int ND, int N> class WhiteningProcessor {
32 public:
43 struct Result {
44 std::array<float, 3 * ND> llr0;
45 std::array<float, 3 * ND> llr1;
48 std::size_t erasures;
49 double avgAbsPre;
50 double avgAbsPost;
51 };
52
72 static Result process(std::array<std::array<float, ND>, NROWS> const &s1,
73 std::array<int, ND> const &symbolWinners,
74 float erasureThreshold, bool debug) {
75 auto const median =
76 [](std::vector<float> &values) -> std::optional<float> {
77 if (values.empty())
78 return std::nullopt;
79
80 auto const mid = values.size() / 2;
81
82 std::nth_element(values.begin(), values.begin() + mid,
83 values.end());
84 float med = values[mid];
85
86 if ((values.size() % 2) == 0 && mid > 0) {
87 std::nth_element(values.begin(), values.begin() + (mid - 1),
88 values.end());
89 med = 0.5f * (med + values[mid - 1]);
90 }
91
92 return med;
93 };
94
95 // Estimate per-tone noise using non-winning tone magnitudes across the
96 // frame.
97 auto const toneNoise =
98 [&]() -> std::optional<std::array<float, NROWS>> {
99 std::array<std::vector<float>, NROWS> toneSamples;
100 std::array<float, NROWS> noise = {};
101
102 // Collect non-winning magnitudes for each tone.
103 for (int j = 0; j < ND; ++j) {
104 int const winner = symbolWinners[j];
105
106 for (int i = 0; i < NROWS; ++i) {
107 if (i != winner)
108 toneSamples[i].push_back(s1[i][j]);
109 }
110 }
111
112 bool ok = true;
113
114 for (int i = 0; i < NROWS; ++i) {
115 if (auto m = median(toneSamples[i]); m) {
116 noise[i] = *m;
117 } else {
118 ok = false;
119 break;
120 }
121 }
122
123 if (!ok)
124 return std::nullopt;
125
126 return noise;
127 }();
128
129 if (toneNoise && debug) {
130 std::ostringstream oss;
131
132 oss << "toneNoise:";
133 for (auto const value : *toneNoise)
134 oss << ' ' << value;
135
136 qCDebug(decoder_js8).noquote() << oss.str().c_str();
137 }
138
139 // Estimate per-symbol noise using non-winning tone magnitudes per
140 // symbol.
141 auto const symbolNoise = [&]() -> std::optional<std::vector<float>> {
142 std::vector<float> noise;
143 noise.reserve(ND);
144
145 for (int j = 0; j < ND; ++j) {
146 std::vector<float> bins;
147 bins.reserve(NROWS - 1);
148
149 int const winner = symbolWinners[j];
150
151 for (int i = 0; i < NROWS; ++i) {
152 if (i != winner)
153 bins.push_back(s1[i][j]);
154 }
155
156 if (auto m = median(bins); m) {
157 noise.push_back(*m);
158 } else {
159 return std::nullopt;
160 }
161 }
162
163 return noise;
164 }();
165
166 if (symbolNoise && !symbolNoise->empty() && debug) {
167 auto const [minIt, maxIt] =
168 std::minmax_element(symbolNoise->begin(), symbolNoise->end());
169 float const avg = std::accumulate(symbolNoise->begin(),
170 symbolNoise->end(), 0.0f) /
171 static_cast<float>(symbolNoise->size());
172
173 qCDebug(decoder_js8)
174 << "symbolNoise avg/min/max" << avg << *minIt << *maxIt;
175 }
176
177 Result result{};
178
179 bool const disableWhitening =
180 std::getenv("JS8_DISABLE_WHITENING") != nullptr;
181 bool const whiteningAvailable = toneNoise && symbolNoise &&
182 !symbolNoise->empty() &&
183 !disableWhitening;
184 bool const applyErasureInWhitening =
185 whiteningAvailable && erasureThreshold > 0.0f;
186 double sumAbsPre = 0.0;
187 double sumAbsPost = 0.0;
188 std::size_t erasures = 0;
189
190 for (int j = 0; j < ND; ++j) {
191 int const i1 = 3 * j; // First column (matches Fortran's i1)
192 int const i2 = 3 * j + 1; // Second column (matches Fortran's i2)
193 int const i4 = 3 * j + 2; // Third column (matches Fortran's i4)
194
195 std::array<float, NROWS> ps;
196
197 for (int i = 0; i < NROWS; ++i)
198 ps[i] = s1[i][j];
199
200 // Assign to `bmeta` in column order, with correct values
201 result.llr0[i1] = std::max({ps[4], ps[5], ps[6], ps[7]}) -
202 std::max({ps[0], ps[1], ps[2], ps[3]}); // r4
203 result.llr0[i2] = std::max({ps[2], ps[3], ps[6], ps[7]}) -
204 std::max({ps[0], ps[1], ps[4], ps[5]}); // r2
205 result.llr0[i4] = std::max({ps[1], ps[3], ps[5], ps[7]}) -
206 std::max({ps[0], ps[2], ps[4], ps[6]}); // r1
207
208 for (auto &x : ps)
209 x = std::log(x + 1e-32f);
210
211 // Assign to `bmetb` in column order, with correct values
212 result.llr1[i1] = std::max({ps[4], ps[5], ps[6], ps[7]}) -
213 std::max({ps[0], ps[1], ps[2], ps[3]}); // r4
214 result.llr1[i2] = std::max({ps[2], ps[3], ps[6], ps[7]}) -
215 std::max({ps[0], ps[1], ps[4], ps[5]}); // r2
216 result.llr1[i4] = std::max({ps[1], ps[3], ps[5], ps[7]}) -
217 std::max({ps[0], ps[2], ps[4], ps[6]}); // r1
218
219 if (whiteningAvailable) {
220 int const winner = symbolWinners[j];
221 float const tn = std::max(0.0f, (*toneNoise)[winner]);
222 float const sn = std::max(0.0f, (*symbolNoise)[j]);
223 float const localNoise = std::sqrt(tn * sn + 1e-12f);
224
225 auto const applyWhitening = [&](float &value) {
226 float const pre = std::abs(value);
227 sumAbsPre += pre;
228
229 if (localNoise > 0.0f && std::isfinite(localNoise)) {
230 value /= localNoise;
231 }
232
233 if (applyErasureInWhitening &&
234 std::abs(value) < erasureThreshold) {
235 value = 0.0f;
236 ++erasures;
237 }
238
239 sumAbsPost += std::abs(value);
240 };
241
242 applyWhitening(result.llr0[i1]);
243 applyWhitening(result.llr0[i2]);
244 applyWhitening(result.llr0[i4]);
245 applyWhitening(result.llr1[i1]);
246 applyWhitening(result.llr1[i2]);
247 applyWhitening(result.llr1[i4]);
248 }
249 }
250
251 auto const normalizeLLR = [](auto &llr) {
252 float sum = 0.0f;
253 float sum_of_squares = 0.0f;
254
255 for (auto const value : llr) {
256 sum += value;
257 sum_of_squares += value * value;
258 }
259
260 float const llrav = sum / llr.size();
261 float const llr2av = sum_of_squares / llr.size();
262 float const variance = llr2av - llrav * llrav;
263 float const llrsig = std::sqrt(variance > 0.0f ? variance : llr2av);
264
265 for (float &val : llr)
266 val = (val / llrsig) * 2.83f;
267 };
268
269 // Normalize and process metrics
270
271 normalizeLLR(result.llr0);
272 normalizeLLR(result.llr1);
273
274 if (whiteningAvailable && debug) {
275 auto const total =
276 static_cast<double>(result.llr0.size() + result.llr1.size());
277 double const avgPre = total > 0.0 ? sumAbsPre / total : 0.0;
278 double const avgPost = total > 0.0 ? sumAbsPost / total : 0.0;
279
280 qCDebug(decoder_js8) << "LLR whitening applied"
281 << "avg|LLR| pre/post:" << avgPre << avgPost
282 << "erasures:" << erasures;
283 }
284
285 result.whiteningApplied = whiteningAvailable;
286 result.erasureApplied = applyErasureInWhitening;
287 result.erasures = erasures;
288 result.avgAbsPre = sumAbsPre;
289 result.avgAbsPost = sumAbsPost;
290 return result;
291 }
292};
293} // namespace js8
Compute per-tone/symbol noise medians and whiten LLRs for a JS8 frame.
Definition whitening_processor.h:31
static Result process(std::array< std::array< float, ND >, NROWS > const &s1, std::array< int, ND > const &symbolWinners, float erasureThreshold, bool debug)
Compute normalized LLR arrays for a single candidate frame.
Definition whitening_processor.h:72
JS8 namespace for Kalman filter-based trackers.
Definition FrequencyTracker.cpp:14
Result of a whitening/LRR normalization pass.
Definition whitening_processor.h:43
std::size_t erasures
Number of LLR elements erased.
Definition whitening_processor.h:48
bool erasureApplied
True when erasure was applied.
Definition whitening_processor.h:47
double avgAbsPost
Aggregate |LLR| after whitening.
Definition whitening_processor.h:50
bool whiteningApplied
True when whitening was applied.
Definition whitening_processor.h:46
std::array< float, 3 *ND > llr1
LLR values for the 1-hypothesis.
Definition whitening_processor.h:45
double avgAbsPre
Aggregate |LLR| before whitening.
Definition whitening_processor.h:49
std::array< float, 3 *ND > llr0
LLR values for the 0-hypothesis.
Definition whitening_processor.h:44