diff --git a/Makefile b/Makefile index 21cdd78..a208742 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,7 @@ ORCH_SOURCES := \ PREPROC_SOURCES := \ data_acq_and_processing/preprocessing/calibration_master/src/calibration_master.cpp \ + data_acq_and_processing/preprocessing/calibration_master/src/channel_bundle_support.cpp \ data_acq_and_processing/preprocessing/calibration_master/src/through_calibrator.cpp \ data_acq_and_processing/preprocessing/reference_master/src/reference_master.cpp \ data_acq_and_processing/preprocessing/data_preprocessor/src/data_preprocessor.cpp \ diff --git a/data_acq_and_processing/common_cpp/config/include/run_config.hpp b/data_acq_and_processing/common_cpp/config/include/run_config.hpp index eea690c..b5a4af7 100644 --- a/data_acq_and_processing/common_cpp/config/include/run_config.hpp +++ b/data_acq_and_processing/common_cpp/config/include/run_config.hpp @@ -75,10 +75,14 @@ struct RuntimeConfig { struct PreprocessConfig { // Names and bundle paths selected by Python GUI layer. - std::string calibration_set{}; - std::string reference_set{}; - std::string calibration_bundle_path{}; - std::string reference_bundle_path{}; + std::string s21_calibration_set{}; + std::string s21_reference_set{}; + std::string s21_calibration_bundle_path{}; + std::string s21_reference_bundle_path{}; + std::string s11_open_calibration_bundle_path{}; + std::string s11_short_calibration_bundle_path{}; + std::string s11_load_calibration_bundle_path{}; + std::string s11_reference_bundle_path{}; }; struct GprTxGeometry { diff --git a/data_acq_and_processing/common_cpp/config/src/run_config.cpp b/data_acq_and_processing/common_cpp/config/src/run_config.cpp index 1cec13d..7d389fe 100644 --- a/data_acq_and_processing/common_cpp/config/src/run_config.cpp +++ b/data_acq_and_processing/common_cpp/config/src/run_config.cpp @@ -438,10 +438,18 @@ auto load_run_config(const std::string& path) -> RunConfig { { const auto* preprocess_obj = as_object(required_field(*root_obj, "preprocess"), "preprocess"); - config.preprocess.calibration_set = optional_string(*preprocess_obj, "calibration_set", ""); - config.preprocess.reference_set = optional_string(*preprocess_obj, "reference_set", ""); - config.preprocess.calibration_bundle_path = optional_string(*preprocess_obj, "calibration_bundle_path", ""); - config.preprocess.reference_bundle_path = optional_string(*preprocess_obj, "reference_bundle_path", ""); + config.preprocess.s21_calibration_set = optional_string(*preprocess_obj, "s21_calibration_set", ""); + config.preprocess.s21_reference_set = optional_string(*preprocess_obj, "s21_reference_set", ""); + config.preprocess.s21_calibration_bundle_path = + optional_string(*preprocess_obj, "s21_calibration_bundle_path", ""); + config.preprocess.s21_reference_bundle_path = optional_string(*preprocess_obj, "s21_reference_bundle_path", ""); + config.preprocess.s11_open_calibration_bundle_path = + optional_string(*preprocess_obj, "s11_open_calibration_bundle_path", ""); + config.preprocess.s11_short_calibration_bundle_path = + optional_string(*preprocess_obj, "s11_short_calibration_bundle_path", ""); + config.preprocess.s11_load_calibration_bundle_path = + optional_string(*preprocess_obj, "s11_load_calibration_bundle_path", ""); + config.preprocess.s11_reference_bundle_path = optional_string(*preprocess_obj, "s11_reference_bundle_path", ""); } if (const auto* gpr_value = optional_field(*root_obj, "gpr"); gpr_value != nullptr) { diff --git a/data_acq_and_processing/preprocessing/calibration_master/include/calibration_master.hpp b/data_acq_and_processing/preprocessing/calibration_master/include/calibration_master.hpp index a0489fc..5508d1a 100644 --- a/data_acq_and_processing/preprocessing/calibration_master/include/calibration_master.hpp +++ b/data_acq_and_processing/preprocessing/calibration_master/include/calibration_master.hpp @@ -1,32 +1,51 @@ #pragma once #include +#include #include -#include #include #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 calibrator); + // `s21_calibrator` encapsulates the S21 calibration algorithm. + explicit CalibrationMaster(std::unique_ptr 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& 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 frequency_hz, + const std::vector& measured_s21 + ) const -> std::vector; + [[nodiscard]] auto apply_s11( + const ipc::ComboKey& combo, + std::span frequency_hz, + const std::vector& measured_s11 + ) const -> std::vector; + [[nodiscard]] auto has_s11_calibration() const noexcept -> bool; private: - std::unique_ptr calibrator_impl_; - std::unordered_map standards_by_combo_{}; + std::unique_ptr s21_calibrator_; + S21CalibrationBundle s21_calibration_bundle_{}; + S11CalibrationBundle s11_calibration_bundle_{}; }; -[[nodiscard]] auto make_through_calibrator() -> std::unique_ptr; +[[nodiscard]] auto make_s21_through_calibrator() -> std::unique_ptr; } // namespace radar::preprocessing diff --git a/data_acq_and_processing/preprocessing/calibration_master/include/channel_bundle_support.hpp b/data_acq_and_processing/preprocessing/calibration_master/include/channel_bundle_support.hpp new file mode 100644 index 0000000..4a85258 --- /dev/null +++ b/data_acq_and_processing/preprocessing/calibration_master/include/channel_bundle_support.hpp @@ -0,0 +1,91 @@ +#pragma once + +#include +#include +#include +#include + +#include "shared_types.hpp" + +namespace radar::preprocessing { + +class CalibratorInterface; + +struct ChannelTrace { + std::vector frequency_hz{}; + std::vector samples{}; +}; + +class S21CalibrationBundle { + public: + void load(const std::string& path); + + [[nodiscard]] auto is_enabled() const noexcept -> bool; + void validate_combos(const std::vector& combos) const; + [[nodiscard]] auto apply( + const ipc::ComboKey& combo, + std::span frequency_hz, + const std::vector& measured, + const CalibratorInterface& calibrator + ) const -> std::vector; + + private: + [[nodiscard]] auto resolve(const ipc::ComboKey& combo) const -> const ChannelTrace&; + + std::unordered_map 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& combos) const; + [[nodiscard]] auto resolve(const ipc::ComboKey& combo) const -> const ChannelTrace&; + + private: + std::unordered_map traces_by_combo_{}; +}; + +class S11CalibrationBundle { + public: + struct Coefficients { + std::vector frequency_hz{}; + std::vector directivity{}; + std::vector source_match{}; + std::vector 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& combos) const; + [[nodiscard]] auto apply( + const ipc::ComboKey& combo, + std::span frequency_hz, + const std::vector& measured + ) const -> std::vector; + + private: + [[nodiscard]] auto resolve(const ipc::ComboKey& combo) const -> const Coefficients&; + + std::unordered_map 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& combos) const; + [[nodiscard]] auto resolve(const ipc::ComboKey& combo) const -> const ChannelTrace&; + + private: + std::unordered_map traces_by_combo_{}; +}; + +} // namespace radar::preprocessing diff --git a/data_acq_and_processing/preprocessing/calibration_master/src/calibration_master.cpp b/data_acq_and_processing/preprocessing/calibration_master/src/calibration_master.cpp index 126f61c..f9ae484 100644 --- a/data_acq_and_processing/preprocessing/calibration_master/src/calibration_master.cpp +++ b/data_acq_and_processing/preprocessing/calibration_master/src/calibration_master.cpp @@ -1,107 +1,60 @@ #include "calibration_master.hpp" -#include -#include -#include #include -#include -#include 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::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::istreambuf_iterator(stream), std::istreambuf_iterator()); -} - -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 calibrator) - : calibrator_impl_(std::move(calibrator)) { - if (!calibrator_impl_) { +CalibrationMaster::CalibrationMaster(std::unique_ptr 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& 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 frequency_hz, + const std::vector& measured_s21 +) const -> std::vector { + return s21_calibration_bundle_.apply(combo, frequency_hz, measured_s21, *s21_calibrator_); +} + +auto CalibrationMaster::apply_s11( + const ipc::ComboKey& combo, + std::span frequency_hz, + const std::vector& measured_s11 +) const -> std::vector { + 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 diff --git a/data_acq_and_processing/preprocessing/calibration_master/src/channel_bundle_support.cpp b/data_acq_and_processing/preprocessing/calibration_master/src/channel_bundle_support.cpp new file mode 100644 index 0000000..631a17e --- /dev/null +++ b/data_acq_and_processing/preprocessing/calibration_master/src/channel_bundle_support.cpp @@ -0,0 +1,405 @@ +#include "channel_bundle_support.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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::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::istreambuf_iterator(stream), std::istreambuf_iterator()); +} + +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 left, std::span 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 expected, + std::span 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 { + return std::complex(value.re, value.im); +} + +[[nodiscard]] auto from_std_complex(const std::complex& 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& { + 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 { + 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 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& traces_by_combo, + const std::vector& 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 +auto resolve_required( + const std::unordered_map& 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& expected, + const std::unordered_map& 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 source_match = std::complex(0.0F, 0.0F); + std::complex reflection_tracking = std::complex(1.0F, 0.0F); + if (std::norm(denominator) > kComplexMagnitudeSquaredEpsilon) { + source_match = (open_delta + short_delta) / denominator; + reflection_tracking = open_delta * (std::complex(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& combos) const { + validate_bundle_combos(traces_by_combo_, combos, "S21 calibration bundle"); +} + +auto S21CalibrationBundle::apply( + const ipc::ComboKey& combo, + std::span frequency_hz, + const std::vector& measured, + const CalibratorInterface& calibrator +) const -> std::vector { + 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& 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& 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 frequency_hz, + const std::vector& measured +) const -> std::vector { + 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 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& 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 diff --git a/data_acq_and_processing/preprocessing/calibration_master/src/through_calibrator.cpp b/data_acq_and_processing/preprocessing/calibration_master/src/through_calibrator.cpp index 5aff8cd..a146397 100644 --- a/data_acq_and_processing/preprocessing/calibration_master/src/through_calibrator.cpp +++ b/data_acq_and_processing/preprocessing/calibration_master/src/through_calibrator.cpp @@ -69,7 +69,7 @@ class ThroughCalibrator final : public CalibratorInterface { } // namespace -auto make_through_calibrator() -> std::unique_ptr { +auto make_s21_through_calibrator() -> std::unique_ptr { return std::make_unique(); } diff --git a/data_acq_and_processing/preprocessing/data_preprocessor/src/data_preprocessor.cpp b/data_acq_and_processing/preprocessing/data_preprocessor/src/data_preprocessor.cpp index 3729de4..9b2c05a 100644 --- a/data_acq_and_processing/preprocessing/data_preprocessor/src/data_preprocessor.cpp +++ b/data_acq_and_processing/preprocessing/data_preprocessor/src/data_preprocessor.cpp @@ -74,8 +74,8 @@ auto DataPreprocessor::preprocess_collection(const ipc::RawSweepCollection& raw_ for (const auto& raw_trace : raw_collection.traces) { // Pipeline order is fixed: calibration first, then reference subtraction. - const auto calibrated = calibration_master_.apply(raw_trace); - const auto referenced = reference_master_.apply(calibrated); + const auto calibrated = calibration_master_.apply_to_trace(raw_trace); + const auto referenced = reference_master_.apply_to_trace(calibrated); preprocessed.traces.push_back(referenced); } diff --git a/data_acq_and_processing/preprocessing/data_preprocessor/src/main.cpp b/data_acq_and_processing/preprocessing/data_preprocessor/src/main.cpp index 48f671a..db4745a 100644 --- a/data_acq_and_processing/preprocessing/data_preprocessor/src/main.cpp +++ b/data_acq_and_processing/preprocessing/data_preprocessor/src/main.cpp @@ -62,12 +62,20 @@ int main(int argc, char** argv) { ); // Load preprocessing assets once before entering run loop. - radar::preprocessing::CalibrationMaster calibration_master(radar::preprocessing::make_through_calibrator()); - calibration_master.load_bundle(config.preprocess.calibration_bundle_path); + radar::preprocessing::CalibrationMaster calibration_master( + radar::preprocessing::make_s21_through_calibrator() + ); + calibration_master.load_s21_calibration_bundle(config.preprocess.s21_calibration_bundle_path); + calibration_master.load_s11_calibration_bundle( + config.preprocess.s11_open_calibration_bundle_path, + config.preprocess.s11_short_calibration_bundle_path, + config.preprocess.s11_load_calibration_bundle_path + ); radar::preprocessing::ReferenceMaster reference_master; - reference_master.load_bundle(config.preprocess.reference_bundle_path); - reference_master.prepare_calibrated(calibration_master); + reference_master.load_s21_reference_bundle(config.preprocess.s21_reference_bundle_path); + reference_master.load_s11_reference_bundle(config.preprocess.s11_reference_bundle_path); + reference_master.prepare_calibrated(calibration_master, config.run_combos); radar::preprocessing::DataPreprocessor preprocessor( config, diff --git a/data_acq_and_processing/preprocessing/reference_master/include/reference_master.hpp b/data_acq_and_processing/preprocessing/reference_master/include/reference_master.hpp index cb23ca1..2d3ae70 100644 --- a/data_acq_and_processing/preprocessing/reference_master/include/reference_master.hpp +++ b/data_acq_and_processing/preprocessing/reference_master/include/reference_master.hpp @@ -1,9 +1,11 @@ #pragma once +#include #include #include #include +#include "channel_bundle_support.hpp" #include "shared_types.hpp" namespace radar::preprocessing { @@ -12,19 +14,33 @@ class CalibrationMaster; class ReferenceMaster { public: - // Loads a serialized raw sweep bundle with one reference trace per combo. - void load_bundle(const std::string& path); + // Loads a serialized raw sweep bundle with one S21 reference trace per combo. + void load_s21_reference_bundle(const std::string& path); + // Loads optional raw S11 reference data. + void load_s11_reference_bundle(const std::string& path); // Builds calibrated references in memory using currently loaded raw references. - // Must be called after load_bundle() and after calibration standards are loaded. - void prepare_calibrated(const CalibrationMaster& calibration_master); + // Must be called after load_s21_reference_bundle() and after calibration standards are loaded. + void prepare_calibrated(const CalibrationMaster& calibration_master, const std::vector& combos); // Ensures all runtime combos are present in loaded references. void validate_combos(const std::vector& combos) const; - // Subtracts per-combo reference trace from calibrated trace. - [[nodiscard]] auto apply(const ipc::SweepTraceBlock& calibrated_trace) const -> ipc::SweepTraceBlock; + // Subtracts both channel references from one calibrated trace. + [[nodiscard]] auto apply_to_trace(const ipc::SweepTraceBlock& calibrated_trace) const -> ipc::SweepTraceBlock; + [[nodiscard]] auto apply_s21( + const ipc::ComboKey& combo, + std::span frequency_hz, + const std::vector& calibrated_s21 + ) const -> std::vector; + [[nodiscard]] auto apply_s11( + const ipc::ComboKey& combo, + std::span frequency_hz, + const std::vector& calibrated_s11 + ) const -> std::vector; private: - std::unordered_map raw_references_by_combo_{}; - std::unordered_map calibrated_references_by_combo_{}; + S21ReferenceBundle raw_s21_reference_bundle_{}; + std::unordered_map calibrated_s21_references_by_combo_{}; + S11ReferenceBundle raw_s11_reference_bundle_{}; + std::unordered_map calibrated_s11_references_by_combo_{}; }; } // namespace radar::preprocessing diff --git a/data_acq_and_processing/preprocessing/reference_master/src/reference_master.cpp b/data_acq_and_processing/preprocessing/reference_master/src/reference_master.cpp index 53d487d..afea007 100644 --- a/data_acq_and_processing/preprocessing/reference_master/src/reference_master.cpp +++ b/data_acq_and_processing/preprocessing/reference_master/src/reference_master.cpp @@ -1,8 +1,8 @@ #include "reference_master.hpp" -#include -#include -#include +#include +#include +#include #include #include #include @@ -15,132 +15,195 @@ namespace radar::preprocessing { namespace { +constexpr float kFrequencyRelativeTolerance = 1e-5F; +constexpr float kFrequencyAbsoluteTolerance = 1e-3F; + [[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::ifstream stream(path, std::ios::binary); - if (!stream.is_open()) { - throw std::runtime_error("Failed to open " + bundle_label + " bundle: " + path); +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) + ); } - - return std::vector(std::istreambuf_iterator(stream), std::istreambuf_iterator()); } -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) - ); +[[nodiscard]] auto frequency_axes_match(std::span left, std::span right) -> bool { + if (left.size() != right.size()) { + return false; } - 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) - ); + + 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; } } // namespace -void ReferenceMaster::load_bundle(const std::string& path) { - if (path.empty()) { - throw std::runtime_error("Reference bundle path must not be empty"); - } - - const auto bytes = read_binary_file(path, "reference"); - if (bytes.empty()) { - throw std::runtime_error("Reference bundle is empty: " + path); - } - - const auto collection = ipc::deserialize_raw_collection(bytes); - raw_references_by_combo_.clear(); - raw_references_by_combo_.reserve(collection.traces.size()); - calibrated_references_by_combo_.clear(); - for (const auto& trace : collection.traces) { - validate_trace_layout(trace, "Reference trace"); - raw_references_by_combo_.insert_or_assign(trace.combo, trace); - } - - if (raw_references_by_combo_.empty()) { - throw std::runtime_error("Reference bundle does not contain traces: " + path); - } +void ReferenceMaster::load_s21_reference_bundle(const std::string& path) { + raw_s21_reference_bundle_.load(path); + calibrated_s21_references_by_combo_.clear(); } -void ReferenceMaster::prepare_calibrated(const CalibrationMaster& calibration_master) { - if (raw_references_by_combo_.empty()) { +void ReferenceMaster::load_s11_reference_bundle(const std::string& path) { + raw_s11_reference_bundle_.load(path); + calibrated_s11_references_by_combo_.clear(); +} + +void ReferenceMaster::prepare_calibrated( + const CalibrationMaster& calibration_master, + const std::vector& combos +) { + if (!raw_s21_reference_bundle_.is_enabled()) { throw std::runtime_error("Reference bundle must be loaded before prepare_calibrated()"); } - calibrated_references_by_combo_.clear(); - calibrated_references_by_combo_.reserve(raw_references_by_combo_.size()); - for (const auto& [combo, raw_reference] : raw_references_by_combo_) { - auto calibrated = calibration_master.apply(raw_reference); - calibrated.combo = combo; - calibrated_references_by_combo_.insert_or_assign(combo, std::move(calibrated)); + calibrated_s21_references_by_combo_.clear(); + calibrated_s21_references_by_combo_.reserve(combos.size()); + for (const auto& combo : combos) { + const auto& raw_reference = raw_s21_reference_bundle_.resolve(combo); + ChannelTrace calibrated{}; + calibrated.frequency_hz = raw_reference.frequency_hz; + calibrated.samples = calibration_master.apply_s21(combo, raw_reference.frequency_hz, raw_reference.samples); + calibrated_s21_references_by_combo_.insert_or_assign(combo, std::move(calibrated)); + } + + calibrated_s11_references_by_combo_.clear(); + if (!raw_s11_reference_bundle_.is_enabled()) { + return; + } + if (!calibration_master.has_s11_calibration()) { + throw std::runtime_error("S11 reference bundle requires S11 calibration bundle"); + } + + calibrated_s11_references_by_combo_.reserve(combos.size()); + for (const auto& combo : combos) { + const auto& raw_reference = raw_s11_reference_bundle_.resolve(combo); + ChannelTrace calibrated{}; + calibrated.frequency_hz = raw_reference.frequency_hz; + calibrated.samples = calibration_master.apply_s11(combo, raw_reference.frequency_hz, raw_reference.samples); + calibrated_s11_references_by_combo_.insert_or_assign(combo, std::move(calibrated)); } } void ReferenceMaster::validate_combos(const std::vector& combos) const { - if (calibrated_references_by_combo_.empty()) { + if (calibrated_s21_references_by_combo_.empty()) { throw std::runtime_error( "Calibrated references are not prepared. Call ReferenceMaster::prepare_calibrated() at startup." ); } + raw_s21_reference_bundle_.validate_combos(combos); for (const auto& combo : combos) { - if (!raw_references_by_combo_.contains(combo)) { - throw std::runtime_error("Raw reference data is missing for combo " + combo_to_string(combo)); + if (!calibrated_s21_references_by_combo_.contains(combo)) { + throw std::runtime_error("Calibrated S21 reference data is missing for combo " + combo_to_string(combo)); } - if (!calibrated_references_by_combo_.contains(combo)) { - throw std::runtime_error("Calibrated reference data is missing for combo " + combo_to_string(combo)); + } + + raw_s11_reference_bundle_.validate_combos(combos); + if (!raw_s11_reference_bundle_.is_enabled()) { + return; + } + if (calibrated_s11_references_by_combo_.empty()) { + throw std::runtime_error("Calibrated S11 references are not prepared"); + } + for (const auto& combo : combos) { + if (!calibrated_s11_references_by_combo_.contains(combo)) { + throw std::runtime_error("Calibrated S11 reference data is missing for combo " + combo_to_string(combo)); } } } -auto ReferenceMaster::apply(const ipc::SweepTraceBlock& calibrated_trace) const -> ipc::SweepTraceBlock { - validate_trace_layout(calibrated_trace, "Calibrated trace"); - - const auto found = calibrated_references_by_combo_.find(calibrated_trace.combo); - if (found == calibrated_references_by_combo_.end()) { - throw std::runtime_error( - "Calibrated reference trace is missing for combo " + combo_to_string(calibrated_trace.combo) - ); - } - - const auto& reference = found->second; - validate_trace_layout(reference, "Calibrated reference trace"); - if (calibrated_trace.s21.size() != reference.s21.size()) { - throw std::runtime_error( - "Reference point count mismatch for combo " + combo_to_string(calibrated_trace.combo) - ); - } - +auto ReferenceMaster::apply_to_trace(const ipc::SweepTraceBlock& calibrated_trace) const -> ipc::SweepTraceBlock { ipc::SweepTraceBlock output{}; output.combo = calibrated_trace.combo; output.frequency_hz = calibrated_trace.frequency_hz; - output.s11 = calibrated_trace.s11; - output.s21.resize(calibrated_trace.s21.size()); + output.s21 = apply_s21(calibrated_trace.combo, calibrated_trace.frequency_hz, calibrated_trace.s21); + output.s11 = apply_s11(calibrated_trace.combo, calibrated_trace.frequency_hz, calibrated_trace.s11); + return output; +} + +auto ReferenceMaster::apply_s21( + const ipc::ComboKey& combo, + std::span frequency_hz, + const std::vector& calibrated_s21 +) const -> std::vector { + const auto found = calibrated_s21_references_by_combo_.find(combo); + if (found == calibrated_s21_references_by_combo_.end()) { + throw std::runtime_error("Calibrated S21 reference is missing for combo " + combo_to_string(combo)); + } + + const auto& reference = found->second; + validate_channel_trace_layout(reference, "Calibrated S21 reference", combo); + if (calibrated_s21.size() != reference.samples.size()) { + throw std::runtime_error("S21 reference point count mismatch for combo " + combo_to_string(combo)); + } + if (!frequency_axes_match(frequency_hz, reference.frequency_hz)) { + throw std::runtime_error("S21 reference frequency axis mismatch for combo " + combo_to_string(combo)); + } + + std::vector output(calibrated_s21.size()); static_assert(sizeof(ipc::Complex32) == sizeof(float) * 2U, "Complex32 layout must be two contiguous floats"); using InterleavedComplexView = Eigen::Matrix; - const auto point_count = static_cast(calibrated_trace.s21.size()); + const auto point_count = static_cast(calibrated_s21.size()); Eigen::Map calibrated_view( - reinterpret_cast(calibrated_trace.s21.data()), + reinterpret_cast(calibrated_s21.data()), point_count, 2 ); Eigen::Map reference_view( - reinterpret_cast(reference.s21.data()), + reinterpret_cast(reference.samples.data()), point_count, 2 ); - Eigen::Map output_view(reinterpret_cast(output.s21.data()), point_count, 2); + Eigen::Map output_view(reinterpret_cast(output.data()), point_count, 2); output_view = calibrated_view - reference_view; + return output; +} +auto ReferenceMaster::apply_s11( + const ipc::ComboKey& combo, + std::span frequency_hz, + const std::vector& calibrated_s11 +) const -> std::vector { + if (!raw_s11_reference_bundle_.is_enabled()) { + return calibrated_s11; + } + + const auto found = calibrated_s11_references_by_combo_.find(combo); + if (found == calibrated_s11_references_by_combo_.end()) { + throw std::runtime_error("Calibrated S11 reference is missing for combo " + combo_to_string(combo)); + } + + const auto& reference = found->second; + validate_channel_trace_layout(reference, "Calibrated S11 reference", combo); + if (calibrated_s11.size() != reference.samples.size()) { + throw std::runtime_error("S11 reference point count mismatch for combo " + combo_to_string(combo)); + } + if (!frequency_axes_match(frequency_hz, reference.frequency_hz)) { + throw std::runtime_error("S11 reference frequency axis mismatch for combo " + combo_to_string(combo)); + } + + std::vector output(calibrated_s11.size()); + for (std::size_t index = 0; index < calibrated_s11.size(); ++index) { + output[index].re = calibrated_s11[index].re - reference.samples[index].re; + output[index].im = calibrated_s11[index].im - reference.samples[index].im; + } return output; } diff --git a/data_acq_and_processing/preprocessing/testdata/s11_load_calibration_bundle.bin b/data_acq_and_processing/preprocessing/testdata/s11_load_calibration_bundle.bin new file mode 100644 index 0000000..6cee6b2 Binary files /dev/null and b/data_acq_and_processing/preprocessing/testdata/s11_load_calibration_bundle.bin differ diff --git a/data_acq_and_processing/preprocessing/testdata/s11_open_calibration_bundle.bin b/data_acq_and_processing/preprocessing/testdata/s11_open_calibration_bundle.bin new file mode 100644 index 0000000..283d22a Binary files /dev/null and b/data_acq_and_processing/preprocessing/testdata/s11_open_calibration_bundle.bin differ diff --git a/data_acq_and_processing/preprocessing/testdata/s11_reference_bundle.bin b/data_acq_and_processing/preprocessing/testdata/s11_reference_bundle.bin new file mode 100644 index 0000000..1d64a3b Binary files /dev/null and b/data_acq_and_processing/preprocessing/testdata/s11_reference_bundle.bin differ diff --git a/data_acq_and_processing/preprocessing/testdata/s11_short_calibration_bundle.bin b/data_acq_and_processing/preprocessing/testdata/s11_short_calibration_bundle.bin new file mode 100644 index 0000000..8b7e104 Binary files /dev/null and b/data_acq_and_processing/preprocessing/testdata/s11_short_calibration_bundle.bin differ diff --git a/data_acq_and_processing/processing/data_processor/include/processing_live_config.hpp b/data_acq_and_processing/processing/data_processor/include/processing_live_config.hpp index d5da5a3..935fc26 100644 --- a/data_acq_and_processing/processing/data_processor/include/processing_live_config.hpp +++ b/data_acq_and_processing/processing/data_processor/include/processing_live_config.hpp @@ -18,6 +18,7 @@ struct ProcessingLiveConfig { std::string processor_mode = "pass_through"; float gain_db = 0.0F; float phase_deg = 0.0F; + std::string pass_through_channel = "s21"; bool pass_through_fixed_y_enabled = false; float pass_through_y_min_db = -100.0F; float pass_through_y_max_db = 0.0F; diff --git a/data_acq_and_processing/processing/data_processor/src/processing_live_config.cpp b/data_acq_and_processing/processing/data_processor/src/processing_live_config.cpp index e74e8d0..3d1989b 100644 --- a/data_acq_and_processing/processing/data_processor/src/processing_live_config.cpp +++ b/data_acq_and_processing/processing/data_processor/src/processing_live_config.cpp @@ -30,6 +30,13 @@ using Json = nlohmann::json; throw std::runtime_error("processing.history_command must be one of: none, remove_last, clear_all"); } +[[nodiscard]] auto parse_pass_through_channel(const std::string& value) -> std::string { + if (value == "s21" || value == "s11") { + return value; + } + throw std::runtime_error("processing.pass_through_channel must be one of: s21, s11"); +} + [[nodiscard]] auto parse_u64_number(const Json& value, const std::string& field_name) -> std::uint64_t { if (!value.is_number()) { throw std::runtime_error(field_name + " must be number"); @@ -101,6 +108,12 @@ using Json = nlohmann::json; } config.phase_deg = static_cast(found->get()); } + if (const auto found = root.find("pass_through_channel"); found != root.end()) { + if (!found->is_string()) { + throw std::runtime_error("processing.pass_through_channel must be string"); + } + config.pass_through_channel = parse_pass_through_channel(found->get()); + } if (const auto found = root.find("pass_through_fixed_y_enabled"); found != root.end()) { if (!found->is_boolean()) { throw std::runtime_error("processing.pass_through_fixed_y_enabled must be bool"); diff --git a/data_acq_and_processing/processing/processors/src/passthrough_processor.cpp b/data_acq_and_processing/processing/processors/src/passthrough_processor.cpp index d55244a..247737a 100644 --- a/data_acq_and_processing/processing/processors/src/passthrough_processor.cpp +++ b/data_acq_and_processing/processing/processors/src/passthrough_processor.cpp @@ -29,7 +29,11 @@ auto PassThroughProcessor::process_collection( payload.processing_name = name(); payload.kind = ipc::ResultKind::TraceComplex; payload.frequency_hz = trace.frequency_hz; - payload.trace = trace.s21; + if (live_config.pass_through_channel == "s11") { + payload.trace = trace.s11; + } else { + payload.trace = trace.s21; + } const float linear_gain = std::pow(10.0F, live_config.gain_db / 20.0F); const float phase_rad = live_config.phase_deg * (kPi / 180.0F); diff --git a/python_app/gui/app_window.py b/python_app/gui/app_window.py index c4a188b..af6b1b9 100644 --- a/python_app/gui/app_window.py +++ b/python_app/gui/app_window.py @@ -78,8 +78,8 @@ class AppWindow( def _init_preprocess_state(self) -> None: """Initialize preprocessing dialog and selected set names.""" self._preprocess_dialog: PreprocessDialog | None = None - self._selected_calibration_set = str(self._defaults_config.preprocess.calibration_set) - self._selected_reference_set = str(self._defaults_config.preprocess.reference_set) + self._selected_s21_calibration_set = str(self._defaults_config.preprocess.s21_calibration_set) + self._selected_s21_reference_set = str(self._defaults_config.preprocess.s21_reference_set) def _init_capture_state(self) -> None: """Initialize one-shot capture and sequence-control flags.""" diff --git a/python_app/gui/controllers/app_window_config_mixin.py b/python_app/gui/controllers/app_window_config_mixin.py index bee62aa..7389e17 100644 --- a/python_app/gui/controllers/app_window_config_mixin.py +++ b/python_app/gui/controllers/app_window_config_mixin.py @@ -118,8 +118,8 @@ class AppWindowConfigMixin: if self._switches_are_effectively_static(config): config.combos = [ComboModel(input=0, output=0)] - config.preprocess.calibration_set = self._selected_calibration_set - config.preprocess.reference_set = self._selected_reference_set + config.preprocess.s21_calibration_set = self._selected_s21_calibration_set + config.preprocess.s21_reference_set = self._selected_s21_reference_set config.gpr.mode = self._gpr_config_mode.currentText() config.gpr.relative_permittivity = float(self._gpr_relative_permittivity.value()) config.gpr.tx_geometry = self._parse_gpr_tx_geometry_text(self._gpr_tx_geometry_input.toPlainText()) @@ -153,6 +153,7 @@ class AppWindowConfigMixin: processor_mode=self._processing_mode.currentText(), gain_db=float(self._processing_gain_db.value()), phase_deg=float(self._processing_phase_deg.value()), + pass_through_channel=self._pass_through_channel.currentText(), pass_through_fixed_y_enabled=bool(self._pass_through_fixed_y_enabled.isChecked()), pass_through_y_min_db=min(y_min_db, y_max_db), pass_through_y_max_db=max(y_min_db, y_max_db), diff --git a/python_app/gui/controllers/app_window_pipeline_mixin.py b/python_app/gui/controllers/app_window_pipeline_mixin.py index 3628249..9c3550d 100644 --- a/python_app/gui/controllers/app_window_pipeline_mixin.py +++ b/python_app/gui/controllers/app_window_pipeline_mixin.py @@ -38,28 +38,28 @@ class AppWindowPipelineMixin: run_signature = self._build_run_history_signature(config) radar_key = self._radar_key(config) - if not config.preprocess.calibration_set or not config.preprocess.reference_set: + if not config.preprocess.s21_calibration_set or not config.preprocess.s21_reference_set: raise RuntimeError("Select calibration and reference sets in Preprocessing Panel before Start") combo_keys = [ComboKey(input_pos=combo.input, output_pos=combo.output) for combo in config.combos] if not self._store.has_combo_coverage( - "calibration", radar_key, config.preprocess.calibration_set, combo_keys + "calibration", radar_key, config.preprocess.s21_calibration_set, combo_keys ): raise RuntimeError("Selected calibration set does not cover requested run combos") if not self._store.has_combo_coverage( - "reference", radar_key, config.preprocess.reference_set, combo_keys + "reference", radar_key, config.preprocess.s21_reference_set, combo_keys ): raise RuntimeError("Selected reference set does not cover requested run combos") - calibration_bundle, reference_bundle = self._config_writer.prepare_bundles( + calibration_bundle, reference_bundle = self._config_writer.prepare_s21_bundles( self._store, radar_key, - config.preprocess.calibration_set, - config.preprocess.reference_set, + config.preprocess.s21_calibration_set, + config.preprocess.s21_reference_set, ) - config.preprocess.calibration_bundle_path = str(calibration_bundle) - config.preprocess.reference_bundle_path = str(reference_bundle) + config.preprocess.s21_calibration_bundle_path = str(calibration_bundle) + config.preprocess.s21_reference_bundle_path = str(reference_bundle) config.runtime.continuous = not single_capture if not single_capture: diff --git a/python_app/gui/controllers/app_window_plot_mixin.py b/python_app/gui/controllers/app_window_plot_mixin.py index 7b85246..0aa0fe8 100644 --- a/python_app/gui/controllers/app_window_plot_mixin.py +++ b/python_app/gui/controllers/app_window_plot_mixin.py @@ -106,6 +106,7 @@ class AppWindowPlotMixin: show_phase = self._show_phase_curves() magnitude_plot = self._trace_magnitude_plot phase_plot = self._trace_phase_plot + pass_through_channel = self._pass_through_channel.currentText().upper() magnitude_plot.setVisible(show_magnitude) phase_plot.setVisible(show_phase) @@ -119,6 +120,7 @@ class AppWindowPlotMixin: mag_item.showAxis("left", show=True) mag_item.showAxis("bottom", show=not show_phase) magnitude_plot.setLabel("left", "Magnitude", units="dB") + magnitude_plot.setTitle(f"Pass-Through {pass_through_channel}") if not show_phase: magnitude_plot.setLabel("bottom", "Frequency", units="Hz") @@ -130,6 +132,7 @@ class AppWindowPlotMixin: phase_item.showAxis("bottom", show=True) phase_plot.setLabel("left", "Phase", units="deg") phase_plot.setLabel("bottom", "Frequency", units="Hz") + phase_plot.setTitle(f"Pass-Through {pass_through_channel}") palette = [ "#4cc9f0", diff --git a/python_app/gui/controllers/app_window_preprocess_mixin.py b/python_app/gui/controllers/app_window_preprocess_mixin.py index 6fa05d1..946d074 100644 --- a/python_app/gui/controllers/app_window_preprocess_mixin.py +++ b/python_app/gui/controllers/app_window_preprocess_mixin.py @@ -40,14 +40,14 @@ class AppWindowPreprocessMixin: def _on_preprocess_selection_changed(self, calibration_set: str, reference_set: str) -> None: """Persist selected preprocessing set names from dialog.""" - self._selected_calibration_set = calibration_set.strip() - self._selected_reference_set = reference_set.strip() + self._selected_s21_calibration_set = calibration_set.strip() + self._selected_s21_reference_set = reference_set.strip() self._refresh_preprocess_summary_labels() def _refresh_preprocess_summary_labels(self) -> None: """Update compact summary labels in the main window.""" - self._selected_calibration_label.setText(self._selected_calibration_set or "") - self._selected_reference_label.setText(self._selected_reference_set or "") + self._selected_calibration_label.setText(self._selected_s21_calibration_set or "") + self._selected_reference_label.setText(self._selected_s21_reference_set or "") def _refresh_sets(self) -> None: """Refresh calibration/reference set lists for current radar key.""" @@ -60,12 +60,12 @@ class AppWindowPreprocessMixin: dialog.set_calibration_sets(calibration_sets) dialog.set_reference_sets(reference_sets) - if self._selected_calibration_set not in calibration_sets: - self._selected_calibration_set = calibration_sets[0] if calibration_sets else "" - if self._selected_reference_set not in reference_sets: - self._selected_reference_set = reference_sets[0] if reference_sets else "" + if self._selected_s21_calibration_set not in calibration_sets: + self._selected_s21_calibration_set = calibration_sets[0] if calibration_sets else "" + if self._selected_s21_reference_set not in reference_sets: + self._selected_s21_reference_set = reference_sets[0] if reference_sets else "" - dialog.set_selected_sets(self._selected_calibration_set, self._selected_reference_set) + dialog.set_selected_sets(self._selected_s21_calibration_set, self._selected_s21_reference_set) self._refresh_preprocess_summary_labels() self._log(f"Set lists refreshed for key={radar_key}") @@ -147,9 +147,9 @@ class AppWindowPreprocessMixin: self._cleanup_capture_session() if kind == "calibration": - self._selected_calibration_set = set_name + self._selected_s21_calibration_set = set_name else: - self._selected_reference_set = set_name + self._selected_s21_reference_set = set_name self._refresh_sets() dialog.set_status(f"{kind.title()} set saved: {set_name} ({len(collection.traces)} traces)") diff --git a/python_app/gui/controllers/app_window_ui_mixin.py b/python_app/gui/controllers/app_window_ui_mixin.py index f500fa3..f184e08 100644 --- a/python_app/gui/controllers/app_window_ui_mixin.py +++ b/python_app/gui/controllers/app_window_ui_mixin.py @@ -79,7 +79,7 @@ class AppWindowUiMixin: # Default view on startup is pass-through traces. self._plot_stack.setCurrentWidget(self._trace_plots_container) - root_layout.addWidget(self._plot_stack, stretch=11) + root_layout.addWidget(self._plot_stack, stretch=12) def _build_bscan_plot_page(self) -> None: """Create B-scan page in plot stack.""" @@ -141,7 +141,7 @@ class AppWindowUiMixin: def _build_settings_panel(self, root_layout: QHBoxLayout, root: QWidget) -> None: """Build right settings panel with controls, status labels, and log.""" self._settings_panel = QWidget(root) - self._settings_panel.setMinimumWidth(659) + self._settings_panel.setMinimumWidth(530) right_layout = QVBoxLayout(self._settings_panel) right_layout.setContentsMargins(0, 0, 0, 0) right_layout.setSpacing(10) @@ -163,7 +163,7 @@ class AppWindowUiMixin: right_layout.addWidget(self._history_label) right_layout.addWidget(self._log_box, stretch=0) - root_layout.addWidget(self._settings_panel, stretch=8) + root_layout.addWidget(self._settings_panel, stretch=6) def _build_settings_scroll(self) -> QScrollArea: """Build scroll area with all control groups in display order.""" diff --git a/python_app/gui/controllers/sections/data_actions_section.py b/python_app/gui/controllers/sections/data_actions_section.py index e1c5b7d..8a68f69 100644 --- a/python_app/gui/controllers/sections/data_actions_section.py +++ b/python_app/gui/controllers/sections/data_actions_section.py @@ -2,6 +2,7 @@ from __future__ import annotations +from PyQt6.QtCore import Qt from PyQt6.QtWidgets import QGroupBox, QHBoxLayout, QLabel, QLineEdit, QPushButton, QSpinBox, QVBoxLayout @@ -11,16 +12,13 @@ def build_data_actions_group(owner) -> QGroupBox: layout = QVBoxLayout(group) layout.setSpacing(8) - save_row = QHBoxLayout() - save_row.setSpacing(8) - - save_button = QPushButton("Save Numpy Snapshot") + save_button = QPushButton("Save Snapshot") save_button.clicked.connect(owner._save_snapshot) - save_vna_json_button = QPushButton("Save VNA History JSON") + save_vna_json_button = QPushButton("Save VNA JSON") save_vna_json_button.clicked.connect(owner._save_vna_history_json) - remove_last_button = QPushButton("Remove Last Runtime Measurement") + remove_last_button = QPushButton("Remove Last Measurement") remove_last_button.clicked.connect(owner._remove_last_runtime_history) - clear_history_button = QPushButton("Clear ALL Runtime History") + clear_history_button = QPushButton("Clear Runtime History") clear_history_button.clicked.connect(owner._clear_all_runtime_history) owner._save_count = QSpinBox() owner._save_count.setMinimum(1) @@ -35,14 +33,20 @@ def build_data_actions_group(owner) -> QGroupBox: owner._vna_json_output_index.setMaximum(65_535) owner._vna_json_output_index.setValue(0) - save_row.addWidget(save_button) - save_row.addWidget(save_vna_json_button) - save_row.addWidget(remove_last_button) - save_row.addWidget(clear_history_button) - save_row.addWidget(QLabel("Last N")) - save_row.addWidget(owner._save_count) - save_row.addStretch(1) - layout.addLayout(save_row) + button_column = QVBoxLayout() + button_column.setSpacing(8) + button_column.addWidget(save_button, alignment=Qt.AlignmentFlag.AlignLeft) + button_column.addWidget(save_vna_json_button, alignment=Qt.AlignmentFlag.AlignLeft) + button_column.addWidget(remove_last_button, alignment=Qt.AlignmentFlag.AlignLeft) + button_column.addWidget(clear_history_button, alignment=Qt.AlignmentFlag.AlignLeft) + layout.addLayout(button_column) + + count_row = QHBoxLayout() + count_row.setSpacing(8) + count_row.addWidget(QLabel("Last N")) + count_row.addWidget(owner._save_count) + count_row.addStretch(1) + layout.addLayout(count_row) json_row = QHBoxLayout() json_row.setSpacing(8) diff --git a/python_app/gui/controllers/sections/hardware_actions_section.py b/python_app/gui/controllers/sections/hardware_actions_section.py index 70d5c87..db9b9ac 100644 --- a/python_app/gui/controllers/sections/hardware_actions_section.py +++ b/python_app/gui/controllers/sections/hardware_actions_section.py @@ -2,24 +2,25 @@ from __future__ import annotations -from PyQt6.QtWidgets import QGroupBox, QHBoxLayout, QPushButton +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QGroupBox, QPushButton, QVBoxLayout def build_hardware_actions_group(owner) -> QGroupBox: """Create hardware action buttons section.""" group = QGroupBox("Hardware Actions") - layout = QHBoxLayout(group) + layout = QVBoxLayout(group) layout.setSpacing(8) - apply_radar_button = QPushButton("Apply Radar Settings") + apply_radar_button = QPushButton("Apply Radar") apply_radar_button.clicked.connect(owner._apply_radar_settings) - layout.addWidget(apply_radar_button) + layout.addWidget(apply_radar_button, alignment=Qt.AlignmentFlag.AlignLeft) - save_config_button = QPushButton("Save Current Config") + save_config_button = QPushButton("Save Config") save_config_button.clicked.connect(owner._save_current_config) - layout.addWidget(save_config_button) + layout.addWidget(save_config_button, alignment=Qt.AlignmentFlag.AlignLeft) - preprocess_button = QPushButton("Preprocessing Panel") + preprocess_button = QPushButton("Preprocessing") preprocess_button.clicked.connect(owner._open_preprocess_panel) - layout.addWidget(preprocess_button) + layout.addWidget(preprocess_button, alignment=Qt.AlignmentFlag.AlignLeft) return group diff --git a/python_app/gui/controllers/sections/pipeline_section.py b/python_app/gui/controllers/sections/pipeline_section.py index 65ee644..ab6be87 100644 --- a/python_app/gui/controllers/sections/pipeline_section.py +++ b/python_app/gui/controllers/sections/pipeline_section.py @@ -2,7 +2,8 @@ from __future__ import annotations -from PyQt6.QtWidgets import QGroupBox, QHBoxLayout, QLabel, QPushButton, QVBoxLayout +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QGroupBox, QLabel, QPushButton, QVBoxLayout def build_pipeline_group(owner) -> QGroupBox: @@ -11,22 +12,17 @@ def build_pipeline_group(owner) -> QGroupBox: layout = QVBoxLayout(group) layout.setSpacing(8) - action_row = QHBoxLayout() - action_row.setSpacing(8) - start_button = QPushButton("Start") start_button.clicked.connect(owner._start_run) - action_row.addWidget(start_button) + layout.addWidget(start_button, alignment=Qt.AlignmentFlag.AlignLeft) single_button = QPushButton("Single Capture") single_button.clicked.connect(owner._start_single_capture) - action_row.addWidget(single_button) + layout.addWidget(single_button, alignment=Qt.AlignmentFlag.AlignLeft) stop_button = QPushButton("Stop") stop_button.clicked.connect(owner._stop_run) - action_row.addWidget(stop_button) - - layout.addLayout(action_row) + layout.addWidget(stop_button, alignment=Qt.AlignmentFlag.AlignLeft) hint = QLabel("Start continuous run or single processed collection capture.") hint.setObjectName("hintLabel") diff --git a/python_app/gui/controllers/sections/processing_section.py b/python_app/gui/controllers/sections/processing_section.py index 611a872..0d0cb63 100644 --- a/python_app/gui/controllers/sections/processing_section.py +++ b/python_app/gui/controllers/sections/processing_section.py @@ -61,6 +61,9 @@ def build_processing_group(owner) -> QGroupBox: owner._processing_phase_deg.setSingleStep(1.0) owner._processing_phase_deg.setValue(0.0) + owner._pass_through_channel = QComboBox() + owner._pass_through_channel.addItems(["s21", "s11"]) + owner._show_magnitude_checkbox = QCheckBox("Show magnitude") owner._show_magnitude_checkbox.setChecked(True) @@ -91,6 +94,7 @@ def build_processing_group(owner) -> QGroupBox: pass_through_form.addRow("Gain dB (live)", owner._processing_gain_db) pass_through_form.addRow("Phase deg (live)", owner._processing_phase_deg) + pass_through_form.addRow("Channel", owner._pass_through_channel) pass_through_form.addRow(owner._show_magnitude_checkbox) pass_through_form.addRow(owner._show_phase_checkbox) pass_through_form.addRow(owner._pass_through_fixed_y_enabled) @@ -206,6 +210,7 @@ def build_processing_group(owner) -> QGroupBox: owner._processing_mode.currentTextChanged.connect(owner._on_processing_mode_changed) owner._processing_gain_db.valueChanged.connect(owner._on_processing_live_settings_changed) owner._processing_phase_deg.valueChanged.connect(owner._on_processing_live_settings_changed) + owner._pass_through_channel.currentTextChanged.connect(owner._on_processing_live_settings_changed) owner._show_magnitude_checkbox.toggled.connect(owner._on_trace_visibility_changed) owner._show_phase_checkbox.toggled.connect(owner._on_trace_visibility_changed) owner._pass_through_fixed_y_enabled.toggled.connect(sync_pass_through_y_controls) diff --git a/python_app/gui/main.py b/python_app/gui/main.py index 5bc6bc9..4aec4c6 100644 --- a/python_app/gui/main.py +++ b/python_app/gui/main.py @@ -23,7 +23,7 @@ def main() -> int: apply_dark_theme(app) pg.setConfigOptions(antialias=True, foreground="#dbe4f1") window = AppWindow(PROJECT_ROOT) - window.show() + window.showMaximized() return app.exec() diff --git a/python_app/gui/runtime/history.py b/python_app/gui/runtime/history.py index e4790ae..1d7c25e 100644 --- a/python_app/gui/runtime/history.py +++ b/python_app/gui/runtime/history.py @@ -79,8 +79,8 @@ def build_run_history_signature( str(config.output_switch.driver), int(config.output_switch.positions), bool(config.output_switch.invert_logic), - str(config.preprocess.calibration_set), - str(config.preprocess.reference_set), + str(config.preprocess.s21_calibration_set), + str(config.preprocess.s21_reference_set), combos_signature, ) diff --git a/python_app/models/run_config_codec.py b/python_app/models/run_config_codec.py index c423d90..4d32564 100644 --- a/python_app/models/run_config_codec.py +++ b/python_app/models/run_config_codec.py @@ -65,13 +65,47 @@ def run_config_from_dict(payload: dict[str, Any]) -> RunConfigModel: run_payload.get("processing_live_config_path", model.runtime.processing_live_config_path) ) - model.preprocess.calibration_set = str(preprocess_payload.get("calibration_set", model.preprocess.calibration_set)) - model.preprocess.reference_set = str(preprocess_payload.get("reference_set", model.preprocess.reference_set)) - model.preprocess.calibration_bundle_path = str( - preprocess_payload.get("calibration_bundle_path", model.preprocess.calibration_bundle_path) + model.preprocess.s21_calibration_set = str( + preprocess_payload.get("s21_calibration_set", model.preprocess.s21_calibration_set) ) - model.preprocess.reference_bundle_path = str( - preprocess_payload.get("reference_bundle_path", model.preprocess.reference_bundle_path) + model.preprocess.s21_reference_set = str( + preprocess_payload.get("s21_reference_set", model.preprocess.s21_reference_set) + ) + model.preprocess.s21_calibration_bundle_path = str( + preprocess_payload.get( + "s21_calibration_bundle_path", + model.preprocess.s21_calibration_bundle_path, + ) + ) + model.preprocess.s21_reference_bundle_path = str( + preprocess_payload.get( + "s21_reference_bundle_path", + model.preprocess.s21_reference_bundle_path, + ) + ) + model.preprocess.s11_open_calibration_bundle_path = str( + preprocess_payload.get( + "s11_open_calibration_bundle_path", + model.preprocess.s11_open_calibration_bundle_path, + ) + ) + model.preprocess.s11_short_calibration_bundle_path = str( + preprocess_payload.get( + "s11_short_calibration_bundle_path", + model.preprocess.s11_short_calibration_bundle_path, + ) + ) + model.preprocess.s11_load_calibration_bundle_path = str( + preprocess_payload.get( + "s11_load_calibration_bundle_path", + model.preprocess.s11_load_calibration_bundle_path, + ) + ) + model.preprocess.s11_reference_bundle_path = str( + preprocess_payload.get( + "s11_reference_bundle_path", + model.preprocess.s11_reference_bundle_path, + ) ) model.gpr.mode = str(gpr_payload.get("mode", model.gpr.mode)) @@ -127,6 +161,7 @@ def run_config_from_dict(payload: dict[str, Any]) -> RunConfigModel: model.ensure_combos() return model + def run_config_to_dict(model: RunConfigModel) -> dict[str, Any]: """Encode :class:`RunConfigModel` to C++ pipeline-compatible JSON structure.""" model.ensure_combos() @@ -178,10 +213,14 @@ def run_config_to_dict(model: RunConfigModel) -> dict[str, Any]: "combos": [{"input": combo.input, "output": combo.output} for combo in model.combos], }, "preprocess": { - "calibration_set": model.preprocess.calibration_set, - "reference_set": model.preprocess.reference_set, - "calibration_bundle_path": model.preprocess.calibration_bundle_path, - "reference_bundle_path": model.preprocess.reference_bundle_path, + "s21_calibration_set": model.preprocess.s21_calibration_set, + "s21_reference_set": model.preprocess.s21_reference_set, + "s21_calibration_bundle_path": model.preprocess.s21_calibration_bundle_path, + "s21_reference_bundle_path": model.preprocess.s21_reference_bundle_path, + "s11_open_calibration_bundle_path": model.preprocess.s11_open_calibration_bundle_path, + "s11_short_calibration_bundle_path": model.preprocess.s11_short_calibration_bundle_path, + "s11_load_calibration_bundle_path": model.preprocess.s11_load_calibration_bundle_path, + "s11_reference_bundle_path": model.preprocess.s11_reference_bundle_path, }, "gpr": { "mode": model.gpr.mode, diff --git a/python_app/models/run_config_schema.py b/python_app/models/run_config_schema.py index 995bd62..89bb5ef 100644 --- a/python_app/models/run_config_schema.py +++ b/python_app/models/run_config_schema.py @@ -89,10 +89,14 @@ class RuntimeModel: class PreprocessModel: """Selected preprocessing artifacts for live acquisition.""" - calibration_set: str = "" - reference_set: str = "" - calibration_bundle_path: str = "" - reference_bundle_path: str = "" + s21_calibration_set: str = "" + s21_reference_set: str = "" + s21_calibration_bundle_path: str = "" + s21_reference_bundle_path: str = "" + s11_open_calibration_bundle_path: str = "" + s11_short_calibration_bundle_path: str = "" + s11_load_calibration_bundle_path: str = "" + s11_reference_bundle_path: str = "" @dataclass(slots=True) diff --git a/python_app/orchestration/config_writer.py b/python_app/orchestration/config_writer.py index adc5c3d..f356c93 100644 --- a/python_app/orchestration/config_writer.py +++ b/python_app/orchestration/config_writer.py @@ -17,19 +17,19 @@ class ConfigWriter: self._runtime_dir = runtime_dir self._runtime_dir.mkdir(parents=True, exist_ok=True) - def prepare_bundles( + def prepare_s21_bundles( self, store: NpzStore, radar_key: str, - calibration_set: str, - reference_set: str, + s21_calibration_set: str, + s21_reference_set: str, ) -> tuple[Path, Path]: """Export calibration/reference sets into binary bundles for preprocessor.""" - calibration_bundle = self._runtime_dir / "calibration_bundle.bin" - reference_bundle = self._runtime_dir / "reference_bundle.bin" + calibration_bundle = self._runtime_dir / "s21_calibration_bundle.bin" + reference_bundle = self._runtime_dir / "s21_reference_bundle.bin" - store.export_set_bundle("calibration", radar_key, calibration_set, calibration_bundle) - store.export_set_bundle("reference", radar_key, reference_set, reference_bundle) + store.export_set_bundle("calibration", radar_key, s21_calibration_set, calibration_bundle) + store.export_set_bundle("reference", radar_key, s21_reference_set, reference_bundle) return calibration_bundle, reference_bundle def write(self, config: RunConfigModel, output_path: Path) -> Path: diff --git a/python_app/orchestration/live_processing_config.py b/python_app/orchestration/live_processing_config.py index 1b67761..2ae98c2 100644 --- a/python_app/orchestration/live_processing_config.py +++ b/python_app/orchestration/live_processing_config.py @@ -14,6 +14,7 @@ class ProcessingLiveConfig: processor_mode: str = "pass_through" gain_db: float = 0.0 phase_deg: float = 0.0 + pass_through_channel: str = "s21" pass_through_fixed_y_enabled: bool = False pass_through_y_min_db: float = -100.0 pass_through_y_max_db: float = 0.0 @@ -52,6 +53,7 @@ class ProcessingLiveConfig: "processor_mode": str(self.processor_mode), "gain_db": float(self.gain_db), "phase_deg": float(self.phase_deg), + "pass_through_channel": str(self.pass_through_channel), "pass_through_fixed_y_enabled": bool(self.pass_through_fixed_y_enabled), "pass_through_y_min_db": float(self.pass_through_y_min_db), "pass_through_y_max_db": float(self.pass_through_y_max_db), diff --git a/python_app/runtime/run_config_smoke.json b/python_app/runtime/run_config_smoke.json index cb081c4..16410ba 100644 --- a/python_app/runtime/run_config_smoke.json +++ b/python_app/runtime/run_config_smoke.json @@ -79,10 +79,14 @@ ] }, "preprocess": { - "calibration_set": "smoke_cal", - "reference_set": "smoke_ref", - "calibration_bundle_path": "/home/europa/Documents/radar_system/python_app/runtime/calibration_bundle.bin", - "reference_bundle_path": "/home/europa/Documents/radar_system/python_app/runtime/reference_bundle.bin" + "s21_calibration_set": "smoke_cal", + "s21_reference_set": "smoke_ref", + "s21_calibration_bundle_path": "/home/europa/Documents/radar_system/python_app/runtime/s21_calibration_bundle.bin", + "s21_reference_bundle_path": "/home/europa/Documents/radar_system/python_app/runtime/s21_reference_bundle.bin", + "s11_open_calibration_bundle_path": "/home/europa/Documents/radar_system/data_acq_and_processing/preprocessing/testdata/s11_open_calibration_bundle.bin", + "s11_short_calibration_bundle_path": "/home/europa/Documents/radar_system/data_acq_and_processing/preprocessing/testdata/s11_short_calibration_bundle.bin", + "s11_load_calibration_bundle_path": "/home/europa/Documents/radar_system/data_acq_and_processing/preprocessing/testdata/s11_load_calibration_bundle.bin", + "s11_reference_bundle_path": "/home/europa/Documents/radar_system/data_acq_and_processing/preprocessing/testdata/s11_reference_bundle.bin" }, "gpr": { "mode": "point", diff --git a/python_app/scripts/manual_smoke_run.py b/python_app/scripts/manual_smoke_run.py index c6eb350..a9b1107 100644 --- a/python_app/scripts/manual_smoke_run.py +++ b/python_app/scripts/manual_smoke_run.py @@ -113,11 +113,16 @@ def main() -> int: store.save_set("calibration", radar_key, "smoke_cal", calibration_set) store.save_set("reference", radar_key, "smoke_ref", reference_set) - calibration_bundle, reference_bundle = config_writer.prepare_bundles(store, radar_key, "smoke_cal", "smoke_ref") - config.preprocess.calibration_set = "smoke_cal" - config.preprocess.reference_set = "smoke_ref" - config.preprocess.calibration_bundle_path = str(calibration_bundle) - config.preprocess.reference_bundle_path = str(reference_bundle) + calibration_bundle, reference_bundle = config_writer.prepare_s21_bundles( + store, + radar_key, + "smoke_cal", + "smoke_ref", + ) + config.preprocess.s21_calibration_set = "smoke_cal" + config.preprocess.s21_reference_set = "smoke_ref" + config.preprocess.s21_calibration_bundle_path = str(calibration_bundle) + config.preprocess.s21_reference_bundle_path = str(reference_bundle) config_path = config_writer.write(config, project_root / "python_app/runtime/run_config_smoke.json") diff --git a/run_config.json b/run_config.json index 5c95a31..0bf5510 100644 --- a/run_config.json +++ b/run_config.json @@ -55,10 +55,14 @@ ] }, "preprocess": { - "calibration_set": "", - "reference_set": "", - "calibration_bundle_path": "python_app/runtime/calibration_bundle.bin", - "reference_bundle_path": "python_app/runtime/reference_bundle.bin" + "s21_calibration_set": "", + "s21_reference_set": "", + "s21_calibration_bundle_path": "python_app/runtime/s21_calibration_bundle.bin", + "s21_reference_bundle_path": "python_app/runtime/s21_reference_bundle.bin", + "s11_open_calibration_bundle_path": "", + "s11_short_calibration_bundle_path": "", + "s11_load_calibration_bundle_path": "", + "s11_reference_bundle_path": "" }, "gpr": { "mode": "point",