"""Module for analysis of SMS data from HDF5 files
Bertus van Heerden and Joshua Botha
University of Pretoria
2020
"""
from __future__ import annotations
# import csv
import os
import sys
from platform import system
import ctypes
import importlib
from PyQt5.QtCore import Qt, QThreadPool, pyqtSlot, QRegExp
from PyQt5.QtGui import QIcon, QRegExpValidator # , QResizeEvent
from PyQt5.QtWidgets import (
QMainWindow,
QProgressBar,
QFileDialog,
QMessageBox,
QInputDialog,
QApplication,
QStyleFactory,
) # , QTreeWidget
from PyQt5 import uic
import pyqtgraph as pg
from typing import Union
import time
from multiprocessing import Process, freeze_support
from threading import Lock
from controllers import (
IntController,
LifetimeController,
GroupingController,
SpectraController,
RasterScanController,
AntibunchingController,
FilteringController,
)
from thread_tasks import OpenFile
from threads import ProcessThread
from tree_model import DatasetTreeNode, DatasetTreeModel
# import save_analysis
from settings_dialog import SettingsDialog, Settings
# try:
# import pkg_resources.py2_warn
# except ImportError:
# pass
import smsh5
from generate_sums import CPSums
from custom_dialogs import TimedMessageBox
import file_manager as fm
from my_logger import setup_logger
from file_conversion import ConvertFileDialog
from exporting import ExportWorker, DATAFRAME_FORMATS
from save_and_load import SaveAnalysisWorker, LoadAnalysisWorker
from selection import RangeSelectionDialog
import smsh5_file_reader
# TODO: Needs to rather be reworked not to use recursion, but rather a loop of some sort
sys.setrecursionlimit(1000 * 10)
[docs]
main_window_file = fm.path(name="main_window.ui", file_type=fm.Type.UI)
UI_Main_Window, _ = uic.loadUiType(main_window_file)
[docs]
logger = setup_logger(__name__, is_main=True)
# noinspection PyUnresolvedReferences
[docs]
class MainWindow(QMainWindow, UI_Main_Window):
"""
Class for Full SMS application that returns QMainWindow object.
This class uses a *.ui that is automatically converted to a *.py script
upon exectution.
"""
def __init__(self):
"""Initialise MainWindow object.
Creates and populates QMainWindow object as described by main_window.ui
"""
[docs]
self.threadpool = QThreadPool()
logger.info(
f"Multi-threading with maximum {self.threadpool.maxThreadCount()} threads"
)
[docs]
self.active_threads = []
[docs]
self.confidence_index = {0: 99, 1: 95, 2: 90, 3: 69}
if system() == "Windows":
logger.info("System -> Windows")
elif system() == "Darwin":
logger.info("System -> Unix/Linus")
os.environ["QT_MAC_WANTS_LAYER"] = "1"
else:
logger.info("System -> Other")
QMainWindow.__init__(self)
UI_Main_Window.__init__(self)
self.setupUi(self)
self.setWindowIcon(QIcon(fm.path("Full-SMS.ico", fm.Type.Icons)))
self.tabWidget.setCurrentIndex(0)
self.tabGroupingMode.setCurrentIndex(0)
self.setWindowTitle("Full SMS")
pg.setConfigOption("antialias", True)
# pg.setConfigOption('leftButtonPan', False)
[docs]
self.settings_dialog = SettingsDialog(self, get_saved_settings=True)
[docs]
self.settings = self.settings_dialog.settings
# self.intensity_lifetime_normalization_dialog = \
# FilteringNormalizationDialog(main_window=self)
self.chbInt_Disp_Resolved.hide()
self.chbInt_Disp_Photon_Bursts.hide()
self.chbInt_Disp_Grouped.hide()
self.chbInt_Disp_Using_Groups.hide()
self.chbInt_Show_Groups.setEnabled(False)
self.chbInt_Show_Global_Groups.setEnabled(False)
[docs]
self.intensity_controller = IntController(main_window=self)
[docs]
self.lifetime_controller = LifetimeController(main_window=self)
[docs]
self.grouping_controller = GroupingController(main_widow=self)
[docs]
self.spectra_controller = SpectraController(main_window=self)
[docs]
self.raster_scan_controller = RasterScanController(main_window=self)
[docs]
self.antibunch_controller = AntibunchingController(
self,
corr_widget=self.pgAntibunching_PlotWidget,
corr_sum_widget=self.pgAntibunching_Sum_PlotWidget,
)
# self.antibunch_controller = AntibunchingController(self, corr_widget=self.pgAntibunching_PlotWidget,
# corr_sum_widget=None)
a_c = self.antibunch_controller
self.btnCorrCurrent.clicked.connect(a_c.gui_correlate_current)
self.btnCorrSelected.clicked.connect(a_c.gui_correlate_selected)
self.btnCorrAll.clicked.connect(a_c.gui_correlate_all)
self.spbBinSizeCorr.valueChanged.connect(a_c.rebin_corrs)
[docs]
self.filtering_controller = FilteringController(main_window=self)
# reg_exp = QRegExp("[+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?")
# reg_val = QRegExpValidator(reg_exp)
# self.spbCorrDiff.setValidator(reg_val)
self.btnSubBackground.clicked.connect(self.spectra_controller.gui_sub_bkg)
self.actionOpen_h5.triggered.connect(self.act_open_h5)
self.actionSave_Selected.triggered.connect(self.act_save_selected)
self.actionSave_Analysis.triggered.connect(self.act_save_analysis)
self.actionSelect_All.triggered.connect(self.act_select_all)
self.actionInvert_Selection.triggered.connect(self.act_invert_selection)
self.actionDeselect_All.triggered.connect(self.act_deselect_all)
self.actionTrim_Dead_Traces.triggered.connect(self.act_trim)
self.actionSwitch_All.triggered.connect(self.act_switch_all)
self.actionSwitch_Selected.triggered.connect(self.act_switch_selected)
self.actionSet_Startpoint.triggered.connect(self.act_set_startpoint)
self.actionConvert_file.triggered.connect(self.convert_file_dialog)
self.actionRange_Selection.triggered.connect(self.range_selection)
self.actionSettings.triggered.connect(self.act_open_settings_dialog)
self.actionFiltering_Normalization.triggered.connect(
self.act_filtering_and_normalization_dialog
)
self.actionDetect_Remove_Bursts_Current.triggered.connect(
self.act_detect_remove_bursts_current
)
self.actionDetect_Remove_Bursts_Selected.triggered.connect(
self.act_detect_remove_bursts_selected
)
self.actionDetect_Remove_Bursts_All.triggered.connect(
self.act_detect_remove_bursts_all
)
self.actionRemove_Bursts_Current.triggered.connect(
self.act_remove_bursts_current
)
self.actionRemove_Bursts_Selected.triggered.connect(
self.act_remove_bursts_selected
)
self.actionRemove_Bursts_All.triggered.connect(self.act_remove_bursts_all)
self.actionRestore_Bursts_Current.triggered.connect(
self.act_restore_bursts_current
)
self.actionRestore_Bursts_Selected.triggered.connect(
self.act_restore_bursts_selected
)
self.actionRestore_Bursts_All.triggered.connect(self.act_restore_bursts_all)
self.actionClose.triggered.connect(self.close_file)
self.chbGroup_Use_ROI.stateChanged.connect(self.gui_group_use_roi)
self.btnEx_Current.clicked.connect(self.gui_export_current)
self.btnEx_Selected.clicked.connect(self.gui_export_selected)
self.btnEx_All.clicked.connect(self.gui_export_all)
self.chbEx_Plot_Intensity.clicked.connect(self.gui_plot_intensity_clicked)
self.chbEx_Plot_Lifetimes.clicked.connect(self.gui_plot_lifetime_clicked)
self.chbEx_DF_Traces.stateChanged.connect(self.set_export_options)
self.chbEx_DF_Levels.stateChanged.connect(self.set_export_options)
self.chbEx_DF_Grouped_Levels.stateChanged.connect(self.set_export_options)
self.btnSelectAllExport.clicked.connect(self.select_all_export_options)
self.btnSelectAllExport_Plots.clicked.connect(
self.select_all_plots_export_options
)
self.btnSelectAllExport_DataFrames.clicked.connect(
self.select_all_dataframes_export_options
)
self.chbInt_Select_Groups.stateChanged.connect(
lambda: self.gui_select_groups_changed(self.chbInt_Select_Groups)
)
self.chbLifetime_Select_Groups.stateChanged.connect(
lambda: self.gui_select_groups_changed(self.chbLifetime_Select_Groups)
)
self.lblGrouping_ROI.setVisible(False)
self.cmbEx_DataFrame_Format.addItems(DATAFRAME_FORMATS)
# Create and connect model for dataset tree
[docs]
self.treemodel = DatasetTreeModel()
self.treeViewParticles.setModel(self.treemodel)
# Connect the tree selection to data display
self.treeViewParticles.selectionModel().currentChanged.connect(
self.display_data
)
self.treemodel.dataChanged.connect(self.selection_changed)
self.treeViewParticles.clicked.connect(self.tree_view_clicked)
# self.treeViewParticles.keyPressEvent().connect(self.tree_view_key_press)
[docs]
self._root_was_checked = False
self.comboSelectCard.currentIndexChanged.connect(self.card_selected)
[docs]
self.part_nodes = list()
[docs]
self.part_index = list()
[docs]
self.tauparam = None
[docs]
self.ampparam = None
[docs]
self.decaybg = None
[docs]
self.addopt = None
self.statusBar().showMessage("Ready...")
[docs]
self.progress = QProgressBar(self)
self.progress.setMinimumSize(170, 19)
self.progress.setVisible(False)
self.progress.setValue(0) # Range of values is from 0 to 100
self.statusBar().addPermanentWidget(self.progress)
[docs]
self.current_progress = float()
[docs]
self.data_loaded = False
[docs]
self.irf_loaded = False
# self._current_level = None
self.tabWidget.currentChanged.connect(self.tab_change)
self.tabGroupingMode.currentChanged.connect(self.tab_change)
# self.tabGroupingMode.currentChanged.connect(lambda: self.grouping_controller.plot_group_bic())
[docs]
self.current_dataset = None
[docs]
self.current_particle = None
self.reset_gui()
self.repaint()
"""#######################################
######## GUI Housekeeping Methods ########
#######################################"""
[docs]
def after_show(self):
# self.pgSpectra.resize(self.tabSpectra.size().height(),
# self.tabSpectra.size().height() - self.btnSubBackground.size().height() - 40)
# QEvent.
# QTimer.singleShot(1000)
self.calc_store_sums()
for i in range(100):
time.sleep(1)
print(i)
pass
# def resizeEvent(self, a0: QResizeEvent):
# if self.tabSpectra.size().height() <= self.tabSpectra.size().width():
# self.pgSpectra.resize(self.tabSpectra.size().height(),
# self.tabSpectra.size().height() - self.btnSubBackground.size().height() - 40)
# else:
# self.pgSpectra.resize(self.tabSpectra.size().width(),
# self.tabSpectra.size().width() - 40)
# pass
[docs]
def sums_file_check(self) -> bool:
should_calc = False
sums_path = fm.path(name="all_sums.pickle", file_type=fm.Type.Data)
if (not os.path.exists(sums_path)) and (not os.path.isfile(sums_path)):
self.status_message(
"Calculating change point sums, this may take several minutes."
)
should_calc = True
return should_calc
[docs]
def calc_store_sums(self) -> None:
"""
Check if the all_sums.pickle file exists, and if it doesn't creates it
"""
create_all_sums = CPSums(only_pickle=True, n_min=10, n_max=1000)
del create_all_sums
self.status_message("Ready...")
[docs]
def gui_export_current(self):
self.gui_export(mode="current")
[docs]
def gui_export_selected(self):
self.gui_export(mode="selected")
[docs]
def gui_export_all(self):
self.gui_export(mode="all")
[docs]
def gui_select_groups_changed(self, chb_changed):
chb_other = (
self.chbLifetime_Select_Groups
if chb_changed is self.chbInt_Select_Groups
else self.chbInt_Select_Groups
)
chb_other.setChecked(chb_changed.isChecked())
[docs]
def act_detect_remove_bursts_current(self):
self.detect_remove_bursts(mode="current")
[docs]
def act_detect_remove_bursts_selected(self):
self.detect_remove_bursts(mode="selected")
[docs]
def act_detect_remove_bursts_all(self):
self.detect_remove_bursts(mode="all")
[docs]
def act_remove_bursts_current(self):
self.remove_bursts(mode="current", confirm=False)
[docs]
def act_remove_bursts_selected(self):
self.remove_bursts(mode="selected", confirm=False)
[docs]
def act_remove_bursts_all(self):
self.remove_bursts(mode="all", confirm=False)
[docs]
def act_restore_bursts_current(self):
self.restore_bursts(mode="current")
[docs]
def act_restore_bursts_selected(self):
self.restore_bursts(mode="selected")
[docs]
def act_restore_bursts_all(self):
self.restore_bursts(mode="all")
[docs]
def set_bin_size(self, bin_size: int):
self.spbBinSize.setValue(bin_size)
[docs]
def act_open_settings_dialog(self):
self.settings_dialog.exec()
[docs]
def act_filtering_and_normalization_dialog(self):
self.intensity_lifetime_normalization_dialog.exec()
[docs]
def gui_group_use_roi(self):
if self.data_loaded:
use_roi = self.chbGroup_Use_ROI.isChecked()
for particle in self.current_dataset.particles:
particle.ahca.use_roi_for_grouping = use_roi
[docs]
def act_open_h5(self):
"""Allows the user to point to a h5 file and then starts a thread that reads and loads the file."""
logger.info("Performing Open H5 Action")
last_opened_file = fm.path(
name="last_opened.txt", file_type=fm.Type.ResourcesRoot
)
last_opened_path = ""
if os.path.exists(last_opened_file) and os.path.isfile(last_opened_file):
with open(last_opened_file, "r") as file:
last_opened_path = file.read()
if not os.path.isdir(last_opened_path):
last_opened_path = ""
file_path = QFileDialog.getOpenFileName(
self, "Open HDF5 file", last_opened_path, "HDF5 files (*.h5)"
)
did_open = False
loading_analysis = False
if os.path.exists(file_path[0][:-2] + "smsa") and os.path.isfile(
file_path[0][:-2] + "smsa"
):
msg_box = QMessageBox(parent=self)
msg_box.setWindowTitle("Load analysis?")
msg_box.setText("Analysis file found. Would you like to load it?")
msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
msg_box.exec()
if msg_box.result() == QMessageBox.Yes:
load_analysis_worker = LoadAnalysisWorker(
main_window=self, file_path=file_path[0][:-2] + "smsa"
)
load_analysis_worker.signals.status_message.connect(self.status_message)
load_analysis_worker.signals.start_progress.connect(self.start_progress)
load_analysis_worker.signals.end_progress.connect(self.end_progress)
load_analysis_worker.signals.error.connect(self.error_handler)
load_analysis_worker.signals.openfile_finished.connect(
self.open_file_thread_complete
)
load_analysis_worker.signals.save_file_version_outdated.connect(
self.open_save_file_version_outdated
)
load_analysis_worker.signals.show_residual_widget.connect(
self.lifetime_controller.show_residuals_widget
)
self.threadpool.start(load_analysis_worker)
loading_analysis = True
did_open = True
if file_path != ("", "") and not loading_analysis:
self.status_message(message="Opening file...")
# logger.info("About to create ProcessThread object")
of_process_thread = ProcessThread(num_processes=1)
# logger.info("About to connect signals")
of_process_thread.worker_signals.add_datasetindex.connect(self.add_dataset)
of_process_thread.worker_signals.add_particlenode.connect(self.add_node)
of_process_thread.worker_signals.add_all_particlenodes.connect(
self.add_all_nodes
)
of_process_thread.worker_signals.bin_size.connect(self.set_bin_size)
of_process_thread.worker_signals.data_loaded.connect(self.set_data_loaded)
of_process_thread.signals.status_update.connect(self.status_message)
of_process_thread.signals.start_progress.connect(self.start_progress)
of_process_thread.signals.set_progress.connect(self.set_progress)
of_process_thread.signals.step_progress.connect(self.update_progress)
of_process_thread.signals.add_progress.connect(self.update_progress)
of_process_thread.signals.end_progress.connect(self.end_progress)
of_process_thread.signals.error.connect(self.error_handler)
of_process_thread.signals.finished.connect(self.open_file_thread_complete)
# logger.info("About to create OpenFile object")
of_obj = OpenFile(
file_path=file_path
) # , progress_tracker=of_progress_tracker)
of_process_thread.add_tasks_from_methods(of_obj, "open_h5")
# logger.info("About to start Process Thread")
self.threadpool.start(of_process_thread)
# logger.info("Started Process Thread")
self.active_threads.append(of_process_thread)
did_open = True
if did_open:
with open(last_opened_file, "w") as file:
file.write(os.path.split(file_path[0])[0])
[docs]
def act_save_selected(self):
""" " Saves selected particles into a new HDF5 file."""
msg = QMessageBox(self)
msg.setWindowTitle("Still in development")
msg.setText("This functionality is still in development")
msg.setStandardButtons(QMessageBox.Ok)
msg.exec_()
return
selected_nums = self.get_checked_nums()
if not len(selected_nums):
msg = QMessageBox(self)
msg.setIcon(QMessageBox.Critical)
msg.setWindowTitle("Save Error")
msg.setText("No particles selected.")
msg.setStandardButtons(QMessageBox.Ok)
msg.exec()
return
fname, _ = QFileDialog.getSaveFileName(
self,
"New or Existing HDF5 file",
"",
"HDF5 files (*.h5)",
options=QFileDialog.DontConfirmOverwrite,
)
if os.path.exists(fname[0]):
msg = QMessageBox(self)
msg.setIcon(QMessageBox.Question)
msg.setWindowTitle("Add To Existing File")
msg.setText("Do you want to add selected particles to existing file?")
msg.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel)
if msg.exec() == QMessageBox.Cancel:
return
if self.current_dataset.name == fname:
msg = QMessageBox(self)
msg.setIcon(QMessageBox.Critical)
msg.setWindowTitle("Save Error")
msg.setText("Can" "t add particles to currently opened file.")
msg.setStandardButtons(QMessageBox.Ok)
msg.exec()
return
self.current_dataset.save_particles(fname, selected_nums)
[docs]
def act_save_analysis(self):
if self.current_dataset is not None:
save_analysis_worker = SaveAnalysisWorker(
main_window=self, dataset=self.current_dataset
)
save_analysis_worker.signals.status_message.connect(self.status_message)
save_analysis_worker.signals.start_progress.connect(self.start_progress)
save_analysis_worker.signals.end_progress.connect(self.end_progress)
save_analysis_worker.signals.error.connect(self.error_handler)
self.threadpool.start(save_analysis_worker)
# save_analysis.save_analysis(self, self.current_dataset)
[docs]
def act_trim(self):
"""Used to trim the 'dead' part of a trace as defined by two parameters."""
print("act_trim")
[docs]
def act_switch_all(self):
self.switching_frequency(all_selected="all")
[docs]
def act_switch_selected(self):
self.switching_frequency(all_selected="selected")
[docs]
def act_set_startpoint(self):
start, ok = QInputDialog.getInt(self, "Input Dialog", "Enter startpoint:")
self.set_startpoint(start)
[docs]
def set_startpoint(self, irf_data=None, start=None):
if start is None:
start = self.lifetime_controller.startpoint
try:
# self.tree2dataset().makehistograms(remove_zeros=False, startpoint=start, channel=True)
dataset = self.current_dataset
dataset.makehistograms(remove_zeros=False, startpoint=start, channel=True)
except Exception as exc:
print(exc)
if self.lifetime_controller.irf_loaded and irf_data:
self.lifetime_controller.change_irf_start(start, irf_data)
if self.lifetime_controller.startpoint is None:
self.lifetime_controller.startpoint = start
self.display_data()
logger.info("Set startpoint")
"""#######################################
############ Internal Methods ############
#######################################"""
[docs]
def add_dataset(self, dataset_node):
self.dataset_node = dataset_node
self.dataset_index = self.treemodel.addChild(dataset_node)
self.current_dataset = dataset_node.dataobj
[docs]
def add_node(self, particle_node, num):
index = self.treemodel.addChild(
particle_node, self.dataset_index
) # , progress_sig)
if num == 0:
self.treeViewParticles.expand(self.dataset_index)
self.treeViewParticles.setCurrentIndex(index)
self.current_particle = particle_node.dataobj
self.part_nodes.append(particle_node)
self.part_index.append(index)
[docs]
def add_all_nodes(self, all_nodes):
for node, num in all_nodes:
if num == -1:
assert (
type(node.dataobj) is smsh5.H5dataset
), "First node must be for H5Dataset"
self.add_dataset(node)
self.treeViewParticles.expand(self.dataset_index)
# index = self.treemodel.addChild(node, self.datasetindex) # , progress_sig)
else:
assert type(node.dataobj) is smsh5.Particle, "Node must be for Particle"
self.add_node(node, num)
[docs]
def tree_view_clicked(self, model_index):
if type(self.treemodel.data(model_index, Qt.UserRole)) is smsh5.Particle:
self.set_export_options()
self.grouping_controller.check_rois_and_set_label()
self.lifetime_controller.update_apply_roi_button_colors()
if self.treemodel.data(model_index, Qt.UserRole) is self.dataset_node.dataobj:
root_node_checked = self.dataset_node.checked()
if all([node.checked() for node in self.part_nodes]) != root_node_checked:
for part_node in self.part_nodes:
part_node.setChecked(root_node_checked)
self._root_was_checked = root_node_checked
if root_node_checked:
self.lblNum_Selected.setText(str(len(self.part_nodes)))
else:
self.lblNum_Selected.setText("0")
self.treeViewParticles.viewport().repaint()
else:
checked_list = [node.checked() for node in self.part_nodes]
all_checked = all(checked_list)
self.dataset_node.setChecked(all_checked)
num_checked = sum(checked_list)
self.lblNum_Selected.setText(str(num_checked))
self.treeViewParticles.viewport().repaint()
[docs]
def tree_view_key_press(self, event):
pass
[docs]
def act_select_all(self, *args, **kwargs):
if self.data_loaded:
for node in self.part_nodes:
node.setChecked(True)
self.lblNum_Selected.setText(str(len(self.part_nodes)))
[docs]
def act_invert_selection(self, *args, **kwargs):
if self.data_loaded:
for node in self.part_nodes:
node.setChecked(not node.checked())
num_checked = sum([node.checked() for node in self.part_nodes])
self.lblNum_Selected.setText(str(num_checked))
self.treeViewParticles.viewport().repaint()
[docs]
def act_deselect_all(self, *args, **kwargs):
if self.data_loaded:
for node in self.part_nodes:
node.setChecked(False)
self.lblNum_Selected.setText("0")
[docs]
def tab_change(self, active_tab_index: int):
if self.data_loaded and hasattr(self, "current_particle"):
tabs_to_display = [
"tabIntensity",
"tabLifetime",
"tabGrouping",
"tabAntibunching",
"tabSpectra",
"tabRaster_Scan",
]
if self.tabWidget.currentWidget().objectName() in tabs_to_display:
self.display_data()
elif self.tabWidget.currentWidget().objectName() == "tabFiltering":
self.filtering_controller.set_levels_to_use()
[docs]
def update_int_gui(self):
cur_part = self.current_particle
if cur_part.has_levels:
self.chbInt_Disp_Resolved.show()
else:
self.chbInt_Disp_Resolved.hide()
if cur_part.has_burst:
self.chbInt_Disp_Photon_Bursts.show()
if cur_part.cpts.bursts_deleted is not None:
self.chbInt_Disp_Photon_Bursts.setChecked(True)
else:
self.chbInt_Disp_Photon_Bursts.setChecked(False)
else:
self.chbInt_Disp_Photon_Bursts.hide()
if cur_part.has_groups:
self.chbInt_Disp_Grouped.show()
self.chbInt_Show_Groups.setEnabled(True)
else:
self.chbInt_Disp_Grouped.hide()
self.chbInt_Show_Groups.setEnabled(False)
self.chbInt_Show_Global_Groups.setEnabled(cur_part.has_global_grouping)
if cur_part.using_group_levels:
self.chbInt_Disp_Using_Groups.show()
else:
self.chbInt_Disp_Using_Groups.hide()
[docs]
def card_selected(self) -> None:
self.display_data(combocard=True)
[docs]
def selection_changed(self) -> None:
self.display_data()
[docs]
def display_data(
self, current=None, prev=None, combocard=False, is_global_group=False
) -> None:
"""Displays the intensity trace and the histogram of the current particle.
Directly called by the tree signal currentChanged, thus the two arguments.
Arguments
---------
current : QtCore.QModelIndex
The index of the current selected particle as defined by QtCore.QModelIndex.
prev : QtCore.QModelIndex
The index of the previous selected particle as defined by QtCore.QModelIndex.
combocard : bool
True if called due to selecting other TCSPC card.
"""
# self.current_level = None
# self.current_ind = current
# self.pre_ind = prev
self.treeViewParticles.viewport().repaint()
if current is not None:
if hasattr(self, "current_particle"):
self.current_particle = self.treemodel.get_particle(current)
# self.current_level = None # Reset current level when particle changes.
if (
hasattr(self, "current_particle")
and type(self.current_particle) is smsh5.Particle
):
# Select primary or secondary particle based on selected tcspc card
if (
self.comboSelectCard.currentIndex() == 1
and self.current_particle.sec_part is not None
):
assert not self.current_particle.is_secondary_part
self.current_particle = self.current_particle.sec_part
elif (
self.comboSelectCard.currentIndex() == 0
and self.current_particle.is_secondary_part
):
self.current_particle = self.current_particle.prim_part
cur_tab_name = self.tabWidget.currentWidget().objectName()
self.txtDescription.setText(self.current_particle.description)
# If not called due to a change in selected card, update the card selector with available choices
if not combocard:
if not self.current_particle.is_secondary_part:
card1 = self.current_particle.tcspc_card
if self.current_particle.sec_part is not None:
card2 = self.current_particle.sec_part.tcspc_card
else:
card2 = None
else:
card1 = self.current_particle.prim_part.tcspc_card
card2 = self.current_particle.tcspc_card
if self.comboSelectCard.count() == 0:
self.comboSelectCard.insertItem(0, card1)
self.comboSelectCard.insertItem(1, card2)
else:
self.comboSelectCard.setItemText(0, card1)
if self.comboSelectCard.count() == 1:
self.comboSelectCard.insertItem(1, card2)
else:
self.comboSelectCard.setItemText(1, card2)
assert self.comboSelectCard.count() <= 2
if cur_tab_name in ["tabIntensity", "tabGrouping", "tabLifetime"]:
if cur_tab_name == "tabIntensity":
self.update_int_gui()
self.intensity_controller.set_bin(self.current_particle.bin_size)
self.intensity_controller.plot_trace()
if self.current_particle.has_levels:
self.intensity_controller.plot_levels()
self.intensity_controller.update_level_info()
if cur_tab_name != "tabLifetime":
self.intensity_controller.plot_hist()
else:
use_selected = (
False
if self.current_particle.level_or_group_selected is None
else True
)
self.lifetime_controller.plot_decay(
use_selected=use_selected, remove_empty=False
)
self.lifetime_controller.plot_convd(use_selected=use_selected)
self.lifetime_controller.plot_residuals(use_selected=use_selected)
self.lifetime_controller.update_results(use_selected=use_selected)
self.lifetime_controller.update_apply_roi_button_colors()
if (
self.current_particle.has_groups
or self.current_particle.has_global_grouping
or is_global_group
):
# if not is_global_group:
self.intensity_controller.plot_group_bounds()
if cur_tab_name == "tabGrouping":
self.grouping_controller.plot_group_bic(
is_global_group=is_global_group
)
else:
self.grouping_controller.clear_bic()
elif cur_tab_name == "tabSpectra" and self.current_particle.has_spectra:
self.spectra_controller.plot_spectra()
elif (
cur_tab_name == "tabRaster_Scan"
and self.current_particle.has_raster_scan
):
self.raster_scan_controller.plot_raster_scan()
elif cur_tab_name == "tabAntibunching":
self.antibunch_controller.plot_corr()
elif cur_tab_name == "tabExport":
self.set_export_options()
# Set Enables
# set_apply_groups = False
# if self.current_particle.has_levels:
# self.int_controller.plot_levels()
# set_group = True
# if self.current_particle.has_groups:
# set_apply_groups = True
# else:
# set_apply_groups = False
# else:
# set_group = False
# self.btnGroupCurrent.setEnabled(set_group)
# self.btnGroupSelected.setEnabled(set_group)
# self.btnGroupAll.setEnabled(set_group)
# self.btnApplyGroupsCurrent.setEnabled(set_apply_groups)
# self.btnApplyGroupsSelected.setEnabled(set_apply_groups)
# self.btnApplyGroupsAll.setEnabled(set_apply_groups)
logger.info(f"{self.current_particle.name} data displayed")
[docs]
def status_message(self, message: str) -> None:
"""
Updates the status bar with the provided message argument.
Parameters
----------
message : str
The message that is to be displayed in the status bar.
"""
if message != "":
self.statusBar().showMessage(message)
# self.statusBar().show()
else:
self.statusBar().clearMessage()
[docs]
def start_progress(self, max_num: int = None) -> None:
"""
Sets the maximum value of the progress bar before use.
reset parameter can be optionally set to False to prevent the setting of the progress bar value to 0.
Parameters
----------
max_num : int
The number of iterations or steps that the complete process is made up of.
"""
if max_num:
assert (
type(max_num) is int
), "MainWindow:\tThe type of the 'max_num' parameter is not int."
self.progress.setMaximum(max_num)
# print(max_num)
self.progress.setValue(0)
self.progress.setVisible(True)
self.current_progress = 0
self.progress.repaint()
self.statusBar().repaint()
self.repaint()
[docs]
def set_progress(self, progress_value: int) -> None:
"""
Sets the maximum value of the progress bar before use.
reset parameter can be optionally set to False to prevent the setting of the progress bar value to 0.
Parameters
----------
progress_value : int
The number of iterations or steps that the complete process is made up of.
"""
assert (
type(progress_value) is int
), "MainWindow:\tThe type of the 'max_num' parameter is not int."
self.progress.setValue(progress_value)
self.progress.repaint()
self.statusBar().repaint()
self.repaint()
[docs]
def update_progress(self, value: Union[int, float] = None) -> None:
"""Used to update the progress bar by an increment of one. If at maximum sets progress bars visibility to False"""
if not value:
value = 1.0
if self.progress.isVisible():
self.current_progress += value
new_show_value = int(self.current_progress // 1)
self.progress.setValue(new_show_value)
# print(self.current_progress)
if self.current_progress >= self.progress.maximum():
self.end_progress()
self.progress.repaint()
self.statusBar().repaint()
self.repaint()
[docs]
def end_progress(self):
self.current_progress = 0
self.progress.setValue(0)
self.progress.setMaximum(0)
self.progress.setVisible(False)
self.progress.repaint()
self.statusBar().repaint()
self.repaint()
[docs]
def tree2particle(self, identifier):
"""Returns the particle dataset for the identifier given.
The identifier could be the number of the particle of the datasetnode value.
Parameters
----------
identifier
The integer number or a datasetnode object of the particle in question.
Returns
-------
"""
if type(identifier) is int:
return self.dataset_index.child(identifier, 0).data(Qt.UserRole)
if type(identifier) is DatasetTreeNode:
return identifier.dataobj
[docs]
def tree2dataset(self) -> smsh5.H5dataset:
"""Returns the H5dataset object of the file loaded.
Returns
-------
smsh5.H5dataset
"""
# return self.treemodel.data(self.treemodel.index(0, 0), Qt.UserRole)
return self.dataset_index.data(Qt.UserRole)
[docs]
def set_data_loaded(self):
self.data_loaded = True
[docs]
def open_save_file_version_outdated(self):
msg_box = QMessageBox(parent=self)
msg_box.setWindowTitle("Save File Outdated")
msg_box.setText("The save file is outdated. Please reload *.h5 file instead.")
msg_box.setStandardButtons(QMessageBox.Ok)
msg_box.exec()
[docs]
def open_file_thread_complete(
self, thread: ProcessThread = None, irf=False
) -> None:
"""Is called as soon as all of the threads have finished."""
if self.data_loaded and not irf:
self.current_dataset = self.tree2dataset()
self.treeViewParticles.expandAll()
print(self.part_index)
self.treeViewParticles.setCurrentIndex(self.part_index[0])
self.current_particle = self.tree2particle(0)
any_spectra = any(
[part.has_spectra for part in self.current_dataset.particles]
)
if any_spectra:
self.current_dataset.has_spectra = True
if not any([p.has_levels for p in self.current_dataset.particles]) and self.settings.auto_resolve_levels:
msgbx = TimedMessageBox(30, parent=self)
msgbx.setIcon(QMessageBox.Question)
msgbx.setText("Would you like to resolve levels now?")
msgbx.set_timeout_text(
message_pretime="(Resolving levels in ",
message_posttime=" seconds)",
)
msgbx.setWindowTitle("Resolve Levels?")
msgbx.setStandardButtons(QMessageBox.No | QMessageBox.Yes)
msgbx.setDefaultButton(QMessageBox.Yes)
msgbx_result, timed_out = msgbx.exec()
if msgbx_result == QMessageBox.Yes:
confidences = ("0.99", "0.95", "0.90", "0.69")
index = None
if timed_out:
index = 0
else:
item, ok = QInputDialog.getItem(
self,
"Choose Confidence",
"Select confidence interval to use.",
confidences,
0,
False,
)
if ok:
index = list(self.confidence_index.values()).index(
int(float(item) * 100)
)
if index is not None:
self.cmbConfIndex.setCurrentIndex(index)
self.intensity_controller.start_resolve_thread("all")
if self.data_loaded:
self.actionSave_Analysis.setEnabled(True)
self.actionSelect_All.setEnabled(True)
self.actionInvert_Selection.setEnabled(True)
self.actionDeselect_All.setEnabled(True)
self.actionRange_Selection.setEnabled(True)
self.menuIntensity.setEnabled(True)
self.menuLifetime.setEnabled(True)
self.chbEx_Use_ROI.setEnabled(True)
self.chbInt_Show_ROI.setEnabled(True)
self.chbGroup_Use_ROI.setEnabled(True)
self.chbEx_Trace.setEnabled(True)
self.chbEx_Hist.setEnabled(True)
self.chbEx_Plot_Intensity.setEnabled(True)
self.rdbInt_Only.setEnabled(True)
self.chbEx_Plot_Lifetimes.setEnabled(True)
self.rdbHist_Only.setEnabled(True)
self.actionRange_Selection.setEnabled(True)
self.set_export_options()
self.reset_gui()
self.chbInt_Show_ROI.setCheckState(1)
self.display_data()
logger.info("File opened")
[docs]
def set_export_options(self):
particles = self.get_checked_particles()
# particles.append(self.current_particle)
if len(particles) == 0:
particles = [self.current_particle]
all_have_levels = all([p.has_levels for p in particles])
all_have_any_groups = all(
[p.has_groups or p.has_global_grouping for p in particles]
)
all_have_individual_groups = all([p.has_groups for p in particles])
all_have_global_groups = all([p.has_global_grouping for p in particles])
all_have_lifetimes = all([p.has_fit_a_lifetime for p in particles])
all_have_raster_scans = all([p.has_raster_scan for p in particles])
all_have_spectra = all([p.has_spectra for p in particles])
all_have_corr = all([p.has_corr for p in particles])
self.chbEx_Levels.setEnabled(all_have_levels)
self.chbEx_DF_Levels.setEnabled(all_have_levels)
if self.chbEx_DF_Levels.isChecked() and all_have_lifetimes:
self.chbEx_DF_Levels_Lifetimes.setEnabled(True)
else:
self.chbEx_DF_Levels_Lifetimes.setEnabled(False)
self.chbEx_Grouped_Levels.setEnabled(all_have_individual_groups)
self.chbEx_Global_Grouped_Levels.setEnabled(all_have_global_groups)
self.chbEx_DF_Grouped_Levels.setEnabled(all_have_individual_groups)
if self.chbEx_DF_Grouped_Levels.isChecked() and all_have_individual_groups:
self.chbEx_DF_Grouped_Levels_Lifetimes.setEnabled(True)
else:
self.chbEx_DF_Grouped_Levels_Lifetimes.setEnabled(False)
self.chbEx_DF_Global_Grouped_Levels.setEnabled(all_have_global_groups)
self.chbEx_Grouping_Info.setEnabled(all_have_individual_groups)
self.chbEx_Grouping_Results.setEnabled(all_have_individual_groups)
self.chbEx_DF_Grouping_Info.setEnabled(all_have_individual_groups)
# Hists always enabled
self.chbEx_Lifetimes.setEnabled(all_have_lifetimes)
self.chbEx_Spectra_2D.setEnabled(all_have_spectra)
# self.chbEx_Spectra_Fitting.setEnabled(False) # Add when spectra analysis added
# self.chbEx_Spectra_Traces.setEnabled(False) # Add when spectra analysis added
# Int plot always enalbed
self.rdbInt_Only.setEnabled(True)
if not (all_have_individual_groups or all_have_levels):
self.rdbInt_Only.setChecked(True)
self.rdbWith_Levels.setEnabled(all_have_levels)
self.rdbAnd_Groups.setEnabled(all_have_individual_groups)
# Always able to export traces
self.chbEx_DF_Traces.setEnabled(True)
# self.chbEx_Hist.setEnabled(all_have_lifetimes) # Shouldn't this be true always?
self.chbEx_Plot_Group_BIC.setEnabled(all_have_any_groups)
self.chbEx_Plot_Lifetimes.setEnabled(all_have_lifetimes)
self.rdbWith_Fit.setEnabled(all_have_lifetimes)
self.rdbAnd_Residuals.setEnabled(all_have_lifetimes)
self.chbEx_Plot_Lifetimes_Only_Groups.setEnabled(all_have_lifetimes)
self.chbEx_Plot_Spectra.setEnabled(all_have_spectra)
self.chbEx_Raster_Scan_2D.setEnabled(all_have_raster_scans)
self.chbEx_Plot_Raster_Scans.setEnabled(all_have_raster_scans)
self.chbEx_Corr.setEnabled(all_have_corr)
self.chbEx_Plot_Corr.setEnabled(all_have_corr)
[docs]
def select_all_export_options(self):
self.chbEx_Trace.setChecked(self.chbEx_Trace.isEnabled())
self.chbEx_Levels.setChecked(self.chbEx_Levels.isEnabled())
self.chbEx_Grouped_Levels.setChecked(self.chbEx_Grouped_Levels.isEnabled())
self.chbEx_Grouping_Info.setChecked(self.chbEx_Grouping_Info.isEnabled())
self.chbEx_Grouping_Results.setChecked(self.chbEx_Grouping_Results.isEnabled())
self.chbEx_DF_Grouping_Info.setChecked(self.chbEx_DF_Grouping_Info.isEnabled())
self.chbEx_Hist.setChecked(self.chbEx_Hist.isEnabled())
self.chbEx_Lifetimes.setChecked(self.chbEx_Lifetimes.isEnabled())
self.chbEx_Spectra_2D.setChecked(self.chbEx_Spectra_2D.isEnabled())
# self.chbEx_Spectra_Fitting.setChecked(self.chbEx_Spectra_Fitting.isEnabled())
# self.chbEx_Sptecra_Traces.setChecked(self.chbEx_Sptecra_Traces.isEnabled())
self.chbEx_Plot_Intensity.setChecked(self.chbEx_Plot_Intensity.isEnabled())
self.rdbInt_Only.setChecked(self.rdbInt_Only.isEnabled())
self.rdbWith_Levels.setChecked(self.rdbWith_Levels.isEnabled())
self.rdbAnd_Groups.setChecked(self.rdbAnd_Groups.isEnabled())
self.chbEx_Plot_Group_BIC.setChecked(self.chbEx_Plot_Group_BIC.isEnabled())
self.chbEx_Plot_Lifetimes.setChecked(self.chbEx_Plot_Lifetimes.isEnabled())
self.rdbHist_Only.setChecked(self.rdbHist_Only.isEnabled())
self.rdbWith_Fit.setChecked(self.rdbWith_Fit.isEnabled())
self.rdbAnd_Residuals.setChecked(self.rdbAnd_Residuals.isEnabled())
self.chbEx_Plot_Spectra.setChecked(self.chbEx_Plot_Spectra.isEnabled())
self.chbEx_Raster_Scan_2D.setChecked(self.chbEx_Raster_Scan_2D.isEnabled())
self.chbEx_Plot_Raster_Scans.setChecked(
self.chbEx_Plot_Raster_Scans.isEnabled()
)
# Not sure if there is only duplication of below
# self.chbEx_DF_Levels.setChecked(self.chbEx_DF_Levels.isEnabled())
# self.chbEx_DF_Levels_Lifetimes.setChecked(self.chbEx_DF_Levels_Lifetimes.isEnabled())
# self.chbEx_DF_Grouped_Levels.setChecked(self.chbEx_DF_Grouped_Levels.isEnabled())
# self.chbEx_DF_Grouped_Levels_Lifetimes.setChecked(
# self.chbEx_DF_Grouped_Levels_Lifetimes.isEnabled())
# self.chbEx_DF_Grouping_Info.setChecked(self.chbEx_DF_Grouping_Info.isEnabled())
self.chbEx_DF_Traces.setChecked(self.chbEx_DF_Traces.isEnabled())
self.chbEx_DF_Levels.setChecked(self.chbEx_DF_Levels.isEnabled())
self.chbEx_DF_Levels_Lifetimes.setChecked(
self.chbEx_DF_Levels_Lifetimes.isEnabled()
)
self.chbEx_DF_Grouped_Levels.setChecked(
self.chbEx_DF_Grouped_Levels.isEnabled()
)
self.chbEx_DF_Grouped_Levels_Lifetimes.setChecked(
self.chbEx_DF_Grouped_Levels_Lifetimes.isEnabled()
)
self.chbEx_DF_Grouping_Info.setChecked(self.chbEx_DF_Grouping_Info.isEnabled())
[docs]
def select_all_plots_export_options(self):
self.chbEx_Plot_Intensity.setChecked(self.chbEx_Plot_Intensity.isEnabled())
self.rdbInt_Only.setChecked(self.rdbInt_Only.isEnabled())
self.rdbWith_Levels.setChecked(self.rdbWith_Levels.isEnabled())
self.rdbAnd_Groups.setChecked(self.rdbAnd_Groups.isEnabled())
self.chbEx_Plot_Group_BIC.setChecked(self.chbEx_Plot_Group_BIC.isEnabled())
self.chbEx_Plot_Lifetimes.setChecked(self.chbEx_Plot_Lifetimes.isEnabled())
self.rdbHist_Only.setChecked(self.rdbHist_Only.isEnabled())
self.rdbWith_Fit.setChecked(self.rdbWith_Fit.isEnabled())
self.rdbAnd_Residuals.setChecked(self.rdbAnd_Residuals.isEnabled())
self.chbEx_Plot_Spectra.setChecked(self.chbEx_Plot_Spectra.isEnabled())
self.chbEx_Plot_Raster_Scans.setChecked(
self.chbEx_Plot_Raster_Scans.isEnabled()
)
[docs]
def select_all_dataframes_export_options(self):
self.chbEx_DF_Traces.setChecked(self.chbEx_DF_Traces.isEnabled())
self.chbEx_DF_Levels.setChecked(self.chbEx_DF_Levels.isEnabled())
self.chbEx_DF_Levels_Lifetimes.setChecked(
self.chbEx_DF_Levels_Lifetimes.isEnabled()
)
self.chbEx_DF_Grouped_Levels.setChecked(
self.chbEx_DF_Grouped_Levels.isEnabled()
)
self.chbEx_DF_Grouped_Levels_Lifetimes.setChecked(
self.chbEx_DF_Grouped_Levels_Lifetimes.isEnabled()
)
self.chbEx_DF_Grouping_Info.setChecked(self.chbEx_DF_Grouping_Info.isEnabled())
@pyqtSlot(Exception)
[docs]
def open_file_error(self, err: Exception):
# logger.error(err)
pass
[docs]
def detect_remove_bursts(self, mode: str = None) -> None:
if mode == "current":
particles = [self.current_particle]
elif mode == "selected":
particles = self.get_checked_particles()
else:
particles = self.current_dataset.particles
for part in particles:
part.cpts.check_burst()
self.remove_bursts(mode=mode)
[docs]
def remove_bursts(self, mode: str = None, confirm: bool = True) -> None:
if mode == "current":
particles = [self.current_particle]
elif mode == "selected":
particles = self.get_checked_particles()
else:
particles = self.current_dataset.particles
has_burst = [particle.has_burst for particle in particles]
if sum(has_burst):
if confirm:
msgbx = TimedMessageBox(30, parent=self)
msgbx.setIcon(QMessageBox.Question)
msgbx.setText("Would you like to remove the photon bursts?")
msgbx.set_timeout_text(
message_pretime="(Removing photon bursts in ",
message_posttime=" seconds)",
)
msgbx.setWindowTitle("Photon bursts detected")
msgbx.setStandardButtons(QMessageBox.No | QMessageBox.Yes)
msgbx.setDefaultButton(QMessageBox.Yes)
msgbx.show()
msgbx_result, _ = msgbx.exec()
if not confirm or msgbx_result == QMessageBox.Yes:
for particle in particles:
if particle.has_burst:
particle.cpts.remove_bursts()
particle.makelevelhists()
if particle.has_groups:
particle.remove_and_reset_grouping()
self.display_data()
[docs]
def restore_bursts(self, mode: str = None) -> None:
if mode == "current":
particles = [self.current_particle]
elif mode == "selected":
particles = self.get_checked_particles()
else:
particles = self.current_dataset.particles
for part in particles:
if part.cpts.bursts_deleted is not None:
part.cpts.restore_bursts()
part.makelevelhists()
if part.has_groups:
part.remove_and_reset_grouping()
self.display_data()
[docs]
def run_parallel_cpa(self, particle):
particle.cpts.run_cpa(confidence=self.conf_parallel, run_levels=True)
[docs]
def switching_frequency(self, all_selected: str = None):
"""
Calculates and exports the accumulated switching frequency of either
all the particles, or only the selected.
Parameters
----------
all_selected : {'all', 'selected'}
Possible values are 'all' (default) or 'selected'.
"""
try:
if all_selected is None:
all_selected = "all"
assert all_selected.lower() in [
"all",
"selected",
], "mode parameter must be either 'all' or 'selected'."
if all_selected == "all":
data = self.treemodel.data(self.treemodel.index(0, 0), Qt.UserRole)
# assert data.
except Exception as exc:
logger.info("Switching frequency analysis failed: ")
else:
pass
[docs]
def get_checked(self):
checked = list()
for ind in range(self.treemodel.rowCount(self.dataset_index)):
if self.part_nodes[ind].checked():
checked.append((ind, self.part_nodes[ind]))
# checked_nums.append(ind)
# checked_particles.append(self.part_nodes[ind])
return checked
[docs]
def get_checked_nums(self):
checked_nums = list()
for ind in range(self.treemodel.rowCount(self.dataset_index)):
if self.part_nodes[ind].checked():
checked_nums.append(ind + 1)
return checked_nums
[docs]
def get_checked_particles(self):
checked_particles = list()
for ind in range(self.treemodel.rowCount(self.dataset_index)):
if self.part_nodes[ind].checked():
particle = self.tree2particle(ind)
if type(particle) is smsh5.Particle:
checked_particles.append(self.tree2particle(ind))
return checked_particles
[docs]
def set_particle_check_state(self, particle_number: int, set_checked: bool):
self.part_nodes[particle_number].setChecked(set_checked)
[docs]
def set_level_resolved(self):
self.current_dataset.level_resolved = True
# print(self.level_resolved)
[docs]
def gui_plot_intensity_clicked(self, new_value):
self.frmPlot_Int_Selection.setEnabled(new_value)
[docs]
def gui_plot_lifetime_clicked(self, new_value):
self.frmPlot_Lifetime_Selection.setEnabled(new_value)
[docs]
def gui_export(self, mode: str = None):
self.lock = Lock()
f_dir = QFileDialog.getExistingDirectory(self)
export_worker = ExportWorker(
main_window=self, mode=mode, lock=self.lock, f_dir=f_dir
)
sigs = export_worker.signals
sigs.start_progress.connect(self.start_progress)
sigs.progress.connect(self.update_progress)
sigs.end_progress.connect(self.end_progress)
sigs.status_message.connect(self.status_message)
sigs.error.connect(self.error_handler)
sigs.plot_trace_lock.connect(self.intensity_controller.plot_trace)
sigs.plot_trace_export_lock.connect(self.intensity_controller.plot_trace)
sigs.plot_levels_lock.connect(self.intensity_controller.plot_levels)
sigs.plot_levels_export_lock.connect(self.intensity_controller.plot_levels)
sigs.plot_group_bounds_export_lock.connect(
self.intensity_controller.plot_group_bounds
)
sigs.plot_grouping_bic_export_lock.connect(
self.grouping_controller.plot_group_bic
)
sigs.plot_decay_lock.connect(self.lifetime_controller.plot_decay)
sigs.plot_decay_export_lock.connect(self.lifetime_controller.plot_decay)
sigs.plot_convd_lock.connect(self.lifetime_controller.plot_convd)
sigs.plot_convd_export_lock.connect(self.lifetime_controller.plot_convd)
sigs.plot_decay_convd_export_lock.connect(
self.lifetime_controller.plot_decay_and_convd
)
sigs.plot_decay_convd_residuals_export_lock.connect(
self.lifetime_controller.plot_decay_convd_and_hist
)
sigs.show_residual_widget_lock.connect(
self.lifetime_controller.show_residuals_widget
)
sigs.plot_residuals_export_lock.connect(self.lifetime_controller.plot_residuals)
sigs.plot_spectra_export_lock.connect(self.spectra_controller.plot_spectra)
sigs.plot_raster_scan_export_lock.connect(
self.raster_scan_controller.plot_raster_scan
)
sigs.plot_corr_lock.connect(self.antibunch_controller.plot_corr)
sigs.plot_corr_export_lock.connect(self.antibunch_controller.plot_corr)
self.threadpool.start(export_worker)
[docs]
def convert_file_dialog(self):
convert_file = ConvertFileDialog(mainwindow=self)
convert_file.exec()
[docs]
def range_selection(self):
range_selection_dialog = RangeSelectionDialog(main_window=self)
if range_selection_dialog.exec_():
selection_indexes = range_selection_dialog.get_selection(
max_range=len(self.part_nodes)
)
mode_only, mode_add, mode_remove, _ = range_selection_dialog.get_mode()
if max(selection_indexes) > len(self.part_nodes):
msg = QMessageBox(self)
msg.setWindowTitle("Range Selection")
msg.setText("Selection out of bounds!")
msg.setIcon(QMessageBox.Warning)
msg.setStandardButtons(QMessageBox.Ok)
msg.exec()
else:
for i, node in enumerate(self.part_nodes):
is_checked = node.checked()
if mode_only:
if i + 1 in selection_indexes:
is_checked = True
else:
is_checked = False
elif mode_add:
if i + 1 in selection_indexes:
is_checked = True
elif mode_remove:
if i + 1 in selection_indexes:
is_checked = False
else:
if i + 1 not in selection_indexes:
is_checked = True
else:
is_checked = False
if is_checked != node.checked():
node.setChecked(is_checked)
num_checked = sum([node.checked() for node in self.part_nodes])
self.lblNum_Selected.setText(str(num_checked))
[docs]
def reset_gui(self):
"""Sets the GUI elements to enabled if it should be accessible."""
logger.info("Reset GUI")
new_state = self.data_loaded
# Intensity
self.tabIntensity.setEnabled(new_state)
self.btnApplyBin.setEnabled(new_state)
self.btnApplyBinAll.setEnabled(new_state)
self.btnResolve.setEnabled(new_state)
self.btnResolve_Selected.setEnabled(new_state)
self.btnResolveAll.setEnabled(new_state)
self.cmbConfIndex.setEnabled(new_state)
self.spbBinSize.setEnabled(new_state)
self.actionReset_Analysis.setEnabled(new_state)
self.actionSave_Selected.setEnabled(new_state)
enable_levels = False
if new_state:
enable_levels = self.current_dataset.has_levels
# self.actionTrim_Dead_Traces.setEnabled(enable_levels)
# self.chbGroup_Auto_Apply.setEnabled(enable_levels)
# self.menuRegion_of_Interest.setEnabled(enable_levels)
# Lifetime
self.tabLifetime.setEnabled(new_state)
self.btnFitParameters.setEnabled(new_state)
self.btnLoadIRF.setEnabled(new_state)
if new_state:
enable_fitting = self.lifetime_controller.irf_loaded
else:
enable_fitting = new_state
self.chbHasIRF.setChecked(self.lifetime_controller.irf_loaded)
self.btnFitCurrent.setEnabled(enable_fitting)
self.btnFit.setEnabled(enable_fitting)
self.btnFitAll.setEnabled(enable_fitting)
self.btnFitSelected.setEnabled(enable_fitting)
self.btnNextLevel.setEnabled(enable_levels)
self.btnPrevLevel.setEnabled(enable_levels)
# print(enable_levels)
# Grouping
if self.current_dataset is not None:
has_levels = any(
[particle.has_levels for particle in self.current_dataset.particles]
)
self.tabGrouping.setEnabled(has_levels)
self.btnGroupCurrent.setEnabled(has_levels)
self.btnGroupSelected.setEnabled(has_levels)
self.btnGroupAll.setEnabled(has_levels)
self.btnGroupGlobal.setEnabled(has_levels)
if has_levels:
has_groups = any(
[
particle.has_groups
for particle in self.current_dataset.particles
if not particle.is_secondary_part
]
)
self.btnApplyGroupsCurrent.setEnabled(has_groups)
self.btnApplyGroupsSelected.setEnabled(has_groups)
self.btnApplyGroupsAll.setEnabled(has_groups)
has_global_grouping = any(
[
particle.has_global_grouping
for particle in self.current_dataset.particles
]
)
self.chbInt_Show_Global_Groups.setEnabled(has_global_grouping)
# Spectral
if self.current_dataset and self.current_dataset.has_spectra:
self.tabSpectra.setEnabled(True)
self.btnSubBackground.setEnabled(new_state)
self.btnRotateROI.setEnabled(new_state)
else:
self.tabSpectra.setEnabled(False)
[docs]
def close_file(self):
if self.current_dataset is not None:
self.current_particle = None
self.current_dataset.file.close()
self.current_dataset = None
self.treemodel = DatasetTreeModel()
self.treeViewParticles.setModel(self.treemodel)
self.data_loaded = False
[docs]
def error_handler(self, e: Exception):
raise e
[docs]
def display_on():
ctypes.windll.kernel32.SetThreadExecutionState(0x80000002)
logger.info("Execution State set to Always On")
[docs]
def display_reset():
ctypes.windll.kernel32.SetThreadExecutionState(0x80000000)
logger.info("Execution State Reset")
sys.exit(0)
[docs]
def main():
"""
Creates QApplication and runs MainWindow().
"""
# convert_convert_ui()
app = QApplication([])
print("Currently used style:", app.style().metaObject().className())
print("Available styles:", QStyleFactory.keys())
logger.info("App created")
main_window = MainWindow()
logger.info("Main Window created")
main_window.show()
should_calc = main_window.sums_file_check()
if should_calc:
app.processEvents()
main_window.calc_store_sums()
app.processEvents()
# main_window.tabSpectra.repaint()
logger.info("Main Window shown")
if system() == "Windows":
display_on()
app.instance().exec_()
if system() == "Windows":
display_reset()
logger.info("App excuted")
if __name__ == "__main__":
# Create version file for distribution. Or use the command bellow:
# create-version-file version.yml --outfile versionfile.txt --version SMS_VERSION
if "--dev" in sys.argv:
try:
# noinspection PyUnresolvedReferences
import pyinstaller_versionfile
pyinstaller_versionfile.create_versionfile_from_input_file(
output_file="versionfile.txt",
input_file="version.yml",
version=SMS_VERSION,
)
except ImportError as e:
pass
if "--debug" not in sys.argv:
freeze_support()
Process(target=main).start()
else:
main()