added legacy gpr

This commit is contained in:
Ayzen
2026-05-06 11:54:59 +03:00
parent e86f30023e
commit 1b7d9e417d
16 changed files with 2697 additions and 1335 deletions
@@ -38,12 +38,18 @@ class AppWindowLiveProcessingMixin:
bscan_gain=float(self._bscan_gain.value()),
bscan_start_freq_mhz=float(self._bscan_start_freq_mhz.value()),
bscan_stop_freq_mhz=float(self._bscan_stop_freq_mhz.value()),
gpr_algorithm=self._gpr_algorithm.currentText(),
gpr_input_positions=self._parse_csv_int_list(self._gpr_input_positions_input.text()),
gpr_output_positions=self._parse_csv_int_list(self._gpr_output_positions_input.text()),
gpr_min_depth_m=float(self._gpr_min_depth_m.value()),
gpr_max_depth_m=float(self._gpr_max_depth_m.value()),
gpr_range_comp_power=float(self._gpr_range_comp_power.value()),
gpr_angle_comp_power=float(self._gpr_angle_comp_power.value()),
gpr_comp_power=float(self._gpr_comp_power.value()),
gpr_speed_m_s=float(self._gpr_speed_m_s.value()),
gpr_look_angle_deg=float(self._gpr_look_angle_deg.value()),
gpr_snr_thresh=float(self._gpr_snr_thresh.value()),
gpr_snr_comp_max=float(self._gpr_snr_comp_max.value()),
gpr_start_freq_mhz=float(self._gpr_start_freq_mhz.value()),
gpr_stop_freq_mhz=float(self._gpr_stop_freq_mhz.value()),
gpr_background_subtract_enabled=bool(self._gpr_background_subtract_enabled.isChecked()),
@@ -176,12 +182,15 @@ class AppWindowLiveProcessingMixin:
elif mode == "gpr":
self._log(
"Processing mode selected: gpr "
f"(inputs={self._gpr_input_positions_input.text().strip() or '<all>'}, "
f"(algorithm={self._gpr_algorithm.currentText()}, "
f"inputs={self._gpr_input_positions_input.text().strip() or '<all>'}, "
f"outputs={self._gpr_output_positions_input.text().strip() or '<all>'}, "
f"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"comp={self._gpr_comp_power.value():g}, "
f"snr={self._gpr_snr_thresh.value():g}, "
f"background_subtract={self._gpr_background_subtract_enabled.isChecked()}, "
f"mean_count={self._gpr_background_mean_count.value()}, "
f"remove_sidelobes={self._gpr_remove_sidelobe_objects_enabled.isChecked()}, "
@@ -70,6 +70,25 @@ class AppWindowConfigProfileIOMixin:
self._pass_through_y_min_db.setEnabled(enabled)
self._pass_through_y_max_db.setEnabled(enabled)
def _sync_gpr_algorithm_controls(self) -> None:
"""Enable only controls that affect the selected GPR algorithm."""
backprojection_enabled = self._gpr_algorithm.currentText() == "backprojection"
for widget in (
self._gpr_range_comp_power,
self._gpr_angle_comp_power,
self._gpr_remove_sidelobe_objects_enabled,
):
widget.setEnabled(backprojection_enabled)
for widget in (
self._gpr_comp_power,
self._gpr_speed_m_s,
self._gpr_look_angle_deg,
self._gpr_snr_thresh,
self._gpr_snr_comp_max,
):
widget.setEnabled(not backprojection_enabled)
def _apply_history_limit_from_config(self, config) -> None:
"""Resize in-memory history buffers to match the loaded config."""
history_limit = self._history_limit_for_config(config)
@@ -194,6 +213,7 @@ class AppWindowConfigProfileIOMixin:
self._bscan_start_freq_mhz,
self._bscan_stop_freq_mhz,
self._bscan_subtract_mean_ascan,
self._gpr_algorithm,
self._gpr_relative_permittivity,
self._gpr_tx_geometry_input,
self._gpr_rx_geometry_input,
@@ -203,6 +223,11 @@ class AppWindowConfigProfileIOMixin:
self._gpr_max_depth_m,
self._gpr_range_comp_power,
self._gpr_angle_comp_power,
self._gpr_comp_power,
self._gpr_speed_m_s,
self._gpr_look_angle_deg,
self._gpr_snr_thresh,
self._gpr_snr_comp_max,
self._gpr_start_freq_mhz,
self._gpr_stop_freq_mhz,
self._gpr_background_subtract_enabled,
@@ -265,10 +290,16 @@ class AppWindowConfigProfileIOMixin:
)
self._gpr_input_positions_input.setText(str(gui_state.processing.gpr.input_positions))
self._gpr_output_positions_input.setText(str(gui_state.processing.gpr.output_positions))
self._set_combo_current_text(self._gpr_algorithm, gui_state.processing.gpr.algorithm)
self._gpr_min_depth_m.setValue(float(gui_state.processing.gpr.min_depth_m))
self._gpr_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_comp_power.setValue(float(gui_state.processing.gpr.comp_power))
self._gpr_speed_m_s.setValue(float(gui_state.processing.gpr.speed_m_s))
self._gpr_look_angle_deg.setValue(float(gui_state.processing.gpr.look_angle_deg))
self._gpr_snr_thresh.setValue(float(gui_state.processing.gpr.snr_thresh))
self._gpr_snr_comp_max.setValue(float(gui_state.processing.gpr.snr_comp_max))
self._gpr_start_freq_mhz.setValue(float(gui_state.processing.gpr.start_freq_mhz))
self._gpr_stop_freq_mhz.setValue(float(gui_state.processing.gpr.stop_freq_mhz))
self._gpr_background_subtract_enabled.setChecked(
@@ -299,6 +330,7 @@ class AppWindowConfigProfileIOMixin:
self._gpr_geometry_signature = None
self._gpr_selected_geometry = None
self._sync_pass_through_y_controls()
self._sync_gpr_algorithm_controls()
self._refresh_preprocess_summary_labels()
if self._preprocess_dialog is not None:
@@ -252,12 +252,18 @@ class AppWindowConfigStateBuildersMixin:
subtract_mean_ascan=bool(self._bscan_subtract_mean_ascan.isChecked()),
),
gpr=GuiGprStateModel(
algorithm=self._gpr_algorithm.currentText(),
input_positions=self._gpr_input_positions_input.text().strip(),
output_positions=self._gpr_output_positions_input.text().strip(),
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()),
comp_power=float(self._gpr_comp_power.value()),
speed_m_s=float(self._gpr_speed_m_s.value()),
look_angle_deg=float(self._gpr_look_angle_deg.value()),
snr_thresh=float(self._gpr_snr_thresh.value()),
snr_comp_max=float(self._gpr_snr_comp_max.value()),
start_freq_mhz=float(self._gpr_start_freq_mhz.value()),
stop_freq_mhz=float(self._gpr_stop_freq_mhz.value()),
background_subtract_enabled=bool(self._gpr_background_subtract_enabled.isChecked()),
@@ -4,6 +4,8 @@ from __future__ import annotations
import time
from PyQt6.QtCore import QSignalBlocker
from python_app.gui.runtime.constraints import validate_processing_mode_constraints
from python_app.gui.runtime.history import build_run_history_signature, record_result_history
from python_app.hardware_full.single_radar_service import create_single_radar_service
@@ -470,10 +472,21 @@ class AppWindowPipelineMixin:
)
def _drain_locator_speed_updates(self) -> None:
"""Drain queued locator speed packets; coherent BP does not use motion speed."""
"""Apply the newest queued locator speed packet to live processing settings."""
if self._locator_service is None:
return
self._locator_service.drain_speed_updates()
speed_m_s = self._locator_service.drain_speed_updates()
if speed_m_s is None:
return
previous_speed_m_s = float(self._gpr_speed_m_s.value())
with QSignalBlocker(self._gpr_speed_m_s):
self._gpr_speed_m_s.setValue(float(speed_m_s))
current_speed_m_s = float(self._gpr_speed_m_s.value())
if current_speed_m_s == previous_speed_m_s:
return
self._write_live_processing_config()
def _publish_locator_snapshot_from_collection(self, collection: ResultCollection) -> None:
"""Publish one locator snapshot from a GPR result collection."""
@@ -154,6 +154,10 @@ def build_processing_group(owner) -> QGroupBox:
gpr_defaults = owner._defaults_config.gpr
owner._gpr_algorithm = QComboBox()
owner._gpr_algorithm.addItems(["backprojection", "legacy_point", "legacy_extended"])
owner._set_combo_current_text(owner._gpr_algorithm, gpr_live_defaults.algorithm)
owner._gpr_relative_permittivity = QDoubleSpinBox()
owner._gpr_relative_permittivity.setDecimals(4)
owner._gpr_relative_permittivity.setRange(0.0001, 1000.0)
@@ -198,6 +202,36 @@ def build_processing_group(owner) -> QGroupBox:
owner._gpr_angle_comp_power.setSingleStep(0.01)
owner._gpr_angle_comp_power.setValue(float(gpr_live_defaults.angle_comp_power))
owner._gpr_comp_power = QDoubleSpinBox()
owner._gpr_comp_power.setDecimals(3)
owner._gpr_comp_power.setRange(0.0, 5.0)
owner._gpr_comp_power.setSingleStep(0.05)
owner._gpr_comp_power.setValue(float(gpr_live_defaults.comp_power))
owner._gpr_speed_m_s = QDoubleSpinBox()
owner._gpr_speed_m_s.setDecimals(3)
owner._gpr_speed_m_s.setRange(-100.0, 100.0)
owner._gpr_speed_m_s.setSingleStep(0.01)
owner._gpr_speed_m_s.setValue(float(gpr_live_defaults.speed_m_s))
owner._gpr_look_angle_deg = QDoubleSpinBox()
owner._gpr_look_angle_deg.setDecimals(2)
owner._gpr_look_angle_deg.setRange(-90.0, 90.0)
owner._gpr_look_angle_deg.setSingleStep(0.1)
owner._gpr_look_angle_deg.setValue(float(gpr_live_defaults.look_angle_deg))
owner._gpr_snr_thresh = QDoubleSpinBox()
owner._gpr_snr_thresh.setDecimals(2)
owner._gpr_snr_thresh.setRange(0.0, 1_000.0)
owner._gpr_snr_thresh.setSingleStep(0.1)
owner._gpr_snr_thresh.setValue(float(gpr_live_defaults.snr_thresh))
owner._gpr_snr_comp_max = QDoubleSpinBox()
owner._gpr_snr_comp_max.setDecimals(2)
owner._gpr_snr_comp_max.setRange(0.0, 1_000.0)
owner._gpr_snr_comp_max.setSingleStep(0.5)
owner._gpr_snr_comp_max.setValue(float(gpr_live_defaults.snr_comp_max))
owner._gpr_start_freq_mhz = QDoubleSpinBox()
owner._gpr_start_freq_mhz.setDecimals(1)
owner._gpr_start_freq_mhz.setRange(100.0, 8800.0)
@@ -220,6 +254,8 @@ def build_processing_group(owner) -> QGroupBox:
owner._gpr_remove_sidelobe_objects_enabled = QCheckBox("Remove sidelobe objects")
owner._gpr_remove_sidelobe_objects_enabled.setChecked(bool(gpr_live_defaults.remove_sidelobe_objects_enabled))
owner._sync_gpr_algorithm_controls()
owner._gpr_render_mode = QComboBox()
owner._gpr_render_mode.addItems(["heatmap", "objects_only"])
owner._set_combo_current_text(owner._gpr_render_mode, gpr_live_defaults.render_mode)
@@ -257,6 +293,7 @@ def build_processing_group(owner) -> QGroupBox:
gpr_page = _build_processing_mode_page(
owner._processing_mode_pages,
[
("Algorithm", owner._gpr_algorithm),
("Relative permittivity", owner._gpr_relative_permittivity),
("Input positions", owner._gpr_input_positions_input),
("Output positions", owner._gpr_output_positions_input),
@@ -264,12 +301,17 @@ def build_processing_group(owner) -> QGroupBox:
("Max depth m", owner._gpr_max_depth_m),
("Range comp power", owner._gpr_range_comp_power),
("Angle comp power", owner._gpr_angle_comp_power),
("Comp power", owner._gpr_comp_power),
("SNR thresh", owner._gpr_snr_thresh),
("SNR comp max", owner._gpr_snr_comp_max),
("Render mode", owner._gpr_render_mode),
("Min visible score", owner._gpr_min_visible_score),
("Tx geometry", owner._gpr_tx_geometry_input),
("Rx geometry", owner._gpr_rx_geometry_input),
("Start MHz", owner._gpr_start_freq_mhz),
("Stop MHz", owner._gpr_stop_freq_mhz),
("Speed m/s", owner._gpr_speed_m_s),
("Look angle deg", owner._gpr_look_angle_deg),
("Visible X min m", owner._gpr_visible_x_min_m),
("Visible X max m", owner._gpr_visible_x_max_m),
("Visible Z min m", owner._gpr_visible_z_min_m),
@@ -278,7 +320,7 @@ def build_processing_group(owner) -> QGroupBox:
("Mean count", owner._gpr_background_mean_count),
owner._gpr_remove_sidelobe_objects_enabled,
],
split_index=10,
split_index=12,
)
owner._processing_mode_pages.addWidget(gpr_page)
@@ -299,12 +341,19 @@ def build_processing_group(owner) -> QGroupBox:
owner._bscan_start_freq_mhz.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._bscan_stop_freq_mhz.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._bscan_subtract_mean_ascan.toggled.connect(owner._on_processing_live_settings_changed)
owner._gpr_algorithm.currentTextChanged.connect(owner._sync_gpr_algorithm_controls)
owner._gpr_algorithm.currentTextChanged.connect(owner._on_processing_live_settings_changed)
owner._gpr_input_positions_input.editingFinished.connect(owner._on_processing_live_settings_changed)
owner._gpr_output_positions_input.editingFinished.connect(owner._on_processing_live_settings_changed)
owner._gpr_min_depth_m.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._gpr_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_comp_power.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._gpr_speed_m_s.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._gpr_look_angle_deg.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._gpr_snr_thresh.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._gpr_snr_comp_max.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._gpr_start_freq_mhz.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._gpr_stop_freq_mhz.valueChanged.connect(owner._on_processing_live_settings_changed)
owner._gpr_background_subtract_enabled.toggled.connect(owner._on_processing_live_settings_changed)
+57
View File
@@ -91,6 +91,13 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
pass_through_object = _as_dict(processing_object.get("pass_through"), "gui.processing.pass_through")
bscan_object = _as_dict(processing_object.get("bscan"), "gui.processing.bscan")
gpr_object = _as_dict(processing_object.get("gpr"), "gui.processing.gpr")
root_gpr_object = payload.get("gpr")
gpr_algorithm_default = gui.processing.gpr.algorithm
if isinstance(root_gpr_object, dict):
if root_gpr_object.get("mode") == "point":
gpr_algorithm_default = "legacy_point"
elif root_gpr_object.get("mode") == "extended":
gpr_algorithm_default = "legacy_extended"
gui.processing = GuiProcessingStateModel(
selected_mode=_optional_string(
processing_object,
@@ -160,6 +167,12 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
),
),
gpr=GuiGprStateModel(
algorithm=_optional_string(
gpr_object,
"algorithm",
gpr_algorithm_default,
"gui.processing.gpr",
),
input_positions=_optional_string(
gpr_object,
"input_positions",
@@ -196,6 +209,36 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
gui.processing.gpr.angle_comp_power,
"gui.processing.gpr",
),
comp_power=_optional_float(
gpr_object,
"comp_power",
gui.processing.gpr.comp_power,
"gui.processing.gpr",
),
speed_m_s=_optional_float(
gpr_object,
"speed_m_s",
gui.processing.gpr.speed_m_s,
"gui.processing.gpr",
),
look_angle_deg=_optional_float(
gpr_object,
"look_angle_deg",
gui.processing.gpr.look_angle_deg,
"gui.processing.gpr",
),
snr_thresh=_optional_float(
gpr_object,
"snr_thresh",
gui.processing.gpr.snr_thresh,
"gui.processing.gpr",
),
snr_comp_max=_optional_float(
gpr_object,
"snr_comp_max",
gui.processing.gpr.snr_comp_max,
"gui.processing.gpr",
),
start_freq_mhz=_optional_float(
gpr_object,
"start_freq_mhz",
@@ -268,12 +311,20 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel:
raise ValueError("gui.processing.selected_mode must be one of: pass_through, bscan, gpr")
if gui.processing.bscan.axis not in {"abs", "real", "phase"}:
raise ValueError("gui.processing.bscan.axis must be one of: abs, real, phase")
if gui.processing.gpr.algorithm not in {"backprojection", "legacy_point", "legacy_extended"}:
raise ValueError("gui.processing.gpr.algorithm must be one of: backprojection, legacy_point, legacy_extended")
if gui.processing.gpr.render_mode not in {"heatmap", "objects_only"}:
raise ValueError("gui.processing.gpr.render_mode must be one of: heatmap, objects_only")
if gui.processing.gpr.range_comp_power < 0.0:
raise ValueError("gui.processing.gpr.range_comp_power must be >= 0")
if gui.processing.gpr.angle_comp_power < 0.0:
raise ValueError("gui.processing.gpr.angle_comp_power must be >= 0")
if gui.processing.gpr.comp_power < 0.0:
raise ValueError("gui.processing.gpr.comp_power must be >= 0")
if gui.processing.gpr.snr_thresh < 0.0:
raise ValueError("gui.processing.gpr.snr_thresh must be >= 0")
if gui.processing.gpr.snr_comp_max < 0.0:
raise ValueError("gui.processing.gpr.snr_comp_max must be >= 0")
if gui.processing.gpr.min_visible_score < 0.0:
raise ValueError("gui.processing.gpr.min_visible_score must be >= 0")
@@ -356,12 +407,18 @@ def gui_profile_to_dict(model: GuiProfileModel) -> dict[str, Any]:
"subtract_mean_ascan": gui.processing.bscan.subtract_mean_ascan,
},
"gpr": {
"algorithm": gui.processing.gpr.algorithm,
"input_positions": gui.processing.gpr.input_positions,
"output_positions": gui.processing.gpr.output_positions,
"min_depth_m": gui.processing.gpr.min_depth_m,
"max_depth_m": gui.processing.gpr.max_depth_m,
"range_comp_power": gui.processing.gpr.range_comp_power,
"angle_comp_power": gui.processing.gpr.angle_comp_power,
"comp_power": gui.processing.gpr.comp_power,
"speed_m_s": gui.processing.gpr.speed_m_s,
"look_angle_deg": gui.processing.gpr.look_angle_deg,
"snr_thresh": gui.processing.gpr.snr_thresh,
"snr_comp_max": gui.processing.gpr.snr_comp_max,
"start_freq_mhz": gui.processing.gpr.start_freq_mhz,
"stop_freq_mhz": gui.processing.gpr.stop_freq_mhz,
"background_subtract_enabled": gui.processing.gpr.background_subtract_enabled,
+6
View File
@@ -49,12 +49,18 @@ class GuiBscanStateModel:
class GuiGprStateModel:
"""UI-only defaults for GPR live settings."""
algorithm: str = "backprojection"
input_positions: str = ""
output_positions: str = ""
min_depth_m: float = 2.0
max_depth_m: float = 14.0
range_comp_power: float = 0.28
angle_comp_power: float = 0.10
comp_power: float = 0.2
speed_m_s: float = 0.0
look_angle_deg: float = 0.0
snr_thresh: float = 4.5
snr_comp_max: float = 25.0
start_freq_mhz: float = 3000.0
stop_freq_mhz: float = 6000.0
background_subtract_enabled: bool = True
@@ -23,12 +23,18 @@ class ProcessingLiveConfig:
bscan_gain: float = 1.0
bscan_start_freq_mhz: float = 100.0
bscan_stop_freq_mhz: float = 8800.0
gpr_algorithm: str = "backprojection"
gpr_input_positions: list[int] | None = None
gpr_output_positions: list[int] | None = None
gpr_min_depth_m: float = 2.0
gpr_max_depth_m: float = 14.0
gpr_range_comp_power: float = 0.28
gpr_angle_comp_power: float = 0.10
gpr_comp_power: float = 0.2
gpr_speed_m_s: float = 0.0
gpr_look_angle_deg: float = 0.0
gpr_snr_thresh: float = 4.5
gpr_snr_comp_max: float = 25.0
gpr_start_freq_mhz: float = 3000.0
gpr_stop_freq_mhz: float = 6000.0
gpr_background_subtract_enabled: bool = True
@@ -63,12 +69,18 @@ class ProcessingLiveConfig:
"bscan_gain": float(self.bscan_gain),
"bscan_start_freq_mhz": float(self.bscan_start_freq_mhz),
"bscan_stop_freq_mhz": float(self.bscan_stop_freq_mhz),
"gpr_algorithm": str(self.gpr_algorithm),
"gpr_input_positions": [int(value) for value in self.gpr_input_positions],
"gpr_output_positions": [int(value) for value in self.gpr_output_positions],
"gpr_min_depth_m": float(self.gpr_min_depth_m),
"gpr_max_depth_m": float(self.gpr_max_depth_m),
"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_speed_m_s": float(self.gpr_speed_m_s),
"gpr_look_angle_deg": float(self.gpr_look_angle_deg),
"gpr_snr_thresh": float(self.gpr_snr_thresh),
"gpr_snr_comp_max": float(self.gpr_snr_comp_max),
"gpr_start_freq_mhz": float(self.gpr_start_freq_mhz),
"gpr_stop_freq_mhz": float(self.gpr_stop_freq_mhz),
"gpr_background_subtract_enabled": bool(self.gpr_background_subtract_enabled),