multi config calibration added

This commit is contained in:
Ayzen
2026-04-09 00:48:23 +03:00
parent 1d7d0a756e
commit 61685d7a4d
10 changed files with 738 additions and 26 deletions
@@ -9,6 +9,11 @@ from python_app.orchestration.preprocess_assets import (
preprocess_asset_channel,
preprocess_asset_display_name,
)
from python_app.workflows.multi_radar_capture_workflow import (
MultiRadarCaptureBatch,
MultiRadarSequentialCaptureSession,
)
from python_app.workflows.radar_config_variants import scan_radar_config_variants
from python_app.workflows.sequential_capture_workflow import SequentialCaptureSession
@@ -16,10 +21,18 @@ class AppWindowPreprocessMixin:
"""Handles preprocess set management and sequential capture workflows."""
@staticmethod
def _capture_log_entries_for_session(session: SequentialCaptureSession) -> list[str]:
def _capture_log_entries_for_session(session: SequentialCaptureSession | MultiRadarSequentialCaptureSession) -> list[str]:
"""Build capture-log rows from current session traces."""
display_name = preprocess_asset_display_name(session.kind)
total_count = session.state().total_count
if isinstance(session, MultiRadarSequentialCaptureSession):
return [
f"{display_name}: {index}/{total_count} | "
f"input={batch.combo.input} output={batch.combo.output} | "
f"radar_configs={session.radar_variant_count()}"
for index, batch in enumerate(session.captured_batches(), start=1)
]
entries: list[str] = []
for index, trace in enumerate(session.captured_traces(), start=1):
entries.append(
@@ -98,18 +111,36 @@ class AppWindowPreprocessMixin:
dialog = PreprocessDialog(self)
self._preprocess_dialog = dialog
dialog.set_set_name(self._preprocess_set_name)
dialog.set_radar_config_dir(self._preprocess_radar_config_dir)
dialog.set_use_all_radar_configs(self._preprocess_use_all_radar_configs)
dialog.set_selected_sets(self._selected_preprocess_sets, emit_signal=False)
dialog.refresh_requested.connect(self._refresh_sets)
dialog.selection_changed.connect(self._on_preprocess_selection_changed)
dialog.radar_config_dir_changed.connect(self._on_preprocess_radar_config_inputs_changed)
dialog.multi_radar_option_changed.connect(self._on_preprocess_radar_config_inputs_changed)
dialog.start_sequence_requested.connect(self._start_capture_sequence)
dialog.capture_next_requested.connect(self._capture_next_combo)
dialog.capture_all_requested.connect(self._capture_all_remaining)
dialog.undo_last_requested.connect(self._undo_last_capture)
dialog.finalize_sequence_requested.connect(self._finalize_capture_sequence)
dialog.abort_sequence_requested.connect(self._abort_capture_sequence)
dialog.set_radar_config_summary(
directory_path=self._preprocess_radar_scan_summary.directory_path,
json_file_count=self._preprocess_radar_scan_summary.json_file_count,
valid_variant_count=self._preprocess_radar_scan_summary.valid_variant_count,
skipped_file_count=self._preprocess_radar_scan_summary.skipped_file_count,
duplicate_variant_count=self._preprocess_radar_scan_summary.duplicate_variant_count,
)
self._update_capture_dialog_state()
return dialog
def _on_preprocess_radar_config_inputs_changed(self) -> None:
"""Persist preprocess radar-config directory options and refresh the scan result."""
dialog = self._ensure_preprocess_dialog()
self._preprocess_radar_config_dir = dialog.radar_config_dir()
self._preprocess_use_all_radar_configs = dialog.use_all_radar_configs()
self._refresh_sets()
def _on_preprocess_selection_changed(self) -> None:
"""Persist selected preprocessing set names from dialog."""
dialog = self._ensure_preprocess_dialog()
@@ -139,9 +170,12 @@ class AppWindowPreprocessMixin:
radar_key = self._radar_key_from_ui()
self._selected_preprocess_radar_key = radar_key
dialog = self._ensure_preprocess_dialog()
self._preprocess_radar_config_dir = dialog.radar_config_dir()
self._preprocess_use_all_radar_configs = dialog.use_all_radar_configs()
available_sets = self._available_preprocess_sets_for_radar_key(radar_key)
dialog.set_available_sets(available_sets)
self._refresh_preprocess_radar_variants()
unavailable_selections: list[str] = []
for key, names in available_sets.items():
@@ -166,6 +200,32 @@ class AppWindowPreprocessMixin:
details="\n".join(unavailable_selections),
)
def _refresh_preprocess_radar_variants(self) -> None:
"""Reload valid radar sweep variants from the preprocess dialog directory."""
config = self._build_config()
variants, summary = scan_radar_config_variants(
self._preprocess_radar_config_dir,
base_config=config,
)
self._preprocess_radar_variants = variants
self._preprocess_radar_scan_summary = summary
if self._preprocess_dialog is not None:
self._preprocess_dialog.set_radar_config_summary(
directory_path=summary.directory_path,
json_file_count=summary.json_file_count,
valid_variant_count=summary.valid_variant_count,
skipped_file_count=summary.skipped_file_count,
duplicate_variant_count=summary.duplicate_variant_count,
)
if summary.issues:
self._log_warning(
"Some radar config JSON files were skipped during preprocess scan.",
details="\n".join(summary.issues),
once_key=f"preprocess_radar_config_scan_{summary.directory_path}",
)
def _start_capture_sequence(self, kind: str) -> None:
"""Start sequential capture session for requested preprocess asset."""
if self._capture_session is not None:
@@ -187,13 +247,20 @@ class AppWindowPreprocessMixin:
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")
if self._preprocess_use_all_radar_configs:
session = self._build_multi_radar_capture_session(config=config, kind=kind, set_name=set_name)
radar_summary = (
f"radar_variants={session.radar_variant_count()}, "
f"combos={session.state().total_count}"
)
else:
session = self._build_single_radar_capture_session(config=config, kind=kind, set_name=set_name)
radar_summary = (
f"radar_key={self._radar_key(config)}, "
f"combos={session.state().total_count}"
)
session = SequentialCaptureSession(config=config, kind=kind, set_name=set_name)
session.open()
self._capture_session = session
@@ -203,14 +270,59 @@ class AppWindowPreprocessMixin:
self._clear_trace_plots()
self._update_capture_dialog_state()
self._log(
f"{display_name} sequence started: set={set_name}, radar_key={radar_key}, "
f"combos={session.state().total_count}"
f"{display_name} sequence started: set={set_name}, {radar_summary}"
)
except Exception as exc: # noqa: BLE001
self._cleanup_capture_session()
self._show_exception(f"Failed to start {kind} sequence", exc)
self._resume_pipeline_if_needed()
def _build_single_radar_capture_session(
self,
*,
config,
kind: str,
set_name: str,
) -> SequentialCaptureSession:
"""Validate and create the existing single-radar capture session."""
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")
return SequentialCaptureSession(config=config, kind=kind, set_name=set_name)
def _build_multi_radar_capture_session(
self,
*,
config,
kind: str,
set_name: str,
) -> MultiRadarSequentialCaptureSession:
"""Validate and create the multi-radar capture session."""
self._refresh_preprocess_radar_variants()
if not self._preprocess_radar_variants:
raise RuntimeError("No valid radar config variants were found in the selected directory")
display_name = preprocess_asset_display_name(kind)
duplicate_set_keys: list[str] = []
for variant in self._preprocess_radar_variants:
existing_sets = self._store.list_sets(PREPROCESS_ASSET_SPECS[kind].set_kind, variant.radar_key)
if set_name in existing_sets:
duplicate_set_keys.append(f"{variant.display_name} -> {variant.radar_key}")
if duplicate_set_keys:
raise RuntimeError(
f"Set '{set_name}' already exists for {display_name} in these radar variants:\n"
+ "\n".join(duplicate_set_keys)
)
return MultiRadarSequentialCaptureSession(
base_config=config,
kind=kind,
set_name=set_name,
radar_variants=self._preprocess_radar_variants,
)
def _capture_next_combo(self) -> None:
"""Capture next combo in active sequential capture session."""
session = self._capture_session
@@ -219,8 +331,8 @@ class AppWindowPreprocessMixin:
return
try:
trace = session.capture_current_combo()
self._record_preprocess_capture(session, trace)
capture_result = session.capture_current_combo()
self._record_preprocess_capture(session, capture_result)
except Exception as exc: # noqa: BLE001
self._show_exception("Failed to capture preprocess combo", exc)
self._abort_capture_sequence()
@@ -247,31 +359,47 @@ class AppWindowPreprocessMixin:
)
try:
while not session.is_complete():
trace = session.capture_current_combo()
self._record_preprocess_capture(session, trace)
capture_result = session.capture_current_combo()
self._record_preprocess_capture(session, capture_result)
except Exception as exc: # noqa: BLE001
self._show_exception("Failed to capture preprocess combo", exc)
self._abort_capture_sequence()
def _record_preprocess_capture(self, session: SequentialCaptureSession, trace) -> None:
def _record_preprocess_capture(
self,
session: SequentialCaptureSession | MultiRadarSequentialCaptureSession,
capture_result,
) -> None:
"""Update UI, preview, and logs after one successful preprocess capture."""
dialog = self._ensure_preprocess_dialog()
state = session.state()
display_name = preprocess_asset_display_name(session.kind)
channel = preprocess_asset_channel(session.kind)
if isinstance(session, MultiRadarSequentialCaptureSession):
assert isinstance(capture_result, MultiRadarCaptureBatch)
trace = capture_result.display_trace
input_pos = capture_result.combo.input
output_pos = capture_result.combo.output
extra_details = f" | radar_configs={len(capture_result.traces)}"
else:
trace = capture_result
input_pos = trace.combo.input_pos
output_pos = trace.combo.output_pos
extra_details = ""
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,
input_pos=input_pos,
output_pos=output_pos,
extra_details=extra_details,
)
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}"
f"input={input_pos} output={output_pos}{extra_details}"
)
self._update_capture_dialog_state()
@@ -291,10 +419,19 @@ class AppWindowPreprocessMixin:
dialog = self._ensure_preprocess_dialog()
try:
removed_trace = session.undo_last_capture()
removed_capture = session.undo_last_capture()
state = session.state()
display_name = preprocess_asset_display_name(session.kind)
channel = preprocess_asset_channel(session.kind)
if isinstance(session, MultiRadarSequentialCaptureSession):
assert isinstance(removed_capture, MultiRadarCaptureBatch)
removed_input = removed_capture.combo.input
removed_output = removed_capture.combo.output
extra_details = f" | radar_configs={len(removed_capture.traces)}"
else:
removed_input = removed_capture.combo.input_pos
removed_output = removed_capture.combo.output_pos
extra_details = ""
dialog.set_capture_log_entries(self._capture_log_entries_for_session(session))
last_trace = session.last_captured_trace()
@@ -309,8 +446,8 @@ class AppWindowPreprocessMixin:
self._update_capture_dialog_state()
self._log(
f"{display_name} undo last capture: removed input={removed_trace.combo.input_pos} "
f"output={removed_trace.combo.output_pos}; remaining={state.captured_count}/{state.total_count}"
f"{display_name} undo last capture: removed input={removed_input} "
f"output={removed_output}{extra_details}; remaining={state.captured_count}/{state.total_count}"
)
except Exception as exc: # noqa: BLE001
self._show_exception("Failed to undo last preprocess capture", exc)
@@ -333,16 +470,33 @@ class AppWindowPreprocessMixin:
dialog = self._ensure_preprocess_dialog()
try:
radar_key, collection = session.finalize(self._store)
set_name = session.set_name
kind = session.kind
display_name = preprocess_asset_display_name(kind)
if isinstance(session, MultiRadarSequentialCaptureSession):
saved_sets = session.finalize(self._store)
else:
radar_key, collection = session.finalize(self._store)
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}")
if isinstance(session, MultiRadarSequentialCaptureSession):
assert isinstance(saved_sets, list)
dialog.set_status(
f"{display_name} set saved: {set_name} ({len(saved_sets)} radar variants)"
)
saved_summary = ", ".join(
f"{saved.display_name}:{saved.trace_count}"
for saved in saved_sets
)
self._log(
f"{display_name} sequence completed and saved: set={set_name}, "
f"radar_variants={len(saved_sets)} [{saved_summary}]"
)
else:
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()
except Exception as exc: # noqa: BLE001
self._show_exception("Failed to save preprocess set", exc)
@@ -375,6 +529,7 @@ class AppWindowPreprocessMixin:
can_undo=False,
can_finalize=False,
can_capture_all=False,
variant_count=1,
)
return
@@ -394,6 +549,7 @@ class AppWindowPreprocessMixin:
can_undo=state.can_undo,
can_finalize=state.is_complete,
can_capture_all=(not state.is_complete and state.current_combo is not None),
variant_count=state.variant_count,
)
def _cleanup_capture_session(self) -> None: