231 lines
9.3 KiB
Python
231 lines
9.3 KiB
Python
"""Preprocessing-set selection and sequential capture workflow mixin."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from python_app.gui.preprocess_dialog import PreprocessDialog
|
|
from python_app.orchestration.preprocess_assets import (
|
|
PREPROCESS_ASSET_KEYS,
|
|
PREPROCESS_ASSET_SPECS,
|
|
preprocess_asset_channel,
|
|
preprocess_asset_display_name,
|
|
)
|
|
from python_app.workflows.sequential_capture_workflow import SequentialCaptureSession
|
|
|
|
|
|
class AppWindowPreprocessMixin:
|
|
"""Handles preprocess set management and sequential capture workflows."""
|
|
|
|
def _open_preprocess_panel(self) -> None:
|
|
"""Open preprocessing dialog and refresh available sets."""
|
|
dialog = self._ensure_preprocess_dialog()
|
|
try:
|
|
self._refresh_sets()
|
|
self._update_capture_dialog_state()
|
|
except Exception as exc: # noqa: BLE001
|
|
self._show_error(f"Failed to open preprocessing panel: {exc}")
|
|
return
|
|
|
|
dialog.show()
|
|
dialog.raise_()
|
|
dialog.activateWindow()
|
|
|
|
def _ensure_preprocess_dialog(self) -> PreprocessDialog:
|
|
"""Create preprocessing dialog lazily and wire its signals once."""
|
|
if self._preprocess_dialog is not None:
|
|
return self._preprocess_dialog
|
|
|
|
dialog = PreprocessDialog(self)
|
|
dialog.refresh_requested.connect(self._refresh_sets)
|
|
dialog.selection_changed.connect(self._on_preprocess_selection_changed)
|
|
dialog.start_sequence_requested.connect(self._start_capture_sequence)
|
|
dialog.capture_next_requested.connect(self._capture_next_combo)
|
|
dialog.abort_sequence_requested.connect(self._abort_capture_sequence)
|
|
self._preprocess_dialog = dialog
|
|
self._update_capture_dialog_state()
|
|
return dialog
|
|
|
|
def _on_preprocess_selection_changed(self) -> None:
|
|
"""Persist selected preprocessing set names from dialog."""
|
|
dialog = self._ensure_preprocess_dialog()
|
|
self._selected_preprocess_sets = dialog.selection_snapshot()
|
|
self._refresh_preprocess_summary_labels()
|
|
|
|
def _refresh_preprocess_summary_labels(self) -> None:
|
|
"""Update compact summary labels in the main window."""
|
|
for key in PREPROCESS_ASSET_KEYS:
|
|
self._selected_preprocess_labels[key].setText(self._selected_preprocess_sets.get(key, "") or "<not selected>")
|
|
|
|
def _refresh_sets(self) -> None:
|
|
"""Refresh preprocess set lists for current radar key."""
|
|
config = self._build_config()
|
|
radar_key = self._radar_key(config)
|
|
dialog = self._ensure_preprocess_dialog()
|
|
|
|
available_sets = {
|
|
key: self._store.list_sets(PREPROCESS_ASSET_SPECS[key].set_kind, radar_key)
|
|
for key in PREPROCESS_ASSET_KEYS
|
|
}
|
|
dialog.set_available_sets(available_sets)
|
|
|
|
for key, names in available_sets.items():
|
|
if self._selected_preprocess_sets.get(key, "") not in names:
|
|
self._selected_preprocess_sets[key] = names[0] if names else ""
|
|
|
|
dialog.set_selected_sets(self._selected_preprocess_sets)
|
|
self._refresh_preprocess_summary_labels()
|
|
self._log(f"Preprocess set lists refreshed for key={radar_key}")
|
|
|
|
def _start_capture_sequence(self, kind: str) -> None:
|
|
"""Start sequential capture session for requested preprocess asset."""
|
|
if self._capture_session is not None:
|
|
self._show_error("Another capture sequence is already active")
|
|
return
|
|
|
|
dialog = self._ensure_preprocess_dialog()
|
|
set_name = dialog.set_name()
|
|
if not set_name:
|
|
self._show_error("Set name is required")
|
|
return
|
|
|
|
was_running = self._supervisor.is_running()
|
|
if was_running:
|
|
self._log("Pipeline paused for exclusive hardware capture")
|
|
self._stop_run()
|
|
|
|
self._resume_pipeline_after_capture = was_running
|
|
|
|
try:
|
|
config = self._build_config()
|
|
radar_key = self._radar_key(config)
|
|
existing_sets = self._store.list_sets(PREPROCESS_ASSET_SPECS[kind].set_kind, radar_key)
|
|
display_name = preprocess_asset_display_name(kind)
|
|
if set_name in existing_sets:
|
|
raise RuntimeError(f"Set '{set_name}' already exists for {display_name} and cannot be overwritten")
|
|
|
|
session = SequentialCaptureSession(config=config, kind=kind, set_name=set_name)
|
|
session.open()
|
|
self._capture_session = session
|
|
|
|
dialog.clear_capture_log()
|
|
dialog.set_status(f"{display_name} sequence started")
|
|
self._update_capture_dialog_state()
|
|
self._log(f"{display_name} sequence started for set={set_name}; fill all N*M combos")
|
|
except Exception as exc: # noqa: BLE001
|
|
self._cleanup_capture_session()
|
|
self._show_error(f"Failed to start {kind} sequence: {exc}")
|
|
self._resume_pipeline_if_needed()
|
|
|
|
def _capture_next_combo(self) -> None:
|
|
"""Capture next combo in active sequential capture session."""
|
|
session = self._capture_session
|
|
if session is None:
|
|
self._show_error("No active capture sequence")
|
|
return
|
|
|
|
dialog = self._ensure_preprocess_dialog()
|
|
|
|
try:
|
|
trace = session.capture_current_combo()
|
|
state = session.state()
|
|
tx_label, rx_label = dialog.antenna_labels()
|
|
display_name = preprocess_asset_display_name(session.kind)
|
|
channel = preprocess_asset_channel(session.kind)
|
|
|
|
dialog.append_capture_log_entry(
|
|
kind=display_name,
|
|
captured_count=state.captured_count,
|
|
total_count=state.total_count,
|
|
input_pos=trace.combo.input_pos,
|
|
output_pos=trace.combo.output_pos,
|
|
tx_label=tx_label,
|
|
rx_label=rx_label,
|
|
)
|
|
|
|
dialog.draw_last_trace(trace, title=f"{display_name} captured", channel=channel)
|
|
self._draw_single_trace(trace, title=f"{display_name} last trace", channel=channel)
|
|
|
|
self._log(
|
|
f"{display_name} capture: {state.captured_count}/{state.total_count} | "
|
|
f"input={trace.combo.input_pos} output={trace.combo.output_pos}"
|
|
)
|
|
|
|
if session.is_complete():
|
|
radar_key, collection = session.finalize(self._store)
|
|
set_name = session.set_name
|
|
kind = session.kind
|
|
display_name = preprocess_asset_display_name(kind)
|
|
self._cleanup_capture_session()
|
|
|
|
self._selected_preprocess_sets[kind] = set_name
|
|
self._refresh_sets()
|
|
dialog.set_status(f"{display_name} set saved: {set_name} ({len(collection.traces)} traces)")
|
|
self._log(f"{display_name} sequence completed and saved: set={set_name}, key={radar_key}")
|
|
self._resume_pipeline_if_needed()
|
|
else:
|
|
self._update_capture_dialog_state()
|
|
except Exception as exc: # noqa: BLE001
|
|
self._show_error(f"Failed to capture combo: {exc}")
|
|
self._abort_capture_sequence()
|
|
|
|
def _abort_capture_sequence(self, *, resume_pipeline: bool = True) -> None:
|
|
"""Abort active capture session and optionally resume pipeline."""
|
|
if self._capture_session is None:
|
|
return
|
|
|
|
display_name = preprocess_asset_display_name(self._capture_session.kind)
|
|
self._cleanup_capture_session()
|
|
dialog = self._ensure_preprocess_dialog()
|
|
dialog.set_status(f"{display_name} sequence aborted")
|
|
self._log(f"{display_name} sequence aborted")
|
|
if resume_pipeline:
|
|
self._resume_pipeline_if_needed()
|
|
|
|
def _update_capture_dialog_state(self) -> None:
|
|
"""Sync dialog state widgets with active capture session."""
|
|
if self._preprocess_dialog is None:
|
|
return
|
|
|
|
if self._capture_session is None:
|
|
self._preprocess_dialog.set_capture_state(
|
|
kind=None,
|
|
captured_count=0,
|
|
total_count=0,
|
|
next_input=None,
|
|
next_output=None,
|
|
)
|
|
return
|
|
|
|
state = self._capture_session.state()
|
|
next_input = None
|
|
next_output = None
|
|
if state.current_combo is not None:
|
|
next_input = state.current_combo.input
|
|
next_output = state.current_combo.output
|
|
|
|
self._preprocess_dialog.set_capture_state(
|
|
kind=state.kind,
|
|
captured_count=state.captured_count,
|
|
total_count=state.total_count,
|
|
next_input=next_input,
|
|
next_output=next_output,
|
|
)
|
|
|
|
def _cleanup_capture_session(self) -> None:
|
|
"""Close and clear current capture session object."""
|
|
if self._capture_session is not None:
|
|
self._capture_session.close()
|
|
self._capture_session = None
|
|
self._update_capture_dialog_state()
|
|
|
|
def _resume_pipeline_if_needed(self) -> None:
|
|
"""Resume acquisition pipeline if it was paused for capture session."""
|
|
should_resume = self._resume_pipeline_after_capture
|
|
self._resume_pipeline_after_capture = False
|
|
if not should_resume:
|
|
return
|
|
|
|
try:
|
|
self._start_run()
|
|
except Exception as exc: # noqa: BLE001
|
|
self._show_error(f"Failed to resume pipeline after capture: {exc}")
|