/
front_panel_settings.py
405 lines (359 loc) 路 21.4 KB
/
front_panel_settings.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
#####################################################################
# #
# /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)
class FrontPanelSettings(object):
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
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
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
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
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
@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
@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)
@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()