Source code for cosedaUI.mainwindow_sections.workspace

import configparser
import os
from functools import partial

import h5py
import numpy as np
from PyQt6.QtCore import QCoreApplication, QThread, Qt
from PyQt6.QtWidgets import QApplication, QFileDialog, QMessageBox

from coseda.logging_utils import log_print
from coseda.io import config_to_paths
from cosedaUI.h5_metadata_loader import H5MetadataLoader
from cosedaUI.metadata_window import MetadataWindow
from cosedaUI import CenterFinderSettingsWindow, CenterRefinementSettingsWindow, PeakFinderSettingsWindow
from cosedaUI.workspace_functions import (
    add_generated_ini_paths,
    add_ini_files,
    add_ini_paths,
    load_last_directory,
    load_last_workspace_state,
    new_workspace,
    open_workspace,
    remove_ini_files,
    save_workspace,
    save_workspace_state,
)
from cosedaUI.workflow_wizzard import (
    check_peakfinding_allowed,
    check_findcenters_allowed,
    check_refinecenters_allowed,
    check_index_integrate_allowed,
)


[docs] class WorkspaceMixin: add_generated_ini_paths = add_generated_ini_paths add_ini_files = add_ini_files add_ini_paths = add_ini_paths remove_ini_files = remove_ini_files new_workspace = new_workspace open_workspace = open_workspace save_workspace = save_workspace save_workspace_state = save_workspace_state load_last_workspace_state = load_last_workspace_state load_last_directory = load_last_directory
[docs] def on_ini_select(self): selected_items = self.ini_listbox.selectedItems() if not selected_items: return ini_file_name = selected_items[0].text() self.full_ini_file_path = self.workspace_ini_paths.get(ini_file_name) # Notify listeners (e.g., Index & Integrate window) if self.full_ini_file_path: self.ini_selection_changed.emit(self.full_ini_file_path) # Clear any previously selected indexing stream until a run provides one self.on_indexing_run_stream_changed(None) self.strongest_frame_index = None self.current_frame_index = 0 self.go_to_strongest_frame_button.setEnabled(False) self.open_file(self.full_ini_file_path) self.file_name_label.setText(f"File: {ini_file_name}") self.highlight_selected_file() log_print(f'Selected {self.full_ini_file_path}', level="debug") self.update_workflow_buttons()
[docs] def update_workflow_buttons(self): # Temporarily keep Merge button enabled at all times self.workflow_merge_btn.setEnabled(True) stream_available = False try: stream_available = bool(self.indexing_manager.is_stream_available()) except Exception: stream_available = False if self.hdf5_path: # Enable Find Peaks only if peakfinding allowed and metadata complete try: # Instantiate metadata dialog off-screen to check completeness meta_win = MetadataWindow(self.full_ini_file_path) metadata_ok = meta_win.is_metadata_complete() meta_win.deleteLater() except Exception: metadata_ok = False self.workflow_find_peaks_btn.setEnabled(check_peakfinding_allowed(self.hdf5_path) and metadata_ok) self.workflow_strip_files_btn.setEnabled(check_findcenters_allowed(self.hdf5_path)) self.workflow_find_centers_btn.setEnabled(check_findcenters_allowed(self.hdf5_path)) self.workflow_refine_centers_btn.setEnabled(check_refinecenters_allowed(self.hdf5_path)) # Enable Index & Integrate button based on detector shift dataset (using workflow_wizzard logic) self.workflow_index_integrate_btn.setEnabled(check_index_integrate_allowed(self.hdf5_path)) # Enable Preflight Check button when INI is loaded self.workflow_check_metadata_btn.setEnabled(True) # Enable Export when a file/workspace is present self.workflow_export_btn.setEnabled(self.hdf5_path is not None) if hasattr(self, "unit_cell_hist_action"): self.unit_cell_hist_action.setEnabled(stream_available) else: self.workflow_find_peaks_btn.setEnabled(False) self.workflow_strip_files_btn.setEnabled(False) self.workflow_find_centers_btn.setEnabled(False) self.workflow_refine_centers_btn.setEnabled(False) self.workflow_index_integrate_btn.setEnabled(False) # Disable Preflight Check button when no INI is loaded self.workflow_check_metadata_btn.setEnabled(False) # Enable Export when a file/workspace is present self.workflow_export_btn.setEnabled(self.hdf5_path is not None) if hasattr(self, "unit_cell_hist_action"): self.unit_cell_hist_action.setEnabled(False)
[docs] def open_peakfinder_settings_window(self): if not self.full_ini_file_path or not os.path.exists(self.full_ini_file_path): QMessageBox.warning(self, "No INI File", "Please load an INI file first.") return if self.settings_window_instance is None or not self.settings_window_instance.isVisible(): self.settings_window_instance = PeakFinderSettingsWindow( parent=self, ini_directory=self.ini_directory, ini_file_path=self.full_ini_file_path ) self.settings_window_instance.settings_applied.connect(self.apply_peakfinder_settings) self.settings_window_instance.coordinate_selection_requested.connect(self.start_coordinate_selection) # Connect the close signal to stop live preview when window is closed self.settings_window_instance.settings_closed.connect(self.stop_live_preview) # Reload file on close, preserving current frame self.settings_window_instance.settings_closed.connect( self.reload_file_preserve_frame ) self.settings_window_instance.show() else: self.settings_window_instance.raise_() self.settings_window_instance.activateWindow()
[docs] def start_coordinate_selection(self): # Track which window requested the coordinate so we can route the result try: self.coordinate_selection_target = self.sender() except Exception: self.coordinate_selection_target = None self.coordinate_selection_active = True # Change cursor to crosshair self.image_label.setCursor(Qt.CursorShape.CrossCursor) # Show hint in status area instead of modal popup (avoids focus issues e.g. on WSL) if hasattr(self, "_set_status_message"): self._set_status_message("Click on the image to select x0/y0")
[docs] def stop_live_preview(self): # Stop the live preview self.live_preview_active = False # Optionally update the image if needed self.update_image() log_print("Live preview stopped.", level="debug")
[docs] def apply_peakfinder_settings(self, settings): """ Slot function to apply peak finder settings emitted from PeakFinderSettingsWindow. """ try: self.current_peak_settings = settings self.live_preview_active = settings.get('live_preview', False) # Handle live_preview #QMessageBox.information(self, "Settings Applied", "Peak Finder settings have been applied.") # Reprocess the current frame to display the peak preview immediately self.update_image() except Exception as e: QMessageBox.critical(self, "Error", f"Failed to apply peak finder settings:\n{str(e)}")
[docs] def open_centerfinder_settings_window(self): """ Opens the Center Finder Settings window. """ if not self.full_ini_file_path or not os.path.exists(self.full_ini_file_path): QMessageBox.warning(self, "No INI File", "Please load an INI file first.") return # Prevent multiple instances if self.centerfindersettings_window_instance is None or not self.centerfindersettings_window_instance.isVisible(): self.centerfindersettings_window_instance = CenterFinderSettingsWindow( parent=self, apply_callback=self.apply_center_finder_settings, ini_directory=self.ini_directory, ini_file_path=self.full_ini_file_path ) self.centerfindersettings_window_instance.coordinate_selection_requested.connect(self.start_coordinate_selection) # Reload the file when the dialog closes (preserves current frame) self.centerfindersettings_window_instance.finished.connect( lambda _result=None: self.reload_file_preserve_frame() ) self.centerfindersettings_window_instance.setModal(False) self.centerfindersettings_window_instance.show() else: self.centerfindersettings_window_instance.raise_() self.centerfindersettings_window_instance.activateWindow()
[docs] def open_centerrefinement_settings_window(self): """ Opens the Center Refinement Settings window. """ if not self.full_ini_file_path or not os.path.exists(self.full_ini_file_path): QMessageBox.warning(self, "No INI File", "Please load an INI file first.") return # Prevent multiple instances if self.centerrefinementsettings_window_instance is None or not self.centerrefinementsettings_window_instance.isVisible(): self.centerrefinementsettings_window_instance = CenterRefinementSettingsWindow( parent=self, ini_directory=self.ini_directory, ini_file_path=self.full_ini_file_path ) # Reload the file when the dialog closes (preserves current frame) self.centerrefinementsettings_window_instance.finished.connect( lambda _result=None: self.reload_file_preserve_frame() ) self.centerrefinementsettings_window_instance.show() else: self.centerrefinementsettings_window_instance.raise_() self.centerrefinementsettings_window_instance.activateWindow()
[docs] def reload_current_file(self): """ Reload the currently loaded INI/HDF5 while keeping the current frame. """ if not self.full_ini_file_path or not os.path.exists(self.full_ini_file_path): QMessageBox.warning(self, "No INI File", "Please load an INI file first.") return try: self.reload_file_preserve_frame() except Exception as e: QMessageBox.critical(self, "Reload Error", f"Failed to reload the current file:\n{e}")
[docs] def reload_file_preserve_frame(self): """Reload the current INI/HDF5 but keep the same frame index.""" self.open_file(self.full_ini_file_path, preserve_frame=True)
[docs] def apply_center_finder_settings(self, settings): # Implement how to apply settings within your application pass
[docs] def ensure_index_dataset(self, hdf5_path): """ Ensure that an 'index' dataset exists in the 'entry/data' group. If not, and if the length of the 'images' dataset matches one of the stage position datasets, create the 'index' dataset as a sequence [0, 1, ..., num_frames-1]. Parameters: hdf5_path (str): The path to the HDF5 file. """ try: with h5py.File(hdf5_path, 'r+') as f: data_group = f.get('entry/data') if data_group is None: log_print("The 'entry/data' group is missing in the HDF5 file.") return if 'index' in data_group: # Index dataset already exists. return if 'images' not in data_group: log_print("The 'images' dataset is missing in the HDF5 file.") return num_frames = data_group['images'].shape[0] # Prefer checking stagepos_x_refined; if missing, use stagepos_y. stagepos_len = None if 'stagepos_x_refined' in data_group: stagepos_len = data_group['stagepos_x_refined'].shape[0] elif 'stagepos_y' in data_group: stagepos_len = data_group['stagepos_y'].shape[0] else: log_print("No stage position dataset ('stagepos_x_refined' or 'stagepos_y') found.") return # If lengths match, create the index dataset. if num_frames == stagepos_len: index_data = np.arange(num_frames) data_group.create_dataset('index', data=index_data) log_print(f"Index dataset created with {num_frames} entries.") else: log_print(f"Mismatch: 'images' dataset has {num_frames} frames, but stage position dataset has {stagepos_len} entries. Index dataset not created.") except Exception as e: log_print(f"Error while ensuring the index dataset: {e}")
[docs] def highlight_selected_file(self): file_label = self.file_name_label.text() if file_label.startswith("File: "): file_name = file_label[6:] files = [self.ini_listbox.item(i).text() for i in range(self.ini_listbox.count())] if file_name in files: idx = files.index(file_name) self.ini_listbox.blockSignals(True) self.ini_listbox.setCurrentRow(idx) self.ini_listbox.blockSignals(False)
def _reset_ui_for_new_file(self): """Disable navigation controls while a new dataset is loading.""" self.total_frames = 0 self.original_image = None self.raw_image = None self.frame_label.setText("Frame: -/-") self.n_peaks_label.setText("Number of Peaks: -") self.beam_center_label.setText("Beam Center: -,-") if hasattr(self, '_clear_strong_peaks_order'): self._clear_strong_peaks_order() if hasattr(self, '_clear_maxres_order'): self._clear_maxres_order() if hasattr(self, '_maxres_order_active'): self._maxres_order_active = False controls = [ self.prev_button, self.next_button, self.frame_slider, self.frame_entry, self.open_map_button, self.go_to_strongest_frame_button, self.process_menu, self.view_menu, self.inspect_image_action, self.reload_file_btn, ] for widget in controls: if widget is not None: widget.setEnabled(False) # Avoid spurious valueChanged signals while resetting self.frame_slider.blockSignals(True) self.frame_slider.setValue(0) self.frame_slider.setMaximum(0) self.frame_slider.blockSignals(False) self.frame_entry.clear() self.show_point_checkbox.setEnabled(False) self.show_point_checkbox.setChecked(False) self.show_peaks_checkbox.setEnabled(False) self.show_peaks_checkbox.setChecked(False) if hasattr(self, 'show_strong_peaks_checkbox'): self.show_strong_peaks_checkbox.blockSignals(True) self.show_strong_peaks_checkbox.setEnabled(False) self.show_strong_peaks_checkbox.setChecked(False) self.show_strong_peaks_checkbox.blockSignals(False) if hasattr(self, '_set_strong_peaks_option_available'): self._set_strong_peaks_option_available(False) if hasattr(self, '_set_maxres_option_available'): self._set_maxres_option_available(False) if hasattr(self, '_sync_frame_order_combo_with_mode'): self._sync_frame_order_combo_with_mode() if hasattr(self, 'show_indexing_checkbox'): self.show_indexing_checkbox.setEnabled(False) self.show_indexing_checkbox.setChecked(False) if hasattr(self, 'show_resolution_rings_checkbox'): self.show_resolution_rings_checkbox.setEnabled(False) self.show_resolution_rings_checkbox.setChecked(False) if hasattr(self, 'aurora_plot_action'): self.aurora_plot_action.setEnabled(False) self._rings_warning_shown = False self._update_indexed_navigation_controls(False) def _is_h5_loader_active(self) -> bool: """Return True while the background HDF5 loader is running.""" return self._h5_loader_active def _start_h5_metadata_loader(self): """Kick off the background worker that reads essential HDF5 metadata.""" if not self.hdf5_path: return token = object() self._h5_loader_token = token self._h5_loader_active = True thread = QThread(self) worker = H5MetadataLoader(self.hdf5_path) worker.moveToThread(thread) thread.started.connect(worker.run) worker.finished.connect(partial(self._on_h5_metadata_ready, token)) worker.failed.connect(partial(self._on_h5_metadata_failed, token)) worker.finished.connect(thread.quit) worker.failed.connect(thread.quit) worker.finished.connect(worker.deleteLater) worker.failed.connect(worker.deleteLater) thread.finished.connect(thread.deleteLater) thread.finished.connect(self._clear_h5_loader_refs) self._h5_loader_thread = thread self._h5_loader_worker = worker thread.start() def _on_h5_metadata_ready(self, token, info: dict): """Handle successful completion of background metadata loading.""" if token != self._h5_loader_token: return self._h5_loader_active = False self._h5_loader_token = None self._clear_status_message() QApplication.restoreOverrideCursor() total_frames = int(info.get('total_frames', 0) or 0) if total_frames <= 0: QMessageBox.warning( self, "HDF5 Error", "The selected HDF5 file does not contain any frames." ) self.hdf5_path = None self.update_workflow_buttons() return self.total_frames = total_frames self._set_frame_slider_range_for_mode() self.frame_slider.setEnabled(True) self.frame_entry.setEnabled(True) self.next_button.setEnabled(self.total_frames > 1) self.prev_button.setEnabled(False) self.process_menu.setEnabled(True) self.view_menu.setEnabled(True) self.inspect_image_action.setEnabled(True) self.reload_file_btn.setEnabled(True) stagepos_exists = bool(info.get('stagepos_exists', False)) self.open_map_button.setEnabled(stagepos_exists) has_center = bool(info.get('has_center', False)) self.show_point_checkbox.setEnabled(has_center) if not has_center: self.show_point_checkbox.setChecked(False) has_peaks = bool(info.get('has_peaks', False)) self.show_peaks_checkbox.setEnabled(has_peaks) if not has_peaks: self.show_peaks_checkbox.setChecked(False) if hasattr(self, 'aurora_plot_action'): self.aurora_plot_action.setEnabled(has_peaks) has_npeaks = bool(info.get('has_npeaks', False)) if hasattr(self, 'show_strong_peaks_checkbox'): if hasattr(self, '_set_strong_peaks_option_available'): self._set_strong_peaks_option_available(has_npeaks) if hasattr(self, '_set_maxres_option_available'): self._set_maxres_option_available(has_npeaks) self.show_strong_peaks_checkbox.blockSignals(True) self.show_strong_peaks_checkbox.setEnabled(has_npeaks) if not has_npeaks: self.show_strong_peaks_checkbox.setChecked(False) self._clear_strong_peaks_order() if hasattr(self, '_clear_maxres_order'): self._clear_maxres_order() if hasattr(self, '_maxres_order_active'): self._maxres_order_active = False else: # Rebuild order lazily for the newly loaded file. self._clear_strong_peaks_order() self._ensure_strong_peaks_order() self.show_strong_peaks_checkbox.blockSignals(False) if hasattr(self, '_sync_frame_order_combo_with_mode'): self._sync_frame_order_combo_with_mode() self._set_frame_slider_range_for_mode() if hasattr(self, 'show_resolution_rings_checkbox'): self.show_resolution_rings_checkbox.setEnabled(True) self.calculate_intensities = False intensity_exists = bool(info.get('intensity_exists', False)) if stagepos_exists and not intensity_exists: msg = QMessageBox(self) msg.setWindowTitle("Missing Intensity Datasets") msg.setText("Intensity datasets are missing. Would you like to calculate them now?") btn_this = msg.addButton("This file only", QMessageBox.ButtonRole.AcceptRole) btn_all = msg.addButton("All workspace files", QMessageBox.ButtonRole.AcceptRole) msg.addButton(QMessageBox.StandardButton.No) msg.exec() clicked = msg.clickedButton() if clicked == btn_this: self.calculate_intensities = True elif clicked == btn_all: self.calculate_intensities = True self._calculate_intensities_for_workspace() self.ensure_index_dataset(self.hdf5_path) if self.calculate_intensities: try: from coseda.pipeline.frame_intensities import calculate_mean_intensities_dask_file_inprocess with self._with_busy_status("Calculating intensities…"): calculate_mean_intensities_dask_file_inprocess(self.hdf5_path, self.log_file_path) except Exception as exc: QMessageBox.critical( self, "Intensity Calculation Failed", f"Failed to calculate intensity datasets:\n{exc}" ) if stagepos_exists: self.ensure_atlas_for_current_file() self.update_map_thumbnail() target_frame = 0 if self._pending_frame_restore is not None: target_frame = min(max(int(self._pending_frame_restore), 0), self.total_frames - 1) self.current_frame_index = target_frame self._sync_frame_slider_with_current_frame() try: self.update_image() finally: self._pending_frame_restore = None if has_npeaks: self.find_strongest_frame() else: self.go_to_strongest_frame_button.setEnabled(False) bit_depth = int(info.get('bit_depth', 16) or 16) if bit_depth < 16: self._set_warning_message(f"Warning: dataset is {bit_depth}-bit (16-bit recommended)") self.update_workflow_buttons()
[docs] def calculate_radial_profiles(self): """Compute radial intensity profiles for this file or all workspace files.""" if not getattr(self, 'hdf5_path', None): QMessageBox.warning(self, "No file", "Open an HDF5 file first.") return msg = QMessageBox(self) msg.setWindowTitle("Calculate radial profiles") msg.setText("Compute per-frame radial intensity profiles?") btn_this = msg.addButton("This file only", QMessageBox.ButtonRole.AcceptRole) btn_all = msg.addButton("All workspace files", QMessageBox.ButtonRole.AcceptRole) msg.addButton(QMessageBox.StandardButton.Cancel) msg.exec() clicked = msg.clickedButton() if clicked == btn_this: self._calculate_radial_profiles_for_file(self.hdf5_path, self.log_file_path) elif clicked == btn_all: self._calculate_radial_profiles_for_workspace()
def _calculate_radial_profiles_for_file(self, h5path, logpath): """Compute and save radial profiles for a single file.""" try: from coseda.pipeline.frame_intensities import calculate_radial_profiles_dask_file_inprocess with self._with_busy_status("Calculating radial profiles…"): calculate_radial_profiles_dask_file_inprocess(h5path, logpath) QMessageBox.information( self, "Radial profiles", "Saved entry/data/frame_radial_intensities." ) except Exception as exc: QMessageBox.critical( self, "Radial profile calculation failed", f"Failed to calculate radial profiles:\n{exc}" ) def _calculate_radial_profiles_for_workspace(self): """Compute radial profiles for all workspace files that are missing them.""" from coseda.pipeline.frame_intensities import calculate_radial_profiles_dask_file_inprocess workspace = getattr(self, 'workspace_ini_paths', {}) if not workspace: QMessageBox.information(self, "Radial profiles", "No files in the workspace.") return files_to_process = [] for name, ini_path in workspace.items(): try: _, _, _, logfile_path, _, h5file_path = config_to_paths(ini_path) with h5py.File(h5file_path, 'r') as f: data_group = f.get('entry/data') if data_group is None: continue if 'frame_radial_intensities' not in data_group: files_to_process.append((h5file_path, logfile_path)) except Exception: continue if not files_to_process: QMessageBox.information( self, "Radial profiles", "All workspace files already have radial profiles." ) return total = len(files_to_process) for idx, (h5path, logpath) in enumerate(files_to_process, 1): try: self._set_status_message(f"Calculating radial profiles ({idx}/{total})", animate=True) QCoreApplication.processEvents() calculate_radial_profiles_dask_file_inprocess(h5path, logpath) except Exception as exc: QMessageBox.warning( self, "Radial profile calculation failed", f"Failed for {os.path.basename(h5path)}:\n{exc}" ) self._clear_status_message() QMessageBox.information( self, "Radial profiles", f"Finished radial profiles for {total} file(s)." ) def _calculate_intensities_for_workspace(self): """Calculate mean intensities for all workspace files that are missing them.""" from coseda.pipeline.frame_intensities import calculate_mean_intensities_dask_file_inprocess workspace = getattr(self, 'workspace_ini_paths', {}) if not workspace: return files_to_process = [] for name, ini_path in workspace.items(): try: _, _, _, logfile_path, _, h5file_path = config_to_paths(ini_path) with h5py.File(h5file_path, 'r') as f: data_group = f.get('entry/data') if data_group is None: continue has_intensities = any( key in data_group for key in ['frame_mean_intensities', 'frame_total_intensities'] ) if not has_intensities: files_to_process.append((h5file_path, logfile_path)) except Exception: continue if not files_to_process: return total = len(files_to_process) for idx, (h5path, logpath) in enumerate(files_to_process, 1): try: self._set_status_message(f"Calculating intensities ({idx}/{total})", animate=True) QCoreApplication.processEvents() calculate_mean_intensities_dask_file_inprocess(h5path, logpath) except Exception as exc: QMessageBox.warning( self, "Intensity Calculation Failed", f"Failed for {os.path.basename(h5path)}:\n{exc}" ) self._clear_status_message() def _on_h5_metadata_failed(self, token, message: str): """Handle background loader failures.""" if token != self._h5_loader_token: return self._h5_loader_active = False self._h5_loader_token = None self._clear_status_message() QApplication.restoreOverrideCursor() QMessageBox.critical( self, "HDF5 Error", f"Error reading HDF5 file:\n{message}" ) self.hdf5_path = None self.total_frames = 0 self._pending_frame_restore = None self.file_name_label.setText("File: -") self.update_workflow_buttons() def _clear_h5_loader_refs(self): """Reset loader thread references once it finishes.""" self._h5_loader_thread = None self._h5_loader_worker = None
[docs] def open_file(self, ini_file_path=None, preserve_frame=False): try: if self._is_h5_loader_active(): QMessageBox.information( self, "Please wait", "The application is still loading the previous file. Please wait a moment." ) return if isinstance(ini_file_path, bool): ini_file_path = None if not ini_file_path: options = QFileDialog.Option.ReadOnly ini_file_path, _ = QFileDialog.getOpenFileName( self, "Open INI File", "", "INI Files (*.ini);;All Files (*)", options=options, ) if not ini_file_path: return if not os.path.exists(ini_file_path): QMessageBox.critical(self, "INI Error", f"INI file not found: {ini_file_path}") return restore_index = self.current_frame_index if preserve_frame else None self._pending_frame_restore = restore_index self.ini_directory = os.path.dirname(ini_file_path) self.full_ini_file_path = ini_file_path config = configparser.ConfigParser() config.read(ini_file_path) outputfolder, outputfolder_path, logfile, logfile_path, h5file, h5file_path = config_to_paths(ini_file_path) self.hdf5_path = h5file_path self.log_file_path = logfile_path self.indexing_manager.reset_for_new_hdf5() self._index_original_to_new = {} self._last_peak_mismatch_frame = None self._h5_width = None self._h5_height = None self._set_indexing_controls_checked(False) self._update_indexed_navigation_controls(False) if not h5file_path or not os.path.exists(h5file_path): QMessageBox.critical(self, "HDF5 Error", f"HDF5 file not found: {h5file_path}") self.hdf5_path = None return self.file_name_label.setText(f"File: {os.path.basename(h5file)}") self.highlight_selected_file() # If the map window is open, switch it to the newly selected file. map_window = getattr(self, "map_window", None) if map_window is not None and map_window.isVisible(): try: map_window.set_hdf5_path(self.hdf5_path, ini_path=self.full_ini_file_path) except Exception as exc: log_print(f"Failed to switch map window file: {exc}", level="warning") # Show a saved map (if any) as a thumbnail in the main window. self.update_map_thumbnail() batch_size_str = config.get('Parameters', 'centerfinding_batch_size', fallback="1000") try: batch_size = int(batch_size_str) except ValueError: batch_size = 1000 if 'Parameters' not in config: config.add_section('Parameters') config['Parameters']['centerfinding_batch_size'] = str(batch_size) with open(ini_file_path, 'w') as config_file: config.write(config_file) self.current_frame_index = 0 self._reset_ui_for_new_file() self.calculate_intensities = False self._set_status_message("Loading HDF5 data", animate=True) QCoreApplication.processEvents() QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) self._start_h5_metadata_loader() except FileNotFoundError as e: QMessageBox.critical(self, "File Not Found", f"The specified file was not found:\n{str(e)}") except configparser.Error as e: QMessageBox.critical(self, "Configuration Error", f"Error parsing INI file:\n{str(e)}") except h5py.HDF5Error as e: QMessageBox.critical(self, "HDF5 Error", f"Error reading HDF5 file:\n{str(e)}") except Exception as e: QMessageBox.critical(self, "Unexpected Error", f"An unexpected error occurred:\n{str(e)}")
[docs] def update_frame_from_map_click(self, frame_index): """ Slot to update the frame index when a point is clicked in the map. """ if 0 <= frame_index < self.total_frames: self.current_frame_index = frame_index self.update_image() else: QMessageBox.warning(self, "Warning", f"Frame index {frame_index} is out of range.")
[docs] def on_indexintegrate_requested_select_ini(self, ini_path: str): """Slot: the Index & Integrate window asked us to select a given INI path. We set the QListWidget selection accordingly; this triggers on_ini_select().""" try: if not ini_path or not os.path.exists(ini_path): return # Find display name for this path in the workspace mapping display_name = None for name, path in self.workspace_ini_paths.items(): if os.path.abspath(path) == os.path.abspath(ini_path): display_name = name break if display_name is None: return # Select in the list without re-entrancy storms items = [self.ini_listbox.item(i).text() for i in range(self.ini_listbox.count())] if display_name in items: idx = items.index(display_name) if self.ini_listbox.currentRow() != idx: self.ini_listbox.setCurrentRow(idx) except Exception as e: log_print(f"on_indexintegrate_requested_select_ini failed: {e}", level="debug")