init commit

This commit is contained in:
Ayzen
2026-03-05 14:42:33 +03:00
commit fd4618b20d
964 changed files with 325114 additions and 0 deletions
+137
View File
@@ -0,0 +1,137 @@
"""Main GUI window composed from focused mixins."""
from __future__ import annotations
from collections import deque
import json
from pathlib import Path
from PyQt6.QtCore import QTimer
from PyQt6.QtWidgets import QMainWindow, QMessageBox
from python_app.gui.controllers.app_window_config_mixin import AppWindowConfigMixin
from python_app.gui.controllers.app_window_pipeline_mixin import AppWindowPipelineMixin
from python_app.gui.controllers.app_window_plot_mixin import AppWindowPlotMixin
from python_app.gui.controllers.app_window_preprocess_mixin import AppWindowPreprocessMixin
from python_app.gui.controllers.app_window_snapshot_mixin import AppWindowSnapshotMixin
from python_app.gui.controllers.app_window_ui_mixin import AppWindowUiMixin
from python_app.gui.preprocess_dialog import PreprocessDialog
from python_app.models.dataset_model import ResultCollection, SweepCollection
from python_app.models.run_config_model import RunConfigModel
from python_app.orchestration.config_writer import ConfigWriter
from python_app.orchestration.live_processing_config import ProcessingLiveConfigWriter
from python_app.orchestration.process_supervisor import ProcessSupervisor
from python_app.orchestration.shm_reader import ShmRingReader
from python_app.storage.npz_store import NpzStore
from python_app.workflows.sequential_capture_workflow import SequentialCaptureSession
class AppWindow(
AppWindowUiMixin,
AppWindowConfigMixin,
AppWindowPreprocessMixin,
AppWindowPlotMixin,
AppWindowPipelineMixin,
AppWindowSnapshotMixin,
QMainWindow,
):
"""Top-level application window coordinating UI and acquisition runtime."""
def __init__(self, project_root: Path) -> None:
"""Initialize application state, services, UI, and polling timer."""
super().__init__()
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")
self._raw_reader: ShmRingReader | None = None
self._pre_reader: ShmRingReader | None = None
self._result_reader: ShmRingReader | None = None
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)
self._capture_session: SequentialCaptureSession | None = None
self._resume_pipeline_after_capture = False
self._single_capture_active = False
self._single_capture_start_ns: int | None = None
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)
self._history_command_seq = self._load_history_command_seq(self._live_config_writer.path)
self._bscan_history_limit = result_history_limit
self._bscan_history_by_combo = {}
self._bscan_depth_axis_by_combo = {}
self._bscan_history_floor_collection_id = 0
self._bscan_render_signature = None
self._phase_viewbox = None
self._history_run_signature = None
self._radar_limits: dict[str, float | int] | None = None
self._max_pop_per_poll = 256
self._max_pop_per_snapshot_drain = 4096
self._timer = QTimer(self)
self._timer.setInterval(50)
self._timer.timeout.connect(self._poll_rings)
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._write_live_processing_config()
self._timer.start()
def _log(self, text: str) -> None:
"""Append a line to the runtime log panel."""
self._log_box.appendPlainText(text)
@staticmethod
def _load_history_command_seq(config_path: Path) -> int:
"""Load previously used live-command sequence from runtime config file."""
try:
payload = json.loads(config_path.read_text(encoding="utf-8"))
except Exception: # noqa: BLE001
return 0
raw_value = payload.get("history_command_seq", 0)
if isinstance(raw_value, bool):
return 0
if isinstance(raw_value, (int, float)):
return max(0, int(raw_value))
return 0
def _show_error(self, message: str) -> None:
"""Log and present an error in a modal dialog."""
self._log(f"ERROR: {message}")
QMessageBox.critical(self, "Error", message)
def closeEvent(self, event) -> None: # noqa: N802
"""Ensure workers and dialogs are closed before window destruction."""
try:
self._resume_pipeline_after_capture = False
self._abort_capture_sequence(resume_pipeline=False)
self._stop_all_processes()
if self._preprocess_dialog is not None:
self._preprocess_dialog.close()
finally:
super().closeEvent(event)