added s11!

This commit is contained in:
Ayzen
2026-03-26 18:29:42 +03:00
parent 9ddbde22bd
commit 077542cbd0
43 changed files with 713 additions and 347 deletions
@@ -73,16 +73,31 @@ struct RuntimeConfig {
std::string processing_live_config_path = "python_app/runtime/processing_live.json";
};
struct PreprocessAssetConfig {
std::string set_name{};
std::string bundle_path{};
};
struct S21PreprocessConfig {
PreprocessAssetConfig calibration{};
PreprocessAssetConfig reference{};
};
struct S11CalibrationPreprocessConfig {
PreprocessAssetConfig open{};
PreprocessAssetConfig short_standard{};
PreprocessAssetConfig load{};
};
struct S11PreprocessConfig {
S11CalibrationPreprocessConfig calibration{};
PreprocessAssetConfig reference{};
};
struct PreprocessConfig {
// Names and bundle paths selected by Python GUI layer.
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{};
// Channel-specific preprocessing assets selected by Python GUI layer.
S21PreprocessConfig s21{};
S11PreprocessConfig s11{};
};
struct GprTxGeometry {
@@ -140,6 +140,20 @@ using Json = nlohmann::json;
return fallback;
}
[[nodiscard]] auto parse_preprocess_asset(
const Json& object,
const std::string& key,
const std::string& context
) -> PreprocessAssetConfig {
const auto asset_context = context + "." + key;
const auto* asset_obj = as_object(required_field(object, key), asset_context);
PreprocessAssetConfig asset{};
asset.set_name = optional_string(*asset_obj, "set_name", "");
asset.bundle_path = optional_string(*asset_obj, "bundle_path", "");
return asset;
}
[[nodiscard]] auto parse_driver_mode(const std::string& value) -> DriverMode {
if (value == "mock") {
return DriverMode::Mock;
@@ -438,18 +452,20 @@ auto load_run_config(const std::string& path) -> RunConfig {
{
const auto* preprocess_obj = as_object(required_field(*root_obj, "preprocess"), "preprocess");
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", "");
const auto* s21_obj = as_object(required_field(*preprocess_obj, "s21"), "preprocess.s21");
config.preprocess.s21.calibration = parse_preprocess_asset(*s21_obj, "calibration", "preprocess.s21");
config.preprocess.s21.reference = parse_preprocess_asset(*s21_obj, "reference", "preprocess.s21");
const auto* s11_obj = as_object(required_field(*preprocess_obj, "s11"), "preprocess.s11");
const auto* s11_calibration_obj =
as_object(required_field(*s11_obj, "calibration"), "preprocess.s11.calibration");
config.preprocess.s11.calibration.open =
parse_preprocess_asset(*s11_calibration_obj, "open", "preprocess.s11.calibration");
config.preprocess.s11.calibration.short_standard =
parse_preprocess_asset(*s11_calibration_obj, "short", "preprocess.s11.calibration");
config.preprocess.s11.calibration.load =
parse_preprocess_asset(*s11_calibration_obj, "load", "preprocess.s11.calibration");
config.preprocess.s11.reference = parse_preprocess_asset(*s11_obj, "reference", "preprocess.s11");
}
if (const auto* gpr_value = optional_field(*root_obj, "gpr"); gpr_value != nullptr) {
@@ -65,16 +65,16 @@ int main(int argc, char** argv) {
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_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
config.preprocess.s11.calibration.open.bundle_path,
config.preprocess.s11.calibration.short_standard.bundle_path,
config.preprocess.s11.calibration.load.bundle_path
);
radar::preprocessing::ReferenceMaster reference_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.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(
@@ -23,6 +23,7 @@ struct ProcessingLiveConfig {
float pass_through_y_min_db = -100.0F;
float pass_through_y_max_db = 0.0F;
std::string bscan_axis = "abs";
std::string bscan_channel = "s21";
float bscan_cut_m = 0.824F;
float bscan_max_depth_m = 1.0F;
float bscan_gain = 1.0F;
@@ -30,11 +30,11 @@ 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 {
[[nodiscard]] auto parse_s_parameter_channel(const std::string& value, const std::string& field_name) -> std::string {
if (value == "s21" || value == "s11") {
return value;
}
throw std::runtime_error("processing.pass_through_channel must be one of: s21, s11");
throw std::runtime_error(field_name + " must be one of: s21, s11");
}
[[nodiscard]] auto parse_u64_number(const Json& value, const std::string& field_name) -> std::uint64_t {
@@ -112,7 +112,8 @@ using Json = nlohmann::json;
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<std::string>());
config.pass_through_channel =
parse_s_parameter_channel(found->get<std::string>(), "processing.pass_through_channel");
}
if (const auto found = root.find("pass_through_fixed_y_enabled"); found != root.end()) {
if (!found->is_boolean()) {
@@ -138,6 +139,12 @@ using Json = nlohmann::json;
}
config.bscan_axis = found->get<std::string>();
}
if (const auto found = root.find("bscan_channel"); found != root.end()) {
if (!found->is_string()) {
throw std::runtime_error("processing.bscan_channel must be string");
}
config.bscan_channel = parse_s_parameter_channel(found->get<std::string>(), "processing.bscan_channel");
}
if (const auto found = root.find("bscan_cut_m"); found != root.end()) {
if (!found->is_number()) {
throw std::runtime_error("processing.bscan_cut_m must be number");
@@ -6,6 +6,7 @@
#include <cstddef>
#include <cstdint>
#include <limits>
#include <span>
#include <string_view>
#include <utility>
#include <vector>
@@ -92,15 +93,28 @@ void fft_inplace(std::vector<std::complex<double>>& values, bool inverse) {
return std::abs(sample);
}
[[nodiscard]] auto fallback_profile(const ipc::SweepTraceBlock& trace) -> BScanProfile {
const std::size_t point_count = std::min(trace.frequency_hz.size(), trace.s21.size());
[[nodiscard]] auto selected_trace_samples(
const ipc::SweepTraceBlock& trace,
const ProcessingLiveConfig& live_config
) -> std::span<const ipc::Complex32> {
if (live_config.bscan_channel == "s11") {
return trace.s11;
}
return trace.s21;
}
[[nodiscard]] auto fallback_profile(
const ipc::SweepTraceBlock& trace,
std::span<const ipc::Complex32> selected_samples
) -> BScanProfile {
const std::size_t point_count = std::min(trace.frequency_hz.size(), selected_samples.size());
BScanProfile fallback{};
fallback.depth_m.reserve(point_count);
fallback.response.reserve(point_count);
const float denominator = point_count > 1U ? static_cast<float>(point_count - 1U) : 1.0F;
for (std::size_t index = 0U; index < point_count; ++index) {
const auto& sample = trace.s21[index];
const auto& sample = selected_samples[index];
fallback.depth_m.push_back(static_cast<float>(static_cast<float>(index) / denominator));
fallback.response.push_back(std::sqrt(sample.re * sample.re + sample.im * sample.im));
}
@@ -111,9 +125,10 @@ void fft_inplace(std::vector<std::complex<double>>& values, bool inverse) {
const ipc::SweepTraceBlock& trace,
const ProcessingLiveConfig& live_config
) -> BScanProfile {
const std::size_t point_count = std::min(trace.frequency_hz.size(), trace.s21.size());
const auto selected_samples = selected_trace_samples(trace, live_config);
const std::size_t point_count = std::min(trace.frequency_hz.size(), selected_samples.size());
if (point_count < 2U) {
return fallback_profile(trace);
return fallback_profile(trace, selected_samples);
}
const double configured_start_hz = static_cast<double>(live_config.bscan_start_freq_mhz) * 1'000'000.0;
@@ -122,55 +137,55 @@ void fft_inplace(std::vector<std::complex<double>>& values, bool inverse) {
const double stop_hz = std::max(configured_start_hz, configured_stop_hz);
std::vector<double> filtered_freq_hz{};
std::vector<std::complex<double>> filtered_s21{};
std::vector<std::complex<double>> filtered_samples{};
filtered_freq_hz.reserve(point_count);
filtered_s21.reserve(point_count);
filtered_samples.reserve(point_count);
for (std::size_t index = 0U; index < point_count; ++index) {
const double frequency_hz = static_cast<double>(trace.frequency_hz[index]);
if (frequency_hz < start_hz || frequency_hz > stop_hz) {
continue;
}
const auto& sample = trace.s21[index];
const auto& sample = selected_samples[index];
filtered_freq_hz.push_back(frequency_hz);
filtered_s21.emplace_back(static_cast<double>(sample.re), static_cast<double>(sample.im));
filtered_samples.emplace_back(static_cast<double>(sample.re), static_cast<double>(sample.im));
}
if (filtered_freq_hz.size() < 2U) {
return fallback_profile(trace);
return fallback_profile(trace, selected_samples);
}
const std::size_t filtered_count = filtered_freq_hz.size();
const double df = (filtered_freq_hz.back() - filtered_freq_hz.front()) / static_cast<double>(filtered_count - 1U);
if (df <= 0.0) {
return fallback_profile(trace);
return fallback_profile(trace, selected_samples);
}
const auto start_bin = static_cast<std::int64_t>(std::llround(filtered_freq_hz.front() / df));
if (start_bin < 0) {
return fallback_profile(trace);
return fallback_profile(trace, selected_samples);
}
const auto start_index = static_cast<std::size_t>(start_bin);
if (start_index > (std::numeric_limits<std::size_t>::max() / 2U)) {
return fallback_profile(trace);
return fallback_profile(trace, selected_samples);
}
if (start_index > (std::numeric_limits<std::size_t>::max() - filtered_count + 1U)) {
return fallback_profile(trace);
return fallback_profile(trace, selected_samples);
}
const std::size_t min_fft_len = 2U * (start_index + filtered_count - 1U);
const std::size_t fft_len = next_power_of_two(min_fft_len);
if (fft_len < min_fft_len || (fft_len & (fft_len - 1U)) != 0U) {
return fallback_profile(trace);
return fallback_profile(trace, selected_samples);
}
if (start_index > fft_len || filtered_count > (fft_len - start_index)) {
return fallback_profile(trace);
return fallback_profile(trace, selected_samples);
}
std::vector<std::complex<double>> spectrum(fft_len, std::complex<double>(0.0, 0.0));
for (std::size_t index = 0U; index < filtered_count; ++index) {
spectrum[start_index + index] = filtered_s21[index];
spectrum[start_index + index] = filtered_samples[index];
}
fft_inplace(spectrum, true);