Files
tina/tina.py
2025-01-06 10:27:56 +01:00

420 lines
15 KiB
Python

'''Tina.py module for measuring the number of turns
'''
import os
import platform
import sys
import time
from qtpy.QtCore import __version__ as QT_VERSION_STR
from qtpy.QtCore import PYQT_VERSION_STR, Slot
from qtpy.QtWidgets import QApplication, QMessageBox
from apps4ops.bdbase.base import BaseWindow
from apps4ops.bdbase import h5_storage, utils
from apps4ops.bdbase.enumkind import Facility, MsgSeverity, UserMode
from apps4ops.bdbase.helpbrowser import HelpBrowser
from apps4ops.hipa.sendeloghipa import QSendToELOG
from apps4ops.hipa.enumkind import ElogHIPA
from src.gui import AppGui
from pyrcc5 import tina_resources
#try:
# import pyscan
# import pyscan.interface.pyScan as PyScan
#except:
# sys.path.append('/hipa/bd/applications/deps/apps4ops/v1.12.0/common/packages/')
#or insert to jump the queue
#sys.path.insert(0,'/hipa/bd/applications/deps/apps4ops/v1.12.0/common/packages/')
# import pyscan
# import pyscan.interface.pyScan as PyScan
# print("from common.packages import pyscan")
_pymodule = os.path.basename(__file__)
_appname, _appext = _pymodule.split('.')
_abspath = os.path.dirname(os.path.abspath(__file__))
_appversion = '0.0.1'
_title = 'No of Turns Measurement'
_appname = 'Tina'
class StartMain(BaseWindow):
''' Application to measure the no of turns in Injector 2 and the
Ring Cyclotron
'''
ring_cyclotron = 'Cyclotron'
injector_2 = 'Injector'
def __init__(self, parent=None):
super().__init__(
parent=parent, pymodule=_pymodule, appversion=_appversion,
title=_title, user_mode=UserMode.OPERATION, facility=Facility.HIPA,
has_optics=False, has_procedure=True)
self.appname = _appname
self.source_file = _abspath # required for HDF
self.elog_enum = ElogHIPA()
self.message_elog = None
self.default_idx = self.settings.data['Parameters']['accelerator'][
'data']['value']
self.accelerator = self.ring_cyclotron if self.default_idx else \
self.injector_2
# self.from_hdf = False in base class
self.message = ''
self.correlation_peak_diff_min_value = 0.010
self.gui = AppGui(self)
def prepare_results_message(self):
"""Prepare results message
"""
try:
self.no_turns = self.all_data['Processed data']['nturns']
lag_full = self.all_data['Processed data']['lag']
delay = self.all_data['Processed data']['delay']
self.peak_diff = self.all_data['Processed data']['peak_diff']
except KeyError:
self.message = ''
self.message_elog = ''
return
try:
self.accelerator = self.all_data['Input data']['accelerator']
except KeyError as ex:
self.logger.debug(f'KeyError {ex}')
try:
self.accelerator = self.all_data['Input data']['qtabdata']
except KeyError as ex:
self.logger.debug(f'KeyError {ex}')
mess = 'Reanalysis from HDF5. ' if self.from_hdf else ''
self.message_elog = (
mess +
'''No. turns measured in the {0} = {1:0.0f} ({2:.2f}) <br>
lag = {3}, delay = {4:.3f} \u00B5s<br>'''.format(
self.accelerator, (self.no_turns), self.no_turns,
lag_full, delay*10**6))
self.message = (
mess +
'''
No. turns measured in the {0} = {1:0.0f} ({2:.2f})
lag = {3}, delay = {4:.3f} \u00B5s'''.format(
self.accelerator, self.no_turns, self.no_turns,
lag_full, delay*10**6))
def prepare_elog_message(self):
'''Define elog parameters and define message
'''
self.projekt_idx = self.elog_enum.projekt.NONE
self.system_idx = self.elog_enum.system.BEAMDYNAMICS
self.eintrag_idx = self.elog_enum.eintrag.INFO
self.ort_idx = self.elog_enum.ort.RING_CYCLOTRON if \
'Cyclotron' in self.accelerator else self.elog_enum.ort.INJECTOR2
self.status_idx = self.elog_enum.status.NONE
self.effekt_idx = self.elog_enum.effekt.NO
self.attach_files = []
simulation = self.input_parameters['simulation']
if self.all_data:
if self.all_data['Input data'] is not None:
try:
simulation = self.all_data['Input data']['simulation']
except KeyError:
simulation = self.input_parameters['simulation']
pass
self.logbook = 'Sandkasten' if simulation else 'HIPA'
self.title = _title
def verify_analysis_preconditions(self):
if self.injector_2 in self.input_parameters['accelerator']:
mess = ('Measurement procedure for Injector 2 \n' +
'has not yet been implementented.')
QMessageBox.information(self, 'Injector 2', mess, QMessageBox.Ok)
QApplication.processEvents()
return False
if self.input_parameters['simulation']:
return True
return True
injector_current = self.cafe.getCache("MWC2:IST:2")
if not injector_current:
stat = self.cafe.getStatus("MWC2:IST:2")
self.check_status(_pymodule, 'getCache', injector_current, stat,
utils.line_no())
mess = ('Unable to read Injector 2 current\n' +
'Please try again.')
QMessageBox.information(self, 'Cyclotron', mess, QMessageBox.Ok)
QApplication.processEvents()
return False
elif injector_current < 0.001:
mess = ('Injector 2 current is below threshold.\n' +
'Measurememt cannot be untertaken at the present time.')
QMessageBox.information(self, 'Cyclotron', mess, QMessageBox.Ok)
QApplication.processEvents()
return False
return True
@Slot()
def analysis_thread_finished(self):
BaseWindow.analysis_thread_finished(self)
if self.all_data is not None:
try:
if self.all_data['Figure data'] is not None:
self.gui_frame.central_tab_widget.setCurrentIndex(1)
except KeyError:
print('No analysis performed')
return
else:
print('Thread finished with no data')
ncanvas = len(self.settings.data['GUI']['subResultsTabTitle'])
dict_fig = {}
dict_fig['Figure data'] = {}
for i in range(0, ncanvas):
canvas = f'Canvas {i+1}'
dict_fig['Figure data'][canvas] = None
# Delete old figures
self.gui.gui_frame.canvas_update(dict_fig['Figure data'])
return
self.prepare_results_message()
self.show_log_message(MsgSeverity.INFO, _pymodule, utils.line_no(),
self.message)
@Slot()
def hdf_thread_finished(self):
BaseWindow.hdf_thread_finished(self)
self.prepare_results_message()
self.show_log_message(MsgSeverity.INFO, _pymodule, utils.line_no(),
self.message)
@Slot()
def save_to_hdf_dialog(self):
if self.from_hdf:
mess = ('This is a repeat analysis from HDF. \n' +
'Saving duplicate data to HDF is declined.')
QMessageBox.information(self, 'HDF', mess, QMessageBox.Ok)
QApplication.processEvents()
return False
BaseWindow.save_to_hdf_dialog(self)
@Slot()
def save_to_hdf(self, from_dialog=False):
if not self.verify_save_to_hdf():
return False
if self.from_hdf:
mess = ('This is a repeat analysis from HDF. \n' +
'Saving duplicate data to HDF is declined.')
QMessageBox.information(self, 'HDF', mess, QMessageBox.Ok)
QApplication.processEvents()
return False
if self.all_data is not None:
self.save_hdf_thread = self.HDFSave(self, from_dialog)
# self.save_hdf_thread.started.connect(self.save_to_hdf_started)
# self.save_hdf_thread.finished.connect(self.save_to_hdf_finished)
self.save_hdf_thread.start()
self.hdf_save_completed = True
time.sleep(0.05) # Wait a tick
return True
def add_to_hdf(self, datah5, proc=True, raw=False):
'''User supplied hdf data
'''
if self.all_data is not None:
all_data = {}
all_data['Raw data'] = {}
all_data['Raw data']['Input_data'] = self.all_data[
'Input data']
all_data['Raw data']['Ambient_data'] = self.all_data[
'Ambient data']
all_data['Raw data']['Processed_data'] = self.all_data[
'Processed data']
all_data['Raw data']['Raw_data'] = self.all_data[
'Raw data']
h5_storage.saveH5Recursive(
self.hdf_filename, all_data['Raw data'], datah5)
@Slot()
def send_to_elog(self):
'''Override abstract method
'''
@Slot()
def save_fig_thread_finished():
'''Can take a few seconds to send to elog,
hence choose do this in a thread.
'''
time.sleep(0.2)
if self.all_data:
QSendToELOG(self, logbook=self.logbook,
projektIdx=self.projekt_idx,
eintragIdx=self.eintrag_idx,
systemIdx=self.system_idx,
statusIdx=self.status_idx,
ortIdx=self.ort_idx,
effektIdx=self.effekt_idx,
title=self.title,
message=self.message_elog,
attachFile=self.attach_files)
time.sleep(0.5)
self.prepare_elog_message()
if not self.all_data:
QSendToELOG(self, logbook=self.logbook,
projektIdx=self.projekt_idx,
eintragIdx=self.eintrag_idx,
systemIdx=self.system_idx,
statusIdx=self.status_idx,
ortIdx=self.ort_idx,
effektIdx=self.effekt_idx,
title=self.title,
message=self.message_elog,
attachFile=self.attach_files)
return
folder_name = self.elog_dest
if not os.path.exists(folder_name):
os.makedirs(folder_name)
time_in_seconds = self.all_data['Ambient data']['Time in seconds']
try:
reanalysis_time = self.all_data['Processed data'][
'Reanalysis time in seconds']
except KeyError:
reanalysis_time = None
self.folder_name = folder_name
save_fig_thread = self.SaveFigureThread(
self, self.folder_name, time_in_seconds, reanalysis_time)
save_fig_thread.finished.connect(save_fig_thread_finished)
save_fig_thread.start()
time.sleep(0.05)
def save_to_epics(self):
''' Write the number of turns calculated to an EPICS PV
'''
if not BaseWindow.verify_save_to_epics(self):
return False
if self.from_hdf:
mess = ('This is a repeat analysis from HDF. \n' +
'Analysis results are not saved to EPICS')
QMessageBox.information(self, 'EPICS', mess, QMessageBox.Ok)
QApplication.processEvents()
return False
if self.peak_diff < self.correlation_peak_diff_min_value:
mess = (f'Measurement is suspect as difference in top two peak \n' +
'values of ' +
f'{self.peak_diff:0.3f} is less than accepted minimum ' +
f'of {self.correlation_peak_diff_min_value:0.3f}. \n' +
f'Analysis result is not saved to EPICS!')
QMessageBox.information(self, 'EPICS', mess, QMessageBox.Ok)
QApplication.processEvents()
return False
simulation = self.input_parameters['simulation']
if not simulation:
dict_bunch = {}
nturns = round(self.no_turns)
pv = self.settings.data['PV']['Cyclotron']['nturns']
dict_bunch[pv] = nturns
status,_ = self.send_to_epics(dict_bunch)
if status == self.cyca.ICAFE_NORMAL:
mess = f'Saved data to EPICS; No turns = {nturns}'
sev = MsgSeverity.INFO
else:
mess = f'Value (nturns={nturns}) not saved to epics'
sev = MsgSeverity.ERROR
self.show_log_message(sev.name, _pymodule, utils.line_no(), mess)
return True
@Slot()
def closeEvent(self, event):
'''Close application only if conditions allow
'''
if not self.verify_close_event():
event.ignore()
return
BaseWindow.closeEvent(self, event)
@Slot()
def show_about(self):
'''Behind the scences information
'''
QApplication.processEvents()
QMessageBox.about(
self, 'About',
"""<b>{0}</b> v {1}
<p>Copyright &copy; Paul Scherrer Institut (PSI).
All rights reserved.</p>
<p>Authors: P.-A. Duperrex, J. Chrin, A. Facchetti, W. Koprek, </p>
<p>A python implementation of the LabVIEW measurement developed
by P.-A. Duperrex <br>
Ref: P.-A. Duperrex and A. Facchetti <br>
'Number of Turn Measurements on the HIPA Cyclotrons at PSI' <br>
doi:10.18429/JACoW-IPAC2018-WEPAL067 </p>
<p>Responsible: W. Koprek, WBBA/315, Tel. x3765,
waldemar.koprek@psi.ch </p>
<p>A main-window style application for the measurement of
the number of turns in the HIPA cyclotron and injector </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 tina_resources
'''
index_html = 'index.html'
help_base = ':'
help_page = HelpBrowser(help_base, index_html, self)
help_page.show()
#########################################################################
if __name__ == '__main__':
app = QApplication(sys.argv)
splash = BaseWindow.initialize_application(
app, appname=_appname, delay=5, facility=Facility.HIPA)
myapp = StartMain()
myapp.show()
if splash is not None:
splash.finish(myapp)
app.exec_()