This commit is contained in:
Ayzen
2026-05-05 15:45:52 +03:00
parent 5a70235ef3
commit e86f30023e
29 changed files with 1743 additions and 1797 deletions
@@ -42,15 +42,13 @@ class AppWindowLiveProcessingMixin:
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_comp_power=float(self._gpr_comp_power.value()),
gpr_range_comp_power=float(self._gpr_range_comp_power.value()),
gpr_angle_comp_power=float(self._gpr_angle_comp_power.value()),
gpr_start_freq_mhz=float(self._gpr_start_freq_mhz.value()),
gpr_stop_freq_mhz=float(self._gpr_stop_freq_mhz.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_background_subtract_enabled=bool(self._gpr_background_subtract_enabled.isChecked()),
gpr_background_mean_count=int(self._gpr_background_mean_count.value()),
gpr_remove_sidelobe_objects_enabled=bool(self._gpr_remove_sidelobe_objects_enabled.isChecked()),
history_command_seq=int(self._history_command_seq),
history_command=str(history_command),
)
@@ -61,25 +59,25 @@ class AppWindowLiveProcessingMixin:
self._history_command_seq += 1
self._live_config_writer.write(self._live_processing_config(history_command=history_command))
def _apply_external_gpr_speed_update(self, speed_m_s: float) -> None:
"""Apply GPR speed received from locator clients without recursive signals."""
with QSignalBlocker(self._gpr_speed_m_s):
self._gpr_speed_m_s.setValue(float(speed_m_s))
self._write_live_processing_config()
def _on_processing_live_settings_changed(self, *_args) -> None:
"""Handle live-processing setting changes and trigger redraw when needed."""
try:
self._write_live_processing_config()
current_mode = self._processing_mode.currentText()
if current_mode == "gpr":
self._drain_results_until_quiet(timeout_s=0.05, poll_s=0.005)
self._write_live_processing_config()
if current_mode == "bscan":
self._drain_results_until_quiet(timeout_s=0.25, poll_s=0.01)
self._sync_bscan_history_from_results()
self._draw_bscan_heatmap_from_history()
elif current_mode == "gpr":
self._drain_results_until_quiet(timeout_s=0.35, poll_s=0.01)
if self._result_history:
if not self._draw_results(self._result_history[-1]):
latest = self._drain_results_until_quiet(timeout_s=0.8, poll_s=0.02)
collection = latest
if collection is None and self._result_history:
collection = self._result_history[-1]
if collection is not None:
if not self._draw_results(collection):
self._clear_gpr_plot()
else:
self._clear_gpr_plot()
@@ -121,6 +119,19 @@ class AppWindowLiveProcessingMixin:
except Exception as exc: # noqa: BLE001
self._show_exception("Failed to update locator GPR window", exc)
def _set_processing_mode_page(self, mode: str) -> None:
"""Show the parameter page for `mode` without changing runtime state."""
mode_to_page = {
"pass_through": 0,
"bscan": 1,
"gpr": 2,
}
self._processing_mode_pages.setCurrentIndex(mode_to_page.get(mode, 0))
current_page = self._processing_mode_pages.currentWidget()
if current_page is not None:
self._processing_mode_pages.setFixedHeight(current_page.sizeHint().height())
self._processing_mode_pages.updateGeometry()
def _on_processing_mode_changed(self, mode: str) -> None:
"""Switch processing parameter page and refresh corresponding visualization."""
previous_mode = getattr(self, "_active_processing_mode", "pass_through")
@@ -137,21 +148,12 @@ class AppWindowLiveProcessingMixin:
return
self._active_processing_mode = mode
mode_to_page = {
"pass_through": 0,
"bscan": 1,
"gpr": 2,
}
self._set_plot_mode(mode)
self._processing_mode_pages.setCurrentIndex(mode_to_page.get(mode, 0))
current_page = self._processing_mode_pages.currentWidget()
if current_page is not None:
self._processing_mode_pages.setFixedHeight(current_page.sizeHint().height())
self._processing_mode_pages.updateGeometry()
self._set_processing_mode_page(mode)
self._on_processing_live_settings_changed()
if mode == "gpr":
self._publish_locator_snapshot_from_latest_result()
elif previous_mode == "gpr":
elif previous_mode == "gpr" and self._locator_service is not None:
self._locator_service.publish_empty()
if mode == "pass_through":
self._log(
@@ -178,14 +180,13 @@ class AppWindowLiveProcessingMixin:
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"speed={self._gpr_speed_m_s.value():g} m/s, "
f"look_angle={self._gpr_look_angle_deg.value():g} deg, "
f"snr_thresh={self._gpr_snr_thresh.value():g}, "
f"snr_comp_max={self._gpr_snr_comp_max.value():g}, "
f"range_comp={self._gpr_range_comp_power.value():g}, "
f"angle_comp={self._gpr_angle_comp_power.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()}, "
f"render_mode={self._gpr_render_mode.currentText()}, "
f"min_pairs={self._gpr_min_visible_pair_count.value()})"
f"min_score={self._gpr_min_visible_score.value():g})"
)
def _clear_history_mode_caches(self) -> None:
@@ -194,7 +194,6 @@ class AppWindowConfigProfileIOMixin:
self._bscan_start_freq_mhz,
self._bscan_stop_freq_mhz,
self._bscan_subtract_mean_ascan,
self._gpr_config_mode,
self._gpr_relative_permittivity,
self._gpr_tx_geometry_input,
self._gpr_rx_geometry_input,
@@ -202,17 +201,15 @@ class AppWindowConfigProfileIOMixin:
self._gpr_output_positions_input,
self._gpr_min_depth_m,
self._gpr_max_depth_m,
self._gpr_comp_power,
self._gpr_range_comp_power,
self._gpr_angle_comp_power,
self._gpr_start_freq_mhz,
self._gpr_stop_freq_mhz,
self._gpr_speed_m_s,
self._gpr_look_angle_deg,
self._gpr_snr_thresh,
self._gpr_snr_comp_max,
self._gpr_background_subtract_enabled,
self._gpr_background_mean_count,
self._gpr_remove_sidelobe_objects_enabled,
self._gpr_render_mode,
self._gpr_min_visible_pair_count,
self._gpr_min_visible_score,
self._gpr_visible_x_min_m,
self._gpr_visible_x_max_m,
self._gpr_visible_z_min_m,
@@ -253,7 +250,6 @@ class AppWindowConfigProfileIOMixin:
self._bscan_stop_freq_mhz.setValue(float(gui_state.processing.bscan.stop_freq_mhz))
self._bscan_subtract_mean_ascan.setChecked(bool(gui_state.processing.bscan.subtract_mean_ascan))
self._set_combo_current_text(self._gpr_config_mode, str(config.gpr.mode))
self._gpr_relative_permittivity.setValue(float(config.gpr.relative_permittivity))
self._gpr_tx_geometry_input.setPlainText(
"\n".join(
@@ -271,19 +267,19 @@ class AppWindowConfigProfileIOMixin:
self._gpr_output_positions_input.setText(str(gui_state.processing.gpr.output_positions))
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_comp_power.setValue(float(gui_state.processing.gpr.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_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_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_background_subtract_enabled.setChecked(
bool(gui_state.processing.gpr.background_subtract_enabled)
)
self._gpr_background_mean_count.setValue(int(gui_state.processing.gpr.background_mean_count))
self._gpr_remove_sidelobe_objects_enabled.setChecked(
bool(gui_state.processing.gpr.remove_sidelobe_objects_enabled)
)
self._set_combo_current_text(self._gpr_render_mode, gui_state.processing.gpr.render_mode)
self._gpr_min_visible_pair_count.setValue(int(gui_state.processing.gpr.min_visible_pair_count))
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))
@@ -169,17 +169,15 @@ class AppWindowConfigStateBuildersMixin:
output_positions=self._default_gpr_output_positions_from_config(config),
min_depth_m=2.0,
max_depth_m=14.0,
comp_power=0.2,
range_comp_power=0.28,
angle_comp_power=0.10,
start_freq_mhz=3000.0,
stop_freq_mhz=6000.0,
speed_m_s=0.0,
look_angle_deg=0.0,
snr_thresh=4.5,
snr_comp_max=25.0,
background_subtract_enabled=True,
background_mean_count=10,
remove_sidelobe_objects_enabled=True,
render_mode="heatmap",
min_visible_pair_count=1,
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,
@@ -258,17 +256,15 @@ class AppWindowConfigStateBuildersMixin:
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()),
comp_power=float(self._gpr_comp_power.value()),
range_comp_power=float(self._gpr_range_comp_power.value()),
angle_comp_power=float(self._gpr_angle_comp_power.value()),
start_freq_mhz=float(self._gpr_start_freq_mhz.value()),
stop_freq_mhz=float(self._gpr_stop_freq_mhz.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()),
background_subtract_enabled=bool(self._gpr_background_subtract_enabled.isChecked()),
background_mean_count=int(self._gpr_background_mean_count.value()),
remove_sidelobe_objects_enabled=bool(self._gpr_remove_sidelobe_objects_enabled.isChecked()),
render_mode=self._gpr_render_mode.currentText(),
min_visible_pair_count=int(self._gpr_min_visible_pair_count.value()),
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()),
@@ -323,14 +319,13 @@ class AppWindowConfigStateBuildersMixin:
combo_text = self._combos_text.text()
config.combos = parse_combos_from_text(combo_text)
config.ensure_combos()
if self._switches_are_effectively_static(config):
if self._processing_mode.currentText() != "gpr" and self._switches_are_effectively_static(config):
config.combos = [ComboModel(input=0, output=0)]
for key in PREPROCESS_ASSET_KEYS:
preprocess_asset_model(config, key).bundle_path = ""
for key in VISIBLE_PREPROCESS_ASSET_KEYS:
preprocess_asset_model(config, key).set_name = self._selected_preprocess_sets.get(key, "")
config.gpr.mode = self._gpr_config_mode.currentText()
config.gpr.relative_permittivity = float(self._gpr_relative_permittivity.value())
config.gpr.tx_geometry = self._parse_gpr_tx_geometry_text(self._gpr_tx_geometry_input.toPlainText())
config.gpr.rx_geometry = self._parse_gpr_rx_geometry_text(self._gpr_rx_geometry_input.toPlainText())
@@ -378,7 +373,7 @@ class AppWindowConfigStateBuildersMixin:
@staticmethod
def _switches_are_effectively_static(config: RunConfigModel) -> bool:
"""Return `True` when switch setup effectively yields one fixed combo."""
"""Return `True` when non-GPR switch setup effectively yields one fixed combo."""
if config.is_multi_device:
return False
has_single_position = config.input_switch.positions <= 1 and config.output_switch.positions <= 1