added new filtration and fixed processing parameters
This commit is contained in:
@@ -188,7 +188,7 @@ auto ShmRing::open_or_create(
|
|||||||
const bool geometry_ok = header->capacity == capacity && header->slot_size_bytes == slot_size_bytes;
|
const bool geometry_ok = header->capacity == capacity && header->slot_size_bytes == slot_size_bytes;
|
||||||
|
|
||||||
if (magic_ok && version_ok && geometry_ok) {
|
if (magic_ok && version_ok && geometry_ok) {
|
||||||
// Fix #50: the requested mapped_size matched the header geometry, but the
|
// the requested mapped_size matched the header geometry, but the
|
||||||
// backing file may have been created undersized by another process. Confirm
|
// backing file may have been created undersized by another process. Confirm
|
||||||
// st_size covers the geometry before trusting the mapping.
|
// st_size covers the geometry before trusting the mapping.
|
||||||
struct stat info {};
|
struct stat info {};
|
||||||
@@ -257,7 +257,7 @@ auto ShmRing::open_existing(const std::string& name) -> ShmRing {
|
|||||||
if (header->version != kRingVersion) {
|
if (header->version != kRingVersion) {
|
||||||
throw std::runtime_error("Shared memory ring version mismatch for " + name);
|
throw std::runtime_error("Shared memory ring version mismatch for " + name);
|
||||||
}
|
}
|
||||||
// Fix #50: ensure the mapping actually spans every slot the header describes.
|
// ensure the mapping actually spans every slot the header describes.
|
||||||
validate_geometry(*header, mapped_size, name);
|
validate_geometry(*header, mapped_size, name);
|
||||||
|
|
||||||
ShmRing ring{};
|
ShmRing ring{};
|
||||||
@@ -411,7 +411,7 @@ void ShmRing::validate_name(const std::string& name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ShmRing::validate_geometry(const Header& header, std::size_t mapped_size, const std::string& name) {
|
void ShmRing::validate_geometry(const Header& header, std::size_t mapped_size, const std::string& name) {
|
||||||
// Fix #50: derive the expected size from the header's own geometry fields and
|
// derive the expected size from the header's own geometry fields and
|
||||||
// require the real mapping to cover it. A bogus capacity/slot_size or a truncated
|
// require the real mapping to cover it. A bogus capacity/slot_size or a truncated
|
||||||
// mapping would otherwise yield out-of-bounds slot offsets and a SIGSEGV.
|
// mapping would otherwise yield out-of-bounds slot offsets and a SIGSEGV.
|
||||||
const std::uint32_t capacity = header.capacity;
|
const std::uint32_t capacity = header.capacity;
|
||||||
|
|||||||
+9
-5
@@ -45,8 +45,10 @@ struct ProcessingLiveConfig {
|
|||||||
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.1F;
|
float gpr_range_comp_power = 0.1F;
|
||||||
float gpr_angle_comp_power = 0.0F;
|
|
||||||
float gpr_comp_power = 0.2F;
|
float gpr_comp_power = 0.2F;
|
||||||
|
// BP object-detection stop level, as a fraction of the global peak (Python
|
||||||
|
// Horns_motion_3libre.py BP_OBJECT_MIN_FRAC); peaks below it are not objects.
|
||||||
|
float gpr_object_min_frac = 0.7F;
|
||||||
std::string gpr_score_mode = "combined";
|
std::string gpr_score_mode = "combined";
|
||||||
// Backprojection intra-sweep speed-correction mode: "int_minus" (full
|
// Backprojection intra-sweep speed-correction mode: "int_minus" (full
|
||||||
// correction) or "int_focus" (focusing residual only). Mirrors the Python
|
// correction) or "int_focus" (focusing residual only). Mirrors the Python
|
||||||
@@ -73,13 +75,15 @@ struct ProcessingLiveConfig {
|
|||||||
// BP image is computed in the y=imaging_plane_y_m slice of the 3D grid.
|
// BP image is computed in the y=imaging_plane_y_m slice of the 3D grid.
|
||||||
// Default 0 keeps legacy 1D antenna layouts imaging in the antenna plane.
|
// Default 0 keeps legacy 1D antenna layouts imaging in the antenna plane.
|
||||||
float gpr_imaging_plane_y_m = 0.0F;
|
float gpr_imaging_plane_y_m = 0.0F;
|
||||||
// Locator filter parameters. Mode-dependent threshold (legacy_gpr uses
|
// Coherent BP object visibility (window + the draw limits below) is applied in
|
||||||
// `legacy_gpr_min_visible_pair_count`, everything else uses
|
// the processor itself, matching Horns_motion_3libre.py — there is NO score
|
||||||
// `gpr_min_visible_score`). Draw limits apply only to non-legacy modes.
|
// threshold for it. Only legacy GPR still thresholds, on a pair count.
|
||||||
float gpr_min_visible_score = 0.0F;
|
|
||||||
float legacy_gpr_min_visible_pair_count = 0.0F;
|
float legacy_gpr_min_visible_pair_count = 0.0F;
|
||||||
std::uint32_t gpr_max_detected_objects_to_draw = 0;
|
std::uint32_t gpr_max_detected_objects_to_draw = 0;
|
||||||
std::uint32_t gpr_draw_top_m_objects = 0;
|
std::uint32_t gpr_draw_top_m_objects = 0;
|
||||||
|
// Cross-frame approach filter: an object is shown only once it persists as a
|
||||||
|
// motion-consistent track over this many consecutive frames (<= 1 disables it).
|
||||||
|
std::uint32_t gpr_object_approach_min_frames = 3;
|
||||||
// Visible X/Z window (metres). The locator clips broadcast objects to this
|
// Visible X/Z window (metres). The locator clips broadcast objects to this
|
||||||
// window so the socket emits only what the desktop plot actually shows.
|
// window so the socket emits only what the desktop plot actually shows.
|
||||||
float gpr_visible_x_min_m = -2.0F;
|
float gpr_visible_x_min_m = -2.0F;
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ void DataProcessor::run(const std::atomic<bool>& stop_requested) {
|
|||||||
std::uint64_t last_applied_history_command_seq = 0;
|
std::uint64_t last_applied_history_command_seq = 0;
|
||||||
std::uint64_t error_count = 0;
|
std::uint64_t error_count = 0;
|
||||||
std::uint64_t consecutive_errors = 0;
|
std::uint64_t consecutive_errors = 0;
|
||||||
// Fix #55: track the socket-fed speed used for the last reprocess so a change
|
// track the socket-fed speed used for the last reprocess so a change
|
||||||
// arriving without a live-config revision bump still triggers a reprocess of
|
// arriving without a live-config revision bump still triggers a reprocess of
|
||||||
// the current result (gated below by reprocess_current_result).
|
// the current result (gated below by reprocess_current_result).
|
||||||
std::optional<double> last_reprocessed_socket_speed = std::nullopt;
|
std::optional<double> last_reprocessed_socket_speed = std::nullopt;
|
||||||
@@ -124,7 +124,7 @@ void DataProcessor::run(const std::atomic<bool>& stop_requested) {
|
|||||||
publish_locator(replay_result, live_config);
|
publish_locator(replay_result, live_config);
|
||||||
}
|
}
|
||||||
last_replayed_revision = live_revision;
|
last_replayed_revision = live_revision;
|
||||||
// Fix #55: record the speed we just reprocessed with so an
|
//record the speed we just reprocessed with so an
|
||||||
// unchanged socket value does not retrigger every iteration.
|
// unchanged socket value does not retrigger every iteration.
|
||||||
last_reprocessed_socket_speed = current_socket_speed;
|
last_reprocessed_socket_speed = current_socket_speed;
|
||||||
}
|
}
|
||||||
@@ -232,29 +232,23 @@ auto DataProcessor::build_locator_filter(const ProcessingLiveConfig& live_config
|
|||||||
live_config.processor_mode.empty() ? default_processor_mode_ : live_config.processor_mode;
|
live_config.processor_mode.empty() ? default_processor_mode_ : live_config.processor_mode;
|
||||||
|
|
||||||
radar::locator::FilterParams filter{};
|
radar::locator::FilterParams filter{};
|
||||||
// Clip broadcast objects to the same visible X/Z window the desktop plot uses,
|
|
||||||
// so the socket emits only the objects the operator actually sees.
|
|
||||||
filter.visible_bounds = radar::locator::VisibleBounds{
|
|
||||||
.x_min = live_config.gpr_visible_x_min_m,
|
|
||||||
.x_max = live_config.gpr_visible_x_max_m,
|
|
||||||
.z_min = live_config.gpr_visible_z_min_m,
|
|
||||||
.z_max = live_config.gpr_visible_z_max_m,
|
|
||||||
};
|
|
||||||
if (requested_mode == "legacy_gpr") {
|
if (requested_mode == "legacy_gpr") {
|
||||||
|
// Legacy GPR emits its objects unfiltered, so the socket applies the legacy
|
||||||
|
// rule here: clip to the visible window and threshold on the pair count. The
|
||||||
|
// GUI disables "draw top N" for legacy, so we skip it on the wire to match.
|
||||||
|
filter.visible_bounds = radar::locator::VisibleBounds{
|
||||||
|
.x_min = live_config.gpr_visible_x_min_m,
|
||||||
|
.x_max = live_config.gpr_visible_x_max_m,
|
||||||
|
.z_min = live_config.gpr_visible_z_min_m,
|
||||||
|
.z_max = live_config.gpr_visible_z_max_m,
|
||||||
|
};
|
||||||
filter.min_score = live_config.legacy_gpr_min_visible_pair_count;
|
filter.min_score = live_config.legacy_gpr_min_visible_pair_count;
|
||||||
// The GUI deliberately disables the "draw top N" capping for legacy
|
|
||||||
// GPR, so we also skip it on the wire to match observation semantics.
|
|
||||||
filter.draw_limits.reset();
|
filter.draw_limits.reset();
|
||||||
} else {
|
|
||||||
filter.min_score = live_config.gpr_min_visible_score;
|
|
||||||
if (live_config.gpr_max_detected_objects_to_draw > 0U
|
|
||||||
&& live_config.gpr_draw_top_m_objects > 0U) {
|
|
||||||
filter.draw_limits = radar::locator::DrawLimits{
|
|
||||||
.max_detected_objects = live_config.gpr_max_detected_objects_to_draw,
|
|
||||||
.draw_top_objects = live_config.gpr_draw_top_m_objects,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// Coherent BP already emits the FINAL visible object set from the processor
|
||||||
|
// (window + N/M, no score threshold — matching Horns_motion_3libre.py), so the
|
||||||
|
// socket forwards it verbatim. The default FilterParams passes everything through
|
||||||
|
// (it only drops non-finite rows), keeping the filtering logic in one place.
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -255,11 +255,11 @@ void apply_legacy_gpr_algorithm_alias(ProcessingLiveConfig& config, const std::s
|
|||||||
}
|
}
|
||||||
config.gpr_range_comp_power = static_cast<float>(found->get<double>());
|
config.gpr_range_comp_power = static_cast<float>(found->get<double>());
|
||||||
}
|
}
|
||||||
if (const auto found = root.find("gpr_angle_comp_power"); found != root.end()) {
|
if (const auto found = root.find("gpr_object_min_frac"); found != root.end()) {
|
||||||
if (!found->is_number()) {
|
if (!found->is_number()) {
|
||||||
throw std::runtime_error("processing.gpr_angle_comp_power must be number");
|
throw std::runtime_error("processing.gpr_object_min_frac must be number");
|
||||||
}
|
}
|
||||||
config.gpr_angle_comp_power = static_cast<float>(found->get<double>());
|
config.gpr_object_min_frac = static_cast<float>(found->get<double>());
|
||||||
}
|
}
|
||||||
if (const auto found = root.find("gpr_comp_power"); found != root.end()) {
|
if (const auto found = root.find("gpr_comp_power"); found != root.end()) {
|
||||||
if (!found->is_number()) {
|
if (!found->is_number()) {
|
||||||
@@ -355,12 +355,6 @@ void apply_legacy_gpr_algorithm_alias(ProcessingLiveConfig& config, const std::s
|
|||||||
}
|
}
|
||||||
config.gpr_imaging_plane_y_m = static_cast<float>(found->get<double>());
|
config.gpr_imaging_plane_y_m = static_cast<float>(found->get<double>());
|
||||||
}
|
}
|
||||||
if (const auto found = root.find("gpr_min_visible_score"); found != root.end()) {
|
|
||||||
if (!found->is_number()) {
|
|
||||||
throw std::runtime_error("processing.gpr_min_visible_score must be number");
|
|
||||||
}
|
|
||||||
config.gpr_min_visible_score = static_cast<float>(found->get<double>());
|
|
||||||
}
|
|
||||||
for (const auto& [key, target] : {
|
for (const auto& [key, target] : {
|
||||||
std::pair{"gpr_visible_x_min_m", &config.gpr_visible_x_min_m},
|
std::pair{"gpr_visible_x_min_m", &config.gpr_visible_x_min_m},
|
||||||
std::pair{"gpr_visible_x_max_m", &config.gpr_visible_x_max_m},
|
std::pair{"gpr_visible_x_max_m", &config.gpr_visible_x_max_m},
|
||||||
@@ -388,6 +382,10 @@ void apply_legacy_gpr_algorithm_alias(ProcessingLiveConfig& config, const std::s
|
|||||||
config.gpr_draw_top_m_objects =
|
config.gpr_draw_top_m_objects =
|
||||||
parse_u32_number(*found, "processing.gpr_draw_top_m_objects");
|
parse_u32_number(*found, "processing.gpr_draw_top_m_objects");
|
||||||
}
|
}
|
||||||
|
if (const auto found = root.find("gpr_object_approach_min_frames"); found != root.end()) {
|
||||||
|
config.gpr_object_approach_min_frames =
|
||||||
|
parse_u32_number(*found, "processing.gpr_object_approach_min_frames");
|
||||||
|
}
|
||||||
if (const auto found = root.find("ignore_socket_speed"); found != root.end()) {
|
if (const auto found = root.find("ignore_socket_speed"); found != root.end()) {
|
||||||
if (!found->is_boolean()) {
|
if (!found->is_boolean()) {
|
||||||
throw std::runtime_error("processing.ignore_socket_speed must be bool");
|
throw std::runtime_error("processing.ignore_socket_speed must be bool");
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "object_approach_filter.hpp"
|
||||||
#include "processor_interface.hpp"
|
#include "processor_interface.hpp"
|
||||||
|
|
||||||
namespace radar::processing {
|
namespace radar::processing {
|
||||||
@@ -13,6 +14,11 @@ class GprProcessor final : public ProcessorInterface {
|
|||||||
std::span<const ipc::PreprocessedCollection> previous_collections,
|
std::span<const ipc::PreprocessedCollection> previous_collections,
|
||||||
const ProcessingLiveConfig& live_config
|
const ProcessingLiveConfig& live_config
|
||||||
) -> ipc::ResultCollection override;
|
) -> ipc::ResultCollection override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Cross-frame "approach" track filter. Persists across collections because the
|
||||||
|
// owning processor instance is long-lived (one per data_processor run).
|
||||||
|
ObjectApproachFilter approach_filter_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class LegacyGprProcessor final : public ProcessorInterface {
|
class LegacyGprProcessor final : public ProcessorInterface {
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <deque>
|
||||||
|
#include <limits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace radar::processing {
|
||||||
|
|
||||||
|
// Temporal "approach" filter for coherent-BP detections — a port of the objects-only
|
||||||
|
// filter in Horns_motion_3libre's demonstrate notebook.
|
||||||
|
//
|
||||||
|
// It keeps only objects that persist as a *motion-consistent track* across at least
|
||||||
|
// `min_frames` consecutive frames: as the radar moves, a real target reappears at a
|
||||||
|
// predictable, shifting (x, z), whereas a one-frame noise spike forms no track and is
|
||||||
|
// dropped. The expected per-frame change in range is `speed * dt * cos(look_angle)`,
|
||||||
|
// where `dt` is the real interval between consecutive frames (so dropped frames and a
|
||||||
|
// varying frame period are handled naturally).
|
||||||
|
//
|
||||||
|
// CAUSAL: unlike the offline notebook (which can look forward over the whole sequence),
|
||||||
|
// this confirms a track by looking *backward* — an object is kept once it ends a track
|
||||||
|
// of `min_frames` frames seen so far. A target therefore first appears after it has
|
||||||
|
// persisted `min_frames` frames; the early frames of its track are not shown
|
||||||
|
// retroactively.
|
||||||
|
//
|
||||||
|
// Stateless across pipeline restarts is approximated by breaking tracks across a large
|
||||||
|
// inter-frame gap (`kMaxFrameGapSeconds`), so a stale history from a previous run cannot
|
||||||
|
// spuriously confirm objects.
|
||||||
|
//
|
||||||
|
// IDEMPOTENT under reprocessing: the data_processor re-runs the last collection (or
|
||||||
|
// replays the whole window) whenever live settings or the socket speed change, with no
|
||||||
|
// new sweep. The history is therefore keyed by `frame_id` (the strictly increasing
|
||||||
|
// collection id): the same id replaces its entry (reprocess of the current frame), a
|
||||||
|
// smaller id rebuilds from scratch (a replay restart), so repeated reprocessing never
|
||||||
|
// duplicates frames or falsely confirms a track.
|
||||||
|
class ObjectApproachFilter {
|
||||||
|
public:
|
||||||
|
struct Point {
|
||||||
|
double x_m;
|
||||||
|
double z_m;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Record `objects` as frame `frame_id` and return, per object, whether it is
|
||||||
|
// confirmed (ends a >= `min_frames` motion-consistent track). `frame_id` is the
|
||||||
|
// collection id (identity/order, survives reprocessing); `frame_time_seconds` is the
|
||||||
|
// frame's wall-clock timestamp (drives the inter-frame interval); `speed_m_s`/
|
||||||
|
// `look_angle_deg` are the live motion estimate. `min_frames <= 1` disables filtering.
|
||||||
|
[[nodiscard]] auto confirm(
|
||||||
|
const std::vector<Point>& objects,
|
||||||
|
std::uint64_t frame_id,
|
||||||
|
double frame_time_seconds,
|
||||||
|
double speed_m_s,
|
||||||
|
double look_angle_deg,
|
||||||
|
std::size_t min_frames
|
||||||
|
) -> std::vector<bool> {
|
||||||
|
record_frame(Frame{frame_id, frame_time_seconds, objects});
|
||||||
|
const std::size_t history_depth = std::max<std::size_t>(min_frames, 1U);
|
||||||
|
while (history_.size() > history_depth) {
|
||||||
|
history_.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<bool> confirmed(objects.size(), min_frames <= 1U);
|
||||||
|
if (min_frames <= 1U || history_.size() < min_frames) {
|
||||||
|
return confirmed; // disabled, or not enough history yet to confirm anything
|
||||||
|
}
|
||||||
|
|
||||||
|
const double range_step_per_second = std::abs(speed_m_s) * std::cos(to_radians(look_angle_deg));
|
||||||
|
for (std::size_t object_index = 0U; object_index < objects.size(); ++object_index) {
|
||||||
|
confirmed[object_index] = has_backward_track(objects[object_index], min_frames, range_step_per_second);
|
||||||
|
}
|
||||||
|
return confirmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() { history_.clear(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Frame {
|
||||||
|
std::uint64_t id;
|
||||||
|
double time_seconds;
|
||||||
|
std::vector<Point> objects;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Append a genuinely new frame, replace the current one on reprocessing (same id),
|
||||||
|
// or rebuild from scratch when the id steps backward (a replay restart). This keeps
|
||||||
|
// the history one entry per distinct collection no matter how often settings change.
|
||||||
|
void record_frame(Frame frame) {
|
||||||
|
if (history_.empty() || frame.id > history_.back().id) {
|
||||||
|
history_.push_back(std::move(frame));
|
||||||
|
} else if (frame.id == history_.back().id) {
|
||||||
|
history_.back() = std::move(frame);
|
||||||
|
} else {
|
||||||
|
history_.clear();
|
||||||
|
history_.push_back(std::move(frame));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixed matching tolerances (Horns_motion notebook 0.2 block). Range decreases as the
|
||||||
|
// radar approaches the target, hence the negative Z sign.
|
||||||
|
static constexpr double kXToleranceM = 0.45;
|
||||||
|
static constexpr double kRangeToleranceFraction = 0.85;
|
||||||
|
static constexpr double kRangeToleranceFloorM = 0.12;
|
||||||
|
static constexpr double kRangeSign = -1.0;
|
||||||
|
static constexpr double kMaxFrameGapSeconds = 2.0;
|
||||||
|
|
||||||
|
[[nodiscard]] static auto to_radians(double degrees) -> double {
|
||||||
|
return degrees * (M_PI / 180.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk back from the current object through the history, matching a motion-consistent
|
||||||
|
// predecessor in each earlier frame. Confirmed iff a full chain of `min_frames` frames
|
||||||
|
// (the current one plus `min_frames - 1` predecessors) is found.
|
||||||
|
[[nodiscard]] auto has_backward_track(
|
||||||
|
const Point& object,
|
||||||
|
std::size_t min_frames,
|
||||||
|
double range_step_per_second
|
||||||
|
) const -> bool {
|
||||||
|
const std::size_t newest = history_.size() - 1U;
|
||||||
|
Point current = object;
|
||||||
|
for (std::size_t step = 1U; step < min_frames; ++step) {
|
||||||
|
const std::size_t earlier_index = newest - step;
|
||||||
|
const Frame& earlier = history_[earlier_index];
|
||||||
|
const double dt = history_[earlier_index + 1U].time_seconds - earlier.time_seconds;
|
||||||
|
if (!(dt > 0.0) || dt > kMaxFrameGapSeconds) {
|
||||||
|
return false; // non-monotonic time, or a gap that breaks the track
|
||||||
|
}
|
||||||
|
const double expected_range_shift = range_step_per_second * dt;
|
||||||
|
const Point* predecessor = match_predecessor(current, earlier.objects, expected_range_shift);
|
||||||
|
if (predecessor == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
current = *predecessor;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The best earlier-frame object consistent with `object` having moved by one frame:
|
||||||
|
// its range was larger by `expected_range_shift` (radar since approached), within the
|
||||||
|
// cross-range and range tolerances. Returns nullptr when nothing matches.
|
||||||
|
[[nodiscard]] static auto match_predecessor(
|
||||||
|
const Point& object,
|
||||||
|
const std::vector<Point>& candidates,
|
||||||
|
double expected_range_shift
|
||||||
|
) -> const Point* {
|
||||||
|
const double target_z = object.z_m - (kRangeSign * expected_range_shift);
|
||||||
|
const double range_tolerance =
|
||||||
|
std::max(kRangeToleranceFloorM, kRangeToleranceFraction * expected_range_shift);
|
||||||
|
|
||||||
|
const Point* best = nullptr;
|
||||||
|
double best_cost = std::numeric_limits<double>::infinity();
|
||||||
|
for (const Point& candidate : candidates) {
|
||||||
|
const double dx = std::abs(candidate.x_m - object.x_m);
|
||||||
|
const double dz = std::abs(candidate.z_m - target_z);
|
||||||
|
if (dx > kXToleranceM || dz > range_tolerance) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const double cost = (dx / kXToleranceM) * (dx / kXToleranceM)
|
||||||
|
+ (dz / range_tolerance) * (dz / range_tolerance);
|
||||||
|
if (cost < best_cost) {
|
||||||
|
best = &candidate;
|
||||||
|
best_cost = cost;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::deque<Frame> history_{}; // recent frames, newest at the back; capped to min_frames
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace radar::processing
|
||||||
+62
-14
@@ -21,7 +21,6 @@ constexpr double kSmoothSigma = 1.5;
|
|||||||
// same default 'reflect' (half-sample symmetric) extension — see reflect_index.
|
// same default 'reflect' (half-sample symmetric) extension — see reflect_index.
|
||||||
constexpr double kGaussianTruncate = 4.0;
|
constexpr double kGaussianTruncate = 4.0;
|
||||||
constexpr std::size_t kMaxObjects = 10U;
|
constexpr std::size_t kMaxObjects = 10U;
|
||||||
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;
|
||||||
@@ -1405,7 +1404,8 @@ void apply_depth_gate(
|
|||||||
|
|
||||||
[[nodiscard]] auto find_bp_objects(
|
[[nodiscard]] auto find_bp_objects(
|
||||||
const std::vector<double>& bp_image,
|
const std::vector<double>& bp_image,
|
||||||
const GridDefinition& grid
|
const GridDefinition& grid,
|
||||||
|
double min_frac
|
||||||
) -> std::vector<ObjectRecord> {
|
) -> std::vector<ObjectRecord> {
|
||||||
std::vector<ObjectRecord> objects{};
|
std::vector<ObjectRecord> objects{};
|
||||||
if (bp_image.empty() || grid.x_grid.size() < 2U || grid.z_grid.size() < 2U) {
|
if (bp_image.empty() || grid.x_grid.size() < 2U || grid.z_grid.size() < 2U) {
|
||||||
@@ -1414,7 +1414,7 @@ void apply_depth_gate(
|
|||||||
|
|
||||||
std::vector<double> work = bp_image;
|
std::vector<double> work = bp_image;
|
||||||
const double global_peak = max_value(work);
|
const double global_peak = max_value(work);
|
||||||
const double stop_level = kObjectMinFrac * global_peak;
|
const double stop_level = min_frac * global_peak;
|
||||||
if (!(global_peak > 0.0)) {
|
if (!(global_peak > 0.0)) {
|
||||||
return objects;
|
return objects;
|
||||||
}
|
}
|
||||||
@@ -1740,7 +1740,15 @@ void add_bp_score_metrics(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] auto output_objects_sorted(
|
// Select the FINAL visible objects exactly as Horns_motion_3libre.py does, so the
|
||||||
|
// processor is the single source of truth: the GUI plot and the locator socket both
|
||||||
|
// consume this set verbatim (no second, duplicated filter). Steps, in order:
|
||||||
|
// 1. drop sidelobe candidates (when enabled), then sort by score (peak tie-break);
|
||||||
|
// 2. clip to the visible X/Z window (display window doubles as an object gate);
|
||||||
|
// 3. apply the N/M draw rule (BP_MAX_DETECTED_OBJECTS_TO_DRAW / BP_DRAW_TOP_M_OBJECTS):
|
||||||
|
// if more than N survive, show none; otherwise keep the top M.
|
||||||
|
// There is deliberately NO score threshold (the Python reference has none).
|
||||||
|
[[nodiscard]] auto select_visible_objects(
|
||||||
const std::vector<ObjectRecord>& objects,
|
const std::vector<ObjectRecord>& objects,
|
||||||
const ProcessingLiveConfig& live_config
|
const ProcessingLiveConfig& live_config
|
||||||
) -> std::vector<const ObjectRecord*> {
|
) -> std::vector<const ObjectRecord*> {
|
||||||
@@ -1750,6 +1758,12 @@ void add_bp_score_metrics(
|
|||||||
if (live_config.gpr_remove_sidelobe_objects_enabled && object.sidelobe_candidate) {
|
if (live_config.gpr_remove_sidelobe_objects_enabled && object.sidelobe_candidate) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (object.x_m < live_config.gpr_visible_x_min_m
|
||||||
|
|| object.x_m > live_config.gpr_visible_x_max_m
|
||||||
|
|| object.z_m < live_config.gpr_visible_z_min_m
|
||||||
|
|| object.z_m > live_config.gpr_visible_z_max_m) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
visible.push_back(&object);
|
visible.push_back(&object);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1759,6 +1773,17 @@ void add_bp_score_metrics(
|
|||||||
}
|
}
|
||||||
return left->selected_score > right->selected_score;
|
return left->selected_score > right->selected_score;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// N/M draw rule (0 on either disables limiting, mirroring the GUI/locator default).
|
||||||
|
const auto max_detected = live_config.gpr_max_detected_objects_to_draw;
|
||||||
|
const auto draw_top = live_config.gpr_draw_top_m_objects;
|
||||||
|
if (max_detected > 0U && draw_top > 0U) {
|
||||||
|
if (visible.size() > max_detected) {
|
||||||
|
visible.clear();
|
||||||
|
} else if (visible.size() > draw_top) {
|
||||||
|
visible.resize(draw_top);
|
||||||
|
}
|
||||||
|
}
|
||||||
return visible;
|
return visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1815,7 +1840,8 @@ void add_bp_score_metrics(
|
|||||||
const config::RunConfig& run_config,
|
const config::RunConfig& run_config,
|
||||||
const ipc::PreprocessedCollection& collection,
|
const ipc::PreprocessedCollection& collection,
|
||||||
std::span<const ipc::PreprocessedCollection> previous_collections,
|
std::span<const ipc::PreprocessedCollection> previous_collections,
|
||||||
const ProcessingLiveConfig& live_config
|
const ProcessingLiveConfig& live_config,
|
||||||
|
ObjectApproachFilter& approach_filter
|
||||||
) -> ipc::ResultCollection {
|
) -> ipc::ResultCollection {
|
||||||
ipc::ResultCollection results{};
|
ipc::ResultCollection results{};
|
||||||
results.collection_id = collection.collection_id;
|
results.collection_id = collection.collection_id;
|
||||||
@@ -1833,8 +1859,10 @@ void add_bp_score_metrics(
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
const double velocity_mps =
|
// Coherent BP fixes the medium to vacuum/air (eps_r = 1), matching the Python
|
||||||
kSpeedOfLightMetersPerSec / std::sqrt(std::max(1e-6, static_cast<double>(run_config.gpr.relative_permittivity)));
|
// reference Horns_motion_3libre.py (its 0.3 block hardcodes eps_r = 1.0). The
|
||||||
|
// configurable relative_permittivity stays a legacy-GPR-only knob.
|
||||||
|
const double velocity_mps = kSpeedOfLightMetersPerSec;
|
||||||
const double start_hz = static_cast<double>(live_config.gpr_start_freq_mhz) * 1'000'000.0;
|
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 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 min_depth_m = static_cast<double>(live_config.gpr_min_depth_m);
|
||||||
@@ -1920,7 +1948,7 @@ void add_bp_score_metrics(
|
|||||||
min_depth_m,
|
min_depth_m,
|
||||||
max_depth_m,
|
max_depth_m,
|
||||||
std::max(0.0, static_cast<double>(live_config.gpr_range_comp_power)),
|
std::max(0.0, static_cast<double>(live_config.gpr_range_comp_power)),
|
||||||
std::max(0.0, static_cast<double>(live_config.gpr_angle_comp_power))
|
0.0 // angle compensation fixed off (Python Horns_motion_3libre.py COMP_ANGLE_POWER = 0.0)
|
||||||
);
|
);
|
||||||
if (bp.image.empty()) {
|
if (bp.image.empty()) {
|
||||||
return results;
|
return results;
|
||||||
@@ -1930,7 +1958,7 @@ void add_bp_score_metrics(
|
|||||||
const auto incoherent_display_map =
|
const auto incoherent_display_map =
|
||||||
normalize_bp_map(bp.incoherent, grid, min_depth_m, max_depth_m, kSmoothSigma);
|
normalize_bp_map(bp.incoherent, grid, min_depth_m, max_depth_m, kSmoothSigma);
|
||||||
|
|
||||||
auto objects = find_bp_objects(display_map, grid);
|
auto objects = find_bp_objects(display_map, grid, static_cast<double>(live_config.gpr_object_min_frac));
|
||||||
add_local_prominence_metrics(objects, display_map, grid, min_depth_m, max_depth_m);
|
add_local_prominence_metrics(objects, display_map, grid, min_depth_m, max_depth_m);
|
||||||
add_incoherent_support_metrics(objects, incoherent_display_map, bp.coherence_factor);
|
add_incoherent_support_metrics(objects, incoherent_display_map, bp.coherence_factor);
|
||||||
mark_sidelobe_candidates(objects, selected_traces, selection, imaging_plane_y_m);
|
mark_sidelobe_candidates(objects, selected_traces, selection, imaging_plane_y_m);
|
||||||
@@ -1951,14 +1979,34 @@ void add_bp_score_metrics(
|
|||||||
|
|
||||||
results.collection_payloads.push_back(build_image_payload("gpr_accumulator", grid.x_grid, grid.z_grid, display_map));
|
results.collection_payloads.push_back(build_image_payload("gpr_accumulator", grid.x_grid, grid.z_grid, display_map));
|
||||||
|
|
||||||
|
// Final visible set (window + N/M), then the cross-frame approach filter: keep only
|
||||||
|
// objects confirmed as a >= min_frames motion-consistent track (Horns_motion notebook).
|
||||||
|
const auto visible = select_visible_objects(objects, live_config);
|
||||||
|
std::vector<ObjectApproachFilter::Point> visible_points{};
|
||||||
|
visible_points.reserve(visible.size());
|
||||||
|
for (const auto* object : visible) {
|
||||||
|
visible_points.push_back({object->x_m, object->z_m});
|
||||||
|
}
|
||||||
|
const auto confirmed = approach_filter.confirm(
|
||||||
|
visible_points,
|
||||||
|
collection.collection_id,
|
||||||
|
static_cast<double>(collection.monotonic_ns) * 1e-9,
|
||||||
|
static_cast<double>(live_config.gpr_speed_m_s),
|
||||||
|
static_cast<double>(live_config.gpr_look_angle_deg),
|
||||||
|
static_cast<std::size_t>(live_config.gpr_object_approach_min_frames)
|
||||||
|
);
|
||||||
|
|
||||||
std::vector<std::vector<float>> point_rows{};
|
std::vector<std::vector<float>> point_rows{};
|
||||||
point_rows.reserve(objects.size());
|
point_rows.reserve(visible.size());
|
||||||
for (const auto* object : output_objects_sorted(objects, live_config)) {
|
for (std::size_t index = 0U; index < visible.size(); ++index) {
|
||||||
|
if (!confirmed[index]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
point_rows.push_back(
|
point_rows.push_back(
|
||||||
{
|
{
|
||||||
static_cast<float>(object->x_m),
|
static_cast<float>(visible[index]->x_m),
|
||||||
static_cast<float>(object->z_m),
|
static_cast<float>(visible[index]->z_m),
|
||||||
static_cast<float>(object->selected_score),
|
static_cast<float>(visible[index]->selected_score),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ auto GprProcessor::process_collection(
|
|||||||
std::span<const ipc::PreprocessedCollection> previous_collections,
|
std::span<const ipc::PreprocessedCollection> previous_collections,
|
||||||
const ProcessingLiveConfig& live_config
|
const ProcessingLiveConfig& live_config
|
||||||
) -> ipc::ResultCollection {
|
) -> ipc::ResultCollection {
|
||||||
return process_backprojection_gpr(run_config, collection, previous_collections, live_config);
|
return process_backprojection_gpr(run_config, collection, previous_collections, live_config, approach_filter_);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto LegacyGprProcessor::name() const -> std::string {
|
auto LegacyGprProcessor::name() const -> std::string {
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ SweepOrchestrator::SweepOrchestrator(
|
|||||||
void SweepOrchestrator::run(const std::atomic<bool>& stop_requested) {
|
void SweepOrchestrator::run(const std::atomic<bool>& stop_requested) {
|
||||||
// Fail fast on a config error: a slot that is too small for the worst-case payload can
|
// Fail fast on a config error: a slot that is too small for the worst-case payload can
|
||||||
// never carry a full collection, so report it clearly at startup instead of dropping
|
// never carry a full collection, so report it clearly at startup instead of dropping
|
||||||
// every collection at runtime (fix #27).
|
// every collection at runtime.
|
||||||
const auto worst_case_bytes = worst_case_serialized_bytes(config_.run_combos.size(), config_.radar.sweep.points);
|
const auto worst_case_bytes = worst_case_serialized_bytes(config_.run_combos.size(), config_.radar.sweep.points);
|
||||||
if (worst_case_bytes > raw_ring_.slot_size_bytes()) {
|
if (worst_case_bytes > raw_ring_.slot_size_bytes()) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
@@ -219,7 +219,7 @@ void SweepOrchestrator::run(const std::atomic<bool>& stop_requested) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DriverLifecycleGuard lifecycle_guard(radar_driver_, input_switch_driver_, output_switch_driver_);
|
DriverLifecycleGuard lifecycle_guard(radar_driver_, input_switch_driver_, output_switch_driver_);
|
||||||
// Wait for the devices to become available before starting (fix #8/#9): an absent device
|
// Wait for the devices to become available before starting: an absent device
|
||||||
// makes the orchestrator wait, not exit.
|
// makes the orchestrator wait, not exit.
|
||||||
if (!lifecycle_guard.open_all_with_retry(stop_requested)) {
|
if (!lifecycle_guard.open_all_with_retry(stop_requested)) {
|
||||||
return; // stop requested before any device became available
|
return; // stop requested before any device became available
|
||||||
|
|||||||
@@ -58,14 +58,14 @@ _WEB_LIVE_SCHEMA = [
|
|||||||
("gpr_stop_freq_mhz", "Geometry & depth", _GPR_MODES, _dual("_gpr_stop_freq_mhz", "_legacy_gpr_stop_freq_mhz")),
|
("gpr_stop_freq_mhz", "Geometry & depth", _GPR_MODES, _dual("_gpr_stop_freq_mhz", "_legacy_gpr_stop_freq_mhz")),
|
||||||
("gpr_imaging_plane_y_m", "Geometry & depth", ("gpr",), _attr("_gpr_imaging_plane_y_m")),
|
("gpr_imaging_plane_y_m", "Geometry & depth", ("gpr",), _attr("_gpr_imaging_plane_y_m")),
|
||||||
("gpr_range_comp_power", "Imaging", ("gpr",), _attr("_gpr_range_comp_power")),
|
("gpr_range_comp_power", "Imaging", ("gpr",), _attr("_gpr_range_comp_power")),
|
||||||
("gpr_angle_comp_power", "Imaging", ("gpr",), _attr("_gpr_angle_comp_power")),
|
|
||||||
("gpr_score_mode", "Imaging", ("gpr",), _attr("_gpr_score_mode")),
|
("gpr_score_mode", "Imaging", ("gpr",), _attr("_gpr_score_mode")),
|
||||||
("gpr_background_subtract_enabled", "Imaging", _GPR_MODES, _dual("_gpr_background_subtract_enabled", "_legacy_gpr_background_subtract_enabled")),
|
("gpr_background_subtract_enabled", "Imaging", _GPR_MODES, _dual("_gpr_background_subtract_enabled", "_legacy_gpr_background_subtract_enabled")),
|
||||||
("gpr_background_mean_count", "Imaging", _GPR_MODES, _dual("_gpr_background_mean_count", "_legacy_gpr_background_mean_count")),
|
("gpr_background_mean_count", "Imaging", _GPR_MODES, _dual("_gpr_background_mean_count", "_legacy_gpr_background_mean_count")),
|
||||||
("gpr_remove_sidelobe_objects_enabled", "Imaging", ("gpr",), _attr("_gpr_remove_sidelobe_objects_enabled")),
|
("gpr_remove_sidelobe_objects_enabled", "Imaging", ("gpr",), _attr("_gpr_remove_sidelobe_objects_enabled")),
|
||||||
("gpr_min_visible_score", "Detection", ("gpr",), _attr("_gpr_min_visible_score")),
|
("gpr_object_min_frac", "Detection", ("gpr",), _attr("_gpr_object_min_frac")),
|
||||||
("gpr_max_detected_objects_to_draw", "Detection", ("gpr",), _attr("_gpr_max_detected_objects_to_draw")),
|
("gpr_max_detected_objects_to_draw", "Detection", ("gpr",), _attr("_gpr_max_detected_objects_to_draw")),
|
||||||
("gpr_draw_top_m_objects", "Detection", ("gpr",), _attr("_gpr_draw_top_m_objects")),
|
("gpr_draw_top_m_objects", "Detection", ("gpr",), _attr("_gpr_draw_top_m_objects")),
|
||||||
|
("gpr_object_approach_min_frames", "Detection", ("gpr",), _attr("_gpr_object_approach_min_frames")),
|
||||||
("gpr_comp_power", "Detection", ("legacy_gpr",), _attr("_legacy_gpr_comp_power")),
|
("gpr_comp_power", "Detection", ("legacy_gpr",), _attr("_legacy_gpr_comp_power")),
|
||||||
("gpr_snr_thresh", "Detection", ("legacy_gpr",), _attr("_legacy_gpr_snr_thresh")),
|
("gpr_snr_thresh", "Detection", ("legacy_gpr",), _attr("_legacy_gpr_snr_thresh")),
|
||||||
("gpr_snr_comp_max", "Detection", ("legacy_gpr",), _attr("_legacy_gpr_snr_comp_max")),
|
("gpr_snr_comp_max", "Detection", ("legacy_gpr",), _attr("_legacy_gpr_snr_comp_max")),
|
||||||
@@ -110,7 +110,9 @@ _WEB_DISPLAY_SCHEMA = [
|
|||||||
# take effect only when the pipeline (re)starts — not hot-reloaded — so the web marks them
|
# take effect only when the pipeline (re)starts — not hot-reloaded — so the web marks them
|
||||||
# "applies on Start" and editing them just updates the widget for the next start.
|
# "applies on Start" and editing them just updates the widget for the next start.
|
||||||
_WEB_STABLE_SCHEMA = [
|
_WEB_STABLE_SCHEMA = [
|
||||||
("relative_permittivity", "Geometry & medium", _GPR_MODES, _attr("_gpr_relative_permittivity")),
|
# Coherent BP fixes the medium to eps_r = 1 (Horns_motion_3libre.py), so relative
|
||||||
|
# permittivity is a legacy-GPR-only knob; BP ignores it.
|
||||||
|
("relative_permittivity", "Geometry & medium", ("legacy_gpr",), _attr("_gpr_relative_permittivity")),
|
||||||
("tx_geometry", "Geometry & medium", _GPR_MODES, _attr("_gpr_tx_geometry_input")),
|
("tx_geometry", "Geometry & medium", _GPR_MODES, _attr("_gpr_tx_geometry_input")),
|
||||||
("rx_geometry", "Geometry & medium", _GPR_MODES, _attr("_gpr_rx_geometry_input")),
|
("rx_geometry", "Geometry & medium", _GPR_MODES, _attr("_gpr_rx_geometry_input")),
|
||||||
]
|
]
|
||||||
@@ -475,14 +477,13 @@ class AppWindowLiveProcessingMixin:
|
|||||||
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"object_min_frac={self._gpr_object_min_frac.value():g}, "
|
||||||
f"score_mode={self._gpr_score_mode.currentText()}, "
|
f"score_mode={self._gpr_score_mode.currentText()}, "
|
||||||
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()}, "
|
||||||
f"imaging_plane_y={self._gpr_imaging_plane_y_m.value():g} m, "
|
f"imaging_plane_y={self._gpr_imaging_plane_y_m.value():g} m, "
|
||||||
f"render_mode={self._gpr_render_mode.currentText()}, "
|
f"render_mode={self._gpr_render_mode.currentText()}, "
|
||||||
f"min_score={self._gpr_min_visible_score.value():g}, "
|
|
||||||
f"max_draw={self._gpr_max_detected_objects_to_draw.value()}, "
|
f"max_draw={self._gpr_max_detected_objects_to_draw.value()}, "
|
||||||
f"draw_top={self._gpr_draw_top_m_objects.value()})"
|
f"draw_top={self._gpr_draw_top_m_objects.value()})"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ class AppWindowConfigProfileIOMixin:
|
|||||||
self._gpr_min_depth_m,
|
self._gpr_min_depth_m,
|
||||||
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_object_min_frac,
|
||||||
self._gpr_score_mode,
|
self._gpr_score_mode,
|
||||||
self._gpr_motion_mode,
|
self._gpr_motion_mode,
|
||||||
self._gpr_look_angle_deg,
|
self._gpr_look_angle_deg,
|
||||||
@@ -300,6 +300,7 @@ class AppWindowConfigProfileIOMixin:
|
|||||||
self._gpr_speed_m_s,
|
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_object_approach_min_frames,
|
||||||
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,
|
||||||
@@ -307,7 +308,6 @@ class AppWindowConfigProfileIOMixin:
|
|||||||
self._gpr_remove_sidelobe_objects_enabled,
|
self._gpr_remove_sidelobe_objects_enabled,
|
||||||
self._gpr_imaging_plane_y_m,
|
self._gpr_imaging_plane_y_m,
|
||||||
self._gpr_render_mode,
|
self._gpr_render_mode,
|
||||||
self._gpr_min_visible_score,
|
|
||||||
self._gpr_visible_x_min_m,
|
self._gpr_visible_x_min_m,
|
||||||
self._gpr_visible_x_max_m,
|
self._gpr_visible_x_max_m,
|
||||||
self._gpr_visible_z_min_m,
|
self._gpr_visible_z_min_m,
|
||||||
@@ -457,7 +457,7 @@ class AppWindowConfigProfileIOMixin:
|
|||||||
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_object_min_frac.setValue(float(gui_state.processing.gpr.object_min_frac))
|
||||||
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._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_look_angle_deg.setValue(float(gui_state.processing.gpr.look_angle_deg))
|
||||||
@@ -470,6 +470,7 @@ class AppWindowConfigProfileIOMixin:
|
|||||||
int(gui_state.processing.gpr.max_detected_objects_to_draw)
|
int(gui_state.processing.gpr.max_detected_objects_to_draw)
|
||||||
)
|
)
|
||||||
self._gpr_draw_top_m_objects.setValue(int(gui_state.processing.gpr.draw_top_m_objects))
|
self._gpr_draw_top_m_objects.setValue(int(gui_state.processing.gpr.draw_top_m_objects))
|
||||||
|
self._gpr_object_approach_min_frames.setValue(int(gui_state.processing.gpr.object_approach_min_frames))
|
||||||
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(
|
||||||
@@ -481,7 +482,6 @@ class AppWindowConfigProfileIOMixin:
|
|||||||
)
|
)
|
||||||
self._gpr_imaging_plane_y_m.setValue(float(gui_state.processing.gpr.imaging_plane_y_m))
|
self._gpr_imaging_plane_y_m.setValue(float(gui_state.processing.gpr.imaging_plane_y_m))
|
||||||
self._set_combo_current_text(self._gpr_render_mode, gui_state.processing.gpr.render_mode)
|
self._set_combo_current_text(self._gpr_render_mode, gui_state.processing.gpr.render_mode)
|
||||||
self._gpr_min_visible_score.setValue(float(gui_state.processing.gpr.min_visible_score))
|
|
||||||
self._gpr_visible_x_min_m.setValue(float(gui_state.processing.gpr.visible_x_min_m))
|
self._gpr_visible_x_min_m.setValue(float(gui_state.processing.gpr.visible_x_min_m))
|
||||||
self._gpr_visible_x_max_m.setValue(float(gui_state.processing.gpr.visible_x_max_m))
|
self._gpr_visible_x_max_m.setValue(float(gui_state.processing.gpr.visible_x_max_m))
|
||||||
self._gpr_visible_z_min_m.setValue(float(gui_state.processing.gpr.visible_z_min_m))
|
self._gpr_visible_z_min_m.setValue(float(gui_state.processing.gpr.visible_z_min_m))
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ class AppWindowConfigStateBuildersMixin:
|
|||||||
min_depth_m=2.0,
|
min_depth_m=2.0,
|
||||||
max_depth_m=14.0,
|
max_depth_m=14.0,
|
||||||
range_comp_power=0.1,
|
range_comp_power=0.1,
|
||||||
angle_comp_power=0.0,
|
object_min_frac=0.7,
|
||||||
score_mode="combined",
|
score_mode="combined",
|
||||||
motion_mode="int_minus",
|
motion_mode="int_minus",
|
||||||
look_angle_deg=0.0,
|
look_angle_deg=0.0,
|
||||||
@@ -233,6 +233,7 @@ class AppWindowConfigStateBuildersMixin:
|
|||||||
ignore_socket_speed_enabled=False,
|
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,
|
||||||
|
object_approach_min_frames=3,
|
||||||
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,
|
||||||
@@ -240,7 +241,6 @@ class AppWindowConfigStateBuildersMixin:
|
|||||||
remove_sidelobe_objects_enabled=True,
|
remove_sidelobe_objects_enabled=True,
|
||||||
imaging_plane_y_m=0.0,
|
imaging_plane_y_m=0.0,
|
||||||
render_mode="heatmap",
|
render_mode="heatmap",
|
||||||
min_visible_score=0.0,
|
|
||||||
visible_x_min_m=default_gpr_x_min_m,
|
visible_x_min_m=default_gpr_x_min_m,
|
||||||
visible_x_max_m=default_gpr_x_max_m,
|
visible_x_max_m=default_gpr_x_max_m,
|
||||||
visible_z_min_m=0.0,
|
visible_z_min_m=0.0,
|
||||||
@@ -351,7 +351,7 @@ class AppWindowConfigStateBuildersMixin:
|
|||||||
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()),
|
object_min_frac=float(self._gpr_object_min_frac.value()),
|
||||||
score_mode=self._gpr_score_mode.currentText(),
|
score_mode=self._gpr_score_mode.currentText(),
|
||||||
motion_mode=self._gpr_motion_mode.currentText(),
|
motion_mode=self._gpr_motion_mode.currentText(),
|
||||||
look_angle_deg=float(self._gpr_look_angle_deg.value()),
|
look_angle_deg=float(self._gpr_look_angle_deg.value()),
|
||||||
@@ -360,6 +360,7 @@ class AppWindowConfigStateBuildersMixin:
|
|||||||
ignore_socket_speed_enabled=bool(self._gpr_ignore_socket_speed_enabled.isChecked()),
|
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()),
|
||||||
|
object_approach_min_frames=int(self._gpr_object_approach_min_frames.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()),
|
||||||
@@ -367,7 +368,6 @@ class AppWindowConfigStateBuildersMixin:
|
|||||||
remove_sidelobe_objects_enabled=bool(self._gpr_remove_sidelobe_objects_enabled.isChecked()),
|
remove_sidelobe_objects_enabled=bool(self._gpr_remove_sidelobe_objects_enabled.isChecked()),
|
||||||
imaging_plane_y_m=float(self._gpr_imaging_plane_y_m.value()),
|
imaging_plane_y_m=float(self._gpr_imaging_plane_y_m.value()),
|
||||||
render_mode=self._gpr_render_mode.currentText(),
|
render_mode=self._gpr_render_mode.currentText(),
|
||||||
min_visible_score=float(self._gpr_min_visible_score.value()),
|
|
||||||
visible_x_min_m=float(self._gpr_visible_x_min_m.value()),
|
visible_x_min_m=float(self._gpr_visible_x_min_m.value()),
|
||||||
visible_x_max_m=float(self._gpr_visible_x_max_m.value()),
|
visible_x_max_m=float(self._gpr_visible_x_max_m.value()),
|
||||||
visible_z_min_m=float(self._gpr_visible_z_min_m.value()),
|
visible_z_min_m=float(self._gpr_visible_z_min_m.value()),
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import pyqtgraph as pg
|
|||||||
|
|
||||||
from python_app.models.dataset_model import ResultCollection
|
from python_app.models.dataset_model import ResultCollection
|
||||||
from python_app.orchestration.gpr_locator import (
|
from python_app.orchestration.gpr_locator import (
|
||||||
apply_object_draw_limits as gpr_apply_object_draw_limits,
|
|
||||||
collection_payload_by_name as gpr_collection_payload_by_name,
|
collection_payload_by_name as gpr_collection_payload_by_name,
|
||||||
collection_payloads_by_prefix as gpr_collection_payloads_by_prefix,
|
collection_payloads_by_prefix as gpr_collection_payloads_by_prefix,
|
||||||
filter_object_rows as gpr_filter_object_rows,
|
filter_object_rows as gpr_filter_object_rows,
|
||||||
@@ -371,26 +370,6 @@ class AppWindowGprPlotMixin:
|
|||||||
return self._legacy_gpr_render_mode.currentText()
|
return self._legacy_gpr_render_mode.currentText()
|
||||||
return self._gpr_render_mode.currentText()
|
return self._gpr_render_mode.currentText()
|
||||||
|
|
||||||
def _gpr_locator_threshold(self) -> float:
|
|
||||||
"""Return object threshold using the active GPR mode's score semantics."""
|
|
||||||
if self._processing_mode.currentText() == "legacy_gpr":
|
|
||||||
return float(self._legacy_gpr_min_visible_pair_count.value())
|
|
||||||
return float(self._gpr_min_visible_score.value())
|
|
||||||
|
|
||||||
def _gpr_draw_limits(self) -> tuple[int, int] | None:
|
|
||||||
"""Return GPR object draw limits, or None for legacy GPR."""
|
|
||||||
if self._processing_mode.currentText() == "legacy_gpr":
|
|
||||||
return None
|
|
||||||
return (
|
|
||||||
int(self._gpr_max_detected_objects_to_draw.value()),
|
|
||||||
int(self._gpr_draw_top_m_objects.value()),
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _apply_object_draw_limits(rows: np.ndarray, limits: tuple[int, int] | None) -> np.ndarray:
|
|
||||||
"""Apply object count/top-M drawing rules to already-filtered rows."""
|
|
||||||
return gpr_apply_object_draw_limits(rows, limits)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _gpr_display_y_min(z_min: float, z_max: float) -> float:
|
def _gpr_display_y_min(z_min: float, z_max: float) -> float:
|
||||||
"""Return lower display bound, preserving surface markers only when surface is visible."""
|
"""Return lower display bound, preserving surface markers only when surface is visible."""
|
||||||
@@ -506,18 +485,24 @@ class AppWindowGprPlotMixin:
|
|||||||
return extract_gpr_object_rows(collection)
|
return extract_gpr_object_rows(collection)
|
||||||
|
|
||||||
def _filtered_gpr_object_rows(self, collection: ResultCollection) -> np.ndarray:
|
def _filtered_gpr_object_rows(self, collection: ResultCollection) -> np.ndarray:
|
||||||
"""Return object rows filtered by threshold, visible X/Z bounds, and active GPR draw limits."""
|
"""Return the object rows to draw for the active GPR mode.
|
||||||
|
|
||||||
|
Coherent BP is already finalized by the processor (visible window + N/M draw
|
||||||
|
limits, no score threshold — exactly Horns_motion_3libre.py), so its rows are
|
||||||
|
drawn verbatim. Legacy GPR is still filtered here by its pair-count threshold
|
||||||
|
and the visible window.
|
||||||
|
"""
|
||||||
rows = self._gpr_object_rows(collection)
|
rows = self._gpr_object_rows(collection)
|
||||||
if rows.size == 0:
|
if rows.size == 0 or self._processing_mode.currentText() != "legacy_gpr":
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
x_min, x_max, z_min, z_max = self._gpr_visible_object_bounds()
|
x_min, x_max, z_min, z_max = self._gpr_visible_object_bounds()
|
||||||
return gpr_filter_object_rows(
|
return gpr_filter_object_rows(
|
||||||
rows,
|
rows,
|
||||||
min_score=self._gpr_locator_threshold(),
|
min_score=float(self._legacy_gpr_min_visible_pair_count.value()),
|
||||||
x_bounds=(x_min, x_max),
|
x_bounds=(x_min, x_max),
|
||||||
z_bounds=(z_min, z_max),
|
z_bounds=(z_min, z_max),
|
||||||
draw_limits=self._gpr_draw_limits(),
|
draw_limits=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _draw_gpr_objects_only(self, collection: ResultCollection) -> bool:
|
def _draw_gpr_objects_only(self, collection: ResultCollection) -> bool:
|
||||||
|
|||||||
@@ -177,10 +177,14 @@ def build_processing_group(owner) -> QGroupBox:
|
|||||||
|
|
||||||
gpr_defaults = owner._defaults_config.gpr
|
gpr_defaults = owner._defaults_config.gpr
|
||||||
|
|
||||||
|
# Medium permittivity is a legacy-GPR-only knob (shown on the legacy page below).
|
||||||
|
# Coherent BP fixes the medium to eps_r = 1 (Horns_motion_3libre.py), so it is not
|
||||||
|
# offered there. Applies on the next pipeline start (a run_config field).
|
||||||
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)
|
||||||
owner._gpr_relative_permittivity.setSingleStep(0.05)
|
owner._gpr_relative_permittivity.setSingleStep(0.05)
|
||||||
|
owner._gpr_relative_permittivity.setToolTip("Applied on the next pipeline start (Save Config and restart).")
|
||||||
owner._gpr_relative_permittivity.setValue(float(gpr_defaults.relative_permittivity))
|
owner._gpr_relative_permittivity.setValue(float(gpr_defaults.relative_permittivity))
|
||||||
|
|
||||||
owner._gpr_tx_geometry_input = QPlainTextEdit(_format_tx_geometry(owner))
|
owner._gpr_tx_geometry_input = QPlainTextEdit(_format_tx_geometry(owner))
|
||||||
@@ -194,14 +198,13 @@ def build_processing_group(owner) -> QGroupBox:
|
|||||||
owner._gpr_common_page = _build_processing_mode_page(
|
owner._gpr_common_page = _build_processing_mode_page(
|
||||||
group,
|
group,
|
||||||
[
|
[
|
||||||
("Relative permittivity", owner._gpr_relative_permittivity),
|
|
||||||
("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),
|
||||||
],
|
],
|
||||||
split_index=1,
|
split_index=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
owner._gpr_geometry_hint = QLabel("To apply Tx/Rx geometry or permittivity changes: Save Config and restart the app")
|
owner._gpr_geometry_hint = QLabel("To apply Tx/Rx geometry changes: Save Config and restart the app")
|
||||||
owner._gpr_geometry_hint.setWordWrap(True)
|
owner._gpr_geometry_hint.setWordWrap(True)
|
||||||
owner._gpr_common_page.layout().addWidget(owner._gpr_geometry_hint)
|
owner._gpr_common_page.layout().addWidget(owner._gpr_geometry_hint)
|
||||||
|
|
||||||
@@ -229,11 +232,15 @@ def build_processing_group(owner) -> QGroupBox:
|
|||||||
owner._gpr_range_comp_power.setSingleStep(0.01)
|
owner._gpr_range_comp_power.setSingleStep(0.01)
|
||||||
owner._gpr_range_comp_power.setValue(float(gpr_live_defaults.range_comp_power))
|
owner._gpr_range_comp_power.setValue(float(gpr_live_defaults.range_comp_power))
|
||||||
|
|
||||||
owner._gpr_angle_comp_power = QDoubleSpinBox()
|
owner._gpr_object_min_frac = QDoubleSpinBox()
|
||||||
owner._gpr_angle_comp_power.setDecimals(3)
|
owner._gpr_object_min_frac.setDecimals(2)
|
||||||
owner._gpr_angle_comp_power.setRange(0.0, 5.0)
|
owner._gpr_object_min_frac.setRange(0.0, 1.0)
|
||||||
owner._gpr_angle_comp_power.setSingleStep(0.01)
|
owner._gpr_object_min_frac.setSingleStep(0.05)
|
||||||
owner._gpr_angle_comp_power.setValue(float(gpr_live_defaults.angle_comp_power))
|
owner._gpr_object_min_frac.setToolTip(
|
||||||
|
"Object detection stops once a peak falls below this fraction of the global "
|
||||||
|
"maximum (Horns_motion_3libre.py BP_OBJECT_MIN_FRAC)."
|
||||||
|
)
|
||||||
|
owner._gpr_object_min_frac.setValue(float(gpr_live_defaults.object_min_frac))
|
||||||
|
|
||||||
owner._gpr_score_mode = QComboBox()
|
owner._gpr_score_mode = QComboBox()
|
||||||
owner._gpr_score_mode.addItems(["peak", "combined"])
|
owner._gpr_score_mode.addItems(["peak", "combined"])
|
||||||
@@ -289,6 +296,14 @@ def build_processing_group(owner) -> QGroupBox:
|
|||||||
owner._gpr_draw_top_m_objects.setRange(0, 10_000)
|
owner._gpr_draw_top_m_objects.setRange(0, 10_000)
|
||||||
owner._gpr_draw_top_m_objects.setValue(int(gpr_live_defaults.draw_top_m_objects))
|
owner._gpr_draw_top_m_objects.setValue(int(gpr_live_defaults.draw_top_m_objects))
|
||||||
|
|
||||||
|
owner._gpr_object_approach_min_frames = QSpinBox()
|
||||||
|
owner._gpr_object_approach_min_frames.setRange(1, 100)
|
||||||
|
owner._gpr_object_approach_min_frames.setToolTip(
|
||||||
|
"Show an object only after it persists as a motion-consistent track this many "
|
||||||
|
"consecutive frames (1 disables the approach filter)."
|
||||||
|
)
|
||||||
|
owner._gpr_object_approach_min_frames.setValue(int(gpr_live_defaults.object_approach_min_frames))
|
||||||
|
|
||||||
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)
|
||||||
@@ -315,12 +330,6 @@ def build_processing_group(owner) -> QGroupBox:
|
|||||||
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)
|
||||||
|
|
||||||
owner._gpr_min_visible_score = QDoubleSpinBox()
|
|
||||||
owner._gpr_min_visible_score.setDecimals(2)
|
|
||||||
owner._gpr_min_visible_score.setRange(0.0, 1.0)
|
|
||||||
owner._gpr_min_visible_score.setSingleStep(0.05)
|
|
||||||
owner._gpr_min_visible_score.setValue(float(gpr_live_defaults.min_visible_score))
|
|
||||||
|
|
||||||
owner._gpr_visible_x_min_m = QDoubleSpinBox()
|
owner._gpr_visible_x_min_m = QDoubleSpinBox()
|
||||||
owner._gpr_visible_x_min_m.setDecimals(2)
|
owner._gpr_visible_x_min_m.setDecimals(2)
|
||||||
owner._gpr_visible_x_min_m.setRange(-100.0, 100.0)
|
owner._gpr_visible_x_min_m.setRange(-100.0, 100.0)
|
||||||
@@ -362,7 +371,7 @@ def build_processing_group(owner) -> QGroupBox:
|
|||||||
("Min depth m", owner._gpr_min_depth_m),
|
("Min depth m", owner._gpr_min_depth_m),
|
||||||
("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),
|
("Object min frac", owner._gpr_object_min_frac),
|
||||||
("Score mode", owner._gpr_score_mode),
|
("Score mode", owner._gpr_score_mode),
|
||||||
("Motion mode", owner._gpr_motion_mode),
|
("Motion mode", owner._gpr_motion_mode),
|
||||||
("Look angle deg", owner._gpr_look_angle_deg),
|
("Look angle deg", owner._gpr_look_angle_deg),
|
||||||
@@ -370,9 +379,9 @@ def build_processing_group(owner) -> QGroupBox:
|
|||||||
owner._gpr_ignore_socket_speed_enabled,
|
owner._gpr_ignore_socket_speed_enabled,
|
||||||
("Speed m/s", owner._gpr_speed_m_s),
|
("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),
|
|
||||||
("Max detected objects", owner._gpr_max_detected_objects_to_draw),
|
("Max detected objects", owner._gpr_max_detected_objects_to_draw),
|
||||||
("Draw top M objects", owner._gpr_draw_top_m_objects),
|
("Draw top M objects", owner._gpr_draw_top_m_objects),
|
||||||
|
("Approach min frames", owner._gpr_object_approach_min_frames),
|
||||||
("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),
|
||||||
("Imaging plane Y m", owner._gpr_imaging_plane_y_m),
|
("Imaging plane Y m", owner._gpr_imaging_plane_y_m),
|
||||||
@@ -522,6 +531,7 @@ def build_processing_group(owner) -> QGroupBox:
|
|||||||
owner._processing_mode_pages,
|
owner._processing_mode_pages,
|
||||||
[
|
[
|
||||||
("Config mode", owner._legacy_gpr_config_mode),
|
("Config mode", owner._legacy_gpr_config_mode),
|
||||||
|
("Relative permittivity", owner._gpr_relative_permittivity),
|
||||||
("Input positions", owner._legacy_gpr_input_positions_input),
|
("Input positions", owner._legacy_gpr_input_positions_input),
|
||||||
("Output positions", owner._legacy_gpr_output_positions_input),
|
("Output positions", owner._legacy_gpr_output_positions_input),
|
||||||
("Min depth m", owner._legacy_gpr_min_depth_m),
|
("Min depth m", owner._legacy_gpr_min_depth_m),
|
||||||
@@ -574,7 +584,7 @@ def build_processing_group(owner) -> QGroupBox:
|
|||||||
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_object_min_frac.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_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_look_angle_deg.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||||
@@ -588,9 +598,9 @@ def build_processing_group(owner) -> QGroupBox:
|
|||||||
owner._gpr_remove_sidelobe_objects_enabled.toggled.connect(owner._on_processing_live_settings_changed)
|
owner._gpr_remove_sidelobe_objects_enabled.toggled.connect(owner._on_processing_live_settings_changed)
|
||||||
owner._gpr_imaging_plane_y_m.valueChanged.connect(owner._on_processing_live_settings_changed)
|
owner._gpr_imaging_plane_y_m.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||||
owner._gpr_render_mode.currentTextChanged.connect(owner._on_gpr_visual_settings_changed)
|
owner._gpr_render_mode.currentTextChanged.connect(owner._on_gpr_visual_settings_changed)
|
||||||
owner._gpr_min_visible_score.valueChanged.connect(owner._on_gpr_locator_threshold_changed)
|
|
||||||
owner._gpr_max_detected_objects_to_draw.valueChanged.connect(owner._on_gpr_locator_threshold_changed)
|
owner._gpr_max_detected_objects_to_draw.valueChanged.connect(owner._on_gpr_locator_threshold_changed)
|
||||||
owner._gpr_draw_top_m_objects.valueChanged.connect(owner._on_gpr_locator_threshold_changed)
|
owner._gpr_draw_top_m_objects.valueChanged.connect(owner._on_gpr_locator_threshold_changed)
|
||||||
|
owner._gpr_object_approach_min_frames.valueChanged.connect(owner._on_processing_live_settings_changed)
|
||||||
owner._gpr_visible_x_min_m.valueChanged.connect(owner._on_gpr_locator_window_changed)
|
owner._gpr_visible_x_min_m.valueChanged.connect(owner._on_gpr_locator_window_changed)
|
||||||
owner._gpr_visible_x_max_m.valueChanged.connect(owner._on_gpr_locator_window_changed)
|
owner._gpr_visible_x_max_m.valueChanged.connect(owner._on_gpr_locator_window_changed)
|
||||||
owner._gpr_visible_z_min_m.valueChanged.connect(owner._on_gpr_locator_window_changed)
|
owner._gpr_visible_z_min_m.valueChanged.connect(owner._on_gpr_locator_window_changed)
|
||||||
|
|||||||
@@ -276,10 +276,10 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
|
|||||||
gui.processing.gpr.range_comp_power,
|
gui.processing.gpr.range_comp_power,
|
||||||
"gui.processing.gpr",
|
"gui.processing.gpr",
|
||||||
),
|
),
|
||||||
angle_comp_power=_optional_float(
|
object_min_frac=_optional_float(
|
||||||
gpr_object,
|
gpr_object,
|
||||||
"angle_comp_power",
|
"object_min_frac",
|
||||||
gui.processing.gpr.angle_comp_power,
|
gui.processing.gpr.object_min_frac,
|
||||||
"gui.processing.gpr",
|
"gui.processing.gpr",
|
||||||
),
|
),
|
||||||
score_mode=_optional_string(
|
score_mode=_optional_string(
|
||||||
@@ -330,6 +330,12 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
|
|||||||
gui.processing.gpr.draw_top_m_objects,
|
gui.processing.gpr.draw_top_m_objects,
|
||||||
"gui.processing.gpr",
|
"gui.processing.gpr",
|
||||||
),
|
),
|
||||||
|
object_approach_min_frames=_optional_int(
|
||||||
|
gpr_object,
|
||||||
|
"object_approach_min_frames",
|
||||||
|
gui.processing.gpr.object_approach_min_frames,
|
||||||
|
"gui.processing.gpr",
|
||||||
|
),
|
||||||
start_freq_mhz=_optional_float(
|
start_freq_mhz=_optional_float(
|
||||||
gpr_object,
|
gpr_object,
|
||||||
"start_freq_mhz",
|
"start_freq_mhz",
|
||||||
@@ -372,12 +378,6 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
|
|||||||
gui.processing.gpr.render_mode,
|
gui.processing.gpr.render_mode,
|
||||||
"gui.processing.gpr",
|
"gui.processing.gpr",
|
||||||
),
|
),
|
||||||
min_visible_score=_optional_float(
|
|
||||||
gpr_object,
|
|
||||||
"min_visible_score",
|
|
||||||
gui.processing.gpr.min_visible_score,
|
|
||||||
"gui.processing.gpr",
|
|
||||||
),
|
|
||||||
visible_x_min_m=_optional_float(
|
visible_x_min_m=_optional_float(
|
||||||
gpr_object,
|
gpr_object,
|
||||||
"visible_x_min_m",
|
"visible_x_min_m",
|
||||||
@@ -467,14 +467,14 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
|
|||||||
)
|
)
|
||||||
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 not 0.0 <= gui.processing.gpr.object_min_frac <= 1.0:
|
||||||
raise ValueError("gui.processing.gpr.angle_comp_power must be >= 0")
|
raise ValueError("gui.processing.gpr.object_min_frac must be within [0, 1]")
|
||||||
if gui.processing.gpr.min_visible_score < 0.0:
|
|
||||||
raise ValueError("gui.processing.gpr.min_visible_score must be >= 0")
|
|
||||||
if gui.processing.gpr.max_detected_objects_to_draw < 0:
|
if gui.processing.gpr.max_detected_objects_to_draw < 0:
|
||||||
raise ValueError("gui.processing.gpr.max_detected_objects_to_draw must be >= 0")
|
raise ValueError("gui.processing.gpr.max_detected_objects_to_draw must be >= 0")
|
||||||
if gui.processing.gpr.draw_top_m_objects < 0:
|
if gui.processing.gpr.draw_top_m_objects < 0:
|
||||||
raise ValueError("gui.processing.gpr.draw_top_m_objects must be >= 0")
|
raise ValueError("gui.processing.gpr.draw_top_m_objects must be >= 0")
|
||||||
|
if gui.processing.gpr.object_approach_min_frames < 1:
|
||||||
|
raise ValueError("gui.processing.gpr.object_approach_min_frames must be >= 1")
|
||||||
if gui.processing.legacy_gpr.comp_power < 0.0:
|
if gui.processing.legacy_gpr.comp_power < 0.0:
|
||||||
raise ValueError("gui.processing.legacy_gpr.comp_power must be >= 0")
|
raise ValueError("gui.processing.legacy_gpr.comp_power must be >= 0")
|
||||||
if gui.processing.legacy_gpr.snr_thresh < 0.0:
|
if gui.processing.legacy_gpr.snr_thresh < 0.0:
|
||||||
@@ -586,7 +586,7 @@ def gui_profile_to_dict(model: GuiProfileModel) -> dict[str, Any]:
|
|||||||
"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,
|
"object_min_frac": gui.processing.gpr.object_min_frac,
|
||||||
"score_mode": gui.processing.gpr.score_mode,
|
"score_mode": gui.processing.gpr.score_mode,
|
||||||
"motion_mode": gui.processing.gpr.motion_mode,
|
"motion_mode": gui.processing.gpr.motion_mode,
|
||||||
"look_angle_deg": gui.processing.gpr.look_angle_deg,
|
"look_angle_deg": gui.processing.gpr.look_angle_deg,
|
||||||
@@ -595,6 +595,7 @@ def gui_profile_to_dict(model: GuiProfileModel) -> dict[str, Any]:
|
|||||||
"ignore_socket_speed_enabled": gui.processing.gpr.ignore_socket_speed_enabled,
|
"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,
|
||||||
|
"object_approach_min_frames": gui.processing.gpr.object_approach_min_frames,
|
||||||
"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,
|
||||||
@@ -602,7 +603,6 @@ def gui_profile_to_dict(model: GuiProfileModel) -> dict[str, Any]:
|
|||||||
"remove_sidelobe_objects_enabled": gui.processing.gpr.remove_sidelobe_objects_enabled,
|
"remove_sidelobe_objects_enabled": gui.processing.gpr.remove_sidelobe_objects_enabled,
|
||||||
"imaging_plane_y_m": gui.processing.gpr.imaging_plane_y_m,
|
"imaging_plane_y_m": gui.processing.gpr.imaging_plane_y_m,
|
||||||
"render_mode": gui.processing.gpr.render_mode,
|
"render_mode": gui.processing.gpr.render_mode,
|
||||||
"min_visible_score": gui.processing.gpr.min_visible_score,
|
|
||||||
"visible_x_min_m": gui.processing.gpr.visible_x_min_m,
|
"visible_x_min_m": gui.processing.gpr.visible_x_min_m,
|
||||||
"visible_x_max_m": gui.processing.gpr.visible_x_max_m,
|
"visible_x_max_m": gui.processing.gpr.visible_x_max_m,
|
||||||
"visible_z_min_m": gui.processing.gpr.visible_z_min_m,
|
"visible_z_min_m": gui.processing.gpr.visible_z_min_m,
|
||||||
|
|||||||
@@ -59,7 +59,10 @@ class GuiGprStateModel:
|
|||||||
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.1
|
range_comp_power: float = 0.1
|
||||||
angle_comp_power: float = 0.0
|
# BP object-detection stop level, fraction of the global peak (Horns_motion_3libre.py
|
||||||
|
# BP_OBJECT_MIN_FRAC). Angle compensation and permittivity are fixed for coherent BP
|
||||||
|
# (Python 0.3 block), so they are not exposed here.
|
||||||
|
object_min_frac: float = 0.7
|
||||||
score_mode: str = "combined"
|
score_mode: str = "combined"
|
||||||
motion_mode: str = "int_minus"
|
motion_mode: str = "int_minus"
|
||||||
# Intra-sweep motion-correction inputs. Sweep time is derived from acquisition
|
# Intra-sweep motion-correction inputs. Sweep time is derived from acquisition
|
||||||
@@ -71,6 +74,9 @@ class GuiGprStateModel:
|
|||||||
ignore_socket_speed_enabled: bool = False
|
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
|
||||||
|
# Cross-frame approach filter: show an object only after it persists as a
|
||||||
|
# motion-consistent track this many consecutive frames (<= 1 disables it).
|
||||||
|
object_approach_min_frames: int = 3
|
||||||
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
|
||||||
@@ -78,7 +84,6 @@ class GuiGprStateModel:
|
|||||||
remove_sidelobe_objects_enabled: bool = True
|
remove_sidelobe_objects_enabled: bool = True
|
||||||
imaging_plane_y_m: float = 0.0
|
imaging_plane_y_m: float = 0.0
|
||||||
render_mode: str = "heatmap"
|
render_mode: str = "heatmap"
|
||||||
min_visible_score: float = 0.0
|
|
||||||
visible_x_min_m: float = -2.0
|
visible_x_min_m: float = -2.0
|
||||||
visible_x_max_m: float = 2.0
|
visible_x_max_m: float = 2.0
|
||||||
visible_z_min_m: float = 0.0
|
visible_z_min_m: float = 0.0
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ class ProcessingLiveConfig:
|
|||||||
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.1
|
gpr_range_comp_power: float = 0.1
|
||||||
gpr_angle_comp_power: float = 0.0
|
|
||||||
gpr_comp_power: float = 0.2
|
gpr_comp_power: float = 0.2
|
||||||
|
gpr_object_min_frac: float = 0.7
|
||||||
gpr_score_mode: str = "combined"
|
gpr_score_mode: str = "combined"
|
||||||
# Backprojection intra-sweep speed-correction mode: "int_minus" (full
|
# Backprojection intra-sweep speed-correction mode: "int_minus" (full
|
||||||
# correction) or "int_focus" (focusing residual only). Mirrors Python
|
# correction) or "int_focus" (focusing residual only). Mirrors Python
|
||||||
@@ -41,6 +41,7 @@ class ProcessingLiveConfig:
|
|||||||
gpr_motion_mode: str = "int_minus"
|
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_object_approach_min_frames: int = 3
|
||||||
gpr_speed_m_s: float = 0.0
|
gpr_speed_m_s: float = 0.0
|
||||||
gpr_look_angle_deg: float = 0.0
|
gpr_look_angle_deg: float = 0.0
|
||||||
# Motion-model knobs for the legacy GPR pipeline. `direction_sign` flips
|
# Motion-model knobs for the legacy GPR pipeline. `direction_sign` flips
|
||||||
@@ -61,8 +62,9 @@ class ProcessingLiveConfig:
|
|||||||
gpr_background_mean_count: int = 10
|
gpr_background_mean_count: int = 10
|
||||||
gpr_remove_sidelobe_objects_enabled: bool = True
|
gpr_remove_sidelobe_objects_enabled: bool = True
|
||||||
gpr_imaging_plane_y_m: float = 0.0
|
gpr_imaging_plane_y_m: float = 0.0
|
||||||
# Locator filter parameters consumed by the C++ TCP locator server.
|
# Locator filter parameter consumed by the C++ TCP locator server. Coherent BP
|
||||||
gpr_min_visible_score: float = 0.0
|
# objects are already finalized in the processor (no score threshold); only legacy
|
||||||
|
# GPR still thresholds, on a pair count.
|
||||||
legacy_gpr_min_visible_pair_count: float = 0.0
|
legacy_gpr_min_visible_pair_count: float = 0.0
|
||||||
# Visible X/Z window (metres). The locator and the desktop plot both clip
|
# Visible X/Z window (metres). The locator and the desktop plot both clip
|
||||||
# detected objects to this window, so the socket broadcasts only what is shown.
|
# detected objects to this window, so the socket broadcasts only what is shown.
|
||||||
@@ -109,12 +111,13 @@ class ProcessingLiveConfig:
|
|||||||
"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_comp_power": float(self.gpr_comp_power),
|
"gpr_comp_power": float(self.gpr_comp_power),
|
||||||
|
"gpr_object_min_frac": float(self.gpr_object_min_frac),
|
||||||
"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_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_object_approach_min_frames": int(self.gpr_object_approach_min_frames),
|
||||||
"gpr_speed_m_s": float(self.gpr_speed_m_s),
|
"gpr_speed_m_s": float(self.gpr_speed_m_s),
|
||||||
"gpr_look_angle_deg": float(self.gpr_look_angle_deg),
|
"gpr_look_angle_deg": float(self.gpr_look_angle_deg),
|
||||||
"gpr_direction_sign": float(self.gpr_direction_sign),
|
"gpr_direction_sign": float(self.gpr_direction_sign),
|
||||||
@@ -128,7 +131,6 @@ class ProcessingLiveConfig:
|
|||||||
"gpr_background_mean_count": int(self.gpr_background_mean_count),
|
"gpr_background_mean_count": int(self.gpr_background_mean_count),
|
||||||
"gpr_remove_sidelobe_objects_enabled": bool(self.gpr_remove_sidelobe_objects_enabled),
|
"gpr_remove_sidelobe_objects_enabled": bool(self.gpr_remove_sidelobe_objects_enabled),
|
||||||
"gpr_imaging_plane_y_m": float(self.gpr_imaging_plane_y_m),
|
"gpr_imaging_plane_y_m": float(self.gpr_imaging_plane_y_m),
|
||||||
"gpr_min_visible_score": float(self.gpr_min_visible_score),
|
|
||||||
"legacy_gpr_min_visible_pair_count": float(self.legacy_gpr_min_visible_pair_count),
|
"legacy_gpr_min_visible_pair_count": float(self.legacy_gpr_min_visible_pair_count),
|
||||||
"gpr_visible_x_min_m": float(self.gpr_visible_x_min_m),
|
"gpr_visible_x_min_m": float(self.gpr_visible_x_min_m),
|
||||||
"gpr_visible_x_max_m": float(self.gpr_visible_x_max_m),
|
"gpr_visible_x_max_m": float(self.gpr_visible_x_max_m),
|
||||||
|
|||||||
@@ -161,8 +161,8 @@ class WebControllerTest(unittest.TestCase):
|
|||||||
def test_known_field_emits_and_returns_snapshot(self) -> None:
|
def test_known_field_emits_and_returns_snapshot(self) -> None:
|
||||||
received: list[dict] = []
|
received: list[dict] = []
|
||||||
self.controller.apply_settings_requested.connect(received.append)
|
self.controller.apply_settings_requested.connect(received.append)
|
||||||
out = self.controller.apply_live_settings({"gpr_min_visible_score": 0.5})
|
out = self.controller.apply_live_settings({"gpr_object_min_frac": 0.5})
|
||||||
self.assertEqual(received, [{"gpr_min_visible_score": 0.5}])
|
self.assertEqual(received, [{"gpr_object_min_frac": 0.5}])
|
||||||
self.assertIsInstance(out, list)
|
self.assertIsInstance(out, list)
|
||||||
|
|
||||||
def test_snapshot_is_replaced_and_returned_as_copy(self) -> None:
|
def test_snapshot_is_replaced_and_returned_as_copy(self) -> None:
|
||||||
|
|||||||
+2
-2
@@ -282,7 +282,7 @@
|
|||||||
"min_depth_m": 2.0,
|
"min_depth_m": 2.0,
|
||||||
"max_depth_m": 14.0,
|
"max_depth_m": 14.0,
|
||||||
"range_comp_power": 0.1,
|
"range_comp_power": 0.1,
|
||||||
"angle_comp_power": 0.0,
|
"object_min_frac": 0.7,
|
||||||
"score_mode": "combined",
|
"score_mode": "combined",
|
||||||
"motion_mode": "int_minus",
|
"motion_mode": "int_minus",
|
||||||
"look_angle_deg": 0.0,
|
"look_angle_deg": 0.0,
|
||||||
@@ -291,6 +291,7 @@
|
|||||||
"ignore_socket_speed_enabled": false,
|
"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,
|
||||||
|
"object_approach_min_frames": 3,
|
||||||
"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,
|
||||||
@@ -298,7 +299,6 @@
|
|||||||
"remove_sidelobe_objects_enabled": false,
|
"remove_sidelobe_objects_enabled": false,
|
||||||
"imaging_plane_y_m": 0.0,
|
"imaging_plane_y_m": 0.0,
|
||||||
"render_mode": "heatmap",
|
"render_mode": "heatmap",
|
||||||
"min_visible_score": 0.0,
|
|
||||||
"visible_x_min_m": -2.0,
|
"visible_x_min_m": -2.0,
|
||||||
"visible_x_max_m": 2.0,
|
"visible_x_max_m": 2.0,
|
||||||
"visible_z_min_m": 0.0,
|
"visible_z_min_m": 0.0,
|
||||||
|
|||||||
@@ -282,7 +282,7 @@
|
|||||||
"min_depth_m": 2.0,
|
"min_depth_m": 2.0,
|
||||||
"max_depth_m": 14.0,
|
"max_depth_m": 14.0,
|
||||||
"range_comp_power": 0.1,
|
"range_comp_power": 0.1,
|
||||||
"angle_comp_power": 0.0,
|
"object_min_frac": 0.7,
|
||||||
"score_mode": "combined",
|
"score_mode": "combined",
|
||||||
"motion_mode": "int_minus",
|
"motion_mode": "int_minus",
|
||||||
"look_angle_deg": 0.0,
|
"look_angle_deg": 0.0,
|
||||||
@@ -291,6 +291,7 @@
|
|||||||
"ignore_socket_speed_enabled": false,
|
"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,
|
||||||
|
"object_approach_min_frames": 3,
|
||||||
"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,
|
||||||
@@ -298,7 +299,6 @@
|
|||||||
"remove_sidelobe_objects_enabled": false,
|
"remove_sidelobe_objects_enabled": false,
|
||||||
"imaging_plane_y_m": 0.0,
|
"imaging_plane_y_m": 0.0,
|
||||||
"render_mode": "heatmap",
|
"render_mode": "heatmap",
|
||||||
"min_visible_score": 0.0,
|
|
||||||
"visible_x_min_m": -2.0,
|
"visible_x_min_m": -2.0,
|
||||||
"visible_x_max_m": 2.0,
|
"visible_x_max_m": 2.0,
|
||||||
"visible_z_min_m": 0.0,
|
"visible_z_min_m": 0.0,
|
||||||
|
|||||||
@@ -282,7 +282,7 @@
|
|||||||
"min_depth_m": 2.0,
|
"min_depth_m": 2.0,
|
||||||
"max_depth_m": 14.0,
|
"max_depth_m": 14.0,
|
||||||
"range_comp_power": 0.1,
|
"range_comp_power": 0.1,
|
||||||
"angle_comp_power": 0.0,
|
"object_min_frac": 0.7,
|
||||||
"score_mode": "combined",
|
"score_mode": "combined",
|
||||||
"motion_mode": "int_minus",
|
"motion_mode": "int_minus",
|
||||||
"look_angle_deg": 0.0,
|
"look_angle_deg": 0.0,
|
||||||
@@ -291,6 +291,7 @@
|
|||||||
"ignore_socket_speed_enabled": false,
|
"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,
|
||||||
|
"object_approach_min_frames": 3,
|
||||||
"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,
|
||||||
@@ -298,7 +299,6 @@
|
|||||||
"remove_sidelobe_objects_enabled": false,
|
"remove_sidelobe_objects_enabled": false,
|
||||||
"imaging_plane_y_m": 0.0,
|
"imaging_plane_y_m": 0.0,
|
||||||
"render_mode": "heatmap",
|
"render_mode": "heatmap",
|
||||||
"min_visible_score": 0.0,
|
|
||||||
"visible_x_min_m": -2.0,
|
"visible_x_min_m": -2.0,
|
||||||
"visible_x_max_m": 2.0,
|
"visible_x_max_m": 2.0,
|
||||||
"visible_z_min_m": 0.0,
|
"visible_z_min_m": 0.0,
|
||||||
|
|||||||
Reference in New Issue
Block a user