little refactoring done
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
"""Main GUI window composed from focused mixins."""
|
||||
"""Main GUI composition root.
|
||||
|
||||
This module wires UI/controller mixins together and owns application-level
|
||||
state shared across them (runtime services, readers, history buffers, timer).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -35,28 +39,50 @@ class AppWindow(
|
||||
AppWindowSnapshotMixin,
|
||||
QMainWindow,
|
||||
):
|
||||
"""Top-level application window coordinating UI and acquisition runtime."""
|
||||
"""Top-level window coordinating GUI state and acquisition runtime."""
|
||||
|
||||
def __init__(self, project_root: Path) -> None:
|
||||
"""Initialize application state, services, UI, and polling timer."""
|
||||
"""Initialize all app subsystems in deterministic order."""
|
||||
super().__init__()
|
||||
|
||||
self._init_paths_and_defaults(project_root)
|
||||
self._init_runtime_services()
|
||||
self._init_reader_handles()
|
||||
self._init_preprocess_state()
|
||||
self._init_capture_state()
|
||||
self._init_history_state()
|
||||
self._init_runtime_limits()
|
||||
self._init_polling_timer()
|
||||
self._bootstrap_ui_runtime()
|
||||
|
||||
def _init_paths_and_defaults(self, project_root: Path) -> None:
|
||||
"""Initialize project paths and baseline run configuration."""
|
||||
self._project_root = project_root
|
||||
self._defaults_config_path = project_root / "run_config.json"
|
||||
self._defaults_config = RunConfigModel.load_from_path(self._defaults_config_path)
|
||||
|
||||
self._store = NpzStore(project_root / "python_app/data")
|
||||
self._config_writer = ConfigWriter(project_root / "python_app/runtime")
|
||||
self._supervisor = ProcessSupervisor(project_root)
|
||||
self._live_config_writer = ProcessingLiveConfigWriter(project_root / "python_app/runtime/processing_live.json")
|
||||
def _init_runtime_services(self) -> None:
|
||||
"""Initialize long-lived service objects used by mixins."""
|
||||
runtime_dir = self._project_root / "python_app/runtime"
|
||||
self._store = NpzStore(self._project_root / "python_app/data")
|
||||
self._config_writer = ConfigWriter(runtime_dir)
|
||||
self._supervisor = ProcessSupervisor(self._project_root)
|
||||
self._live_config_writer = ProcessingLiveConfigWriter(runtime_dir / "processing_live.json")
|
||||
|
||||
def _init_reader_handles(self) -> None:
|
||||
"""Initialize SHM readers as detached (not connected) handles."""
|
||||
self._raw_reader: ShmRingReader | None = None
|
||||
self._pre_reader: ShmRingReader | None = None
|
||||
self._result_reader: ShmRingReader | None = None
|
||||
|
||||
def _init_preprocess_state(self) -> None:
|
||||
"""Initialize preprocessing dialog and selected set names."""
|
||||
self._preprocess_dialog: PreprocessDialog | None = None
|
||||
self._selected_calibration_set = str(self._defaults_config.preprocess.calibration_set)
|
||||
self._selected_reference_set = str(self._defaults_config.preprocess.reference_set)
|
||||
|
||||
def _init_capture_state(self) -> None:
|
||||
"""Initialize one-shot capture and sequence-control flags."""
|
||||
self._capture_session: SequentialCaptureSession | None = None
|
||||
self._resume_pipeline_after_capture = False
|
||||
self._single_capture_active = False
|
||||
@@ -64,19 +90,16 @@ class AppWindow(
|
||||
self._single_capture_seen_raw = False
|
||||
self._single_capture_target_collection_id: int | None = None
|
||||
|
||||
self._raw_history: deque[SweepCollection] = deque(maxlen=512)
|
||||
self._pre_history: deque[SweepCollection] = deque(maxlen=512)
|
||||
result_history_limit = max(
|
||||
1,
|
||||
min(
|
||||
int(self._defaults_config.rings.preprocessed.capacity),
|
||||
int(self._defaults_config.rings.results.capacity),
|
||||
50,
|
||||
),
|
||||
)
|
||||
self._result_history: deque[ResultCollection] = deque(maxlen=result_history_limit)
|
||||
def _init_history_state(self) -> None:
|
||||
"""Initialize runtime history buffers and render-cache state."""
|
||||
history_limit = self._history_limit_from_config()
|
||||
self._raw_history: deque[SweepCollection] = deque(maxlen=history_limit)
|
||||
self._pre_history: deque[SweepCollection] = deque(maxlen=history_limit)
|
||||
self._result_history: deque[ResultCollection] = deque(maxlen=history_limit)
|
||||
|
||||
# Sequence id must survive GUI restarts so history commands stay monotonic.
|
||||
self._history_command_seq = self._load_history_command_seq(self._live_config_writer.path)
|
||||
self._bscan_history_limit = result_history_limit
|
||||
self._bscan_history_limit = history_limit
|
||||
self._bscan_history_by_combo = {}
|
||||
self._bscan_depth_axis_by_combo = {}
|
||||
self._bscan_history_floor_collection_id = 0
|
||||
@@ -85,22 +108,43 @@ class AppWindow(
|
||||
self._history_run_signature = None
|
||||
self._radar_limits: dict[str, float | int] | None = None
|
||||
|
||||
def _history_limit_from_config(self) -> int:
|
||||
"""Return unified GUI history limit derived from configured ring capacities."""
|
||||
return max(
|
||||
1,
|
||||
min(
|
||||
int(self._defaults_config.rings.raw_tap.capacity),
|
||||
int(self._defaults_config.rings.preprocessed_tap.capacity),
|
||||
int(self._defaults_config.rings.results.capacity),
|
||||
),
|
||||
)
|
||||
|
||||
def _init_runtime_limits(self) -> None:
|
||||
"""Initialize read/drain loop limits used by polling and snapshot code."""
|
||||
self._max_pop_per_poll = 256
|
||||
self._max_pop_per_snapshot_drain = 4096
|
||||
|
||||
def _init_polling_timer(self) -> None:
|
||||
"""Create periodic timer that polls SHM rings for new data."""
|
||||
self._timer = QTimer(self)
|
||||
self._timer.setInterval(50)
|
||||
self._timer.timeout.connect(self._poll_rings)
|
||||
|
||||
def _bootstrap_ui_runtime(self) -> None:
|
||||
"""Build UI and apply initial runtime-bound state after widgets exist."""
|
||||
self._build_ui()
|
||||
self._refresh_preprocess_summary_labels()
|
||||
if self._radar_mode.currentText() == "native":
|
||||
self._refresh_radar_limits_from_device()
|
||||
else:
|
||||
self._apply_radar_limits_to_ui(None)
|
||||
self._apply_initial_radar_limits()
|
||||
self._write_live_processing_config()
|
||||
self._timer.start()
|
||||
|
||||
def _apply_initial_radar_limits(self) -> None:
|
||||
"""Apply startup radar-limits strategy according to selected radar mode."""
|
||||
if self._radar_mode.currentText() == "native":
|
||||
self._refresh_radar_limits_from_device()
|
||||
return
|
||||
self._apply_radar_limits_to_ui(None)
|
||||
|
||||
def _log(self, text: str) -> None:
|
||||
"""Append a line to the runtime log panel."""
|
||||
self._log_box.appendPlainText(text)
|
||||
@@ -111,6 +155,7 @@ class AppWindow(
|
||||
try:
|
||||
payload = json.loads(config_path.read_text(encoding="utf-8"))
|
||||
except Exception: # noqa: BLE001
|
||||
# Missing or malformed file should not block startup.
|
||||
return 0
|
||||
|
||||
raw_value = payload.get("history_command_seq", 0)
|
||||
@@ -129,8 +174,11 @@ class AppWindow(
|
||||
"""Ensure workers and dialogs are closed before window destruction."""
|
||||
try:
|
||||
self._resume_pipeline_after_capture = False
|
||||
# 1) Abort active capture first (releases exclusive hardware resources).
|
||||
self._abort_capture_sequence(resume_pipeline=False)
|
||||
# 2) Stop all managed processes/readers.
|
||||
self._stop_all_processes()
|
||||
# 3) Close auxiliary dialog windows.
|
||||
if self._preprocess_dialog is not None:
|
||||
self._preprocess_dialog.close()
|
||||
finally:
|
||||
|
||||
Reference in New Issue
Block a user