Files
bdbase/base.py
2022-06-28 10:04:37 +02:00

1934 lines
74 KiB
Python

""" Base accelerator module
"""
import argparse
from collections import OrderedDict
from datetime import datetime
import getpass
import h5py
import inspect
import platform
import re
import os
import sys
import time
#import numpy as np
# Third-party modules
from qtpy.QtCore import (PYQT_VERSION_STR, QEventLoop, QFile, QIODevice,
QSettings, QSize, Qt, QThread, QTimer, Signal, Slot)
from qtpy.QtCore import __version__ as QT_VERSION_STR
from qtpy.QtGui import (QColor, QKeySequence, QIcon, QPainter, QPalette,
QPixmap)
from qtpy.QtPrintSupport import QPrinter, QPrintDialog
from qtpy.QtWidgets import (QAction, QApplication, QDialog, QFrame, QLabel,
QMainWindow, QMessageBox, QPlainTextEdit,
QProgressBar, QScrollArea, QSizePolicy,
QSplashScreen, QVBoxLayout, QWidget)
from pyqtacc.bdbase.enumkind import Facility, MsgSeverity, UserMode
from pyqtacc.bdbase.helpbrowser import HelpBrowser
from pyqtacc.bdbase.readjson import ReadJSON
from pyqtacc.bdbase.savehdf import QSaveHDF
from pyqtacc.bdbase.hdf5filemenu import HDF5GroupBox
from pyqtacc.bdbase.sendelog import QSendToELOG
from pyqtacc.bdbase.screenshot import QScreenshot
from pyqtacc.bdbase.guiframe import GUIFrame
from caqtwidgets.pvwidgets import QNoDockWidget
import PyCafe
_abspath = os.path.dirname(os.path.abspath(__file__))
_pymodule = os.path.basename(__file__)
_appname, _appext = _pymodule.split(".")
_appversion = "1.0.0"
_author = "J. Chrin"
PROGRESS_BAR_THREAD_INIT = 0
PROGRESS_BAR_THREAD_START = 1
PROGRESS_BAR_THREAD_ABORTING = 2
PROGRESS_BAR_THREAD_ABORTED = 3
PROGRESS_BAR_THREAD_ERROR = 4
PROGRESS_BAR_THREAD_END = 100
CENTRAL_WIDGET_MINIMUM_HEIGHT = 840
CENTRAL_WIDGET_MINIMUM_WIDTH = 1240
SLS_CENTRAL_WIDGET_MINIMUM_HEIGHT = 840
SLS_CENTRAL_WIDGET_MINIMUM_WIDTH = 940
def _line():
"""Macro to return the current line number.
The current line number within the file is used when
reporting messages to the message logging window.
Returns:
int: Current line number.
"""
return inspect.currentframe().f_back.f_lineno
class BaseWindow(QMainWindow):
""" BaseWindow
"""
####trigger_elog_entry = Signal(bool, str, str)
trigger_log_message = Signal(str, str, int, str, dict)
trigger_progressbar = Signal(int)
trigger_progressbar_str = Signal(int, str)
##trigger_hdf_save = Signal()
class SaveFigureThread(QThread):
def __init__(self, parent, folder_name, time_in_seconds,
reanalysis_time):
QThread.__init__(self)
self.parent = parent
self.settings = self.parent.settings
self.folder_name = folder_name
self.time_in_seconds = time_in_seconds
self.reanalysis_time = reanalysis_time
self.all_data = self.parent.all_data
#def __del__(self):
# self.wait()
def run(self):
attach_files = []
folder_name = self.folder_name
date_str = self.parent.add_date_to_path(
time_in_seconds=self.time_in_seconds,
reanalysis_time_in_seconds=self.reanalysis_time)
for i, (nfig, name) in enumerate(
zip(self.settings.data["GUI"]["resultsSeq"],
self.settings.data["GUI"]["subResultsTabTitle"])):
canvas = 'Canvas {0}'.format(i+1)
name_base = name.replace(' ', '_').lower()
for idx in range(0, nfig):
if self.all_data['Figure data'][canvas] is not None:
name = name_base + "_{0}".format(
idx) if idx > 0 else name_base
save_dest = (folder_name + date_str + '_' + name +
'.png')
if not os.path.exists(save_dest):
if self.all_data['Figure data'][canvas][
idx] is not None:
self.all_data['Figure data'][canvas][
idx].savefig(save_dest)
attach_files.append(save_dest)
##self.parent.attach_files = attach_files
if attach_files:
self.parent.attach_files = attach_files
print("All files attached")
else:
print("No files to attach", flush=True)
time.sleep(0.2) #
class HDFSave(QThread):
"""Thread for hdf analysis
"""
def __init__(self, parent, from_dialog):
QThread.__init__(self)
self.parent = parent
self.from_dialog=from_dialog
def __del__(self):
self.wait()
def run(self):
"""Run hdf thread
"""
QApplication.processEvents(QEventLoop.ExcludeUserInputEvents, 5000)
self.all_data = self.parent.all_data
#Reanalysis data
if self.all_data is not None:
ts_in_seconds = self.all_data['Ambient data']['Time in seconds']
now_in_seconds = self.all_data['Processed data'][
'Reanalysis time in seconds'] if \
self.all_data['Processed data']['Reanalysis time'] else None
from_hdf = bool(
self.all_data['Processed data']['Reanalysis time'])
#print("Parent Thread before ==>", self.parent.hdf_filename)
if self.parent.hdf_filename is None or not self.from_dialog:
self.parent.set_new_hdf_filename(ts_in_seconds,
now_in_seconds)
#else:
# self.parent.set_new_hdf_filename(ts_in_seconds,
# now_in_seconds)
#print("Parent Thread after ==>", self.parent.hdf_filename)
#Open hdf5file here and
#mess = "HDF save to file {0} proceeding...".format(
# self.parent.hdf_filename)
#self.parent.trigger_log_message.emit(MsgSeverity.INFO.name,
# _pymodule, _line(),
# mess, {})
isOK = [True] * 3
try:
with h5py.File(self.parent.hdf_filename, 'w') as dataH5:
isOK[0] = self.parent.add_pvs_to_hdf(
dataH5, pv_list=self.parent.pv_machine_list,
from_hdf=from_hdf)
isOK[1] = self.parent.add_general_to_hdf(dataH5)
isOK[2] = self.parent.add_to_hdf(dataH5, proc=True,
raw=True)
self.parent.hdf_save_completed = True
base_mess = "Processed data saved" \
if False not in isOK \
else "Data integrity errors in saving"
show_mess = "{0} to {1}".format(
base_mess, self.parent.hdf_filename)
self.parent.trigger_log_message.emit(
MsgSeverity.INFO.name, _pymodule, _line(),
show_mess, {})
except OSError as e:
_mess = "OSError in saving to file {0}: {1}".format(
self.parent.hdf_filename, str(e))
self.parent.trigger_log_message.emit(
MsgSeverity.ERROR.name, _pymodule, _line(), _mess, {})
self.parent.hdf_save_completed = False
class HDFThread(QThread):
"""Thread for hdf analysis
"""
trigger_thread_event = Signal(dict)
def __init__(self, parent, analysis_procedure, all_data):
QThread.__init__(self)
self.parent = parent
self.analysis_procedure = analysis_procedure
self.all_data = all_data
self.hdf_filename_loaded = self.parent.hdf_filename_loaded
def __del__(self):
self.wait()
def run(self):
"""Run hdf thread
"""
self.all_data = self.analysis_procedure.load_hdf_file(
self.hdf_filename_loaded)
if not self.all_data:
return
try:
expt_dict = self.all_data['experiment']
except KeyError:
expt_dict = None
pass
#Open hdf5file here and
mess = "HDF file {} analysis proceeding...".format(
self.hdf_filename_loaded)
self.parent.trigger_log_message.emit(MsgSeverity.INFO.name,
_pymodule, _line(),
mess, {})
all_dict = self.analysis_procedure.reanalyze(self.all_data)
if expt_dict is not None:
all_dict['experiment'] = expt_dict
# Emit results
if all_dict is not None:
self.trigger_thread_event.emit(all_dict)
mess = "HDF file {} analysis succeeded".format(
self.hdf_filename_loaded)
self.parent.trigger_log_message.emit(
MsgSeverity.INFO.name, _pymodule, _line(), mess, {})
else:
mess = "HDF file {} has wrong content!!".format(
self.hdf_filename_loaded)
self.parent.trigger_log_message.emit(
MsgSeverity.ERROR.name, _pymodule, _line(), mess, {})
class AnalysisThread(QThread): #QRunnable does not have status methods
#e.g. isRunning()
#nor supports signals and slots
"""Analysis thread
"""
trigger_thread_event = Signal(dict)
def __init__(self, parent, analysis_procedure, input_parameters):
#QThread.__init__(self)
super().__init__()
self.parent = parent
self.analysis_procedure = analysis_procedure
self.input_parameters = input_parameters
try:
if input_parameters['debug']:
print("AnalysisThread", self.input_parameters)
except KeyError:
pass
def __del__(self):
self.wait()
def run(self):
"""Run thread
"""
all_dict = self.analysis_procedure.measure_and_analyze(
self.input_parameters)
# Emit results
if all_dict is not None:
self.trigger_thread_event.emit(all_dict)
mess = "Analysis completed"
self.parent.trigger_log_message.emit(
MsgSeverity.INFO.name, _pymodule, _line(), mess, {})
else:
mess = "No data returned from analysis procedure."
self.parent.trigger_log_message.emit(
MsgSeverity.WARN.name, _pymodule, _line(), mess, {})
def __init__(self, parent=None, pymodule=None, appversion=None, title="",
user_mode=UserMode.OPERATION, facility=Facility.SwissFEL,
extended=True, has_optics=True):
super(BaseWindow, self).__init__(parent)
self.parent = parent
self.has_optics = has_optics
self.pymodule = pymodule if pymodule else _pymodule
self.appversion = appversion if appversion else _appversion
self.source_file = None
self.author = _author
self.appversion = _appversion
self.title = title
self.facility = facility
self.user_mode = user_mode
self.appname, self.appext = self.pymodule.split(".")
print("=============================================")
print("Starting {0} at: {1}".format(self.appname, datetime.now()))
print("User: {0} Host: {1}".format(os.getlogin(), os.uname()[1]))
print("=============================================", flush=True)
self.cafe = PyCafe.CyCafe()
self.cyca = PyCafe.CyCa()
self.cafe_exception = PyCafe.CafeException
self.cafe.enableExceptions = False
self.caget = self.cafe.caget
self.caput = self.cafe.caput
self.application_recent_files = []
self.application_geometry = None
self.screenshot_titles = []
self.screenshot_items = []
self.filename = None
self.hdf_filename_loaded = "NONE" #For loading into hdf dockwidget
self.hdf_filename = None #For saving
self.hdf_dialog = None
self.daq_analysis_completed = False
self.hdf_save_completed = False
self.setObjectName("MainWindow")
self.setWindowTitle(self.appname)
self.statusbar_label = QLabel()
self.statusbar = self.statusBar()
self.progressbar = QProgressBar(self)
self.menu = self.menuBar()
self.settings = ReadJSON(self.appname)
'''
try:
dirname = self.settings.data["stdout"]["destination"]
except KeyError:
dirname = "/tmp/"
if not os.path.exists(dirname):
os.mkdir(dirname)
fname = dirname + self.appname + ".log"
file_obj = os.open(fname, os.O_RDWR|os.O_CREAT)
os.close(file_obj)
'''
self.elog_dest = self.settings.data["Elog"]["destination"]
self.screenshot_dest = self.settings.data["screenshot"]["destination"]
self.stdlog_dest = (self.settings.data["stdlog"]["destination"] +
self.appname + ".log")
self.date_str = None
self.autopost_elog = True
try:
self.hdf_dest = self.settings.data["hdf"]["destination"]
except KeyError:
self.hdf_dest = None
try:
self.pv_machine_list = self.settings.data["hdf"]["experiment"]
#pv_machine_dict = self.cafe.getDictionary(self.pv_machine_list)[0]
except KeyError:
self.pv_machine_list = None
try:
self.autopost_epics = self.settings.data["menuFlags"]["hasEpics"]
except KeyError as error:
print("KeyError in base.py, init:", error)
self.autopost_epics = True
try:
self.autopost_hdf = self.settings.data["menuFlags"]["hasH5"]
except KeyError as error:
print("KeyError in base.py, init:", error)
self.autopost_hdf = True
self.all_input_parameters = {} #gui
self.all_input_labels = {} #gui
self.all_expert_parameters = {} #gui
self.all_expert_labels = {} #gui
self.input_parameters = {} #analysis thread
self.input_labels = {}
#self.input_value_dict = {}
self.expert_parameters = {} #added to input_parameters
self.expert_labels = {}
self.read_input_parameters()
self.all_data = {}
self.hdf_thread = None
self.save_hdf_thread = None
self.analysis_thread = None
self.analysis_procedure = None
try:
from src.analysis import AnalysisProcedure
self.analysis_procedure = AnalysisProcedure(self)
except ImportError as e:
print("Import Error:", e)
##self.trigger_elog_entry.connect(self.receive_elog_notification)
##self.trigger_hdf_save.connect(self.save_to_hdf)
self.msg_severity_qcolor = {
MsgSeverity.FATAL: QColor(
self.settings.data["MsgSeverity"]["fatal"]),
MsgSeverity.ERROR: QColor(
self.settings.data["MsgSeverity"]["error"]),
MsgSeverity.WARN: QColor(
self.settings.data["MsgSeverity"]["warn"]),
MsgSeverity.WARNING: QColor(
self.settings.data["MsgSeverity"]["warn"]),
MsgSeverity.INFO: QColor(
self.settings.data["MsgSeverity"]["info"]),
MsgSeverity.DEBUG: QColor(
self.settings.data["MsgSeverity"]["debug"]),
MsgSeverity.FATAL.name: QColor(
self.settings.data["MsgSeverity"]["fatal"]),
MsgSeverity.ERROR.name: QColor(
self.settings.data["MsgSeverity"]["error"]),
MsgSeverity.WARN.name: QColor(
self.settings.data["MsgSeverity"]["warn"]),
MsgSeverity.WARNING.name: QColor(
self.settings.data["MsgSeverity"]["warn"]),
MsgSeverity.INFO.name: QColor(
self.settings.data["MsgSeverity"]["info"]),
MsgSeverity.DEBUG.name: QColor(
self.settings.data["MsgSeverity"]["debug"])
}
#QSetttings
self.restore_application_settings()
self.init_toolbar()
self.init_statusbar_wgt()
self.init_progressbar_wgt()
self.trigger_log_message.connect(self.receive_log_message)
self.trigger_progressbar.connect(self.receive_progressbar)
self.trigger_progressbar_str.connect(self.receive_progressbar)
self.statusbar.addPermanentWidget(self.progressbar, 0)
self.appname = self.pymodule.split(".")[0]
self.mainwindow = QWidget()
self.mainwindow_layout = QVBoxLayout()
if self.facility == Facility.SwissFEL:
from pyqtacc.sf.guiheader import GUIHeader
elif self.facility == Facility.SLS:
from pyqtacc.sls.guiheader import GUIHeader
self.gui_header = GUIHeader(self, user_mode=self.user_mode,
extended=extended)
self.mainwindow_layout.addWidget(self.gui_header.header_wgt)
self.gui_frame = GUIFrame(self, has_optics=self.has_optics)
self.show_log_message = self.gui_frame.show_log_message
self.hdf_dock_widget = QNoDockWidget(" HDF5", self)
self.init_hdf_analysis_wgt()
self.mainwindow_layout.addWidget(self.gui_frame.central_tab_widget)
self.mainwindow.setLayout(self.mainwindow_layout)
if self.facility == Facility.SwissFEL:
self.mainwindow.setMinimumHeight(CENTRAL_WIDGET_MINIMUM_HEIGHT)
self.mainwindow.setMinimumWidth(CENTRAL_WIDGET_MINIMUM_WIDTH)
else:
self.mainwindow.setMinimumHeight(SLS_CENTRAL_WIDGET_MINIMUM_HEIGHT)
self.mainwindow.setMinimumWidth(SLS_CENTRAL_WIDGET_MINIMUM_WIDTH)
self.setCentralWidget(self.mainwindow)
self.show_log_message(MsgSeverity.INFO, _pymodule, _line(),
"Application configured")
def read_input_parameters(self):
for key, dictval in self.settings.data["Parameters"].items():
if not isinstance(dictval, dict):
continue
if "value" in dictval["data"]:
if self.settings.data["Parameters"][key]["flag"]:
self.input_parameters[key] = dictval["data"]["value"]
self.all_input_parameters[key] = dictval["data"]["value"]
else:
try:
link_list = []
if 'link' in dictval["data"]:
link_list = dictval["data"]["link"]
#print("link_list", link_list)
if len(link_list) > 1:
val = self.settings.data[link_list[0]]
for link in link_list[1:]:
val = val[link]
#print("lval", val)
else:
val = []
if isinstance(link_list[0], dict):
for inner_key in self.settings.data[
link_list[0]].keys():
val.append(inner_list)
else:
for lvalue in self.settings.data[link_list[0]]:
val.append(lvalue)
if self.settings.data["Parameters"][key]["flag"]:
self.input_parameters[key] = val
self.all_input_parameters[key] = val
#print("val=",val, key)
except KeyError as e:
print("Key Error in base.py initialization:", e)
if self.settings.data["Parameters"][key]["flag"]:
self.input_labels[key] = dictval["data"]["text"]
self.all_input_labels[key] = dictval["data"]["text"]
#print(key, self.settings.data["Parameters"][key]["flag"])
if "Expert" not in self.settings.data:
return
for key, dictval in self.settings.data["Expert"].items():
if not isinstance(dictval, dict):
continue
if "value" in dictval["data"]:
if self.settings.data["Expert"][key]["flag"]:
self.expert_parameters[key] = dictval["data"]["value"]
self.all_expert_parameters[key] = dictval["data"]["value"]
else:
try:
link_list = []
if 'link' in dictval["data"]:
link_list = dictval["data"]["link"]
#print("link_list", link_list)
if len(link_list) > 1:
val = self.settings.data[link_list[0]]
for link in link_list[1:]:
val = val[link]
#print("lval", val)
else:
val = []
if isinstance(link_list[0], dict):
for inner_key in self.settings.data[
link_list[0]].keys():
val.append(inner_list)
else:
for lvalue in self.settings.data[link_list[0]]:
val.append(lvalue)
if self.settings.data["Expert"][key]["flag"]:
self.expert_parameters[key] = val
self.all_expert_parameters[key] = val
#print("val=",val, key)
except KeyError as e:
print("Key Error in base.py initialization:", e)
if self.settings.data["Expert"][key]["flag"]:
self.expert_labels[key] = dictval["data"]["text"]
self.all_expert_labels[key] = dictval["data"]["text"]
def init_toolbar(self):
""" Prepare toolbar
"""
#menu / toolbar
font = self.menu.font()
font.setPointSize(12)
self.menu.setFont(font)
menu_file = self.menu.addMenu("&Output")
menu_file.setFont(font)
menu_file_actions = []
menu_view = self.menu.addMenu("&View")
menu_view.setFont(font)
menu_view_actions = []
menu_help = self.menu.addMenu("&Help")
menu_help.setFont(font)
menu_help_actions = []
file_toolbar = self.addToolBar("Output")
file_toolbar.setObjectName("FileToolBar")
view_toolbar = self.addToolBar("View")
view_toolbar.setObjectName("ViewToolBar")
# To implement
#menu_daq = self.menu.addMenu("&DAQ")
#menu_daq_actions = []
#daq_toolbar = self.addToolBar("DAQ")
#daq_toolbar.setObjectName("ViewToolBar")
help_toolbar = self.addToolBar("Help")
help_toolbar.setObjectName("HelpToolBar")
if self.autopost_epics:
epics_action = self.create_action(
"&EPICS", slot=self.save_to_epics, shortcut="Ctrl+Shift+E",
icon="epics", tip="Write to EPICS PVs")
menu_file_actions.append(epics_action)
if self.autopost_hdf:
hdf_action = self.create_action(
"Save&HDF", slot=self.save_to_hdf_dialog, shortcut="Ctrl+H",
icon="hdf", tip="Save as HDF")
menu_file_actions.append(hdf_action)
elog_action = self.create_action(
"&Elog", slot=self.send_to_elog, shortcut="Alt+E", icon="elog",
tip="Send to ELOG")
menu_file_actions.append(elog_action)
menu_file_actions.append(QAction().setSeparator(True))
screenshot_action = self.create_action(
"&Screenshot", slot=self.take_screenshot, shortcut="Alt+S",
icon="screenshot", tip="Screenshot of selected items")
menu_file_actions.append(screenshot_action)
print_action = self.create_action(
"&Print", slot=self.send_to_printer, shortcut=QKeySequence.Print,
icon="fileprint", tip="Send to printer")
menu_file_actions.append(print_action)
stdout_action = self.create_action(
"stdout &Log", slot=self.open_stdout_log, shortcut="Alt+L",
icon="filestdlog", tip="Open stdout log file")
menu_view_actions.append(stdout_action)
clear_log_action = self.create_action(
"&Clear log window", slot=self.clear_log_window, shortcut="Alt+C",
icon="viewclearlog", tip="Clears message log window")
menu_view_actions.append(clear_log_action)
quit_action = self.create_action(
"&Quit", slot=self.close, shortcut="Ctrl+Q", icon="filequit",
tip="Exit application, closing all windows")
menu_file_actions.append(QAction().setSeparator(True))
menu_file_actions.append(quit_action)
help_action = self.create_action(
"Help", slot=self.show_help, shortcut=QKeySequence.HelpContents,
icon="home", tip="Help!")
menu_help_actions.append(help_action)
about_action = self.create_action(
"About", slot=self.show_about, shortcut=QKeySequence.WhatsThis,
icon="helpabout", tip="About this app")
menu_help_actions.append(about_action)
self.add_actions(menu_file, tuple(menu_file_actions))
self.add_actions(menu_view, tuple(menu_view_actions))
self.add_actions(menu_help, tuple(menu_help_actions))
self.add_actions(file_toolbar, tuple(menu_file_actions[:-1]))
self.add_actions(view_toolbar, tuple(menu_view_actions))
self.add_actions(help_toolbar, tuple(menu_help_actions))
save_all_action = self.create_action(
"Save&ALL", slot=self.write_to_destinations,
shortcut="Ctrl+Z", icon="save_all", tip="Save All")
#toolbar
# Place ExitWidget on the far right
exit_toolbar = self.addToolBar("EXIT")
exit_toolbar.setObjectName("ExitToolBar")
spacer_wgt = QWidget()
spacer_wgt.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
spacer_wgt.setVisible(True)
exit_toolbar.addWidget(spacer_wgt)
#_qsize = exit_toolbar.iconSize()
#print("qsize", _qsize)
true_list = [self.autopost_epics, self.autopost_hdf, self.autopost_elog]
exit_toolbar.setIconSize(QSize(true_list.count(True)*30+10, 24))
#exit_toolbar.setIconSize(_qsize)
self.add_actions(exit_toolbar, (save_all_action, "Space", None, "Space",
quit_action))
def combine_save_icons(self): #epics=False, hdf=False, elog=False):
epics = self.autopost_epics
hdf = self.autopost_hdf
elog = self.autopost_elog
combinedIcon = QIcon()
startx1 = 0
startx2 = 0
startx3 = 0
true_list = [epics, hdf, elog]
if true_list.count(True) == 1:
startx1 = 0
startx2 = 0
startx3 = 0
elif true_list.count(True) == 3:
startx1 = 20
startx2 = 150
startx3 = 300
else:
if epics:
startx1 = 20
startx2 = 150
startx3 = 150
elif hdf:
startx2 = 20
startx3 = 150
comboPixmap = QPixmap(130*true_list.count(True), 100) #420, 100
#comboPixmap.fill(Qt.transparent)
comboPixmap.fill(QColor("#dcdcdc")) #"#bcc6cc"
epicsImage = QPixmap(":/epics.png")
hdfImage = QPixmap(":/hdfS.png")
elogImage = QPixmap(":/elog.png")
painter = QPainter(comboPixmap)
if epics:
painter.drawPixmap(startx1, 0, epicsImage, 0, -20, 100, 160)
if hdf:
painter.drawPixmap(startx2, 0, hdfImage, 0, -20, 100, 160)
if elog:
painter.drawPixmap(startx3, 0, elogImage)
combinedIcon.addPixmap(comboPixmap)
painter.end()
return combinedIcon
################# Actions
def add_actions(self, target, actions):
""" Add to toolbar
"""
for action in actions:
if action == "Space":
qf = QFrame()
qf.setFixedWidth(6)
target.addWidget(qf)
elif action is None:
target.addSeparator()
else:
target.addAction(action)
def create_action(self, text, slot=None, shortcut=None, icon=None, tip=None,
checkable=False):# signal="triggered()"):
""" Helper fn
"""
action = QAction(text, self)
if icon is not None:
if icon == "save_all":
action.setIcon(QIcon(self.combine_save_icons()))
action.setIconText("Save All")
else:
action.setIcon(QIcon(":/{0}.png".format(icon)))
if shortcut is not None:
action.setShortcut(shortcut)
if tip is not None:
action.setToolTip(tip)
if slot is not None:
action.triggered.connect(slot)
if checkable:
action.setCheckable(True)
return action
def verify_close_event(self):
""" Overrides QMainWindow method
"""
if self.analysis_thread is not None:
if self.analysis_thread.isRunning():
_mess = ("Measurement in progress. " +
"Please try again in a few seconds.")
QMessageBox.information(self, "Exit", _mess,
QMessageBox.Ok)
return False
if self.hdf_thread:
if self.hdf_thread.isRunning():
_mess = ("HDF analysis in progress. " +
"Please try again in a few seconds.")
QMessageBox.information(self, "Exit",
_mess, QMessageBox.Ok)
return False
if self.daq_analysis_completed and not self.hdf_save_completed:
if not self.all_data['Processed data']['Reanalysis time']:
_mess = ("Are you sure you wish to exit " +
"without saving data to HDF?")
qm = QMessageBox()
reply = qm.warning(self, "Exit", _mess,
QMessageBox.Yes | QMessageBox.No)
if reply == QMessageBox.No:
return False
return True
@Slot()
def closeEvent(self, event):
""" Overrides QMainWindow method
"""
#Close all dock widgets
#self.removeDockWidget(self.hdf_dock_widget)
self.save_application_settings()
QApplication.processEvents()
self.cafe.monitorStopAll()
time.sleep(0.05)
self.cafe.terminate()
time.sleep(0.05)
QApplication.closeAllWindows()
event.accept()
@Slot()
def clear_log_window(self):
""" Requests user confirmation before wiping out log message
window
"""
reply = QMessageBox.question(
self, "Clear Message Log",
"Do you wish to wipe out all messages from the log window?",
QMessageBox.Yes | QMessageBox.No)
if reply == QMessageBox.No:
return False
elif reply == QMessageBox.Yes:
return self.gui_frame.log_wgt.clear()
@Slot()
def open_stdout_log(self):
""" Slot to stdout_action
"""
log_title = self.appname + ": stdout"
sys.stdout.flush()
file = None
if QFile.exists(self.stdlog_dest):
file = open(self.stdlog_dest)
else:
message = "Error Log File: '{0}' does not exist!".format(
self.stdlog_dest)
QMessageBox.warning(self, log_title, message)
return
text = file.read()
file.close()
text_edit = QPlainTextEdit()
text_edit.setPlainText(text)
text_edit.setReadOnly(True)
bgcolor = ("QPlainTextEdit{background-color:" +
self.settings.data["StyleGuide"]["bgErrorLogFile"] + "}")
text_edit.setStyleSheet(bgcolor)
text_edit.verticalScrollBar().setValue(
text_edit.verticalScrollBar().maximum())
text_edit.horizontalScrollBar().setValue(
text_edit.horizontalScrollBar().maximum())
scroll_area = QScrollArea()
scroll_area.setBackgroundRole(QPalette.Light)
scroll_area.setWidgetResizable(True)
scroll_area.setMinimumHeight(600)
scroll_area.setMinimumWidth(700)
scroll_area.setWindowTitle(log_title)
scroll_area.verticalScrollBar().setValue(
scroll_area.verticalScrollBar().maximum())
scroll_area.horizontalScrollBar().setValue(
scroll_area.horizontalScrollBar().maximum())
scroll_area.verticalScrollBar().setSliderPosition(
scroll_area.verticalScrollBar().maximum())
scroll_area.horizontalScrollBar().setSliderPosition(
scroll_area.horizontalScrollBar().maximum())
scroll_area.setWidget(text_edit)
text_edit.scrollArea = scroll_area
layout = QVBoxLayout()
layout.addWidget(scroll_area)
layout.setContentsMargins(0, 0, 0, 0)
qdialog = QDialog(self)
qdialog.setWindowTitle(log_title)
qdialog.setLayout(layout)
qdialog.show()
#def save_to_hdf_started(self):
# QApplication.processEvents()
#def save_to_hdf_finished(self):
# QApplication.processEvents()
@Slot()
def write_to_destinations(self):
#disable signal
#self.gui_frame.autopost_save.blockSignals(True)
QApplication.processEvents()
if self.autopost_epics:
self.save_to_epics()
QApplication.processEvents()
if self.autopost_hdf:
self.save_to_hdf()
QApplication.processEvents()
if self.autopost_elog:
self.send_to_elog()
QApplication.processEvents()
#enable signal
#self.gui_frame.autopost_save.blockSignals(False)
def set_new_hdf_filename(self, time_in_seconds=None,
reanalysis_time_in_seconds=None):
self.hdf_filename = (self.hdf_dest + self.add_date_to_path(
time_in_seconds, reanalysis_time_in_seconds) + ".h5")
def add_date_to_path(self, time_in_seconds=None,
reanalysis_time_in_seconds=None):
if time_in_seconds is None:
time_in_seconds = time.time()
when = datetime.fromtimestamp(time_in_seconds)
_date_label = when.strftime("%Y-%m-%d_%H:%M:%S")
if reanalysis_time_in_seconds is not None:
now = datetime.fromtimestamp(reanalysis_time_in_seconds)
_date_now = now.strftime("%Y-%m-%d_%H:%M:%S")
_date_label += "_re" + _date_now
_year = now.strftime("%Y")
_month = now.strftime("%m")
_day = now.strftime("%d")
else:
_year = when.strftime("%Y")
_month = when.strftime("%m")
_day = when.strftime("%d")
return str(_year + "/" + _month + "/" + _day + "/" + self.appname +
"_" + _date_label)
def verify_save_to_hdf(self):
""" To be called by user from save_to_hdf
A return of True tells the user to continue to next step
"""
if self.analysis_thread is not None:
if self.analysis_thread.isRunning():
_mess = ("Measurement in progress. " +
"Please try again in a few seconds.")
QMessageBox.information(self, "HDF", _mess,
QMessageBox.Ok)
QApplication.processEvents()
return False
if self.hdf_thread is not None:
if self.hdf_thread.isRunning():
_mess = ("HDF analysis in progress. " +
"Please try again in a few seconds.")
QMessageBox.information(self, "HDF", _mess,
QMessageBox.Ok)
QApplication.processEvents()
return False
if not self.daq_analysis_completed:
QMessageBox.information(self, "HDF", (
("No data to save to hdf5; no measurement undertaken!")),
QMessageBox.Ok)
QApplication.processEvents()
return False
if self.hdf_save_completed:
_mess = "Data previously saved to hdf:\n{0}".format(
self.hdf_filename)
QMessageBox.information(self, "HDF", _mess,
QMessageBox.Ok)
QApplication.processEvents()
return False
return True
def add_to_hdf(self): # dataH5=None, proc=True, raw=False):
""" Abstract method to be overwritten by user
"""
QM = QMessageBox()
QM.setText(
str(NotImplementedError("add_to_hdf method has not been " +
"implemented in this application. \n If " +
"not required, consider removing the hdf" +
"icon from the application/config file."))
)
QM.exec()
@Slot()
def save_to_hdf(self):
""" Abstract method to be overwritten by user
"""
QM = QMessageBox()
QM.setText(
str(NotImplementedError("save_to_hdf method has not been " +
"implemented in this application. \n" +
"If not required, consider removing the " +
"icon from the application/config file."))
)
QM.exec()
def add_pvs_to_hdf(self, dataH5, top_group="experiment", pv_list=None,
from_hdf=False):
isOK = True
if not pv_list:
return
grp_data = dataH5.create_group(top_group)
if from_hdf and top_group in self.all_data.keys():
for key1 in self.all_data[top_group].keys():
dt_name_top = key1 + "/"
for key2, value in self.all_data[top_group][key1].items():
dt_name = dt_name_top + key2
#print("dt_name", dt_name, value[()])
#TEST
#value = np.array([1,2,3], dtype=np.dtype('O'))
try:
grp_data.create_dataset(dt_name, data=value[()])
except TypeError as err:
isOK = False
_mess = ("Error in saving to HDF ['{0}']['{1}']. " +
"{2}").format(top_group, dt_name, str(err))
self.trigger_log_message.emit(
MsgSeverity.WARN.name, _pymodule, _line(), _mess,
{})
return isOK
if from_hdf:
return isOK
self.cafe.attachContext(pv_list[0])
for pv in pv_list:
self.cafe.setGetActionWhenMonitorPolicy(
pv, self.cyca.GET_FROM_CACHE)
pv_dict = self.cafe.getDictionary(pv_list)[0]
for key, value in pv_dict.items():
k = key.split(":")
if len(k) <= 1:
k = key.split("-", 1)
dt_name = k[0] + "/" + k[1]
try:
#value = np.array([1,2,3], dtype=np.dtype('O'))
grp_data.create_dataset(dt_name, data=value)
except TypeError as err:
isOK = False
_mess = ("Error in saving to HDF ['{0}']['{1}'] " +
"{2}").format(dt_name, top_group, str(err))
self.trigger_log_message.emit(
MsgSeverity.WARN.name, _pymodule, _line(), _mess, {})
return isOK
def add_general_to_hdf(self, dataH5):
isOK = True
user_dict = {}
user_dict['Comment'] = self.hdf_dialog.user_dict[
'Comment'] if self.hdf_dialog is not None else str(
"HDF file generated via Save All button")
user_dict['Author'] = self.author
user_dict['Application'] = self.pymodule
user_dict['Version'] = self.appversion
#user_dict['Comment'] = comment if comment is not None else str(
# "HDF file generated via Save All button")
user_dict['Filepath'] = self.source_file
#user_dict['Process'] = os.getpid()
#user_dict['UID'] = os.geteuid()
user_dict['User'] = getpass.getuser()
if self.all_data is not None:
time_in_seconds = self.all_data['Ambient data']['Time in seconds']
now = datetime.fromtimestamp(time_in_seconds)
else:
now = datetime.now()
_date = now.strftime("%Y-%m-%d_%H:%M:%S")
user_dict['Created'] = _date
grp_data = dataH5.create_group("general")
for key in user_dict:
lowkey = key.lower()
try:
##TEST
##user_dict[key] = np.array([1,2,3], dtype=np.dtype('O'))
grp_data.create_dataset(lowkey.replace(' ', '_'),
data=user_dict[key])
except TypeError as err:
isOK = False
_mess = ("Error in saving to HDF ['general']['{0}']. " +
"{1}").format(key, str(err))
self.trigger_log_message.emit(
MsgSeverity.WARN.name, _pymodule, _line(), _mess, {})
return isOK
@Slot()
def save_to_hdf_dialog(self):
""" This uses the widget interface to allow the user to enter
additional meta-data
"""
if not self.verify_save_to_hdf():
return False
input_options = OrderedDict()
#print(self.all_data['Ambient data'])
#print(self.all_data['Processed data'])
#QCombobox if list
#input_options['QComboBox'] = ['one', 'two', 'three']
#input_options['Comment'] = 'Please enter a comment'
if self.all_data is not None:
ts_in_seconds = self.all_data['Ambient data']['Time in seconds']
now_in_seconds = self.all_data['Processed data'][
'Reanalysis time in seconds'] if \
self.all_data['Processed data']['Reanalysis time'] else None
self.set_new_hdf_filename(ts_in_seconds, now_in_seconds)
input_options['Destination'] = self.hdf_filename
input_options['Time in seconds'] = self.all_data['Ambient data'][
'Time in seconds']
self.hdf_dialog = QSaveHDF(self, input_options=input_options,
from_dialog=True)
#user_dict = self.hdf_dialog.get_data()
#self.hdf_filename = user_dict['Destination']
#print("filename", self.hdf_filename)
def verify_send_to_elog(self):
if self.analysis_thread is not None:
if self.analysis_thread.isRunning():
_mess = ("Measurement in progress. " +
"Please try again in a few seconds.")
QMessageBox.information(self, "ELOG", _mess,
QMessageBox.Ok)
return False
if not self.daq_analysis_completed:
_mess = ("Opening ELOG, but please note that no new measurement " +
"has been undertaken")
QMessageBox.information(self, "ELOG", _mess,
QMessageBox.Ok)
return True
if self.save_hdf_thread is not None:
if self.save_hdf_thread.isRunning():
return True
if not self.hdf_save_completed:
_mess = ("Opening ELOG, but please note that data have not " +
"been saved to HDF. " +
"<br>Click on the HDF icon to do this if desired")
QMessageBox.information(self, "ELOG", _mess,
QMessageBox.Ok)
return True
return True
@Slot()
def send_to_elog(self):
""" Response to elog_action; normally overwritten
"""
if self.analysis_thread is not None:
if self.analysis_thread.isRunning():
_mess = ("Measurement in progress. " +
"Please try again in a few seconds.")
QMessageBox.information(self, "ELOG", _mess,
QMessageBox.Ok)
return
#_elog_sf = ElogSwissFEL()
_logbook = None
_category_idx = 0 #_elog_sf.category.MEASUREMENT=1
_domain_idx = 0 #_elog_sf.system.NONE
_section_idx = 0 #_elog_sf.system.BEAMDYNAMICS=1
_attach_files = []
_message = ""
QSendToELOG(self, logbook=_logbook, categoryIdx=_category_idx,
domainIdx=_domain_idx, sectionIdx=_section_idx,
title=self.title, message=_message,
attachFile=_attach_files)
QApplication.processEvents()
def initiate_cycling(self, pv_list=[]):
if not pv_list:
return False
pv_cycle_quads = [s for s in pv_list if s.endswith("CYCLE")]
print("Initiate Cycle_quads", pv_cycle_quads)
if pv_cycle_quads != pv_list:
for quad in pv_list:
if "CYCLE" not in quad:
_mess = "Wrong PV name for cycling! pv = {0}".format(quad)
self.show_log_message(MsgSeverity.ERROR, _pymodule, _line(),
_mess)
self.statusbar.showMessage(_mess)
if not pv_cycle_quads:
return False
if not self.input_parameters['simulation']:
pv_cycle_value = [2] * len(pv_cycle_quads)
status, status_list = self.cafe.setScalarList(pv_cycle_quads,
pv_cycle_value)
if status != self.cyca.ICAFE_NORMAL:
self.send_to_log_window(pv_list=pv_cycle_quads, status=status,
status_list=status_list,
operation='set', pymodule=_pymodule,
line=_line())
return True
def is_cycling(self, pv_cycle_quads=[]):
if pv_cycle_quads:
value_list, _status, _status_list = self.cafe.getScalarList(
pv_cycle_quads, dt='native')
if _status != self.cyca.ICAFE_NORMAL:
self.send_to_log_window(pv_list=pv_cycle_quads, status=_status,
status_list=_status_list,
operation='get', pymodule=_pymodule,
line=_line())
if "Cycling" in value_list:
# _mess = ("Presently cycling quads. " +
# "\nPlease try again in a few seconds")
# QMessageBox.information(self, "Verification", _mess,
# QMessageBox.Ok)
return True
return False
def wait_for_cycling(self, deflector, profile_monitor, pv_cycle_quads=[],
timeout=30):
if not pv_cycle_quads:
return
start = time.time()
while self.is_cycling(pv_cycle_quads):
time.sleep(0.005)
QApplication.processEvents()
if time.time()-start > timeout:
_mess = ("Timeout ({0}s) reached for Cycling. " +
"\nPlease check set points for " +
"\nDeflector: {1} " +
"\nProfile Monitor: {2}").format(
timeout, deflector, profile_monitor)
qm = QMessageBox()
qm.warning(self, "Set Optics", _mess, QMessageBox.Ok)
break
@Slot()
def set_optics(self):
""" Abstract method to be overwritten by user
"""
QM = QMessageBox()
QM.setText(
str(NotImplementedError("set_optics method has not been " +
"implemented in this application. \n"))
)
QM.exec()
@Slot()
def restore_optics(self):
""" Abstract method to be overwritten by user
"""
QM = QMessageBox()
QM.setText(
str(NotImplementedError("restore_optics method has not been " +
"implemented in this application. \n"))
)
QM.exec()
def verify_save_to_epics(self):
""" To be called by user from save_to_epics
"""
if self.all_data:
#Data from hdf analysis - do not save to epics
if self.all_data['Processed data']['Reanalysis time']:
print("HDF RUN - data not written to epics")
return False
if self.all_data['Input data']['simulation']:
return False
else:
if self.input_parameters['simulation']:
return False
if self.analysis_thread is not None:
if self.analysis_thread.isRunning():
_mess = ("Measurement in progress. " +
"Please try again in a few seconds.")
QMessageBox.information(self, "EPICS", _mess,
QMessageBox.Ok)
QApplication.processEvents()
return False
if not self.all_data:
_mess = "No data to save to epics: data dictionary is empty!"
QMessageBox.information(self, "EPICS", _mess,
QMessageBox.Ok)
QApplication.processEvents()
return False
if not self.daq_analysis_completed:
_mess = "No data to save to epics; no measurement undertaken!!"
QMessageBox.information(self, "EPICS", _mess,
QMessageBox.Ok)
QApplication.processEvents()
return False
return True
def save_to_epics(self):
""" Abstract method to be overwritten by user
"""
QM = QMessageBox()
QM.setText(
str(NotImplementedError("save_to_epics method has not been " +
"implemented in this application. \n" +
"If not required, consider removing the " +
"icon from the application/config file."))
)
QM.exec()
def send_to_log_window(self, pv_list: list = [], status: int = 1,
status_list: list = [], operation: str = 'set/get',
pymodule=_pymodule, line: int = _line()):
if status != self.cyca.ICAFE_NORMAL:
ibad = 0
for _status, _pv in zip(status_list, pv_list):
if _status != self.cyca.ICAFE_NORMAL:
ibad += 1
_mess = "Error in '{1}' for pv {0}.".format(_pv, operation)
_options = {}
_options['statusCode'] = "{0} {1}".format(
str(_status), self.cafe.getStatusCodeAsString(_status))
_options['statusInfo'] = self.cafe.getStatusInfo(_status)
self.show_log_message(MsgSeverity.ERROR, pymodule, line,
_mess, _options)
_mess = ("{0} of the {1} PV operations resulted in an error. " +
"See Log window").format(ibad, len(status_list))
self.statusbar.showMessage(_mess)
def send_to_epics(self, pv_dict: dict = None, pv_names: list = None,
pv_values: list = None) -> (int, list):
if pv_dict is not None:
pv_names = list(pv_dict.keys())
pv_values = list(pv_dict.values())
else:
if len(pv_names) != len(pv_values):
_mess = ("len(pv_values)={0} does not match " +
"len(pv_names)={1}").format(len(pv_values),
len(pv_names))
self.show_log_message(MsgSeverity.ERROR, _pymodule, _line(),
_mess)
self.statusbar.showMessage(_mess)
return
self.cafe.openPrepare()
self.cafe.open(pv_names)
self.cafe.openNowAndWait(0.4)
status, status_list = self.cafe.setCompoundList(pv_names, pv_values)
if status != self.cyca.ICAFE_NORMAL:
ibad = 0
for _status, _pv in zip(status_list, pv_names):
if _status != self.cyca.ICAFE_NORMAL:
ibad += 1
_mess = "Error in 'set' for pv {0}.".format(_pv)
_options = {}
_options['statusCode'] = "{0} {1}".format(
str(_status), self.cafe.getStatusCodeAsString(_status))
_options['statusInfo'] = self.cafe.getStatusInfo(_status)
self.show_log_message(MsgSeverity.ERROR, _pymodule,
_line(), _mess, _options)
_mess = ("{0} of the {1} PVs could not be set. " +
"See Log window").format(ibad, len(status_list))
self.statusbar.showMessage(_mess)
QMessageBox.warning(self, _pymodule, _mess, QMessageBox.Ok)
else:
_mess = "Data saved to epics successully"
self.show_log_message(MsgSeverity.INFO, _pymodule, _line(), _mess)
self.statusbar.showMessage(_mess)
return status, status_list
@Slot()
def send_to_printer(self):
""" Response to printer_action
"""
self.printer = QPrinter(QPrinter.HighResolution)
self.printer.setPaperSize(QPrinter.A4)
self.printer.setOrientation(QPrinter.Landscape)
form = QPrintDialog(self.printer, self)
if form.exec_():
painter = QPainter(self.printer)
w = self.centralWidget()
xscale = self.printer.pageRect().width() / (w.width())
yscale = self.printer.pageRect().height() / (w.height())
scale = min(xscale, yscale)
painter.translate(self.printer.paperRect().x() +
self.printer.pageRect().width() / 2,
self.printer.paperRect().y() +
self.printer.pageRect().height() / 2)
painter.scale(scale, scale)
painter.translate(-w.width()/2, -w.height()/2)
w.render(painter)
@Slot()
def show_about(self):
""" To overide by application
"""
QApplication.processEvents()
QMessageBox.about(
self, "About",
"""<b>{0}</b> v {1}
<p>Copyright &copy; Paul Scherrer Institut (PSI).
All rights reserved.</p>
<p>Author: J. Chrin </p>
<p>1st Responsible: J. Chrin, Tel. 2930,
+41 76 206 0988, jan.chrin@psi.ch </p>
<p>2nd Responsible: 2nd name if appropriate </p>
<p>A main-window style application with menus,
a status bar, a toolbar and log window </p>
<p>Python {2} - Qt {3} - PyQt {4} <br>
cafe {5} - epics {6} on {7}""".format(
_pymodule, _appversion, platform.python_version(),
QT_VERSION_STR, PYQT_VERSION_STR,
self.cafe.CAFE_version(), self.cafe.EPICS_version(),
platform.system()))
QApplication.processEvents()
@Slot()
def show_help(self):
""" Invoke help pages from qrc_resources
"""
index_html = self.appname + "/index.html"
help_base = ":/help/" + self.appname
help_page = HelpBrowser(help_base, index_html, self)
help_page.show()
@Slot()
def take_screenshot(self):
QScreenshot(self, window_title=self.appname,
screen_items=self.screenshot_items,
screen_titles=self.screenshot_titles)
def add_screenshot(self, title=None, item=None):
if title and item:
if title not in self.screenshot_titles and \
item not in self.screenshot_items:
self.screenshot_titles.append(title)
self.screenshot_items.append(item)
def clear_screenshot(self, title=None, item=None):
if title and item:
if title in self.screenshot_titles and \
item in self.screenshot_items:
self.screenshot_titles.remove(title)
self.screenshot_items.remove(item)
else:
self.screenshot_titles.clear()
self.screenshot_items.clear()
############################### Init
def init_statusbar_wgt(self):
""" Configure statusbar
"""
self.statusbar_label.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
self.statusbar.setSizeGripEnabled(False)
self.statusbar.addPermanentWidget(self.statusbar_label)
self.statusbar.clearMessage()
self.statusbar.showMessage("Initialization complete")
def init_progressbar_wgt(self):
self.progressbar_simulation = "simulation"
self.progressbar_standard = "blue"
self.progressbar_abort = "abort"
self.progressbar_color = self.progressbar_standard
self.progressbar.setObjectName(self.progressbar_color)
self.progressbar.setRange(PROGRESS_BAR_THREAD_START,
PROGRESS_BAR_THREAD_END)
self.progressbar.setTextVisible(True)
self.progressbar.setAlignment(Qt.AlignCenter)
self.progressbar.setVisible(False)
def init_hdf_analysis_wgt(self):
self.h5_groupbox = HDF5GroupBox(self, title="HDF5 Analysis",
layout="Hor")
self.hdf_dock_widget.setObjectName("hdfDockWidget")
self.hdf_dock_widget.setVisible(False)
#hdfDockWidget.setFloating(True)
self.hdf_dock_widget.setContentsMargins(9, 9, 9, 9)
self.h5_groupbox.analyze_h5_widget.clicked.connect(
self.start_hdf_thread)
self.hdf_dock_widget.setWidget(self.h5_groupbox)
self.addDockWidget(Qt.TopDockWidgetArea, self.hdf_dock_widget)
def restore_application_settings(self):
""" Restore platform-independent application settings
"""
qsettings = QSettings()
if qsettings.value("RecentFiles"):
self.application_recent_files = str(
qsettings.value("RecentFiles")).split(',')
if qsettings.value("BaseWindow/Geometry"):
self.restoreGeometry(qsettings.value("BaseWindow/Geometry"))
if qsettings.value("BaseWindow/State"):
self.restoreState(qsettings.value("BaseWindow/State"))
self.application_geometry = self.geometry()
def save_application_settings(self):
""" Save platform-independent application settings
"""
settings = QSettings()
if self.filename:
settings.setValue("LastFile", str(self.filename))
if self.application_recent_files:
recent_files = ', '.join(self.application_recent_files)
settings.setValue("RecentFiles", (recent_files))
settings.setValue("BaseWindow/Geometry", (self.saveGeometry()))
settings.setValue("BaseWindow/State", (self.saveState()))
#To be overloaded by user
def load_hdf_file(self):
""" load_hdf5 file into analysis dictionary
"""
return {}
@Slot()
def start_hdf_thread(self):
""" Start hdf re-analysis thread
"""
if not self.analysis_procedure:
mess = "HDF analysis thread not configured for this application"
self.show_log_message(MsgSeverity.ERROR, _pymodule, _line(), mess)
self.statusbar.showMessage(mess)
return
if self.analysis_thread:
if self.analysis_thread.isRunning():
_mess = ("Measurement already in progress.")
QMessageBox.information(self, "Measurement in progress..",
_mess, QMessageBox.Ok)
return
if self.hdf_thread:
if self.hdf_thread.isRunning():
_mess = ("HDF analysis already in progress.")
QMessageBox.information(self, "HDF analysis in progress..",
_mess, QMessageBox.Ok)
return
self.hdf_thread_started()
self.statusbar.showMessage("Loading {0}".format(
self.hdf_filename_loaded))
self.trigger_progressbar_str.emit(
5, "Loading HDF file for analysis")
#Fill all data from HDF file
###hdf_all_data = self.load_hdf_file()
###if not hdf_all_data:
### self.hdf_thread_finished()
### return
self.hdf_thread = self.HDFThread(
self, self.analysis_procedure, all_data=None) #=hdf_all_data
self.hdf_thread.trigger_thread_event.connect(
self.receive_analysis_results)
#procedure moved above
#self.hdf_thread.started.connect(self.hdf_thread_started)
self.hdf_thread.finished.connect(self.hdf_thread_finished)
self.hdf_thread.start()
QApplication.processEvents()
#To be overloaded by user
def verify_analysis_preconditions(self):
return True
@Slot()
def start_analysis_thread(self):
if not self.analysis_procedure:
mess = "Analysis thread not configured for this application"
self.show_log_message(MsgSeverity.ERROR, _pymodule, _line(), mess)
self.statusbar.showMessage(mess)
return
if self.analysis_thread:
if self.analysis_thread.isRunning():
_mess = ("Measurement already in progress.")
QMessageBox.information(self, "Measurement in progress..",
_mess, QMessageBox.Ok)
return
if self.hdf_thread:
if self.hdf_thread.isRunning():
_mess = ("HDF analysis already in progress.")
QMessageBox.information(self, "HDF analysis in progress..",
_mess, QMessageBox.Ok)
return
if not self.verify_analysis_preconditions():
return
self.analysis_thread = self.AnalysisThread(
self, self.analysis_procedure, self.input_parameters)
#pool = QThreadPool.globalInstance()
self.analysis_thread.trigger_thread_event.connect(
self.receive_analysis_results)
self.analysis_thread.started.connect(self.analysis_thread_started)
self.analysis_thread.finished.connect(self.analysis_thread_finished)
#pool.start(self.analysis_thread)
self.analysis_thread.start()
QApplication.processEvents()
@Slot()
def analysis_thread_started(self):
""" Change state of widgets when measuring
"""
self.gui_frame.in_measurement_procedure()
QApplication.processEvents()
#print("Thread Started")
@Slot()
def analysis_thread_finished(self):
""" Reset widgets to intial pre-measurement state
"""
self.gui_frame.reset_procedure()
QApplication.processEvents()
#print("Thread Finished")
@Slot()
def hdf_thread_started(self):
""" Change state of widgets when measuring
"""
self.gui_frame.in_hdf_measurement_procedure()
QApplication.processEvents()
#print("Thread Started")
@Slot()
def hdf_thread_finished(self):
""" Reset widgets to intial pre-measurement state
"""
self.gui_frame.hdf_reset_procedure()
QApplication.processEvents()
#print("Thread Finished")
@Slot(dict)
def receive_analysis_results(self, all_dict):
#print("receive analysis results", all_dict)
self.all_data = all_dict
self.gui_frame.canvas_update(all_dict['Figure data'])
self.gui_frame.central_tab_widget.setCurrentIndex(1)
self.gui_frame.results_tab_wgt.setCurrentIndex(0)
@Slot()
def receive_abort_analysis(self):
"""Instantiate action on abort
"""
print("Aborting")
if self.analysis_procedure.abort:
return
self.gui_frame.in_abort_procedure()
# Trigger abort signal to the analysis thread
self.analysis_procedure.trigger_abort.emit()
#self.trigger_progressbar.emit(PROGRESS_BAR_THREAD_ABORTING)
QApplication.processEvents()
@Slot(str, str, int, str, dict)
def receive_log_message(self, severity, module, line, message, options={}):
'''Receive message from thread for routing to log window'''
self.show_log_message(severity, module, line, message, options)
self.statusbar.showMessage(message)
@Slot(int)
@Slot(int, str)
def receive_progressbar(self, value=0,
message="Measurement in progress..."):
'''Receives update of message'''
self.progressbar.setVisible(True)
if value == PROGRESS_BAR_THREAD_INIT:
self.progressbar.setVisible(False)
self.progressbar.setFormat("")
self.progressbar.reset()
self.progressbar.setObjectName(self.progressbar_color)
self.statusbar.clearMessage()
elif value == PROGRESS_BAR_THREAD_START:
self.statusbar.clearMessage()
self.progressbar.setFormat("Measurement started")
self.progressbar.setValue(value)
self.progressbar.setObjectName(self.progressbar_color)
self.daq_analysis_completed = False
elif value == PROGRESS_BAR_THREAD_ABORTING:
self.progressbar.setFormat(
"Aborting procedure at the next available break point")
self.progressbar.setObjectName(self.progressbar_abort)
prog_val = re.findall(r'\d+', message)
if prog_val:
self.progressbar.setValue(int(prog_val[0]))
mess = "Aborting measurement procedure"
self.show_log_message(
MsgSeverity.WARN.name, _pymodule, _line(), mess)
#self.statusbar.showMessage(mess)
elif value == PROGRESS_BAR_THREAD_ABORTED:
self.progressbar.setFormat("Procedure aborted")
self.progressbar.setObjectName(self.progressbar_abort)
mess = "Measurement procedure aborted"
self.show_log_message(
MsgSeverity.WARN.name, _pymodule, _line(), mess)
self.statusbar.showMessage(mess)
self.daq_analysis_completed = False
QTimer.singleShot(2000, lambda: self.trigger_progressbar.emit(
PROGRESS_BAR_THREAD_INIT))
elif value == PROGRESS_BAR_THREAD_ERROR:
mess = "Error in Thread. No data returned! See Log window"
self.progressbar.setFormat(mess)
self.progressbar.setObjectName(self.progressbar_abort)
self.statusbar.showMessage(mess)
QTimer.singleShot(10000, lambda: self.trigger_progressbar.emit(
PROGRESS_BAR_THREAD_INIT))
elif value == PROGRESS_BAR_THREAD_END:
self.progressbar.setFormat("Measurement completed")
self.progressbar.setValue(value)
self.progressbar.setObjectName(self.progressbar_color)
self.daq_analysis_completed = True
self.hdf_save_completed = False
QTimer.singleShot(2000, lambda: self.progressbar.setVisible(False))
else:
self.progressbar.setFormat(message)
self.progressbar.setValue(value)
self.progressbar.setObjectName(self.progressbar_color)
self.progressbar.style().polish(self.progressbar)
QApplication.processEvents()
time.sleep(0.0001)
def initialize_application(self, appname=_appname, delay=None,
facility=Facility.SwissFEL):
"""
Initialize application configuration for use by QSettings.
Loads application stylesheet from qrc_resources or input file
These are saved into $HOME/.config
e.g., $HOME/.config/<facility>/appname.conf
Provide QSplashScreen if delay is set.
"""
self.splash_appname = appname
if facility == Facility.SwissFEL:
from pyqtacc.qrc_resources.facility.sf.pyrcc5 import qrc_resources
print("FACILITY SwissFEL")
elif facility == Facility.SLS:
from pyqtacc.qrc_resources.facility.sls.pyrcc5 import qrc_resources
print("FACILITY SLS")
else:
print("Unknown Facility; assuming SLS")
from pyqtacc.qrc_resources.facility.sls.pyrcc5 import qrc_resources
self.setStyle("motif")
self.setOrganizationName(facility.name)
self.setOrganizationDomain("psi.ch")
self.setApplicationName(appname)
self.setWindowIcon(QIcon(":/icon.png"))
parser = argparse.ArgumentParser(description="stylesheet")
parser.add_argument("-q", "--qss", action="store", dest="qss")
parser.add_argument("-s", "--style")
parser.add_argument("-f", "--facility")
parser.add_argument("-u", "--user")
#result, unknown = parser.parse_known_args(["--qss", ":/sfop.qss"])
#result, unknown = parser.parse_known_args(["--qss", ":/sfop.qss",
#"-c", "--facility", "--user"])
#print(result)
#print(unknown)
result = parser.parse_args()
if result.qss:
print("qss file {0} supplied by user".format(result.qss))
qss_file = result.qss if result.qss else ":/acc.qss"
qss = QFile(qss_file)
if qss.open(QIODevice.ReadOnly):
fByteArray = qss.readAll()
self.setStyleSheet(str(fByteArray.data(), encoding='utf-8'))
qss.close()
else:
print("COULD NOT OPEN {0} stylesheet".format(qss_file))
exit(1)
if delay is not None:
delay = max(delay, 5)
self.splash_screen = QSplashScreen(QPixmap(":/loading.gif"))
self.splash_screen.setAttribute(Qt.WA_TranslucentBackground, False)
self.splash_screen.setWindowFlags(Qt.WindowStaysOnTopHint)
self.splash_screen.showMessage(
"""
<br><p style='color:black; font-weight:bold;
font-size:28px; margin-right:10px;'>
LOADING APPLICATION:
<font color=royalblue > {0} </font> </p>
<p style='color:black; font-weight:bold;
font-size:20px; margin-right:22px;'>
This message will self-destruct in {1} seconds<br></p>
""".format(appname, round(delay)), Qt.AlignCenter | Qt.AlignTop)
width = 860 + (len(appname)-10)*15
height = 220
self.splash_screen.resize(width, height)
self.splash_progressbar = QProgressBar(self.splash_screen)
self.splash_timer = QTimer()
self.splash_screen.show()
# Custom progress bar stylesheet
progressbar_stylesheet = """
QProgressBar:horizontal {
border: 1px solid royalblue;
background: white;
padding: 1px;
}
QProgressBar::chunk:horizontal {
background-color: qlineargradient(spread: pad, x1: 1, y1: 0.5,
x2: 0, y2: 0.5, stop: 0 royalblue, stop: 1 white);
}
"""
self.splash_progressbar.setStyleSheet(progressbar_stylesheet)
self.splash_progressbar.setGeometry(
10, self.splash_screen.height() - 40,
self.splash_screen.width() - 20, 30)
start1 = time.time()
def show_time():
now = time.time()
val = (now-start1)*100/delay
val = 5 * round(val/5)
int_seconds_remaining = int(delay - (now-start1))
seconds_remaining = '{:2d}'.format(int_seconds_remaining)
self.splash_progressbar.setValue(val)
self.processEvents()
sec_str = "s" if abs(int_seconds_remaining) != 1 else ""
mess = """
<br><p style='color:black; font-weight:bold;
font-size:28px; margin-right:10px;'>
LOADING APPLICATION:
<font color=royalblue > {0} </font> </p>
<p style='color:black; font-weight:bold;
font-size:20px; margin-left:120px; margin-right:2px;'>
{1:>2} second{2} remaining<br></p>
""".format(self.splash_appname, seconds_remaining, sec_str)
self.splash_screen.showMessage(mess, Qt.AlignCenter|Qt.AlignTop)
self.processEvents()
#print(val, seconds_remaining)
self.splash_timer.timeout.connect(show_time)
self.splash_timer.start(200) #ms with 10 samples per sec.
return self.splash_screen
def initialize_finished(self, myapp):
self.splash_timer.stop()
self.splash_progressbar.setValue(100)
time.sleep(1.5)
#app.processEvents()
self.splash_screen.showMessage(
"""
<br><p style='color:black; font-weight:bold;
font-size:28px; margin-right:10px;'>
LOADING APPLICATION:
<font color=royalblue > {0} </font> </p>
<p style='color:black; font-weight:bold;
font-size:20px; margin-left:120px; margin-right:2px;'>
Ready.... &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
<br></p>
""".format(self.splash_appname), Qt.AlignCenter | Qt.AlignTop)
self.splash_screen.finish(myapp)