Add object calibration

This commit is contained in:
Ayzen
2026-10-05 17:39:40 +03:00
parent 8f097dfa2e
commit 552cdc688a
33 changed files with 3141 additions and 517 deletions
@@ -50,6 +50,18 @@ class S21ReferenceBundle {
std::unordered_map<ipc::ComboKey, ChannelTrace, ipc::ComboKeyHash> traces_by_combo_{};
};
// Object-derived MIMO correction, exported on each pair's runtime frequency grid.
// An empty path disables it; enabled bundles must cover every active pair.
class S21ObjectCalibrationBundle {
public:
void load(const std::string& path);
void validate_combos(const std::vector<ipc::ComboKey>& combos) const;
void apply(ipc::SweepTraceBlock& trace) const;
private:
std::unordered_map<ipc::ComboKey, ChannelTrace, ipc::ComboKeyHash> factors_by_combo_{};
};
class S11CalibrationBundle {
public:
struct Coefficients {
@@ -8,6 +8,7 @@
#include <fstream>
#include <iostream>
#include <iterator>
#include <limits>
#include <span>
#include <stdexcept>
#include <string>
@@ -197,11 +198,11 @@ template <typename TValue>
auto resolve_required(
const std::unordered_map<ipc::ComboKey, TValue, ipc::ComboKeyHash>& values_by_combo,
const ipc::ComboKey& combo,
const std::string& value_label
const char* value_label
) -> const TValue& {
const auto found = values_by_combo.find(combo);
if (found == values_by_combo.end()) {
throw std::runtime_error(value_label + " is missing for combo " + combo_to_string(combo));
throw std::runtime_error(std::string(value_label) + " is missing for combo " + combo_to_string(combo));
}
return found->second;
}
@@ -345,6 +346,38 @@ auto S21ReferenceBundle::resolve(const ipc::ComboKey& combo) const -> const Chan
return resolve_required(traces_by_combo_, combo, "S21 reference trace");
}
void S21ObjectCalibrationBundle::load(const std::string& path) {
if (path.empty()) {
factors_by_combo_.clear();
return;
}
factors_by_combo_ = load_channel_traces(path, "S21 object calibration", RawTraceChannel::S21);
}
void S21ObjectCalibrationBundle::validate_combos(const std::vector<ipc::ComboKey>& combos) const {
validate_bundle_combos(factors_by_combo_, combos, "S21 object calibration bundle");
}
void S21ObjectCalibrationBundle::apply(ipc::SweepTraceBlock& trace) const {
if (factors_by_combo_.empty()) { return; }
const auto& factors = resolve_required(factors_by_combo_, trace.combo, "S21 object calibration");
ensure_frequency_axis_match(factors.frequency_hz, trace.frequency_hz, "S21 object calibration");
if (trace.s21.size() != factors.samples.size()) {
throw std::runtime_error("S21 object calibration vector size mismatch");
}
for (std::size_t index = 0; index < trace.s21.size(); ++index) {
const auto value = trace.s21[index];
const auto factor = factors.samples[index];
const auto corrected = std::complex<double>(value.re, value.im) * std::complex<double>(factor.re, factor.im);
if (!std::isfinite(corrected.real()) || !std::isfinite(corrected.imag()) ||
std::abs(corrected.real()) > std::numeric_limits<float>::max() ||
std::abs(corrected.imag()) > std::numeric_limits<float>::max()) {
throw std::runtime_error("S21 object calibration result must fit finite float32");
}
trace.s21[index] = {.re = static_cast<float>(corrected.real()), .im = static_cast<float>(corrected.imag())};
}
}
void S11CalibrationBundle::load(
const std::string& open_path,
const std::string& short_path,
@@ -39,6 +39,7 @@ class DataPreprocessor {
const config::RunConfig& config_;
CalibrationMaster& calibration_master_;
ReferenceMaster& reference_master_;
S21ObjectCalibrationBundle object_calibration_;
ipc::ShmRing& raw_ring_;
ipc::ShmRing& preprocessed_ring_;
ipc::ShmRing* preprocessed_tap_ring_ = nullptr;
@@ -8,6 +8,7 @@
#include <span>
#include <stdexcept>
#include <string_view>
#include <utility>
#include <vector>
namespace radar::preprocessing {
@@ -104,11 +105,14 @@ DataPreprocessor::DataPreprocessor(
reference_master_(reference_master),
raw_ring_(raw_ring),
preprocessed_ring_(preprocessed_ring),
preprocessed_tap_ring_(preprocessed_tap_ring) {}
preprocessed_tap_ring_(preprocessed_tap_ring) {
object_calibration_.load(config_.preprocess.s21.object_calibration.bundle_path);
}
void DataPreprocessor::validate_startup_prerequisites() const {
calibration_master_.validate_combos(config_.run_combos);
reference_master_.validate_combos(config_.run_combos);
object_calibration_.validate_combos(config_.run_combos);
}
auto DataPreprocessor::try_pop_raw_collection(
@@ -188,10 +192,12 @@ auto DataPreprocessor::preprocess_collection(const ipc::RawSweepCollection& raw_
preprocessed.traces.reserve(raw_collection.traces.size());
for (const auto& raw_trace : raw_collection.traces) {
// Pipeline order is fixed: calibration first, then reference subtraction.
// Object-derived corrections act on the completed preprocessing output.
const auto calibrated = calibration_master_.apply_to_trace(raw_trace);
const auto referenced = reference_master_.apply_to_trace(calibrated);
preprocessed.traces.push_back(apply_notch_filter(referenced, config_.preprocess.notch));
auto filtered = apply_notch_filter(referenced, config_.preprocess.notch);
object_calibration_.apply(filtered);
preprocessed.traces.push_back(std::move(filtered));
}
return preprocessed;