init commit

This commit is contained in:
Ayzen
2026-03-05 14:42:33 +03:00
commit fd4618b20d
964 changed files with 325114 additions and 0 deletions
@@ -0,0 +1,32 @@
#pragma once
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "calibrator_interface.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);
// Loads a serialized raw sweep bundle with one calibration standard per combo.
void load_bundle(const std::string& 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;
private:
std::unique_ptr<CalibratorInterface> calibrator_impl_;
std::unordered_map<ipc::ComboKey, ipc::SweepTraceBlock, ipc::ComboKeyHash> standards_by_combo_{};
};
[[nodiscard]] auto make_through_calibrator() -> std::unique_ptr<CalibratorInterface>;
} // namespace radar::preprocessing
@@ -0,0 +1,21 @@
#pragma once
#include <string>
#include <vector>
#include "shared_types.hpp"
namespace radar::preprocessing {
class CalibratorInterface {
public:
virtual ~CalibratorInterface() = default;
[[nodiscard]] virtual auto name() const -> std::string = 0;
[[nodiscard]] virtual auto apply(
const std::vector<ipc::Complex32>& measured,
const std::vector<ipc::Complex32>& calibration
) const -> std::vector<ipc::Complex32> = 0;
};
} // namespace radar::preprocessing
@@ -0,0 +1,101 @@
#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)
);
}
}
} // namespace
CalibrationMaster::CalibrationMaster(std::unique_ptr<CalibratorInterface> calibrator)
: calibrator_impl_(std::move(calibrator)) {
if (!calibrator_impl_) {
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");
}
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::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));
}
}
}
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)
);
}
ipc::SweepTraceBlock output{};
output.combo = measured_trace.combo;
output.frequency_hz = measured_trace.frequency_hz;
output.s21 = calibrator_impl_->apply(measured_trace.s21, standard.s21);
return output;
}
} // namespace radar::preprocessing
@@ -0,0 +1,76 @@
#include "calibration_master.hpp"
#include <cstddef>
#include <memory>
#include <stdexcept>
#include <vector>
#include <Eigen/Core>
namespace radar::preprocessing {
namespace {
class ThroughCalibrator final : public CalibratorInterface {
public:
[[nodiscard]] auto name() const -> std::string override {
return "through";
}
[[nodiscard]] auto apply(
const std::vector<ipc::Complex32>& measured,
const std::vector<ipc::Complex32>& calibration
) const -> std::vector<ipc::Complex32> override {
if (measured.size() != calibration.size()) {
throw std::runtime_error("Through calibration vector size mismatch");
}
static_assert(sizeof(ipc::Complex32) == sizeof(float) * 2U, "Complex32 layout must be two contiguous floats");
constexpr float epsilon = 1e-12F;
using InterleavedComplexView = Eigen::Matrix<float, Eigen::Dynamic, 2, Eigen::RowMajor>;
const auto point_count = static_cast<Eigen::Index>(measured.size());
Eigen::Map<const InterleavedComplexView> measured_view(
reinterpret_cast<const float*>(measured.data()),
point_count,
2
);
Eigen::Map<const InterleavedComplexView> calibration_view(
reinterpret_cast<const float*>(calibration.data()),
point_count,
2
);
const auto measured_re = measured_view.col(0).array();
const auto measured_im = measured_view.col(1).array();
const auto calibration_re = calibration_view.col(0).array();
const auto calibration_im = calibration_view.col(1).array();
const Eigen::ArrayXf magnitude_squared = calibration_re.square() + calibration_im.square();
const auto stable_division_mask = (magnitude_squared > epsilon);
const Eigen::ArrayXf corrected_re = stable_division_mask.select(
((measured_re * calibration_re) + (measured_im * calibration_im)) / magnitude_squared,
measured_re
);
const Eigen::ArrayXf corrected_im = stable_division_mask.select(
((measured_im * calibration_re) - (measured_re * calibration_im)) / magnitude_squared,
measured_im
);
std::vector<ipc::Complex32> corrected(measured.size());
Eigen::Map<InterleavedComplexView> corrected_view(reinterpret_cast<float*>(corrected.data()), point_count, 2);
corrected_view.col(0) = corrected_re.matrix();
corrected_view.col(1) = corrected_im.matrix();
return corrected;
}
};
} // namespace
auto make_through_calibrator() -> std::unique_ptr<CalibratorInterface> {
return std::make_unique<ThroughCalibrator>();
}
} // namespace radar::preprocessing