added white theme, some features to processing and saving
This commit is contained in:
@@ -94,10 +94,23 @@ struct S11PreprocessConfig {
|
||||
PreprocessAssetConfig reference{};
|
||||
};
|
||||
|
||||
struct PreprocessNotchBandConfig {
|
||||
float low_hz = 0.0F;
|
||||
float high_hz = 0.0F;
|
||||
};
|
||||
|
||||
struct PreprocessNotchConfig {
|
||||
bool enabled = false;
|
||||
std::vector<PreprocessNotchBandConfig> bands_hz{};
|
||||
float taper_width_hz = 40'000'000.0F;
|
||||
std::string taper_type = "cosine";
|
||||
};
|
||||
|
||||
struct PreprocessConfig {
|
||||
// Channel-specific preprocessing assets selected by Python GUI layer.
|
||||
S21PreprocessConfig s21{};
|
||||
S11PreprocessConfig s11{};
|
||||
PreprocessNotchConfig notch{};
|
||||
};
|
||||
|
||||
struct GprTxGeometry {
|
||||
|
||||
@@ -154,6 +154,46 @@ using Json = nlohmann::json;
|
||||
return asset;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto parse_preprocess_notch(
|
||||
const Json& object,
|
||||
const std::string& context
|
||||
) -> PreprocessNotchConfig {
|
||||
PreprocessNotchConfig notch{};
|
||||
notch.enabled = optional_bool(object, "enabled", false);
|
||||
notch.taper_width_hz = optional_f32(object, "taper_width_hz", 40'000'000.0F);
|
||||
notch.taper_type = optional_string(object, "taper_type", "cosine");
|
||||
|
||||
if (const auto* bands_value = optional_field(object, "bands_hz"); bands_value != nullptr) {
|
||||
const auto* bands_array = as_array(*bands_value, context + ".bands_hz");
|
||||
notch.bands_hz.reserve(bands_array->size());
|
||||
for (const auto& band_value : *bands_array) {
|
||||
const auto* band_array = as_array(band_value, context + ".bands_hz[]");
|
||||
if (band_array->size() != 2U) {
|
||||
throw std::runtime_error(context + ".bands_hz[] must contain exactly two numbers");
|
||||
}
|
||||
|
||||
PreprocessNotchBandConfig band{};
|
||||
band.low_hz = static_cast<float>(as_number((*band_array)[0], context + ".bands_hz[][0]"));
|
||||
band.high_hz = static_cast<float>(as_number((*band_array)[1], context + ".bands_hz[][1]"));
|
||||
notch.bands_hz.push_back(band);
|
||||
}
|
||||
}
|
||||
|
||||
if (notch.taper_width_hz < 0.0F) {
|
||||
throw std::runtime_error(context + ".taper_width_hz must be >= 0");
|
||||
}
|
||||
if (notch.taper_type != "cosine" && notch.taper_type != "hard") {
|
||||
throw std::runtime_error(context + ".taper_type must be 'cosine' or 'hard'");
|
||||
}
|
||||
for (const auto& band : notch.bands_hz) {
|
||||
if (band.high_hz < band.low_hz) {
|
||||
throw std::runtime_error(context + ".bands_hz[] high_hz must be >= low_hz");
|
||||
}
|
||||
}
|
||||
|
||||
return notch;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto parse_driver_mode(const std::string& value) -> DriverMode {
|
||||
if (value == "mock") {
|
||||
return DriverMode::Mock;
|
||||
@@ -466,6 +506,10 @@ auto load_run_config(const std::string& path) -> RunConfig {
|
||||
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* notch_value = optional_field(*preprocess_obj, "notch"); notch_value != nullptr) {
|
||||
config.preprocess.notch = parse_preprocess_notch(*as_object(*notch_value, "preprocess.notch"), "preprocess.notch");
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto* gpr_value = optional_field(*root_obj, "gpr"); gpr_value != nullptr) {
|
||||
|
||||
@@ -1,11 +1,94 @@
|
||||
#include "data_preprocessor.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace radar::preprocessing {
|
||||
namespace {
|
||||
|
||||
constexpr double kPi = 3.14159265358979323846;
|
||||
|
||||
[[nodiscard]] auto should_apply_notch(const config::PreprocessNotchConfig& notch) -> bool {
|
||||
return notch.enabled && !notch.bands_hz.empty();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto build_notch_mask(
|
||||
std::span<const float> frequency_hz,
|
||||
const config::PreprocessNotchConfig& notch
|
||||
) -> std::vector<float> {
|
||||
std::vector<float> mask(frequency_hz.size(), 1.0F);
|
||||
if (!should_apply_notch(notch)) {
|
||||
return mask;
|
||||
}
|
||||
|
||||
const bool hard_taper = notch.taper_type == "hard";
|
||||
const double taper_width_hz = std::max(0.0, static_cast<double>(notch.taper_width_hz));
|
||||
for (const auto& band : notch.bands_hz) {
|
||||
const double low_hz = static_cast<double>(band.low_hz);
|
||||
const double high_hz = static_cast<double>(band.high_hz);
|
||||
const double trans_low_hz = low_hz - taper_width_hz;
|
||||
const double trans_high_hz = high_hz + taper_width_hz;
|
||||
|
||||
for (std::size_t index = 0U; index < frequency_hz.size(); ++index) {
|
||||
const double frequency = static_cast<double>(frequency_hz[index]);
|
||||
if (frequency >= low_hz && frequency <= high_hz) {
|
||||
mask[index] = 0.0F;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hard_taper || taper_width_hz <= 0.0) {
|
||||
if (frequency >= trans_low_hz && frequency <= trans_high_hz) {
|
||||
mask[index] = 0.0F;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (frequency >= trans_low_hz && frequency < low_hz) {
|
||||
const double arg = kPi * (frequency - trans_low_hz) / taper_width_hz;
|
||||
mask[index] *= static_cast<float>(0.5 * (1.0 - std::cos(arg)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (frequency > high_hz && frequency <= trans_high_hz) {
|
||||
const double arg = kPi * (frequency - high_hz) / taper_width_hz;
|
||||
mask[index] *= static_cast<float>(0.5 * (1.0 + std::cos(arg)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
void apply_notch_mask(std::vector<ipc::Complex32>& samples, std::span<const float> mask) {
|
||||
const std::size_t point_count = std::min(samples.size(), mask.size());
|
||||
for (std::size_t index = 0U; index < point_count; ++index) {
|
||||
samples[index].re *= mask[index];
|
||||
samples[index].im *= mask[index];
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto apply_notch_filter(
|
||||
const ipc::SweepTraceBlock& trace,
|
||||
const config::PreprocessNotchConfig& notch
|
||||
) -> ipc::SweepTraceBlock {
|
||||
if (!should_apply_notch(notch)) {
|
||||
return trace;
|
||||
}
|
||||
|
||||
ipc::SweepTraceBlock filtered = trace;
|
||||
const auto mask = build_notch_mask(filtered.frequency_hz, notch);
|
||||
apply_notch_mask(filtered.s21, mask);
|
||||
apply_notch_mask(filtered.s11, mask);
|
||||
return filtered;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DataPreprocessor::DataPreprocessor(
|
||||
const config::RunConfig& config,
|
||||
@@ -78,7 +161,7 @@ auto DataPreprocessor::preprocess_collection(const ipc::RawSweepCollection& raw_
|
||||
// Pipeline order is fixed: calibration first, then reference subtraction.
|
||||
const auto calibrated = calibration_master_.apply_to_trace(raw_trace);
|
||||
const auto referenced = reference_master_.apply_to_trace(calibrated);
|
||||
preprocessed.traces.push_back(referenced);
|
||||
preprocessed.traces.push_back(apply_notch_filter(referenced, config_.preprocess.notch));
|
||||
}
|
||||
|
||||
return preprocessed;
|
||||
|
||||
Reference in New Issue
Block a user