"""Preprocessing-set selection and sequential capture workflow mixin.""" from __future__ import annotations from python_app.gui.preprocess_dialog import PreprocessDialog from python_app.hardware_full.kamil_adc_service import KamilAdcService from python_app.orchestration.preprocess_assets import ( PREPROCESS_ASSET_SPECS, VISIBLE_PREPROCESS_ASSET_KEYS, preprocess_asset_channel, preprocess_asset_display_name, ) from python_app.workflows.kamil_adc_neutral_preprocess import build_kamil_adc_neutral_s21_sets 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.reference_workflow import capture_reference_set from python_app.workflows.sequential_capture_workflow import SequentialCaptureSession TMP_REFERENCE_SET_NAME = "tmp_reference" class AppWindowPreprocessMixin: """Handles preprocess set management and sequential capture workflows.""" @staticmethod 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( f"{display_name}: {index}/{total_count} | " f"input={trace.combo.input} output={trace.combo.output}" ) return entries def _available_preprocess_sets_for_radar_key(self, radar_key: str) -> dict[str, list[str]]: """Load available preprocess-set names for one radar key.""" return { key: self._store.list_sets(PREPROCESS_ASSET_SPECS[key].set_kind, radar_key) for key in VISIBLE_PREPROCESS_ASSET_KEYS } def _reset_preprocess_selection_after_radar_key_change(self) -> None: """Clear selected preprocess sets when radar-key-defining settings change.""" try: radar_key = self._radar_key_from_ui() except Exception: return previous_radar_key = getattr(self, "_selected_preprocess_radar_key", radar_key) if radar_key == previous_radar_key: return self._selected_preprocess_radar_key = radar_key cleared = { key: value for key, value in self._selected_preprocess_sets.items() if value } if not cleared: if self._preprocess_dialog is not None: self._refresh_sets() return self._selected_preprocess_sets = { key: "" for key in VISIBLE_PREPROCESS_ASSET_KEYS } self._refresh_preprocess_summary_labels() if self._preprocess_dialog is not None: self._preprocess_dialog.set_selected_sets(self._selected_preprocess_sets, emit_signal=False) self._refresh_sets() cleared_summary = ", ".join( f"{preprocess_asset_display_name(key)}={value}" for key, value in cleared.items() ) self._log( "Preprocess selection reset after radar settings changed: " f"{previous_radar_key} -> {radar_key}; cleared {cleared_summary}" ) 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_exception("Failed to open preprocessing panel", exc) return dialog.showMaximized() dialog.raise_() dialog.activateWindow() def _capture_tmp_reference(self) -> None: """Capture, save, and select a temporary S21 reference with current sweep settings.""" if self._capture_session is not None: self._show_error( "Cannot capture tmp reference during active capture sequence", details=self._capture_state_details(), ) return # One shared re-entrancy guard for BOTH the GUI button and the GPIO trigger: # this capture spins the Qt event loop (stop/start run, dialogs), so a second # request from either source must not start a nested capture. if self._control_button_busy: self._log("Tmp reference capture already in progress; ignoring duplicate request.") return self._control_button_busy = True pipeline_was_running = self._supervisor.is_running() pipeline_was_paused = False try: if pipeline_was_running: self._log("Pipeline paused for tmp reference capture") self._stop_run() pipeline_was_paused = True config = self._build_config() radar_key = self._radar_key(config) self._log( "Tmp S21 Reference capture started: " f"set={TMP_REFERENCE_SET_NAME}, radar_key={radar_key}" ) radar_key, collection = capture_reference_set( config, TMP_REFERENCE_SET_NAME, self._store, median_sweep_count=self._preprocess_median_sweep_count, ) self._selected_preprocess_sets["s21_reference"] = TMP_REFERENCE_SET_NAME self._selected_preprocess_radar_key = radar_key # Do NOT restart data_processor for a reference change: it does not consume # the S21 reference (the data_preprocessor does), and restarting it would tear # down the locator server it hosts and drop every connected client. The # preprocessor reloads the new reference when acquisition restarts below; the # processor keeps running. We only re-baseline the on-screen display history. self._history_run_signature = None self._reset_runtime_history() # Tell the still-running data_processor to drop its accumulated background/ # history so the new reference takes effect cleanly (no old/new-reference # blend) without restarting the process. Reuses the live-config clear_all # command, applied on the processor's next poll. if self._supervisor.is_processor_running(): self._write_live_processing_config(history_command="clear_all", bump_history_seq=True) self._refresh_preprocess_summary_labels() if collection.traces: self._draw_single_trace( collection.traces[-1], title="Tmp S21 Reference last trace", channel="s21", ) if self._preprocess_dialog is not None: self._refresh_sets() self._preprocess_dialog.set_status( f"S21 Reference set saved: {TMP_REFERENCE_SET_NAME} ({len(collection.traces)} traces)" ) self._log( "Tmp S21 Reference captured and selected: " f"set={TMP_REFERENCE_SET_NAME}, key={radar_key}, traces={len(collection.traces)}; " "runtime history reset" ) except Exception as exc: # noqa: BLE001 self._show_exception("Failed to capture tmp reference", exc) finally: if pipeline_was_paused: self._start_run() self._control_button_busy = False 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) 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_median_sweep_count(self._preprocess_median_sweep_count) 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.median_sweep_count_changed.connect(self._on_preprocess_median_sweep_count_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.create_kamil_adc_neutral_sets_requested.connect(self._create_kamil_adc_neutral_sets) dialog.set_kamil_adc_neutral_sets_visible(self._defaults_config.is_kamil_adc) 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_median_sweep_count_changed(self) -> None: """Persist updated per-combo median sweep count from the preprocessing dialog.""" dialog = self._ensure_preprocess_dialog() self._preprocess_median_sweep_count = dialog.median_sweep_count() def _on_preprocess_selection_changed(self) -> None: """Persist selected preprocessing set names from dialog.""" dialog = self._ensure_preprocess_dialog() previous_selection = dict(self._selected_preprocess_sets) self._selected_preprocess_sets = dialog.selection_snapshot() self._refresh_preprocess_summary_labels() changes = [] for key in VISIBLE_PREPROCESS_ASSET_KEYS: previous_value = previous_selection.get(key, "") current_value = self._selected_preprocess_sets.get(key, "") if previous_value == current_value: continue changes.append( f"{preprocess_asset_display_name(key)}: " f"{previous_value or ''} -> {current_value or ''}" ) if changes: self._log("Preprocess selection changed: " + "; ".join(changes)) def _refresh_preprocess_summary_labels(self) -> None: """Update compact summary labels in the main window.""" for key in VISIBLE_PREPROCESS_ASSET_KEYS: self._selected_preprocess_labels[key].setText(self._selected_preprocess_sets.get(key, "") or "") def _refresh_sets(self) -> None: """Refresh preprocess set lists for current radar key.""" 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(): current_value = self._selected_preprocess_sets.get(key, "") if not current_value or current_value in names: continue unavailable_selections.append( f"{preprocess_asset_display_name(key)}: " f"{current_value} (not available for current radar key)" ) dialog.set_selected_sets(self._selected_preprocess_sets) self._refresh_preprocess_summary_labels() available_counts = ", ".join( f"{preprocess_asset_display_name(key)}={len(names)}" for key, names in available_sets.items() ) dialog.set_kamil_adc_neutral_sets_visible(self._defaults_config.is_kamil_adc) self._log(f"Preprocess set lists refreshed: radar_key={radar_key}, {available_counts}") if unavailable_selections: self._log_warning( "Some selected preprocess sets are not currently available for this radar key.", 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: self._show_error("Another capture sequence is already active", details=self._capture_state_details()) 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() display_name = preprocess_asset_display_name(kind) median_sweep_count = dialog.median_sweep_count() self._preprocess_median_sweep_count = median_sweep_count if self._preprocess_use_all_radar_configs: session = self._build_multi_radar_capture_session( config=config, kind=kind, set_name=set_name, median_sweep_count=median_sweep_count, ) radar_summary = ( f"radar_variants={session.radar_variant_count()}, " f"combos={session.state().total_count}, " f"median_sweep_count={median_sweep_count}" ) else: session = self._build_single_radar_capture_session( config=config, kind=kind, set_name=set_name, median_sweep_count=median_sweep_count, ) radar_summary = ( f"radar_key={self._radar_key(config)}, " f"combos={session.state().total_count}, " f"median_sweep_count={median_sweep_count}" ) session.open() self._capture_session = session dialog.clear_capture_log() dialog.reset_preview() dialog.set_status(f"{display_name} sequence started") self._clear_trace_plots() self._update_capture_dialog_state() self._log( 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 _create_kamil_adc_neutral_sets(self) -> None: """Save neutral S21 calibration/reference sets for the current Kamil ADC settings.""" if self._capture_session is not None: self._show_error( "Cannot create neutral sets during active capture sequence", details=self._capture_state_details(), ) return dialog = self._ensure_preprocess_dialog() set_name = dialog.set_name() if not set_name: self._show_error("Set name is required") return pipeline_was_paused = False try: config = self._build_config() if not config.is_kamil_adc: self._show_error("Neutral S21 sets are available only for kamil_adc") return radar_key = self._radar_key(config) duplicate_assets = [ preprocess_asset_display_name(key) for key in ("s21_calibration", "s21_reference") if set_name in self._store.list_sets(PREPROCESS_ASSET_SPECS[key].set_kind, radar_key) ] if duplicate_assets: raise RuntimeError( f"Set '{set_name}' already exists for: " + ", ".join(duplicate_assets) ) if self._supervisor.is_running(): self._log("Pipeline paused for Kamil ADC neutral-set creation") self._stop_run() pipeline_was_paused = True point_count = self._read_kamil_adc_point_count(config) calibration, reference = build_kamil_adc_neutral_s21_sets(config, point_count) self._store.save_set("s21_calibration", radar_key, set_name, calibration) self._store.save_set("s21_reference", radar_key, set_name, reference) self._selected_preprocess_sets["s21_calibration"] = set_name self._selected_preprocess_sets["s21_reference"] = set_name self._selected_preprocess_radar_key = radar_key self._processor_run_signature = None self._history_run_signature = None self._reset_runtime_history() self._refresh_sets() dialog.set_status( f"Neutral S21 sets saved: {set_name} ({len(calibration.traces)} combos, {point_count} points)" ) self._log( "Kamil ADC neutral S21 sets saved: " f"set={set_name}, radar_key={radar_key}, combos={len(calibration.traces)}, points={point_count}" ) except Exception as exc: # noqa: BLE001 self._show_exception("Failed to create Kamil ADC neutral sets", exc) finally: if pipeline_was_paused: self._start_run() def _read_kamil_adc_point_count(self, config) -> int: """Read one Kamil ADC sweep and return its actual point count.""" dialog = self._ensure_preprocess_dialog() dialog.set_status("Reading one Kamil ADC sweep to detect point count...") self._log("Reading one Kamil ADC sweep to detect neutral-set point count") radar = KamilAdcService(config) try: radar.open() radar.configure(config.radar.sweep) sweep = radar.acquire() finally: radar.close() point_count = int(sweep.x.size) if point_count <= 0: raise RuntimeError("Kamil ADC returned an empty sweep while detecting point count") return point_count def _build_single_radar_capture_session( self, *, config, kind: str, set_name: str, median_sweep_count: int, ) -> 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, median_sweep_count=median_sweep_count, ) def _build_multi_radar_capture_session( self, *, config, kind: str, set_name: str, median_sweep_count: int, ) -> 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, median_sweep_count=median_sweep_count, ) 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 try: capture_result = session.capture_current_combo() except Exception as exc: # noqa: BLE001 self._on_capture_combo_failed(session, exc) return self._record_preprocess_capture(session, capture_result) def _capture_all_remaining(self) -> None: """Capture all remaining combos for the active preprocess session.""" session = self._capture_session if session is None: self._show_error("No active capture sequence") return if session.is_complete(): self._show_error( "Capture sequence is already complete", details=self._capture_state_details(), ) return display_name = preprocess_asset_display_name(session.kind) dialog = self._ensure_preprocess_dialog() dialog.set_status(f"{display_name} batch capture started") self._log( f"{display_name} batch capture started: remaining=" f"{session.state().total_count - session.state().captured_count}" ) while not session.is_complete(): try: capture_result = session.capture_current_combo() except Exception as exc: # noqa: BLE001 self._on_capture_combo_failed(session, exc) return self._record_preprocess_capture(session, capture_result) def _on_capture_combo_failed( self, session: SequentialCaptureSession | MultiRadarSequentialCaptureSession, exc: BaseException, ) -> None: """Report a failed combo capture while preserving the session and prior captures.""" state = session.state() combo = state.current_combo combo_text = ( f"input={combo.input}, output={combo.output}" if combo is not None else "" ) self._show_exception( f"Failed to capture combo {combo_text}; previous captures kept, retry when ready", exc, ) display_name = preprocess_asset_display_name(session.kind) dialog = self._ensure_preprocess_dialog() dialog.set_status( f"{display_name} capture failed at {combo_text}: " f"{state.captured_count}/{state.total_count} kept, ready to retry" ) self._update_capture_dialog_state() 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 output_pos = trace.combo.output extra_details = "" dialog.append_capture_log_entry( kind=display_name, captured_count=state.captured_count, total_count=state.total_count, 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={input_pos} output={output_pos}{extra_details}" ) self._update_capture_dialog_state() if session.is_complete(): dialog.set_status(f"{display_name} sequence complete. Review captures or save the set.") self._log( f"{display_name} sequence capture complete: " f"{state.captured_count}/{state.total_count}; waiting for save or undo" ) def _undo_last_capture(self) -> None: """Remove the most recently captured combo and rewind capture cursor.""" session = self._capture_session if session is None: self._show_error("No active capture sequence") return dialog = self._ensure_preprocess_dialog() try: 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 removed_output = removed_capture.combo.output extra_details = "" dialog.set_capture_log_entries(self._capture_log_entries_for_session(session)) last_trace = session.last_captured_trace() if last_trace is None: dialog.reset_preview() dialog.set_status(f"{display_name} last capture removed. No captured combos remain.") self._clear_trace_plots() else: dialog.draw_last_trace(last_trace, title=f"{display_name} last trace", channel=channel) dialog.set_status(f"{display_name} last capture removed. Ready to recapture.") self._draw_single_trace(last_trace, title=f"{display_name} last trace", channel=channel) self._update_capture_dialog_state() self._log( 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) def _finalize_capture_sequence(self) -> None: """Persist completed capture session into preprocess-set storage.""" session = self._capture_session if session is None: self._show_error("No active capture sequence") return if not session.is_complete(): self._show_error( "Capture sequence is not complete", details=( f"Captured {session.state().captured_count}/{session.state().total_count} combos. " "Finish the remaining captures before saving." ), ) return dialog = self._ensure_preprocess_dialog() try: 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() 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) 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, can_undo=False, can_finalize=False, can_capture_all=False, variant_count=1, ) 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, can_undo=state.can_undo, can_finalize=state.is_complete, can_capture_all=( state.supports_batch_capture and not state.is_complete and state.current_combo is not None ), variant_count=state.variant_count, ) 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_exception("Failed to resume pipeline after capture", exc)