added GPR
This commit is contained in:
@@ -81,6 +81,26 @@ struct PreprocessConfig {
|
||||
std::string reference_bundle_path{};
|
||||
};
|
||||
|
||||
struct GprTxGeometry {
|
||||
// Transmitter geometry keyed by output switch position.
|
||||
std::uint32_t output_pos = 0;
|
||||
float x_m = 0.0F;
|
||||
};
|
||||
|
||||
struct GprRxGeometry {
|
||||
// Receiver geometry keyed by input switch position.
|
||||
std::uint32_t input_pos = 0;
|
||||
float x_m = 0.0F;
|
||||
};
|
||||
|
||||
struct GprConfig {
|
||||
// Stable collection-level GPR processing settings.
|
||||
std::string mode = "point";
|
||||
float relative_permittivity = 1.0F;
|
||||
std::vector<GprTxGeometry> tx_geometry{};
|
||||
std::vector<GprRxGeometry> rx_geometry{};
|
||||
};
|
||||
|
||||
struct RunConfig {
|
||||
// Single configuration object shared by all C++ processes.
|
||||
RadarConfig radar{};
|
||||
@@ -89,6 +109,7 @@ struct RunConfig {
|
||||
RingsConfig rings{};
|
||||
RuntimeConfig runtime{};
|
||||
PreprocessConfig preprocess{};
|
||||
GprConfig gpr{};
|
||||
std::vector<radar::ipc::ComboKey> run_combos{};
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
@@ -214,6 +216,69 @@ void validate_combo_key(const ipc::ComboKey& key, const RunConfig& config) {
|
||||
}
|
||||
}
|
||||
|
||||
void validate_gpr_config(const GprConfig& config, const RunConfig& run_config) {
|
||||
if (config.mode != "point" && config.mode != "extended") {
|
||||
throw std::runtime_error("gpr.mode must be either 'point' or 'extended'");
|
||||
}
|
||||
if (!(config.relative_permittivity > 0.0F)) {
|
||||
throw std::runtime_error("gpr.relative_permittivity must be > 0");
|
||||
}
|
||||
|
||||
std::unordered_set<std::uint32_t> seen_tx_positions{};
|
||||
for (const auto& entry : config.tx_geometry) {
|
||||
if (entry.output_pos >= run_config.output_switch.positions) {
|
||||
throw std::runtime_error("gpr.tx_geometry output_pos is out of range");
|
||||
}
|
||||
if (!seen_tx_positions.insert(entry.output_pos).second) {
|
||||
throw std::runtime_error("gpr.tx_geometry contains duplicate output_pos");
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<std::uint32_t> seen_rx_positions{};
|
||||
for (const auto& entry : config.rx_geometry) {
|
||||
if (entry.input_pos >= run_config.input_switch.positions) {
|
||||
throw std::runtime_error("gpr.rx_geometry input_pos is out of range");
|
||||
}
|
||||
if (!seen_rx_positions.insert(entry.input_pos).second) {
|
||||
throw std::runtime_error("gpr.rx_geometry contains duplicate input_pos");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto parse_gpr_config(const Json& object, const RunConfig& run_config) -> GprConfig {
|
||||
const auto* gpr_obj = as_object(object, "gpr");
|
||||
GprConfig config{};
|
||||
config.mode = optional_string(*gpr_obj, "mode", "point");
|
||||
config.relative_permittivity = optional_f32(*gpr_obj, "relative_permittivity", 1.0F);
|
||||
|
||||
if (const auto* tx_value = optional_field(*gpr_obj, "tx_geometry"); tx_value != nullptr) {
|
||||
const auto* tx_array = as_array(*tx_value, "gpr.tx_geometry");
|
||||
config.tx_geometry.reserve(tx_array->size());
|
||||
for (const auto& entry_value : *tx_array) {
|
||||
const auto* entry_obj = as_object(entry_value, "gpr.tx_geometry[]");
|
||||
GprTxGeometry entry{};
|
||||
entry.output_pos = optional_u32(*entry_obj, "output_pos", 0U);
|
||||
entry.x_m = optional_f32(*entry_obj, "x_m", 0.0F);
|
||||
config.tx_geometry.push_back(std::move(entry));
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto* rx_value = optional_field(*gpr_obj, "rx_geometry"); rx_value != nullptr) {
|
||||
const auto* rx_array = as_array(*rx_value, "gpr.rx_geometry");
|
||||
config.rx_geometry.reserve(rx_array->size());
|
||||
for (const auto& entry_value : *rx_array) {
|
||||
const auto* entry_obj = as_object(entry_value, "gpr.rx_geometry[]");
|
||||
GprRxGeometry entry{};
|
||||
entry.input_pos = optional_u32(*entry_obj, "input_pos", 0U);
|
||||
entry.x_m = optional_f32(*entry_obj, "x_m", 0.0F);
|
||||
config.rx_geometry.push_back(std::move(entry));
|
||||
}
|
||||
}
|
||||
|
||||
validate_gpr_config(config, run_config);
|
||||
return config;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto parse_switch_config(
|
||||
const Json& object,
|
||||
const std::string& default_name,
|
||||
@@ -379,6 +444,12 @@ auto load_run_config(const std::string& path) -> RunConfig {
|
||||
config.preprocess.reference_bundle_path = optional_string(*preprocess_obj, "reference_bundle_path", "");
|
||||
}
|
||||
|
||||
if (const auto* gpr_value = optional_field(*root_obj, "gpr"); gpr_value != nullptr) {
|
||||
config.gpr = parse_gpr_config(*gpr_value, config);
|
||||
} else {
|
||||
validate_gpr_config(config.gpr, config);
|
||||
}
|
||||
|
||||
{
|
||||
const auto* rings_obj = as_object(required_field(*root_obj, "rings"), "rings");
|
||||
config.rings.raw = parse_ring_endpoint(*rings_obj, "raw", config.rings.raw);
|
||||
|
||||
@@ -49,6 +49,8 @@ using PreprocessedCollection = RawSweepCollection;
|
||||
enum class ResultKind : std::uint8_t {
|
||||
TraceComplex = 1,
|
||||
ScalarF32 = 2,
|
||||
ImageF32 = 3,
|
||||
TableF32 = 4,
|
||||
};
|
||||
|
||||
struct ResultPayload {
|
||||
@@ -60,6 +62,13 @@ struct ResultPayload {
|
||||
std::vector<Complex32> trace{};
|
||||
// Valid for ScalarF32 payloads.
|
||||
float scalar_value = 0.0F;
|
||||
// Valid for ImageF32 payloads.
|
||||
std::vector<float> image_x_axis{};
|
||||
std::vector<float> image_y_axis{};
|
||||
std::vector<float> image_values{};
|
||||
// Valid for TableF32 payloads.
|
||||
std::uint32_t table_columns = 0;
|
||||
std::vector<float> table_values{};
|
||||
};
|
||||
|
||||
struct ResultBlock {
|
||||
@@ -70,6 +79,7 @@ struct ResultBlock {
|
||||
struct ResultCollection {
|
||||
std::uint64_t collection_id = 0;
|
||||
std::uint64_t monotonic_ns = 0;
|
||||
std::vector<ResultPayload> collection_payloads{};
|
||||
std::vector<ResultBlock> blocks{};
|
||||
};
|
||||
|
||||
|
||||
@@ -200,6 +200,75 @@ auto read_trace_result_payload(BinaryReader& reader, ResultPayload* payload) ->
|
||||
}
|
||||
}
|
||||
|
||||
void write_image_result_payload(BinaryWriter& writer, const ResultPayload& payload) {
|
||||
const auto x_count = checked_count_to_u32(payload.image_x_axis.size(), "Result image x axis point count");
|
||||
const auto y_count = checked_count_to_u32(payload.image_y_axis.size(), "Result image y axis point count");
|
||||
const std::size_t expected_value_count = static_cast<std::size_t>(x_count) * static_cast<std::size_t>(y_count);
|
||||
if (payload.image_values.size() != expected_value_count) {
|
||||
throw std::runtime_error("Result image payload has inconsistent axis/value sizes");
|
||||
}
|
||||
|
||||
writer.write(x_count);
|
||||
writer.write(y_count);
|
||||
for (const auto value : payload.image_x_axis) {
|
||||
writer.write(value);
|
||||
}
|
||||
for (const auto value : payload.image_y_axis) {
|
||||
writer.write(value);
|
||||
}
|
||||
for (const auto value : payload.image_values) {
|
||||
writer.write(value);
|
||||
}
|
||||
}
|
||||
|
||||
auto read_image_result_payload(BinaryReader& reader, ResultPayload* payload) -> void {
|
||||
const auto x_count = reader.read<std::uint32_t>();
|
||||
const auto y_count = reader.read<std::uint32_t>();
|
||||
|
||||
payload->image_x_axis.reserve(x_count);
|
||||
payload->image_y_axis.reserve(y_count);
|
||||
payload->image_values.reserve(static_cast<std::size_t>(x_count) * static_cast<std::size_t>(y_count));
|
||||
|
||||
for (std::uint32_t index = 0; index < x_count; ++index) {
|
||||
payload->image_x_axis.push_back(reader.read<float>());
|
||||
}
|
||||
for (std::uint32_t index = 0; index < y_count; ++index) {
|
||||
payload->image_y_axis.push_back(reader.read<float>());
|
||||
}
|
||||
for (std::size_t index = 0; index < static_cast<std::size_t>(x_count) * static_cast<std::size_t>(y_count); ++index) {
|
||||
payload->image_values.push_back(reader.read<float>());
|
||||
}
|
||||
}
|
||||
|
||||
void write_table_result_payload(BinaryWriter& writer, const ResultPayload& payload) {
|
||||
const auto column_count = payload.table_columns;
|
||||
if (column_count == 0U && !payload.table_values.empty()) {
|
||||
throw std::runtime_error("Result table payload must declare table_columns when values are present");
|
||||
}
|
||||
if (column_count != 0U && (payload.table_values.size() % column_count) != 0U) {
|
||||
throw std::runtime_error("Result table payload has inconsistent column/value sizes");
|
||||
}
|
||||
|
||||
const std::uint32_t row_count =
|
||||
column_count == 0U ? 0U : checked_count_to_u32(payload.table_values.size() / column_count, "Result table row count");
|
||||
|
||||
writer.write(column_count);
|
||||
writer.write(row_count);
|
||||
for (const auto value : payload.table_values) {
|
||||
writer.write(value);
|
||||
}
|
||||
}
|
||||
|
||||
auto read_table_result_payload(BinaryReader& reader, ResultPayload* payload) -> void {
|
||||
const auto column_count = reader.read<std::uint32_t>();
|
||||
const auto row_count = reader.read<std::uint32_t>();
|
||||
payload->table_columns = column_count;
|
||||
payload->table_values.reserve(static_cast<std::size_t>(column_count) * static_cast<std::size_t>(row_count));
|
||||
for (std::size_t index = 0; index < static_cast<std::size_t>(column_count) * static_cast<std::size_t>(row_count); ++index) {
|
||||
payload->table_values.push_back(reader.read<float>());
|
||||
}
|
||||
}
|
||||
|
||||
void write_result_payload(BinaryWriter& writer, const ResultPayload& payload) {
|
||||
writer.write(static_cast<std::uint8_t>(payload.kind));
|
||||
writer.write_string(payload.processing_name);
|
||||
@@ -211,6 +280,12 @@ void write_result_payload(BinaryWriter& writer, const ResultPayload& payload) {
|
||||
case ResultKind::ScalarF32:
|
||||
writer.write(payload.scalar_value);
|
||||
return;
|
||||
case ResultKind::ImageF32:
|
||||
write_image_result_payload(writer, payload);
|
||||
return;
|
||||
case ResultKind::TableF32:
|
||||
write_table_result_payload(writer, payload);
|
||||
return;
|
||||
default:
|
||||
throw std::runtime_error("Unsupported result payload kind");
|
||||
}
|
||||
@@ -228,6 +303,12 @@ void write_result_payload(BinaryWriter& writer, const ResultPayload& payload) {
|
||||
case ResultKind::ScalarF32:
|
||||
payload.scalar_value = reader.read<float>();
|
||||
return payload;
|
||||
case ResultKind::ImageF32:
|
||||
read_image_result_payload(reader, &payload);
|
||||
return payload;
|
||||
case ResultKind::TableF32:
|
||||
read_table_result_payload(reader, &payload);
|
||||
return payload;
|
||||
default:
|
||||
throw std::runtime_error("Unsupported result payload kind in stream");
|
||||
}
|
||||
@@ -296,8 +377,12 @@ auto serialize_result_collection(const ResultCollection& collection) -> std::vec
|
||||
writer.write(kResultCollectionMagic);
|
||||
writer.write(collection.collection_id);
|
||||
writer.write(collection.monotonic_ns);
|
||||
writer.write(checked_count_to_u32(collection.collection_payloads.size(), "Collection payload count"));
|
||||
writer.write(checked_count_to_u32(collection.blocks.size(), "Result block count"));
|
||||
|
||||
for (const auto& payload : collection.collection_payloads) {
|
||||
write_result_payload(writer, payload);
|
||||
}
|
||||
for (const auto& block : collection.blocks) {
|
||||
write_result_block(writer, block);
|
||||
}
|
||||
@@ -317,7 +402,13 @@ auto deserialize_result_collection(std::span<const std::uint8_t> bytes) -> Resul
|
||||
collection.collection_id = reader.read<std::uint64_t>();
|
||||
collection.monotonic_ns = reader.read<std::uint64_t>();
|
||||
|
||||
const auto collection_payload_count = reader.read<std::uint32_t>();
|
||||
const auto block_count = reader.read<std::uint32_t>();
|
||||
collection.collection_payloads.reserve(collection_payload_count);
|
||||
for (std::uint32_t index = 0; index < collection_payload_count; ++index) {
|
||||
collection.collection_payloads.push_back(read_result_payload(reader));
|
||||
}
|
||||
|
||||
collection.blocks.reserve(block_count);
|
||||
for (std::uint32_t index = 0; index < block_count; ++index) {
|
||||
collection.blocks.push_back(read_result_block(reader));
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -29,11 +30,13 @@ class DataProcessor {
|
||||
private:
|
||||
[[nodiscard]] auto process_collection(
|
||||
const ipc::PreprocessedCollection& preprocessed,
|
||||
std::span<const ipc::PreprocessedCollection> previous_collections,
|
||||
ProcessorInterface& processor,
|
||||
const ProcessingLiveConfig& live_config
|
||||
) -> ipc::ResultCollection;
|
||||
|
||||
[[nodiscard]] auto resolve_processor(const ProcessingLiveConfig& live_config) -> ProcessorInterface&;
|
||||
[[nodiscard]] auto should_replay_entire_history(const ProcessingLiveConfig& live_config) const -> bool;
|
||||
|
||||
const config::RunConfig& config_;
|
||||
ipc::ShmRing& preprocessed_ring_;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace radar::processing {
|
||||
|
||||
@@ -17,12 +18,24 @@ struct ProcessingLiveConfig {
|
||||
std::string processor_mode = "pass_through";
|
||||
float gain_db = 0.0F;
|
||||
float phase_deg = 0.0F;
|
||||
bool pass_through_fixed_y_enabled = false;
|
||||
float pass_through_y_min_db = -100.0F;
|
||||
float pass_through_y_max_db = 0.0F;
|
||||
std::string bscan_axis = "abs";
|
||||
float bscan_cut_m = 0.824F;
|
||||
float bscan_max_depth_m = 1.0F;
|
||||
float bscan_gain = 1.0F;
|
||||
float bscan_start_freq_mhz = 100.0F;
|
||||
float bscan_stop_freq_mhz = 8800.0F;
|
||||
std::vector<std::uint32_t> gpr_input_positions{};
|
||||
std::vector<std::uint32_t> gpr_output_positions{};
|
||||
float gpr_min_depth_m = 2.0F;
|
||||
float gpr_max_depth_m = 14.0F;
|
||||
float gpr_comp_power = 0.2F;
|
||||
float gpr_start_freq_mhz = 3000.0F;
|
||||
float gpr_stop_freq_mhz = 6000.0F;
|
||||
bool gpr_background_subtract_enabled = true;
|
||||
std::uint32_t gpr_background_mean_count = 10U;
|
||||
std::uint64_t history_command_seq = 0;
|
||||
HistoryCommand history_command = HistoryCommand::None;
|
||||
};
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "bscan_processor.hpp"
|
||||
#include "gpr_processor.hpp"
|
||||
#include "passthrough_processor.hpp"
|
||||
|
||||
namespace radar::processing {
|
||||
@@ -54,7 +55,7 @@ DataProcessor::DataProcessor(
|
||||
|
||||
void DataProcessor::run(const std::atomic<bool>& stop_requested) {
|
||||
std::vector<std::uint8_t> bytes{};
|
||||
std::deque<ipc::PreprocessedCollection> preprocessed_history{};
|
||||
std::vector<ipc::PreprocessedCollection> preprocessed_history{};
|
||||
const std::size_t history_limit = replay_history_limit(config_);
|
||||
std::uint64_t last_replayed_revision = live_config_loader_.revision();
|
||||
std::uint64_t last_applied_history_command_seq = 0;
|
||||
@@ -76,13 +77,23 @@ void DataProcessor::run(const std::atomic<bool>& stop_requested) {
|
||||
last_applied_history_command_seq = live_config.history_command_seq;
|
||||
}
|
||||
|
||||
if (live_config.processor_mode == "bscan") {
|
||||
for (const auto& cached : preprocessed_history) {
|
||||
const auto replay_result = process_collection(cached, processor, live_config);
|
||||
if (should_replay_entire_history(live_config)) {
|
||||
for (std::size_t index = 0; index < preprocessed_history.size(); ++index) {
|
||||
const auto replay_result = process_collection(
|
||||
preprocessed_history[index],
|
||||
std::span<const ipc::PreprocessedCollection>(preprocessed_history.data(), index),
|
||||
processor,
|
||||
live_config
|
||||
);
|
||||
publish_result_collection(replay_result, results_ring_);
|
||||
}
|
||||
} else if (!preprocessed_history.empty()) {
|
||||
const auto replay_result = process_collection(preprocessed_history.back(), processor, live_config);
|
||||
const auto replay_result = process_collection(
|
||||
preprocessed_history.back(),
|
||||
std::span<const ipc::PreprocessedCollection>(preprocessed_history.data(), preprocessed_history.size() - 1U),
|
||||
processor,
|
||||
live_config
|
||||
);
|
||||
publish_result_collection(replay_result, results_ring_);
|
||||
}
|
||||
last_replayed_revision = live_revision;
|
||||
@@ -92,10 +103,15 @@ void DataProcessor::run(const std::atomic<bool>& stop_requested) {
|
||||
auto preprocessed = ipc::deserialize_preprocessed_collection(bytes);
|
||||
preprocessed_history.push_back(std::move(preprocessed));
|
||||
while (preprocessed_history.size() > history_limit) {
|
||||
preprocessed_history.pop_front();
|
||||
preprocessed_history.erase(preprocessed_history.begin());
|
||||
}
|
||||
|
||||
const auto result_collection = process_collection(preprocessed_history.back(), processor, live_config);
|
||||
const auto result_collection = process_collection(
|
||||
preprocessed_history.back(),
|
||||
std::span<const ipc::PreprocessedCollection>(preprocessed_history.data(), preprocessed_history.size() - 1U),
|
||||
processor,
|
||||
live_config
|
||||
);
|
||||
publish_result_collection(result_collection, results_ring_);
|
||||
continue;
|
||||
}
|
||||
@@ -106,25 +122,11 @@ void DataProcessor::run(const std::atomic<bool>& stop_requested) {
|
||||
|
||||
auto DataProcessor::process_collection(
|
||||
const ipc::PreprocessedCollection& preprocessed,
|
||||
std::span<const ipc::PreprocessedCollection> previous_collections,
|
||||
ProcessorInterface& processor,
|
||||
const ProcessingLiveConfig& live_config
|
||||
) -> ipc::ResultCollection {
|
||||
ipc::ResultCollection results{};
|
||||
results.collection_id = preprocessed.collection_id;
|
||||
// Keep source monotonic timestamp stable across live-config replays.
|
||||
results.monotonic_ns = preprocessed.monotonic_ns;
|
||||
results.blocks.reserve(preprocessed.traces.size());
|
||||
|
||||
for (const auto& trace : preprocessed.traces) {
|
||||
ipc::ResultBlock block{};
|
||||
block.combo = trace.combo;
|
||||
block.payloads.reserve(1U);
|
||||
block.payloads.push_back(processor.process(trace, live_config));
|
||||
|
||||
results.blocks.push_back(std::move(block));
|
||||
}
|
||||
|
||||
return results;
|
||||
return processor.process_collection(config_, preprocessed, previous_collections, live_config);
|
||||
}
|
||||
|
||||
auto DataProcessor::resolve_processor(const ProcessingLiveConfig& live_config) -> ProcessorInterface& {
|
||||
@@ -140,6 +142,12 @@ auto DataProcessor::resolve_processor(const ProcessingLiveConfig& live_config) -
|
||||
return *(processors_.begin()->second);
|
||||
}
|
||||
|
||||
auto DataProcessor::should_replay_entire_history(const ProcessingLiveConfig& live_config) const -> bool {
|
||||
const std::string requested_mode =
|
||||
live_config.processor_mode.empty() ? default_processor_mode_ : live_config.processor_mode;
|
||||
return requested_mode == "bscan";
|
||||
}
|
||||
|
||||
auto create_default_processors() -> ProcessorRegistry {
|
||||
ProcessorRegistry processors{};
|
||||
{
|
||||
@@ -150,6 +158,10 @@ auto create_default_processors() -> ProcessorRegistry {
|
||||
auto processor = std::make_unique<BScanProcessor>();
|
||||
processors.emplace(processor->name(), std::move(processor));
|
||||
}
|
||||
{
|
||||
auto processor = std::make_unique<GprProcessor>();
|
||||
processors.emplace(processor->name(), std::move(processor));
|
||||
}
|
||||
return processors;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,27 @@ using Json = nlohmann::json;
|
||||
return static_cast<std::uint64_t>(rounded);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto parse_u32_number(const Json& value, const std::string& field_name) -> std::uint32_t {
|
||||
const auto parsed = parse_u64_number(value, field_name);
|
||||
if (parsed > static_cast<std::uint64_t>(std::numeric_limits<std::uint32_t>::max())) {
|
||||
throw std::runtime_error(field_name + " is out of uint32 range");
|
||||
}
|
||||
return static_cast<std::uint32_t>(parsed);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto parse_u32_array(const Json& value, const std::string& field_name) -> std::vector<std::uint32_t> {
|
||||
if (!value.is_array()) {
|
||||
throw std::runtime_error(field_name + " must be array");
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> result{};
|
||||
result.reserve(value.size());
|
||||
for (std::size_t index = 0; index < value.size(); ++index) {
|
||||
result.push_back(parse_u32_number(value[index], field_name + "[" + std::to_string(index) + "]"));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto parse_live_config(const std::string& json_text, const std::string& path) -> ProcessingLiveConfig {
|
||||
if (json_text.empty()) {
|
||||
throw std::runtime_error("Processing live config is empty: " + path);
|
||||
@@ -80,6 +101,24 @@ using Json = nlohmann::json;
|
||||
}
|
||||
config.phase_deg = static_cast<float>(found->get<double>());
|
||||
}
|
||||
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");
|
||||
}
|
||||
config.pass_through_fixed_y_enabled = found->get<bool>();
|
||||
}
|
||||
if (const auto found = root.find("pass_through_y_min_db"); found != root.end()) {
|
||||
if (!found->is_number()) {
|
||||
throw std::runtime_error("processing.pass_through_y_min_db must be number");
|
||||
}
|
||||
config.pass_through_y_min_db = static_cast<float>(found->get<double>());
|
||||
}
|
||||
if (const auto found = root.find("pass_through_y_max_db"); found != root.end()) {
|
||||
if (!found->is_number()) {
|
||||
throw std::runtime_error("processing.pass_through_y_max_db must be number");
|
||||
}
|
||||
config.pass_through_y_max_db = static_cast<float>(found->get<double>());
|
||||
}
|
||||
if (const auto found = root.find("bscan_axis"); found != root.end()) {
|
||||
if (!found->is_string()) {
|
||||
throw std::runtime_error("processing.bscan_axis must be string");
|
||||
@@ -116,6 +155,51 @@ using Json = nlohmann::json;
|
||||
}
|
||||
config.bscan_stop_freq_mhz = static_cast<float>(found->get<double>());
|
||||
}
|
||||
if (const auto found = root.find("gpr_input_positions"); found != root.end()) {
|
||||
config.gpr_input_positions = parse_u32_array(*found, "processing.gpr_input_positions");
|
||||
}
|
||||
if (const auto found = root.find("gpr_output_positions"); found != root.end()) {
|
||||
config.gpr_output_positions = parse_u32_array(*found, "processing.gpr_output_positions");
|
||||
}
|
||||
if (const auto found = root.find("gpr_min_depth_m"); found != root.end()) {
|
||||
if (!found->is_number()) {
|
||||
throw std::runtime_error("processing.gpr_min_depth_m must be number");
|
||||
}
|
||||
config.gpr_min_depth_m = static_cast<float>(found->get<double>());
|
||||
}
|
||||
if (const auto found = root.find("gpr_max_depth_m"); found != root.end()) {
|
||||
if (!found->is_number()) {
|
||||
throw std::runtime_error("processing.gpr_max_depth_m must be number");
|
||||
}
|
||||
config.gpr_max_depth_m = static_cast<float>(found->get<double>());
|
||||
}
|
||||
if (const auto found = root.find("gpr_comp_power"); found != root.end()) {
|
||||
if (!found->is_number()) {
|
||||
throw std::runtime_error("processing.gpr_comp_power must be number");
|
||||
}
|
||||
config.gpr_comp_power = static_cast<float>(found->get<double>());
|
||||
}
|
||||
if (const auto found = root.find("gpr_start_freq_mhz"); found != root.end()) {
|
||||
if (!found->is_number()) {
|
||||
throw std::runtime_error("processing.gpr_start_freq_mhz must be number");
|
||||
}
|
||||
config.gpr_start_freq_mhz = static_cast<float>(found->get<double>());
|
||||
}
|
||||
if (const auto found = root.find("gpr_stop_freq_mhz"); found != root.end()) {
|
||||
if (!found->is_number()) {
|
||||
throw std::runtime_error("processing.gpr_stop_freq_mhz must be number");
|
||||
}
|
||||
config.gpr_stop_freq_mhz = static_cast<float>(found->get<double>());
|
||||
}
|
||||
if (const auto found = root.find("gpr_background_subtract_enabled"); found != root.end()) {
|
||||
if (!found->is_boolean()) {
|
||||
throw std::runtime_error("processing.gpr_background_subtract_enabled must be bool");
|
||||
}
|
||||
config.gpr_background_subtract_enabled = found->get<bool>();
|
||||
}
|
||||
if (const auto found = root.find("gpr_background_mean_count"); found != root.end()) {
|
||||
config.gpr_background_mean_count = parse_u32_number(*found, "processing.gpr_background_mean_count");
|
||||
}
|
||||
if (const auto found = root.find("history_command_seq"); found != root.end()) {
|
||||
config.history_command_seq = parse_u64_number(*found, "processing.history_command_seq");
|
||||
}
|
||||
|
||||
@@ -7,10 +7,12 @@ namespace radar::processing {
|
||||
class BScanProcessor final : public ProcessorInterface {
|
||||
public:
|
||||
[[nodiscard]] auto name() const -> std::string override;
|
||||
[[nodiscard]] auto process(
|
||||
const ipc::SweepTraceBlock& trace,
|
||||
[[nodiscard]] auto process_collection(
|
||||
const config::RunConfig& run_config,
|
||||
const ipc::PreprocessedCollection& collection,
|
||||
std::span<const ipc::PreprocessedCollection> previous_collections,
|
||||
const ProcessingLiveConfig& live_config
|
||||
) -> ipc::ResultPayload override;
|
||||
) -> ipc::ResultCollection override;
|
||||
};
|
||||
|
||||
} // namespace radar::processing
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "processor_interface.hpp"
|
||||
|
||||
namespace radar::processing {
|
||||
|
||||
class GprProcessor final : public ProcessorInterface {
|
||||
public:
|
||||
[[nodiscard]] auto name() const -> std::string override;
|
||||
[[nodiscard]] auto process_collection(
|
||||
const config::RunConfig& run_config,
|
||||
const ipc::PreprocessedCollection& collection,
|
||||
std::span<const ipc::PreprocessedCollection> previous_collections,
|
||||
const ProcessingLiveConfig& live_config
|
||||
) -> ipc::ResultCollection override;
|
||||
};
|
||||
|
||||
} // namespace radar::processing
|
||||
@@ -7,10 +7,12 @@ namespace radar::processing {
|
||||
class PassThroughProcessor final : public ProcessorInterface {
|
||||
public:
|
||||
[[nodiscard]] auto name() const -> std::string override;
|
||||
[[nodiscard]] auto process(
|
||||
const ipc::SweepTraceBlock& trace,
|
||||
[[nodiscard]] auto process_collection(
|
||||
const config::RunConfig& run_config,
|
||||
const ipc::PreprocessedCollection& collection,
|
||||
std::span<const ipc::PreprocessedCollection> previous_collections,
|
||||
const ProcessingLiveConfig& live_config
|
||||
) -> ipc::ResultPayload override;
|
||||
) -> ipc::ResultCollection override;
|
||||
};
|
||||
|
||||
} // namespace radar::processing
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#include "processing_live_config.hpp"
|
||||
#include "run_config.hpp"
|
||||
#include "shared_types.hpp"
|
||||
|
||||
namespace radar::processing {
|
||||
@@ -12,10 +14,12 @@ class ProcessorInterface {
|
||||
virtual ~ProcessorInterface() = default;
|
||||
|
||||
[[nodiscard]] virtual auto name() const -> std::string = 0;
|
||||
[[nodiscard]] virtual auto process(
|
||||
const ipc::SweepTraceBlock& trace,
|
||||
[[nodiscard]] virtual auto process_collection(
|
||||
const config::RunConfig& run_config,
|
||||
const ipc::PreprocessedCollection& collection,
|
||||
std::span<const ipc::PreprocessedCollection> previous_collections,
|
||||
const ProcessingLiveConfig& live_config
|
||||
) -> ipc::ResultPayload = 0;
|
||||
) -> ipc::ResultCollection = 0;
|
||||
};
|
||||
|
||||
} // namespace radar::processing
|
||||
|
||||
@@ -215,23 +215,39 @@ auto BScanProcessor::name() const -> std::string {
|
||||
return "bscan";
|
||||
}
|
||||
|
||||
auto BScanProcessor::process(const ipc::SweepTraceBlock& trace, const ProcessingLiveConfig& live_config)
|
||||
-> ipc::ResultPayload {
|
||||
ipc::ResultPayload payload{};
|
||||
payload.processing_name = name();
|
||||
payload.kind = ipc::ResultKind::TraceComplex;
|
||||
auto BScanProcessor::process_collection(
|
||||
const config::RunConfig& /*run_config*/,
|
||||
const ipc::PreprocessedCollection& collection,
|
||||
std::span<const ipc::PreprocessedCollection> /*previous_collections*/,
|
||||
const ProcessingLiveConfig& live_config
|
||||
) -> ipc::ResultCollection {
|
||||
ipc::ResultCollection results{};
|
||||
results.collection_id = collection.collection_id;
|
||||
results.monotonic_ns = collection.monotonic_ns;
|
||||
results.blocks.reserve(collection.traces.size());
|
||||
|
||||
auto profile = compute_bscan_profile(trace, live_config);
|
||||
payload.frequency_hz = std::move(profile.depth_m);
|
||||
payload.trace.reserve(profile.response.size());
|
||||
for (const auto value : profile.response) {
|
||||
payload.trace.push_back(ipc::Complex32{
|
||||
.re = value,
|
||||
.im = 0.0F,
|
||||
});
|
||||
for (const auto& trace : collection.traces) {
|
||||
ipc::ResultPayload payload{};
|
||||
payload.processing_name = name();
|
||||
payload.kind = ipc::ResultKind::TraceComplex;
|
||||
|
||||
auto profile = compute_bscan_profile(trace, live_config);
|
||||
payload.frequency_hz = std::move(profile.depth_m);
|
||||
payload.trace.reserve(profile.response.size());
|
||||
for (const auto value : profile.response) {
|
||||
payload.trace.push_back(ipc::Complex32{
|
||||
.re = value,
|
||||
.im = 0.0F,
|
||||
});
|
||||
}
|
||||
|
||||
ipc::ResultBlock block{};
|
||||
block.combo = trace.combo;
|
||||
block.payloads.push_back(std::move(payload));
|
||||
results.blocks.push_back(std::move(block));
|
||||
}
|
||||
|
||||
return payload;
|
||||
return results;
|
||||
}
|
||||
|
||||
} // namespace radar::processing
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,27 +13,43 @@ auto PassThroughProcessor::name() const -> std::string {
|
||||
return "pass_through";
|
||||
}
|
||||
|
||||
auto PassThroughProcessor::process(const ipc::SweepTraceBlock& trace, const ProcessingLiveConfig& live_config)
|
||||
-> ipc::ResultPayload {
|
||||
ipc::ResultPayload payload{};
|
||||
payload.processing_name = name();
|
||||
payload.kind = ipc::ResultKind::TraceComplex;
|
||||
payload.frequency_hz = trace.frequency_hz;
|
||||
payload.trace = trace.s21;
|
||||
auto PassThroughProcessor::process_collection(
|
||||
const config::RunConfig& /*run_config*/,
|
||||
const ipc::PreprocessedCollection& collection,
|
||||
std::span<const ipc::PreprocessedCollection> /*previous_collections*/,
|
||||
const ProcessingLiveConfig& live_config
|
||||
) -> ipc::ResultCollection {
|
||||
ipc::ResultCollection results{};
|
||||
results.collection_id = collection.collection_id;
|
||||
results.monotonic_ns = collection.monotonic_ns;
|
||||
results.blocks.reserve(collection.traces.size());
|
||||
|
||||
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);
|
||||
const float cos_phase = std::cos(phase_rad);
|
||||
const float sin_phase = std::sin(phase_rad);
|
||||
for (const auto& trace : collection.traces) {
|
||||
ipc::ResultPayload payload{};
|
||||
payload.processing_name = name();
|
||||
payload.kind = ipc::ResultKind::TraceComplex;
|
||||
payload.frequency_hz = trace.frequency_hz;
|
||||
payload.trace = trace.s21;
|
||||
|
||||
for (auto& sample : payload.trace) {
|
||||
const float re = sample.re;
|
||||
const float im = sample.im;
|
||||
sample.re = linear_gain * ((re * cos_phase) - (im * sin_phase));
|
||||
sample.im = linear_gain * ((re * sin_phase) + (im * cos_phase));
|
||||
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);
|
||||
const float cos_phase = std::cos(phase_rad);
|
||||
const float sin_phase = std::sin(phase_rad);
|
||||
|
||||
for (auto& sample : payload.trace) {
|
||||
const float re = sample.re;
|
||||
const float im = sample.im;
|
||||
sample.re = linear_gain * ((re * cos_phase) - (im * sin_phase));
|
||||
sample.im = linear_gain * ((re * sin_phase) + (im * cos_phase));
|
||||
}
|
||||
|
||||
ipc::ResultBlock block{};
|
||||
block.combo = trace.combo;
|
||||
block.payloads.push_back(std::move(payload));
|
||||
results.blocks.push_back(std::move(block));
|
||||
}
|
||||
|
||||
return payload;
|
||||
return results;
|
||||
}
|
||||
|
||||
} // namespace radar::processing
|
||||
|
||||
Reference in New Issue
Block a user