Refactor radar system

This commit is contained in:
Ayzen
2026-10-05 16:46:28 +03:00
parent 515049d2ed
commit 8f097dfa2e
179 changed files with 8212 additions and 6676 deletions
@@ -6,7 +6,8 @@ from pathlib import Path
import time
from PyQt6.QtWidgets import QFileDialog
from python_app.gui.runtime.history import record_result_history, remove_last_aligned_histories
from python_app.gui.controllers.app_window_config.state_builders import GUI_SAVE_HISTORY_LIMIT
from python_app.gui.runtime.history import remove_last_aligned_histories
from python_app.storage.npz.paths import radar_config_filename_prefix
@@ -39,8 +40,10 @@ class AppWindowSnapshotMixin:
return output_dir / "config_profile.json"
def _save_snapshot(self) -> None:
"""Save the last-N runtime measurements as a numpy snapshot (the "Save Dataset" button)."""
"""Save up to 1000 recent runtime measurements as a numpy snapshot."""
self._drain_runtime_rings_for_snapshot()
if getattr(self, "_closing", False):
return
if not self._raw_history and not self._pre_history and not self._result_history:
self._show_error("No runtime data is available for save", details=self._runtime_history_details())
@@ -50,7 +53,8 @@ class AppWindowSnapshotMixin:
list(self._raw_history),
list(self._pre_history),
list(self._result_history),
int(self._save_count.value()),
GUI_SAVE_HISTORY_LIMIT,
include_unaligned=True,
)
def _snapshot_destination_dir(self) -> Path:
@@ -72,10 +76,12 @@ class AppWindowSnapshotMixin:
preprocessed_history: list,
result_history: list,
last_n: int,
*,
include_unaligned: bool = False,
) -> None:
"""Save the given aligned histories as a numpy snapshot + adjacent config profile.
"""Save the given histories as a numpy snapshot + adjacent config profile.
Shared by the manual "Save Dataset" button (which passes the runtime history)
Shared by the manual "Save Last 1000" button (which passes the runtime history)
and the on-the-fly disk recorder (which passes the freshly recorded buffers),
so both produce byte-identical dataset layouts and identical error reporting.
"""
@@ -90,6 +96,7 @@ class AppWindowSnapshotMixin:
result_history,
last_n,
name_prefix=self._radar_config_name_prefix(),
include_unaligned=include_unaligned,
)
config_profile_path = self._snapshot_config_profile_path(snapshot_dir)
try:
@@ -124,13 +131,15 @@ class AppWindowSnapshotMixin:
def _save_vna_history_json(self) -> None:
"""Save one VNA-history JSON per available combo in runtime history."""
self._drain_runtime_rings_for_snapshot()
if getattr(self, "_closing", False):
return
if not self._raw_history and not self._pre_history and not self._result_history:
self._show_error("No runtime data is available for save", details=self._runtime_history_details())
return
try:
last_n = int(self._save_count.value())
last_n = GUI_SAVE_HISTORY_LIMIT
channel = "s21"
output_root = Path(self._save_path_input.text().strip()).expanduser()
output_name = self._save_name_input.text().strip()
@@ -144,6 +153,7 @@ class AppWindowSnapshotMixin:
channel=channel,
primary_stage="preprocessed",
name_prefix=self._radar_config_name_prefix(),
include_unaligned=True,
)
output_stem = str(summary.get("output_stem", "")).strip()
if not output_stem:
@@ -210,6 +220,7 @@ class AppWindowSnapshotMixin:
return
resume_acquisition = self._supervisor.is_running()
resume_seq = getattr(self, "_stop_request_seq", 0) + 1
dropped_raw = 0
dropped_pre = 0
dropped_results = 0
@@ -220,6 +231,8 @@ class AppWindowSnapshotMixin:
try:
if resume_acquisition:
self._stop_run()
if not self._pipeline_resume_allowed(resume_seq):
return
dropped_raw = self._raw_reader.drop_all() if self._raw_reader is not None else 0
dropped_pre = self._pre_reader.drop_all() if self._pre_reader is not None else 0
@@ -260,7 +273,7 @@ class AppWindowSnapshotMixin:
f"results={dropped_results}"
)
if resume_acquisition:
if resume_acquisition and self._pipeline_resume_allowed(resume_seq):
self._start_run()
except Exception as exc: # noqa: BLE001
self._show_exception(f"Failed to {error_action}", exc)
@@ -280,64 +293,28 @@ class AppWindowSnapshotMixin:
if self._raw_reader is None and self._pre_reader is None and self._result_reader is None:
return
try:
start_raw_count = len(self._raw_history)
start_pre_count = len(self._pre_history)
start_result_count = len(self._result_history)
readers = (self._raw_reader, self._pre_reader, self._result_reader)
observed = [False, False, False]
deadline = time.monotonic() + 1.2
while True:
progress = False
while time.monotonic() < deadline and not getattr(self, "_closing", False):
limit = self._max_pop_per_snapshot_drain
raw = self._read_all_raw(max_collections=limit) if self._raw_reader is not None else None
preprocessed = self._read_all_preprocessed(max_collections=limit)
result = self._read_all_results(max_collections=limit) if self._result_reader is not None else None
latest = (raw, preprocessed, result)
observed = [seen or collection is not None for seen, collection in zip(observed, latest)]
if self._raw_reader is not None:
for _ in range(self._max_pop_per_snapshot_drain):
collection = self._raw_reader.pop_raw_collection()
if collection is None:
break
self._raw_history.append(collection)
progress = True
if self._pre_reader is not None:
for _ in range(self._max_pop_per_snapshot_drain):
collection = self._pre_reader.pop_preprocessed_collection()
if collection is None:
break
self._pre_history.append(collection)
progress = True
if self._result_reader is not None:
for _ in range(self._max_pop_per_snapshot_drain):
collection = self._result_reader.pop_result_collection()
if collection is None:
break
record_result_history(self._result_history, collection)
progress = True
if progress:
if any(collection is not None for collection in latest):
continue
raw_count = len(self._raw_history)
pre_count = len(self._pre_history)
result_count = len(self._result_history)
missing_raw = self._raw_reader is not None and raw_count == start_raw_count
missing_pre = self._pre_reader is not None and pre_count == start_pre_count
missing_results = self._result_reader is not None and result_count == start_result_count
got_results = result_count > start_result_count
got_raw_or_pre = raw_count > start_raw_count or pre_count > start_pre_count
# Event-loop-friendly waits so the keepalive/watchdog timers and
# queued signals keep firing instead of freezing the headless daemon.
if got_results and (missing_raw or missing_pre) and time.monotonic() < deadline:
self._pump_events_during_drain(0.01)
continue
if got_raw_or_pre and missing_results and time.monotonic() < deadline:
self._pump_events_during_drain(0.01)
continue
if (
self._result_reader is not None
and max(raw_count, pre_count) > result_count
and time.monotonic() < deadline
):
missing_stage = any(reader is not None and not seen for reader, seen in zip(readers, observed))
awaiting_results = self._result_reader is not None and (
max(len(self._raw_history), len(self._pre_history)) > len(self._result_history)
)
if (any(observed) and missing_stage) or awaiting_results:
self._pump_events_during_drain(0.01)
continue
break