Add object calibration
This commit is contained in:
+12
@@ -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 {
|
||||
|
||||
+35
-2
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user