added pass through s11
This commit is contained in:
+29
-10
@@ -1,32 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "calibrator_interface.hpp"
|
||||
#include "channel_bundle_support.hpp"
|
||||
#include "shared_types.hpp"
|
||||
|
||||
namespace radar::preprocessing {
|
||||
|
||||
class CalibrationMaster {
|
||||
public:
|
||||
// `calibrator` encapsulates the actual calibration algorithm (v1: through).
|
||||
explicit CalibrationMaster(std::unique_ptr<CalibratorInterface> calibrator);
|
||||
// `s21_calibrator` encapsulates the S21 calibration algorithm.
|
||||
explicit CalibrationMaster(std::unique_ptr<CalibratorInterface> s21_calibrator);
|
||||
|
||||
// Loads a serialized raw sweep bundle with one calibration standard per combo.
|
||||
void load_bundle(const std::string& path);
|
||||
// Loads a serialized raw sweep bundle with one S21 calibration standard per combo.
|
||||
void load_s21_calibration_bundle(const std::string& path);
|
||||
// Loads optional one-port OSL calibration data for S11 from raw sweep bundles.
|
||||
void load_s11_calibration_bundle(
|
||||
const std::string& open_path,
|
||||
const std::string& short_path,
|
||||
const std::string& load_path
|
||||
);
|
||||
// Ensures all runtime combos are present in loaded standards.
|
||||
void validate_combos(const std::vector<ipc::ComboKey>& combos) const;
|
||||
// Applies calibration standard corresponding to measured trace combo.
|
||||
[[nodiscard]] auto apply(const ipc::SweepTraceBlock& measured_trace) const -> ipc::SweepTraceBlock;
|
||||
// Applies both channel calibrations to one measured trace.
|
||||
[[nodiscard]] auto apply_to_trace(const ipc::SweepTraceBlock& measured_trace) const -> ipc::SweepTraceBlock;
|
||||
[[nodiscard]] auto apply_s21(
|
||||
const ipc::ComboKey& combo,
|
||||
std::span<const float> frequency_hz,
|
||||
const std::vector<ipc::Complex32>& measured_s21
|
||||
) const -> std::vector<ipc::Complex32>;
|
||||
[[nodiscard]] auto apply_s11(
|
||||
const ipc::ComboKey& combo,
|
||||
std::span<const float> frequency_hz,
|
||||
const std::vector<ipc::Complex32>& measured_s11
|
||||
) const -> std::vector<ipc::Complex32>;
|
||||
[[nodiscard]] auto has_s11_calibration() const noexcept -> bool;
|
||||
|
||||
private:
|
||||
std::unique_ptr<CalibratorInterface> calibrator_impl_;
|
||||
std::unordered_map<ipc::ComboKey, ipc::SweepTraceBlock, ipc::ComboKeyHash> standards_by_combo_{};
|
||||
std::unique_ptr<CalibratorInterface> s21_calibrator_;
|
||||
S21CalibrationBundle s21_calibration_bundle_{};
|
||||
S11CalibrationBundle s11_calibration_bundle_{};
|
||||
};
|
||||
|
||||
[[nodiscard]] auto make_through_calibrator() -> std::unique_ptr<CalibratorInterface>;
|
||||
[[nodiscard]] auto make_s21_through_calibrator() -> std::unique_ptr<CalibratorInterface>;
|
||||
|
||||
} // namespace radar::preprocessing
|
||||
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "shared_types.hpp"
|
||||
|
||||
namespace radar::preprocessing {
|
||||
|
||||
class CalibratorInterface;
|
||||
|
||||
struct ChannelTrace {
|
||||
std::vector<float> frequency_hz{};
|
||||
std::vector<ipc::Complex32> samples{};
|
||||
};
|
||||
|
||||
class S21CalibrationBundle {
|
||||
public:
|
||||
void load(const std::string& path);
|
||||
|
||||
[[nodiscard]] auto is_enabled() const noexcept -> bool;
|
||||
void validate_combos(const std::vector<ipc::ComboKey>& combos) const;
|
||||
[[nodiscard]] auto apply(
|
||||
const ipc::ComboKey& combo,
|
||||
std::span<const float> frequency_hz,
|
||||
const std::vector<ipc::Complex32>& measured,
|
||||
const CalibratorInterface& calibrator
|
||||
) const -> std::vector<ipc::Complex32>;
|
||||
|
||||
private:
|
||||
[[nodiscard]] auto resolve(const ipc::ComboKey& combo) const -> const ChannelTrace&;
|
||||
|
||||
std::unordered_map<ipc::ComboKey, ChannelTrace, ipc::ComboKeyHash> traces_by_combo_{};
|
||||
};
|
||||
|
||||
class S21ReferenceBundle {
|
||||
public:
|
||||
void load(const std::string& path);
|
||||
|
||||
[[nodiscard]] auto is_enabled() const noexcept -> bool;
|
||||
void validate_combos(const std::vector<ipc::ComboKey>& combos) const;
|
||||
[[nodiscard]] auto resolve(const ipc::ComboKey& combo) const -> const ChannelTrace&;
|
||||
|
||||
private:
|
||||
std::unordered_map<ipc::ComboKey, ChannelTrace, ipc::ComboKeyHash> traces_by_combo_{};
|
||||
};
|
||||
|
||||
class S11CalibrationBundle {
|
||||
public:
|
||||
struct Coefficients {
|
||||
std::vector<float> frequency_hz{};
|
||||
std::vector<ipc::Complex32> directivity{};
|
||||
std::vector<ipc::Complex32> source_match{};
|
||||
std::vector<ipc::Complex32> reflection_tracking{};
|
||||
};
|
||||
|
||||
void load(
|
||||
const std::string& open_path,
|
||||
const std::string& short_path,
|
||||
const std::string& load_path
|
||||
);
|
||||
|
||||
[[nodiscard]] auto is_enabled() const noexcept -> bool;
|
||||
void validate_combos(const std::vector<ipc::ComboKey>& combos) const;
|
||||
[[nodiscard]] auto apply(
|
||||
const ipc::ComboKey& combo,
|
||||
std::span<const float> frequency_hz,
|
||||
const std::vector<ipc::Complex32>& measured
|
||||
) const -> std::vector<ipc::Complex32>;
|
||||
|
||||
private:
|
||||
[[nodiscard]] auto resolve(const ipc::ComboKey& combo) const -> const Coefficients&;
|
||||
|
||||
std::unordered_map<ipc::ComboKey, Coefficients, ipc::ComboKeyHash> coefficients_by_combo_{};
|
||||
};
|
||||
|
||||
class S11ReferenceBundle {
|
||||
public:
|
||||
void load(const std::string& path);
|
||||
|
||||
[[nodiscard]] auto is_enabled() const noexcept -> bool;
|
||||
void validate_combos(const std::vector<ipc::ComboKey>& combos) const;
|
||||
[[nodiscard]] auto resolve(const ipc::ComboKey& combo) const -> const ChannelTrace&;
|
||||
|
||||
private:
|
||||
std::unordered_map<ipc::ComboKey, ChannelTrace, ipc::ComboKeyHash> traces_by_combo_{};
|
||||
};
|
||||
|
||||
} // namespace radar::preprocessing
|
||||
+37
-84
@@ -1,107 +1,60 @@
|
||||
#include "calibration_master.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace radar::preprocessing {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] auto combo_to_string(const ipc::ComboKey& combo) -> std::string {
|
||||
return "input=" + std::to_string(combo.input_pos) + " output=" + std::to_string(combo.output_pos);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto read_binary_file(const std::string& path, const std::string& bundle_label)
|
||||
-> std::vector<std::uint8_t> {
|
||||
std::ifstream stream(path, std::ios::binary);
|
||||
if (!stream.is_open()) {
|
||||
throw std::runtime_error("Failed to open " + bundle_label + " bundle: " + path);
|
||||
}
|
||||
|
||||
return std::vector<std::uint8_t>(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
void validate_trace_layout(const ipc::SweepTraceBlock& trace, const std::string& trace_label) {
|
||||
if (trace.frequency_hz.size() != trace.s21.size()) {
|
||||
throw std::runtime_error(
|
||||
trace_label + " frequency/complex vector size mismatch for combo " + combo_to_string(trace.combo)
|
||||
);
|
||||
}
|
||||
if (trace.frequency_hz.size() != trace.s11.size()) {
|
||||
throw std::runtime_error(
|
||||
trace_label + " frequency/S11 vector size mismatch for combo " + combo_to_string(trace.combo)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CalibrationMaster::CalibrationMaster(std::unique_ptr<CalibratorInterface> calibrator)
|
||||
: calibrator_impl_(std::move(calibrator)) {
|
||||
if (!calibrator_impl_) {
|
||||
CalibrationMaster::CalibrationMaster(std::unique_ptr<CalibratorInterface> s21_calibrator)
|
||||
: s21_calibrator_(std::move(s21_calibrator)) {
|
||||
if (!s21_calibrator_) {
|
||||
throw std::runtime_error("CalibrationMaster requires a non-null calibrator");
|
||||
}
|
||||
}
|
||||
|
||||
void CalibrationMaster::load_bundle(const std::string& path) {
|
||||
if (path.empty()) {
|
||||
throw std::runtime_error("Calibration bundle path must not be empty");
|
||||
}
|
||||
void CalibrationMaster::load_s21_calibration_bundle(const std::string& path) {
|
||||
s21_calibration_bundle_.load(path);
|
||||
}
|
||||
|
||||
const auto bytes = read_binary_file(path, "calibration");
|
||||
if (bytes.empty()) {
|
||||
throw std::runtime_error("Calibration bundle is empty: " + path);
|
||||
}
|
||||
|
||||
const auto collection = ipc::deserialize_raw_collection(bytes);
|
||||
standards_by_combo_.clear();
|
||||
standards_by_combo_.reserve(collection.traces.size());
|
||||
for (const auto& trace : collection.traces) {
|
||||
validate_trace_layout(trace, "Calibration standard");
|
||||
standards_by_combo_.insert_or_assign(trace.combo, trace);
|
||||
}
|
||||
|
||||
if (standards_by_combo_.empty()) {
|
||||
throw std::runtime_error("Calibration bundle does not contain traces: " + path);
|
||||
}
|
||||
void CalibrationMaster::load_s11_calibration_bundle(
|
||||
const std::string& open_path,
|
||||
const std::string& short_path,
|
||||
const std::string& load_path
|
||||
) {
|
||||
s11_calibration_bundle_.load(open_path, short_path, load_path);
|
||||
}
|
||||
|
||||
void CalibrationMaster::validate_combos(const std::vector<ipc::ComboKey>& combos) const {
|
||||
for (const auto& combo : combos) {
|
||||
if (!standards_by_combo_.contains(combo)) {
|
||||
throw std::runtime_error("Calibration data is missing for combo " + combo_to_string(combo));
|
||||
}
|
||||
}
|
||||
s21_calibration_bundle_.validate_combos(combos);
|
||||
s11_calibration_bundle_.validate_combos(combos);
|
||||
}
|
||||
|
||||
auto CalibrationMaster::apply(const ipc::SweepTraceBlock& measured_trace) const -> ipc::SweepTraceBlock {
|
||||
validate_trace_layout(measured_trace, "Measured trace");
|
||||
|
||||
const auto found = standards_by_combo_.find(measured_trace.combo);
|
||||
if (found == standards_by_combo_.end()) {
|
||||
throw std::runtime_error(
|
||||
"Calibration standard is missing for measured combo " + combo_to_string(measured_trace.combo)
|
||||
);
|
||||
}
|
||||
|
||||
const auto& standard = found->second;
|
||||
validate_trace_layout(standard, "Calibration standard");
|
||||
if (measured_trace.s21.size() != standard.s21.size()) {
|
||||
throw std::runtime_error(
|
||||
"Calibration standard point count mismatch for combo " + combo_to_string(measured_trace.combo)
|
||||
);
|
||||
}
|
||||
|
||||
auto CalibrationMaster::apply_to_trace(const ipc::SweepTraceBlock& measured_trace) const -> ipc::SweepTraceBlock {
|
||||
ipc::SweepTraceBlock output{};
|
||||
output.combo = measured_trace.combo;
|
||||
output.frequency_hz = measured_trace.frequency_hz;
|
||||
output.s11 = measured_trace.s11;
|
||||
output.s21 = calibrator_impl_->apply(measured_trace.s21, standard.s21);
|
||||
|
||||
output.s21 = apply_s21(measured_trace.combo, measured_trace.frequency_hz, measured_trace.s21);
|
||||
output.s11 = apply_s11(measured_trace.combo, measured_trace.frequency_hz, measured_trace.s11);
|
||||
return output;
|
||||
}
|
||||
|
||||
auto CalibrationMaster::apply_s21(
|
||||
const ipc::ComboKey& combo,
|
||||
std::span<const float> frequency_hz,
|
||||
const std::vector<ipc::Complex32>& measured_s21
|
||||
) const -> std::vector<ipc::Complex32> {
|
||||
return s21_calibration_bundle_.apply(combo, frequency_hz, measured_s21, *s21_calibrator_);
|
||||
}
|
||||
|
||||
auto CalibrationMaster::apply_s11(
|
||||
const ipc::ComboKey& combo,
|
||||
std::span<const float> frequency_hz,
|
||||
const std::vector<ipc::Complex32>& measured_s11
|
||||
) const -> std::vector<ipc::Complex32> {
|
||||
return s11_calibration_bundle_.apply(combo, frequency_hz, measured_s11);
|
||||
}
|
||||
|
||||
auto CalibrationMaster::has_s11_calibration() const noexcept -> bool {
|
||||
return s11_calibration_bundle_.is_enabled();
|
||||
}
|
||||
|
||||
} // namespace radar::preprocessing
|
||||
|
||||
+405
@@ -0,0 +1,405 @@
|
||||
#include "channel_bundle_support.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <complex>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "calibrator_interface.hpp"
|
||||
|
||||
namespace radar::preprocessing {
|
||||
namespace {
|
||||
|
||||
enum class RawTraceChannel {
|
||||
S11,
|
||||
S21,
|
||||
};
|
||||
|
||||
constexpr float kFrequencyRelativeTolerance = 1e-5F;
|
||||
constexpr float kFrequencyAbsoluteTolerance = 1e-3F;
|
||||
constexpr float kComplexMagnitudeSquaredEpsilon = 1e-12F;
|
||||
|
||||
[[nodiscard]] auto combo_to_string(const ipc::ComboKey& combo) -> std::string {
|
||||
return "input=" + std::to_string(combo.input_pos) + " output=" + std::to_string(combo.output_pos);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto read_binary_file(const std::string& path, const std::string& bundle_label)
|
||||
-> std::vector<std::uint8_t> {
|
||||
std::ifstream stream(path, std::ios::binary);
|
||||
if (!stream.is_open()) {
|
||||
throw std::runtime_error("Failed to open " + bundle_label + " bundle: " + path);
|
||||
}
|
||||
|
||||
return std::vector<std::uint8_t>(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
void validate_raw_trace_layout(const ipc::SweepTraceBlock& trace, const std::string& trace_label) {
|
||||
if (trace.frequency_hz.size() != trace.s11.size()) {
|
||||
throw std::runtime_error(
|
||||
trace_label + " frequency/S11 vector size mismatch for combo " + combo_to_string(trace.combo)
|
||||
);
|
||||
}
|
||||
if (trace.frequency_hz.size() != trace.s21.size()) {
|
||||
throw std::runtime_error(
|
||||
trace_label + " frequency/S21 vector size mismatch for combo " + combo_to_string(trace.combo)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void validate_channel_trace_layout(
|
||||
const ChannelTrace& trace,
|
||||
const std::string& trace_label,
|
||||
const ipc::ComboKey& combo
|
||||
) {
|
||||
if (trace.frequency_hz.size() != trace.samples.size()) {
|
||||
throw std::runtime_error(
|
||||
trace_label + " frequency/channel vector size mismatch for combo " + combo_to_string(combo)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto frequency_axes_match(std::span<const float> left, std::span<const float> right) -> bool {
|
||||
if (left.size() != right.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (std::size_t index = 0; index < left.size(); ++index) {
|
||||
const auto delta = std::fabs(left[index] - right[index]);
|
||||
const auto scale = std::max(std::fabs(left[index]), std::fabs(right[index]));
|
||||
const auto tolerance = std::max(kFrequencyAbsoluteTolerance, scale * kFrequencyRelativeTolerance);
|
||||
if (delta > tolerance) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ensure_frequency_axis_match(
|
||||
std::span<const float> expected,
|
||||
std::span<const float> actual,
|
||||
const std::string& label
|
||||
) {
|
||||
if (!frequency_axes_match(expected, actual)) {
|
||||
throw std::runtime_error(label + " frequency axis mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto to_std_complex(const ipc::Complex32& value) -> std::complex<float> {
|
||||
return std::complex<float>(value.re, value.im);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto from_std_complex(const std::complex<float>& value) -> ipc::Complex32 {
|
||||
return ipc::Complex32{
|
||||
.re = value.real(),
|
||||
.im = value.imag(),
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] auto pick_channel_samples(const ipc::SweepTraceBlock& trace, RawTraceChannel channel)
|
||||
-> const std::vector<ipc::Complex32>& {
|
||||
if (channel == RawTraceChannel::S11) {
|
||||
return trace.s11;
|
||||
}
|
||||
return trace.s21;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto to_channel_trace(
|
||||
const ipc::SweepTraceBlock& trace,
|
||||
RawTraceChannel channel,
|
||||
const std::string& bundle_label
|
||||
) -> ChannelTrace {
|
||||
validate_raw_trace_layout(trace, bundle_label + " trace");
|
||||
|
||||
ChannelTrace channel_trace{};
|
||||
channel_trace.frequency_hz = trace.frequency_hz;
|
||||
channel_trace.samples = pick_channel_samples(trace, channel);
|
||||
validate_channel_trace_layout(channel_trace, bundle_label + " trace", trace.combo);
|
||||
return channel_trace;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto load_channel_traces(
|
||||
const std::string& path,
|
||||
const std::string& bundle_label,
|
||||
RawTraceChannel channel
|
||||
) -> std::unordered_map<ipc::ComboKey, ChannelTrace, ipc::ComboKeyHash> {
|
||||
const auto bytes = read_binary_file(path, bundle_label);
|
||||
if (bytes.empty()) {
|
||||
throw std::runtime_error(bundle_label + " bundle is empty: " + path);
|
||||
}
|
||||
|
||||
const auto collection = ipc::deserialize_raw_collection(bytes);
|
||||
std::unordered_map<ipc::ComboKey, ChannelTrace, ipc::ComboKeyHash> traces_by_combo{};
|
||||
traces_by_combo.reserve(collection.traces.size());
|
||||
for (const auto& trace : collection.traces) {
|
||||
traces_by_combo.insert_or_assign(trace.combo, to_channel_trace(trace, channel, bundle_label));
|
||||
}
|
||||
|
||||
if (traces_by_combo.empty()) {
|
||||
throw std::runtime_error(bundle_label + " bundle does not contain traces: " + path);
|
||||
}
|
||||
return traces_by_combo;
|
||||
}
|
||||
|
||||
void validate_bundle_combos(
|
||||
const std::unordered_map<ipc::ComboKey, ChannelTrace, ipc::ComboKeyHash>& traces_by_combo,
|
||||
const std::vector<ipc::ComboKey>& combos,
|
||||
const std::string& bundle_label
|
||||
) {
|
||||
if (traces_by_combo.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& combo : combos) {
|
||||
if (!traces_by_combo.contains(combo)) {
|
||||
throw std::runtime_error(bundle_label + " is missing combo " + combo_to_string(combo));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 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));
|
||||
}
|
||||
return found->second;
|
||||
}
|
||||
|
||||
void ensure_combo_coverage(
|
||||
const std::unordered_map<ipc::ComboKey, ChannelTrace, ipc::ComboKeyHash>& expected,
|
||||
const std::unordered_map<ipc::ComboKey, ChannelTrace, ipc::ComboKeyHash>& actual,
|
||||
const std::string& expected_label,
|
||||
const std::string& actual_label
|
||||
) {
|
||||
for (const auto& entry : expected) {
|
||||
const auto& combo = entry.first;
|
||||
if (!actual.contains(combo)) {
|
||||
throw std::runtime_error(
|
||||
actual_label + " is missing combo " + combo_to_string(combo) +
|
||||
" required by " + expected_label
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto solve_osl_coefficients(
|
||||
const ipc::ComboKey& combo,
|
||||
const ChannelTrace& open_trace,
|
||||
const ChannelTrace& short_trace,
|
||||
const ChannelTrace& load_trace
|
||||
) -> S11CalibrationBundle::Coefficients {
|
||||
validate_channel_trace_layout(open_trace, "S11 open calibration", combo);
|
||||
validate_channel_trace_layout(short_trace, "S11 short calibration", combo);
|
||||
validate_channel_trace_layout(load_trace, "S11 load calibration", combo);
|
||||
ensure_frequency_axis_match(open_trace.frequency_hz, short_trace.frequency_hz, "S11 short calibration");
|
||||
ensure_frequency_axis_match(open_trace.frequency_hz, load_trace.frequency_hz, "S11 load calibration");
|
||||
|
||||
S11CalibrationBundle::Coefficients coefficients{};
|
||||
coefficients.frequency_hz = open_trace.frequency_hz;
|
||||
coefficients.directivity.resize(open_trace.frequency_hz.size());
|
||||
coefficients.source_match.resize(open_trace.frequency_hz.size());
|
||||
coefficients.reflection_tracking.resize(open_trace.frequency_hz.size());
|
||||
|
||||
for (std::size_t index = 0; index < open_trace.frequency_hz.size(); ++index) {
|
||||
const auto load = to_std_complex(load_trace.samples[index]);
|
||||
const auto open_delta = to_std_complex(open_trace.samples[index]) - load;
|
||||
const auto short_delta = to_std_complex(short_trace.samples[index]) - load;
|
||||
const auto denominator = open_delta - short_delta;
|
||||
|
||||
std::complex<float> source_match = std::complex<float>(0.0F, 0.0F);
|
||||
std::complex<float> reflection_tracking = std::complex<float>(1.0F, 0.0F);
|
||||
if (std::norm(denominator) > kComplexMagnitudeSquaredEpsilon) {
|
||||
source_match = (open_delta + short_delta) / denominator;
|
||||
reflection_tracking = open_delta * (std::complex<float>(1.0F, 0.0F) - source_match);
|
||||
}
|
||||
|
||||
coefficients.directivity[index] = load_trace.samples[index];
|
||||
coefficients.source_match[index] = from_std_complex(source_match);
|
||||
coefficients.reflection_tracking[index] = from_std_complex(reflection_tracking);
|
||||
}
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void S21CalibrationBundle::load(const std::string& path) {
|
||||
traces_by_combo_.clear();
|
||||
|
||||
if (path.empty()) {
|
||||
throw std::runtime_error("S21 calibration bundle path must not be empty");
|
||||
}
|
||||
|
||||
traces_by_combo_ = load_channel_traces(path, "S21 calibration", RawTraceChannel::S21);
|
||||
}
|
||||
|
||||
auto S21CalibrationBundle::is_enabled() const noexcept -> bool {
|
||||
return !traces_by_combo_.empty();
|
||||
}
|
||||
|
||||
void S21CalibrationBundle::validate_combos(const std::vector<ipc::ComboKey>& combos) const {
|
||||
validate_bundle_combos(traces_by_combo_, combos, "S21 calibration bundle");
|
||||
}
|
||||
|
||||
auto S21CalibrationBundle::apply(
|
||||
const ipc::ComboKey& combo,
|
||||
std::span<const float> frequency_hz,
|
||||
const std::vector<ipc::Complex32>& measured,
|
||||
const CalibratorInterface& calibrator
|
||||
) const -> std::vector<ipc::Complex32> {
|
||||
const auto& calibration_trace = resolve(combo);
|
||||
ensure_frequency_axis_match(calibration_trace.frequency_hz, frequency_hz, "S21 calibration");
|
||||
if (measured.size() != calibration_trace.samples.size()) {
|
||||
throw std::runtime_error("S21 calibration vector size mismatch");
|
||||
}
|
||||
return calibrator.apply(measured, calibration_trace.samples);
|
||||
}
|
||||
|
||||
auto S21CalibrationBundle::resolve(const ipc::ComboKey& combo) const -> const ChannelTrace& {
|
||||
return resolve_required(traces_by_combo_, combo, "S21 calibration trace");
|
||||
}
|
||||
|
||||
void S21ReferenceBundle::load(const std::string& path) {
|
||||
traces_by_combo_.clear();
|
||||
|
||||
if (path.empty()) {
|
||||
throw std::runtime_error("S21 reference bundle path must not be empty");
|
||||
}
|
||||
|
||||
traces_by_combo_ = load_channel_traces(path, "S21 reference", RawTraceChannel::S21);
|
||||
}
|
||||
|
||||
auto S21ReferenceBundle::is_enabled() const noexcept -> bool {
|
||||
return !traces_by_combo_.empty();
|
||||
}
|
||||
|
||||
void S21ReferenceBundle::validate_combos(const std::vector<ipc::ComboKey>& combos) const {
|
||||
validate_bundle_combos(traces_by_combo_, combos, "S21 reference bundle");
|
||||
}
|
||||
|
||||
auto S21ReferenceBundle::resolve(const ipc::ComboKey& combo) const -> const ChannelTrace& {
|
||||
return resolve_required(traces_by_combo_, combo, "S21 reference trace");
|
||||
}
|
||||
|
||||
void S11CalibrationBundle::load(
|
||||
const std::string& open_path,
|
||||
const std::string& short_path,
|
||||
const std::string& load_path
|
||||
) {
|
||||
coefficients_by_combo_.clear();
|
||||
|
||||
if (open_path.empty() && short_path.empty() && load_path.empty()) {
|
||||
return;
|
||||
}
|
||||
if (open_path.empty() || short_path.empty() || load_path.empty()) {
|
||||
throw std::runtime_error("S11 calibration requires open, short, and load raw bundle paths");
|
||||
}
|
||||
|
||||
const auto open_traces = load_channel_traces(open_path, "S11 open calibration", RawTraceChannel::S11);
|
||||
const auto short_traces = load_channel_traces(short_path, "S11 short calibration", RawTraceChannel::S11);
|
||||
const auto load_traces = load_channel_traces(load_path, "S11 load calibration", RawTraceChannel::S11);
|
||||
|
||||
ensure_combo_coverage(open_traces, short_traces, "S11 open calibration", "S11 short calibration");
|
||||
ensure_combo_coverage(open_traces, load_traces, "S11 open calibration", "S11 load calibration");
|
||||
ensure_combo_coverage(short_traces, open_traces, "S11 short calibration", "S11 open calibration");
|
||||
ensure_combo_coverage(load_traces, open_traces, "S11 load calibration", "S11 open calibration");
|
||||
|
||||
coefficients_by_combo_.reserve(open_traces.size());
|
||||
for (const auto& [combo, open_trace] : open_traces) {
|
||||
coefficients_by_combo_.insert_or_assign(
|
||||
combo,
|
||||
solve_osl_coefficients(combo, open_trace, short_traces.at(combo), load_traces.at(combo))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
auto S11CalibrationBundle::is_enabled() const noexcept -> bool {
|
||||
return !coefficients_by_combo_.empty();
|
||||
}
|
||||
|
||||
void S11CalibrationBundle::validate_combos(const std::vector<ipc::ComboKey>& combos) const {
|
||||
if (!is_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& combo : combos) {
|
||||
if (!coefficients_by_combo_.contains(combo)) {
|
||||
throw std::runtime_error("S11 calibration data is missing for combo " + combo_to_string(combo));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto S11CalibrationBundle::apply(
|
||||
const ipc::ComboKey& combo,
|
||||
std::span<const float> frequency_hz,
|
||||
const std::vector<ipc::Complex32>& measured
|
||||
) const -> std::vector<ipc::Complex32> {
|
||||
if (!is_enabled()) {
|
||||
return measured;
|
||||
}
|
||||
if (frequency_hz.size() != measured.size()) {
|
||||
throw std::runtime_error("S11 calibration input frequency/sample size mismatch");
|
||||
}
|
||||
|
||||
const auto& coefficients = resolve(combo);
|
||||
ensure_frequency_axis_match(coefficients.frequency_hz, frequency_hz, "S11 calibration");
|
||||
if (measured.size() != coefficients.directivity.size()) {
|
||||
throw std::runtime_error("S11 calibration vector size mismatch");
|
||||
}
|
||||
|
||||
std::vector<ipc::Complex32> corrected{};
|
||||
corrected.reserve(measured.size());
|
||||
for (std::size_t index = 0; index < measured.size(); ++index) {
|
||||
const auto numerator = to_std_complex(measured[index]) - to_std_complex(coefficients.directivity[index]);
|
||||
const auto denominator =
|
||||
to_std_complex(coefficients.reflection_tracking[index]) +
|
||||
(to_std_complex(coefficients.source_match[index]) * numerator);
|
||||
|
||||
if (std::norm(denominator) > kComplexMagnitudeSquaredEpsilon) {
|
||||
corrected.push_back(from_std_complex(numerator / denominator));
|
||||
} else {
|
||||
corrected.push_back(from_std_complex(numerator));
|
||||
}
|
||||
}
|
||||
return corrected;
|
||||
}
|
||||
|
||||
auto S11CalibrationBundle::resolve(const ipc::ComboKey& combo) const -> const Coefficients& {
|
||||
return resolve_required(coefficients_by_combo_, combo, "S11 calibration coefficients");
|
||||
}
|
||||
|
||||
void S11ReferenceBundle::load(const std::string& path) {
|
||||
traces_by_combo_.clear();
|
||||
|
||||
if (path.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
traces_by_combo_ = load_channel_traces(path, "S11 reference", RawTraceChannel::S11);
|
||||
}
|
||||
|
||||
auto S11ReferenceBundle::is_enabled() const noexcept -> bool {
|
||||
return !traces_by_combo_.empty();
|
||||
}
|
||||
|
||||
void S11ReferenceBundle::validate_combos(const std::vector<ipc::ComboKey>& combos) const {
|
||||
validate_bundle_combos(traces_by_combo_, combos, "S11 reference bundle");
|
||||
}
|
||||
|
||||
auto S11ReferenceBundle::resolve(const ipc::ComboKey& combo) const -> const ChannelTrace& {
|
||||
return resolve_required(traces_by_combo_, combo, "S11 reference trace");
|
||||
}
|
||||
|
||||
} // namespace radar::preprocessing
|
||||
@@ -69,7 +69,7 @@ class ThroughCalibrator final : public CalibratorInterface {
|
||||
|
||||
} // namespace
|
||||
|
||||
auto make_through_calibrator() -> std::unique_ptr<CalibratorInterface> {
|
||||
auto make_s21_through_calibrator() -> std::unique_ptr<CalibratorInterface> {
|
||||
return std::make_unique<ThroughCalibrator>();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user