new GPR parameters and some UI fixes

This commit is contained in:
Ayzen
2026-04-07 12:55:26 +03:00
parent 4b78c2808d
commit 202993325d
27 changed files with 1494 additions and 168 deletions
@@ -11,6 +11,7 @@ from PyQt6.QtCore import QSignalBlocker
from PyQt6.QtWidgets import QFileDialog
from python_app.hardware_full.librevna_service import LibreVnaService
from python_app.gui.runtime.constraints import validate_processing_mode_constraints
from python_app.models.gui_profile_model import (
GuiBscanStateModel,
GuiDataActionsStateModel,
@@ -37,6 +38,14 @@ from python_app.storage.npz_store import radar_key_from_config
class AppWindowConfigMixin:
"""Builds runtime config models from current UI state."""
def _validate_processing_mode_selection(self, mode: str) -> None:
"""Validate requested processing mode against current stable/live GUI state."""
validate_processing_mode_constraints(
mode,
self._build_config(),
self._live_processing_config(),
)
@staticmethod
def _parse_csv_int_list(text: str) -> list[int]:
"""Parse comma-separated integer selection list."""
@@ -117,6 +126,16 @@ class AppWindowConfigMixin:
values = sorted(geometry_values & combo_values) or sorted(geometry_values)
return ",".join(str(value) for value in values)
@staticmethod
def _default_gpr_visible_x_bounds_from_config(config: RunConfigModel) -> tuple[float, float]:
"""Build default visible X-range for GPR object-only rendering."""
x_values = [float(entry.x_m) for entry in config.gpr.tx_geometry]
x_values.extend(float(entry.x_m) for entry in config.gpr.rx_geometry)
if not x_values:
return (-2.0, 2.0)
margin_m = 2.0
return (min(x_values) - margin_m, max(x_values) + margin_m)
@staticmethod
def _history_limit_for_config(config: RunConfigModel) -> int:
"""Return unified GUI history limit derived from config ring capacities."""
@@ -133,6 +152,7 @@ class AppWindowConfigMixin:
"""Build fallback GUI-only defaults for a stable run config."""
default_combo = config.combos[0] if config.combos else ComboModel(input=0, output=0)
default_mode = "single" if len(config.combos) == 1 else "text"
default_gpr_x_min_m, default_gpr_x_max_m = self._default_gpr_visible_x_bounds_from_config(config)
return GuiStateModel(
switches=GuiSwitchStateModel(
combo_mode=default_mode,
@@ -167,8 +187,16 @@ class AppWindowConfigMixin:
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,
render_mode="heatmap",
min_visible_pair_count=1,
visible_x_min_m=default_gpr_x_min_m,
visible_x_max_m=default_gpr_x_max_m,
visible_z_min_m=0.0,
visible_z_max_m=14.0,
),
),
data_actions=GuiDataActionsStateModel(
@@ -221,8 +249,16 @@ class AppWindowConfigMixin:
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()),
render_mode=self._gpr_render_mode.currentText(),
min_visible_pair_count=int(self._gpr_min_visible_pair_count.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()),
visible_z_max_m=float(self._gpr_visible_z_max_m.value()),
),
),
data_actions=GuiDataActionsStateModel(
@@ -404,8 +440,16 @@ class AppWindowConfigMixin:
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_render_mode,
self._gpr_min_visible_pair_count,
self._gpr_visible_x_min_m,
self._gpr_visible_x_max_m,
self._gpr_visible_z_min_m,
self._gpr_visible_z_max_m,
self._save_count,
self._save_path_input,
self._save_name_input,
@@ -466,10 +510,18 @@ class AppWindowConfigMixin:
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._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_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))
self._gpr_visible_z_max_m.setValue(float(gui_state.processing.gpr.visible_z_max_m))
self._save_count.setValue(int(gui_state.data_actions.save_count))
self._save_path_input.setText(str(gui_state.data_actions.save_path))
@@ -478,6 +530,7 @@ class AppWindowConfigMixin:
self._defaults_config = config
self._gui_defaults = gui_state
self._selected_preprocess_sets = selected_preprocess_sets
self._selected_preprocess_radar_key = self._radar_key(config)
self._preprocess_set_name = str(gui_state.preprocess_dialog.set_name)
self._apply_history_limit_from_config(config)
self._gpr_geometry_signature = None
@@ -552,6 +605,18 @@ class AppWindowConfigMixin:
power_dbm=config.radar.sweep.power_dbm,
)
def _radar_key_from_ui(self) -> str:
"""Build current radar key directly from radar widgets only."""
return radar_key_from_config(
model_name=self._defaults_config.radar.model,
serial=self._serial_input.text().strip(),
sweep_start_hz=float(self._start_hz_input.text().strip()),
sweep_stop_hz=float(self._stop_hz_input.text().strip()),
sweep_points=int(self._points_input.text().strip()),
ifbw_hz=float(self._ifbw_input.text().strip()),
power_dbm=float(self._power_input.text().strip()),
)
def _live_processing_config(self, *, history_command: str = "none") -> ProcessingLiveConfig:
"""Build live processing config from current processing widgets."""
self._sync_bscan_frequency_limits_with_radar()
@@ -580,6 +645,8 @@ class AppWindowConfigMixin:
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()),
history_command_seq=int(self._history_command_seq),
@@ -615,8 +682,33 @@ class AppWindowConfigMixin:
except Exception as exc: # noqa: BLE001
self._show_exception("Failed to update live processing settings", exc)
def _on_gpr_visual_settings_changed(self, *_args) -> None:
"""Redraw current GPR result using updated GUI-only render settings."""
if self._processing_mode.currentText() != "gpr":
return
try:
if self._result_history and self._draw_results(self._result_history[-1]):
return
self._clear_gpr_plot()
except Exception as exc: # noqa: BLE001
self._show_exception("Failed to update GPR render settings", exc)
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")
if mode != previous_mode:
try:
self._validate_processing_mode_selection(mode)
except Exception as exc: # noqa: BLE001
with QSignalBlocker(self._processing_mode):
self._set_combo_current_text(self._processing_mode, previous_mode)
self._show_error(
f"Cannot switch processing mode to {mode}",
details=str(exc) or type(exc).__name__,
)
return
self._active_processing_mode = mode
mode_to_page = {
"pass_through": 0,
"bscan": 1,
@@ -655,8 +747,12 @@ class AppWindowConfigMixin:
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"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"render_mode={self._gpr_render_mode.currentText()}, "
f"min_pairs={self._gpr_min_visible_pair_count.value()})"
)
def _clear_history_mode_caches(self) -> None:
@@ -665,6 +761,7 @@ class AppWindowConfigMixin:
self._clear_bscan_plot_history()
if hasattr(self, "_bscan_plot"):
self._bscan_plot.clear()
self._configure_bscan_plot_axes()
self._clear_trace_plots()
if hasattr(self, "_gpr_plot"):
self._clear_gpr_plot()
@@ -676,6 +773,7 @@ class AppWindowConfigMixin:
self._sync_bscan_history_from_results()
if not self._draw_bscan_heatmap_from_history():
self._bscan_plot.clear()
self._configure_bscan_plot_axes()
return
if self._processing_mode.currentText() == "gpr":
if self._result_history and self._draw_results(self._result_history[-1]):
@@ -686,10 +784,12 @@ class AppWindowConfigMixin:
self._draw_results(self._result_history[-1])
return
self._bscan_plot.clear()
self._configure_bscan_plot_axes()
self._clear_trace_plots()
def _on_radar_identity_changed(self, *_args) -> None:
"""Refresh device limits when radar identity/mode changes."""
self._reset_preprocess_selection_after_radar_key_change()
if self._radar_mode.currentText() != "native":
self._apply_radar_limits_to_ui(None)
return
@@ -699,6 +799,7 @@ class AppWindowConfigMixin:
def _on_radar_sweep_limits_changed(self) -> None:
"""Clamp processing frequency bounds after sweep start/stop edits."""
self._reset_preprocess_selection_after_radar_key_change()
if self._sync_processing_frequency_limits_with_radar():
self._on_processing_live_settings_changed()