added legacy gpr
This commit is contained in:
@@ -13,6 +13,12 @@ enum class HistoryCommand {
|
||||
ClearAll,
|
||||
};
|
||||
|
||||
enum class GprAlgorithm {
|
||||
Backprojection,
|
||||
LegacyPoint,
|
||||
LegacyExtended,
|
||||
};
|
||||
|
||||
struct ProcessingLiveConfig {
|
||||
std::string processor_mode = "pass_through";
|
||||
std::string pass_through_channel = "s21";
|
||||
@@ -26,12 +32,18 @@ struct ProcessingLiveConfig {
|
||||
float bscan_gain = 1.0F;
|
||||
float bscan_start_freq_mhz = 100.0F;
|
||||
float bscan_stop_freq_mhz = 8800.0F;
|
||||
GprAlgorithm gpr_algorithm = GprAlgorithm::Backprojection;
|
||||
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_range_comp_power = 0.28F;
|
||||
float gpr_angle_comp_power = 0.10F;
|
||||
float gpr_comp_power = 0.2F;
|
||||
float gpr_speed_m_s = 0.0F;
|
||||
float gpr_look_angle_deg = 0.0F;
|
||||
float gpr_snr_thresh = 4.5F;
|
||||
float gpr_snr_comp_max = 25.0F;
|
||||
float gpr_start_freq_mhz = 3000.0F;
|
||||
float gpr_stop_freq_mhz = 6000.0F;
|
||||
bool gpr_background_subtract_enabled = true;
|
||||
|
||||
@@ -31,6 +31,21 @@ using Json = nlohmann::json;
|
||||
throw std::runtime_error("processing.history_command must be one of: none, remove_last, clear_all");
|
||||
}
|
||||
|
||||
[[nodiscard]] auto parse_gpr_algorithm(const std::string& value) -> GprAlgorithm {
|
||||
if (value == "backprojection") {
|
||||
return GprAlgorithm::Backprojection;
|
||||
}
|
||||
if (value == "legacy_point") {
|
||||
return GprAlgorithm::LegacyPoint;
|
||||
}
|
||||
if (value == "legacy_extended") {
|
||||
return GprAlgorithm::LegacyExtended;
|
||||
}
|
||||
throw std::runtime_error(
|
||||
"processing.gpr_algorithm must be one of: backprojection, legacy_point, legacy_extended"
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto parse_s_parameter_channel(const std::string& value, const std::string& field_name) -> std::string {
|
||||
if (value == "s21" || value == "s11") {
|
||||
return value;
|
||||
@@ -164,6 +179,12 @@ using Json = nlohmann::json;
|
||||
}
|
||||
config.bscan_stop_freq_mhz = static_cast<float>(found->get<double>());
|
||||
}
|
||||
if (const auto found = root.find("gpr_algorithm"); found != root.end()) {
|
||||
if (!found->is_string()) {
|
||||
throw std::runtime_error("processing.gpr_algorithm must be string");
|
||||
}
|
||||
config.gpr_algorithm = parse_gpr_algorithm(found->get<std::string>());
|
||||
}
|
||||
if (const auto found = root.find("gpr_input_positions"); found != root.end()) {
|
||||
config.gpr_input_positions = parse_u32_array(*found, "processing.gpr_input_positions");
|
||||
}
|
||||
@@ -194,6 +215,36 @@ using Json = nlohmann::json;
|
||||
}
|
||||
config.gpr_angle_comp_power = 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_speed_m_s"); found != root.end()) {
|
||||
if (!found->is_number()) {
|
||||
throw std::runtime_error("processing.gpr_speed_m_s must be number");
|
||||
}
|
||||
config.gpr_speed_m_s = static_cast<float>(found->get<double>());
|
||||
}
|
||||
if (const auto found = root.find("gpr_look_angle_deg"); found != root.end()) {
|
||||
if (!found->is_number()) {
|
||||
throw std::runtime_error("processing.gpr_look_angle_deg must be number");
|
||||
}
|
||||
config.gpr_look_angle_deg = static_cast<float>(found->get<double>());
|
||||
}
|
||||
if (const auto found = root.find("gpr_snr_thresh"); found != root.end()) {
|
||||
if (!found->is_number()) {
|
||||
throw std::runtime_error("processing.gpr_snr_thresh must be number");
|
||||
}
|
||||
config.gpr_snr_thresh = static_cast<float>(found->get<double>());
|
||||
}
|
||||
if (const auto found = root.find("gpr_snr_comp_max"); found != root.end()) {
|
||||
if (!found->is_number()) {
|
||||
throw std::runtime_error("processing.gpr_snr_comp_max must be number");
|
||||
}
|
||||
config.gpr_snr_comp_max = 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");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,827 @@
|
||||
constexpr double kLegacyGridZMinM = 0.20;
|
||||
constexpr double kLegacySmoothSigma = 3.0;
|
||||
constexpr double kLegacyCleanSuppressRadiusM = 0.07;
|
||||
constexpr double kLegacyCleanThresholdFrac = 0.05;
|
||||
constexpr std::size_t kLegacyMaxObjects = 15U;
|
||||
constexpr double kLegacyExtendedThresholdFrac = 0.75;
|
||||
constexpr double kLegacyExtendedMinAreaCm2 = 2.0;
|
||||
|
||||
struct LegacyAscanResult {
|
||||
std::vector<double> time_s{};
|
||||
std::vector<double> depth_m{};
|
||||
std::vector<double> amplitude{};
|
||||
double bandwidth_hz = 0.0;
|
||||
};
|
||||
|
||||
struct LegacyPeakRecord {
|
||||
double z_app = 0.0;
|
||||
double tau = 0.0;
|
||||
double tau_corr = 0.0;
|
||||
double z_corr = 0.0;
|
||||
double snr_raw = 0.0;
|
||||
double snr_comp = 0.0;
|
||||
};
|
||||
|
||||
struct LegacyPointRecord {
|
||||
double x_m = 0.0;
|
||||
double z_m = 0.0;
|
||||
double score = 0.0;
|
||||
};
|
||||
|
||||
struct LegacyRegionRecord {
|
||||
double x_m = 0.0;
|
||||
double z_m = 0.0;
|
||||
double score = 0.0;
|
||||
double pixel_count = 0.0;
|
||||
std::vector<float> mask{};
|
||||
};
|
||||
|
||||
struct LegacyPairTiming {
|
||||
double dtau_motion_s = 0.0;
|
||||
};
|
||||
|
||||
enum class LegacyPeakDomain {
|
||||
Apparent,
|
||||
Corrected,
|
||||
};
|
||||
|
||||
[[nodiscard]] auto find_legacy_peak_indices(
|
||||
const std::vector<double>& values,
|
||||
std::size_t start_index,
|
||||
std::size_t stop_index,
|
||||
double threshold,
|
||||
std::size_t min_distance
|
||||
) -> std::vector<std::size_t> {
|
||||
if (stop_index <= start_index + 2U) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::size_t> candidates{};
|
||||
for (std::size_t index = start_index + 1U; index + 1U < stop_index; ++index) {
|
||||
const bool below_threshold = values[index] < threshold;
|
||||
const bool not_rising = values[index] <= values[index - 1U];
|
||||
const bool still_rising = values[index] < values[index + 1U];
|
||||
if (below_threshold || not_rising || still_rising) {
|
||||
continue;
|
||||
}
|
||||
candidates.push_back(index);
|
||||
}
|
||||
|
||||
std::sort(candidates.begin(), candidates.end(), [&](std::size_t left, std::size_t right) {
|
||||
return values[left] > values[right];
|
||||
});
|
||||
|
||||
std::vector<std::size_t> selected{};
|
||||
for (const auto index : candidates) {
|
||||
bool keep = true;
|
||||
for (const auto accepted : selected) {
|
||||
const auto distance = accepted > index ? accepted - index : index - accepted;
|
||||
if (distance < min_distance) {
|
||||
keep = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (keep) {
|
||||
selected.push_back(index);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(selected.begin(), selected.end());
|
||||
return selected;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto legacy_attenuation_at_depth(
|
||||
std::size_t tx_index,
|
||||
std::size_t rx_index,
|
||||
double z_app,
|
||||
const std::vector<double>& x_tx,
|
||||
const std::vector<double>& x_rx
|
||||
) -> double {
|
||||
const double x_center = 0.5 * (x_tx[tx_index] + x_rx[rx_index]);
|
||||
const double r_tx = std::hypot(x_center - x_tx[tx_index], z_app);
|
||||
const double r_rx = std::hypot(x_center - x_rx[rx_index], z_app);
|
||||
const double geo = 1.0 / ((r_tx * r_rx) + 1e-12);
|
||||
const double pattern =
|
||||
std::pow(z_app / (r_tx + 1e-12), 2.0) *
|
||||
std::pow(z_app / (r_rx + 1e-12), 2.0);
|
||||
return (geo * pattern) + 1e-30;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto lower_bound_index(const std::vector<double>& axis, double value) -> std::size_t {
|
||||
const auto found = std::lower_bound(axis.begin(), axis.end(), value);
|
||||
return static_cast<std::size_t>(std::distance(axis.begin(), found));
|
||||
}
|
||||
|
||||
[[nodiscard]] auto compute_legacy_ascan(
|
||||
const SelectedTrace& trace,
|
||||
double start_hz,
|
||||
double stop_hz,
|
||||
double velocity_mps
|
||||
) -> LegacyAscanResult {
|
||||
LegacyAscanResult result{};
|
||||
if (trace.frequency_hz.size() != trace.s21.size()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const double low_hz = std::min(start_hz, stop_hz);
|
||||
const double high_hz = std::max(start_hz, stop_hz);
|
||||
std::vector<double> frequency_hz{};
|
||||
std::vector<std::complex<double>> s21{};
|
||||
frequency_hz.reserve(trace.frequency_hz.size());
|
||||
s21.reserve(trace.s21.size());
|
||||
for (std::size_t index = 0U; index < trace.frequency_hz.size(); ++index) {
|
||||
const double frequency_value = trace.frequency_hz[index];
|
||||
if (frequency_value < low_hz || frequency_value > high_hz) {
|
||||
continue;
|
||||
}
|
||||
frequency_hz.push_back(frequency_value);
|
||||
s21.push_back(trace.s21[index]);
|
||||
}
|
||||
|
||||
if (frequency_hz.size() < 2U) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const std::size_t point_count = frequency_hz.size();
|
||||
const double df_hz = (frequency_hz.back() - frequency_hz.front()) / static_cast<double>(point_count - 1U);
|
||||
if (!(df_hz > 0.0)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto start_bin = static_cast<std::int64_t>(std::llround(frequency_hz.front() / df_hz));
|
||||
if (start_bin < 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto start_index = static_cast<std::size_t>(start_bin);
|
||||
const std::size_t min_fft_len = 2U * (start_index + point_count - 1U);
|
||||
const std::size_t fft_len = next_power_of_two(min_fft_len);
|
||||
if (fft_len < min_fft_len || start_index > fft_len || point_count > (fft_len - start_index)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::complex<double>> spectrum(fft_len, std::complex<double>(0.0, 0.0));
|
||||
for (std::size_t index = 0U; index < point_count; ++index) {
|
||||
const double window = point_count > 1U
|
||||
? 0.5 - (0.5 * std::cos((2.0 * kPi * static_cast<double>(index)) / static_cast<double>(point_count - 1U)))
|
||||
: 1.0;
|
||||
spectrum[start_index + index] = s21[index] * window;
|
||||
}
|
||||
|
||||
fft_inplace(spectrum, true);
|
||||
|
||||
result.bandwidth_hz = frequency_hz.back() - frequency_hz.front();
|
||||
const double dt_s = 1.0 / (static_cast<double>(fft_len) * df_hz);
|
||||
result.time_s.resize(fft_len, 0.0);
|
||||
result.depth_m.resize(fft_len, 0.0);
|
||||
result.amplitude.resize(fft_len, 0.0);
|
||||
for (std::size_t index = 0U; index < fft_len; ++index) {
|
||||
result.time_s[index] = static_cast<double>(index) * dt_s;
|
||||
result.depth_m[index] = result.time_s[index] * velocity_mps * 0.5;
|
||||
result.amplitude[index] = std::abs(spectrum[index]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto legacy_peak_depth_for_domain(const LegacyPeakRecord& peak, LegacyPeakDomain domain) -> double {
|
||||
return domain == LegacyPeakDomain::Corrected ? peak.z_corr : peak.z_app;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto legacy_peak_tau_for_domain(const LegacyPeakRecord& peak, LegacyPeakDomain domain) -> double {
|
||||
return domain == LegacyPeakDomain::Corrected ? peak.tau_corr : peak.tau;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto is_legacy_depth_excluded(
|
||||
double z_value,
|
||||
const std::vector<std::pair<double, double>>& ranges
|
||||
) -> bool {
|
||||
for (const auto& [low, high] : ranges) {
|
||||
if (z_value >= low && z_value <= high) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto build_legacy_motion_timing_by_pair(
|
||||
const std::vector<SelectedTrace>& traces,
|
||||
std::size_t total_combo_count,
|
||||
std::uint64_t capture_start_ns,
|
||||
std::uint64_t capture_end_ns,
|
||||
const ProcessingLiveConfig& live_config,
|
||||
double velocity_mps
|
||||
) -> std::unordered_map<PairKey, LegacyPairTiming> {
|
||||
std::unordered_map<PairKey, LegacyPairTiming> timing_by_pair{};
|
||||
timing_by_pair.reserve(traces.size());
|
||||
|
||||
const double speed_mps = static_cast<double>(live_config.gpr_speed_m_s);
|
||||
if (!(std::abs(speed_mps) > 1e-12)) {
|
||||
for (const auto& trace : traces) {
|
||||
timing_by_pair.emplace(make_pair_key(trace.tx_local_index, trace.rx_local_index), LegacyPairTiming{});
|
||||
}
|
||||
return timing_by_pair;
|
||||
}
|
||||
|
||||
if (total_combo_count == 0U) {
|
||||
throw std::runtime_error("Legacy GPR requires at least one run combo");
|
||||
}
|
||||
if (capture_end_ns <= capture_start_ns) {
|
||||
throw std::runtime_error(
|
||||
"Legacy GPR requires valid capture_start_ns/capture_end_ns metadata when speed is non-zero"
|
||||
);
|
||||
}
|
||||
|
||||
const double capture_span_s = static_cast<double>(capture_end_ns - capture_start_ns) * 1e-9;
|
||||
const double slot_duration_s = capture_span_s / static_cast<double>(total_combo_count);
|
||||
if (!(slot_duration_s > 0.0)) {
|
||||
throw std::runtime_error("Legacy GPR requires positive collection capture span when speed is non-zero");
|
||||
}
|
||||
|
||||
const double t_ref_s = 0.5 * capture_span_s;
|
||||
const double cos_theta = std::cos((static_cast<double>(live_config.gpr_look_angle_deg) * kPi) / 180.0);
|
||||
for (const auto& trace : traces) {
|
||||
const double t_center_s = (static_cast<double>(trace.run_order) + 0.5) * slot_duration_s;
|
||||
const double dz_motion_m = speed_mps * (t_center_s - t_ref_s) * cos_theta;
|
||||
timing_by_pair.emplace(
|
||||
make_pair_key(trace.tx_local_index, trace.rx_local_index),
|
||||
LegacyPairTiming{.dtau_motion_s = (2.0 * dz_motion_m) / velocity_mps}
|
||||
);
|
||||
}
|
||||
|
||||
return timing_by_pair;
|
||||
}
|
||||
|
||||
void apply_legacy_motion_correction(
|
||||
std::unordered_map<PairKey, std::vector<LegacyPeakRecord>>& peaks_by_pair,
|
||||
const std::unordered_map<PairKey, LegacyPairTiming>& timing_by_pair,
|
||||
double velocity_mps
|
||||
) {
|
||||
for (auto& [key, peaks] : peaks_by_pair) {
|
||||
const auto timing_it = timing_by_pair.find(key);
|
||||
if (timing_it == timing_by_pair.end()) {
|
||||
throw std::runtime_error("Missing motion timing for selected legacy GPR combo");
|
||||
}
|
||||
|
||||
for (auto& peak : peaks) {
|
||||
peak.tau_corr = peak.tau + timing_it->second.dtau_motion_s;
|
||||
peak.z_corr = 0.5 * velocity_mps * peak.tau_corr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto build_legacy_accumulator(
|
||||
const GridDefinition& grid,
|
||||
const std::unordered_map<PairKey, std::vector<LegacyPeakRecord>>& peaks_by_pair,
|
||||
const std::vector<std::pair<double, double>>& exclude_ranges,
|
||||
double velocity_mps,
|
||||
double shell_sigma_m,
|
||||
const std::vector<double>& x_tx,
|
||||
const std::vector<double>& x_rx,
|
||||
LegacyPeakDomain domain
|
||||
) -> std::vector<double> {
|
||||
const std::size_t width = grid.x_grid.size();
|
||||
const std::size_t height = grid.z_grid.size();
|
||||
std::vector<double> accumulator(width * height, 0.0);
|
||||
if (!(shell_sigma_m > 0.0)) {
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
for (std::size_t tx_index = 0U; tx_index < x_tx.size(); ++tx_index) {
|
||||
for (std::size_t rx_index = 0U; rx_index < x_rx.size(); ++rx_index) {
|
||||
const auto peak_it = peaks_by_pair.find(
|
||||
make_pair_key(static_cast<std::uint32_t>(tx_index), static_cast<std::uint32_t>(rx_index))
|
||||
);
|
||||
if (peak_it == peaks_by_pair.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& tx_grid = grid.tx_distance_grids[tx_index];
|
||||
const auto& rx_grid = grid.rx_distance_grids[rx_index];
|
||||
for (const auto& peak : peak_it->second) {
|
||||
if (is_legacy_depth_excluded(legacy_peak_depth_for_domain(peak, domain), exclude_ranges)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const double range_total = velocity_mps * legacy_peak_tau_for_domain(peak, domain);
|
||||
for (std::size_t cell_index = 0U; cell_index < accumulator.size(); ++cell_index) {
|
||||
const double residual = tx_grid[cell_index] + rx_grid[cell_index] - range_total;
|
||||
const double shell = std::exp(-0.5 * std::pow(residual / shell_sigma_m, 2.0));
|
||||
accumulator[cell_index] += shell * peak.snr_comp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto count_legacy_agreeing_ellipses(
|
||||
double x_est,
|
||||
double z_est,
|
||||
const std::unordered_map<PairKey, std::vector<LegacyPeakRecord>>& peaks_by_pair,
|
||||
const std::vector<std::pair<double, double>>& exclude_ranges,
|
||||
const std::vector<double>& x_tx,
|
||||
const std::vector<double>& x_rx,
|
||||
double velocity_mps,
|
||||
double shell_sigma_m,
|
||||
LegacyPeakDomain domain
|
||||
) -> double {
|
||||
std::size_t count = 0U;
|
||||
for (std::size_t tx_index = 0U; tx_index < x_tx.size(); ++tx_index) {
|
||||
for (std::size_t rx_index = 0U; rx_index < x_rx.size(); ++rx_index) {
|
||||
const auto peak_it = peaks_by_pair.find(
|
||||
make_pair_key(static_cast<std::uint32_t>(tx_index), static_cast<std::uint32_t>(rx_index))
|
||||
);
|
||||
if (peak_it == peaks_by_pair.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& peak : peak_it->second) {
|
||||
if (is_legacy_depth_excluded(legacy_peak_depth_for_domain(peak, domain), exclude_ranges)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const double r_tx = std::hypot(x_est - x_tx[tx_index], z_est);
|
||||
const double r_rx = std::hypot(x_est - x_rx[rx_index], z_est);
|
||||
if (std::abs((r_tx + r_rx) - (velocity_mps * legacy_peak_tau_for_domain(peak, domain))) <
|
||||
shell_sigma_m * 6.0) {
|
||||
count += 1U;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<double>(count);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto find_legacy_centroid(
|
||||
const std::vector<double>& values,
|
||||
std::size_t width,
|
||||
std::size_t height,
|
||||
std::size_t row,
|
||||
std::size_t col,
|
||||
std::size_t radius_z,
|
||||
std::size_t radius_x,
|
||||
const std::vector<double>& x_grid,
|
||||
const std::vector<double>& z_grid
|
||||
) -> std::pair<double, double> {
|
||||
if (values.empty()) {
|
||||
return {x_grid[col], z_grid[row]};
|
||||
}
|
||||
|
||||
const auto row_start = row > radius_z ? row - radius_z : 0U;
|
||||
const auto row_stop = std::min(height, row + radius_z + 1U);
|
||||
const auto col_start = col > radius_x ? col - radius_x : 0U;
|
||||
const auto col_stop = std::min(width, col + radius_x + 1U);
|
||||
|
||||
double weight_sum = 0.0;
|
||||
double row_weighted_sum = 0.0;
|
||||
double col_weighted_sum = 0.0;
|
||||
for (std::size_t sample_row = row_start; sample_row < row_stop; ++sample_row) {
|
||||
for (std::size_t sample_col = col_start; sample_col < col_stop; ++sample_col) {
|
||||
const double weight = values[(sample_row * width) + sample_col];
|
||||
weight_sum += weight;
|
||||
row_weighted_sum += static_cast<double>(sample_row) * weight;
|
||||
col_weighted_sum += static_cast<double>(sample_col) * weight;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(weight_sum > 0.0)) {
|
||||
return {x_grid[col], z_grid[row]};
|
||||
}
|
||||
|
||||
const auto centroid_row =
|
||||
clamp_index(static_cast<std::ptrdiff_t>(std::llround(row_weighted_sum / weight_sum)), height);
|
||||
const auto centroid_col =
|
||||
clamp_index(static_cast<std::ptrdiff_t>(std::llround(col_weighted_sum / weight_sum)), width);
|
||||
return {x_grid[centroid_col], z_grid[centroid_row]};
|
||||
}
|
||||
|
||||
[[nodiscard]] auto clean_legacy_find_points(
|
||||
const GridDefinition& grid,
|
||||
const std::unordered_map<PairKey, std::vector<LegacyPeakRecord>>& peaks_by_pair,
|
||||
const std::vector<double>& x_tx,
|
||||
const std::vector<double>& x_rx,
|
||||
double velocity_mps,
|
||||
double shell_sigma_m,
|
||||
LegacyPeakDomain domain
|
||||
) -> std::pair<std::vector<LegacyPointRecord>, std::vector<double>> {
|
||||
std::vector<LegacyPointRecord> found{};
|
||||
const auto accumulator =
|
||||
build_legacy_accumulator(grid, peaks_by_pair, {}, velocity_mps, shell_sigma_m, x_tx, x_rx, domain);
|
||||
const double initial_max = max_value(accumulator);
|
||||
if (!(initial_max > 0.0) || grid.x_grid.size() < 2U || grid.z_grid.size() < 2U) {
|
||||
return {found, gaussian_filter_2d(accumulator, grid.x_grid.size(), grid.z_grid.size(), kLegacySmoothSigma)};
|
||||
}
|
||||
|
||||
const double dx = grid.x_grid[1] - grid.x_grid[0];
|
||||
const double dz = grid.z_grid[1] - grid.z_grid[0];
|
||||
const auto radius_x =
|
||||
static_cast<std::size_t>(std::max(1.0, std::round(kLegacyCleanSuppressRadiusM / std::max(dx, 1e-6))));
|
||||
const auto radius_z =
|
||||
static_cast<std::size_t>(std::max(1.0, std::round(kLegacyCleanSuppressRadiusM / std::max(dz, 1e-6))));
|
||||
|
||||
std::vector<std::pair<double, double>> excluded_ranges{};
|
||||
for (std::size_t step = 0U; step < kLegacyMaxObjects; ++step) {
|
||||
const auto current = build_legacy_accumulator(
|
||||
grid,
|
||||
peaks_by_pair,
|
||||
excluded_ranges,
|
||||
velocity_mps,
|
||||
shell_sigma_m,
|
||||
x_tx,
|
||||
x_rx,
|
||||
domain
|
||||
);
|
||||
const auto smoothed = gaussian_filter_2d(current, grid.x_grid.size(), grid.z_grid.size(), kLegacySmoothSigma);
|
||||
const double smoothed_max = max_value(smoothed);
|
||||
if (!(smoothed_max > (kLegacyCleanThresholdFrac * initial_max))) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto max_it = std::max_element(smoothed.begin(), smoothed.end());
|
||||
const auto max_index = static_cast<std::size_t>(std::distance(smoothed.begin(), max_it));
|
||||
const auto peak_row = max_index / grid.x_grid.size();
|
||||
const auto peak_col = max_index % grid.x_grid.size();
|
||||
const auto [x_est, z_est] = find_legacy_centroid(
|
||||
smoothed,
|
||||
grid.x_grid.size(),
|
||||
grid.z_grid.size(),
|
||||
peak_row,
|
||||
peak_col,
|
||||
radius_z,
|
||||
radius_x,
|
||||
grid.x_grid,
|
||||
grid.z_grid
|
||||
);
|
||||
|
||||
found.push_back(
|
||||
LegacyPointRecord{
|
||||
.x_m = x_est,
|
||||
.z_m = z_est,
|
||||
.score = count_legacy_agreeing_ellipses(
|
||||
x_est,
|
||||
z_est,
|
||||
peaks_by_pair,
|
||||
excluded_ranges,
|
||||
x_tx,
|
||||
x_rx,
|
||||
velocity_mps,
|
||||
shell_sigma_m,
|
||||
domain
|
||||
),
|
||||
}
|
||||
);
|
||||
|
||||
std::vector<double> matched_depths{};
|
||||
for (std::size_t tx_index = 0U; tx_index < x_tx.size(); ++tx_index) {
|
||||
for (std::size_t rx_index = 0U; rx_index < x_rx.size(); ++rx_index) {
|
||||
const auto peak_it = peaks_by_pair.find(
|
||||
make_pair_key(static_cast<std::uint32_t>(tx_index), static_cast<std::uint32_t>(rx_index))
|
||||
);
|
||||
if (peak_it == peaks_by_pair.end()) {
|
||||
continue;
|
||||
}
|
||||
for (const auto& peak : peak_it->second) {
|
||||
if (is_legacy_depth_excluded(legacy_peak_depth_for_domain(peak, domain), excluded_ranges)) {
|
||||
continue;
|
||||
}
|
||||
const double r_tx = std::hypot(x_est - x_tx[tx_index], z_est);
|
||||
const double r_rx = std::hypot(x_est - x_rx[rx_index], z_est);
|
||||
if (std::abs((r_tx + r_rx) - (velocity_mps * legacy_peak_tau_for_domain(peak, domain))) <
|
||||
shell_sigma_m * 3.0) {
|
||||
matched_depths.push_back(legacy_peak_depth_for_domain(peak, domain));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!matched_depths.empty()) {
|
||||
const auto [min_it, max_it_depth] = std::minmax_element(matched_depths.begin(), matched_depths.end());
|
||||
excluded_ranges.emplace_back(*min_it - shell_sigma_m, *max_it_depth + shell_sigma_m);
|
||||
}
|
||||
}
|
||||
|
||||
return {found, gaussian_filter_2d(accumulator, grid.x_grid.size(), grid.z_grid.size(), kLegacySmoothSigma)};
|
||||
}
|
||||
|
||||
[[nodiscard]] auto extended_legacy_find_regions(
|
||||
const GridDefinition& grid,
|
||||
const std::unordered_map<PairKey, std::vector<LegacyPeakRecord>>& peaks_by_pair,
|
||||
const std::vector<double>& x_tx,
|
||||
const std::vector<double>& x_rx,
|
||||
double velocity_mps,
|
||||
double shell_sigma_m
|
||||
) -> std::pair<std::vector<LegacyRegionRecord>, std::vector<double>> {
|
||||
std::vector<LegacyRegionRecord> regions{};
|
||||
const auto accumulator = build_legacy_accumulator(
|
||||
grid,
|
||||
peaks_by_pair,
|
||||
{},
|
||||
velocity_mps,
|
||||
shell_sigma_m,
|
||||
x_tx,
|
||||
x_rx,
|
||||
LegacyPeakDomain::Apparent
|
||||
);
|
||||
const auto smoothed = gaussian_filter_2d(accumulator, grid.x_grid.size(), grid.z_grid.size(), kLegacySmoothSigma);
|
||||
const double smoothed_max = max_value(smoothed);
|
||||
if (!(smoothed_max > 0.0) || grid.x_grid.size() < 2U || grid.z_grid.size() < 2U) {
|
||||
return {regions, smoothed};
|
||||
}
|
||||
|
||||
const double dx_cm = std::abs(grid.x_grid[1] - grid.x_grid[0]) * 100.0;
|
||||
const double dz_cm = std::abs(grid.z_grid[1] - grid.z_grid[0]) * 100.0;
|
||||
const double pixel_area_cm2 = std::max(dx_cm * dz_cm, 1e-6);
|
||||
const auto min_pixels =
|
||||
static_cast<std::size_t>(std::max(1.0, std::floor(kLegacyExtendedMinAreaCm2 / pixel_area_cm2)));
|
||||
|
||||
const std::size_t width = grid.x_grid.size();
|
||||
const std::size_t height = grid.z_grid.size();
|
||||
const double threshold = kLegacyExtendedThresholdFrac * smoothed_max;
|
||||
std::vector<std::uint8_t> visited(width * height, 0U);
|
||||
|
||||
for (std::size_t row = 0U; row < height; ++row) {
|
||||
for (std::size_t col = 0U; col < width; ++col) {
|
||||
const auto start_index = (row * width) + col;
|
||||
if (visited[start_index] != 0U || smoothed[start_index] <= threshold) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<std::size_t> stack{start_index};
|
||||
std::vector<std::size_t> component{};
|
||||
visited[start_index] = 1U;
|
||||
|
||||
while (!stack.empty()) {
|
||||
const auto cell_index = stack.back();
|
||||
stack.pop_back();
|
||||
component.push_back(cell_index);
|
||||
|
||||
const auto cell_row = cell_index / width;
|
||||
const auto cell_col = cell_index % width;
|
||||
const std::pair<std::ptrdiff_t, std::ptrdiff_t> offsets[] = {
|
||||
{-1, 0},
|
||||
{1, 0},
|
||||
{0, -1},
|
||||
{0, 1},
|
||||
};
|
||||
|
||||
for (const auto& [row_offset, col_offset] : offsets) {
|
||||
const auto next_row = static_cast<std::ptrdiff_t>(cell_row) + row_offset;
|
||||
const auto next_col = static_cast<std::ptrdiff_t>(cell_col) + col_offset;
|
||||
if (next_row < 0 || next_col < 0) {
|
||||
continue;
|
||||
}
|
||||
const bool row_out_of_bounds = next_row >= static_cast<std::ptrdiff_t>(height);
|
||||
const bool col_out_of_bounds = next_col >= static_cast<std::ptrdiff_t>(width);
|
||||
if (row_out_of_bounds || col_out_of_bounds) {
|
||||
continue;
|
||||
}
|
||||
const auto next_index =
|
||||
(static_cast<std::size_t>(next_row) * width) + static_cast<std::size_t>(next_col);
|
||||
if (visited[next_index] != 0U || smoothed[next_index] <= threshold) {
|
||||
continue;
|
||||
}
|
||||
visited[next_index] = 1U;
|
||||
stack.push_back(next_index);
|
||||
}
|
||||
}
|
||||
|
||||
if (component.size() < min_pixels) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LegacyRegionRecord region{};
|
||||
region.mask.assign(width * height, 0.0F);
|
||||
double weight_sum = 0.0;
|
||||
double x_weight_sum = 0.0;
|
||||
double z_weight_sum = 0.0;
|
||||
for (const auto cell_index : component) {
|
||||
const auto cell_row = cell_index / width;
|
||||
const auto cell_col = cell_index % width;
|
||||
const double weight = smoothed[cell_index];
|
||||
weight_sum += weight;
|
||||
x_weight_sum += grid.x_grid[cell_col] * weight;
|
||||
z_weight_sum += grid.z_grid[cell_row] * weight;
|
||||
region.mask[cell_index] = 1.0F;
|
||||
}
|
||||
|
||||
if (!(weight_sum > 0.0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
region.x_m = x_weight_sum / weight_sum;
|
||||
region.z_m = z_weight_sum / weight_sum;
|
||||
region.score = count_legacy_agreeing_ellipses(
|
||||
region.x_m,
|
||||
region.z_m,
|
||||
peaks_by_pair,
|
||||
{},
|
||||
x_tx,
|
||||
x_rx,
|
||||
velocity_mps,
|
||||
shell_sigma_m,
|
||||
LegacyPeakDomain::Apparent
|
||||
);
|
||||
region.pixel_count = static_cast<double>(component.size());
|
||||
regions.push_back(std::move(region));
|
||||
}
|
||||
}
|
||||
|
||||
return {regions, smoothed};
|
||||
}
|
||||
|
||||
[[nodiscard]] auto process_legacy_gpr(
|
||||
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;
|
||||
|
||||
const auto selection = build_geometry_selection(run_config, live_config);
|
||||
if (selection.input_positions.empty() || selection.output_positions.empty()) {
|
||||
return results;
|
||||
}
|
||||
|
||||
validate_collection_trace_order(run_config, collection);
|
||||
const auto background_mean = build_background_mean(previous_collections, selection, live_config);
|
||||
const auto selected_traces = collect_selected_traces(collection, selection, background_mean);
|
||||
if (selected_traces.empty()) {
|
||||
return results;
|
||||
}
|
||||
|
||||
const double velocity_mps =
|
||||
kSpeedOfLightMetersPerSec / std::sqrt(std::max(1e-6, static_cast<double>(run_config.gpr.relative_permittivity)));
|
||||
const double start_hz = static_cast<double>(live_config.gpr_start_freq_mhz) * 1'000'000.0;
|
||||
const double stop_hz = static_cast<double>(live_config.gpr_stop_freq_mhz) * 1'000'000.0;
|
||||
const double min_depth_m = static_cast<double>(live_config.gpr_min_depth_m);
|
||||
const double max_depth_m = static_cast<double>(live_config.gpr_max_depth_m);
|
||||
if (!(max_depth_m > min_depth_m)) {
|
||||
return results;
|
||||
}
|
||||
|
||||
std::unordered_map<PairKey, LegacyAscanResult> ascans_by_pair{};
|
||||
double bandwidth_hz = 0.0;
|
||||
for (const auto& trace : selected_traces) {
|
||||
auto ascan = compute_legacy_ascan(trace, start_hz, stop_hz, velocity_mps);
|
||||
if (ascan.amplitude.empty() || !(ascan.bandwidth_hz > 0.0)) {
|
||||
continue;
|
||||
}
|
||||
bandwidth_hz = std::max(bandwidth_hz, ascan.bandwidth_hz);
|
||||
ascans_by_pair.emplace(make_pair_key(trace.tx_local_index, trace.rx_local_index), std::move(ascan));
|
||||
}
|
||||
if (ascans_by_pair.empty() || !(bandwidth_hz > 0.0)) {
|
||||
return results;
|
||||
}
|
||||
|
||||
const auto grid = build_grid(selection.x_tx, selection.x_rx, max_depth_m, kLegacyGridZMinM);
|
||||
if (grid.x_grid.empty() || grid.z_grid.empty()) {
|
||||
return results;
|
||||
}
|
||||
|
||||
const double shell_sigma_m = velocity_mps / bandwidth_hz * 0.5;
|
||||
const double snr_thresh = std::max(0.0, static_cast<double>(live_config.gpr_snr_thresh));
|
||||
const double snr_comp_max = std::max(0.0, static_cast<double>(live_config.gpr_snr_comp_max));
|
||||
const double comp_power = std::max(0.0, static_cast<double>(live_config.gpr_comp_power));
|
||||
|
||||
std::unordered_map<PairKey, std::vector<LegacyPeakRecord>> peaks_by_pair{};
|
||||
for (std::size_t tx_index = 0U; tx_index < selection.x_tx.size(); ++tx_index) {
|
||||
for (std::size_t rx_index = 0U; rx_index < selection.x_rx.size(); ++rx_index) {
|
||||
const auto key = make_pair_key(static_cast<std::uint32_t>(tx_index), static_cast<std::uint32_t>(rx_index));
|
||||
const auto ascan_it = ascans_by_pair.find(key);
|
||||
if (ascan_it == ascans_by_pair.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& ascan = ascan_it->second;
|
||||
if (ascan.depth_m.size() < 3U || ascan.amplitude.size() < 3U) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto min_index = lower_bound_index(ascan.depth_m, min_depth_m);
|
||||
const auto max_index = lower_bound_index(ascan.depth_m, max_depth_m);
|
||||
if (max_index <= min_index + 2U || max_index > ascan.amplitude.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto noise_begin = ascan.amplitude.begin() + static_cast<std::ptrdiff_t>(min_index);
|
||||
const auto noise_end = ascan.amplitude.begin() + static_cast<std::ptrdiff_t>(max_index);
|
||||
const double noise = median_copy(std::vector<double>(noise_begin, noise_end));
|
||||
const double z_step = std::max(ascan.depth_m[1] - ascan.depth_m[0], 1e-6);
|
||||
const std::size_t min_distance = static_cast<std::size_t>(std::max(
|
||||
4.0,
|
||||
std::floor(((velocity_mps / (2.0 * bandwidth_hz)) / z_step) * 0.7)
|
||||
));
|
||||
const auto peak_indices =
|
||||
find_legacy_peak_indices(ascan.amplitude, min_index, max_index, noise * snr_thresh, min_distance);
|
||||
|
||||
auto& peaks = peaks_by_pair[key];
|
||||
peaks.reserve(peak_indices.size());
|
||||
for (const auto peak_index : peak_indices) {
|
||||
const double z_app = ascan.depth_m[peak_index];
|
||||
const double snr_raw = ascan.amplitude[peak_index] / std::max(noise, 1e-12);
|
||||
const double attenuation =
|
||||
legacy_attenuation_at_depth(tx_index, rx_index, z_app, selection.x_tx, selection.x_rx);
|
||||
const double attenuation_ref =
|
||||
legacy_attenuation_at_depth(tx_index, rx_index, 3.0, selection.x_tx, selection.x_rx);
|
||||
const double snr_comp = std::min(
|
||||
snr_raw / (std::pow(attenuation / attenuation_ref, comp_power) + 1e-12),
|
||||
snr_comp_max
|
||||
);
|
||||
peaks.push_back(
|
||||
LegacyPeakRecord{
|
||||
.z_app = z_app,
|
||||
.tau = ascan.time_s[peak_index],
|
||||
.tau_corr = ascan.time_s[peak_index],
|
||||
.z_corr = z_app,
|
||||
.snr_raw = snr_raw,
|
||||
.snr_comp = snr_comp,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (peaks_by_pair.empty()) {
|
||||
return results;
|
||||
}
|
||||
|
||||
if (live_config.gpr_algorithm == GprAlgorithm::LegacyExtended) {
|
||||
const auto [regions, smoothed_accumulator] = extended_legacy_find_regions(
|
||||
grid,
|
||||
peaks_by_pair,
|
||||
selection.x_tx,
|
||||
selection.x_rx,
|
||||
velocity_mps,
|
||||
shell_sigma_m
|
||||
);
|
||||
results.collection_payloads.push_back(
|
||||
build_image_payload("gpr_accumulator", grid.x_grid, grid.z_grid, smoothed_accumulator)
|
||||
);
|
||||
|
||||
std::vector<std::vector<float>> region_rows{};
|
||||
region_rows.reserve(regions.size());
|
||||
for (std::size_t index = 0U; index < regions.size(); ++index) {
|
||||
const auto& region = regions[index];
|
||||
region_rows.push_back(
|
||||
{
|
||||
static_cast<float>(region.x_m),
|
||||
static_cast<float>(region.z_m),
|
||||
static_cast<float>(region.score),
|
||||
static_cast<float>(region.pixel_count),
|
||||
}
|
||||
);
|
||||
results.collection_payloads.push_back(
|
||||
build_image_payload(
|
||||
"gpr_region_mask_" + std::to_string(index),
|
||||
grid.x_grid,
|
||||
grid.z_grid,
|
||||
std::vector<double>(region.mask.begin(), region.mask.end())
|
||||
)
|
||||
);
|
||||
}
|
||||
results.collection_payloads.push_back(build_table_payload("gpr_region_centers", region_rows, 4U));
|
||||
return results;
|
||||
}
|
||||
|
||||
const auto motion_timing_by_pair = build_legacy_motion_timing_by_pair(
|
||||
selected_traces,
|
||||
run_config.run_combos.size(),
|
||||
collection.capture_start_ns,
|
||||
collection.capture_end_ns,
|
||||
live_config,
|
||||
velocity_mps
|
||||
);
|
||||
apply_legacy_motion_correction(peaks_by_pair, motion_timing_by_pair, velocity_mps);
|
||||
|
||||
const auto [points, smoothed_accumulator] = clean_legacy_find_points(
|
||||
grid,
|
||||
peaks_by_pair,
|
||||
selection.x_tx,
|
||||
selection.x_rx,
|
||||
velocity_mps,
|
||||
shell_sigma_m,
|
||||
LegacyPeakDomain::Corrected
|
||||
);
|
||||
results.collection_payloads.push_back(
|
||||
build_image_payload("gpr_accumulator", grid.x_grid, grid.z_grid, smoothed_accumulator)
|
||||
);
|
||||
|
||||
std::vector<std::vector<float>> point_rows{};
|
||||
point_rows.reserve(points.size());
|
||||
for (const auto& point : points) {
|
||||
point_rows.push_back(
|
||||
{
|
||||
static_cast<float>(point.x_m),
|
||||
static_cast<float>(point.z_m),
|
||||
static_cast<float>(point.score),
|
||||
}
|
||||
);
|
||||
}
|
||||
results.collection_payloads.push_back(build_table_payload("gpr_points", point_rows, 3U));
|
||||
return results;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+18
-1
@@ -18,6 +18,7 @@ run_config_librevna.example.json
|
||||
run_config_librevna_multi.example.json
|
||||
run_config_compact_m_k209.example.json
|
||||
run_config_compact_m_k209_local_mock_switches.example.json
|
||||
run_config_simulator.example.json
|
||||
```
|
||||
|
||||
## Common Commands
|
||||
@@ -47,6 +48,23 @@ The GUI process supervisor starts the correct producer automatically:
|
||||
- `compact_m_k209` -> `build/bin/sweep_orchestrator`
|
||||
- `librevna_multi` -> `python_app.scripts.multi_device_raw_producer`
|
||||
|
||||
## Pure Simulator
|
||||
|
||||
Use `run_config_simulator.example.json` to run the full GUI pipeline without
|
||||
radar hardware or GPIO. It uses the single-LibreVNA mock producer, mock
|
||||
switches, and the synthetic `smoke_cal` / `smoke_ref` preprocessing sets stored
|
||||
under `python_app/data`.
|
||||
|
||||
Typical local check:
|
||||
|
||||
```bash
|
||||
cd /path/to/radar_system
|
||||
make
|
||||
.venv/bin/python -m python_app.gui.main
|
||||
```
|
||||
|
||||
Then load `run_config_simulator.example.json` in the GUI and press Start.
|
||||
|
||||
## Single LibreVNA
|
||||
|
||||
Use this mode when one LibreVNA is connected directly over USB to the machine
|
||||
@@ -210,4 +228,3 @@ sends one command byte and receives only binary `S11` and `S21` `float32`
|
||||
arrays.
|
||||
|
||||
Use wired Ethernet. Wi-Fi works for tests but adds jitter.
|
||||
|
||||
|
||||
@@ -38,12 +38,18 @@ class AppWindowLiveProcessingMixin:
|
||||
bscan_gain=float(self._bscan_gain.value()),
|
||||
bscan_start_freq_mhz=float(self._bscan_start_freq_mhz.value()),
|
||||
bscan_stop_freq_mhz=float(self._bscan_stop_freq_mhz.value()),
|
||||
gpr_algorithm=self._gpr_algorithm.currentText(),
|
||||
gpr_input_positions=self._parse_csv_int_list(self._gpr_input_positions_input.text()),
|
||||
gpr_output_positions=self._parse_csv_int_list(self._gpr_output_positions_input.text()),
|
||||
gpr_min_depth_m=float(self._gpr_min_depth_m.value()),
|
||||
gpr_max_depth_m=float(self._gpr_max_depth_m.value()),
|
||||
gpr_range_comp_power=float(self._gpr_range_comp_power.value()),
|
||||
gpr_angle_comp_power=float(self._gpr_angle_comp_power.value()),
|
||||
gpr_comp_power=float(self._gpr_comp_power.value()),
|
||||
gpr_speed_m_s=float(self._gpr_speed_m_s.value()),
|
||||
gpr_look_angle_deg=float(self._gpr_look_angle_deg.value()),
|
||||
gpr_snr_thresh=float(self._gpr_snr_thresh.value()),
|
||||
gpr_snr_comp_max=float(self._gpr_snr_comp_max.value()),
|
||||
gpr_start_freq_mhz=float(self._gpr_start_freq_mhz.value()),
|
||||
gpr_stop_freq_mhz=float(self._gpr_stop_freq_mhz.value()),
|
||||
gpr_background_subtract_enabled=bool(self._gpr_background_subtract_enabled.isChecked()),
|
||||
@@ -176,12 +182,15 @@ class AppWindowLiveProcessingMixin:
|
||||
elif mode == "gpr":
|
||||
self._log(
|
||||
"Processing mode selected: gpr "
|
||||
f"(inputs={self._gpr_input_positions_input.text().strip() or '<all>'}, "
|
||||
f"(algorithm={self._gpr_algorithm.currentText()}, "
|
||||
f"inputs={self._gpr_input_positions_input.text().strip() or '<all>'}, "
|
||||
f"outputs={self._gpr_output_positions_input.text().strip() or '<all>'}, "
|
||||
f"depth={self._gpr_min_depth_m.value():g}..{self._gpr_max_depth_m.value():g} m, "
|
||||
f"freq={self._gpr_start_freq_mhz.value():g}..{self._gpr_stop_freq_mhz.value():g} MHz, "
|
||||
f"range_comp={self._gpr_range_comp_power.value():g}, "
|
||||
f"angle_comp={self._gpr_angle_comp_power.value():g}, "
|
||||
f"comp={self._gpr_comp_power.value():g}, "
|
||||
f"snr={self._gpr_snr_thresh.value():g}, "
|
||||
f"background_subtract={self._gpr_background_subtract_enabled.isChecked()}, "
|
||||
f"mean_count={self._gpr_background_mean_count.value()}, "
|
||||
f"remove_sidelobes={self._gpr_remove_sidelobe_objects_enabled.isChecked()}, "
|
||||
|
||||
@@ -70,6 +70,25 @@ class AppWindowConfigProfileIOMixin:
|
||||
self._pass_through_y_min_db.setEnabled(enabled)
|
||||
self._pass_through_y_max_db.setEnabled(enabled)
|
||||
|
||||
def _sync_gpr_algorithm_controls(self) -> None:
|
||||
"""Enable only controls that affect the selected GPR algorithm."""
|
||||
backprojection_enabled = self._gpr_algorithm.currentText() == "backprojection"
|
||||
for widget in (
|
||||
self._gpr_range_comp_power,
|
||||
self._gpr_angle_comp_power,
|
||||
self._gpr_remove_sidelobe_objects_enabled,
|
||||
):
|
||||
widget.setEnabled(backprojection_enabled)
|
||||
|
||||
for widget in (
|
||||
self._gpr_comp_power,
|
||||
self._gpr_speed_m_s,
|
||||
self._gpr_look_angle_deg,
|
||||
self._gpr_snr_thresh,
|
||||
self._gpr_snr_comp_max,
|
||||
):
|
||||
widget.setEnabled(not backprojection_enabled)
|
||||
|
||||
def _apply_history_limit_from_config(self, config) -> None:
|
||||
"""Resize in-memory history buffers to match the loaded config."""
|
||||
history_limit = self._history_limit_for_config(config)
|
||||
@@ -194,6 +213,7 @@ class AppWindowConfigProfileIOMixin:
|
||||
self._bscan_start_freq_mhz,
|
||||
self._bscan_stop_freq_mhz,
|
||||
self._bscan_subtract_mean_ascan,
|
||||
self._gpr_algorithm,
|
||||
self._gpr_relative_permittivity,
|
||||
self._gpr_tx_geometry_input,
|
||||
self._gpr_rx_geometry_input,
|
||||
@@ -203,6 +223,11 @@ class AppWindowConfigProfileIOMixin:
|
||||
self._gpr_max_depth_m,
|
||||
self._gpr_range_comp_power,
|
||||
self._gpr_angle_comp_power,
|
||||
self._gpr_comp_power,
|
||||
self._gpr_speed_m_s,
|
||||
self._gpr_look_angle_deg,
|
||||
self._gpr_snr_thresh,
|
||||
self._gpr_snr_comp_max,
|
||||
self._gpr_start_freq_mhz,
|
||||
self._gpr_stop_freq_mhz,
|
||||
self._gpr_background_subtract_enabled,
|
||||
@@ -265,10 +290,16 @@ class AppWindowConfigProfileIOMixin:
|
||||
)
|
||||
self._gpr_input_positions_input.setText(str(gui_state.processing.gpr.input_positions))
|
||||
self._gpr_output_positions_input.setText(str(gui_state.processing.gpr.output_positions))
|
||||
self._set_combo_current_text(self._gpr_algorithm, gui_state.processing.gpr.algorithm)
|
||||
self._gpr_min_depth_m.setValue(float(gui_state.processing.gpr.min_depth_m))
|
||||
self._gpr_max_depth_m.setValue(float(gui_state.processing.gpr.max_depth_m))
|
||||
self._gpr_range_comp_power.setValue(float(gui_state.processing.gpr.range_comp_power))
|
||||
self._gpr_angle_comp_power.setValue(float(gui_state.processing.gpr.angle_comp_power))
|
||||
self._gpr_comp_power.setValue(float(gui_state.processing.gpr.comp_power))
|
||||
self._gpr_speed_m_s.setValue(float(gui_state.processing.gpr.speed_m_s))
|
||||
self._gpr_look_angle_deg.setValue(float(gui_state.processing.gpr.look_angle_deg))
|
||||
self._gpr_snr_thresh.setValue(float(gui_state.processing.gpr.snr_thresh))
|
||||
self._gpr_snr_comp_max.setValue(float(gui_state.processing.gpr.snr_comp_max))
|
||||
self._gpr_start_freq_mhz.setValue(float(gui_state.processing.gpr.start_freq_mhz))
|
||||
self._gpr_stop_freq_mhz.setValue(float(gui_state.processing.gpr.stop_freq_mhz))
|
||||
self._gpr_background_subtract_enabled.setChecked(
|
||||
@@ -299,6 +330,7 @@ class AppWindowConfigProfileIOMixin:
|
||||
self._gpr_geometry_signature = None
|
||||
self._gpr_selected_geometry = None
|
||||
self._sync_pass_through_y_controls()
|
||||
self._sync_gpr_algorithm_controls()
|
||||
self._refresh_preprocess_summary_labels()
|
||||
|
||||
if self._preprocess_dialog is not None:
|
||||
|
||||
@@ -252,12 +252,18 @@ class AppWindowConfigStateBuildersMixin:
|
||||
subtract_mean_ascan=bool(self._bscan_subtract_mean_ascan.isChecked()),
|
||||
),
|
||||
gpr=GuiGprStateModel(
|
||||
algorithm=self._gpr_algorithm.currentText(),
|
||||
input_positions=self._gpr_input_positions_input.text().strip(),
|
||||
output_positions=self._gpr_output_positions_input.text().strip(),
|
||||
min_depth_m=float(self._gpr_min_depth_m.value()),
|
||||
max_depth_m=float(self._gpr_max_depth_m.value()),
|
||||
range_comp_power=float(self._gpr_range_comp_power.value()),
|
||||
angle_comp_power=float(self._gpr_angle_comp_power.value()),
|
||||
comp_power=float(self._gpr_comp_power.value()),
|
||||
speed_m_s=float(self._gpr_speed_m_s.value()),
|
||||
look_angle_deg=float(self._gpr_look_angle_deg.value()),
|
||||
snr_thresh=float(self._gpr_snr_thresh.value()),
|
||||
snr_comp_max=float(self._gpr_snr_comp_max.value()),
|
||||
start_freq_mhz=float(self._gpr_start_freq_mhz.value()),
|
||||
stop_freq_mhz=float(self._gpr_stop_freq_mhz.value()),
|
||||
background_subtract_enabled=bool(self._gpr_background_subtract_enabled.isChecked()),
|
||||
|
||||
@@ -4,6 +4,8 @@ from __future__ import annotations
|
||||
|
||||
import time
|
||||
|
||||
from PyQt6.QtCore import QSignalBlocker
|
||||
|
||||
from python_app.gui.runtime.constraints import validate_processing_mode_constraints
|
||||
from python_app.gui.runtime.history import build_run_history_signature, record_result_history
|
||||
from python_app.hardware_full.single_radar_service import create_single_radar_service
|
||||
@@ -470,10 +472,21 @@ class AppWindowPipelineMixin:
|
||||
)
|
||||
|
||||
def _drain_locator_speed_updates(self) -> None:
|
||||
"""Drain queued locator speed packets; coherent BP does not use motion speed."""
|
||||
"""Apply the newest queued locator speed packet to live processing settings."""
|
||||
if self._locator_service is None:
|
||||
return
|
||||
self._locator_service.drain_speed_updates()
|
||||
speed_m_s = self._locator_service.drain_speed_updates()
|
||||
if speed_m_s is None:
|
||||
return
|
||||
|
||||
previous_speed_m_s = float(self._gpr_speed_m_s.value())
|
||||
with QSignalBlocker(self._gpr_speed_m_s):
|
||||
self._gpr_speed_m_s.setValue(float(speed_m_s))
|
||||
current_speed_m_s = float(self._gpr_speed_m_s.value())
|
||||
if current_speed_m_s == previous_speed_m_s:
|
||||
return
|
||||
|
||||
self._write_live_processing_config()
|
||||
|
||||
def _publish_locator_snapshot_from_collection(self, collection: ResultCollection) -> None:
|
||||
"""Publish one locator snapshot from a GPR result collection."""
|
||||
|
||||
@@ -154,6 +154,10 @@ def build_processing_group(owner) -> QGroupBox:
|
||||
|
||||
gpr_defaults = owner._defaults_config.gpr
|
||||
|
||||
owner._gpr_algorithm = QComboBox()
|
||||
owner._gpr_algorithm.addItems(["backprojection", "legacy_point", "legacy_extended"])
|
||||
owner._set_combo_current_text(owner._gpr_algorithm, gpr_live_defaults.algorithm)
|
||||
|
||||
owner._gpr_relative_permittivity = QDoubleSpinBox()
|
||||
owner._gpr_relative_permittivity.setDecimals(4)
|
||||
owner._gpr_relative_permittivity.setRange(0.0001, 1000.0)
|
||||
@@ -198,6 +202,36 @@ def build_processing_group(owner) -> QGroupBox:
|
||||
owner._gpr_angle_comp_power.setSingleStep(0.01)
|
||||
owner._gpr_angle_comp_power.setValue(float(gpr_live_defaults.angle_comp_power))
|
||||
|
||||
owner._gpr_comp_power = QDoubleSpinBox()
|
||||
owner._gpr_comp_power.setDecimals(3)
|
||||
owner._gpr_comp_power.setRange(0.0, 5.0)
|
||||
owner._gpr_comp_power.setSingleStep(0.05)
|
||||
owner._gpr_comp_power.setValue(float(gpr_live_defaults.comp_power))
|
||||
|
||||
owner._gpr_speed_m_s = QDoubleSpinBox()
|
||||
owner._gpr_speed_m_s.setDecimals(3)
|
||||
owner._gpr_speed_m_s.setRange(-100.0, 100.0)
|
||||
owner._gpr_speed_m_s.setSingleStep(0.01)
|
||||
owner._gpr_speed_m_s.setValue(float(gpr_live_defaults.speed_m_s))
|
||||
|
||||
owner._gpr_look_angle_deg = QDoubleSpinBox()
|
||||
owner._gpr_look_angle_deg.setDecimals(2)
|
||||
owner._gpr_look_angle_deg.setRange(-90.0, 90.0)
|
||||
owner._gpr_look_angle_deg.setSingleStep(0.1)
|
||||
owner._gpr_look_angle_deg.setValue(float(gpr_live_defaults.look_angle_deg))
|
||||
|
||||
owner._gpr_snr_thresh = QDoubleSpinBox()
|
||||
owner._gpr_snr_thresh.setDecimals(2)
|
||||
owner._gpr_snr_thresh.setRange(0.0, 1_000.0)
|
||||
owner._gpr_snr_thresh.setSingleStep(0.1)
|
||||
owner._gpr_snr_thresh.setValue(float(gpr_live_defaults.snr_thresh))
|
||||
|
||||
owner._gpr_snr_comp_max = QDoubleSpinBox()
|
||||
owner._gpr_snr_comp_max.setDecimals(2)
|
||||
owner._gpr_snr_comp_max.setRange(0.0, 1_000.0)
|
||||
owner._gpr_snr_comp_max.setSingleStep(0.5)
|
||||
owner._gpr_snr_comp_max.setValue(float(gpr_live_defaults.snr_comp_max))
|
||||
|
||||
owner._gpr_start_freq_mhz = QDoubleSpinBox()
|
||||
owner._gpr_start_freq_mhz.setDecimals(1)
|
||||
owner._gpr_start_freq_mhz.setRange(100.0, 8800.0)
|
||||
@@ -220,6 +254,8 @@ def build_processing_group(owner) -> QGroupBox:
|
||||
owner._gpr_remove_sidelobe_objects_enabled = QCheckBox("Remove sidelobe objects")
|
||||
owner._gpr_remove_sidelobe_objects_enabled.setChecked(bool(gpr_live_defaults.remove_sidelobe_objects_enabled))
|
||||
|
||||
owner._sync_gpr_algorithm_controls()
|
||||
|
||||
owner._gpr_render_mode = QComboBox()
|
||||
owner._gpr_render_mode.addItems(["heatmap", "objects_only"])
|
||||
owner._set_combo_current_text(owner._gpr_render_mode, gpr_live_defaults.render_mode)
|
||||
@@ -257,6 +293,7 @@ def build_processing_group(owner) -> QGroupBox:
|
||||
gpr_page = _build_processing_mode_page(
|
||||
owner._processing_mode_pages,
|
||||
[
|
||||
("Algorithm", owner._gpr_algorithm),
|
||||
("Relative permittivity", owner._gpr_relative_permittivity),
|
||||
("Input positions", owner._gpr_input_positions_input),
|
||||
("Output positions", owner._gpr_output_positions_input),
|
||||
@@ -264,12 +301,17 @@ def build_processing_group(owner) -> QGroupBox:
|
||||
("Max depth m", owner._gpr_max_depth_m),
|
||||
("Range comp power", owner._gpr_range_comp_power),
|
||||
("Angle comp power", owner._gpr_angle_comp_power),
|
||||
("Comp power", owner._gpr_comp_power),
|
||||
("SNR thresh", owner._gpr_snr_thresh),
|
||||
("SNR comp max", owner._gpr_snr_comp_max),
|
||||
("Render mode", owner._gpr_render_mode),
|
||||
("Min visible score", owner._gpr_min_visible_score),
|
||||
("Tx geometry", owner._gpr_tx_geometry_input),
|
||||
("Rx geometry", owner._gpr_rx_geometry_input),
|
||||
("Start MHz", owner._gpr_start_freq_mhz),
|
||||
("Stop MHz", owner._gpr_stop_freq_mhz),
|
||||
("Speed m/s", owner._gpr_speed_m_s),
|
||||
("Look angle deg", owner._gpr_look_angle_deg),
|
||||
("Visible X min m", owner._gpr_visible_x_min_m),
|
||||
("Visible X max m", owner._gpr_visible_x_max_m),
|
||||
("Visible Z min m", owner._gpr_visible_z_min_m),
|
||||
@@ -278,7 +320,7 @@ def build_processing_group(owner) -> QGroupBox:
|
||||
("Mean count", owner._gpr_background_mean_count),
|
||||
owner._gpr_remove_sidelobe_objects_enabled,
|
||||
],
|
||||
split_index=10,
|
||||
split_index=12,
|
||||
)
|
||||
owner._processing_mode_pages.addWidget(gpr_page)
|
||||
|
||||
@@ -299,12 +341,19 @@ def build_processing_group(owner) -> QGroupBox:
|
||||
owner._bscan_start_freq_mhz.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._bscan_stop_freq_mhz.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._bscan_subtract_mean_ascan.toggled.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_algorithm.currentTextChanged.connect(owner._sync_gpr_algorithm_controls)
|
||||
owner._gpr_algorithm.currentTextChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_input_positions_input.editingFinished.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_output_positions_input.editingFinished.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_min_depth_m.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_max_depth_m.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_range_comp_power.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_angle_comp_power.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_comp_power.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_speed_m_s.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_look_angle_deg.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_snr_thresh.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_snr_comp_max.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_start_freq_mhz.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_stop_freq_mhz.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||
owner._gpr_background_subtract_enabled.toggled.connect(owner._on_processing_live_settings_changed)
|
||||
|
||||
@@ -91,6 +91,13 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
|
||||
pass_through_object = _as_dict(processing_object.get("pass_through"), "gui.processing.pass_through")
|
||||
bscan_object = _as_dict(processing_object.get("bscan"), "gui.processing.bscan")
|
||||
gpr_object = _as_dict(processing_object.get("gpr"), "gui.processing.gpr")
|
||||
root_gpr_object = payload.get("gpr")
|
||||
gpr_algorithm_default = gui.processing.gpr.algorithm
|
||||
if isinstance(root_gpr_object, dict):
|
||||
if root_gpr_object.get("mode") == "point":
|
||||
gpr_algorithm_default = "legacy_point"
|
||||
elif root_gpr_object.get("mode") == "extended":
|
||||
gpr_algorithm_default = "legacy_extended"
|
||||
gui.processing = GuiProcessingStateModel(
|
||||
selected_mode=_optional_string(
|
||||
processing_object,
|
||||
@@ -160,6 +167,12 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
|
||||
),
|
||||
),
|
||||
gpr=GuiGprStateModel(
|
||||
algorithm=_optional_string(
|
||||
gpr_object,
|
||||
"algorithm",
|
||||
gpr_algorithm_default,
|
||||
"gui.processing.gpr",
|
||||
),
|
||||
input_positions=_optional_string(
|
||||
gpr_object,
|
||||
"input_positions",
|
||||
@@ -196,6 +209,36 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
|
||||
gui.processing.gpr.angle_comp_power,
|
||||
"gui.processing.gpr",
|
||||
),
|
||||
comp_power=_optional_float(
|
||||
gpr_object,
|
||||
"comp_power",
|
||||
gui.processing.gpr.comp_power,
|
||||
"gui.processing.gpr",
|
||||
),
|
||||
speed_m_s=_optional_float(
|
||||
gpr_object,
|
||||
"speed_m_s",
|
||||
gui.processing.gpr.speed_m_s,
|
||||
"gui.processing.gpr",
|
||||
),
|
||||
look_angle_deg=_optional_float(
|
||||
gpr_object,
|
||||
"look_angle_deg",
|
||||
gui.processing.gpr.look_angle_deg,
|
||||
"gui.processing.gpr",
|
||||
),
|
||||
snr_thresh=_optional_float(
|
||||
gpr_object,
|
||||
"snr_thresh",
|
||||
gui.processing.gpr.snr_thresh,
|
||||
"gui.processing.gpr",
|
||||
),
|
||||
snr_comp_max=_optional_float(
|
||||
gpr_object,
|
||||
"snr_comp_max",
|
||||
gui.processing.gpr.snr_comp_max,
|
||||
"gui.processing.gpr",
|
||||
),
|
||||
start_freq_mhz=_optional_float(
|
||||
gpr_object,
|
||||
"start_freq_mhz",
|
||||
@@ -268,12 +311,20 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
|
||||
raise ValueError("gui.processing.selected_mode must be one of: pass_through, bscan, gpr")
|
||||
if gui.processing.bscan.axis not in {"abs", "real", "phase"}:
|
||||
raise ValueError("gui.processing.bscan.axis must be one of: abs, real, phase")
|
||||
if gui.processing.gpr.algorithm not in {"backprojection", "legacy_point", "legacy_extended"}:
|
||||
raise ValueError("gui.processing.gpr.algorithm must be one of: backprojection, legacy_point, legacy_extended")
|
||||
if gui.processing.gpr.render_mode not in {"heatmap", "objects_only"}:
|
||||
raise ValueError("gui.processing.gpr.render_mode must be one of: heatmap, objects_only")
|
||||
if gui.processing.gpr.range_comp_power < 0.0:
|
||||
raise ValueError("gui.processing.gpr.range_comp_power must be >= 0")
|
||||
if gui.processing.gpr.angle_comp_power < 0.0:
|
||||
raise ValueError("gui.processing.gpr.angle_comp_power must be >= 0")
|
||||
if gui.processing.gpr.comp_power < 0.0:
|
||||
raise ValueError("gui.processing.gpr.comp_power must be >= 0")
|
||||
if gui.processing.gpr.snr_thresh < 0.0:
|
||||
raise ValueError("gui.processing.gpr.snr_thresh must be >= 0")
|
||||
if gui.processing.gpr.snr_comp_max < 0.0:
|
||||
raise ValueError("gui.processing.gpr.snr_comp_max must be >= 0")
|
||||
if gui.processing.gpr.min_visible_score < 0.0:
|
||||
raise ValueError("gui.processing.gpr.min_visible_score must be >= 0")
|
||||
|
||||
@@ -356,12 +407,18 @@ def gui_profile_to_dict(model: GuiProfileModel) -> dict[str, Any]:
|
||||
"subtract_mean_ascan": gui.processing.bscan.subtract_mean_ascan,
|
||||
},
|
||||
"gpr": {
|
||||
"algorithm": gui.processing.gpr.algorithm,
|
||||
"input_positions": gui.processing.gpr.input_positions,
|
||||
"output_positions": gui.processing.gpr.output_positions,
|
||||
"min_depth_m": gui.processing.gpr.min_depth_m,
|
||||
"max_depth_m": gui.processing.gpr.max_depth_m,
|
||||
"range_comp_power": gui.processing.gpr.range_comp_power,
|
||||
"angle_comp_power": gui.processing.gpr.angle_comp_power,
|
||||
"comp_power": gui.processing.gpr.comp_power,
|
||||
"speed_m_s": gui.processing.gpr.speed_m_s,
|
||||
"look_angle_deg": gui.processing.gpr.look_angle_deg,
|
||||
"snr_thresh": gui.processing.gpr.snr_thresh,
|
||||
"snr_comp_max": gui.processing.gpr.snr_comp_max,
|
||||
"start_freq_mhz": gui.processing.gpr.start_freq_mhz,
|
||||
"stop_freq_mhz": gui.processing.gpr.stop_freq_mhz,
|
||||
"background_subtract_enabled": gui.processing.gpr.background_subtract_enabled,
|
||||
|
||||
@@ -49,12 +49,18 @@ class GuiBscanStateModel:
|
||||
class GuiGprStateModel:
|
||||
"""UI-only defaults for GPR live settings."""
|
||||
|
||||
algorithm: str = "backprojection"
|
||||
input_positions: str = ""
|
||||
output_positions: str = ""
|
||||
min_depth_m: float = 2.0
|
||||
max_depth_m: float = 14.0
|
||||
range_comp_power: float = 0.28
|
||||
angle_comp_power: float = 0.10
|
||||
comp_power: float = 0.2
|
||||
speed_m_s: float = 0.0
|
||||
look_angle_deg: float = 0.0
|
||||
snr_thresh: float = 4.5
|
||||
snr_comp_max: float = 25.0
|
||||
start_freq_mhz: float = 3000.0
|
||||
stop_freq_mhz: float = 6000.0
|
||||
background_subtract_enabled: bool = True
|
||||
|
||||
@@ -23,12 +23,18 @@ class ProcessingLiveConfig:
|
||||
bscan_gain: float = 1.0
|
||||
bscan_start_freq_mhz: float = 100.0
|
||||
bscan_stop_freq_mhz: float = 8800.0
|
||||
gpr_algorithm: str = "backprojection"
|
||||
gpr_input_positions: list[int] | None = None
|
||||
gpr_output_positions: list[int] | None = None
|
||||
gpr_min_depth_m: float = 2.0
|
||||
gpr_max_depth_m: float = 14.0
|
||||
gpr_range_comp_power: float = 0.28
|
||||
gpr_angle_comp_power: float = 0.10
|
||||
gpr_comp_power: float = 0.2
|
||||
gpr_speed_m_s: float = 0.0
|
||||
gpr_look_angle_deg: float = 0.0
|
||||
gpr_snr_thresh: float = 4.5
|
||||
gpr_snr_comp_max: float = 25.0
|
||||
gpr_start_freq_mhz: float = 3000.0
|
||||
gpr_stop_freq_mhz: float = 6000.0
|
||||
gpr_background_subtract_enabled: bool = True
|
||||
@@ -63,12 +69,18 @@ class ProcessingLiveConfig:
|
||||
"bscan_gain": float(self.bscan_gain),
|
||||
"bscan_start_freq_mhz": float(self.bscan_start_freq_mhz),
|
||||
"bscan_stop_freq_mhz": float(self.bscan_stop_freq_mhz),
|
||||
"gpr_algorithm": str(self.gpr_algorithm),
|
||||
"gpr_input_positions": [int(value) for value in self.gpr_input_positions],
|
||||
"gpr_output_positions": [int(value) for value in self.gpr_output_positions],
|
||||
"gpr_min_depth_m": float(self.gpr_min_depth_m),
|
||||
"gpr_max_depth_m": float(self.gpr_max_depth_m),
|
||||
"gpr_range_comp_power": float(self.gpr_range_comp_power),
|
||||
"gpr_angle_comp_power": float(self.gpr_angle_comp_power),
|
||||
"gpr_comp_power": float(self.gpr_comp_power),
|
||||
"gpr_speed_m_s": float(self.gpr_speed_m_s),
|
||||
"gpr_look_angle_deg": float(self.gpr_look_angle_deg),
|
||||
"gpr_snr_thresh": float(self.gpr_snr_thresh),
|
||||
"gpr_snr_comp_max": float(self.gpr_snr_comp_max),
|
||||
"gpr_start_freq_mhz": float(self.gpr_start_freq_mhz),
|
||||
"gpr_stop_freq_mhz": float(self.gpr_stop_freq_mhz),
|
||||
"gpr_background_subtract_enabled": bool(self.gpr_background_subtract_enabled),
|
||||
|
||||
+27
-26
@@ -1,23 +1,18 @@
|
||||
{
|
||||
"radar": {
|
||||
"model": "librevna_multi",
|
||||
"serial": "207730885532",
|
||||
"remote_host": "127.0.0.1",
|
||||
"remote_port": 50209,
|
||||
"driver_mode": "native",
|
||||
"model": "librevna",
|
||||
"serial": "",
|
||||
"driver_mode": "mock",
|
||||
"mock_signal_hz": 5000000.0,
|
||||
"multi_device": {
|
||||
"slave_serials": [
|
||||
"20A1307D5532",
|
||||
"2072306C5532"
|
||||
],
|
||||
"force_external_reference": true,
|
||||
"slave_serials": [],
|
||||
"force_external_reference": false,
|
||||
"recovery_attempts": 3
|
||||
},
|
||||
"sweep": {
|
||||
"start_hz": 1000000.0,
|
||||
"stop_hz": 6000000000.0,
|
||||
"points": 4501,
|
||||
"points": 201,
|
||||
"if_bandwidth_hz": 50000.0,
|
||||
"stimulus_power_dbm": -10.0
|
||||
}
|
||||
@@ -52,7 +47,7 @@
|
||||
"settling_ms": 0,
|
||||
"idle_sleep_ms": 2,
|
||||
"continuous": true,
|
||||
"processing_live_config_path": "/home/europa/Documents/radar_system/python_app/runtime/processing_live.json",
|
||||
"processing_live_config_path": "python_app/runtime/processing_live.json",
|
||||
"locator_server": {
|
||||
"device_id": 3,
|
||||
"protocol_version": 1,
|
||||
@@ -100,11 +95,11 @@
|
||||
"preprocess": {
|
||||
"s21": {
|
||||
"calibration": {
|
||||
"set_name": "set_001",
|
||||
"set_name": "smoke_cal",
|
||||
"bundle_path": ""
|
||||
},
|
||||
"reference": {
|
||||
"set_name": "set_001",
|
||||
"set_name": "smoke_ref",
|
||||
"bundle_path": ""
|
||||
}
|
||||
},
|
||||
@@ -168,27 +163,27 @@
|
||||
},
|
||||
"rings": {
|
||||
"raw": {
|
||||
"name": "/radar_raw",
|
||||
"name": "/radar_raw_simulator",
|
||||
"capacity": 50,
|
||||
"slot_size_bytes": 2097152
|
||||
},
|
||||
"raw_tap": {
|
||||
"name": "/radar_raw_tap",
|
||||
"name": "/radar_raw_tap_simulator",
|
||||
"capacity": 50,
|
||||
"slot_size_bytes": 2097152
|
||||
},
|
||||
"preprocessed": {
|
||||
"name": "/radar_preprocessed",
|
||||
"name": "/radar_preprocessed_simulator",
|
||||
"capacity": 50,
|
||||
"slot_size_bytes": 2097152
|
||||
},
|
||||
"preprocessed_tap": {
|
||||
"name": "/radar_preprocessed_tap",
|
||||
"name": "/radar_preprocessed_tap_simulator",
|
||||
"capacity": 50,
|
||||
"slot_size_bytes": 2097152
|
||||
},
|
||||
"results": {
|
||||
"name": "/radar_results",
|
||||
"name": "/radar_results_simulator",
|
||||
"capacity": 50,
|
||||
"slot_size_bytes": 2097152
|
||||
}
|
||||
@@ -220,32 +215,38 @@
|
||||
"subtract_mean_ascan": false
|
||||
},
|
||||
"gpr": {
|
||||
"algorithm": "backprojection",
|
||||
"input_positions": "0,1,2,3",
|
||||
"output_positions": "0,1",
|
||||
"min_depth_m": 2.0,
|
||||
"max_depth_m": 14.0,
|
||||
"range_comp_power": 0.28,
|
||||
"angle_comp_power": 0.1,
|
||||
"comp_power": 0.2,
|
||||
"speed_m_s": 0.0,
|
||||
"look_angle_deg": 0.0,
|
||||
"snr_thresh": 4.5,
|
||||
"snr_comp_max": 25.0,
|
||||
"start_freq_mhz": 3000.0,
|
||||
"stop_freq_mhz": 6000.0,
|
||||
"background_subtract_enabled": true,
|
||||
"background_mean_count": 10,
|
||||
"remove_sidelobe_objects_enabled": false,
|
||||
"render_mode": "heatmap",
|
||||
"min_visible_score": 0.049999999999999684,
|
||||
"visible_x_min_m": -1.609999999999999,
|
||||
"visible_x_max_m": 1.1099999999999985,
|
||||
"visible_z_min_m": 0.30000000000000004,
|
||||
"min_visible_score": 0.0,
|
||||
"visible_x_min_m": -2.0,
|
||||
"visible_x_max_m": 2.0,
|
||||
"visible_z_min_m": 0.0,
|
||||
"visible_z_max_m": 14.0
|
||||
}
|
||||
},
|
||||
"data_actions": {
|
||||
"save_count": 10,
|
||||
"save_path": "/home/europa/Documents/radar_system/python_app/data/snapshots",
|
||||
"save_name": "snapshot_manual"
|
||||
"save_path": "python_app/data/snapshots",
|
||||
"save_name": "snapshot_simulator"
|
||||
},
|
||||
"preprocess_dialog": {
|
||||
"set_name": "set_001",
|
||||
"set_name": "smoke_cal",
|
||||
"radar_config_dir": "",
|
||||
"use_all_radar_configs": false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
{
|
||||
"radar": {
|
||||
"model": "librevna",
|
||||
"serial": "",
|
||||
"driver_mode": "mock",
|
||||
"mock_signal_hz": 5000000.0,
|
||||
"multi_device": {
|
||||
"slave_serials": [],
|
||||
"force_external_reference": false,
|
||||
"recovery_attempts": 3
|
||||
},
|
||||
"sweep": {
|
||||
"start_hz": 1000000.0,
|
||||
"stop_hz": 6000000000.0,
|
||||
"points": 201,
|
||||
"if_bandwidth_hz": 50000.0,
|
||||
"stimulus_power_dbm": -10.0
|
||||
}
|
||||
},
|
||||
"switches": {
|
||||
"port1": {
|
||||
"name": "port1",
|
||||
"driver_mode": "mock",
|
||||
"driver": "h7992",
|
||||
"radar_port": 1,
|
||||
"positions": 2,
|
||||
"default_position": 0,
|
||||
"gpio_chip": "/dev/gpiochip0",
|
||||
"pin_a": 17,
|
||||
"pin_b": 27,
|
||||
"invert_logic": false
|
||||
},
|
||||
"port2": {
|
||||
"name": "port2",
|
||||
"driver_mode": "mock",
|
||||
"driver": "h7992",
|
||||
"radar_port": 2,
|
||||
"positions": 4,
|
||||
"default_position": 0,
|
||||
"gpio_chip": "/dev/gpiochip0",
|
||||
"pin_a": 22,
|
||||
"pin_b": 23,
|
||||
"invert_logic": false
|
||||
}
|
||||
},
|
||||
"run": {
|
||||
"settling_ms": 0,
|
||||
"idle_sleep_ms": 2,
|
||||
"continuous": true,
|
||||
"processing_live_config_path": "python_app/runtime/processing_live.json",
|
||||
"locator_server": {
|
||||
"device_id": 3,
|
||||
"protocol_version": 1,
|
||||
"host": "0.0.0.0",
|
||||
"port": 8888,
|
||||
"max_payload_bytes": 65536,
|
||||
"client_queue_size": 32,
|
||||
"logger_name": "locator_runtime"
|
||||
},
|
||||
"combos": [
|
||||
{
|
||||
"input": 0,
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"input": 1,
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"input": 2,
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"input": 3,
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"input": 0,
|
||||
"output": 1
|
||||
},
|
||||
{
|
||||
"input": 1,
|
||||
"output": 1
|
||||
},
|
||||
{
|
||||
"input": 2,
|
||||
"output": 1
|
||||
},
|
||||
{
|
||||
"input": 3,
|
||||
"output": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"preprocess": {
|
||||
"s21": {
|
||||
"calibration": {
|
||||
"set_name": "smoke_cal",
|
||||
"bundle_path": ""
|
||||
},
|
||||
"reference": {
|
||||
"set_name": "smoke_ref",
|
||||
"bundle_path": ""
|
||||
}
|
||||
},
|
||||
"s11": {
|
||||
"calibration": {
|
||||
"open": {
|
||||
"set_name": "",
|
||||
"bundle_path": ""
|
||||
},
|
||||
"short": {
|
||||
"set_name": "",
|
||||
"bundle_path": ""
|
||||
},
|
||||
"load": {
|
||||
"set_name": "",
|
||||
"bundle_path": ""
|
||||
}
|
||||
},
|
||||
"reference": {
|
||||
"set_name": "",
|
||||
"bundle_path": ""
|
||||
}
|
||||
},
|
||||
"notch": {
|
||||
"enabled": true,
|
||||
"bands_hz": [],
|
||||
"taper_width_hz": 40000000.0,
|
||||
"taper_type": "cosine"
|
||||
}
|
||||
},
|
||||
"gpr": {
|
||||
"relative_permittivity": 1.0,
|
||||
"tx_geometry": [
|
||||
{
|
||||
"output_pos": 0,
|
||||
"x_m": 0.905
|
||||
},
|
||||
{
|
||||
"output_pos": 1,
|
||||
"x_m": -0.905
|
||||
}
|
||||
],
|
||||
"rx_geometry": [
|
||||
{
|
||||
"input_pos": 0,
|
||||
"x_m": -0.18
|
||||
},
|
||||
{
|
||||
"input_pos": 1,
|
||||
"x_m": 0.485
|
||||
},
|
||||
{
|
||||
"input_pos": 2,
|
||||
"x_m": -0.49
|
||||
},
|
||||
{
|
||||
"input_pos": 3,
|
||||
"x_m": 0.185
|
||||
}
|
||||
]
|
||||
},
|
||||
"rings": {
|
||||
"raw": {
|
||||
"name": "/radar_raw_simulator",
|
||||
"capacity": 50,
|
||||
"slot_size_bytes": 2097152
|
||||
},
|
||||
"raw_tap": {
|
||||
"name": "/radar_raw_tap_simulator",
|
||||
"capacity": 50,
|
||||
"slot_size_bytes": 2097152
|
||||
},
|
||||
"preprocessed": {
|
||||
"name": "/radar_preprocessed_simulator",
|
||||
"capacity": 50,
|
||||
"slot_size_bytes": 2097152
|
||||
},
|
||||
"preprocessed_tap": {
|
||||
"name": "/radar_preprocessed_tap_simulator",
|
||||
"capacity": 50,
|
||||
"slot_size_bytes": 2097152
|
||||
},
|
||||
"results": {
|
||||
"name": "/radar_results_simulator",
|
||||
"capacity": 50,
|
||||
"slot_size_bytes": 2097152
|
||||
}
|
||||
},
|
||||
"gui": {
|
||||
"version": 1,
|
||||
"switches": {
|
||||
"combo_mode": "text",
|
||||
"combos_text": "0:0,1:0,2:0,3:0,0:1,1:1,2:1,3:1",
|
||||
"single_input": "0",
|
||||
"single_output": "0"
|
||||
},
|
||||
"processing": {
|
||||
"selected_mode": "gpr",
|
||||
"pass_through": {
|
||||
"show_magnitude": true,
|
||||
"show_phase": false,
|
||||
"fixed_y_enabled": false,
|
||||
"y_min_db": -100.0,
|
||||
"y_max_db": 0.0
|
||||
},
|
||||
"bscan": {
|
||||
"axis": "abs",
|
||||
"cut_m": 0.0,
|
||||
"max_depth_m": 3.0,
|
||||
"gain": 1.0,
|
||||
"start_freq_mhz": 100.0,
|
||||
"stop_freq_mhz": 6000.0,
|
||||
"subtract_mean_ascan": false
|
||||
},
|
||||
"gpr": {
|
||||
"algorithm": "backprojection",
|
||||
"input_positions": "0,1,2,3",
|
||||
"output_positions": "0,1",
|
||||
"min_depth_m": 2.0,
|
||||
"max_depth_m": 14.0,
|
||||
"range_comp_power": 0.28,
|
||||
"angle_comp_power": 0.1,
|
||||
"comp_power": 0.2,
|
||||
"speed_m_s": 0.0,
|
||||
"look_angle_deg": 0.0,
|
||||
"snr_thresh": 4.5,
|
||||
"snr_comp_max": 25.0,
|
||||
"start_freq_mhz": 3000.0,
|
||||
"stop_freq_mhz": 6000.0,
|
||||
"background_subtract_enabled": true,
|
||||
"background_mean_count": 10,
|
||||
"remove_sidelobe_objects_enabled": false,
|
||||
"render_mode": "heatmap",
|
||||
"min_visible_score": 0.0,
|
||||
"visible_x_min_m": -2.0,
|
||||
"visible_x_max_m": 2.0,
|
||||
"visible_z_min_m": 0.0,
|
||||
"visible_z_max_m": 14.0
|
||||
}
|
||||
},
|
||||
"data_actions": {
|
||||
"save_count": 10,
|
||||
"save_path": "python_app/data/snapshots",
|
||||
"save_name": "snapshot_simulator"
|
||||
},
|
||||
"preprocess_dialog": {
|
||||
"set_name": "smoke_cal",
|
||||
"radar_config_dir": "",
|
||||
"use_all_radar_configs": false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user