added legacy gpr

This commit is contained in:
Ayzen
2026-05-06 11:54:59 +03:00
parent e86f30023e
commit 1b7d9e417d
16 changed files with 2697 additions and 1335 deletions
@@ -13,6 +13,12 @@ enum class HistoryCommand {
ClearAll, ClearAll,
}; };
enum class GprAlgorithm {
Backprojection,
LegacyPoint,
LegacyExtended,
};
struct ProcessingLiveConfig { struct ProcessingLiveConfig {
std::string processor_mode = "pass_through"; std::string processor_mode = "pass_through";
std::string pass_through_channel = "s21"; std::string pass_through_channel = "s21";
@@ -26,12 +32,18 @@ struct ProcessingLiveConfig {
float bscan_gain = 1.0F; float bscan_gain = 1.0F;
float bscan_start_freq_mhz = 100.0F; float bscan_start_freq_mhz = 100.0F;
float bscan_stop_freq_mhz = 8800.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_input_positions{};
std::vector<std::uint32_t> gpr_output_positions{}; std::vector<std::uint32_t> gpr_output_positions{};
float gpr_min_depth_m = 2.0F; float gpr_min_depth_m = 2.0F;
float gpr_max_depth_m = 14.0F; float gpr_max_depth_m = 14.0F;
float gpr_range_comp_power = 0.28F; float gpr_range_comp_power = 0.28F;
float gpr_angle_comp_power = 0.10F; 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_start_freq_mhz = 3000.0F;
float gpr_stop_freq_mhz = 6000.0F; float gpr_stop_freq_mhz = 6000.0F;
bool gpr_background_subtract_enabled = true; 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"); 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 { [[nodiscard]] auto parse_s_parameter_channel(const std::string& value, const std::string& field_name) -> std::string {
if (value == "s21" || value == "s11") { if (value == "s21" || value == "s11") {
return value; return value;
@@ -164,6 +179,12 @@ using Json = nlohmann::json;
} }
config.bscan_stop_freq_mhz = static_cast<float>(found->get<double>()); 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()) { if (const auto found = root.find("gpr_input_positions"); found != root.end()) {
config.gpr_input_positions = parse_u32_array(*found, "processing.gpr_input_positions"); 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>()); 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 (const auto found = root.find("gpr_start_freq_mhz"); found != root.end()) {
if (!found->is_number()) { if (!found->is_number()) {
throw std::runtime_error("processing.gpr_start_freq_mhz must be number"); throw std::runtime_error("processing.gpr_start_freq_mhz must be number");
@@ -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
View File
@@ -18,6 +18,7 @@ run_config_librevna.example.json
run_config_librevna_multi.example.json run_config_librevna_multi.example.json
run_config_compact_m_k209.example.json run_config_compact_m_k209.example.json
run_config_compact_m_k209_local_mock_switches.example.json run_config_compact_m_k209_local_mock_switches.example.json
run_config_simulator.example.json
``` ```
## Common Commands ## Common Commands
@@ -47,6 +48,23 @@ The GUI process supervisor starts the correct producer automatically:
- `compact_m_k209` -> `build/bin/sweep_orchestrator` - `compact_m_k209` -> `build/bin/sweep_orchestrator`
- `librevna_multi` -> `python_app.scripts.multi_device_raw_producer` - `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 ## Single LibreVNA
Use this mode when one LibreVNA is connected directly over USB to the machine 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. arrays.
Use wired Ethernet. Wi-Fi works for tests but adds jitter. 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_gain=float(self._bscan_gain.value()),
bscan_start_freq_mhz=float(self._bscan_start_freq_mhz.value()), bscan_start_freq_mhz=float(self._bscan_start_freq_mhz.value()),
bscan_stop_freq_mhz=float(self._bscan_stop_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_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_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_min_depth_m=float(self._gpr_min_depth_m.value()),
gpr_max_depth_m=float(self._gpr_max_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_range_comp_power=float(self._gpr_range_comp_power.value()),
gpr_angle_comp_power=float(self._gpr_angle_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_start_freq_mhz=float(self._gpr_start_freq_mhz.value()),
gpr_stop_freq_mhz=float(self._gpr_stop_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()), gpr_background_subtract_enabled=bool(self._gpr_background_subtract_enabled.isChecked()),
@@ -176,12 +182,15 @@ class AppWindowLiveProcessingMixin:
elif mode == "gpr": elif mode == "gpr":
self._log( self._log(
"Processing mode selected: gpr " "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"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"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"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"range_comp={self._gpr_range_comp_power.value():g}, "
f"angle_comp={self._gpr_angle_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"background_subtract={self._gpr_background_subtract_enabled.isChecked()}, "
f"mean_count={self._gpr_background_mean_count.value()}, " f"mean_count={self._gpr_background_mean_count.value()}, "
f"remove_sidelobes={self._gpr_remove_sidelobe_objects_enabled.isChecked()}, " 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_min_db.setEnabled(enabled)
self._pass_through_y_max_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: def _apply_history_limit_from_config(self, config) -> None:
"""Resize in-memory history buffers to match the loaded config.""" """Resize in-memory history buffers to match the loaded config."""
history_limit = self._history_limit_for_config(config) history_limit = self._history_limit_for_config(config)
@@ -194,6 +213,7 @@ class AppWindowConfigProfileIOMixin:
self._bscan_start_freq_mhz, self._bscan_start_freq_mhz,
self._bscan_stop_freq_mhz, self._bscan_stop_freq_mhz,
self._bscan_subtract_mean_ascan, self._bscan_subtract_mean_ascan,
self._gpr_algorithm,
self._gpr_relative_permittivity, self._gpr_relative_permittivity,
self._gpr_tx_geometry_input, self._gpr_tx_geometry_input,
self._gpr_rx_geometry_input, self._gpr_rx_geometry_input,
@@ -203,6 +223,11 @@ class AppWindowConfigProfileIOMixin:
self._gpr_max_depth_m, self._gpr_max_depth_m,
self._gpr_range_comp_power, self._gpr_range_comp_power,
self._gpr_angle_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_start_freq_mhz,
self._gpr_stop_freq_mhz, self._gpr_stop_freq_mhz,
self._gpr_background_subtract_enabled, 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_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._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_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_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_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_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_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_stop_freq_mhz.setValue(float(gui_state.processing.gpr.stop_freq_mhz))
self._gpr_background_subtract_enabled.setChecked( self._gpr_background_subtract_enabled.setChecked(
@@ -299,6 +330,7 @@ class AppWindowConfigProfileIOMixin:
self._gpr_geometry_signature = None self._gpr_geometry_signature = None
self._gpr_selected_geometry = None self._gpr_selected_geometry = None
self._sync_pass_through_y_controls() self._sync_pass_through_y_controls()
self._sync_gpr_algorithm_controls()
self._refresh_preprocess_summary_labels() self._refresh_preprocess_summary_labels()
if self._preprocess_dialog is not None: if self._preprocess_dialog is not None:
@@ -252,12 +252,18 @@ class AppWindowConfigStateBuildersMixin:
subtract_mean_ascan=bool(self._bscan_subtract_mean_ascan.isChecked()), subtract_mean_ascan=bool(self._bscan_subtract_mean_ascan.isChecked()),
), ),
gpr=GuiGprStateModel( gpr=GuiGprStateModel(
algorithm=self._gpr_algorithm.currentText(),
input_positions=self._gpr_input_positions_input.text().strip(), input_positions=self._gpr_input_positions_input.text().strip(),
output_positions=self._gpr_output_positions_input.text().strip(), output_positions=self._gpr_output_positions_input.text().strip(),
min_depth_m=float(self._gpr_min_depth_m.value()), min_depth_m=float(self._gpr_min_depth_m.value()),
max_depth_m=float(self._gpr_max_depth_m.value()), max_depth_m=float(self._gpr_max_depth_m.value()),
range_comp_power=float(self._gpr_range_comp_power.value()), range_comp_power=float(self._gpr_range_comp_power.value()),
angle_comp_power=float(self._gpr_angle_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()), start_freq_mhz=float(self._gpr_start_freq_mhz.value()),
stop_freq_mhz=float(self._gpr_stop_freq_mhz.value()), stop_freq_mhz=float(self._gpr_stop_freq_mhz.value()),
background_subtract_enabled=bool(self._gpr_background_subtract_enabled.isChecked()), background_subtract_enabled=bool(self._gpr_background_subtract_enabled.isChecked()),
@@ -4,6 +4,8 @@ from __future__ import annotations
import time import time
from PyQt6.QtCore import QSignalBlocker
from python_app.gui.runtime.constraints import validate_processing_mode_constraints 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.gui.runtime.history import build_run_history_signature, record_result_history
from python_app.hardware_full.single_radar_service import create_single_radar_service 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: 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: if self._locator_service is None:
return 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: def _publish_locator_snapshot_from_collection(self, collection: ResultCollection) -> None:
"""Publish one locator snapshot from a GPR result collection.""" """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 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 = QDoubleSpinBox()
owner._gpr_relative_permittivity.setDecimals(4) owner._gpr_relative_permittivity.setDecimals(4)
owner._gpr_relative_permittivity.setRange(0.0001, 1000.0) 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.setSingleStep(0.01)
owner._gpr_angle_comp_power.setValue(float(gpr_live_defaults.angle_comp_power)) 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 = QDoubleSpinBox()
owner._gpr_start_freq_mhz.setDecimals(1) owner._gpr_start_freq_mhz.setDecimals(1)
owner._gpr_start_freq_mhz.setRange(100.0, 8800.0) 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 = QCheckBox("Remove sidelobe objects")
owner._gpr_remove_sidelobe_objects_enabled.setChecked(bool(gpr_live_defaults.remove_sidelobe_objects_enabled)) 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 = QComboBox()
owner._gpr_render_mode.addItems(["heatmap", "objects_only"]) owner._gpr_render_mode.addItems(["heatmap", "objects_only"])
owner._set_combo_current_text(owner._gpr_render_mode, gpr_live_defaults.render_mode) 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( gpr_page = _build_processing_mode_page(
owner._processing_mode_pages, owner._processing_mode_pages,
[ [
("Algorithm", owner._gpr_algorithm),
("Relative permittivity", owner._gpr_relative_permittivity), ("Relative permittivity", owner._gpr_relative_permittivity),
("Input positions", owner._gpr_input_positions_input), ("Input positions", owner._gpr_input_positions_input),
("Output positions", owner._gpr_output_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), ("Max depth m", owner._gpr_max_depth_m),
("Range comp power", owner._gpr_range_comp_power), ("Range comp power", owner._gpr_range_comp_power),
("Angle comp power", owner._gpr_angle_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), ("Render mode", owner._gpr_render_mode),
("Min visible score", owner._gpr_min_visible_score), ("Min visible score", owner._gpr_min_visible_score),
("Tx geometry", owner._gpr_tx_geometry_input), ("Tx geometry", owner._gpr_tx_geometry_input),
("Rx geometry", owner._gpr_rx_geometry_input), ("Rx geometry", owner._gpr_rx_geometry_input),
("Start MHz", owner._gpr_start_freq_mhz), ("Start MHz", owner._gpr_start_freq_mhz),
("Stop MHz", owner._gpr_stop_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 min m", owner._gpr_visible_x_min_m),
("Visible X max m", owner._gpr_visible_x_max_m), ("Visible X max m", owner._gpr_visible_x_max_m),
("Visible Z min m", owner._gpr_visible_z_min_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), ("Mean count", owner._gpr_background_mean_count),
owner._gpr_remove_sidelobe_objects_enabled, owner._gpr_remove_sidelobe_objects_enabled,
], ],
split_index=10, split_index=12,
) )
owner._processing_mode_pages.addWidget(gpr_page) 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_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_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._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_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_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_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_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_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_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_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_stop_freq_mhz.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._gpr_background_subtract_enabled.toggled.connect(owner._on_processing_live_settings_changed) owner._gpr_background_subtract_enabled.toggled.connect(owner._on_processing_live_settings_changed)
+57
View File
@@ -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") 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") bscan_object = _as_dict(processing_object.get("bscan"), "gui.processing.bscan")
gpr_object = _as_dict(processing_object.get("gpr"), "gui.processing.gpr") 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( gui.processing = GuiProcessingStateModel(
selected_mode=_optional_string( selected_mode=_optional_string(
processing_object, processing_object,
@@ -160,6 +167,12 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
), ),
), ),
gpr=GuiGprStateModel( gpr=GuiGprStateModel(
algorithm=_optional_string(
gpr_object,
"algorithm",
gpr_algorithm_default,
"gui.processing.gpr",
),
input_positions=_optional_string( input_positions=_optional_string(
gpr_object, gpr_object,
"input_positions", "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.angle_comp_power,
"gui.processing.gpr", "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( start_freq_mhz=_optional_float(
gpr_object, gpr_object,
"start_freq_mhz", "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") raise ValueError("gui.processing.selected_mode must be one of: pass_through, bscan, gpr")
if gui.processing.bscan.axis not in {"abs", "real", "phase"}: if gui.processing.bscan.axis not in {"abs", "real", "phase"}:
raise ValueError("gui.processing.bscan.axis must be one of: 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"}: 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") raise ValueError("gui.processing.gpr.render_mode must be one of: heatmap, objects_only")
if gui.processing.gpr.range_comp_power < 0.0: if gui.processing.gpr.range_comp_power < 0.0:
raise ValueError("gui.processing.gpr.range_comp_power must be >= 0") raise ValueError("gui.processing.gpr.range_comp_power must be >= 0")
if gui.processing.gpr.angle_comp_power < 0.0: if gui.processing.gpr.angle_comp_power < 0.0:
raise ValueError("gui.processing.gpr.angle_comp_power must be >= 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: if gui.processing.gpr.min_visible_score < 0.0:
raise ValueError("gui.processing.gpr.min_visible_score must be >= 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, "subtract_mean_ascan": gui.processing.bscan.subtract_mean_ascan,
}, },
"gpr": { "gpr": {
"algorithm": gui.processing.gpr.algorithm,
"input_positions": gui.processing.gpr.input_positions, "input_positions": gui.processing.gpr.input_positions,
"output_positions": gui.processing.gpr.output_positions, "output_positions": gui.processing.gpr.output_positions,
"min_depth_m": gui.processing.gpr.min_depth_m, "min_depth_m": gui.processing.gpr.min_depth_m,
"max_depth_m": gui.processing.gpr.max_depth_m, "max_depth_m": gui.processing.gpr.max_depth_m,
"range_comp_power": gui.processing.gpr.range_comp_power, "range_comp_power": gui.processing.gpr.range_comp_power,
"angle_comp_power": gui.processing.gpr.angle_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, "start_freq_mhz": gui.processing.gpr.start_freq_mhz,
"stop_freq_mhz": gui.processing.gpr.stop_freq_mhz, "stop_freq_mhz": gui.processing.gpr.stop_freq_mhz,
"background_subtract_enabled": gui.processing.gpr.background_subtract_enabled, "background_subtract_enabled": gui.processing.gpr.background_subtract_enabled,
+6
View File
@@ -49,12 +49,18 @@ class GuiBscanStateModel:
class GuiGprStateModel: class GuiGprStateModel:
"""UI-only defaults for GPR live settings.""" """UI-only defaults for GPR live settings."""
algorithm: str = "backprojection"
input_positions: str = "" input_positions: str = ""
output_positions: str = "" output_positions: str = ""
min_depth_m: float = 2.0 min_depth_m: float = 2.0
max_depth_m: float = 14.0 max_depth_m: float = 14.0
range_comp_power: float = 0.28 range_comp_power: float = 0.28
angle_comp_power: float = 0.10 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 start_freq_mhz: float = 3000.0
stop_freq_mhz: float = 6000.0 stop_freq_mhz: float = 6000.0
background_subtract_enabled: bool = True background_subtract_enabled: bool = True
@@ -23,12 +23,18 @@ class ProcessingLiveConfig:
bscan_gain: float = 1.0 bscan_gain: float = 1.0
bscan_start_freq_mhz: float = 100.0 bscan_start_freq_mhz: float = 100.0
bscan_stop_freq_mhz: float = 8800.0 bscan_stop_freq_mhz: float = 8800.0
gpr_algorithm: str = "backprojection"
gpr_input_positions: list[int] | None = None gpr_input_positions: list[int] | None = None
gpr_output_positions: list[int] | None = None gpr_output_positions: list[int] | None = None
gpr_min_depth_m: float = 2.0 gpr_min_depth_m: float = 2.0
gpr_max_depth_m: float = 14.0 gpr_max_depth_m: float = 14.0
gpr_range_comp_power: float = 0.28 gpr_range_comp_power: float = 0.28
gpr_angle_comp_power: float = 0.10 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_start_freq_mhz: float = 3000.0
gpr_stop_freq_mhz: float = 6000.0 gpr_stop_freq_mhz: float = 6000.0
gpr_background_subtract_enabled: bool = True gpr_background_subtract_enabled: bool = True
@@ -63,12 +69,18 @@ class ProcessingLiveConfig:
"bscan_gain": float(self.bscan_gain), "bscan_gain": float(self.bscan_gain),
"bscan_start_freq_mhz": float(self.bscan_start_freq_mhz), "bscan_start_freq_mhz": float(self.bscan_start_freq_mhz),
"bscan_stop_freq_mhz": float(self.bscan_stop_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_input_positions": [int(value) for value in self.gpr_input_positions],
"gpr_output_positions": [int(value) for value in self.gpr_output_positions], "gpr_output_positions": [int(value) for value in self.gpr_output_positions],
"gpr_min_depth_m": float(self.gpr_min_depth_m), "gpr_min_depth_m": float(self.gpr_min_depth_m),
"gpr_max_depth_m": float(self.gpr_max_depth_m), "gpr_max_depth_m": float(self.gpr_max_depth_m),
"gpr_range_comp_power": float(self.gpr_range_comp_power), "gpr_range_comp_power": float(self.gpr_range_comp_power),
"gpr_angle_comp_power": float(self.gpr_angle_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_start_freq_mhz": float(self.gpr_start_freq_mhz),
"gpr_stop_freq_mhz": float(self.gpr_stop_freq_mhz), "gpr_stop_freq_mhz": float(self.gpr_stop_freq_mhz),
"gpr_background_subtract_enabled": bool(self.gpr_background_subtract_enabled), "gpr_background_subtract_enabled": bool(self.gpr_background_subtract_enabled),
+27 -26
View File
@@ -1,23 +1,18 @@
{ {
"radar": { "radar": {
"model": "librevna_multi", "model": "librevna",
"serial": "207730885532", "serial": "",
"remote_host": "127.0.0.1", "driver_mode": "mock",
"remote_port": 50209,
"driver_mode": "native",
"mock_signal_hz": 5000000.0, "mock_signal_hz": 5000000.0,
"multi_device": { "multi_device": {
"slave_serials": [ "slave_serials": [],
"20A1307D5532", "force_external_reference": false,
"2072306C5532"
],
"force_external_reference": true,
"recovery_attempts": 3 "recovery_attempts": 3
}, },
"sweep": { "sweep": {
"start_hz": 1000000.0, "start_hz": 1000000.0,
"stop_hz": 6000000000.0, "stop_hz": 6000000000.0,
"points": 4501, "points": 201,
"if_bandwidth_hz": 50000.0, "if_bandwidth_hz": 50000.0,
"stimulus_power_dbm": -10.0 "stimulus_power_dbm": -10.0
} }
@@ -52,7 +47,7 @@
"settling_ms": 0, "settling_ms": 0,
"idle_sleep_ms": 2, "idle_sleep_ms": 2,
"continuous": true, "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": { "locator_server": {
"device_id": 3, "device_id": 3,
"protocol_version": 1, "protocol_version": 1,
@@ -100,11 +95,11 @@
"preprocess": { "preprocess": {
"s21": { "s21": {
"calibration": { "calibration": {
"set_name": "set_001", "set_name": "smoke_cal",
"bundle_path": "" "bundle_path": ""
}, },
"reference": { "reference": {
"set_name": "set_001", "set_name": "smoke_ref",
"bundle_path": "" "bundle_path": ""
} }
}, },
@@ -168,27 +163,27 @@
}, },
"rings": { "rings": {
"raw": { "raw": {
"name": "/radar_raw", "name": "/radar_raw_simulator",
"capacity": 50, "capacity": 50,
"slot_size_bytes": 2097152 "slot_size_bytes": 2097152
}, },
"raw_tap": { "raw_tap": {
"name": "/radar_raw_tap", "name": "/radar_raw_tap_simulator",
"capacity": 50, "capacity": 50,
"slot_size_bytes": 2097152 "slot_size_bytes": 2097152
}, },
"preprocessed": { "preprocessed": {
"name": "/radar_preprocessed", "name": "/radar_preprocessed_simulator",
"capacity": 50, "capacity": 50,
"slot_size_bytes": 2097152 "slot_size_bytes": 2097152
}, },
"preprocessed_tap": { "preprocessed_tap": {
"name": "/radar_preprocessed_tap", "name": "/radar_preprocessed_tap_simulator",
"capacity": 50, "capacity": 50,
"slot_size_bytes": 2097152 "slot_size_bytes": 2097152
}, },
"results": { "results": {
"name": "/radar_results", "name": "/radar_results_simulator",
"capacity": 50, "capacity": 50,
"slot_size_bytes": 2097152 "slot_size_bytes": 2097152
} }
@@ -220,32 +215,38 @@
"subtract_mean_ascan": false "subtract_mean_ascan": false
}, },
"gpr": { "gpr": {
"algorithm": "backprojection",
"input_positions": "0,1,2,3", "input_positions": "0,1,2,3",
"output_positions": "0,1", "output_positions": "0,1",
"min_depth_m": 2.0, "min_depth_m": 2.0,
"max_depth_m": 14.0, "max_depth_m": 14.0,
"range_comp_power": 0.28, "range_comp_power": 0.28,
"angle_comp_power": 0.1, "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, "start_freq_mhz": 3000.0,
"stop_freq_mhz": 6000.0, "stop_freq_mhz": 6000.0,
"background_subtract_enabled": true, "background_subtract_enabled": true,
"background_mean_count": 10, "background_mean_count": 10,
"remove_sidelobe_objects_enabled": false, "remove_sidelobe_objects_enabled": false,
"render_mode": "heatmap", "render_mode": "heatmap",
"min_visible_score": 0.049999999999999684, "min_visible_score": 0.0,
"visible_x_min_m": -1.609999999999999, "visible_x_min_m": -2.0,
"visible_x_max_m": 1.1099999999999985, "visible_x_max_m": 2.0,
"visible_z_min_m": 0.30000000000000004, "visible_z_min_m": 0.0,
"visible_z_max_m": 14.0 "visible_z_max_m": 14.0
} }
}, },
"data_actions": { "data_actions": {
"save_count": 10, "save_count": 10,
"save_path": "/home/europa/Documents/radar_system/python_app/data/snapshots", "save_path": "python_app/data/snapshots",
"save_name": "snapshot_manual" "save_name": "snapshot_simulator"
}, },
"preprocess_dialog": { "preprocess_dialog": {
"set_name": "set_001", "set_name": "smoke_cal",
"radar_config_dir": "", "radar_config_dir": "",
"use_all_radar_configs": false "use_all_radar_configs": false
} }
+254
View File
@@ -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
}
}
}