added legacy gpr
This commit is contained in:
@@ -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");
|
||||||
|
|||||||
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_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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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