added new filtration and fixed processing parameters

This commit is contained in:
Ayzen
2026-06-23 21:55:40 +03:00
parent 4ca4b27246
commit 42532c9868
21 changed files with 365 additions and 140 deletions
@@ -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_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_angle_comp_power", "Imaging", ("gpr",), _attr("_gpr_angle_comp_power")),
("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_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_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_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_snr_thresh", "Detection", ("legacy_gpr",), _attr("_legacy_gpr_snr_thresh")),
("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
# "applies on Start" and editing them just updates the widget for the next start.
_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")),
("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"freq={self._gpr_start_freq_mhz.value():g}..{self._gpr_stop_freq_mhz.value():g} MHz, "
f"range_comp={self._gpr_range_comp_power.value():g}, "
f"angle_comp={self._gpr_angle_comp_power.value():g}, "
f"object_min_frac={self._gpr_object_min_frac.value():g}, "
f"score_mode={self._gpr_score_mode.currentText()}, "
f"background_subtract={self._gpr_background_subtract_enabled.isChecked()}, "
f"mean_count={self._gpr_background_mean_count.value()}, "
f"remove_sidelobes={self._gpr_remove_sidelobe_objects_enabled.isChecked()}, "
f"imaging_plane_y={self._gpr_imaging_plane_y_m.value():g} m, "
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"draw_top={self._gpr_draw_top_m_objects.value()})"
)
@@ -291,7 +291,7 @@ class AppWindowConfigProfileIOMixin:
self._gpr_min_depth_m,
self._gpr_max_depth_m,
self._gpr_range_comp_power,
self._gpr_angle_comp_power,
self._gpr_object_min_frac,
self._gpr_score_mode,
self._gpr_motion_mode,
self._gpr_look_angle_deg,
@@ -300,6 +300,7 @@ class AppWindowConfigProfileIOMixin:
self._gpr_speed_m_s,
self._gpr_max_detected_objects_to_draw,
self._gpr_draw_top_m_objects,
self._gpr_object_approach_min_frames,
self._gpr_start_freq_mhz,
self._gpr_stop_freq_mhz,
self._gpr_background_subtract_enabled,
@@ -307,7 +308,6 @@ class AppWindowConfigProfileIOMixin:
self._gpr_remove_sidelobe_objects_enabled,
self._gpr_imaging_plane_y_m,
self._gpr_render_mode,
self._gpr_min_visible_score,
self._gpr_visible_x_min_m,
self._gpr_visible_x_max_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_max_depth_m.setValue(float(gui_state.processing.gpr.max_depth_m))
self._gpr_range_comp_power.setValue(float(gui_state.processing.gpr.range_comp_power))
self._gpr_angle_comp_power.setValue(float(gui_state.processing.gpr.angle_comp_power))
self._gpr_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_motion_mode, gui_state.processing.gpr.motion_mode)
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)
)
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_stop_freq_mhz.setValue(float(gui_state.processing.gpr.stop_freq_mhz))
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._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_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))
@@ -224,7 +224,7 @@ class AppWindowConfigStateBuildersMixin:
min_depth_m=2.0,
max_depth_m=14.0,
range_comp_power=0.1,
angle_comp_power=0.0,
object_min_frac=0.7,
score_mode="combined",
motion_mode="int_minus",
look_angle_deg=0.0,
@@ -233,6 +233,7 @@ class AppWindowConfigStateBuildersMixin:
ignore_socket_speed_enabled=False,
max_detected_objects_to_draw=5,
draw_top_m_objects=2,
object_approach_min_frames=3,
start_freq_mhz=3000.0,
stop_freq_mhz=6000.0,
background_subtract_enabled=True,
@@ -240,7 +241,6 @@ class AppWindowConfigStateBuildersMixin:
remove_sidelobe_objects_enabled=True,
imaging_plane_y_m=0.0,
render_mode="heatmap",
min_visible_score=0.0,
visible_x_min_m=default_gpr_x_min_m,
visible_x_max_m=default_gpr_x_max_m,
visible_z_min_m=0.0,
@@ -351,7 +351,7 @@ class AppWindowConfigStateBuildersMixin:
min_depth_m=float(self._gpr_min_depth_m.value()),
max_depth_m=float(self._gpr_max_depth_m.value()),
range_comp_power=float(self._gpr_range_comp_power.value()),
angle_comp_power=float(self._gpr_angle_comp_power.value()),
object_min_frac=float(self._gpr_object_min_frac.value()),
score_mode=self._gpr_score_mode.currentText(),
motion_mode=self._gpr_motion_mode.currentText(),
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()),
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()),
object_approach_min_frames=int(self._gpr_object_approach_min_frames.value()),
start_freq_mhz=float(self._gpr_start_freq_mhz.value()),
stop_freq_mhz=float(self._gpr_stop_freq_mhz.value()),
background_subtract_enabled=bool(self._gpr_background_subtract_enabled.isChecked()),
@@ -367,7 +368,6 @@ class AppWindowConfigStateBuildersMixin:
remove_sidelobe_objects_enabled=bool(self._gpr_remove_sidelobe_objects_enabled.isChecked()),
imaging_plane_y_m=float(self._gpr_imaging_plane_y_m.value()),
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_max_m=float(self._gpr_visible_x_max_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.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_payloads_by_prefix as gpr_collection_payloads_by_prefix,
filter_object_rows as gpr_filter_object_rows,
@@ -371,26 +370,6 @@ class AppWindowGprPlotMixin:
return self._legacy_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
def _gpr_display_y_min(z_min: float, z_max: float) -> float:
"""Return lower display bound, preserving surface markers only when surface is visible."""
@@ -506,18 +485,24 @@ class AppWindowGprPlotMixin:
return extract_gpr_object_rows(collection)
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)
if rows.size == 0:
if rows.size == 0 or self._processing_mode.currentText() != "legacy_gpr":
return rows
x_min, x_max, z_min, z_max = self._gpr_visible_object_bounds()
return gpr_filter_object_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),
z_bounds=(z_min, z_max),
draw_limits=self._gpr_draw_limits(),
draw_limits=None,
)
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
# 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.setDecimals(4)
owner._gpr_relative_permittivity.setRange(0.0001, 1000.0)
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_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(
group,
[
("Relative permittivity", owner._gpr_relative_permittivity),
("Tx geometry", owner._gpr_tx_geometry_input),
("Rx geometry", owner._gpr_rx_geometry_input),
],
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_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.setValue(float(gpr_live_defaults.range_comp_power))
owner._gpr_angle_comp_power = QDoubleSpinBox()
owner._gpr_angle_comp_power.setDecimals(3)
owner._gpr_angle_comp_power.setRange(0.0, 5.0)
owner._gpr_angle_comp_power.setSingleStep(0.01)
owner._gpr_angle_comp_power.setValue(float(gpr_live_defaults.angle_comp_power))
owner._gpr_object_min_frac = QDoubleSpinBox()
owner._gpr_object_min_frac.setDecimals(2)
owner._gpr_object_min_frac.setRange(0.0, 1.0)
owner._gpr_object_min_frac.setSingleStep(0.05)
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.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.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.setDecimals(1)
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._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.setDecimals(2)
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),
("Max depth m", owner._gpr_max_depth_m),
("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),
("Motion mode", owner._gpr_motion_mode),
("Look angle deg", owner._gpr_look_angle_deg),
@@ -370,9 +379,9 @@ def build_processing_group(owner) -> QGroupBox:
owner._gpr_ignore_socket_speed_enabled,
("Speed m/s", owner._gpr_speed_m_s),
("Render mode", owner._gpr_render_mode),
("Min visible score", owner._gpr_min_visible_score),
("Max detected objects", owner._gpr_max_detected_objects_to_draw),
("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),
("Stop MHz", owner._gpr_stop_freq_mhz),
("Imaging plane Y m", owner._gpr_imaging_plane_y_m),
@@ -522,6 +531,7 @@ def build_processing_group(owner) -> QGroupBox:
owner._processing_mode_pages,
[
("Config mode", owner._legacy_gpr_config_mode),
("Relative permittivity", owner._gpr_relative_permittivity),
("Input positions", owner._legacy_gpr_input_positions_input),
("Output positions", owner._legacy_gpr_output_positions_input),
("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_max_depth_m.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._gpr_range_comp_power.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._gpr_angle_comp_power.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._gpr_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_motion_mode.currentTextChanged.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_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_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_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_max_m.valueChanged.connect(owner._on_gpr_locator_window_changed)
owner._gpr_visible_z_min_m.valueChanged.connect(owner._on_gpr_locator_window_changed)