122 lines
5.7 KiB
Python
122 lines
5.7 KiB
Python
"""Manual dataset export and paused deletion of retained acquisitions."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
from pathlib import Path
|
|
|
|
from python_app.models.gui_profile_schema import ProfileWriter
|
|
from python_app.gui.controllers.pipeline_controller import PipelineController
|
|
from python_app.gui.controllers.sections.views import PrimaryView, DataView
|
|
from python_app.models.run_config_schema import RunConfigModel
|
|
from python_app.storage.npz.store import NpzStore
|
|
|
|
|
|
class SnapshotController:
|
|
"""Save stage histories and hold deletion ownership through final redraw."""
|
|
|
|
def __init__(
|
|
self, pipeline: PipelineController, store: NpzStore, write_profile: ProfileWriter,
|
|
*, destination: Callable[[RunConfigModel], tuple[Path, str, str]],
|
|
build_config: Callable[[], RunConfigModel], processing_settings: dict[int, dict],
|
|
allowed: Callable[[bool], bool], log: Callable[[str], None],
|
|
error: Callable[[str, Exception], None],
|
|
) -> None:
|
|
self.pipeline, self.store = pipeline, store
|
|
self._write_profile, self._log, self._error = write_profile, log, error
|
|
self._destination, self._build_config = destination, build_config
|
|
self._settings, self._allowed = processing_settings, allowed
|
|
|
|
def bind_actions(self, primary: PrimaryView, data: DataView) -> None:
|
|
primary.clear.clicked.connect(lambda: self.delete_history(remove_last=False))
|
|
data.remove_last.clicked.connect(lambda: self.delete_history(remove_last=True))
|
|
data.save.clicked.connect(lambda: self.save())
|
|
data.save_json.clicked.connect(lambda: self.save(json_export=True))
|
|
data.record.clicked.connect(lambda: self.start_recording(data.record_count.value()))
|
|
|
|
def start_recording(self, count: int) -> None:
|
|
if not self._allowed(False):
|
|
return
|
|
try:
|
|
config = self.pipeline.active_config if self.pipeline.running else None
|
|
self.pipeline.recording.start(
|
|
*self._destination(config or self._build_config()), count,
|
|
running=self.pipeline.running, start_acquisition=self.pipeline.start,
|
|
)
|
|
except Exception as exc:
|
|
self._error("Failed to start disk recording", exc)
|
|
|
|
def save(self, *, json_export: bool = False) -> None:
|
|
if not self._allowed(False):
|
|
return
|
|
action = "VNA history JSON" if json_export else "snapshot"
|
|
try:
|
|
active = self.pipeline.active_config
|
|
root, name, prefix = self._destination(active or self._build_config())
|
|
self.pipeline.drain_snapshot()
|
|
if self.pipeline.closing or self.pipeline.active_config is not active:
|
|
return
|
|
raw = list(self.pipeline.history.raw)
|
|
preprocessed = list(self.pipeline.history.preprocessed)
|
|
results = list(self.pipeline.history.results)
|
|
if not (raw or preprocessed or results):
|
|
raise ValueError("No runtime data is available for save")
|
|
if json_export:
|
|
paths = self.store.save_runtime_vna_history_json_batch(
|
|
root, name, raw, preprocessed, results,
|
|
primary_stage="preprocessed", name_prefix=prefix,
|
|
)
|
|
directory = paths[0].parent
|
|
profile_path = directory / "vna_config_profile.json"
|
|
else:
|
|
directory = self.store.save_runtime_snapshot_numpy(
|
|
root, name, raw, preprocessed, results,
|
|
name_prefix=prefix, processing_settings=self._settings,
|
|
)
|
|
profile_path = directory / "config_profile.json"
|
|
try:
|
|
self._write_profile(profile_path, allow_overwrite=json_export, dataset=True)
|
|
except Exception as exc:
|
|
self._error(
|
|
f"{action} data was saved in {directory}, but the adjacent config profile "
|
|
f"could not be written: {profile_path}", exc,
|
|
)
|
|
return
|
|
self._log(
|
|
f"Saved {action}: {directory} "
|
|
f"(raw={len(raw)}, preprocessed={len(preprocessed)}, results={len(results)})"
|
|
)
|
|
except Exception as exc:
|
|
self._error(f"Failed to save {action}", exc)
|
|
|
|
def delete_history(self, *, remove_last: bool) -> None:
|
|
if not self._allowed(True):
|
|
return
|
|
action = "remove last runtime measurement" if remove_last else "clear runtime history"
|
|
try:
|
|
with self.pipeline.pause() as operation:
|
|
if not operation.valid:
|
|
return
|
|
self.pipeline.discard_replay()
|
|
dropped_raw, dropped_pre, dropped_results = self.pipeline.drop_pending()
|
|
self.pipeline.history.delete(remove_last=remove_last)
|
|
try:
|
|
self.pipeline.publish_live()
|
|
finally:
|
|
self.pipeline.plots.clear()
|
|
self.pipeline.settle_deleted_results()
|
|
if not operation.valid:
|
|
return
|
|
self.pipeline.history_changed()
|
|
self.pipeline.plots.redraw()
|
|
# Removing the newest sweep brings an older one, maybe never replayed, into a stopped B-scan.
|
|
self.pipeline.schedule_replay()
|
|
self._log(
|
|
f"{action}: dropped raw={dropped_raw}, "
|
|
f"preprocessed={dropped_pre}, results={dropped_results}"
|
|
)
|
|
except InterruptedError:
|
|
self.pipeline.operation_cancelled()
|
|
except Exception as exc:
|
|
self._error(f"Failed to {action}", exc)
|