Source code for pyccapt.control.gui.gui_main

import logging
import multiprocessing
import sys
from importlib.metadata import PackageNotFoundError, version
from pathlib import Path

from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.QtCore import Qt

# Local module and scripts
from pyccapt.control.core import device_checks, loggi, runtime
from pyccapt.control.devices import camera as camera_device
from pyccapt.control.gui import (
    main_parameters,
    process_coordinator,
    gui_baking,
    gui_gates,
    gui_laser_control,
    gui_pumps_vacuum,
    gui_stage_control,
    tooltips,
)


[docs] class Ui_PyCCAPT(object): def __init__(self, variables, conf, x_plot, y_plot, t_plot, main_v_dc_plot): """ Constructor for the PyCCAPT UI class. Args: variables (object): Global experiment variables. conf (dict): Configuration settings. parent: Parent widget (optional). Returns: None """ self.conf = conf self.variables = variables self.emitter = SignalEmitter() self.flag_super_user = False self.vdc_hold_old = False self.x_plot = x_plot self.y_plot = y_plot self.t_plot = t_plot self.main_v_dc_plot = main_v_dc_plot self.experiment_running = False self.experimetn_finished_event = multiprocessing.Event() self.camera_closed_event = multiprocessing.Event() self.visualization_closed_event = multiprocessing.Event() # Typed command channels to the camera/visualization subprocesses. # Replaces the previous (Event + flag_*) pair which was racy: # main puts a string command (e.g. "show", "show_front", "hide"); # the subprocess drains the queue every poll tick and dispatches. self.camera_command_queue = multiprocessing.Queue() self.visualization_command_queue = multiprocessing.Queue() self.process_coordinator = process_coordinator.ProcessCoordinator() self.camera_process = None self.camera_available = False self.camera_status_message = ""
[docs] def setupUi(self, PyCCAPT): PyCCAPT.setObjectName("PyCCAPT") PyCCAPT.resize(901, 800) self.centralwidget = QtWidgets.QWidget(parent=PyCCAPT) self.centralwidget.setObjectName("centralwidget") self.gridLayout_7 = QtWidgets.QGridLayout(self.centralwidget) self.gridLayout_7.setObjectName("gridLayout_7") self.gridLayout_6 = QtWidgets.QGridLayout() self.gridLayout_6.setObjectName("gridLayout_6") self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.gates_control = QtWidgets.QPushButton(parent=self.centralwidget) self.gates_control.setMinimumSize(QtCore.QSize(0, 40)) self.gates_control.setStyleSheet( "QPushButton{\n" " background: rgb(85, 170, 255)\n" " }\n" " " ) self.gates_control.setObjectName("gates_control") self.horizontalLayout.addWidget(self.gates_control) self.pumps_vaccum = QtWidgets.QPushButton(parent=self.centralwidget) self.pumps_vaccum.setMinimumSize(QtCore.QSize(0, 40)) self.pumps_vaccum.setStyleSheet( "QPushButton{\n" " background: rgb(85, 170, 255)\n" " }\n" " " ) self.pumps_vaccum.setObjectName("pumps_vaccum") self.horizontalLayout.addWidget(self.pumps_vaccum) self.camears = QtWidgets.QPushButton(parent=self.centralwidget) self.camears.setMinimumSize(QtCore.QSize(0, 40)) self.camears.setStyleSheet( "QPushButton{\n" " background: rgb(85, 170, 255)\n" " }\n" " " ) self.camears.setObjectName("camears") self.horizontalLayout.addWidget(self.camears) self.laser_control = QtWidgets.QPushButton(parent=self.centralwidget) self.laser_control.setMinimumSize(QtCore.QSize(0, 40)) self.laser_control.setSizeIncrement(QtCore.QSize(0, 0)) self.laser_control.setStyleSheet( "QPushButton{\n" " background: rgb(85, 170, 255)\n" " }\n" " " ) self.laser_control.setObjectName("laser_control") self.horizontalLayout.addWidget(self.laser_control) self.stage_control = QtWidgets.QPushButton(parent=self.centralwidget) self.stage_control.setMinimumSize(QtCore.QSize(0, 40)) self.stage_control.setSizeIncrement(QtCore.QSize(0, 0)) self.stage_control.setStyleSheet( "QPushButton{\n" " background: rgb(85, 170, 255)\n" " }\n" " " ) self.stage_control.setObjectName("stage_control") self.horizontalLayout.addWidget(self.stage_control) self.visualization = QtWidgets.QPushButton(parent=self.centralwidget) self.visualization.setMinimumSize(QtCore.QSize(0, 40)) self.visualization.setSizeIncrement(QtCore.QSize(0, 0)) self.visualization.setStyleSheet( "QPushButton{\n" " background: rgb(85, 170, 255)\n" " }\n" " " ) self.visualization.setObjectName("visualization") self.horizontalLayout.addWidget(self.visualization) self.baking = QtWidgets.QPushButton(parent=self.centralwidget) self.baking.setMinimumSize(QtCore.QSize(0, 40)) self.baking.setSizeIncrement(QtCore.QSize(0, 0)) self.baking.setStyleSheet( "QPushButton{\n" " background: rgb(85, 170, 255)\n" " }\n" " " ) self.baking.setObjectName("baking") self.horizontalLayout.addWidget(self.baking) self.gridLayout_6.addLayout(self.horizontalLayout, 0, 0, 1, 2) self.verticalLayout = QtWidgets.QVBoxLayout() self.verticalLayout.setObjectName("verticalLayout") self.gridLayout_4 = QtWidgets.QGridLayout() self.gridLayout_4.setObjectName("gridLayout_4") self.label_173 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_173.sizePolicy().hasHeightForWidth()) self.label_173.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setBold(True) self.label_173.setFont(font) self.label_173.setObjectName("label_173") self.gridLayout_4.addWidget(self.label_173, 0, 0, 1, 1) self.parameters_source = QtWidgets.QComboBox(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.parameters_source.sizePolicy().hasHeightForWidth()) self.parameters_source.setSizePolicy(sizePolicy) self.parameters_source.setMinimumSize(QtCore.QSize(0, 20)) self.parameters_source.setStyleSheet( "QComboBox{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.parameters_source.setObjectName("parameters_source") self.parameters_source.addItem("") self.parameters_source.addItem("") self.gridLayout_4.addWidget(self.parameters_source, 0, 1, 1, 1) self.label_183 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_183.sizePolicy().hasHeightForWidth()) self.label_183.setSizePolicy(sizePolicy) self.label_183.setObjectName("label_183") self.gridLayout_4.addWidget(self.label_183, 1, 0, 1, 1) self.ex_number = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.ex_number.sizePolicy().hasHeightForWidth()) self.ex_number.setSizePolicy(sizePolicy) self.ex_number.setMinimumSize(QtCore.QSize(0, 20)) self.ex_number.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.ex_number.setObjectName("ex_number") self.gridLayout_4.addWidget(self.ex_number, 1, 1, 1, 1) self.label_174 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_174.sizePolicy().hasHeightForWidth()) self.label_174.setSizePolicy(sizePolicy) self.label_174.setObjectName("label_174") self.gridLayout_4.addWidget(self.label_174, 2, 0, 1, 1) self.ex_user = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.ex_user.sizePolicy().hasHeightForWidth()) self.ex_user.setSizePolicy(sizePolicy) self.ex_user.setMinimumSize(QtCore.QSize(0, 20)) self.ex_user.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.ex_user.setObjectName("ex_user") self.gridLayout_4.addWidget(self.ex_user, 2, 1, 1, 1) self.label_175 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_175.sizePolicy().hasHeightForWidth()) self.label_175.setSizePolicy(sizePolicy) self.label_175.setObjectName("label_175") self.gridLayout_4.addWidget(self.label_175, 3, 0, 1, 1) self.ex_name = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.ex_name.sizePolicy().hasHeightForWidth()) self.ex_name.setSizePolicy(sizePolicy) self.ex_name.setMinimumSize(QtCore.QSize(0, 20)) self.ex_name.setMaximumSize(QtCore.QSize(16777215, 100)) self.ex_name.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.ex_name.setObjectName("ex_name") self.gridLayout_4.addWidget(self.ex_name, 3, 1, 1, 1) self.label_190 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_190.sizePolicy().hasHeightForWidth()) self.label_190.setSizePolicy(sizePolicy) self.label_190.setObjectName("label_190") self.gridLayout_4.addWidget(self.label_190, 4, 0, 1, 1) self.email = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.email.sizePolicy().hasHeightForWidth()) self.email.setSizePolicy(sizePolicy) self.email.setMinimumSize(QtCore.QSize(0, 20)) self.email.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.email.setText("") self.email.setObjectName("email") self.gridLayout_4.addWidget(self.email, 4, 1, 1, 1) self.label_200 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_200.sizePolicy().hasHeightForWidth()) self.label_200.setSizePolicy(sizePolicy) self.label_200.setObjectName("label_200") self.gridLayout_4.addWidget(self.label_200, 5, 0, 1, 1) self.electrode = QtWidgets.QComboBox(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.electrode.sizePolicy().hasHeightForWidth()) self.electrode.setSizePolicy(sizePolicy) self.electrode.setMinimumSize(QtCore.QSize(0, 20)) self.electrode.setStyleSheet( "QComboBox{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.electrode.setObjectName("electrode") self.electrode.addItem("") self.electrode.setItemText(0, "") self.electrode.addItem("") self.electrode.setItemText(1, "") self.gridLayout_4.addWidget(self.electrode, 5, 1, 1, 1) self.verticalLayout.addLayout(self.gridLayout_4) self.line = QtWidgets.QFrame(parent=self.centralwidget) self.line.setFrameShape(QtWidgets.QFrame.Shape.HLine) self.line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) self.line.setObjectName("line") self.verticalLayout.addWidget(self.line) self.gridLayout_3 = QtWidgets.QGridLayout() self.gridLayout_3.setObjectName("gridLayout_3") self.label = QtWidgets.QLabel(parent=self.centralwidget) self.label.setObjectName("label") self.gridLayout_3.addWidget(self.label, 0, 1, 1, 1) self.max_ions = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.max_ions.sizePolicy().hasHeightForWidth()) self.max_ions.setSizePolicy(sizePolicy) self.max_ions.setMinimumSize(QtCore.QSize(0, 20)) self.max_ions.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.max_ions.setObjectName("max_ions") self.gridLayout_3.addWidget(self.max_ions, 1, 3, 1, 1) self.criteria_ions = QtWidgets.QCheckBox(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.criteria_ions.sizePolicy().hasHeightForWidth()) self.criteria_ions.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setItalic(False) self.criteria_ions.setFont(font) self.criteria_ions.setMouseTracking(True) self.criteria_ions.setStyleSheet("") self.criteria_ions.setText("") self.criteria_ions.setChecked(True) self.criteria_ions.setObjectName("criteria_ions") self.gridLayout_3.addWidget(self.criteria_ions, 1, 2, 1, 1) self.label_2 = QtWidgets.QLabel(parent=self.centralwidget) self.label_2.setObjectName("label_2") self.gridLayout_3.addWidget(self.label_2, 1, 1, 1, 1) self.label_3 = QtWidgets.QLabel(parent=self.centralwidget) self.label_3.setObjectName("label_3") self.gridLayout_3.addWidget(self.label_3, 2, 1, 1, 1) self.label_176 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_176.sizePolicy().hasHeightForWidth()) self.label_176.setSizePolicy(sizePolicy) self.label_176.setObjectName("label_176") self.gridLayout_3.addWidget(self.label_176, 0, 0, 1, 1) self.set_min_voltage = QtWidgets.QPushButton(parent=self.centralwidget) self.set_min_voltage.setMinimumSize(QtCore.QSize(0, 20)) self.set_min_voltage.setObjectName("set_min_voltage") self.gridLayout_3.addWidget(self.set_min_voltage, 3, 1, 1, 2) self.ex_time = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.ex_time.sizePolicy().hasHeightForWidth()) self.ex_time.setSizePolicy(sizePolicy) self.ex_time.setMinimumSize(QtCore.QSize(0, 20)) self.ex_time.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.ex_time.setObjectName("ex_time") self.gridLayout_3.addWidget(self.ex_time, 0, 3, 1, 1) self.label_179 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_179.sizePolicy().hasHeightForWidth()) self.label_179.setSizePolicy(sizePolicy) self.label_179.setObjectName("label_179") self.gridLayout_3.addWidget(self.label_179, 3, 0, 1, 1) self.vdc_max = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.vdc_max.sizePolicy().hasHeightForWidth()) self.vdc_max.setSizePolicy(sizePolicy) self.vdc_max.setMinimumSize(QtCore.QSize(0, 20)) self.vdc_max.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.vdc_max.setObjectName("vdc_max") self.gridLayout_3.addWidget(self.vdc_max, 2, 3, 1, 1) self.label_180 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_180.sizePolicy().hasHeightForWidth()) self.label_180.setSizePolicy(sizePolicy) self.label_180.setObjectName("label_180") self.gridLayout_3.addWidget(self.label_180, 2, 0, 1, 1) self.criteria_time = QtWidgets.QCheckBox(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.criteria_time.sizePolicy().hasHeightForWidth()) self.criteria_time.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setItalic(False) self.criteria_time.setFont(font) self.criteria_time.setMouseTracking(True) self.criteria_time.setStyleSheet("") self.criteria_time.setText("") self.criteria_time.setChecked(True) self.criteria_time.setObjectName("criteria_time") self.gridLayout_3.addWidget(self.criteria_time, 0, 2, 1, 1) self.label_177 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_177.sizePolicy().hasHeightForWidth()) self.label_177.setSizePolicy(sizePolicy) self.label_177.setObjectName("label_177") self.gridLayout_3.addWidget(self.label_177, 1, 0, 1, 1) self.criteria_vdc = QtWidgets.QCheckBox(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.criteria_vdc.sizePolicy().hasHeightForWidth()) self.criteria_vdc.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setItalic(False) self.criteria_vdc.setFont(font) self.criteria_vdc.setMouseTracking(True) self.criteria_vdc.setStyleSheet("") self.criteria_vdc.setText("") self.criteria_vdc.setChecked(True) self.criteria_vdc.setObjectName("criteria_vdc") self.gridLayout_3.addWidget(self.criteria_vdc, 2, 2, 1, 1) self.vdc_min = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.vdc_min.sizePolicy().hasHeightForWidth()) self.vdc_min.setSizePolicy(sizePolicy) self.vdc_min.setMinimumSize(QtCore.QSize(0, 20)) self.vdc_min.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.vdc_min.setObjectName("vdc_min") self.gridLayout_3.addWidget(self.vdc_min, 3, 3, 1, 1) self.verticalLayout.addLayout(self.gridLayout_3) self.line_2 = QtWidgets.QFrame(parent=self.centralwidget) self.line_2.setFrameShape(QtWidgets.QFrame.Shape.HLine) self.line_2.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) self.line_2.setObjectName("line_2") self.verticalLayout.addWidget(self.line_2) self.gridLayout_2 = QtWidgets.QGridLayout() self.gridLayout_2.setObjectName("gridLayout_2") self.label_199 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_199.sizePolicy().hasHeightForWidth()) self.label_199.setSizePolicy(sizePolicy) self.label_199.setObjectName("label_199") self.gridLayout_2.addWidget(self.label_199, 0, 0, 1, 1) self.pulse_mode = QtWidgets.QComboBox(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.pulse_mode.sizePolicy().hasHeightForWidth()) self.pulse_mode.setSizePolicy(sizePolicy) self.pulse_mode.setMinimumSize(QtCore.QSize(0, 20)) self.pulse_mode.setStyleSheet( "QComboBox{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.pulse_mode.setObjectName("pulse_mode") self.pulse_mode.addItem("") self.pulse_mode.addItem("") self.pulse_mode.addItem("") self.gridLayout_2.addWidget(self.pulse_mode, 0, 1, 1, 1) self.label_186 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_186.sizePolicy().hasHeightForWidth()) self.label_186.setSizePolicy(sizePolicy) self.label_186.setObjectName("label_186") self.gridLayout_2.addWidget(self.label_186, 1, 0, 1, 1) self.pulse_fraction = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.pulse_fraction.sizePolicy().hasHeightForWidth()) self.pulse_fraction.setSizePolicy(sizePolicy) self.pulse_fraction.setMinimumSize(QtCore.QSize(0, 20)) self.pulse_fraction.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.pulse_fraction.setObjectName("pulse_fraction") self.gridLayout_2.addWidget(self.pulse_fraction, 1, 1, 1, 1) self.label_187 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_187.sizePolicy().hasHeightForWidth()) self.label_187.setSizePolicy(sizePolicy) self.label_187.setObjectName("label_187") self.gridLayout_2.addWidget(self.label_187, 2, 0, 1, 1) self.pulse_frequency = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.pulse_frequency.sizePolicy().hasHeightForWidth()) self.pulse_frequency.setSizePolicy(sizePolicy) self.pulse_frequency.setMinimumSize(QtCore.QSize(0, 20)) self.pulse_frequency.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.pulse_frequency.setObjectName("pulse_frequency") self.gridLayout_2.addWidget(self.pulse_frequency, 2, 1, 1, 1) self.label_188 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_188.sizePolicy().hasHeightForWidth()) self.label_188.setSizePolicy(sizePolicy) self.label_188.setObjectName("label_188") self.gridLayout_2.addWidget(self.label_188, 3, 0, 1, 1) self.detection_rate_init = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.detection_rate_init.sizePolicy().hasHeightForWidth()) self.detection_rate_init.setSizePolicy(sizePolicy) self.detection_rate_init.setMinimumSize(QtCore.QSize(0, 20)) self.detection_rate_init.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.detection_rate_init.setObjectName("detection_rate_init") self.gridLayout_2.addWidget(self.detection_rate_init, 3, 1, 1, 1) self.verticalLayout.addLayout(self.gridLayout_2) self.line_4 = QtWidgets.QFrame(parent=self.centralwidget) self.line_4.setFrameShape(QtWidgets.QFrame.Shape.HLine) self.line_4.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) self.line_4.setObjectName("line_4") self.verticalLayout.addWidget(self.line_4) self.line_3 = QtWidgets.QFrame(parent=self.centralwidget) self.line_3.setFrameShape(QtWidgets.QFrame.Shape.HLine) self.line_3.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) self.line_3.setObjectName("line_3") self.verticalLayout.addWidget(self.line_3) self.gridLayout = QtWidgets.QGridLayout() self.gridLayout.setObjectName("gridLayout") self.label_192 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_192.sizePolicy().hasHeightForWidth()) self.label_192.setSizePolicy(sizePolicy) self.label_192.setObjectName("label_192") self.gridLayout.addWidget(self.label_192, 0, 0, 1, 1) self.counter_source = QtWidgets.QComboBox(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.counter_source.sizePolicy().hasHeightForWidth()) self.counter_source.setSizePolicy(sizePolicy) self.counter_source.setMinimumSize(QtCore.QSize(0, 20)) self.counter_source.setStyleSheet( "QComboBox{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.counter_source.setObjectName("counter_source") self.counter_source.addItem("") self.counter_source.addItem("") self.gridLayout.addWidget(self.counter_source, 0, 1, 1, 1) self.label_191 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_191.sizePolicy().hasHeightForWidth()) self.label_191.setSizePolicy(sizePolicy) self.label_191.setObjectName("label_191") self.gridLayout.addWidget(self.label_191, 1, 0, 1, 1) self.control_algorithm = QtWidgets.QComboBox(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.control_algorithm.sizePolicy().hasHeightForWidth()) self.control_algorithm.setSizePolicy(sizePolicy) self.control_algorithm.setMinimumSize(QtCore.QSize(0, 20)) self.control_algorithm.setStyleSheet( "QComboBox{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.control_algorithm.setObjectName("control_algorithm") self.control_algorithm.addItem("") # Proportional self.control_algorithm.addItem("") # Proportional aggressive self.control_algorithm.addItem("") # Adaptive P self.control_algorithm.addItem("") # PID self.gridLayout.addWidget(self.control_algorithm, 1, 1, 1, 1) self.label_178 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_178.sizePolicy().hasHeightForWidth()) self.label_178.setSizePolicy(sizePolicy) self.label_178.setObjectName("label_178") self.gridLayout.addWidget(self.label_178, 2, 0, 1, 1) self.ex_freq = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.ex_freq.sizePolicy().hasHeightForWidth()) self.ex_freq.setSizePolicy(sizePolicy) self.ex_freq.setMinimumSize(QtCore.QSize(0, 20)) self.ex_freq.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.ex_freq.setObjectName("ex_freq") self.gridLayout.addWidget(self.ex_freq, 2, 1, 1, 1) self.label_184 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_184.sizePolicy().hasHeightForWidth()) self.label_184.setSizePolicy(sizePolicy) self.label_184.setObjectName("label_184") self.gridLayout.addWidget(self.label_184, 3, 0, 1, 1) self.vp_min = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.vp_min.sizePolicy().hasHeightForWidth()) self.vp_min.setSizePolicy(sizePolicy) self.vp_min.setMinimumSize(QtCore.QSize(0, 20)) self.vp_min.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.vp_min.setObjectName("vp_min") self.gridLayout.addWidget(self.vp_min, 3, 1, 1, 1) self.label_185 = QtWidgets.QLabel(parent=self.centralwidget) self.label_185.setObjectName("label_185") self.gridLayout.addWidget(self.label_185, 4, 0, 1, 1) self.vp_max = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.vp_max.sizePolicy().hasHeightForWidth()) self.vp_max.setSizePolicy(sizePolicy) self.vp_max.setMinimumSize(QtCore.QSize(0, 20)) self.vp_max.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.vp_max.setObjectName("vp_max") self.gridLayout.addWidget(self.vp_max, 4, 1, 1, 1) self.label_181 = QtWidgets.QLabel(parent=self.centralwidget) self.label_181.setObjectName("label_181") self.gridLayout.addWidget(self.label_181, 5, 0, 1, 1) self.vdc_steps_up = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.vdc_steps_up.sizePolicy().hasHeightForWidth()) self.vdc_steps_up.setSizePolicy(sizePolicy) self.vdc_steps_up.setMinimumSize(QtCore.QSize(0, 20)) self.vdc_steps_up.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.vdc_steps_up.setObjectName("vdc_steps_up") self.gridLayout.addWidget(self.vdc_steps_up, 5, 1, 1, 1) self.label_182 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_182.sizePolicy().hasHeightForWidth()) self.label_182.setSizePolicy(sizePolicy) self.label_182.setObjectName("label_182") self.gridLayout.addWidget(self.label_182, 6, 0, 1, 1) self.vdc_steps_down = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.vdc_steps_down.sizePolicy().hasHeightForWidth()) self.vdc_steps_down.setSizePolicy(sizePolicy) self.vdc_steps_down.setMinimumSize(QtCore.QSize(0, 20)) self.vdc_steps_down.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.vdc_steps_down.setObjectName("vdc_steps_down") self.gridLayout.addWidget(self.vdc_steps_down, 6, 1, 1, 1) self.verticalLayout.addLayout(self.gridLayout) self.gridLayout_6.addLayout(self.verticalLayout, 1, 0, 2, 1) self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setObjectName("verticalLayout_2") self.gridLayout_5 = QtWidgets.QGridLayout() self.gridLayout_5.setObjectName("gridLayout_5") self.label_193 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_193.sizePolicy().hasHeightForWidth()) self.label_193.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setBold(True) self.label_193.setFont(font) self.label_193.setObjectName("label_193") self.gridLayout_5.addWidget(self.label_193, 0, 0, 1, 1) self.superuser = QtWidgets.QPushButton(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.superuser.sizePolicy().hasHeightForWidth()) self.superuser.setSizePolicy(sizePolicy) self.superuser.setMinimumSize(QtCore.QSize(0, 25)) self.superuser.setStyleSheet( "QPushButton{\n" " background: rgb(193, 193, 193)\n" " }\n" " " ) self.superuser.setObjectName("superuser") self.gridLayout_5.addWidget(self.superuser, 0, 1, 1, 1) self.label_194 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_194.sizePolicy().hasHeightForWidth()) self.label_194.setSizePolicy(sizePolicy) self.label_194.setObjectName("label_194") self.gridLayout_5.addWidget(self.label_194, 1, 0, 1, 1) self.elapsed_time = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.elapsed_time.sizePolicy().hasHeightForWidth()) self.elapsed_time.setSizePolicy(sizePolicy) self.elapsed_time.setMinimumSize(QtCore.QSize(0, 20)) self.elapsed_time.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.elapsed_time.setText("") self.elapsed_time.setObjectName("elapsed_time") self.gridLayout_5.addWidget(self.elapsed_time, 1, 1, 1, 1) self.label_195 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_195.sizePolicy().hasHeightForWidth()) self.label_195.setSizePolicy(sizePolicy) self.label_195.setObjectName("label_195") self.gridLayout_5.addWidget(self.label_195, 2, 0, 1, 1) self.total_ions = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.total_ions.sizePolicy().hasHeightForWidth()) self.total_ions.setSizePolicy(sizePolicy) self.total_ions.setMinimumSize(QtCore.QSize(0, 20)) self.total_ions.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.total_ions.setText("") self.total_ions.setObjectName("total_ions") self.gridLayout_5.addWidget(self.total_ions, 2, 1, 1, 1) self.label_196 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_196.sizePolicy().hasHeightForWidth()) self.label_196.setSizePolicy(sizePolicy) self.label_196.setObjectName("label_196") self.gridLayout_5.addWidget(self.label_196, 3, 0, 1, 1) self.speciemen_voltage = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.speciemen_voltage.sizePolicy().hasHeightForWidth()) self.speciemen_voltage.setSizePolicy(sizePolicy) self.speciemen_voltage.setMinimumSize(QtCore.QSize(0, 20)) self.speciemen_voltage.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.speciemen_voltage.setText("") self.speciemen_voltage.setObjectName("speciemen_voltage") self.gridLayout_5.addWidget(self.speciemen_voltage, 3, 1, 1, 1) self.label_197 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_197.sizePolicy().hasHeightForWidth()) self.label_197.setSizePolicy(sizePolicy) self.label_197.setObjectName("label_197") self.gridLayout_5.addWidget(self.label_197, 4, 0, 1, 1) self.pulse_voltage = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.pulse_voltage.sizePolicy().hasHeightForWidth()) self.pulse_voltage.setSizePolicy(sizePolicy) self.pulse_voltage.setMinimumSize(QtCore.QSize(0, 20)) self.pulse_voltage.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.pulse_voltage.setText("") self.pulse_voltage.setObjectName("pulse_voltage") self.gridLayout_5.addWidget(self.pulse_voltage, 4, 1, 1, 1) self.label_198 = QtWidgets.QLabel(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_198.sizePolicy().hasHeightForWidth()) self.label_198.setSizePolicy(sizePolicy) self.label_198.setObjectName("label_198") self.gridLayout_5.addWidget(self.label_198, 5, 0, 1, 1) self.detection_rate = QtWidgets.QLineEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.detection_rate.sizePolicy().hasHeightForWidth()) self.detection_rate.setSizePolicy(sizePolicy) self.detection_rate.setMinimumSize(QtCore.QSize(0, 20)) self.detection_rate.setStyleSheet( "QLineEdit{\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.detection_rate.setText("") self.detection_rate.setObjectName("detection_rate") self.gridLayout_5.addWidget(self.detection_rate, 5, 1, 1, 1) self.verticalLayout_2.addLayout(self.gridLayout_5) spacerItem = QtWidgets.QSpacerItem( 20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding ) self.verticalLayout_2.addItem(spacerItem) self.text_line = QtWidgets.QTextEdit(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.text_line.sizePolicy().hasHeightForWidth()) self.text_line.setSizePolicy(sizePolicy) self.text_line.setMinimumSize(QtCore.QSize(0, 400)) self.text_line.setStyleSheet( "QWidget{\n" " border: 2px solid gray;\n" " border-radius: 10px;\n" " padding: 0 8px;\n" " background: rgb(223,223,233)\n" " }\n" " " ) self.text_line.setObjectName("text_line") self.verticalLayout_2.addWidget(self.text_line) self.gridLayout_6.addLayout(self.verticalLayout_2, 1, 1, 1, 1) self.start_button = QtWidgets.QPushButton(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.start_button.sizePolicy().hasHeightForWidth()) self.start_button.setSizePolicy(sizePolicy) self.start_button.setMinimumSize(QtCore.QSize(0, 25)) self.start_button.setStyleSheet( "QPushButton{\n" " background: rgb(193, 193, 193)\n" " }\n" " " ) self.start_button.setObjectName("start_button") self.gridLayout_6.addWidget(self.start_button, 2, 2, 2, 1) self.Error = QtWidgets.QLabel(parent=self.centralwidget) self.Error.setMinimumSize(QtCore.QSize(700, 30)) font = QtGui.QFont() font.setPointSize(10) font.setBold(True) font.setStrikeOut(False) self.Error.setFont(font) self.Error.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.Error.setWordWrap(True) self.Error.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.LinksAccessibleByMouse) self.Error.setObjectName("Error") self.gridLayout_6.addWidget(self.Error, 3, 0, 2, 2) self.stop_button = QtWidgets.QPushButton(parent=self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.stop_button.sizePolicy().hasHeightForWidth()) self.stop_button.setSizePolicy(sizePolicy) self.stop_button.setMinimumSize(QtCore.QSize(0, 25)) self.stop_button.setStyleSheet( "QPushButton{\n" " background: rgb(193, 193, 193)\n" " }\n" " " ) self.stop_button.setObjectName("stop_button") self.gridLayout_6.addWidget(self.stop_button, 4, 2, 1, 1) self.gridLayout_7.addLayout(self.gridLayout_6, 0, 0, 1, 1) PyCCAPT.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(parent=PyCCAPT) self.menubar.setGeometry(QtCore.QRect(0, 0, 901, 22)) self.menubar.setObjectName("menubar") self.menuFile = QtWidgets.QMenu(parent=self.menubar) self.menuFile.setObjectName("menuFile") self.menuEdit = QtWidgets.QMenu(parent=self.menubar) self.menuEdit.setObjectName("menuEdit") self.menuHelp = QtWidgets.QMenu(parent=self.menubar) self.menuHelp.setObjectName("menuHelp") self.menuSettings = QtWidgets.QMenu(parent=self.menubar) self.menuSettings.setObjectName("menuSettings") self.menuView = QtWidgets.QMenu(parent=self.menubar) self.menuView.setObjectName("menuView") PyCCAPT.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(parent=PyCCAPT) self.statusbar.setObjectName("statusbar") PyCCAPT.setStatusBar(self.statusbar) self.actionExit = QtGui.QAction(parent=PyCCAPT) self.actionExit.setObjectName("actionExit") self.actiontake_sceernshot = QtGui.QAction(parent=PyCCAPT) self.actiontake_sceernshot.setObjectName("actiontake_sceernshot") self.actionAbout = QtGui.QAction(parent=PyCCAPT) self.actionAbout.setObjectName("actionAbout") # File menu self.actionOpenDataFolder = QtGui.QAction("Open Data Folder", parent=PyCCAPT) self.actionOpenDataFolder.setShortcut("Ctrl+D") self.actionOpenProjectFolder = QtGui.QAction("Open Project Folder", parent=PyCCAPT) self.actiontake_sceernshot.setShortcut("Ctrl+Shift+S") self.actionExit.setShortcut("Ctrl+Q") self.menuFile.addAction(self.actionOpenDataFolder) self.menuFile.addAction(self.actionOpenProjectFolder) self.menuFile.addAction(self.actiontake_sceernshot) self.menuFile.addSeparator() self.menuFile.addAction(self.actionExit) # Edit menu self.actionEditConfig = QtGui.QAction("Edit config.toml…", parent=PyCCAPT) self.actionEditConfig.setShortcut("Ctrl+,") self.actionShowConfigPath = QtGui.QAction("Show Config Location", parent=PyCCAPT) self.menuEdit.addAction(self.actionEditConfig) self.menuEdit.addAction(self.actionShowConfigPath) # View menu (shortcuts to the sub-windows already in the toolbar) self.actionShowCameras = QtGui.QAction("Cameras Window", parent=PyCCAPT) self.actionShowCameras.setShortcut("Ctrl+1") self.actionShowPumps = QtGui.QAction("Pumps && Vacuum Window", parent=PyCCAPT) self.actionShowPumps.setShortcut("Ctrl+2") self.actionShowGates = QtGui.QAction("Gates Window", parent=PyCCAPT) self.actionShowGates.setShortcut("Ctrl+3") self.actionShowLaser = QtGui.QAction("Laser Control Window", parent=PyCCAPT) self.actionShowLaser.setShortcut("Ctrl+4") self.actionShowStage = QtGui.QAction("Stage Control Window", parent=PyCCAPT) self.actionShowStage.setShortcut("Ctrl+5") self.actionShowVisualization = QtGui.QAction("Visualization Window", parent=PyCCAPT) self.actionShowVisualization.setShortcut("Ctrl+6") self.actionShowBaking = QtGui.QAction("Baking Window", parent=PyCCAPT) self.actionShowBaking.setShortcut("Ctrl+7") self.menuView.addAction(self.actionShowCameras) self.menuView.addAction(self.actionShowPumps) self.menuView.addAction(self.actionShowGates) self.menuView.addAction(self.actionShowLaser) self.menuView.addAction(self.actionShowStage) self.menuView.addAction(self.actionShowVisualization) self.menuView.addAction(self.actionShowBaking) # Settings menu self.actionOpenConfigSettings = QtGui.QAction("Open config.toml…", parent=PyCCAPT) self.actionShowDeviceStatus = QtGui.QAction("Show Device Status", parent=PyCCAPT) self.actionShowSerialPorts = QtGui.QAction("Show Available Serial Ports", parent=PyCCAPT) self.menuSettings.addAction(self.actionOpenConfigSettings) self.menuSettings.addAction(self.actionShowDeviceStatus) self.menuSettings.addAction(self.actionShowSerialPorts) # Help menu self.actionDocumentation = QtGui.QAction("Online Documentation", parent=PyCCAPT) self.actionDocumentation.setShortcut("F1") self.actionGitHub = QtGui.QAction("GitHub Repository", parent=PyCCAPT) self.actionReportIssue = QtGui.QAction("Report an Issue…", parent=PyCCAPT) self.actionShortcuts = QtGui.QAction("Keyboard Shortcuts", parent=PyCCAPT) self.menuHelp.addAction(self.actionDocumentation) self.menuHelp.addAction(self.actionGitHub) self.menuHelp.addAction(self.actionReportIssue) self.menuHelp.addSeparator() self.menuHelp.addAction(self.actionShortcuts) self.menuHelp.addAction(self.actionAbout) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuEdit.menuAction()) self.menubar.addAction(self.menuView.menuAction()) self.menubar.addAction(self.menuSettings.menuAction()) self.menubar.addAction(self.menuHelp.menuAction()) self.retranslateUi(PyCCAPT) QtCore.QMetaObject.connectSlotsByName(PyCCAPT) tooltips.apply_tooltips(self, tooltips.MAIN_TOOLTIPS) # Override the literal defaults baked into retranslateUi with the # values from config.toml so that editing config.toml is the single # source of truth for "what the GUI looks like when the operator # opens the software". Operators can still freely edit the fields # afterwards; this only changes the *initial* values. self._apply_config_defaults_to_inputs() PyCCAPT.setTabOrder(self.gates_control, self.pumps_vaccum) PyCCAPT.setTabOrder(self.pumps_vaccum, self.camears) PyCCAPT.setTabOrder(self.camears, self.laser_control) PyCCAPT.setTabOrder(self.laser_control, self.stage_control) PyCCAPT.setTabOrder(self.stage_control, self.visualization) PyCCAPT.setTabOrder(self.visualization, self.baking) PyCCAPT.setTabOrder(self.baking, self.parameters_source) PyCCAPT.setTabOrder(self.parameters_source, self.ex_number) PyCCAPT.setTabOrder(self.ex_number, self.ex_user) PyCCAPT.setTabOrder(self.ex_user, self.ex_name) PyCCAPT.setTabOrder(self.ex_name, self.email) PyCCAPT.setTabOrder(self.email, self.electrode) PyCCAPT.setTabOrder(self.electrode, self.criteria_time) PyCCAPT.setTabOrder(self.criteria_time, self.ex_time) PyCCAPT.setTabOrder(self.ex_time, self.criteria_ions) PyCCAPT.setTabOrder(self.criteria_ions, self.max_ions) PyCCAPT.setTabOrder(self.max_ions, self.criteria_vdc) PyCCAPT.setTabOrder(self.criteria_vdc, self.vdc_max) PyCCAPT.setTabOrder(self.vdc_max, self.set_min_voltage) PyCCAPT.setTabOrder(self.set_min_voltage, self.vdc_min) PyCCAPT.setTabOrder(self.vdc_min, self.pulse_mode) PyCCAPT.setTabOrder(self.pulse_mode, self.pulse_fraction) PyCCAPT.setTabOrder(self.pulse_fraction, self.pulse_frequency) PyCCAPT.setTabOrder(self.pulse_frequency, self.detection_rate_init) PyCCAPT.setTabOrder(self.detection_rate_init, self.counter_source) PyCCAPT.setTabOrder(self.counter_source, self.control_algorithm) PyCCAPT.setTabOrder(self.control_algorithm, self.ex_freq) PyCCAPT.setTabOrder(self.ex_freq, self.vp_min) PyCCAPT.setTabOrder(self.vp_min, self.vp_max) PyCCAPT.setTabOrder(self.vp_max, self.vdc_steps_up) PyCCAPT.setTabOrder(self.vdc_steps_up, self.vdc_steps_down) PyCCAPT.setTabOrder(self.vdc_steps_down, self.superuser) PyCCAPT.setTabOrder(self.superuser, self.elapsed_time) PyCCAPT.setTabOrder(self.elapsed_time, self.total_ions) PyCCAPT.setTabOrder(self.total_ions, self.speciemen_voltage) PyCCAPT.setTabOrder(self.speciemen_voltage, self.pulse_voltage) PyCCAPT.setTabOrder(self.pulse_voltage, self.detection_rate) PyCCAPT.setTabOrder(self.detection_rate, self.text_line) PyCCAPT.setTabOrder(self.text_line, self.start_button) PyCCAPT.setTabOrder(self.start_button, self.stop_button) ### self.actionAbout.triggered.connect(self.about) self.actionExit.triggered.connect(PyCCAPT.close) self.actiontake_sceernshot.triggered.connect(self.take_screenshot) self.actionOpenDataFolder.triggered.connect(self.open_data_folder) self.actionOpenProjectFolder.triggered.connect(self.open_project_folder) self.actionEditConfig.triggered.connect(self.open_config_in_editor) self.actionShowConfigPath.triggered.connect(self.show_config_path) self.actionOpenConfigSettings.triggered.connect(self.open_config_in_editor) self.actionShowDeviceStatus.triggered.connect(self.show_device_status) self.actionShowSerialPorts.triggered.connect(self.show_serial_ports) self.actionShowCameras.triggered.connect(self.open_cameras_win) self.actionShowPumps.triggered.connect(self.open_pumps_vacuum_win) self.actionShowGates.triggered.connect(self.open_gates_win) self.actionShowLaser.triggered.connect(self.open_laser_control_win) self.actionShowStage.triggered.connect(self.open_stage_control_win) self.actionShowVisualization.triggered.connect(self.open_visualization_win) self.actionShowBaking.triggered.connect(self.open_baking_win) self.actionDocumentation.triggered.connect( lambda: self._open_external_url("https://pyccapt.readthedocs.io/en/latest/") ) self.actionGitHub.triggered.connect(lambda: self._open_external_url("https://github.com/mmonajem/pyccapt")) self.actionReportIssue.triggered.connect( lambda: self._open_external_url("https://github.com/mmonajem/pyccapt/issues/new") ) self.actionShortcuts.triggered.connect(self.show_keyboard_shortcuts) self.camears.clicked.connect(self.open_cameras_win) self.gates_control.clicked.connect(self.open_gates_win) self.laser_control.clicked.connect(self.open_laser_control_win) self.stage_control.clicked.connect(self.open_stage_control_win) self.pumps_vaccum.clicked.connect(self.open_pumps_vacuum_win) self.visualization.clicked.connect(self.open_visualization_win) self.baking.clicked.connect(self.open_baking_win) # Create a QTimer to hide the warning message after 8 seconds self.timer = QtCore.QTimer() self.timer.timeout.connect(self.hideMessage) self.camera_close_check_timer = QtCore.QTimer() self.camera_close_check_timer.timeout.connect(self.check_closed_events) QtWidgets.QApplication.instance().aboutToQuit.connect(self.cleanup) self.statistics_timer = QtCore.QTimer() self.statistics_timer.timeout.connect(self.statistics_update) self.timer_stop_exp = QtCore.QTimer() self.timer_stop_exp.timeout.connect(self.on_stop_experiment_worker) # timer to stop the experiment ### self.setup_parameters_changes() self.parameters_source.currentIndexChanged.connect(self.setup_parameters_changes) self.ex_user.editingFinished.connect(self.setup_parameters_changes) self.ex_name.editingFinished.connect(self.setup_parameters_changes) self.electrode.currentIndexChanged.connect(self.setup_parameters_changes) self.ex_time.editingFinished.connect(self.setup_parameters_changes) self.max_ions.editingFinished.connect(self.setup_parameters_changes) self.ex_freq.editingFinished.connect(self.setup_parameters_changes) self.vdc_min.editingFinished.connect(self.setup_parameters_changes) self.vdc_max.editingFinished.connect(self.setup_parameters_changes) self.vdc_steps_up.editingFinished.connect(self.setup_parameters_changes) self.vdc_steps_down.editingFinished.connect(self.setup_parameters_changes) self.vp_min.editingFinished.connect(self.setup_parameters_changes) self.vp_max.editingFinished.connect(self.setup_parameters_changes) self.pulse_fraction.editingFinished.connect(self.setup_parameters_changes) self.pulse_frequency.editingFinished.connect(self.setup_parameters_changes) self.detection_rate_init.editingFinished.connect(self.setup_parameters_changes) self.email.editingFinished.connect(self.setup_parameters_changes) self.counter_source.currentIndexChanged.connect(self.setup_parameters_changes) self.control_algorithm.currentIndexChanged.connect(self.setup_parameters_changes) self.pulse_mode.currentIndexChanged.connect(self.setup_parameters_changes) self.parameters_source.currentIndexChanged.connect(self.setup_parameters_changes) self.criteria_vdc.stateChanged.connect(self.setup_parameters_changes) self.criteria_time.stateChanged.connect(self.setup_parameters_changes) self.criteria_ions.stateChanged.connect(self.setup_parameters_changes) ### self.start_button.clicked.connect(self.start_experiment_clicked) self.stop_button.clicked.connect(self.stop_experiment_clicked) self.set_min_voltage.clicked.connect(self.set_min_voltage_clicked) self.superuser.clicked.connect(self.super_user_access) self.emitter.elapsed_time.connect(self.update_elapsed_time) self.emitter.total_ions.connect(self.update_total_ions) self.emitter.speciemen_voltage.connect(self.update_speciemen_voltage) self.emitter.pulse_voltage.connect(self.update_pulse_voltage) self.emitter.detection_rate.connect(self.update_detection_rate) self.result_list = [] self.camera_close_check_timer.start(500) # check every 500 ms self.statistics_timer.start(333) # check every 333 ms # initialize the wins self.wins_init() self.original_button_style = self.superuser.styleSheet() counter_path = runtime.ensure_counter_file() try: self.variables.counter = int(counter_path.read_text(encoding="utf-8").splitlines()[0].strip()) except (IndexError, ValueError): counter_path.write_text("1", encoding="utf-8") self.variables.counter = 1 self.ex_number.setEnabled(False) self.ex_number.setText(str(self.variables.counter)) self.load_electrode_items(str(runtime.project_path("control", "electrode.toml")))
[docs] def retranslateUi(self, PyCCAPT): """ Retranslate the UI with the selected language Args: PyCCAPT: Main window Return: None """ _translate = QtCore.QCoreApplication.translate ### # PyCCAPT.setWindowTitle(_translate("PyCCAPT", "OXCART")) _translate = QtCore.QCoreApplication.translate PyCCAPT.setWindowTitle(_translate("PyCCAPT", "PyCCAPT APT Experiment Control")) PyCCAPT.setWindowIcon(QtGui.QIcon(str(runtime.project_path("files", "logo.png")))) ### self.gates_control.setText(_translate("PyCCAPT", "Gates Control")) self.pumps_vaccum.setText(_translate("PyCCAPT", "Pumps & Vacuum")) self.camears.setText(_translate("PyCCAPT", "Cameras & Alingment")) self.laser_control.setText(_translate("PyCCAPT", "Laser Control")) self.stage_control.setText(_translate("PyCCAPT", "Stage Control")) self.visualization.setText(_translate("PyCCAPT", "Visualization")) self.baking.setText(_translate("PyCCAPT", "Baking")) self.label_173.setText(_translate("PyCCAPT", "Setup Parameters")) self.parameters_source.setItemText(0, _translate("PyCCAPT", "TextBox")) self.parameters_source.setItemText(1, _translate("PyCCAPT", "TextLine")) self.label_183.setText(_translate("PyCCAPT", "Experiment Number")) self.ex_number.setText(_translate("PyCCAPT", "1")) self.label_174.setText(_translate("PyCCAPT", "Experiment User")) self.ex_user.setText(_translate("PyCCAPT", "user")) self.label_175.setText(_translate("PyCCAPT", "Experiment Name")) self.ex_name.setText(_translate("PyCCAPT", "test")) self.label_190.setText(_translate("PyCCAPT", "Email")) self.label_200.setText(_translate("PyCCAPT", "Electrode")) self.label.setText(_translate("PyCCAPT", "Stop at")) self.max_ions.setText(_translate("PyCCAPT", "2000000")) self.label_2.setText(_translate("PyCCAPT", "Stop at")) self.label_3.setText(_translate("PyCCAPT", "Stop at")) self.label_176.setText(_translate("PyCCAPT", "Max. Experiment Time (s)")) self.set_min_voltage.setText(_translate("PyCCAPT", "Set")) self.ex_time.setText(_translate("PyCCAPT", "3600")) self.label_179.setText(_translate("PyCCAPT", "DC Min. Voltage (V)")) self.vdc_max.setText(_translate("PyCCAPT", "4000")) self.label_180.setText(_translate("PyCCAPT", "DC Max. Voltage (V)")) self.label_177.setText(_translate("PyCCAPT", "Max. Number of Ions")) self.vdc_min.setText(_translate("PyCCAPT", "500")) self.label_199.setText(_translate("PyCCAPT", "Pulse Mode")) self.pulse_mode.setItemText(0, _translate("PyCCAPT", "Voltage")) self.pulse_mode.setItemText(1, _translate("PyCCAPT", "Laser")) self.pulse_mode.setItemText(2, _translate("PyCCAPT", "VoltageLaser")) self.label_186.setText(_translate("PyCCAPT", "Pulse Fraction (%)")) self.pulse_fraction.setText(_translate("PyCCAPT", "20")) self.label_187.setText(_translate("PyCCAPT", "Pulse Frequency (KHz)")) self.pulse_frequency.setText(_translate("PyCCAPT", "200")) self.label_188.setText(_translate("PyCCAPT", "Detection Rate (%)")) self.detection_rate_init.setText(_translate("PyCCAPT", "1")) self.label_192.setText(_translate("PyCCAPT", "Detection Mode")) self.counter_source.setItemText(0, _translate("PyCCAPT", "TDC")) self.counter_source.setItemText(1, _translate("PyCCAPT", "Digitizer")) self.label_191.setText(_translate("PyCCAPT", "Control Algorithm")) self.control_algorithm.setItemText(0, _translate("PyCCAPT", "Proportional")) self.control_algorithm.setItemText(1, _translate("PyCCAPT", "Proportional aggressive")) self.control_algorithm.setItemText(2, _translate("PyCCAPT", "Adaptive P")) self.control_algorithm.setItemText(3, _translate("PyCCAPT", "PID")) self.label_178.setText(_translate("PyCCAPT", "Control Refresh Freq.(Hz)")) self.ex_freq.setText(_translate("PyCCAPT", "5")) self.label_184.setText(_translate("PyCCAPT", "Pulse Min. Voltage (V)")) self.vp_min.setText(_translate("PyCCAPT", "328")) self.label_185.setText(_translate("PyCCAPT", "Pulse Max. Voltage (V)")) self.vp_max.setText(_translate("PyCCAPT", "3281")) self.label_181.setText(_translate("PyCCAPT", "K_p Upwards")) self.vdc_steps_up.setText(_translate("PyCCAPT", "1")) self.label_182.setText(_translate("PyCCAPT", "K_p Downwards")) self.vdc_steps_down.setText(_translate("PyCCAPT", "1")) self.label_193.setText(_translate("PyCCAPT", "Run Statistics")) self.superuser.setText(_translate("PyCCAPT", "Override Access")) self.label_194.setText(_translate("PyCCAPT", "Elapsed Time (s)")) self.label_195.setText(_translate("PyCCAPT", "Total Ions")) self.label_196.setText(_translate("PyCCAPT", "DC Voltage (V)")) self.label_197.setText(_translate("PyCCAPT", "Pulse Voltage (V)")) self.label_198.setText(_translate("PyCCAPT", "Detection Rate (%)")) self.text_line.setHtml( _translate( "PyCCAPT", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n" "<html><head><meta name=\"qrichtext\" content=\"1\" /><meta charset=\"utf-8\" /><style type=\"text/css\">\n" "p, li { white-space: pre-wrap; }\n" "</style></head><body style=\" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;\">\n" "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:'JetBrains Mono,monospace'; font-size:8pt; color:#000000;\">{ex_user=user1;</span><span style=\" font-family:'MS Shell Dlg 2'; font-size:7.875pt;\">ex_name=test1;</span> </p>\n" "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:'MS Shell Dlg 2'; font-size:7.875pt;\">ex_time=90;max_ions=2000;ex_freq=10;</span> </p>\n" "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:'MS Shell Dlg 2'; font-size:7.875pt;\">vdc_min=500;vdc_max=4000;vdc_steps_up=1;</span> </p>\n" "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:'MS Shell Dlg 2'; font-size:7.875pt;\">vdc_steps_down=1;</span><span style=\" font-family:'JetBrains Mono,monospace'; font-size:8pt; color:#000000;\">control_algorithm=PID;</span><span style=\" font-family:'MS Shell Dlg 2'; font-size:7.875pt;\">vp_min=328;</span> </p>\n" "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:'MS Shell Dlg 2'; font-size:7.875pt;\">vp_max=3281;pulse_fraction=20;pulse_frequency=200;</span> </p>\n" "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:'MS Shell Dlg 2'; font-size:7.875pt;\">detection_rate_init=1;hit_displayed=20000;email=;counter_source=TDC</span><span style=\" font-family:'JetBrains Mono,monospace'; font-size:8pt; color:#000000;\">;</span> </p>\n" "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:'JetBrains Mono,monospace'; font-size:8pt; color:#000000;\">criteria_time=True;criteria_ions=False;</span> </p>\n" "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:'JetBrains Mono,monospace'; font-size:8pt; color:#000000;\">criteria_vdc=False}</span> </p>\n" "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:'JetBrains Mono,monospace'; font-size:8pt; color:#000000;\">{ex_user=user2;ex_name=test2;ex_time=100;</span> </p>\n" "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:'JetBrains Mono,monospace'; font-size:8pt; color:#000000;\">max_ions=3000;ex_freq=5;vdc_min=1000;</span> </p>\n" "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:'JetBrains Mono,monospace'; font-size:8pt; color:#000000;\">vdc_max=3000;vdc_steps_up=0.5;vdc_steps_down=0.5;</span> </p>\n" "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:'JetBrains Mono,monospace'; font-size:8pt; color:#000000;\">control_algorithm=proportional;vp_min=400;vp_max=2000;</span> </p>\n" "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:'JetBrains Mono,monospace'; font-size:8pt; color:#000000;\">pulse_fraction=15;pulse_frequency=200;detection_rate_init=2;</span> </p>\n" "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:'JetBrains Mono,monospace'; font-size:8pt; color:#000000;\">hit_displayed=40000;email=;counter_source=DRS;</span> </p>\n" "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:'JetBrains Mono,monospace'; font-size:8pt; color:#000000;\">criteria_time=False;criteria_ions=False;criteria_vdc=True}</span> </p></body></html>", ) ) self.start_button.setText(_translate("PyCCAPT", "Start")) self.Error.setText(_translate("PyCCAPT", "<html><head/><body><p><br/></p></body></html>")) self.stop_button.setText(_translate("PyCCAPT", "Stop")) self.menuFile.setTitle(_translate("PyCCAPT", "File")) self.menuEdit.setTitle(_translate("PyCCAPT", "Edit")) self.menuHelp.setTitle(_translate("PyCCAPT", "Help")) self.menuSettings.setTitle(_translate("PyCCAPT", "Settings")) self.menuView.setTitle(_translate("PyCCAPT", "View")) self.actionExit.setText(_translate("PyCCAPT", "Exit")) self.actiontake_sceernshot.setText(_translate("PyCCAPT", "Take Screenshot")) self.actionAbout.setText(_translate("PyCCAPT", "About PyCCAPT"))
def _apply_config_defaults_to_inputs(self): """Populate the experiment-parameter input fields from config.toml. For each editable field on the main GUI, if a corresponding key is set in ``config.toml`` we use that value as the field's initial contents; otherwise the literal default already written by ``retranslateUi`` stays in place (so the GUI is still usable on a minimal config). Operators can still freely edit any field after the GUI opens -- this method only sets the *initial* values shown when the software starts. Recognised config keys: * ``min_vp`` -> Pulse Min. Voltage (V) * ``max_vp`` -> Pulse Max. Voltage (V) * ``default_vdc_min`` -> DC Min. Voltage (V) * ``default_vdc_max`` -> DC Max. Voltage (V) (separate from ``max_vdc``, which is the *safety* upper bound) * ``default_max_ions`` -> Max. Number of Ions * ``default_ex_time_s`` -> Max. Experiment Time (s) * ``default_ex_freq_hz`` -> Control Refresh Freq. (Hz) * ``default_vdc_step_up`` -> K_p Upwards * ``default_vdc_step_down`` -> K_p Downwards * ``default_detection_rate`` -> Detection Rate (%) * ``default_pulse_fraction`` -> Pulse Fraction (%) * ``default_pulse_frequency_khz`` -> Pulse Frequency (kHz) * ``default_pulse_mode`` -> Pulse Mode dropdown text * ``default_counter_source`` -> Detection Mode dropdown text * ``default_control_algorithm`` -> Control Algorithm dropdown text * ``default_ex_user`` -> Experiment User * ``default_ex_name`` -> Experiment Name """ conf = self.conf def _set_text(widget, key): value = conf.get(key) if value is None or value == "": return widget.setText(str(value)) def _set_combo(widget, key): value = conf.get(key) if value is None or value == "": return text = str(value).strip() idx = widget.findText(text) if idx >= 0: widget.setCurrentIndex(idx) # Voltage limits. The pulse-voltage system limits (`min_vp` / # `max_vp`) double as the experiment-default values because in # this rig the experiment usually starts and ends at those # extremes. The DC voltage GUI defaults are *not* the same as # `max_vdc` (which is the safety hard cap, e.g. 14000) -- so we # use dedicated `default_vdc_*` keys. _set_text(self.vp_min, 'min_vp') _set_text(self.vp_max, 'max_vp') _set_text(self.vdc_min, 'default_vdc_min') _set_text(self.vdc_max, 'default_vdc_max') _set_text(self.max_ions, 'default_max_ions') _set_text(self.ex_time, 'default_ex_time_s') _set_text(self.ex_freq, 'default_ex_freq_hz') _set_text(self.vdc_steps_up, 'default_vdc_step_up') _set_text(self.vdc_steps_down, 'default_vdc_step_down') _set_text(self.detection_rate_init, 'default_detection_rate') _set_text(self.pulse_fraction, 'default_pulse_fraction') _set_text(self.pulse_frequency, 'default_pulse_frequency_khz') # Free-text identity fields. _set_text(self.ex_user, 'default_ex_user') _set_text(self.ex_name, 'default_ex_name') # Dropdowns. We match by visible text (case-sensitive) so the # operator can write 'Voltage' / 'Laser' / 'VoltageLaser' etc. # in config.toml without having to know the index. _set_combo(self.pulse_mode, 'default_pulse_mode') _set_combo(self.counter_source, 'default_counter_source') _set_combo(self.control_algorithm, 'default_control_algorithm')
[docs] def super_user_access(self): """ The function for override access Args: None Returns: None """ if not self.flag_super_user: warning = QtWidgets.QMessageBox(parent=self.centralwidget) warning.setIcon(QtWidgets.QMessageBox.Icon.Warning) warning.setWindowTitle("Confirm Access Override") warning.setText("Access Override can bypass device and gate safety checks.") warning.setInformativeText("Only continue if you understand the risk of running with missing hardware.") warning.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No) warning.setDefaultButton(QtWidgets.QMessageBox.StandardButton.No) if warning.exec() != QtWidgets.QMessageBox.StandardButton.Yes: self.error_message("Access Override was canceled.") self.timer.start(8000) return self.flag_super_user = True self.superuser.setStyleSheet("QPushButton{\nbackground: rgb(0, 255, 26)\n}") self.error_message("!!! Override Access Granted !!!") elif self.flag_super_user: self.flag_super_user = False self.superuser.setStyleSheet(self.original_button_style) self.error_message("!!! Override Access deactivated !!!") self.timer.start(8000)
[docs] def load_electrode_items(self, file_path): items = main_parameters.load_electrode_items(file_path) self.electrode.clear() self.electrode.addItems(items)
[docs] def update_elapsed_time(self, value): """ Update the speciemen voltage Args: value (float): The speciemen voltage Return: None """ self.elapsed_time.setText(str("{:.3f}".format(value)))
[docs] def update_total_ions(self, value): """ Update the speciemen voltage Args: value (float): The speciemen voltage Return: None """ self.total_ions.setText(str(value))
[docs] def update_speciemen_voltage(self, value): """ Update the speciemen voltage Args: value (float): The speciemen voltage Return: None """ self.speciemen_voltage.setText(str("{:.3f}".format(value)))
[docs] def update_pulse_voltage(self, value): """ Update the pulse voltage Args: value (float): The pulse voltage Return: None """ self.pulse_voltage.setText(str("{:.3f}".format(value)))
[docs] def update_detection_rate(self, value): """ Update the detection rate Args: value (float): The detection rate Return: None """ self.detection_rate.setText(str("{:.3f}".format(value)))
[docs] def read_text_lines( self, ): """ Read the text lines and convert them to a dictionary Args: None Return: None """ self.result_list = main_parameters.parse_textline_experiments(self.text_line.toPlainText()) self.variables.number_of_experiment_in_text_line = len(self.result_list) if self.variables.index_experiment_in_text_line < len(self.result_list): index_line = self.variables.index_experiment_in_text_line main_parameters.apply_textline_item( self.variables, self.conf, self.result_list[index_line], self.error_message, )
[docs] def setup_parameters_changes(self): """ Function to set up parameters changes Args: None Return: None """ try: if self.parameters_source.currentText() == 'TextLine': self.read_text_lines() return values = main_parameters.FormValues( user_name=self.ex_user.text(), ex_name=self.ex_name.text(), electrode=self.electrode.currentText(), ex_time=self.ex_time.text(), ex_freq=self.ex_freq.text(), max_ions=self.max_ions.text(), vdc_min=self.vdc_min.text(), vdc_max=self.vdc_max.text(), detection_rate_init=self.detection_rate_init.text(), pulse_fraction=self.pulse_fraction.text(), email=self.email.text(), vdc_steps_up=self.vdc_steps_up.text(), vdc_steps_down=self.vdc_steps_down.text(), control_algorithm=str(self.control_algorithm.currentText()), pulse_mode=str(self.pulse_mode.currentText()), vp_min=self.vp_min.text(), vp_max=self.vp_max.text(), pulse_frequency=self.pulse_frequency.text(), counter_source=str(self.counter_source.currentText()), criteria_time=self.criteria_time.isChecked(), criteria_ions=self.criteria_ions.isChecked(), criteria_vdc=self.criteria_vdc.isChecked(), ) corrections = main_parameters.apply_form_values( self.variables, self.conf, values, self.error_message, ) if "pulse_fraction" in corrections: self.pulse_fraction.setText(corrections["pulse_fraction"]) if "vp_max" in corrections: self.vp_max.setText(corrections["vp_max"]) if "vdc_max" in corrections: self.vdc_max.setText(corrections["vdc_max"]) if "pulse_frequency" in corrections: self.pulse_frequency.setText(corrections["pulse_frequency"]) except (ValueError, main_parameters.ParameterError): self.error_message("Please enter a valid number")
[docs] def start_experiment_clicked(self): """ Start the experiment worker thread Args: None Return: None """ if not self.variables.flag_main_gate or self.flag_super_user: self.start_experiment_worker() else: self.error_message("Please close the main gate or activate the Access Override")
[docs] def statistics_update(self): """ Update the statistics Args: None Return: None """ self.emitter.elapsed_time.emit(self.variables.elapsed_time) self.emitter.total_ions.emit(self.variables.total_ions) self.emitter.speciemen_voltage.emit(self.variables.specimen_voltage) self.emitter.pulse_voltage.emit(self.variables.pulse_voltage) self.emitter.detection_rate.emit(self.variables.detection_rate_current) self._update_vacuum_warning() self._update_laser_warning() if not self.variables.start_flag and self.variables.stop_flag: self.stop_experiment_clicked() if self.variables.vdc_hold != self.vdc_hold_old: self.dc_hold_clicked() self.vdc_hold_old = self.variables.vdc_hold
def _update_vacuum_warning(self): """Show a status-bar warning when any vacuum reading is above its configured threshold. Thresholds live in config.toml under the ``vacuum_threshold_*`` keys. ``-1`` (gauge read error) and missing values are ignored so we don't spam warnings for disabled / disconnected gauges. """ gauges = ( ("Main", "vacuum_main", "vacuum_threshold_main"), ("Buffer", "vacuum_buffer", "vacuum_threshold_buffer"), ("Buffer pre", "vacuum_buffer_backing", "vacuum_threshold_buffer_back"), ("Load lock", "vacuum_load_lock", "vacuum_threshold_load_lock"), ("Load lock pre", "vacuum_load_lock_backing", "vacuum_threshold_load_lock_back"), ("Cryo load lock", "vacuum_cryo_load_lock", "vacuum_threshold_cryo_load_lock"), ("Cryo load lock pre", "vacuum_cryo_load_lock_backing", "vacuum_threshold_cryo_load_lock_back"), ) warnings = [] for label, var_name, conf_key in gauges: value = getattr(self.variables, var_name, None) if value is None or value == -1 or value == 0: continue threshold = self.conf.get(conf_key) if threshold is None: continue try: threshold = float(threshold) except (TypeError, ValueError): continue if value > threshold: warnings.append(f"{label} {value:.2e} > {threshold:.2e} mbar") statusbar = getattr(self, "statusbar", None) if statusbar is None: return if warnings: statusbar.setStyleSheet("color: red; font-weight: bold;") statusbar.showMessage("Vacuum warning: " + "; ".join(warnings)) elif statusbar.currentMessage().startswith("Vacuum warning"): statusbar.clearMessage() statusbar.setStyleSheet("") def _update_laser_warning(self): """Show a status-bar warning when the laser is enabled in ``config.toml`` but the laser GUI cannot reach it on CLI. The flag ``variables.flag_laser_connected`` is set by the laser GUI (True on healthy CLI session, False on disconnect / NKTPBus mode / COM-port error). We only complain if the laser is actually expected to be connected -- if ``laser = "off"`` in config.toml the warning is suppressed. Vacuum warnings take priority on the status bar; this only writes when no vacuum warning is currently shown. """ statusbar = getattr(self, "statusbar", None) if statusbar is None: return if str(self.conf.get("laser", "off")).strip().lower() != "on": # Laser deliberately disabled -- nothing to warn about. if statusbar.currentMessage().startswith("Laser warning"): statusbar.clearMessage() statusbar.setStyleSheet("") return connected = bool(getattr(self.variables, "flag_laser_connected", False)) if not connected: current = statusbar.currentMessage() if current.startswith("Vacuum warning"): # Don't clobber an active vacuum warning. return statusbar.setStyleSheet("color: red; font-weight: bold;") statusbar.showMessage( "Laser warning: not connected on CLI (open Laser Control " "and use Override Access -> Switch to CLI, or check the " "COM port)." ) elif statusbar.currentMessage().startswith("Laser warning"): statusbar.clearMessage() statusbar.setStyleSheet("")
[docs] def stop_experiment_clicked(self): """ Stop the experiment worker thread Args: None Return: None """ if not self.start_button.isEnabled(): self.statistics_timer.stop() self.variables.stop_flag = True # Set the STOP flag self.stop_button.setEnabled(False) # Disable the stop button self.timer_stop_exp.start(1000) # Start the timer to run stop actions after 8 seconds
[docs] def start_experiment_worker(self): """ Start the experiment worker thread Args: None Return: None """ self.variables.start_flag = True self.variables.stop_flag = False self.variables.plot_clear_flag = True self.variables.clear_index_save_image = True self.variables.access_override_enabled = self.flag_super_user gui_logger = logging.getLogger("pyccapt.gui") gui_logger.info( "Start requested. pulse_mode=%s super_user=%s", self.variables.pulse_mode, self.flag_super_user, ) issues = device_checks.collect_startup_device_issues( self.conf, self.variables, pulse_mode=self.variables.pulse_mode, ) self.variables.override_disabled_devices = [issue.device for issue in issues] if self.flag_super_user else [] if issues: if self.flag_super_user: details = "; ".join(f"{item.device}: {item.reason}" for item in issues) warning_message = ( "Override active. Experiment is starting with unavailable enabled devices. " "Review the terminal log for the full device list." ) gui_logger.warning( "Override active. Device check found unavailable enabled devices: " "%s. Experiment will continue and skip unavailable hardware where possible.", details, ) self.error_message(warning_message) else: message = device_checks.format_startup_device_issue_message(issues) gui_logger.error("Experiment start blocked: %s", message) self.error_message(message) self.variables.start_flag = False self.variables.stop_flag = False return try: self.experiment_process = self.process_coordinator.start_experiment( self.variables, self.conf, self.experimetn_finished_event, self.x_plot, self.y_plot, self.t_plot, self.main_v_dc_plot, ) except Exception as exc: message = f"Experiment process could not start: {exc.__class__.__name__}: {exc}" gui_logger.exception("Experiment process could not start") self.error_message(message) self.variables.start_flag = False self.variables.stop_flag = False return gui_logger.info( "Experiment process started. pid=%s", getattr(self.experiment_process, "pid", "?"), ) self.start_button.setEnabled(False) self.counter_source.setEnabled(False) self.pulse_mode.setEnabled(False) self.parameters_source.setEnabled(False) self.pulse_fraction.setEnabled(False) self.ex_freq.setEnabled(False) self.ex_name.setEnabled(False) self.electrode.setEnabled(False) # control_algorithm intentionally stays enabled during the run so # the user can switch between Proportional / Aggressive / Adaptive / # PID on the fly. self.variables.elapsed_time = 0.0 self.variables.total_ions = 0 self.variables.specimen_voltage = 0.0 self.variables.pulse_voltage = 0.0 self.variables.detection_rate_current = 0.0 self.statistics_timer.start()
[docs] def about(self): """ Show the about window Args: None Return: None """ # Get the PyCCAPT version from your setup file # Replace 'your_version_here' with the actual way you extract the version pyccapt_version = 'your_version_here' # Create the about dialog about_win = QtWidgets.QDialog() about_win.setWindowTitle("PyCCAPT APT Experiment Control") about_win.setWindowIcon(QtGui.QIcon(str(runtime.project_path("files", "logo.png")))) # Add version information package_version = get_package_version('pyccapt') version_label = QtWidgets.QLabel(f"PyCCAPT Control Software Version {package_version}") version_label.setAlignment(Qt.AlignmentFlag.AlignCenter) # Add link to documentation documentation_label = QtWidgets.QLabel( 'For documentation, please visit: <a href="https://pyccapt.readthedocs.io/en/latest">Documentation</a>' ) documentation_label.setOpenExternalLinks(True) documentation_label.setAlignment(Qt.AlignmentFlag.AlignCenter) license_label = QtWidgets.QLabel(f"License: GPLv3") license_label.setAlignment(Qt.AlignmentFlag.AlignCenter) # Create a button to close the about dialog ok_button = QtWidgets.QPushButton("OK", about_win) ok_button.clicked.connect(about_win.accept) # Create a layout for the QDialog layout = QtWidgets.QVBoxLayout(about_win) layout.addWidget(version_label) layout.addWidget(documentation_label) layout.addWidget(ok_button) # Show the about dialog about_win.exec()
[docs] def take_screenshot(self): """ Take a screenshot of the GUI Args: None Return: None """ screen = QtWidgets.QApplication.primaryScreen() w = self.centralwidget screenshot = screen.grabWindow(w.winId()) try: screenshot.save(str(Path(self.variables.path) / "screenshot.png"), 'png') except Exception: pass
# ------------------------------------------------------------------ menus def _open_local_path(self, path): """Reveal *path* (file or folder) in the OS file manager / default app.""" url = QtCore.QUrl.fromLocalFile(str(path)) if not QtGui.QDesktopServices.openUrl(url): self.error_message(f"Could not open: {path}") def _open_external_url(self, url): if not QtGui.QDesktopServices.openUrl(QtCore.QUrl(url)): self.error_message(f"Could not open URL: {url}")
[docs] def open_data_folder(self): """Open the experiment data folder (where HDF5 / metadata is written).""" target = getattr(self.variables, "path", None) if not target: self.error_message("No data path is set yet — start an experiment first.") return path = Path(target) if not path.exists(): try: path.mkdir(parents=True, exist_ok=True) except Exception as e: self.error_message(f"Cannot create {path}: {e}") return self._open_local_path(path)
[docs] def open_project_folder(self): """Open the pyccapt project root folder.""" try: self._open_local_path(runtime.find_project_root()) except Exception as e: self.error_message(f"Cannot locate project root: {e}")
[docs] def open_config_in_editor(self): """Open config.toml in the system default editor.""" try: cfg = runtime.project_path("config.toml") except Exception as e: self.error_message(f"Cannot locate config.toml: {e}") return if not cfg.exists(): self.error_message(f"config.toml not found at {cfg}") return self._open_local_path(cfg)
[docs] def show_config_path(self): try: cfg = runtime.project_path("config.toml") except Exception as e: self.error_message(f"Cannot locate config.toml: {e}") return QtWidgets.QMessageBox.information( self.centralwidget, "PyCCAPT — config location", f"config.toml is at:\n{cfg}\n\nEdit it in any text editor and restart PyCCAPT for changes to take effect.", )
[docs] def show_device_status(self): """Pop up a quick view of which configured devices are reachable now.""" try: issues = device_checks.collect_startup_device_issues( self.conf, self.variables, pulse_mode=getattr(self.variables, "pulse_mode", None), ) available = device_checks.list_available_serial_ports() serial_issues = device_checks.collect_configured_serial_port_issues(self.conf, available_ports=available) except Exception as e: self.error_message(f"Could not run device check: {e}") return lines = [] if issues: lines.append("Startup issues:") lines.extend(f" - {i.device}: {i.reason}" for i in issues) else: lines.append("Startup checks: all enabled experiment devices reachable.") if serial_issues: lines.append("") lines.append("Configured serial ports unavailable:") lines.extend(f" - {i.device}: {i.reason}" for i in serial_issues) lines.append("") lines.append(f"Detected serial ports: {', '.join(available) if available else 'none'}") try: backend_ok, backend_msg = camera_device.check_camera_backend() except Exception as e: backend_ok, backend_msg = False, f"camera check failed: {e}" lines.append(f"Camera backend: {'OK' if backend_ok else 'unavailable'} — {backend_msg}") QtWidgets.QMessageBox.information(self.centralwidget, "PyCCAPT — device status", "\n".join(lines))
[docs] def show_serial_ports(self): try: ports = device_checks.list_available_serial_ports() except Exception as e: self.error_message(f"Could not enumerate serial ports: {e}") return text = "\n".join(ports) if ports else "(none detected)" QtWidgets.QMessageBox.information(self.centralwidget, "Available serial ports", text)
[docs] def show_keyboard_shortcuts(self): QtWidgets.QMessageBox.information( self.centralwidget, "PyCCAPT — keyboard shortcuts", "File\n" " Ctrl+D Open data folder\n" " Ctrl+Shift+S Take screenshot\n" " Ctrl+Q Exit\n\n" "Edit\n" " Ctrl+, Edit config.toml\n\n" "View\n" " Ctrl+1 Cameras\n" " Ctrl+2 Pumps & Vacuum\n" " Ctrl+3 Gates\n" " Ctrl+4 Laser control\n" " Ctrl+5 Stage control\n" " Ctrl+6 Visualization\n" " Ctrl+7 Baking\n\n" "Help\n" " F1 Online documentation", )
[docs] def on_stop_experiment_worker(self): """ Enable the start and stop buttons after experiment is finished Args: None Return: None """ self.emitter.total_ions.emit(self.variables.total_ions) # Update the total ions if self.variables.flag_end_experiment: self.start_button.setEnabled(True) self.stop_button.setEnabled(True) self.counter_source.setEnabled(True) # Enable the counter source self.pulse_mode.setEnabled(True) # Enable the pulse mode self.parameters_source.setEnabled(True) # Enable the parameters source self.pulse_fraction.setEnabled(True) # Enable the pulse fraction self.ex_freq.setEnabled(True) self.ex_name.setEnabled(True) self.electrode.setEnabled(True) # control_algorithm was never disabled during the run; nothing # to re-enable here. self.ex_number.setText(str(self.variables.counter)) self.experiment_process.join(1) print('experiment_process joined') # for getting screenshot of GUI screen = QtWidgets.QApplication.primaryScreen() w = self.centralwidget screenshot = screen.grabWindow(w.winId()) # with self.variables.lock_setup_parameters: screenshot.save(str(Path(self.variables.path) / "screenshot.png"), 'png') self.variables.flag_cameras_take_screenshot = True # with self.variables.lock_statistics: if self.variables.index_experiment_in_text_line < len(self.result_list): # Do next experiment in case of TextLine self.variables.index_experiment_in_text_line += 1 self.start_experiment_worker() else: self.variables.index_line = 0 self.variables.flag_end_experiment = False self.variables.flag_stop_tdc = False self.timer_stop_exp.stop()
[docs] def reset_heatmap_clicked(self): """ Reset the heatmap Args: None Return: None """ # with self.variables.lock_setup_parameters: if not self.variables.reset_heatmap: self.variables.reset_heatmap = True
[docs] def dc_hold_clicked(self): """ Hold the DC voltage Args: None Return: None """ if self.variables.vdc_hold: self.pulse_mode.setEnabled(True) # Disable the pulse mode elif not self.variables.vdc_hold: self.pulse_mode.setEnabled(False) # Enable the pulse mode
[docs] def set_min_voltage_clicked(self): """ Set the minimum voltage Args: None Return: None """ if self.variables.vdc_hold: self.variables.flag_new_min_voltage = True else: self.error_message("Hold the DC voltage first")
[docs] def wins_init(self): available_ports = device_checks.list_available_serial_ports() serial_issues = device_checks.collect_configured_serial_port_issues( self.conf, available_ports=available_ports, ) if serial_issues: message = device_checks.format_serial_port_issue_message( serial_issues, available_ports=available_ports, ) print(message) self.error_message(message) if str(self.conf.get("camera", "off")).strip().lower() == "on": self.camera_available, self.camera_status_message = camera_device.check_camera_backend() else: self.camera_available = False self.camera_status_message = "Camera support is disabled in config.toml." if self.camera_available: # Always start the camera process when the backend is present — # the worker handles 0/1/2 cameras dynamically and reconnects # hot-plugged devices, so the button stays usable either way. self.camera_process = self.process_coordinator.start_camera( self.variables, self.conf, self.camera_closed_event, self.camera_command_queue, ) if self.camera_status_message: self.camears.setToolTip(self.camera_status_message) else: self.camears.setEnabled(False) self.camears.setToolTip(self.camera_status_message) if self.camera_status_message and str(self.conf.get("camera", "off")).strip().lower() == "on": print(self.camera_status_message) if not serial_issues: self.error_message(self.camera_status_message) # GUI gate self.gui_gates = gui_gates.Ui_Gates(self.variables, self.conf) self.Gates = gui_gates.GatesWindow(self.gui_gates, flags=QtCore.Qt.WindowType.Tool) self.Gates.setWindowStyleFusion() self.gui_gates.setupUi(self.Gates) self.Gates.closed.connect(lambda: self.reset_button_color(self.gates_control)) # GUI Pumps and Vacuum self.SignalEmitter_Pumps_Vacuum = gui_pumps_vacuum.SignalEmitter() self.gui_pumps_vacuum = gui_pumps_vacuum.Ui_Pumps_Vacuum(self.variables, self.conf, self.SignalEmitter_Pumps_Vacuum) self.Pumps_vacuum = gui_pumps_vacuum.PumpsVacuumWindow( self.gui_pumps_vacuum, self.SignalEmitter_Pumps_Vacuum, flags=Qt.WindowType.Tool ) self.Pumps_vacuum.setWindowStyleFusion() self.gui_pumps_vacuum.setupUi(self.Pumps_vacuum) self.Pumps_vacuum.closed.connect(lambda: self.reset_button_color(self.pumps_vaccum)) self.variables.flag_pumps_vacuum_start = True # GUI Laser Control self.gui_laser_control = gui_laser_control.Ui_Laser_Control(self.variables, self.conf) self.Laser_control = gui_laser_control.LaserControlWindow(self.gui_laser_control, flags=Qt.WindowType.Tool) self.gui_laser_control.setupUi(self.Laser_control) self.Laser_control.closed.connect(lambda: self.reset_button_color(self.laser_control)) # GUI Stage Control self.gui_stage_control = gui_stage_control.Ui_Stage_Control(self.variables, self.conf) self.Stage_control = gui_stage_control.StageControlWindow(self.gui_stage_control, flags=Qt.WindowType.Tool) self.Stage_control.setWindowStyleFusion() self.gui_stage_control.setupUi(self.Stage_control) self.Stage_control.closed.connect(lambda: self.reset_button_color(self.stage_control)) # GUI Visualization self.visualization_process = self.process_coordinator.start_visualization( self.variables, self.conf, self.visualization_closed_event, self.visualization_command_queue, self.x_plot, self.y_plot, self.t_plot, self.main_v_dc_plot, )
def _show_sub_window(self, window, button): if window.isVisible(): window.raise_() window.activateWindow() else: window.show() window.raise_() window.activateWindow() button.setStyleSheet("background-color: green") def _grant_foreground_to(self, process): """Allow *process* to bring its own windows to the foreground. Windows blocks cross-process ``SetForegroundWindow`` calls by default, which is why the camera and visualization sub-windows (running in their own ``multiprocessing.Process``) ignored ``raise_()`` / ``activateWindow()`` requests. Calling ``AllowSetForegroundWindow(target_pid)`` from this process lifts that block for the next focus-grab the target tries. No-op on non-Windows platforms. """ if sys.platform != "win32": return if process is None: return pid = getattr(process, "pid", None) if not pid: return try: import ctypes ctypes.windll.user32.AllowSetForegroundWindow(int(pid)) except Exception: pass
[docs] def open_cameras_win(self): """ Open the Cameras window Args: None Return: None """ if not self.camera_available: self.error_message(self.camera_status_message or "No cameras are available on this system.") return # Send a single typed "show + front" command instead of toggling # two separate handshakes (was: flag_camera_win_show + Event). self._grant_foreground_to(self.camera_process) self.camera_command_queue.put("show_front") self.camears.setStyleSheet("background-color: green")
[docs] def check_closed_events(self): """ Check if the camera window is closed Args: None Return: None """ if self.camera_closed_event.is_set(): # Change the color of the push button when the camera window is closed self.reset_button_color(self.camears) self.camera_closed_event.clear() if self.experimetn_finished_event.is_set(): self.experimetn_finished_event.clear() self.on_stop_experiment_worker() if self.visualization_closed_event.is_set(): # Change the color of the push button when the camera window is closed self.reset_button_color(self.visualization) self.visualization_closed_event.clear()
[docs] def open_gates_win(self): """ Open the Gates window Args: None Return: None """ if hasattr(self, 'Gates') and self.Gates.isVisible(): self._show_sub_window(self.Gates, self.gates_control) else: self._show_sub_window(self.Gates, self.gates_control)
[docs] def open_pumps_vacuum_win( self, ): """ Open the Pumps and Vacuum window Args: None Return: None """ if hasattr(self, 'Pumps_vacuum') and self.Pumps_vacuum.isVisible(): self._show_sub_window(self.Pumps_vacuum, self.pumps_vaccum) else: self._show_sub_window(self.Pumps_vacuum, self.pumps_vaccum)
[docs] def open_laser_control_win(self): """ Open laser control window Args: None Return: None """ if hasattr(self, 'Laser_control') and self.Laser_control.isVisible(): self._show_sub_window(self.Laser_control, self.laser_control) else: self._show_sub_window(self.Laser_control, self.laser_control)
[docs] def open_stage_control_win(self): """ Open stage control window Args: None Return: None """ if hasattr(self, 'Stage_control') and self.Stage_control.isVisible(): self._show_sub_window(self.Stage_control, self.stage_control) else: self._show_sub_window(self.Stage_control, self.stage_control)
[docs] def open_visualization_win( self, ): """ Open visualization window Args: None Return: None """ # Send a single typed "show + front" command instead of toggling # two separate handshakes. self._grant_foreground_to(getattr(self, "visualization_process", None)) self.visualization_command_queue.put("show_front") self.visualization.setStyleSheet("background-color: green")
[docs] def open_baking_win(self): """ Open baking window Args: None Return: None """ if not hasattr(self, 'Baking'): self.gui_baking = gui_baking.Ui_Baking(self.variables, self.conf, self.SignalEmitter_Pumps_Vacuum) self.Baking = gui_baking.BakingWindow(self.gui_baking, flags=Qt.WindowType.Tool) self.Baking.setWindowStyleFusion() self.gui_baking.setupUi(self.Baking) self.Baking.closed.connect(lambda: self.reset_button_color(self.baking)) if self.Baking.isVisible(): self._show_sub_window(self.Baking, self.baking) else: self._show_sub_window(self.Baking, self.baking)
[docs] def reset_button_color(self, button): """ Reset the button color to the original color Args: button (QPushButton): The button to reset the color Return: None """ button.setStyleSheet("QPushButton{ background: rgb(85, 170, 255) }")
[docs] def error_message(self, message): """ Display an error message and start a timer to hide it after 8 seconds Args: message (str): Error message to display Return: None """ _translate = QtCore.QCoreApplication.translate self.Error.setText( _translate( "OXCART", "<html><head/><body><p><span style=\" color:#ff0000;\">" + message + "</span></p></body></html>" ) ) self.timer.start(8000)
[docs] def hideMessage( self, ): """ Hide the message and stop the timer Args: None Return: None """ # Hide the message and stop the timer _translate = QtCore.QCoreApplication.translate self.Error.setText( _translate("OXCART", "<html><head/><body><p><span style=\" color:#ff0000;\"></span></p></body></html>") ) self.timer.stop()
[docs] def cleanup( self, ): """Tear down every long-lived resource so the Python process can exit. The order matters: 1. Stop QThreads / QTimers / device handles inside each Ui_* instance. Without this the laser Worker QThread keeps looping forever (msleep(1000)) and the SmarAct poll timers keep firing on already-closed windows. 2. Force-close the sub-windows so their accept-paths run. 3. Terminate worker subprocesses and *wait briefly* for them to die - terminate() is async, and unjoined zombie subprocesses keep the multiprocessing Manager alive, which is what makes "close" appear to do nothing. 4. Quit the Qt event loop so app.exec() returns and __main__ can release the shared context + os._exit. """ # --- 1. Stop in-process QThreads / timers / device handles ------ for ui_attr in ("gui_baking", "gui_pumps_vacuum", "gui_gates", "gui_laser_control", "gui_stage_control"): ui = getattr(self, ui_attr, None) if ui is None or not hasattr(ui, "stop"): continue try: ui.stop() except Exception as exc: print(f"cleanup: {ui_attr}.stop() failed (non-fatal): {exc}") if hasattr(self, "SignalEmitter_Pumps_Vacuum"): try: self.SignalEmitter_Pumps_Vacuum.bool_flag_while_loop.emit(False) except Exception: pass # Daemon thread - join briefly so it gets a chance to honour the # stop signal, but don't hang if it's wedged on a serial read. if hasattr(self, 'gui_pumps_vacuum') and hasattr(self.gui_pumps_vacuum, 'gauges_thread'): try: self.gui_pumps_vacuum.gauges_thread.join(0.5) except Exception: pass # --- 2. Force-close the Qt sub-windows -------------------------- for attr in ("Gates", "Pumps_vacuum", "Laser_control", "Stage_control", "Baking"): window = getattr(self, attr, None) if window is None: continue try: window.force_close = True window.close() window.deleteLater() except Exception: pass # --- 3. Terminate worker subprocesses, wait briefly ------------- subs = [] if hasattr(self, "experiment_process") and self.experiment_process is not None: subs.append(("experiment", self.experiment_process)) if self.camera_process is not None: subs.append(("camera", self.camera_process)) if hasattr(self, 'visualization_process') and self.visualization_process is not None: subs.append(("visualization", self.visualization_process)) for name, proc in subs: try: if proc.is_alive(): proc.terminate() except Exception: pass for name, proc in subs: try: proc.join(timeout=2.0) if proc.is_alive(): print(f"cleanup: {name} subprocess did not exit within 2 s; killing") proc.kill() proc.join(timeout=1.0) except Exception as exc: print(f"cleanup: {name} join failed (non-fatal): {exc}") # --- 4. Quit the Qt event loop ---------------------------------- QtWidgets.QApplication.quit()
[docs] def closeEvent(self, event): reply = QtWidgets.QMessageBox.question( self, 'Close Confirmation', "Are you sure you want to close the PyCCAPT?", QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No, QtWidgets.QMessageBox.StandardButton.No, ) if reply == QtWidgets.QMessageBox.StandardButton.Yes: event.accept() else: event.ignore()
[docs] class SignalEmitter(QtCore.QObject): elapsed_time = QtCore.pyqtSignal(float) total_ions = QtCore.pyqtSignal(int) speciemen_voltage = QtCore.pyqtSignal(float) pulse_voltage = QtCore.pyqtSignal(float) detection_rate = QtCore.pyqtSignal(float)
[docs] def get_package_version(package_name): try: return version(package_name) except PackageNotFoundError: return None
[docs] class MyPyCCAPT(QtWidgets.QMainWindow): def __init__(self, variables, conf, x_plot, y_plot, t_plot, main_v_dc_plot): super(MyPyCCAPT, self).__init__() self.ui = Ui_PyCCAPT(variables, conf, x_plot, y_plot, t_plot, main_v_dc_plot) self.ui.setupUi(self)
[docs] def closeEvent(self, event): reply = QtWidgets.QMessageBox.question( self, 'Close Confirmation', "Are you sure you want to close the PyCCAPT?", QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No, QtWidgets.QMessageBox.StandardButton.No, ) if reply == QtWidgets.QMessageBox.StandardButton.Yes: # Hard watchdog: fire os._exit after 5 s no matter what. # cleanup() can hang indefinitely if a SmarAct ctl.Close # call collides with an in-flight Reference (the SDK is # not thread-safe) or if a serial port refuses to release. # We can't fix every possible blocker individually, so a # daemon thread that calls os._exit is the only guarantee # the user's "X" click actually closes the program. import os as _os import threading as _threading def _force_exit(): import time as _time _time.sleep(5.0) print("Close watchdog: forcing os._exit after 5 s") _os._exit(0) t = _threading.Thread(target=_force_exit, daemon=True) t.start() try: self.ui.cleanup() except Exception as exc: print(f"Cleanup raised (non-fatal): {exc}") event.accept() else: event.ignore()
if __name__ == "__main__": try: conf, project_root = runtime.load_project_config() except Exception as exc: print('Can not load the configuration file') print(exc) sys.exit() # Configure application-wide logging as early as possible so that any # subsequent device probing, configuration parsing, or process startup # is captured to disk under <project_root>/files/logs/gui/. gui_logger = loggi.setup_application_logging(project_root) loggi.log_configuration_snapshot(gui_logger, conf) shared = runtime.create_shared_context(conf) shared.variables.log_path = str(project_root) app = QtWidgets.QApplication(sys.argv) app.setStyle('Fusion') window = MyPyCCAPT( shared.variables, conf, shared.x_plot, shared.y_plot, shared.t_plot, shared.main_v_dc_plot, ) window.show() exit_code = app.exec() # Tear down the multiprocessing Manager and unlink the shared-memory # ring buffers - without this the parent Python interpreter waits # forever for the Manager subprocess and the close button appears to # do nothing. try: runtime.release_shared_context(shared) except Exception as exc: print(f"Shared-context teardown failed (non-fatal): {exc}") # Safety net: even after cleanup() and release_shared_context, a # rogue daemon thread or unjoinable subprocess can keep the Python # interpreter alive so the user sees a "frozen" main window long # after they hit close. os._exit is a hard exit that bypasses all # of that machinery - on Windows the OS reclaims any leftover # shared memory automatically, so this is safe. import os os._exit(exit_code)