'''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 _pymodule = os.path.basename(__file__) _appname, _appext = _pymodule.split('.') _abspath = os.path.dirname(os.path.abspath(__file__)) _appversion = '1.4.0' _title = 'No of Turns Measurement' _appname = 'Tina' class StartMain(BaseWindow): '''Application to measure the no of turns in Injector 2 and the Ring Cyclotron. A python implementation of the LabView application developed by Pierre-Andre Duperrex Attributes: appname: tina.py source_file: full pathname for inclusion into HDF. elog_enum: enumerated datatypes for elog attributes. message_elog: string to hold the elogbook message. self.default_idx: int to indicate default accelerator. self.accelerator: str to indicate default accelerator. self.message: string to hold message to gui log pane. ''' ring_cyclotron = 'Cyclotron' injector_2 = 'Injector' accelerator_list = [injector_2, ring_cyclotron] def __init__(self, parent=None): '''Initialize class attrinbutes and instantiate GUI.''' super().__init__( parent=parent, pymodule=_pymodule, appversion=_appversion, title=_title, user_mode=UserMode.OPERATION, facility=Facility.HIPA, has_optics=False, has_procedure='VerticalExpert') self.appname = _appname self.source_file = _abspath 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.accelerator_peak_search = ' ' + self.accelerator + ' ' # self.from_hdf = False in base class self.message = '' self.init_pv_dict = self.settings.data['Init'][self.accelerator] self.init_pv_list = list(self.init_pv_dict.keys()) self.init_pv_values = list(self.init_pv_dict.values()) if self.is_front_end_initialized(init=False): mess = 'FEE Initialized' self.trigger_log_message.emit( MsgSeverity.INFO.name, _pymodule, utils.line_no(), mess, {}) AppGui(self) def prepare_results_message(self): '''Prepare results messages ''' 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.correlation_peak_diff = self.all_data['Processed data'][ 'correlation_peak_diff'] self.correlation_min_peak_diff = self.all_data['Processed data'][ 'correlation_min_peak_diff'] except KeyError: self.message = '' self.message_elog = '' return try: self.accelerator = self.all_data['Input data']['accelerator'] except KeyError as ex: logger_mess = f'KeyError {str(ex)}' self.logger.debug(logger_mess) try: self.accelerator = self.all_data['Input data']['qtabdata'] except KeyError as ex: logger_mess = f'KeyError {str(ex)}' self.logger.debug(logger_mess) mess = 'Reanalysis from HDF5. ' if self.from_hdf else '' self.message_elog = ( 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)) 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 is_front_end_initialized(self, init: bool = True): '''Initialized Front End Electonics if required ''' value_list, status, status_list = self.cafe.getScalarList( self.init_pv_list, dt='int') if status != self.cyca.ICAFE_NORMAL: self.check_status_list( _pymodule, 'getScalarList', self.init_pv_list, status_list, utils.line_no()) if value_list != self.init_pv_values and not init: return False def is_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 else: simulation = self.input_parameters['simulation'] return simulation if value_list != self.init_pv_values: for val, val_init, pv in zip(value_list, self.init_pv_values, self.init_pv_list): if val != val_init: if not is_simulation(): status = self.cafe.set(pv, val_init) if status != self.cyca.ICAFE_NORMAL: self.check_status(_pymodule, 'set', pv, status, utils.line_no()) time.sleep(0.02) value_list, status, status_list = self.cafe.getScalarList( self.init_pv_list, dt='int') if status != self.cyca.ICAFE_NORMAL: self.check_status_list( _pymodule, 'getScalarList', self.init_pv_list, status_list, utils.line_no()) if value_list != self.init_pv_values: return False return True def verify_analysis_preconditions(self): '''Verify machine is setup for measurement procedure- Check machine current is above threshold (0.001 mA is default) Check that that the front-end electronics are initialized ''' accelerator = self.input_parameters['accelerator'] if self.injector_2 in 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 min_current = float(self.input_parameters[self.accelerator_peak_search][ 'minimumCurrent']) 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 <= min_current: mess = (f'Injector 2 current is below threshold {min_current} mA.' + '\nMeasurement cannot be untertaken at the present time.') QMessageBox.information(self, 'Cyclotron', mess, QMessageBox.Ok) QApplication.processEvents() return False return self.is_front_end_initialized(init=True) @Slot() def analysis_thread_finished(self): '''If analysis completed successfuly, prepare results message. ''' BaseWindow.analysis_thread_finished(self) def delete_previous_figures(): print('Thread finished with no data') ncanvas = len(self.settings.data['GUI']['subResultsTabTitle']) dict_fig = {} dict_fig['Figure data'] = {} print('canvas', ncanvas, flush=True) for i in range(0, ncanvas): canvas = f'Canvas {i+1}' dict_fig['Figure data'][canvas] = None # Delete old figures try: self.gui_frame.canvas_update(dict_fig['Figure data']) except AttributeError as ex: print('AttributeError', ex, flush=True) pass print('Thread finished with no data', flush=True) if self.all_data is not None: try: try: if self.all_data['Figure data'] is not None: self.gui_frame.central_tab_widget.setCurrentIndex(1) else: delete_previous_figures() except AttributeError: print('No analysis performed') return except KeyError: print('No analysis performed') return else: delete_previous_figures() return self.prepare_results_message() self.show_log_message(MsgSeverity.INFO, _pymodule, utils.line_no(), self.message) @Slot() def hdf_thread_finished(self): '''If hdf analysis completed successfuly, prepare results message. ''' BaseWindow.hdf_thread_finished(self) def delete_previous_figures(): print('Thread finished with no data') ncanvas = len(self.settings.data['GUI']['subResultsTabTitle']) dict_fig = {} dict_fig['Figure data'] = {} print('canvas', ncanvas, flush=True) for i in range(0, ncanvas): canvas = f'Canvas {i+1}' dict_fig['Figure data'][canvas] = None # Delete old figures try: self.gui_frame.canvas_update(dict_fig['Figure data']) except AttributeError as ex: print('AttributeError', ex, flush=True) pass print('Thread finished with no data', flush=True) if self.all_data is not None: try: try: if self.all_data['Figure data'] is not None: self.gui_frame.central_tab_widget.setCurrentIndex(1) else: delete_previous_figures() except AttributeError: print('No analysis performed') return except KeyError: print('No analysis performed') return else: delete_previous_figures() return print("hdf_finished", flush=True) self.prepare_results_message() self.show_log_message(MsgSeverity.INFO, _pymodule, utils.line_no(), self.message) @Slot() def save_to_hdf_dialog(self): '''Save to hdf via the HDF dialogue window in menu bar. ''' 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): '''Save to hdf via pilot mode. ''' 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, data_hdf, 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'], data_hdf) @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.correlation_peak_diff < self.correlation_min_peak_diff: mess = ('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_min_peak_diff:0.3f}. \n' + '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', """{0} v {1}

Copyright © Paul Scherrer Institut (PSI). All rights reserved.

P.-A. Duperrex, J. Chrin, A. Facchetti, D. Felici, W. Koprek, J. Sun

A python implementation of the LabVIEW measurement originally developed by P.-A. Duperrex
Ref: P.-A. Duperrex and A. Facchetti
'Number of Turn Measurements on the HIPA Cyclotrons at PSI'
doi:10.18429/JACoW-IPAC2018-WEPAL067

Contact: J. Sun, WBGA/C32, Tel. x4127, jilei.sun@psi.ch

Responsible (low-level): W. Koprek, WBBA/315, Tel. x3765, waldemar.koprek@psi.ch

Responsible (high-level): J. Chrin, WBBA/318, Tel. x2930, jan.chrin@psi.ch

A main-window style application for the measurement of the number of turns in the HIPA cyclotron and injector

Python {2} - Qt {3} - PyQt {4}
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, xlength=800, ylength=1290) help_page.show() ######################################################################### if __name__ == '__main__': '''Initialize window parameters and start application via a splash window. ''' 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_()