Source code for cosedaUI.indexintegrate_window

"""UI for configuring and running CrystFEL indexing/integration from the workspace."""

from coseda.logging_utils import log_print
from PyQt6.QtWidgets import (
    QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton,
    QFormLayout, QCheckBox, QSpinBox, QDoubleSpinBox, QFileDialog, QMessageBox, QTabWidget, QComboBox, QTextEdit,
    QTreeWidget, QTreeWidgetItem, QStackedWidget, QPlainTextEdit, QGroupBox, QProgressBar
)
from PyQt6.QtGui import QTextCursor
import subprocess
from PyQt6.QtCore import Qt, QTimer, pyqtSignal, QThread, QObject
import os
import signal
import shlex
import sys
import re
import time
import traceback

from coseda.ici import orchestrator as orch
from coseda.initialize import write_icisettings
from coseda.nexus.process import write_nxprocess_indexing
from cosedaUI.indexintegrate_sections import (
    CellfileMixin,
    GeomfileMixin,
    IciMixin,
    IndexSettingsMixin,
    RunControlMixin,
)
from cosedaUI.indexintegrate_sections.helpers import GuiStream, OrchestratorWorker


[docs] class IndexingControlWindow( IndexSettingsMixin, CellfileMixin, GeomfileMixin, IciMixin, RunControlMixin, QWidget, ): """Tabbed dialog for managing runs, editing params, and launching indexamajig.""" ini_selection_changed = pyqtSignal(str) # absolute INI path selected in this window
[docs] class ProcessOutputThread(QThread): """Stream stdout from a subprocess and emit lines to the UI.""" output_received = pyqtSignal(str) def __init__(self, process): super().__init__() self.process = process
[docs] def run(self): """Read lines until the process finishes and emit each one.""" for line in iter(self.process.stdout.readline, b''): try: text = line.decode("utf-8") except: text = str(line) self.output_received.emit(text)
def __init__(self, main_window, ini_directory=None, h5_path=None, parent=None): """Wire UI, load latest runs/settings, and start syncing with the main window.""" super().__init__(parent) self.main_window = main_window self.ini_directory = ini_directory # h5_path is passed from main_window; ini_path is retrieved from main_window self.h5_path = h5_path self.ini_path = getattr(main_window, 'full_ini_file_path', None) # Fallback: pick the first INI in the provided directory so the run tree isn't empty if self.ini_path is None and self.ini_directory and os.path.isdir(self.ini_directory): for f in sorted(os.listdir(self.ini_directory)): if f.endswith(".ini") and not f.startswith("."): self.ini_path = os.path.join(self.ini_directory, f) break # Try to locate the workspace file path from the main window self.workspace_path = ( getattr(main_window, 'workspace_file_path', None) or getattr(main_window, 'workspace_path', None) or getattr(main_window, 'workspacefile', None) ) from configparser import ConfigParser self.setWindowTitle("Indexing & Integration") # Read ini file and paths parser = ConfigParser() parser.read(self.ini_path) base_dir = os.path.dirname(self.ini_path) import glob run_base = self._resolve_run_root(self.ini_path) prev_runs = sorted(glob.glob(os.path.join(run_base, "indexingintegration_*"))) # Do not create a run on open; the Run tab creates fresh runs when starting. self.run_dir = None self.cell_filepath = None self.geom_filepath = None self.list_filepath = None self.stream_filepath = None # Initialize tab widget (right side) self.tabs = QTabWidget() self.method_combo = QComboBox() self.method_combo.addItem("Simple Indexamajig") self.method_combo.addItem("ICI Orchestrator (experimental)") self.method_combo.setToolTip("Select the indexing method to configure and run.") self._init_index_integrate_tab() self._init_cellfile_tab() self._init_geomfile_tab() self._init_start_indexing_tab() self._init_ici_settings_tab() self._init_ici_run_tab() # Left panel: workspace tree and run history left_panel = QWidget() left_v = QVBoxLayout(left_panel) left_v.setContentsMargins(0, 0, 8, 0) left_v.addWidget(QLabel("Workspace Files / Runs")) self.runs_tree = QTreeWidget() self.runs_tree.setHeaderLabels(["Name"]) # single column self.runs_tree.setColumnCount(1) left_v.addWidget(self.runs_tree, 1) # Outer layout: left tree + right method selector + tabs right_panel = QWidget() right_v = QVBoxLayout(right_panel) right_v.addWidget(QLabel("Method")) right_v.addWidget(self.method_combo) right_v.addWidget(self.tabs, 1) outer = QHBoxLayout() outer.addWidget(left_panel, 1) outer.addWidget(right_panel, 3) self.setLayout(outer) # Populate workspace tree self._refresh_runs_tree() self._select_tree_for_ini(self.ini_path) # Connect method selection to tab stacks self.method_combo.currentIndexChanged.connect(self._on_method_changed) self._on_method_changed(self.method_combo.currentIndex()) # Connect tree selection change to handler self.runs_tree.currentItemChanged.connect(self._on_runs_tree_selection_changed) # Bidirectional selection sync: poll main window for selection changes self._last_seen_main_ini = self.ini_path self.sync_timer = QTimer(self) self.sync_timer.setInterval(700) self.sync_timer.timeout.connect(self._sync_from_mainwindow_selection) self.sync_timer.start() # Load initial data for cell and geom tabs if getattr(self, 'prev_cell_file', None) and os.path.exists(self.prev_cell_file): # Prefer the latest existing run's cellfile over workspace defaults self._load_cell_file() else: loaded_from_ws = self._load_cell_from_workspace() if not loaded_from_ws: self._load_cell_file() self._load_geom_file() self._load_ici_defaults_from_ini() # On startup, try to load Index & Integrate settings from the most recent run that actually has them try: import glob, os as _os base_dir = os.path.dirname(self.ini_path) run_base = self._resolve_run_root(self.ini_path) prev_runs = sorted( set(glob.glob(os.path.join(run_base, "indexingintegration_*"))) | set(glob.glob(os.path.join(base_dir, "indexingintegration_*"))) ) latest_settings_run = None for rd in reversed(prev_runs): if os.path.exists(os.path.join(rd, 'index_settings.json')): latest_settings_run = rd break if latest_settings_run: self._load_index_settings_from_run(latest_settings_run) except Exception: pass # Update command label after all tabs/widgets are initialized self._update_command_label() # Placeholder for indexing subprocess self.indexing_process = None # Session-level cache of last applied/saved Index & Integrate settings self._last_index_settings = None # Metrics / progress state self._total_frames_estimate = None # Batch run state self._batch_mode = False self._batch_queue = [] # list of run_dir paths to process sequentially self._proc_poll_timer = QTimer(self) self._proc_poll_timer.setInterval(500) self._proc_poll_timer.timeout.connect(self._check_process_done) # ICI orchestration state self._ici_thread = None self._ici_worker = None self._ici_running = False self._ici_stop_requested = False self._ici_progress_start_time = None self._ici_syncing_flags = False self._ici_batch_mode = False self._ici_batch_queue = [] self._ici_batch_total = 0 self._ici_batch_done = 0 self._ici_batch_original_ini = None self._ici_batch_timestamp = None
# --- Index & Integrate Tab ---