new GPR
This commit is contained in:
File diff suppressed because it is too large
Load Diff
+6
-2
@@ -44,10 +44,14 @@ struct ProcessingLiveConfig {
|
|||||||
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.1F;
|
||||||
float gpr_angle_comp_power = 0.10F;
|
float gpr_angle_comp_power = 0.0F;
|
||||||
float gpr_comp_power = 0.2F;
|
float gpr_comp_power = 0.2F;
|
||||||
std::string gpr_score_mode = "combined";
|
std::string gpr_score_mode = "combined";
|
||||||
|
// Backprojection intra-sweep speed-correction mode: "int_minus" (full
|
||||||
|
// correction) or "int_focus" (focusing residual only). Mirrors the Python
|
||||||
|
// Horns_motion_3libre.py MOTION_CORRECTION_MODE selector.
|
||||||
|
std::string gpr_motion_mode = "int_minus";
|
||||||
float gpr_speed_m_s = 0.0F;
|
float gpr_speed_m_s = 0.0F;
|
||||||
float gpr_look_angle_deg = 0.0F;
|
float gpr_look_angle_deg = 0.0F;
|
||||||
// Motion model parameters for the legacy GPR pipeline. The direction sign
|
// Motion model parameters for the legacy GPR pipeline. The direction sign
|
||||||
|
|||||||
@@ -60,6 +60,13 @@ using Json = nlohmann::json;
|
|||||||
throw std::runtime_error(field_name + " must be one of: peak, combined");
|
throw std::runtime_error(field_name + " must be one of: peak, combined");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto parse_gpr_motion_mode(const std::string& value, const std::string& field_name) -> std::string {
|
||||||
|
if (value == "int_minus" || value == "int_focus") {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
throw std::runtime_error(field_name + " must be one of: int_minus, int_focus");
|
||||||
|
}
|
||||||
|
|
||||||
void apply_legacy_gpr_algorithm_alias(ProcessingLiveConfig& config, const std::string& value) {
|
void apply_legacy_gpr_algorithm_alias(ProcessingLiveConfig& config, const std::string& value) {
|
||||||
if (value == "backprojection") {
|
if (value == "backprojection") {
|
||||||
return;
|
return;
|
||||||
@@ -266,6 +273,12 @@ void apply_legacy_gpr_algorithm_alias(ProcessingLiveConfig& config, const std::s
|
|||||||
}
|
}
|
||||||
config.gpr_score_mode = parse_gpr_score_mode(found->get<std::string>(), "processing.gpr_score_mode");
|
config.gpr_score_mode = parse_gpr_score_mode(found->get<std::string>(), "processing.gpr_score_mode");
|
||||||
}
|
}
|
||||||
|
if (const auto found = root.find("gpr_motion_mode"); found != root.end()) {
|
||||||
|
if (!found->is_string()) {
|
||||||
|
throw std::runtime_error("processing.gpr_motion_mode must be string");
|
||||||
|
}
|
||||||
|
config.gpr_motion_mode = parse_gpr_motion_mode(found->get<std::string>(), "processing.gpr_motion_mode");
|
||||||
|
}
|
||||||
if (const auto found = root.find("gpr_speed_m_s"); found != root.end()) {
|
if (const auto found = root.find("gpr_speed_m_s"); found != root.end()) {
|
||||||
if (!found->is_number()) {
|
if (!found->is_number()) {
|
||||||
throw std::runtime_error("processing.gpr_speed_m_s must be number");
|
throw std::runtime_error("processing.gpr_speed_m_s must be number");
|
||||||
|
|||||||
+283
-53
@@ -16,8 +16,12 @@ constexpr double kPairNormPercentile = 50.0;
|
|||||||
constexpr double kPairNormEps = 1e-15;
|
constexpr double kPairNormEps = 1e-15;
|
||||||
|
|
||||||
constexpr double kSmoothSigma = 1.5;
|
constexpr double kSmoothSigma = 1.5;
|
||||||
|
// Gaussian-kernel half-width in sigmas, matching scipy.ndimage.gaussian_filter's
|
||||||
|
// default `truncate=4.0` (radius = int(truncate*sigma + 0.5)). Boundaries use the
|
||||||
|
// same default 'reflect' (half-sample symmetric) extension — see reflect_index.
|
||||||
|
constexpr double kGaussianTruncate = 4.0;
|
||||||
constexpr std::size_t kMaxObjects = 10U;
|
constexpr std::size_t kMaxObjects = 10U;
|
||||||
constexpr double kObjectMinFrac = 0.35;
|
constexpr double kObjectMinFrac = 0.7;
|
||||||
constexpr double kRegionThresholdFrac = 0.75;
|
constexpr double kRegionThresholdFrac = 0.75;
|
||||||
constexpr double kSuppressThresholdFrac = 0.20;
|
constexpr double kSuppressThresholdFrac = 0.20;
|
||||||
constexpr double kSuppressRadiusXM = 0.80;
|
constexpr double kSuppressRadiusXM = 0.80;
|
||||||
@@ -48,6 +52,16 @@ constexpr double kScoreCfEps = 1e-12;
|
|||||||
|
|
||||||
using PairKey = std::uint64_t;
|
using PairKey = std::uint64_t;
|
||||||
|
|
||||||
|
// Frequency-domain speed correction mode (Horns_motion_3libre.py MOTION_CONFIG):
|
||||||
|
// IntMinus — full intra-frequency correction to the interleaved-sweep centre.
|
||||||
|
// IntFocus — remove the constant + linear-in-frequency part of phi(f), leaving
|
||||||
|
// the focusing residual without a net Z shift.
|
||||||
|
enum class MotionMode { IntMinus, IntFocus };
|
||||||
|
|
||||||
|
[[nodiscard]] auto parse_motion_mode(const std::string& value) -> MotionMode {
|
||||||
|
return value == "int_focus" ? MotionMode::IntFocus : MotionMode::IntMinus;
|
||||||
|
}
|
||||||
|
|
||||||
struct GeometrySelection {
|
struct GeometrySelection {
|
||||||
// Per-local-index Tx/Rx antenna coordinates in metres. y_/z_ default to 0
|
// Per-local-index Tx/Rx antenna coordinates in metres. y_/z_ default to 0
|
||||||
// for legacy configs so the imaging plane coincides with the antennas.
|
// for legacy configs so the imaging plane coincides with the antennas.
|
||||||
@@ -269,6 +283,25 @@ void fft_inplace(std::vector<std::complex<double>>& values, bool inverse) {
|
|||||||
return static_cast<std::size_t>(value);
|
return static_cast<std::size_t>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// scipy 'reflect' boundary (half-sample symmetric): the signal is mirrored about
|
||||||
|
// the outer edge of the first/last sample, so index -1 maps to 0, -2 to 1, n to
|
||||||
|
// n-1, and so on. Matches scipy.ndimage.gaussian_filter's default mode.
|
||||||
|
[[nodiscard]] auto reflect_index(std::ptrdiff_t value, std::size_t limit) -> std::size_t {
|
||||||
|
if (limit <= 1U) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
const auto extent = static_cast<std::ptrdiff_t>(limit);
|
||||||
|
const std::ptrdiff_t period = 2 * extent;
|
||||||
|
std::ptrdiff_t wrapped = value % period;
|
||||||
|
if (wrapped < 0) {
|
||||||
|
wrapped += period;
|
||||||
|
}
|
||||||
|
if (wrapped >= extent) {
|
||||||
|
wrapped = (period - 1) - wrapped;
|
||||||
|
}
|
||||||
|
return static_cast<std::size_t>(wrapped);
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] auto max_value(const std::vector<double>& values) -> double {
|
[[nodiscard]] auto max_value(const std::vector<double>& values) -> double {
|
||||||
if (values.empty()) {
|
if (values.empty()) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
@@ -291,7 +324,7 @@ void normalize_in_place(std::vector<double>& values) {
|
|||||||
return {1.0};
|
return {1.0};
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto radius = static_cast<std::ptrdiff_t>(std::ceil(sigma * 3.0));
|
const auto radius = static_cast<std::ptrdiff_t>((kGaussianTruncate * sigma) + 0.5);
|
||||||
std::vector<double> kernel(static_cast<std::size_t>((radius * 2) + 1), 0.0);
|
std::vector<double> kernel(static_cast<std::size_t>((radius * 2) + 1), 0.0);
|
||||||
double sum = 0.0;
|
double sum = 0.0;
|
||||||
for (std::ptrdiff_t offset = -radius; offset <= radius; ++offset) {
|
for (std::ptrdiff_t offset = -radius; offset <= radius; ++offset) {
|
||||||
@@ -326,7 +359,7 @@ void normalize_in_place(std::vector<double>& values) {
|
|||||||
for (std::size_t col = 0U; col < width; ++col) {
|
for (std::size_t col = 0U; col < width; ++col) {
|
||||||
double sum = 0.0;
|
double sum = 0.0;
|
||||||
for (std::ptrdiff_t offset = -radius; offset <= radius; ++offset) {
|
for (std::ptrdiff_t offset = -radius; offset <= radius; ++offset) {
|
||||||
const auto sample_col = clamp_index(static_cast<std::ptrdiff_t>(col) + offset, width);
|
const auto sample_col = reflect_index(static_cast<std::ptrdiff_t>(col) + offset, width);
|
||||||
sum += values[(row * width) + sample_col] * kernel[static_cast<std::size_t>(offset + radius)];
|
sum += values[(row * width) + sample_col] * kernel[static_cast<std::size_t>(offset + radius)];
|
||||||
}
|
}
|
||||||
temp[(row * width) + col] = sum;
|
temp[(row * width) + col] = sum;
|
||||||
@@ -337,7 +370,7 @@ void normalize_in_place(std::vector<double>& values) {
|
|||||||
for (std::size_t col = 0U; col < width; ++col) {
|
for (std::size_t col = 0U; col < width; ++col) {
|
||||||
double sum = 0.0;
|
double sum = 0.0;
|
||||||
for (std::ptrdiff_t offset = -radius; offset <= radius; ++offset) {
|
for (std::ptrdiff_t offset = -radius; offset <= radius; ++offset) {
|
||||||
const auto sample_row = clamp_index(static_cast<std::ptrdiff_t>(row) + offset, height);
|
const auto sample_row = reflect_index(static_cast<std::ptrdiff_t>(row) + offset, height);
|
||||||
sum += temp[(sample_row * width) + col] * kernel[static_cast<std::size_t>(offset + radius)];
|
sum += temp[(sample_row * width) + col] * kernel[static_cast<std::size_t>(offset + radius)];
|
||||||
}
|
}
|
||||||
output[(row * width) + col] = sum;
|
output[(row * width) + col] = sum;
|
||||||
@@ -580,6 +613,114 @@ void validate_collection_trace_order(
|
|||||||
return traces;
|
return traces;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Frequency-domain speed correction applied to S21 *before* the IFFT, ported
|
||||||
|
// from Horns_motion_3libre.py (apply_intra_sweep_phase_correction +
|
||||||
|
// compute_frequency_sample_times). The measurement is interleaved Tx-by-frequency
|
||||||
|
// — Tx0(f0), Tx1(f0), Tx0(f1), ... — so each Tx occupies one interleave slot and
|
||||||
|
// its frequency k is sampled at global step (slot_count*k + slot). Each in-band
|
||||||
|
// frequency's phase is rotated back to the centre of the (full) interleaved sweep.
|
||||||
|
//
|
||||||
|
// Unlike the offline Python script, every physical input is taken from the live
|
||||||
|
// system: `speed_mps` is the socket-fed velocity, `look_angle_deg`/`direction_sign`
|
||||||
|
// are config, and `tx_sweep_time_s` is derived per-collection from the acquisition
|
||||||
|
// timestamps (capture_span / Tx count). A zero speed or non-positive sweep time
|
||||||
|
// makes this a no-op.
|
||||||
|
void apply_intra_sweep_motion_correction(
|
||||||
|
SelectedTrace& trace,
|
||||||
|
std::size_t slot,
|
||||||
|
std::size_t slot_count,
|
||||||
|
double start_hz,
|
||||||
|
double stop_hz,
|
||||||
|
double velocity_mps,
|
||||||
|
double speed_mps,
|
||||||
|
double look_angle_deg,
|
||||||
|
double direction_sign,
|
||||||
|
double tx_sweep_time_s,
|
||||||
|
MotionMode motion_mode
|
||||||
|
) {
|
||||||
|
const std::size_t n_full = trace.frequency_hz.size();
|
||||||
|
if (n_full < 2U || trace.s21.size() != n_full || slot_count == 0U
|
||||||
|
|| !(velocity_mps > 0.0) || !(tx_sweep_time_s > 0.0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double low_hz = std::min(start_hz, stop_hz);
|
||||||
|
const double high_hz = std::max(start_hz, stop_hz);
|
||||||
|
|
||||||
|
// Absolute sample time of full-sweep frequency index k for this Tx slot.
|
||||||
|
const double dt_base = tx_sweep_time_s / static_cast<double>(n_full - 1U);
|
||||||
|
const auto sample_time_s = [&](std::size_t index) {
|
||||||
|
return ((static_cast<double>(slot_count) * static_cast<double>(index)) + static_cast<double>(slot)) * dt_base;
|
||||||
|
};
|
||||||
|
// t_center is the midpoint of the *full* interleaved sweep for this slot,
|
||||||
|
// i.e. between the first and last full-array frequencies (not the cut band).
|
||||||
|
const double t_center_s = 0.5 * (sample_time_s(0U) + sample_time_s(n_full - 1U));
|
||||||
|
|
||||||
|
const double theta_rad = (look_angle_deg * kPi) / 180.0;
|
||||||
|
const double motion_factor = direction_sign * speed_mps * std::cos(theta_rad);
|
||||||
|
|
||||||
|
std::vector<std::size_t> band_indices{};
|
||||||
|
std::vector<double> band_frequency_hz{};
|
||||||
|
std::vector<double> phi{};
|
||||||
|
band_indices.reserve(n_full);
|
||||||
|
band_frequency_hz.reserve(n_full);
|
||||||
|
phi.reserve(n_full);
|
||||||
|
|
||||||
|
bool any_nonzero = false;
|
||||||
|
for (std::size_t index = 0U; index < n_full; ++index) {
|
||||||
|
const double frequency = trace.frequency_hz[index];
|
||||||
|
if (frequency < low_hz || frequency > high_hz) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const double dt_intra_s = sample_time_s(index) - t_center_s;
|
||||||
|
const double delta_range_m = motion_factor * dt_intra_s;
|
||||||
|
const double delta_path_m = 2.0 * delta_range_m;
|
||||||
|
const double dtau_s = delta_path_m / velocity_mps;
|
||||||
|
if (dtau_s != 0.0) {
|
||||||
|
any_nonzero = true;
|
||||||
|
}
|
||||||
|
band_indices.push_back(index);
|
||||||
|
band_frequency_hz.push_back(frequency);
|
||||||
|
phi.push_back(2.0 * kPi * frequency * dtau_s);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!any_nonzero || band_indices.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// int_focus removes the constant + linear-in-frequency component of phi(f).
|
||||||
|
// With f_rel = f - mean(f) the design columns [1, f_rel] are orthogonal, so
|
||||||
|
// the least-squares fit is intercept = mean(phi), slope = <f_rel, phi>/<f_rel, f_rel>.
|
||||||
|
if (motion_mode == MotionMode::IntFocus) {
|
||||||
|
const auto count = static_cast<double>(band_frequency_hz.size());
|
||||||
|
double mean_frequency = 0.0;
|
||||||
|
double mean_phi = 0.0;
|
||||||
|
for (std::size_t i = 0U; i < band_frequency_hz.size(); ++i) {
|
||||||
|
mean_frequency += band_frequency_hz[i];
|
||||||
|
mean_phi += phi[i];
|
||||||
|
}
|
||||||
|
mean_frequency /= count;
|
||||||
|
mean_phi /= count;
|
||||||
|
|
||||||
|
double cross = 0.0;
|
||||||
|
double f_rel_sq = 0.0;
|
||||||
|
for (std::size_t i = 0U; i < band_frequency_hz.size(); ++i) {
|
||||||
|
const double f_rel = band_frequency_hz[i] - mean_frequency;
|
||||||
|
cross += f_rel * phi[i];
|
||||||
|
f_rel_sq += f_rel * f_rel;
|
||||||
|
}
|
||||||
|
const double slope = (f_rel_sq > 0.0) ? (cross / f_rel_sq) : 0.0;
|
||||||
|
for (std::size_t i = 0U; i < phi.size(); ++i) {
|
||||||
|
const double f_rel = band_frequency_hz[i] - mean_frequency;
|
||||||
|
phi[i] -= mean_phi + (slope * f_rel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 0U; i < band_indices.size(); ++i) {
|
||||||
|
trace.s21[band_indices[i]] *= std::polar(1.0, -phi[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] auto compute_ascan(
|
[[nodiscard]] auto compute_ascan(
|
||||||
const SelectedTrace& trace,
|
const SelectedTrace& trace,
|
||||||
double start_hz,
|
double start_hz,
|
||||||
@@ -841,6 +982,38 @@ void normalize_pair_ascans(
|
|||||||
return std::clamp(range_weight * angle_weight, 0.0, kTotalWeightMax);
|
return std::clamp(range_weight * angle_weight, 0.0, kTotalWeightMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run `body(row_begin, row_end)` over a partition of [0, row_count) across the
|
||||||
|
// available hardware threads. Each call owns a disjoint, contiguous row range, so
|
||||||
|
// a body that writes only its own rows needs no synchronization. The calling
|
||||||
|
// thread runs the first chunk while spawned workers handle the rest. Falls back to
|
||||||
|
// a single serial call when there is one row or no concurrency is reported.
|
||||||
|
template <typename Body>
|
||||||
|
void parallel_for_rows(std::size_t row_count, const Body& body) {
|
||||||
|
if (row_count == 0U) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsigned int detected = std::thread::hardware_concurrency();
|
||||||
|
const std::size_t worker_count = std::clamp<std::size_t>(
|
||||||
|
detected == 0U ? 1U : static_cast<std::size_t>(detected), 1U, row_count
|
||||||
|
);
|
||||||
|
if (worker_count == 1U) {
|
||||||
|
body(0U, row_count);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t chunk = (row_count + worker_count - 1U) / worker_count;
|
||||||
|
std::vector<std::thread> workers;
|
||||||
|
workers.reserve(worker_count - 1U);
|
||||||
|
for (std::size_t begin = chunk; begin < row_count; begin += chunk) {
|
||||||
|
workers.emplace_back(body, begin, std::min(begin + chunk, row_count));
|
||||||
|
}
|
||||||
|
body(0U, std::min(chunk, row_count));
|
||||||
|
for (auto& worker : workers) {
|
||||||
|
worker.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] auto backproject_coherent(
|
[[nodiscard]] auto backproject_coherent(
|
||||||
const std::vector<SelectedTrace>& selected_traces,
|
const std::vector<SelectedTrace>& selected_traces,
|
||||||
const std::unordered_map<PairKey, AscanResult>& ascans_by_pair,
|
const std::unordered_map<PairKey, AscanResult>& ascans_by_pair,
|
||||||
@@ -862,77 +1035,102 @@ void normalize_pair_ascans(
|
|||||||
result.coherent.assign(cell_count, std::complex<double>(0.0, 0.0));
|
result.coherent.assign(cell_count, std::complex<double>(0.0, 0.0));
|
||||||
result.incoherent.assign(cell_count, 0.0);
|
result.incoherent.assign(cell_count, 0.0);
|
||||||
result.coherence_factor.assign(cell_count, 0.0);
|
result.coherence_factor.assign(cell_count, 0.0);
|
||||||
std::vector<double> contribution_count(cell_count, 0.0);
|
|
||||||
|
|
||||||
|
// Resolve each contributing pair once, in selected-trace order. Iterating
|
||||||
|
// these per cell reproduces the serial accumulation order exactly, so the
|
||||||
|
// parallel result is bit-for-bit identical to a single-threaded sweep.
|
||||||
|
struct PairContribution {
|
||||||
|
const std::vector<double>* tx_distances;
|
||||||
|
const std::vector<double>* rx_distances;
|
||||||
|
const AscanResult* ascan;
|
||||||
|
double geo_ref;
|
||||||
|
double angle_ref;
|
||||||
|
double z_tx_ant;
|
||||||
|
double z_rx_ant;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<PairContribution> contributions;
|
||||||
|
contributions.reserve(selected_traces.size());
|
||||||
for (const auto& trace : selected_traces) {
|
for (const auto& trace : selected_traces) {
|
||||||
const auto key = make_pair_key(trace.tx_local_index, trace.rx_local_index);
|
const auto key = make_pair_key(trace.tx_local_index, trace.rx_local_index);
|
||||||
const auto ascan_it = ascans_by_pair.find(key);
|
const auto ascan_it = ascans_by_pair.find(key);
|
||||||
if (ascan_it == ascans_by_pair.end()) {
|
if (ascan_it == ascans_by_pair.end() || ascan_it->second.time_s.empty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto& ascan = ascan_it->second;
|
|
||||||
if (ascan.time_s.empty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto [geo_ref, angle_ref] = attenuation_components_at_ref_depth(
|
const auto [geo_ref, angle_ref] = attenuation_components_at_ref_depth(
|
||||||
trace.tx_local_index,
|
trace.tx_local_index,
|
||||||
trace.rx_local_index,
|
trace.rx_local_index,
|
||||||
selection,
|
selection,
|
||||||
imaging_plane_y_m
|
imaging_plane_y_m
|
||||||
);
|
);
|
||||||
const auto& tx_distances = grid.tx_distance_grids[trace.tx_local_index];
|
contributions.push_back(PairContribution{
|
||||||
const auto& rx_distances = grid.rx_distance_grids[trace.rx_local_index];
|
&grid.tx_distance_grids[trace.tx_local_index],
|
||||||
const double z_tx_ant = selection.z_tx[trace.tx_local_index];
|
&grid.rx_distance_grids[trace.rx_local_index],
|
||||||
const double z_rx_ant = selection.z_rx[trace.rx_local_index];
|
&ascan_it->second,
|
||||||
|
geo_ref,
|
||||||
|
angle_ref,
|
||||||
|
selection.z_tx[trace.tx_local_index],
|
||||||
|
selection.z_rx[trace.rx_local_index],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
for (std::size_t row = 0U; row < height; ++row) {
|
// Each grid row writes only its own cells, so rows partition cleanly across
|
||||||
|
// threads with no shared mutable state. Within a cell the contributions are
|
||||||
|
// summed in pair order and then averaged — the same arithmetic, in the same
|
||||||
|
// order, as the original serial pair-outer/cell-inner loop.
|
||||||
|
const auto accumulate_rows = [&](std::size_t row_begin, std::size_t row_end) {
|
||||||
|
for (std::size_t row = row_begin; row < row_end; ++row) {
|
||||||
const double z_m = grid.z_grid[row];
|
const double z_m = grid.z_grid[row];
|
||||||
const bool in_depth_gate = z_m >= min_depth_m && z_m <= max_depth_m;
|
if (z_m < min_depth_m || z_m > max_depth_m) {
|
||||||
if (!in_depth_gate) {
|
continue; // Depth-gated rows stay zero, as in the serial version.
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
const double dz_tx = z_m - z_tx_ant;
|
|
||||||
const double dz_rx = z_m - z_rx_ant;
|
|
||||||
|
|
||||||
for (std::size_t col = 0U; col < width; ++col) {
|
for (std::size_t col = 0U; col < width; ++col) {
|
||||||
const auto cell_index = (row * width) + col;
|
const auto cell_index = (row * width) + col;
|
||||||
const double r_tx = tx_distances[cell_index];
|
std::complex<double> coherent_sum(0.0, 0.0);
|
||||||
const double r_rx = rx_distances[cell_index];
|
double incoherent_sum = 0.0;
|
||||||
const double tau_s = (r_tx + r_rx) / velocity_mps;
|
double contribution_count = 0.0;
|
||||||
if (tau_s < ascan.time_s.front() || tau_s > ascan.time_s.back()) {
|
|
||||||
|
for (const auto& contribution : contributions) {
|
||||||
|
const double r_tx = (*contribution.tx_distances)[cell_index];
|
||||||
|
const double r_rx = (*contribution.rx_distances)[cell_index];
|
||||||
|
const double tau_s = (r_tx + r_rx) / velocity_mps;
|
||||||
|
const auto& ascan = *contribution.ascan;
|
||||||
|
if (tau_s < ascan.time_s.front() || tau_s > ascan.time_s.back()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto sample = interpolate_complex(ascan, tau_s);
|
||||||
|
const double weight = compensation_weight(
|
||||||
|
r_tx,
|
||||||
|
r_rx,
|
||||||
|
z_m - contribution.z_tx_ant,
|
||||||
|
z_m - contribution.z_rx_ant,
|
||||||
|
contribution.geo_ref,
|
||||||
|
contribution.angle_ref,
|
||||||
|
range_power,
|
||||||
|
angle_power
|
||||||
|
);
|
||||||
|
coherent_sum += sample * weight;
|
||||||
|
incoherent_sum += std::abs(sample) * weight;
|
||||||
|
contribution_count += 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(contribution_count > 0.0)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto sample = interpolate_complex(ascan, tau_s);
|
coherent_sum /= contribution_count;
|
||||||
|
incoherent_sum /= contribution_count;
|
||||||
const double weight = compensation_weight(
|
result.coherent[cell_index] = coherent_sum;
|
||||||
r_tx,
|
result.incoherent[cell_index] = incoherent_sum;
|
||||||
r_rx,
|
result.image[cell_index] = std::abs(coherent_sum);
|
||||||
dz_tx,
|
result.coherence_factor[cell_index] =
|
||||||
dz_rx,
|
std::clamp(result.image[cell_index] / (incoherent_sum + kScoreCfEps), 0.0, 1.0);
|
||||||
geo_ref,
|
|
||||||
angle_ref,
|
|
||||||
range_power,
|
|
||||||
angle_power
|
|
||||||
);
|
|
||||||
result.coherent[cell_index] += sample * weight;
|
|
||||||
result.incoherent[cell_index] += std::abs(sample) * weight;
|
|
||||||
contribution_count[cell_index] += 1.0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
for (std::size_t index = 0U; index < cell_count; ++index) {
|
|
||||||
if (!(contribution_count[index] > 0.0)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
result.coherent[index] /= contribution_count[index];
|
|
||||||
result.incoherent[index] /= contribution_count[index];
|
|
||||||
result.image[index] = std::abs(result.coherent[index]);
|
|
||||||
result.coherence_factor[index] =
|
|
||||||
std::clamp(result.image[index] / (result.incoherent[index] + kScoreCfEps), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
parallel_for_rows(height, accumulate_rows);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1595,7 +1793,7 @@ void add_bp_score_metrics(
|
|||||||
|
|
||||||
validate_collection_trace_order(run_config, collection);
|
validate_collection_trace_order(run_config, collection);
|
||||||
const auto background_mean = build_background_mean(previous_collections, selection, live_config);
|
const auto background_mean = build_background_mean(previous_collections, selection, live_config);
|
||||||
const auto selected_traces = collect_selected_traces(collection, selection, background_mean);
|
auto selected_traces = collect_selected_traces(collection, selection, background_mean);
|
||||||
if (selected_traces.empty()) {
|
if (selected_traces.empty()) {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
@@ -1610,6 +1808,38 @@ void add_bp_score_metrics(
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Intra-sweep speed correction before the IFFT. The Tx interleave slot is the
|
||||||
|
// Tx local index (outputs are sorted), with slot_count = number of selected Tx.
|
||||||
|
// tx_sweep_time is derived per-collection from the acquisition timestamps:
|
||||||
|
// one Tx's interleaved sweep spans the whole frame, so capture_span / N_tx.
|
||||||
|
// Speed comes from the socket-fed live config; look-angle/direction from config.
|
||||||
|
const auto motion_mode = parse_motion_mode(live_config.gpr_motion_mode);
|
||||||
|
const std::size_t motion_slot_count = selection.output_positions.size();
|
||||||
|
const double capture_span_s =
|
||||||
|
collection.capture_end_ns > collection.capture_start_ns
|
||||||
|
? static_cast<double>(collection.capture_end_ns - collection.capture_start_ns) * 1e-9
|
||||||
|
: 0.0;
|
||||||
|
const double tx_sweep_time_s =
|
||||||
|
motion_slot_count > 0U ? capture_span_s / static_cast<double>(motion_slot_count) : 0.0;
|
||||||
|
const double motion_speed_mps = static_cast<double>(live_config.gpr_speed_m_s);
|
||||||
|
const double motion_look_angle_deg = static_cast<double>(live_config.gpr_look_angle_deg);
|
||||||
|
const double motion_direction_sign = static_cast<double>(live_config.gpr_direction_sign);
|
||||||
|
for (auto& trace : selected_traces) {
|
||||||
|
apply_intra_sweep_motion_correction(
|
||||||
|
trace,
|
||||||
|
trace.tx_local_index,
|
||||||
|
motion_slot_count,
|
||||||
|
start_hz,
|
||||||
|
stop_hz,
|
||||||
|
velocity_mps,
|
||||||
|
motion_speed_mps,
|
||||||
|
motion_look_angle_deg,
|
||||||
|
motion_direction_sign,
|
||||||
|
tx_sweep_time_s,
|
||||||
|
motion_mode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
std::unordered_map<PairKey, AscanResult> ascans_by_pair{};
|
std::unordered_map<PairKey, AscanResult> ascans_by_pair{};
|
||||||
for (const auto& trace : selected_traces) {
|
for (const auto& trace : selected_traces) {
|
||||||
auto ascan = compute_ascan(trace, start_hz, stop_hz);
|
auto ascan = compute_ascan(trace, start_hz, stop_hz);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <queue>
|
#include <queue>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from typing import Any, Dict, Tuple
|
from typing import Any, Dict, Tuple
|
||||||
|
|
||||||
HOST = "192.168.2.6"
|
HOST = "127.0.0.1"
|
||||||
PORT = 8888
|
PORT = 8888
|
||||||
CLIENT_DEVICE_ID = 0
|
CLIENT_DEVICE_ID = 0
|
||||||
MIN_TEST_VLC = 5.0
|
MIN_TEST_VLC = 5.0
|
||||||
|
|||||||
@@ -74,11 +74,13 @@ _WEB_LIVE_SCHEMA = [
|
|||||||
("gpr_visible_x_max_m", "Visible window", _GPR_MODES, _dual("_gpr_visible_x_max_m", "_legacy_gpr_visible_x_max_m")),
|
("gpr_visible_x_max_m", "Visible window", _GPR_MODES, _dual("_gpr_visible_x_max_m", "_legacy_gpr_visible_x_max_m")),
|
||||||
("gpr_visible_z_min_m", "Visible window", _GPR_MODES, _dual("_gpr_visible_z_min_m", "_legacy_gpr_visible_z_min_m")),
|
("gpr_visible_z_min_m", "Visible window", _GPR_MODES, _dual("_gpr_visible_z_min_m", "_legacy_gpr_visible_z_min_m")),
|
||||||
("gpr_visible_z_max_m", "Visible window", _GPR_MODES, _dual("_gpr_visible_z_max_m", "_legacy_gpr_visible_z_max_m")),
|
("gpr_visible_z_max_m", "Visible window", _GPR_MODES, _dual("_gpr_visible_z_max_m", "_legacy_gpr_visible_z_max_m")),
|
||||||
("gpr_look_angle_deg", "Motion", ("legacy_gpr",), _attr("_legacy_gpr_look_angle_deg")),
|
("gpr_motion_mode", "Motion", ("gpr",), _attr("_gpr_motion_mode")),
|
||||||
|
("gpr_look_angle_deg", "Motion", _GPR_MODES, _dual("_gpr_look_angle_deg", "_legacy_gpr_look_angle_deg")),
|
||||||
|
("gpr_direction_sign", "Motion", ("gpr",), _attr("_gpr_direction_sign")),
|
||||||
("gpr_apply_freq_phase_correction", "Motion", ("legacy_gpr",), _attr("_legacy_gpr_apply_freq_phase_correction")),
|
("gpr_apply_freq_phase_correction", "Motion", ("legacy_gpr",), _attr("_legacy_gpr_apply_freq_phase_correction")),
|
||||||
("gpr_reference_mode", "Motion", ("legacy_gpr",), _attr("_legacy_gpr_reference_mode")),
|
("gpr_reference_mode", "Motion", ("legacy_gpr",), _attr("_legacy_gpr_reference_mode")),
|
||||||
("ignore_socket_speed", "Motion", ("legacy_gpr",), _attr("_legacy_gpr_ignore_socket_speed_enabled")),
|
("ignore_socket_speed", "Motion", _GPR_MODES, _dual("_gpr_ignore_socket_speed_enabled", "_legacy_gpr_ignore_socket_speed_enabled")),
|
||||||
("gpr_speed_m_s", "Motion", ("legacy_gpr",), _attr("_legacy_gpr_speed_m_s")),
|
("gpr_speed_m_s", "Motion", _GPR_MODES, _dual("_gpr_speed_m_s", "_legacy_gpr_speed_m_s")),
|
||||||
]
|
]
|
||||||
_WEB_LIVE_GETTERS = {field: getter for field, _group, _modes, getter in _WEB_LIVE_SCHEMA}
|
_WEB_LIVE_GETTERS = {field: getter for field, _group, _modes, getter in _WEB_LIVE_SCHEMA}
|
||||||
|
|
||||||
@@ -93,7 +95,6 @@ _NON_WIDGET_LIVE_FIELDS = frozenset({
|
|||||||
"reprocess_current_result",
|
"reprocess_current_result",
|
||||||
"history_command",
|
"history_command",
|
||||||
"history_command_seq",
|
"history_command_seq",
|
||||||
"gpr_direction_sign",
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# Display toggles: GUI-only rendering choices (kept in the GUI profile, not the live
|
# Display toggles: GUI-only rendering choices (kept in the GUI profile, not the live
|
||||||
|
|||||||
@@ -293,6 +293,11 @@ class AppWindowConfigProfileIOMixin:
|
|||||||
self._gpr_range_comp_power,
|
self._gpr_range_comp_power,
|
||||||
self._gpr_angle_comp_power,
|
self._gpr_angle_comp_power,
|
||||||
self._gpr_score_mode,
|
self._gpr_score_mode,
|
||||||
|
self._gpr_motion_mode,
|
||||||
|
self._gpr_look_angle_deg,
|
||||||
|
self._gpr_direction_sign,
|
||||||
|
self._gpr_ignore_socket_speed_enabled,
|
||||||
|
self._gpr_speed_m_s,
|
||||||
self._gpr_max_detected_objects_to_draw,
|
self._gpr_max_detected_objects_to_draw,
|
||||||
self._gpr_draw_top_m_objects,
|
self._gpr_draw_top_m_objects,
|
||||||
self._gpr_start_freq_mhz,
|
self._gpr_start_freq_mhz,
|
||||||
@@ -453,6 +458,13 @@ class AppWindowConfigProfileIOMixin:
|
|||||||
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._set_combo_current_text(self._gpr_score_mode, gui_state.processing.gpr.score_mode)
|
self._set_combo_current_text(self._gpr_score_mode, gui_state.processing.gpr.score_mode)
|
||||||
|
self._set_combo_current_text(self._gpr_motion_mode, gui_state.processing.gpr.motion_mode)
|
||||||
|
self._gpr_look_angle_deg.setValue(float(gui_state.processing.gpr.look_angle_deg))
|
||||||
|
self._gpr_direction_sign.setValue(float(gui_state.processing.gpr.direction_sign))
|
||||||
|
self._gpr_ignore_socket_speed_enabled.setChecked(
|
||||||
|
bool(gui_state.processing.gpr.ignore_socket_speed_enabled)
|
||||||
|
)
|
||||||
|
self._gpr_speed_m_s.setValue(float(gui_state.processing.gpr.speed_m_s))
|
||||||
self._gpr_max_detected_objects_to_draw.setValue(
|
self._gpr_max_detected_objects_to_draw.setValue(
|
||||||
int(gui_state.processing.gpr.max_detected_objects_to_draw)
|
int(gui_state.processing.gpr.max_detected_objects_to_draw)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -223,9 +223,14 @@ class AppWindowConfigStateBuildersMixin:
|
|||||||
output_positions=self._default_gpr_output_positions_from_config(config),
|
output_positions=self._default_gpr_output_positions_from_config(config),
|
||||||
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.1,
|
||||||
angle_comp_power=0.10,
|
angle_comp_power=0.0,
|
||||||
score_mode="combined",
|
score_mode="combined",
|
||||||
|
motion_mode="int_minus",
|
||||||
|
look_angle_deg=0.0,
|
||||||
|
direction_sign=1.0,
|
||||||
|
speed_m_s=0.0,
|
||||||
|
ignore_socket_speed_enabled=False,
|
||||||
max_detected_objects_to_draw=5,
|
max_detected_objects_to_draw=5,
|
||||||
draw_top_m_objects=2,
|
draw_top_m_objects=2,
|
||||||
start_freq_mhz=3000.0,
|
start_freq_mhz=3000.0,
|
||||||
@@ -347,6 +352,11 @@ class AppWindowConfigStateBuildersMixin:
|
|||||||
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()),
|
||||||
score_mode=self._gpr_score_mode.currentText(),
|
score_mode=self._gpr_score_mode.currentText(),
|
||||||
|
motion_mode=self._gpr_motion_mode.currentText(),
|
||||||
|
look_angle_deg=float(self._gpr_look_angle_deg.value()),
|
||||||
|
direction_sign=float(self._gpr_direction_sign.value()),
|
||||||
|
speed_m_s=float(self._gpr_speed_m_s.value()),
|
||||||
|
ignore_socket_speed_enabled=bool(self._gpr_ignore_socket_speed_enabled.isChecked()),
|
||||||
max_detected_objects_to_draw=int(self._gpr_max_detected_objects_to_draw.value()),
|
max_detected_objects_to_draw=int(self._gpr_max_detected_objects_to_draw.value()),
|
||||||
draw_top_m_objects=int(self._gpr_draw_top_m_objects.value()),
|
draw_top_m_objects=int(self._gpr_draw_top_m_objects.value()),
|
||||||
start_freq_mhz=float(self._gpr_start_freq_mhz.value()),
|
start_freq_mhz=float(self._gpr_start_freq_mhz.value()),
|
||||||
|
|||||||
@@ -234,6 +234,48 @@ def build_processing_group(owner) -> QGroupBox:
|
|||||||
owner._gpr_score_mode.addItems(["peak", "combined"])
|
owner._gpr_score_mode.addItems(["peak", "combined"])
|
||||||
owner._set_combo_current_text(owner._gpr_score_mode, gpr_live_defaults.score_mode)
|
owner._set_combo_current_text(owner._gpr_score_mode, gpr_live_defaults.score_mode)
|
||||||
|
|
||||||
|
owner._gpr_motion_mode = QComboBox()
|
||||||
|
owner._gpr_motion_mode.addItems(["int_minus", "int_focus"])
|
||||||
|
owner._set_combo_current_text(owner._gpr_motion_mode, gpr_live_defaults.motion_mode)
|
||||||
|
owner._gpr_motion_mode.setToolTip(
|
||||||
|
"Intra-sweep speed correction before the IFFT. int_minus: full correction; "
|
||||||
|
"int_focus: focusing residual only (no net Z shift)."
|
||||||
|
)
|
||||||
|
|
||||||
|
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.5)
|
||||||
|
owner._gpr_look_angle_deg.setValue(float(gpr_live_defaults.look_angle_deg))
|
||||||
|
owner._gpr_look_angle_deg.setToolTip(
|
||||||
|
"Radar look angle vs the range axis (deg). Used by intra-sweep motion correction."
|
||||||
|
)
|
||||||
|
|
||||||
|
owner._gpr_direction_sign = QDoubleSpinBox()
|
||||||
|
owner._gpr_direction_sign.setDecimals(0)
|
||||||
|
owner._gpr_direction_sign.setRange(-1.0, 1.0)
|
||||||
|
owner._gpr_direction_sign.setSingleStep(2.0)
|
||||||
|
owner._gpr_direction_sign.setValue(float(gpr_live_defaults.direction_sign))
|
||||||
|
owner._gpr_direction_sign.setToolTip(
|
||||||
|
"Motion direction along range: +1 (later frequencies deeper) or -1 (shallower)."
|
||||||
|
)
|
||||||
|
|
||||||
|
owner._gpr_ignore_socket_speed_enabled = QCheckBox("Ignore socket speed")
|
||||||
|
owner._gpr_ignore_socket_speed_enabled.setChecked(bool(gpr_live_defaults.ignore_socket_speed_enabled))
|
||||||
|
owner._gpr_ignore_socket_speed_enabled.setToolTip(
|
||||||
|
"When checked, use the speed below for motion correction instead of the "
|
||||||
|
"value streamed over the locator socket."
|
||||||
|
)
|
||||||
|
|
||||||
|
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.05)
|
||||||
|
owner._gpr_speed_m_s.setValue(float(gpr_live_defaults.speed_m_s))
|
||||||
|
owner._gpr_speed_m_s.setToolTip(
|
||||||
|
"Radar speed (m/s) used by intra-sweep motion correction when 'Ignore socket speed' is on."
|
||||||
|
)
|
||||||
|
|
||||||
owner._gpr_max_detected_objects_to_draw = QSpinBox()
|
owner._gpr_max_detected_objects_to_draw = QSpinBox()
|
||||||
owner._gpr_max_detected_objects_to_draw.setRange(0, 10_000)
|
owner._gpr_max_detected_objects_to_draw.setRange(0, 10_000)
|
||||||
owner._gpr_max_detected_objects_to_draw.setValue(int(gpr_live_defaults.max_detected_objects_to_draw))
|
owner._gpr_max_detected_objects_to_draw.setValue(int(gpr_live_defaults.max_detected_objects_to_draw))
|
||||||
@@ -317,6 +359,11 @@ def build_processing_group(owner) -> QGroupBox:
|
|||||||
("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),
|
||||||
("Score mode", owner._gpr_score_mode),
|
("Score mode", owner._gpr_score_mode),
|
||||||
|
("Motion mode", owner._gpr_motion_mode),
|
||||||
|
("Look angle deg", owner._gpr_look_angle_deg),
|
||||||
|
("Direction sign", owner._gpr_direction_sign),
|
||||||
|
owner._gpr_ignore_socket_speed_enabled,
|
||||||
|
("Speed m/s", owner._gpr_speed_m_s),
|
||||||
("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),
|
||||||
("Max detected objects", owner._gpr_max_detected_objects_to_draw),
|
("Max detected objects", owner._gpr_max_detected_objects_to_draw),
|
||||||
@@ -524,6 +571,11 @@ def build_processing_group(owner) -> QGroupBox:
|
|||||||
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_score_mode.currentTextChanged.connect(owner._on_processing_live_settings_changed)
|
owner._gpr_score_mode.currentTextChanged.connect(owner._on_processing_live_settings_changed)
|
||||||
|
owner._gpr_motion_mode.currentTextChanged.connect(owner._on_processing_live_settings_changed)
|
||||||
|
owner._gpr_look_angle_deg.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||||
|
owner._gpr_direction_sign.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||||
|
owner._gpr_ignore_socket_speed_enabled.toggled.connect(owner._on_processing_live_settings_changed)
|
||||||
|
owner._gpr_speed_m_s.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)
|
||||||
|
|||||||
@@ -288,6 +288,36 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
|
|||||||
gui.processing.gpr.score_mode,
|
gui.processing.gpr.score_mode,
|
||||||
"gui.processing.gpr",
|
"gui.processing.gpr",
|
||||||
),
|
),
|
||||||
|
motion_mode=_optional_string(
|
||||||
|
gpr_object,
|
||||||
|
"motion_mode",
|
||||||
|
gui.processing.gpr.motion_mode,
|
||||||
|
"gui.processing.gpr",
|
||||||
|
),
|
||||||
|
look_angle_deg=_optional_float(
|
||||||
|
gpr_object,
|
||||||
|
"look_angle_deg",
|
||||||
|
gui.processing.gpr.look_angle_deg,
|
||||||
|
"gui.processing.gpr",
|
||||||
|
),
|
||||||
|
direction_sign=_optional_float(
|
||||||
|
gpr_object,
|
||||||
|
"direction_sign",
|
||||||
|
gui.processing.gpr.direction_sign,
|
||||||
|
"gui.processing.gpr",
|
||||||
|
),
|
||||||
|
speed_m_s=_optional_float(
|
||||||
|
gpr_object,
|
||||||
|
"speed_m_s",
|
||||||
|
gui.processing.gpr.speed_m_s,
|
||||||
|
"gui.processing.gpr",
|
||||||
|
),
|
||||||
|
ignore_socket_speed_enabled=_optional_bool(
|
||||||
|
gpr_object,
|
||||||
|
"ignore_socket_speed_enabled",
|
||||||
|
gui.processing.gpr.ignore_socket_speed_enabled,
|
||||||
|
"gui.processing.gpr",
|
||||||
|
),
|
||||||
max_detected_objects_to_draw=_optional_int(
|
max_detected_objects_to_draw=_optional_int(
|
||||||
gpr_object,
|
gpr_object,
|
||||||
"max_detected_objects_to_draw",
|
"max_detected_objects_to_draw",
|
||||||
@@ -425,6 +455,8 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
|
|||||||
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.score_mode not in {"peak", "combined"}:
|
if gui.processing.gpr.score_mode not in {"peak", "combined"}:
|
||||||
raise ValueError("gui.processing.gpr.score_mode must be one of: peak, combined")
|
raise ValueError("gui.processing.gpr.score_mode must be one of: peak, combined")
|
||||||
|
if gui.processing.gpr.motion_mode not in {"int_minus", "int_focus"}:
|
||||||
|
raise ValueError("gui.processing.gpr.motion_mode must be one of: int_minus, int_focus")
|
||||||
if gui.processing.legacy_gpr.mode not in {"point", "extended"}:
|
if gui.processing.legacy_gpr.mode not in {"point", "extended"}:
|
||||||
raise ValueError("gui.processing.legacy_gpr.mode must be one of: point, extended")
|
raise ValueError("gui.processing.legacy_gpr.mode must be one of: point, extended")
|
||||||
if gui.processing.legacy_gpr.render_mode not in {"heatmap", "objects_only"}:
|
if gui.processing.legacy_gpr.render_mode not in {"heatmap", "objects_only"}:
|
||||||
@@ -550,6 +582,11 @@ def gui_profile_to_dict(model: GuiProfileModel) -> dict[str, Any]:
|
|||||||
"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,
|
||||||
"score_mode": gui.processing.gpr.score_mode,
|
"score_mode": gui.processing.gpr.score_mode,
|
||||||
|
"motion_mode": gui.processing.gpr.motion_mode,
|
||||||
|
"look_angle_deg": gui.processing.gpr.look_angle_deg,
|
||||||
|
"direction_sign": gui.processing.gpr.direction_sign,
|
||||||
|
"speed_m_s": gui.processing.gpr.speed_m_s,
|
||||||
|
"ignore_socket_speed_enabled": gui.processing.gpr.ignore_socket_speed_enabled,
|
||||||
"max_detected_objects_to_draw": gui.processing.gpr.max_detected_objects_to_draw,
|
"max_detected_objects_to_draw": gui.processing.gpr.max_detected_objects_to_draw,
|
||||||
"draw_top_m_objects": gui.processing.gpr.draw_top_m_objects,
|
"draw_top_m_objects": gui.processing.gpr.draw_top_m_objects,
|
||||||
"start_freq_mhz": gui.processing.gpr.start_freq_mhz,
|
"start_freq_mhz": gui.processing.gpr.start_freq_mhz,
|
||||||
|
|||||||
@@ -58,9 +58,17 @@ class GuiGprStateModel:
|
|||||||
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.1
|
||||||
angle_comp_power: float = 0.10
|
angle_comp_power: float = 0.0
|
||||||
score_mode: str = "combined"
|
score_mode: str = "combined"
|
||||||
|
motion_mode: str = "int_minus"
|
||||||
|
# Intra-sweep motion-correction inputs. Sweep time is derived from acquisition
|
||||||
|
# metadata; speed comes from the socket unless `ignore_socket_speed_enabled`,
|
||||||
|
# in which case `speed_m_s` from here is used.
|
||||||
|
look_angle_deg: float = 0.0
|
||||||
|
direction_sign: float = 1.0
|
||||||
|
speed_m_s: float = 0.0
|
||||||
|
ignore_socket_speed_enabled: bool = False
|
||||||
max_detected_objects_to_draw: int = 5
|
max_detected_objects_to_draw: int = 5
|
||||||
draw_top_m_objects: int = 2
|
draw_top_m_objects: int = 2
|
||||||
start_freq_mhz: float = 3000.0
|
start_freq_mhz: float = 3000.0
|
||||||
|
|||||||
@@ -31,10 +31,14 @@ class ProcessingLiveConfig:
|
|||||||
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.1
|
||||||
gpr_angle_comp_power: float = 0.10
|
gpr_angle_comp_power: float = 0.0
|
||||||
gpr_comp_power: float = 0.2
|
gpr_comp_power: float = 0.2
|
||||||
gpr_score_mode: str = "combined"
|
gpr_score_mode: str = "combined"
|
||||||
|
# Backprojection intra-sweep speed-correction mode: "int_minus" (full
|
||||||
|
# correction) or "int_focus" (focusing residual only). Mirrors Python
|
||||||
|
# Horns_motion_3libre.py MOTION_CORRECTION_MODE.
|
||||||
|
gpr_motion_mode: str = "int_minus"
|
||||||
gpr_max_detected_objects_to_draw: int = 5
|
gpr_max_detected_objects_to_draw: int = 5
|
||||||
gpr_draw_top_m_objects: int = 2
|
gpr_draw_top_m_objects: int = 2
|
||||||
gpr_speed_m_s: float = 0.0
|
gpr_speed_m_s: float = 0.0
|
||||||
@@ -108,6 +112,7 @@ class ProcessingLiveConfig:
|
|||||||
"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_comp_power": float(self.gpr_comp_power),
|
||||||
"gpr_score_mode": str(self.gpr_score_mode),
|
"gpr_score_mode": str(self.gpr_score_mode),
|
||||||
|
"gpr_motion_mode": str(self.gpr_motion_mode),
|
||||||
"gpr_max_detected_objects_to_draw": int(self.gpr_max_detected_objects_to_draw),
|
"gpr_max_detected_objects_to_draw": int(self.gpr_max_detected_objects_to_draw),
|
||||||
"gpr_draw_top_m_objects": int(self.gpr_draw_top_m_objects),
|
"gpr_draw_top_m_objects": int(self.gpr_draw_top_m_objects),
|
||||||
"gpr_speed_m_s": float(self.gpr_speed_m_s),
|
"gpr_speed_m_s": float(self.gpr_speed_m_s),
|
||||||
|
|||||||
+7
-2
@@ -281,9 +281,14 @@
|
|||||||
"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.1,
|
||||||
"angle_comp_power": 0.1,
|
"angle_comp_power": 0.0,
|
||||||
"score_mode": "combined",
|
"score_mode": "combined",
|
||||||
|
"motion_mode": "int_minus",
|
||||||
|
"look_angle_deg": 0.0,
|
||||||
|
"direction_sign": 1.0,
|
||||||
|
"speed_m_s": 0.0,
|
||||||
|
"ignore_socket_speed_enabled": false,
|
||||||
"max_detected_objects_to_draw": 5,
|
"max_detected_objects_to_draw": 5,
|
||||||
"draw_top_m_objects": 2,
|
"draw_top_m_objects": 2,
|
||||||
"start_freq_mhz": 3000.0,
|
"start_freq_mhz": 3000.0,
|
||||||
|
|||||||
@@ -281,9 +281,14 @@
|
|||||||
"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.1,
|
||||||
"angle_comp_power": 0.1,
|
"angle_comp_power": 0.0,
|
||||||
"score_mode": "combined",
|
"score_mode": "combined",
|
||||||
|
"motion_mode": "int_minus",
|
||||||
|
"look_angle_deg": 0.0,
|
||||||
|
"direction_sign": 1.0,
|
||||||
|
"speed_m_s": 0.0,
|
||||||
|
"ignore_socket_speed_enabled": false,
|
||||||
"max_detected_objects_to_draw": 5,
|
"max_detected_objects_to_draw": 5,
|
||||||
"draw_top_m_objects": 2,
|
"draw_top_m_objects": 2,
|
||||||
"start_freq_mhz": 3000.0,
|
"start_freq_mhz": 3000.0,
|
||||||
|
|||||||
@@ -281,9 +281,14 @@
|
|||||||
"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.1,
|
||||||
"angle_comp_power": 0.1,
|
"angle_comp_power": 0.0,
|
||||||
"score_mode": "combined",
|
"score_mode": "combined",
|
||||||
|
"motion_mode": "int_minus",
|
||||||
|
"look_angle_deg": 0.0,
|
||||||
|
"direction_sign": 1.0,
|
||||||
|
"speed_m_s": 0.0,
|
||||||
|
"ignore_socket_speed_enabled": false,
|
||||||
"max_detected_objects_to_draw": 5,
|
"max_detected_objects_to_draw": 5,
|
||||||
"draw_top_m_objects": 2,
|
"draw_top_m_objects": 2,
|
||||||
"start_freq_mhz": 3000.0,
|
"start_freq_mhz": 3000.0,
|
||||||
|
|||||||
Reference in New Issue
Block a user