Add object calibration
This commit is contained in:
@@ -17,6 +17,7 @@ from PyQt6.QtWidgets import (
|
||||
|
||||
from python_app.gui.runtime.constraints import validate_processing_mode_constraints
|
||||
from python_app.gui.antenna_geometry_editor import AntennaGeometryEditor
|
||||
from python_app.models.run_config_model import GprRxGeometryModel, GprTxGeometryModel
|
||||
from python_app.orchestration.live_processing_config import ProcessingLiveConfig
|
||||
|
||||
_GPR_MODES = ("gpr", "legacy_gpr")
|
||||
@@ -320,6 +321,10 @@ class AppWindowLiveProcessingMixin:
|
||||
reprocess_current_result=not self._can_reprocess_history(),
|
||||
)
|
||||
)
|
||||
active = getattr(self, "_active_run_config", None)
|
||||
if active is not None and self._supervisor.is_running():
|
||||
active.gpr.tx_geometry = [GprTxGeometryModel(**entry) for entry in self._gpr_tx_geometry_input.records()]
|
||||
active.gpr.rx_geometry = [GprRxGeometryModel(**entry) for entry in self._gpr_rx_geometry_input.records()]
|
||||
if getattr(self, "_await_processing_revision", False):
|
||||
self._required_processing_revision = self._processing_settings_revision
|
||||
|
||||
@@ -379,6 +384,8 @@ class AppWindowLiveProcessingMixin:
|
||||
finally:
|
||||
self._suppress_live_settings_handler = False
|
||||
self._sync_socket_speed_controls()
|
||||
if {"tx_geometry", "rx_geometry"} & settings.keys():
|
||||
self._invalidate_object_calibration_after_geometry_change()
|
||||
if {"tx_geometry", "rx_geometry", "relative_permittivity"} & settings.keys():
|
||||
self._await_processing_revision = True
|
||||
if "processor_mode" in settings:
|
||||
@@ -395,9 +402,49 @@ class AppWindowLiveProcessingMixin:
|
||||
def _on_gpr_geometry_changed(self) -> None:
|
||||
if getattr(self, "_suppress_live_settings_handler", False):
|
||||
return
|
||||
self._invalidate_object_calibration_after_geometry_change()
|
||||
self._await_processing_revision = True
|
||||
self._on_processing_live_settings_changed()
|
||||
|
||||
def _invalidate_object_calibration_after_geometry_change(self) -> None:
|
||||
"""Disable a correction tied to another antenna layout and reload the preprocessor."""
|
||||
selected = self._selected_preprocess_sets.get("s21_object_calibration", "")
|
||||
was_running = self._supervisor.is_running()
|
||||
active_config = getattr(self, "_active_run_config", None)
|
||||
active = active_config.preprocess.s21.object_calibration.set_name if was_running and active_config else ""
|
||||
if not selected and not active:
|
||||
return
|
||||
from python_app.workflows.mimo_calibration import validate_for_config
|
||||
|
||||
geometry = {"gpr": {
|
||||
"tx_geometry": self._gpr_tx_geometry_input.records(),
|
||||
"rx_geometry": self._gpr_rx_geometry_input.records(),
|
||||
}}
|
||||
selections = [(selected, self._selected_preprocess_radar_key)]
|
||||
if active:
|
||||
selections.append((active, self._radar_key(active_config)))
|
||||
invalid = set()
|
||||
for name, radar_key in dict.fromkeys(selections):
|
||||
if not name:
|
||||
continue
|
||||
try:
|
||||
validate_for_config(self._store.load_object_calibration(radar_key, name), geometry)
|
||||
except Exception as exc: # noqa: BLE001 - reject unavailable or incompatible saved corrections
|
||||
invalid.add(name)
|
||||
self._log_warning(f"Object calibration {name} disabled for changed antenna geometry: {exc}")
|
||||
if not invalid:
|
||||
return
|
||||
resume_seq = getattr(self, "_stop_request_seq", 0) + 1
|
||||
if active in invalid:
|
||||
self._stop_run()
|
||||
if selected in invalid:
|
||||
self._selected_preprocess_sets["s21_object_calibration"] = ""
|
||||
self._refresh_preprocess_summary_labels()
|
||||
if self._preprocess_dialog is not None:
|
||||
self._preprocess_dialog.set_selected_sets(self._selected_preprocess_sets, emit_signal=False)
|
||||
if active in invalid and self._pipeline_resume_allowed(resume_seq):
|
||||
self._start_run()
|
||||
|
||||
def _on_processing_live_settings_changed(self, *_args) -> None:
|
||||
"""Handle live-processing setting changes and trigger redraw when needed."""
|
||||
# Web-apply sets many widgets at once; their individual change signals are
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from collections import deque
|
||||
from contextlib import ExitStack
|
||||
from copy import deepcopy
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
@@ -23,12 +24,23 @@ from python_app.orchestration.preprocess_assets import (
|
||||
class AppWindowConfigProfileIOMixin:
|
||||
"""Persist and restore full GUI profiles without restarting the app."""
|
||||
|
||||
def _write_gui_profile_to_path(self, output_path: Path, *, allow_overwrite: bool = True) -> GuiProfileModel:
|
||||
"""Serialize current full GUI profile to `output_path` and return the persisted model."""
|
||||
def _write_gui_profile_to_path(
|
||||
self, output_path: Path, *, allow_overwrite: bool = True, dataset: bool = False,
|
||||
) -> GuiProfileModel:
|
||||
"""Save UI settings, preserving the acquisition provenance for recorded data."""
|
||||
if not allow_overwrite and output_path.exists():
|
||||
raise FileExistsError(f"Config profile output already exists: {output_path}")
|
||||
|
||||
profile = self._build_gui_profile()
|
||||
active = getattr(self, "_active_run_config", None)
|
||||
if dataset and active is not None:
|
||||
profile.run_config.radar = deepcopy(active.radar)
|
||||
profile.run_config.input_switch = deepcopy(active.input_switch)
|
||||
profile.run_config.output_switch = deepcopy(active.output_switch)
|
||||
profile.run_config.combos = deepcopy(active.combos)
|
||||
profile.run_config.preprocess = deepcopy(active.preprocess)
|
||||
profile.run_config.gpr.tx_geometry = deepcopy(active.gpr.tx_geometry)
|
||||
profile.run_config.gpr.rx_geometry = deepcopy(active.gpr.rx_geometry)
|
||||
atomic_write_json(output_path, profile.to_dict(), durable=True)
|
||||
return profile
|
||||
|
||||
@@ -206,6 +218,9 @@ class AppWindowConfigProfileIOMixin:
|
||||
capture sequence or the pipeline is active — the operator must stop first.
|
||||
Shared by the desktop dialog and the web loader so both honor the same rule.
|
||||
"""
|
||||
if getattr(self, "_object_capture_job", None) is not None:
|
||||
self._show_error("Wait for object-calibration capture to finish")
|
||||
return False
|
||||
if self._capture_session is not None:
|
||||
self._show_error(
|
||||
"Cannot load config during active capture sequence",
|
||||
|
||||
Reference in New Issue
Block a user