Source code for blacs.front_panel_settings

#####################################################################
#                                                                   #
# /front_panel_settings.py                                          #
#                                                                   #
# Copyright 2013, Monash University                                 #
#                                                                   #
# This file is part of the program BLACS, in the labscript suite    #
# (see http://labscriptsuite.org), and is licensed under the        #
# Simplified BSD License. See the license.txt file in the root of   #
# the project for the full license.                                 #
#                                                                   #
#####################################################################
import os
import logging

from qtutils.qt.QtCore import *
from qtutils.qt.QtGui import *
from qtutils.qt.QtWidgets import *

import labscript_utils.excepthook
import numpy
import labscript_utils.h5_lock, h5py
from qtutils import *

from labscript_utils.connections import ConnectionTable

logger = logging.getLogger('BLACS.FrontPanelSettings')  

def _ensure_str(s):
    """convert bytestrings and numpy strings to python strings"""
    return s.decode() if isinstance(s, bytes) else str(s)

[docs] class FrontPanelSettings(object):
[docs] def __init__(self,settings_path,connection_table): self.settings_path = settings_path self.connection_table = connection_table with h5py.File(settings_path,'a') as h5file: pass
[docs] def setup(self,blacs): self.tablist = blacs.tablist self.attached_devices = blacs.attached_devices self.notebook = blacs.tab_widgets self.window = blacs.ui self.panes = blacs.panes self.blacs = blacs
[docs] def restore(self): # Get list of DO/AO # Does the object have a name? # yes: Then, find the device in the BLACS connection table that matches that name # Also Find the device in the saved connection table. # Do the connection table entries match? # yes: Restore as is # no: Is it JUST the parent device and "connected to" that has changed? # yes: Restore to new device # no: Show error that this device could not be restored # no: Ok, so it isn't in the saved connection table # Does this device/channel exist in the BLACS connection table? # yes: Don't restore, show error that this chanel is now in use by a new device # Give option to restore anyway... # no: Restore as is # # Display errors, give option to cancel starting of BLACS so that the connection table can be edited # Create saved connection table settings = {} question = {} error = {} tab_data = {'BLACS settings':{}} try: saved_ct = ConnectionTable(self.settings_path, logging_prefix='BLACS', exceptions_in_thread=True) ct_match,error = self.connection_table.compare_to(saved_ct) with h5py.File(self.settings_path,'r') as hdf5_file: # Get Tab Data dataset = hdf5_file['/front_panel'].get('_notebook_data',[]) for row in dataset: tab_name = _ensure_str(row['tab_name']) tab_data.setdefault(tab_name,{}) try: tab_data[tab_name] = {'notebook':row['notebook'], 'page':row['page'], 'visible':row['visible'], 'data':eval(_ensure_str(row['data']))} except Exception: logger.info("Could not load tab data for %s"%tab_name) #now get dataset attributes tab_data['BLACS settings'] = dict(dataset.attrs) # Get the front panel values if 'front_panel' in hdf5_file["/front_panel"]: dataset = hdf5_file["/front_panel"].get('front_panel', []) for row in dataset: result = self.check_row(row,ct_match,self.connection_table,saved_ct) columns = ['name', 'device_name', 'channel', 'base_value', 'locked', 'base_step_size', 'current_units'] data_dict = {} for i in range(len(row)): if isinstance(row[i], bytes) or isinstance(row[i], str): data_dict[columns[i]] = _ensure_str(row[i]) else: data_dict[columns[i]] = row[i] settings,question,error = self.handle_return_code(data_dict,result,settings,question,error) # Else Legacy restore from GTK save data! else: # open Datasets type_list = ["AO", "DO", "DDS"] for key in type_list: dataset = hdf5_file["/front_panel"].get(key, []) for row in dataset: result = self.check_row(row,ct_match,self.connection_table,saved_ct) columns = ['name', 'device_name', 'channel', 'base_value', 'locked', 'base_step_size', 'current_units'] data_dict = {} for i in range(len(row)): data_dict[columns[i]] = row[i] settings,question,error = self.handle_return_code(data_dict,result,settings,question,error) except Exception as e: logger.info("Could not load saved settings") logger.info(str(e)) return settings,question,error,tab_data
[docs] def handle_return_code(self,row,result,settings,question,error): # 1: Restore to existing device # 2: Send to new device # 3: Device now exists, use saved values from unnamed device? # Note that if 2 has happened, 3 will also happen # This is because we have the original channel, and the moved channel in the same place #-1: Device no longer in the connection table, throw error #-2: Device parameters not compatible, throw error if type(result) == tuple: connection = result[1] result = result[0] if result == 1: settings.setdefault(row['device_name'],{}) settings[row['device_name']][row['channel']] = row elif result == 2: settings.setdefault(connection.parent.name,{}) settings[connection.parent.name][connection.parent_port] = row elif result == 3: question.setdefault(connection.parent.name,{}) question[connection.parent.name][connection.parent_port] = row elif result == -1: error[row['device_name']+'_'+row['channel']] = row,"missing" elif result == -2: error[row['device_name']+'_'+row['channel']] = row,"changed" return settings,question,error
[docs] def check_row(self,row,ct_match,blacs_ct,saved_ct): # If it has a name if row[0] != "-": if ct_match: # Restore return 1 else: # Find if this device is in the connection table connection = blacs_ct.find_by_name(row[0]) connection2 = saved_ct.find_by_name(row[0]) if connection: # compare the two connections, see what differs # if compare fails only on parent, connected to: # send to new parent # else: # show error, device parameters not compatible with saved data result,error = connection.compare_to(connection2) allowed_length = 0 if "parent_port" in error: allowed_length += 1 if len(error) > allowed_length: return -2 # failure, device parameters not compatible elif error == {} and connection.parent.name == connection2.parent.name: return 1 # All details about this device match else: return 2,connection # moved to new device else: # no longer in connection table, throw error return -1 else: # It doesn't have a name # Does the channel exist for this device in the connection table connection = blacs_ct.find_child(row[1],row[2]) if connection: # throw error, device now exists, should we restore? return 3,connection else: # restore to device return 1
[docs] @inmain_decorator(wait_for_return=True) def get_save_data(self): tab_data = {} notebook_data = {} window_data = {} plugin_data = {} # iterate over all tabs for device_name,tab in self.tablist.items(): tab_data[device_name] = {'front_panel':tab.settings['front_panel_settings'], 'save_data': tab.get_all_save_data()} # Find the notebook the tab is in # # By default we assume it is in notebook0, on page 0. This way, if a tab gets lost somewhere, # and isn't found to be a child of any notebook we know about, # it will revert back to notebook 1 when the file is loaded upon program restart! current_notebook_name = 0 page = 0 visible = False for notebook_name,notebook in self.notebook.items(): if notebook.indexOf(tab._ui) != -1: current_notebook_name = notebook_name page = notebook.indexOf(tab._ui) visible = True if notebook.currentIndex() == page else False break notebook_data[device_name] = {"notebook":current_notebook_name,"page":page, "visible":visible} # iterate over all plugins for module_name, plugin in self.blacs.plugins.items(): try: plugin_data[module_name] = plugin.get_save_data() except Exception as e: logger.error('Could not save data for plugin %s. Error was: %s'%(module_name,str(e))) # save window data # Size of window window_data["_main_window"] = {"width":self.window.normalGeometry().width(), "height":self.window.normalGeometry().height(), "xpos":self.window.normalGeometry().x(), "ypos":self.window.normalGeometry().y(), "maximized":self.window.isMaximized(), "frame_height":abs(self.window.frameGeometry().height()-self.window.normalGeometry().height()), "frame_width":abs(self.window.frameGeometry().width()-self.window.normalGeometry().width()), "_analysis":self.blacs.analysis_submission.get_save_data(), "_queue":self.blacs.queue.get_save_data(), } # Pane positions for name,pane in self.panes.items(): window_data[name] = pane.sizes() return tab_data,notebook_data,window_data,plugin_data
[docs] @inmain_decorator(wait_for_return=True) def save_front_panel_to_h5(self,current_file,states,tab_positions,window_data,plugin_data,silent = {}, force_new_conn_table = False): # Save the front panel! # Does the file exist? # Yes: Check connection table inside matches current connection table. Does it match? # Yes: Does the file have a front panel already saved in it? # Yes: Can we overwrite? # Yes: Delete front_panel group, save new front panel # No: Create error dialog! # No: Save front panel in here # # No: Return # No: Create new file, place inside the connection table and front panel if os.path.isfile(current_file): save_conn_table = True if force_new_conn_table else False result = False if not save_conn_table: try: new_conn = ConnectionTable(current_file) result,error = self.connection_table.compare_to(new_conn) except Exception: # no connection table is present, so also save the connection table! save_conn_table = True # if save_conn_table is True, we don't bother checking to see if the connection tables match, because save_conn_table is only true when the connection table doesn't exist in the current file # As a result, if save_conn_table is True, we ignore connection table checking, and save the connection table in the h5file. if save_conn_table or result: with h5py.File(current_file,'r+') as hdf5_file: if hdf5_file['/'].get('front_panel') != None: # Create a dialog to ask whether we can overwrite! overwrite = False if not silent: message = QMessageBox() message.setText("This file '%s' already contains a connection table."%current_file) message.setInformativeText("Do you wish to replace the existing front panel configuration in this file?") message.setStandardButtons(QMessageBox.Yes | QMessageBox.No) message.setDefaultButton(QMessageBox.No) message.setIcon(QMessageBox.Question) message.setWindowTitle("BLACS") resp = message.exec_() if resp == QMessageBox.Yes : overwrite = True else: overwrite = silent["overwrite"] if overwrite: # Delete Front panel group, save new front panel del hdf5_file['/front_panel'] self.store_front_panel_in_h5(hdf5_file,states,tab_positions,window_data,plugin_data,save_conn_table) else: if not silent: message = QMessageBox() message.setText("Front Panel not saved.") message.setIcon(QMessageBox.Information) message.setWindowTitle("BLACS") message.exec_() else: logger.info("Front Panel not saved as it already existed in the h5 file '"+current_file+"'") return else: # Save Front Panel in here self.store_front_panel_in_h5(hdf5_file,states,tab_positions,window_data,plugin_data,save_conn_table) else: # Create Error dialog (invalid connection table) if not silent: message = QMessageBox() message.setText("The Front Panel was not saved as the file selected contains a connection table which is not a subset of the BLACS connection table.") message.setIcon(QMessageBox.Information) message.setWindowTitle("BLACS") message.exec_() else: logger.info("Front Panel not saved as the connection table in the h5 file '"+current_file+"' didn't match the current connection table.") return else: with h5py.File(current_file,'w') as hdf5_file: # save connection table, save front panel self.store_front_panel_in_h5(hdf5_file,states,tab_positions,window_data,plugin_data,save_conn_table=True)
[docs] @inmain_decorator(wait_for_return=True) def store_front_panel_in_h5(self, hdf5_file,tab_data,notebook_data,window_data,plugin_data,save_conn_table=False,save_queue_data=True): if save_conn_table: if 'connection table' in hdf5_file: del hdf5_file['connection table'] hdf5_file.create_dataset('connection table', data=self.connection_table.raw_table) data_group = hdf5_file['/'].create_group('front_panel') front_panel_list = [] other_data_list = [] front_panel_dtype = [('name','a256'),('device_name','a256'),('channel','a256'),('base_value',float),('locked',bool),('base_step_size',float),('current_units','a256')] max_od_length = 2 # empty dictionary # Iterate over each device within a class for device_name, device_state in tab_data.items(): logger.debug("saving front panel for device:" + device_name) # Insert front panel data into dataset for hardware_name, data in device_state["front_panel"].items(): if data != {}: if isinstance(data['base_value'], (str, bytes)): logger.warning('Could not save data for channel %s on device %s because support for output values that are strings is not yet supported.'%(hardware_name, device_name)) # TODO: Implement saving of Image output type elif float(data['base_value']) == data['base_value']: front_panel_list.append((data['name'], device_name, hardware_name, data['base_value'], data['locked'], data['base_step_size'] if 'base_step_size' in data else 0, data['current_units'] if 'current_units' in data else '' ) ) else: logger.warning('Could not save data for channel %s on device %s because the output value (in base units) was not a string or could not be coerced to a float without loss of precision'%(hardware_name, device_name)) # Save "other data" od = repr(device_state["save_data"]) other_data_list.append(od) max_od_length = len(od) if len(od) > max_od_length else max_od_length # Create datasets if front_panel_list: front_panel_array = numpy.empty(len(front_panel_list),dtype=front_panel_dtype) for i, row in enumerate(front_panel_list): front_panel_array[i] = row data_group.create_dataset('front_panel',data=front_panel_array) # Save tab data i = 0 tab_data = numpy.empty(len(notebook_data),dtype=[('tab_name','a256'),('notebook','a2'),('page',int),('visible',bool),('data','a'+str(max_od_length))]) for device_name,data in notebook_data.items(): tab_data[i] = (device_name,data["notebook"],data["page"],data["visible"],other_data_list[i]) i += 1 # Save BLACS Main GUI Info dataset = data_group.create_dataset("_notebook_data",data=tab_data) dataset.attrs["window_width"] = window_data["_main_window"]["width"] dataset.attrs["window_height"] = window_data["_main_window"]["height"] dataset.attrs["window_xpos"] = window_data["_main_window"]["xpos"] dataset.attrs["window_ypos"] = window_data["_main_window"]["ypos"] dataset.attrs["window_maximized"] = window_data["_main_window"]["maximized"] dataset.attrs["window_frame_height"] = window_data["_main_window"]["frame_height"] dataset.attrs["window_frame_width"] = window_data["_main_window"]["frame_width"] dataset.attrs['plugin_data'] = repr(plugin_data) dataset.attrs['analysis_data'] = repr(window_data["_main_window"]["_analysis"]) if save_queue_data: dataset.attrs['queue_data'] = repr(window_data["_main_window"]["_queue"]) for pane_name,pane_position in window_data.items(): if pane_name != "_main_window": dataset.attrs[pane_name] = pane_position
# Save analysis server settings: #dataset = data_group.create_group("analysis_server") #dataset.attrs['send_for_analysis'] = self.blacs.analysis_submission.toggle_analysis.get_active() #dataset.attrs['server'] = self.blacs.analysis_submission.analysis_host.get_text()