Completed
Push — pulsed_with_queued_connections ( 58679c...83e612 )
by Jan
02:21
created

PulseEditor   F

Complexity

Total Complexity 231

Size/Duplication

Total Lines 1923
Duplicated Lines 15.86 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 305
loc 1923
rs 0.6314
wmc 231

68 Methods

Rating   Name   Duplication   Size   Complexity  
A block_organizer_delete_row_selected() 0 8 1
A block_editor_add_row_after_last() 18 18 2
A block_organizer_clear_table() 0 10 1
A block_editor_delete_row_last() 0 7 1
A _add_config_for_predefined_methods() 0 12 1
A generate_pulse_block_ensemble_clicked() 0 11 2
A _interleave_changed() 0 6 1
A get_current_function_list() 0 18 3
A _create_QCheckBox() 0 12 1
A _create_QLabel() 0 17 1
A _create_QPushButton() 0 12 1
A delete_pulse_block_clicked() 0 19 1
A get_element_in_organizer_table() 0 21 1
F _update_current_pulse_block_ensemble() 0 87 30
A _get_ref_checkbox_predefined_methods_config() 0 9 1
A keep_former_predefined_methods() 0 7 2
A block_organizer_add_row_before_selected() 16 16 3
B _activate_pulse_generator_ui() 0 121 3
A show_predefined_methods_config() 0 4 1
A generator_laser_channel_changed() 0 12 1
C get_pulse_block_table() 36 36 7
B _determine_needed_parameters() 0 25 3
B initialize_cells_block_editor() 41 41 6
A __init__() 0 6 1
A generator_sample_rate_changed() 0 11 1
A set_cfg_param_pbe() 0 18 4
B get_element_in_block_table() 0 24 2
A _activate_pulse_generator_settings_ui() 0 54 3
A show_block_settings() 0 3 1
B generate_pulse_block_ensemble_object() 0 36 2
A _deactivate_pulse_generator_settings_ui() 0 11 1
A _deactivate_pulse_generator_ui() 0 11 2
B set_cfg_param_pb() 0 20 5
F _set_block_editor_columns() 0 134 13
D _create_control_for_predefined_methods() 0 103 8
B _update_current_pulse_block() 0 45 6
A _gen_apply_hardware_constraints() 0 18 1
A show_predefined_methods() 0 4 1
C get_organizer_table() 37 37 7
F load_pulse_block_ensemble_clicked() 0 94 12
A generate_pulse_block_clicked() 0 20 2
A block_editor_delete_row_selected() 0 7 1
D generate_pulse_block_object() 0 74 9
A update_block_organizer_list() 0 11 3
B set_element_in_block_table() 0 33 3
B set_element_in_organizer_table() 28 28 2
B generator_activation_config_changed() 0 35 2
A block_editor_add_row_before_selected() 18 18 3
A block_organizer_add_row_after_last() 18 18 2
B _init_generator_values() 0 31 4
B _function_builder_generate() 0 38 6
A _get_ref_groupbox_predefined_methods() 0 10 1
B initialize_cells_block_organizer() 39 39 6
A get_func_config_list() 0 6 1
A get_current_ensemble_list() 0 6 1
A _create_QDoubleSpinBox() 0 22 1
A block_editor_clear_table() 0 10 1
A keep_former_block_settings() 0 3 1
A _create_QSpinBox() 0 14 1
A apply_block_settings() 0 6 2
A _create_QLineEdit() 0 22 1
B func_dummy_name() 0 22 5
A get_current_pulse_block_list() 0 7 1
A delete_pulse_block_ensemble_clicked() 0 8 1
A update_predefined_methods() 0 7 2
F load_pulse_block_clicked() 0 136 24
B _set_organizer_columns() 54 54 5
A block_organizer_delete_row_last() 0 8 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like PulseEditor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# -*- coding: utf-8 -*-
2
3
from qtpy import QtGui
4
from qtpy import QtCore
5
from qtpy import QtWidgets
6
from qtpy import uic
7
import numpy as np
8
import os
9
from collections import OrderedDict
10
import pyqtgraph as pg
11
import re
12
import inspect
13
import datetime
14
15
from logic.pulse_objects import Pulse_Block_Element
16
from logic.pulse_objects import Pulse_Block
17
from logic.pulse_objects import Pulse_Block_Ensemble
18
from logic.pulse_objects import Pulse_Sequence
19
20
from .spinbox_delegate import SpinBoxDelegate
21
from .doublespinbox_delegate import DoubleSpinBoxDelegate
22
from .combobox_delegate import ComboBoxDelegate
23
from .checkbox_delegate import CheckBoxDelegate
24
25
from core.util.mutex import Mutex
26
from core.util import units
27
28
class PulseEditorWidget(QtWidgets.QWidget):
29
    def __init__(self):
30
        # Get the path to the *.ui file
31
        this_dir = os.path.dirname(__file__)
32
        ui_file = os.path.join(this_dir, 'ui_pulse_editor.ui')
33
        # Load it
34
        super().__init__()
35
        uic.loadUi(ui_file, self)
36
37
38
class BlockSettingsDialog(QtWidgets.QDialog):
39
    def __init__(self):
40
        # Get the path to the *.ui file
41
        this_dir = os.path.dirname(__file__)
42
        ui_file = os.path.join(this_dir, 'ui_pulsed_main_gui_settings_block_gen.ui')
43
44
        # Load it
45
        super().__init__()
46
        uic.loadUi(ui_file, self)
47
48
49
class PredefinedMethodsDialog(QtWidgets.QDialog):
50
    def __init__(self):
51
        # Get the path to the *.ui file
52
        this_dir = os.path.dirname(__file__)
53
        ui_file = os.path.join(this_dir, 'ui_predefined_methods.ui')
54
55
        # Load it
56
        super().__init__()
57
        uic.loadUi(ui_file, self)
58
59
60
class PredefinedMethodsConfigDialog(QtWidgets.QDialog):
61
    def __init__(self):
62
        # Get the path to the *.ui file
63
        this_dir = os.path.dirname(__file__)
64
        ui_file = os.path.join(this_dir, 'ui-predefined_methods_config.ui')
65
66
        # Load it
67
        super().__init__()
68
        uic.loadUi(ui_file, self)
69
70
71
class PulseEditor:
72
    def __init__(self, sequence_gen_logic, pulse_meas_logic):
73
        self._pe = PulseEditorWidget()
74
        self._pulsed_meas_logic = pulse_meas_logic
75
        self._seq_gen_logic = sequence_gen_logic
76
        self._activate_pulse_generator_settings_ui()
77
        self._activate_pulse_generator_ui()
78
79
    def _activate_pulse_generator_settings_ui(self):
80
        """ Initialize, connect and configure the Settings for the
81
            'Pulse Generator' Tab.
82
        """
83
84
        self._bs = BlockSettingsDialog() # initialize the block settings
85
        self._bs.accepted.connect(self.apply_block_settings)
86
        self._bs.rejected.connect(self.keep_former_block_settings)
87
        self._bs.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self.apply_block_settings)
88
89
        #self._bs.use_interleave_CheckBox.setChecked(self._pulsed_meas_logic.get_interleave())
90
        self._bs.use_interleave_CheckBox.stateChanged.connect(self._interleave_changed)
91
92
        # create the Predefined methods Dialog
93
        self._pm = PredefinedMethodsDialog()
94
        self._predefined_methods_list = []  # here are all the names saved of
95
                                            # the created predefined methods.
96
97
        # create a config for the predefined methods:
98
        self._pm_cfg = PredefinedMethodsConfigDialog()
99
100
        # Add in the settings menu within the groupbox widget all the available
101
        # math_functions, based on the list from the Logic. Right now, the GUI
102
        # objects are inserted the 'hard' way, like it is done in the
103
        # Qt-Designer.
104
105
        # FIXME: Make a nicer way of displaying the available functions, maybe
106
        #        with a Table!
107
108
        _encoding = QtWidgets.QApplication.UnicodeUTF8
109
        objectname = self._bs.objectName()
110
        for index, func_name in enumerate(self.get_func_config_list()):
111
112
            name_label = 'func_'+ str(index)
113
            setattr(self._bs, name_label, QtWidgets.QLabel(self._bs.groupBox))
114
            label = getattr(self._bs, name_label)
115
            label.setObjectName(name_label)
116
            self._bs.gridLayout_3.addWidget(label, index, 0, 1, 1)
117
            label.setText(QtWidgets.QApplication.translate(objectname, func_name, None, _encoding))
118
119
            name_checkbox = 'checkbox_'+ str(index)
120
            setattr(self._bs, name_checkbox, QtWidgets.QCheckBox(self._bs.groupBox))
121
            checkbox = getattr(self._bs, name_checkbox)
122
            checkbox.setObjectName(name_checkbox)
123
            self._bs.gridLayout_3.addWidget(checkbox, index, 1, 1, 1)
124
            checkbox.setText(QtWidgets.QApplication.translate(objectname, '', None, _encoding))
125
126
        # make the first 4 Functions as default.
127
        # FIXME: the default functions, must be passed as a config
128
129
        for index in range(4):
130
            name_checkbox = 'checkbox_'+ str(index)
131
            checkbox = getattr(self._bs, name_checkbox)
132
            checkbox.setCheckState(QtCore.Qt.Checked)
133
134
    def _deactivate_pulse_generator_settings_ui(self):
135
        """ Disconnects the configuration of the Settings for the
136
            'Pulse Generator' Tab.
137
138
        @param object e: Fysom.event object from Fysom class. A more detailed
139
                         explanation can be found in the method initUI.
140
        """
141
        self._bs.accepted.disconnect()
142
        self._bs.rejected.disconnect()
143
        self._bs.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.disconnect()
144
        self._bs.close()
145
146
    def _interleave_changed(self, state):
147
        """ React on a Interleave state change.
148
149
        @param int state: 0 for False and 1 or 2 for True.
150
        """
151
        self._seq_gen_logic.set_interleave(bool(state))
152
153
    def show_block_settings(self):
154
        """ Opens the block settings menue. """
155
        self._bs.exec_()
156
157
    def show_predefined_methods(self):
158
        """ Opens the predefined methods Window."""
159
        self._pm.show()
160
        self._pm.raise_()
161
162
    def show_predefined_methods_config(self):
163
        """ Opens the Window for the config of predefined methods."""
164
        self._pm_cfg.show()
165
        self._pm_cfg.raise_()
166
167
    def keep_former_predefined_methods(self):
168
169
        for method_name in self._predefined_methods_list:
170
            groupbox = self._get_ref_groupbox_predefined_methods(method_name)
171
            checkbox = self._get_ref_checkbox_predefined_methods_config(method_name)
172
173
            checkbox.setChecked(groupbox.isVisible())
174
175
    def update_predefined_methods(self):
176
177
        for method_name in self._predefined_methods_list:
178
            groupbox = self._get_ref_groupbox_predefined_methods(method_name)
179
            checkbox = self._get_ref_checkbox_predefined_methods_config(method_name)
180
181
            groupbox.setVisible(checkbox.isChecked())
182
183
184
    def apply_block_settings(self):
185
        """ Write new block settings from the gui to the file. """
186
        if self._bs.use_saupload_CheckBox.isChecked():
187
            self._set_visibility_saupload_button_pulse_gen(state=True)
188
        else:
189
            self._set_visibility_saupload_button_pulse_gen(state=False)
190
191
192
    def keep_former_block_settings(self):
193
        """ Keep the old block settings and restores them in the gui. """
194
        pass
195
196
    def get_current_function_list(self):
197
        """ Retrieve the functions, which are chosen by the user.
198
199
        @return: list[] with strings of the used functions. Names are based on
200
                 the passed func_config dict from the logic. Depending on the
201
                 settings, a current function list is generated.
202
        """
203
        current_functions = []
204
205
        for index, func_name in enumerate(self.get_func_config_list()):
206
            name_checkbox = 'checkbox_'+ str(index)
207
            checkbox = getattr(self._bs, name_checkbox)
208
            if checkbox.isChecked():
209
                name_label = 'func_'+ str(index)
210
                func = getattr(self._bs, name_label)
211
                current_functions.append(func.text())
212
213
        return current_functions
214
215
    def _activate_pulse_generator_ui(self):
216
        """ Initialize, connect and configure the 'Pulse Generator' Tab.
217
        """
218
        # connect the signal for a change of the generator parameters
219
        self._pe.gen_sample_freq_DSpinBox.editingFinished.connect(
220
            self.generator_sample_rate_changed)
221
        self._pe.gen_laserchannel_ComboBox.currentIndexChanged.connect(
222
            self.generator_laser_channel_changed)
223
        self._pe.gen_activation_config_ComboBox.currentIndexChanged.connect(
224
            self.generator_activation_config_changed)
225
226
        # connect signal for file upload and loading of pulser device
227
        #self.sigSampleEnsemble.connect(self._seq_gen_logic.sample_pulse_block_ensemble)
228
        #self.sigUploadToDevice.connect(self._pulsed_meas_logic.upload_asset)
229
        #self.sigLoadToChannel.connect(self._pulsed_meas_logic.load_asset)
230
231
        # set them to maximum or minimum
232
        self._pe.curr_block_bins_SpinBox.setMaximum(2**31 -1)
233
        self._pe.curr_block_laserpulses_SpinBox.setMaximum(2**31 -1)
234
        self._pe.curr_ensemble_bins_SpinBox.setMaximum(2**31 -1)
235
        self._pe.curr_ensemble_length_DSpinBox.setMaximum(np.inf)
236
237
        # connect the signals for the block editor:
238
        self._pe.block_add_last_PushButton.clicked.connect(self.block_editor_add_row_after_last)
239
        self._pe.block_del_last_PushButton.clicked.connect(self.block_editor_delete_row_last)
240
        self._pe.block_add_sel_PushButton.clicked.connect(self.block_editor_add_row_before_selected)
241
        self._pe.block_del_sel_PushButton.clicked.connect(self.block_editor_delete_row_selected)
242
        self._pe.block_clear_PushButton.clicked.connect(self.block_editor_clear_table)
243
244
        self._pe.curr_block_load_PushButton.clicked.connect(self.load_pulse_block_clicked)
245
        self._pe.curr_block_del_PushButton.clicked.connect(self.delete_pulse_block_clicked)
246
247
        # connect the signals for the block organizer:
248
        self._pe.organizer_add_last_PushButton.clicked.connect(self.block_organizer_add_row_after_last)
249
        self._pe.organizer_del_last_PushButton.clicked.connect(self.block_organizer_delete_row_last)
250
        self._pe.organizer_add_sel_PushButton.clicked.connect(self.block_organizer_add_row_before_selected)
251
        self._pe.organizer_del_sel_PushButton.clicked.connect(self.block_organizer_delete_row_selected)
252
        self._pe.organizer_clear_PushButton.clicked.connect(self.block_organizer_clear_table)
253
254
        self._pe.curr_ensemble_load_PushButton.clicked.connect(self.load_pulse_block_ensemble_clicked)
255
        self._pe.curr_ensemble_del_PushButton.clicked.connect(self.delete_pulse_block_ensemble_clicked)
256
257
        # connect the signals for the "Upload on device" section
258
        #self._pe.sample_ensemble_PushButton.clicked.connect(self.sample_ensemble_clicked)
259
        #self._pe.upload_to_device_PushButton.clicked.connect(self.upload_to_device_clicked)
260
        #self._pe.load_channel_PushButton.clicked.connect(self.load_into_channel_clicked)
261
262
        # connect the menue to the actions:
263
        #self._pe.action_Settings_Block_Generation.triggered.connect(self.show_block_settings)
264
        #self._pe.actionOpen_Predefined_Methods.triggered.connect(self.show_predefined_methods)
265
        #self._pe.actionConfigure_Predefined_Methods.triggered.connect(self.show_predefined_methods_config)
266
267
        # emit a trigger event when for all mouse click and keyboard click events:
268
        self._pe.block_editor_TableWidget.setEditTriggers(QtWidgets.QAbstractItemView.AllEditTriggers)
269
        self._pe.block_organizer_TableWidget.setEditTriggers(QtWidgets.QAbstractItemView.AllEditTriggers)
270
        # self._pe.seq_editor_TableWidget.setEditTriggers(QtWidgets.QAbstractItemView.AllEditTriggers)
271
272
        # connect update signals of the sequence_generator_logic
273
        #self._seq_gen_logic.signal_block_list_updated.connect(self.update_block_list)
274
        #self._seq_gen_logic.signal_ensemble_list_updated.connect(self.update_ensemble_list)
275
        #self._seq_gen_logic.sigSampleEnsembleComplete.connect(self.sample_ensemble_finished)
276
277
        # connect update signals of the pulsed_measurement_logic
278
        #self._pulsed_meas_logic.sigUploadAssetComplete.connect(self.upload_to_device_finished)
279
        #self._pulsed_meas_logic.sigLoadAssetComplete.connect(self.load_into_channel_finished)
280
281
        # Definition of this parameter. See fore more explanation in file
282
        # sampling_functions.py
283
        length_def = {'unit': 's', 'init_val': 0.0, 'min': 0.0, 'max': np.inf,
284
                      'view_stepsize': 1e-9, 'dec': 8, 'unit_prefix': 'n', 'type': float}
285
        rep_def = {'unit': '#', 'init_val': 0, 'min': 0, 'max': (2 ** 31 - 1),
286
                   'view_stepsize': 1, 'dec': 0, 'unit_prefix': '', 'type': int}
287
        bool_def = {'unit': 'bool', 'init_val': 0, 'min': 0, 'max': 1,
288
                    'view_stepsize': 1, 'dec': 0, 'unit_prefix': '', 'type': bool}
289
        # make a parameter constraint dict for the additional parameters of the
290
        # Pulse_Block_Ensemble objects:
291
        self._add_pbe_param = OrderedDict()
292
        self._add_pbe_param['length'] = length_def
293
        self._add_pbe_param['increment'] = length_def
294
        self._add_pbe_param['use as tick?'] = bool_def
295
        # make a parameter constraint dict for the additional parameters of the
296
        # Pulse_Block objects:
297
        self._add_pb_param = OrderedDict()
298
        self._add_pb_param['repetition'] = rep_def
299
300
        # create all the needed control widgets on the fly and connect their
301
        # actions to each other:
302
303
        self.keep_former_block_settings()
304
305
        self._set_block_editor_columns()
306
        self._set_organizer_columns()
307
308
        # connect all the needed signal to methods:
309
        self._pe.curr_block_generate_PushButton.clicked.connect(self.generate_pulse_block_clicked)
310
        self._pe.curr_ensemble_generate_PushButton.clicked.connect(self.generate_pulse_block_ensemble_clicked)
311
        self._pe.block_editor_TableWidget.itemChanged.connect(self._update_current_pulse_block)
312
313
        self._pe.block_organizer_TableWidget.itemChanged.connect(self._update_current_pulse_block_ensemble)
314
315
        # the loaded asset will be updated in the GUI:
316
        #self._pulsed_meas_logic.sigLoadedAssetUpdated.connect(self.update_loaded_asset)
317
318
        # connect the actions of the Config for Predefined methods:
319
        self._pm_cfg.accepted.connect(self.update_predefined_methods)
320
        self._pm_cfg.rejected.connect(self.keep_former_predefined_methods)
321
        self._pm_cfg.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self.update_predefined_methods)
322
323
        # set the chosen predefined method to be visible:
324
        for predefined_method in self._predefined_methods_list:
325
            if predefined_method in self._statusVariables:
326
                checkbox = self._get_ref_checkbox_predefined_methods_config(predefined_method)
327
                checkbox.setChecked(self._statusVariables[predefined_method])
328
329
        self.update_predefined_methods()
330
331
        # Apply hardware constraints to input widgets
332
        self._gen_apply_hardware_constraints()
333
334
        # Fill initial values from logic into input widgets
335
        self._init_generator_values()
336
337
        # Modified by me
338
        # self._pe.init_block_TableWidget.viewport().setAttribute(QtCore.Qt.WA_Hover)
339
        # self._pe.repeat_block_TableWidget.viewport().setAttribute(QtCore.Qt.WA_Hover)
340
341
    def _deactivate_pulse_generator_ui(self):
342
        """ Disconnects the configuration for 'Pulse Generator Tab.
343
        """
344
        #FIXME: implement a proper deactivation for that.
345
        self._pm.close()
346
        self._pm_cfg.close()
347
348
        # save which predefined method should be visible:
349
        for predefined_method in self._predefined_methods_list:
350
            checkbox = self._get_ref_checkbox_predefined_methods_config(predefined_method)
351
            self._statusVariables[predefined_method] = checkbox.isChecked()
352
353
    def _gen_apply_hardware_constraints(self):
354
        """
355
        Retrieve the constraints from pulser hardware and apply these constraints to the pulse
356
        generator GUI elements.
357
        """
358
        pulser_constr = self._pulsed_meas_logic.get_pulser_constraints()
359
        sample_min = pulser_constr['sample_rate']['min'] / 1e6
360
        sample_max = pulser_constr['sample_rate']['max'] / 1e6
361
        sample_step = pulser_constr['sample_rate']['step'] / 1e6
362
363
        self._pe.gen_sample_freq_DSpinBox.setMinimum(sample_min)
364
        self._pe.gen_sample_freq_DSpinBox.setMaximum(sample_max)
365
        self._pe.gen_sample_freq_DSpinBox.setSingleStep(sample_step)
366
        self._pe.gen_sample_freq_DSpinBox.setDecimals( (np.log10(sample_step)* -1) )
367
368
        # configure the sequence generator logic to use the hardware compatible file formats
369
        self._seq_gen_logic.waveform_format = pulser_constr['waveform_format']
370
        self._seq_gen_logic.sequence_format = pulser_constr['sequence_format']
371
372
    def _init_generator_values(self):
373
        """
374
        This method will retrieve the initial values from the sequence_generator_logic and
375
        initializes all input GUI elements with these values.
376
        """
377
        # get init values from logic
378
        sample_rate = self._seq_gen_logic.sample_rate
379
        laser_channel = self._seq_gen_logic.laser_channel
380
        activation_config = self._seq_gen_logic.activation_config
381
        # get hardware constraints
382
        avail_activation_configs = self._pulsed_meas_logic.get_pulser_constraints()['activation_config']
383
        # init GUI elements
384
        # set sample rate
385
        self._pe.gen_sample_freq_DSpinBox.setValue(sample_rate/1e6)
386
        self.generator_sample_rate_changed()
387
        # set activation_config. This will also update the laser channel and number of channels
388
        # from the logic.
389
        self._pe.gen_activation_config_ComboBox.blockSignals(True)
390
        self._pe.gen_activation_config_ComboBox.clear()
391
        self._pe.gen_activation_config_ComboBox.addItems(list(avail_activation_configs))
392
        found_config = False
393
        for config in avail_activation_configs:
394
            if avail_activation_configs[config] == activation_config:
395
                index = self._pe.gen_activation_config_ComboBox.findText(config)
396
                self._pe.gen_activation_config_ComboBox.setCurrentIndex(index)
397
                found_config = True
398
                break
399
        if not found_config:
400
            self._pe.gen_activation_config_ComboBox.setCurrentIndex(0)
401
        self._pe.gen_activation_config_ComboBox.blockSignals(False)
402
        self.generator_activation_config_changed()
403
404
    def get_func_config_list(self):
405
        """ Retrieve the possible math functions as a list of strings.
406
407
        @return: list[] with string entries as function names.
408
        """
409
        return list(self._seq_gen_logic.func_config)
410
411
    def get_current_pulse_block_list(self):
412
        """ Retrieve the available Pulse_Block objects from the logic.
413
414
        @return: list[] with strings descriping the available Pulse_Block
415
                        objects.
416
        """
417
        return self._seq_gen_logic.saved_pulse_blocks
418
419
    def get_current_ensemble_list(self):
420
        """ Retrieve the available Pulse_Block_Ensemble objects from the logic.
421
422
        @return: list[] with strings descriping the available Pulse_Block_Ensemble objects.
423
        """
424
        return self._seq_gen_logic.saved_pulse_block_ensembles
425
426
    def generator_sample_rate_changed(self):
427
        """
428
        Is called whenever the sample rate for the sequence generation has changed in the GUI
429
        """
430
        self._pe.gen_sample_freq_DSpinBox.blockSignals(True)
431
        sample_rate = self._pe.gen_sample_freq_DSpinBox.value()*1e6
432
        actual_sample_rate = self._seq_gen_logic.set_sample_rate(sample_rate)
433
        self._pe.gen_sample_freq_DSpinBox.setValue(actual_sample_rate/1e6)
434
        self._pe.gen_sample_freq_DSpinBox.blockSignals(False)
435
        self._update_current_pulse_block()
436
        self._update_current_pulse_block_ensemble()
437
        # self._update_current_pulse_sequence()
438
439
    def generator_laser_channel_changed(self):
440
        """
441
        Is called whenever the laser channel for the sequence generation has changed in the GUI
442
        """
443
        self._pe.gen_laserchannel_ComboBox.blockSignals(True)
444
        laser_channel = self._pe.gen_laserchannel_ComboBox.currentText()
445
        actual_laser_channel = self._seq_gen_logic.set_laser_channel(laser_channel)
446
        index = self._pe.gen_laserchannel_ComboBox.findText(actual_laser_channel)
447
        self._pe.gen_laserchannel_ComboBox.setCurrentIndex(index)
448
        self._pe.gen_laserchannel_ComboBox.blockSignals(False)
449
        self._update_current_pulse_block()
450
        self._update_current_pulse_block_ensemble()
451
452
    def generator_activation_config_changed(self):
453
        """
454
        Is called whenever the channel config for the sequence generation has changed in the GUI
455
        """
456
        self._pe.block_editor_TableWidget.blockSignals(True)
457
        # retreive GUI inputs
458
        new_config_name = self._pe.gen_activation_config_ComboBox.currentText()
459
        new_channel_config = self._pulsed_meas_logic.get_pulser_constraints()['activation_config'][new_config_name]
460
        # set chosen config in sequence generator logic
461
        self._seq_gen_logic.set_activation_config(new_channel_config)
462
        # set display new config alongside with number of channels
463
        display_str = ''
464
        for chnl in new_channel_config:
465
            display_str += chnl + ' | '
466
        display_str = display_str[:-3]
467
        self._pe.gen_activation_config_LineEdit.setText(display_str)
468
        self._pe.gen_analog_channels_SpinBox.setValue(self._seq_gen_logic.analog_channels)
469
        self._pe.gen_digital_channels_SpinBox.setValue(self._seq_gen_logic.digital_channels)
470
        # and update the laser channel combobx
471
        self._pe.gen_laserchannel_ComboBox.blockSignals(True)
472
        self._pe.gen_laserchannel_ComboBox.clear()
473
        self._pe.gen_laserchannel_ComboBox.addItems(new_channel_config)
474
        # set the laser channel in the ComboBox
475
        laser_channel = self._seq_gen_logic.laser_channel
476
        index = self._pe.gen_laserchannel_ComboBox.findText(laser_channel)
477
        self._pe.gen_laserchannel_ComboBox.setCurrentIndex(index)
478
        self._pe.gen_laserchannel_ComboBox.blockSignals(False)
479
480
        # reshape block editor table
481
        self._set_block_editor_columns()
482
483
        self._pe.block_editor_TableWidget.blockSignals(False)
484
485
        self._update_current_pulse_block()
486
        self._update_current_pulse_block_ensemble()
487
488
    def generate_pulse_block_object(self, pb_name, block_matrix):
489
        """ Generates from an given table block_matrix a block_object.
490
491
        @param pb_name: string, Name of the created Pulse_Block Object
492
        @param block_matrix: structured np.array, matrix, in which the
493
                             construction plan for Pulse_Block_Element objects
494
                             are displayed as rows.
495
496
        Three internal dict where used, to get all the needed information about
497
        how parameters, functions are defined (_add_pbe_param,func_config and
498
        _unit_prefix).
499
        The dict cfg_param_pbe (configuration parameter declaration dict for
500
        Pulse_Block_Element) stores how the objects are appearing in the GUI.
501
        This dict enables the proper access to the desired element in the GUI.
502
        """
503
504
        # list of all the pulse block element objects
505
        pbe_obj_list = [None] * len(block_matrix)
506
507
        # seperate digital and analogue channels
508
        activation_config = self._seq_gen_logic.activation_config
509
        analog_chnl_names = [chnl for chnl in activation_config if 'a_ch' in chnl]
510
        digital_chnl_names = [chnl for chnl in activation_config if 'd_ch' in chnl]
511
512
        for row_index, row in enumerate(block_matrix):
513
            # check how length is displayed and convert it to bins:
514
            length_time = row[self._cfg_param_pbe['length']]
515
            init_length_bins = int(np.round(length_time * self._seq_gen_logic.sample_rate))
516
517
            # check how increment is displayed and convert it to bins:
518
            increment_time = row[self._cfg_param_pbe['increment']]
519
            increment_bins = int(np.round(increment_time * self._seq_gen_logic.sample_rate))
520
521
            # get the dict with all possible functions and their parameters:
522
            func_dict = self._seq_gen_logic.func_config
523
524
            # get the proper pulse_functions and its parameters:
525
            pulse_function = [None] * self._seq_gen_logic.analog_channels
526
            parameter_list = [None] * self._seq_gen_logic.analog_channels
527
            for num in range(self._seq_gen_logic.analog_channels):
528
                # get the number of the analogue channel according to the channel activation_config
529
                a_chnl_number = analog_chnl_names[num].split('ch')[-1]
530
                pulse_function[num] = row[self._cfg_param_pbe['function_' + a_chnl_number]].decode(
531
                    'UTF-8')
532
533
                # search for this function in the dictionary and get all the
534
                # parameter with their names in list:
535
                param_dict = func_dict[pulse_function[num]]
536
                parameters = {}
537
                for entry in list(param_dict):
538
                    # Obtain how the value is displayed in the table:
539
                    param_value = row[self._cfg_param_pbe[entry + '_' + a_chnl_number]]
540
                    parameters[entry] = param_value
541
                parameter_list[num] = parameters
542
543
            digital_high = [None] * self._seq_gen_logic.digital_channels
544
            for num in range(self._seq_gen_logic.digital_channels):
545
                # get the number of the digital channel according to the channel activation_config
546
                d_chnl_number = digital_chnl_names[num].split('ch')[-1]
547
                digital_high[num] = bool(row[self._cfg_param_pbe['digital_' + d_chnl_number]])
548
549
            use_as_tick = bool(row[self._cfg_param_pbe['use']])
550
551
            # create here actually the object with all the obtained information:
552
            pbe_obj_list[row_index] = Pulse_Block_Element(init_length_bins=init_length_bins,
553
                                                          increment_bins=increment_bins,
554
                                                          pulse_function=pulse_function,
555
                                                          digital_high=digital_high,
556
                                                          parameters=parameter_list,
557
                                                          use_as_tick=use_as_tick)
558
559
        pb_obj = Pulse_Block(pb_name, pbe_obj_list)
560
        # save block
561
        self._seq_gen_logic.save_block(pb_name, pb_obj)
562
563
564
    def generate_pulse_block_ensemble_object(self, ensemble_name, ensemble_matrix, rotating_frame=True):
565
        """
566
        Generates from an given table ensemble_matrix a ensemble object.
567
568
        @param str ensemble_name: Name of the created Pulse_Block_Ensemble object
569
        @param np.array ensemble_matrix: structured 2D np.array, matrix, in which the construction
570
                                         plan for Pulse_Block objects are displayed as rows.
571
        @param str laser_channel: the channel controlling the laser
572
        @param bool rotating_frame: optional, whether the phase preservation is mentained
573
                                    throughout the sequence.
574
575
        The dict cfg_param_pb (configuration parameter declaration dict for Pulse_Block) stores how
576
        the objects are related to each other in a sequencial way. That relationship is used in the
577
        GUI, where the parameters appear in columns.
578
        This dict enables the proper access to the desired element in the GUI.
579
        """
580
581
        # list of all the pulse block element objects
582
        pb_obj_list = [None] * len(ensemble_matrix)
583
584
        for row_index, row in enumerate(ensemble_matrix):
585
            pulse_block_name = row[self._cfg_param_pb['pulse_block']].decode('UTF-8')
586
            pulse_block_reps = row[self._cfg_param_pb['repetition']]
587
            # Fetch previously saved block object
588
            block = self._seq_gen_logic.get_pulse_block(pulse_block_name)
589
            # Append block object along with repetitions to the block list
590
            pb_obj_list[row_index] = (block, pulse_block_reps)
591
592
        # Create the Pulse_Block_Ensemble object
593
        pulse_block_ensemble = Pulse_Block_Ensemble(name=ensemble_name, block_list=pb_obj_list,
594
                                                    activation_config=self._seq_gen_logic.activation_config,
595
                                                    sample_rate=self._seq_gen_logic.sample_rate,
596
                                                    laser_channel=self._seq_gen_logic.laser_channel,
597
                                                    rotating_frame=rotating_frame)
598
        # save ensemble
599
        self._seq_gen_logic.save_ensemble(ensemble_name, pulse_block_ensemble)
600
601
602
    # -------------------------------------------------------------------------
603
    #           Methods for the Pulse Block Editor
604
    # -------------------------------------------------------------------------
605
606
    def get_element_in_block_table(self, row, column):
607
        """ Simplified wrapper function to get the data from a specific cell
608
            in the block table.
609
610
        @param int row: row index
611
        @param int column: column index
612
        @return: the value of the corresponding cell, which can be a string, a
613
                 float or an integer. Remember that the checkbox state
614
                 unchecked corresponds to 0 and check to 2. That is Qt
615
                 convention.
616
617
        Note that the order of the arguments in this function (first row index
618
        and then column index) was taken from the Qt convention.
619
        """
620
        tab = self._pe.block_editor_TableWidget
621
        # Get from the corresponding delegate the data access model
622
        access = tab.itemDelegateForColumn(column).model_data_access
623
        data = tab.model().index(row, column).data(access)
624
        # check whether the value has to be normalized to SI values.
625
        if hasattr(tab.itemDelegateForColumn(column),'get_unit_prefix'):
626
            unit_prefix = tab.itemDelegateForColumn(column).get_unit_prefix()
627
            # access the method defined in base for unit prefix:
628
            return data * units.get_unit_prefix_dict()[unit_prefix]
629
        return data
630
631
    def set_element_in_block_table(self, row, column, value):
632
        """ Simplified wrapper function to set the data to a specific cell
633
            in the block table.
634
635
        @param int row: row index
636
        @param int column: column index
637
638
        Note that the order of the arguments in this function (first row index
639
        and then column index) was taken from the Qt convention.
640
        A type check will be performed for the passed value argument. If the
641
        type does not correspond to the delegate, then the value will not be
642
        changed. You have to ensure that
643
        """
644
        tab = self._pe.block_editor_TableWidget
645
        model = tab.model()
646
        access = tab.itemDelegateForColumn(column).model_data_access
647
        data = tab.model().index(row, column).data(access)
648
649
        if type(data) == type(value):
650
            # check whether the SI value has to be adjusted according to the
651
            # desired unit prefix of the current viewbox:
652
            if hasattr(tab.itemDelegateForColumn(column),'get_unit_prefix'):
653
                unit_prefix = tab.itemDelegateForColumn(column).get_unit_prefix()
654
                # access the method defined in base for unit prefix:
655
                value = value / units.get_unit_prefix_dict()[unit_prefix]
656
            model.setData(model.index(row,column), value, access)
657
        else:
658
            self.log.warning('The cell ({0},{1}) in block table could not be '
659
                        'assigned with the value="{2}", since the type "{3}" '
660
                        'of the cell from the delegated type differs from '
661
                        '"{4}" of the value!\nPrevious value will be '
662
                        'kept.'.format(row, column, value, type(data),
663
                                       type(value)))
664
665
666
    def _update_current_pulse_block(self):
667
        """ Update the current Pulse Block Info in the display. """
668
        length = 0.0 # in ns
669
        bin_length = 0
670
        col_ind = self._cfg_param_pbe['length']
671
672
        laser_channel = self._seq_gen_logic.laser_channel
673
        num_laser_ch = 0
674
675
        # Simple search routine:
676
        if 'a' in laser_channel:
677
            # extract with regular expression module the number from the
678
            # string:
679
            num = re.findall('\d+', laser_channel)
680
            laser_column = self._cfg_param_pbe['function_'+str(num[0])]
681
        elif 'd' in laser_channel:
682
            num = re.findall('\d+', laser_channel)
683
            laser_column = self._cfg_param_pbe['digital_'+str(num[0])]
684
        else:
685
            return
686
687
        # This bool is to prevent two consecutive laser on states to be counted as two laser pulses.
688
        laser_on = False
689
        # Iterate over the editor rows
690
        for row_ind in range(self._pe.block_editor_TableWidget.rowCount()):
691
            curr_length = self.get_element_in_block_table(row_ind, col_ind)
692
            curr_bin_length = int(np.round(curr_length*(self._seq_gen_logic.sample_rate)))
693
            length += curr_length
694
            bin_length += curr_bin_length
695
696
            laser_val =self.get_element_in_block_table(row_ind, laser_column)
697
            if laser_val in ('DC', 2):
698
                if not laser_on:
699
                    num_laser_ch += 1
700
                    laser_on = True
701
            else:
702
                laser_on = False
703
704
        #FIXME: The display unit will be later on set in the settings, so that
705
        #       one can choose which units are suiting the best. For now on it
706
        #       will be fixed to microns.
707
708
        self._pe.curr_block_length_DSpinBox.setValue(length*1e6) # in microns
709
        self._pe.curr_block_bins_SpinBox.setValue(bin_length)
710
        self._pe.curr_block_laserpulses_SpinBox.setValue(num_laser_ch)
711
712 View Code Duplication
    def get_pulse_block_table(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
713
        """ Convert block table data to numpy array.
714
715
        @return: np.array[rows][columns] which has a structure, i.e. strings
716
                 integer and float values are represented by this array.
717
                 The structure was taken according to the init table itself.
718
        """
719
720
        tab = self._pe.block_editor_TableWidget
721
722
        # create a structure for the output numpy array:
723
        structure = ''
724
        for column in range(tab.columnCount()):
725
            elem = self.get_element_in_block_table(0,column)
726
            if type(elem) is str:
727
                structure = structure + '|S20, '
728
            elif type(elem) is int:
729
                structure = structure + '|i4, '
730
            elif type(elem) is float:
731
                structure = structure + '|f4, '
732
            else:
733
                self.log.error('Type definition not found in the block table.'
734
                            '\nType is neither a string, integer or float. '
735
                            'Include that type in the get_pulse_block_table '
736
                            'method!')
737
738
        # remove the last two elements since these are a comma and a space:
739
        structure = structure[:-2]
740
        table = np.zeros(tab.rowCount(), dtype=structure)
741
742
        # fill the table:
743
        for column in range(tab.columnCount()):
744
            for row in range(tab.rowCount()):
745
                table[row][column] = self.get_element_in_block_table(row, column)
746
747
        return table
748
749
    def load_pulse_block_clicked(self, block_name=None):
750
        """ Loads the current selected Pulse_Block object from the logic into
751
            the editor or a specified Pulse_Block with name block_name.
752
753
        @param str block_name: optional, name of the Pulse_Block object, which
754
                               should be loaded in the GUI Block Organizer. If
755
                               no name passed, the current Pulse_Block from the
756
                               Logic is taken to be loaded.
757
758
        Unfortuanetly this method needs to know how Pulse_Block objects are
759
        looking like and cannot be that general.
760
        """
761
762
        # NOTE: This method will be connected to the CLICK event of a
763
        #       QPushButton, which passes as an optional argument a a bool value
764
        #       depending on the checked state of the QPushButton. Therefore
765
        #       the passed boolean value has to be handled in addition!
766
767
        if (block_name is not None) and (type(block_name) is not bool):
768
            current_block_name = block_name
769
        else:
770
            current_block_name = self._pe.saved_blocks_ComboBox.currentText()
771
772
        block = self._seq_gen_logic.get_pulse_block(current_block_name, set_as_current_block=True)
773
774
        # of no object was found then block has reference to None
775
        if block is None:
776
            return -1
777
778
        # get the number of currently set analogue and digital channels from the logic.
779
        num_analog_chnl = self._seq_gen_logic.analog_channels
780
        num_digital_chnl = self._seq_gen_logic.digital_channels
781
        # get currently active activation_config and all possible configs.
782
        activation_config = self._pulsed_meas_logic.get_pulser_constraints()['activation_config']
783
        current_config = self._seq_gen_logic.activation_config
784
785
        # check if the currently set activation_config has the same number of channels as
786
        # the block object to be loaded. If this is not the case, change the config
787
        # to something suitable and inform the user.
788
        if num_analog_chnl != block.analog_channels or num_digital_chnl != block.digital_channels:
789
            # find the first valid activation config
790
            config_to_set = None
791
            for config in activation_config:
792
                num_analog = len([chnl for chnl in activation_config[config] if 'a_ch' in chnl])
793
                num_digital = len([chnl for chnl in activation_config[config] if 'd_ch' in chnl])
794
                if num_analog == block.analog_channels and num_digital == block.digital_channels:
795
                    config_to_set = config
796
                    break
797
            if config_to_set is None:
798
                self.log.error('Mismatch in number of channels between block '
799
                        'to load and chosen activation_config. Need {0} '
800
                        'digital and {1} analogue channels. Could not find a '
801
                        'matching activation_config.'.format(
802
                            block.digital_channels, block.analog_channels))
803
                return -1
804
            # find index of the config inside the ComboBox
805
            index_to_set = self._pe.gen_activation_config_ComboBox.findText(config_to_set)
806
            self._pe.gen_activation_config_ComboBox.setCurrentIndex(index_to_set)
807
            self.log.error('Mismatch in number of channels between block to '
808
                    'load and chosen activation_config. Need {0} digital '
809
                    'and {1} analogue channels. The following '
810
                    'activation_config was chosen: "{2}"'.format(
811
                        block.digital_channels,
812
                        block.analog_channels,
813
                        config_to_set))
814
815
            # get currently active activation_config.
816
            current_config = activation_config[config_to_set]
817
818
        # seperate active analog and digital channels in lists
819
        active_analog = [chnl for chnl in current_config if 'a_ch' in chnl]
820
        active_digital = [chnl for chnl in current_config if 'd_ch' in chnl]
821
822
        self.block_editor_clear_table()  # clear table
823
        rows = len(block.element_list)  # get amout of rows needed for display
824
825
        # configuration dict from the logic:
826
        block_config_dict = self._cfg_param_pbe
827
828
        self.block_editor_add_row_after_last(rows - 1)  # since one is already present
829
830
        for row_index, elem in enumerate(block.element_list):
831
832
            # set at first all digital channels:
833
            for digital_ch in range(elem.digital_channels):
834
                column = block_config_dict['digital_' + active_digital[digital_ch].split('ch')[-1]]
835
                value = elem.digital_high[digital_ch]
836
                if value:
837
                    value = 2
838
                else:
839
                    value = 0
840
                self.set_element_in_block_table(row_index, column, value)
841
842
            # now set all parameters for the analog channels:
843
            for analog_ch in range(elem.analog_channels):
844
                # the function text:
845
                column = block_config_dict['function_' + active_analog[analog_ch].split('ch')[-1]]
846
                func_text = elem.pulse_function[analog_ch]
847
                self.set_element_in_block_table(row_index, column, func_text)
848
849
                # then the parameter dictionary:
850
                parameter_dict = elem.parameters[analog_ch]
851
                for parameter in parameter_dict:
852
                    column = block_config_dict[parameter + '_' + active_analog[analog_ch].split('ch')[-1]]
853
                    value = np.float(parameter_dict[parameter])
854
                    self.set_element_in_block_table(row_index, column, value)
855
856
            # FIXME: that is not really general, since the name 'use_as_tick' is
857
            #       directly taken. That must be more general! Right now it is
858
            #       hard to make it in a general way.
859
860
            # now set use as tick parameter:
861
            column = block_config_dict['use']
862
            value = elem.use_as_tick
863
            # the ckeckbox has a special input value, it is 0, 1 or 2. (tri-state)
864
            if value:
865
                value = 2
866
            else:
867
                value = 0
868
            self.set_element_in_block_table(row_index, column, value)
869
870
            # and set the init_length_bins:
871
            column = block_config_dict['length']
872
            value = elem.init_length_bins / (self._seq_gen_logic.sample_rate)
873
            # the setter method will handle the proper unit for that value!
874
            # Just make sure to pass to the function the value in SI units!
875
            self.set_element_in_block_table(row_index, column, value)
876
877
            # and set the increment parameter
878
            column = block_config_dict['increment']
879
            value = elem.increment_bins / (self._seq_gen_logic.sample_rate)
880
            # the setter method will handle the proper unit for that value!
881
            # Just make sure to pass to the function the value in SI units!
882
            self.set_element_in_block_table(row_index, column, value)
883
884
        self._pe.curr_block_name_LineEdit.setText(current_block_name)
885
886
887
    def delete_pulse_block_clicked(self):
888
        """
889
        Actions to perform when the delete button in the block editor is clicked
890
        """
891
        name = self._pe.saved_blocks_ComboBox.currentText()
892
        self._seq_gen_logic.delete_block(name)
893
894
        # update at first the comboboxes within the organizer table and block
895
        # all the signals which might cause an error, because during the update
896
        # there is access on the table and that happens row by row, i.e. not all
897
        # cells are updated if the first signal is emited and there might be
898
        # some cells which are basically empty, which would cause an error in
899
        # the display of the current ensemble configuration.
900
        self._pe.block_organizer_TableWidget.blockSignals(True)
901
        self.update_block_organizer_list()
902
        self._pe.block_organizer_TableWidget.blockSignals(False)
903
        # after everything is fine, perform the update:
904
        self._update_current_pulse_block_ensemble()
905
        return
906
907
    def generate_pulse_block_clicked(self):
908
        """ Generate a Pulse_Block object."""
909
        objectname = self._pe.curr_block_name_LineEdit.text()
910
        if objectname == '':
911
            self.log.warning('No Name for Pulse_Block specified. Generation '
912
                        'aborted!')
913
            return
914
        self.generate_pulse_block_object(objectname, self.get_pulse_block_table())
915
916
        # update at first the comboboxes within the organizer table and block
917
        # all the signals which might cause an error, because during the update
918
        # there is access on the table and that happens row by row, i.e. not all
919
        # cells are updated if the first signal is emited and there might be
920
        # some cells which are basically empty, which would cause an error in
921
        # the display of the current ensemble configuration.
922
        self._pe.block_organizer_TableWidget.blockSignals(True)
923
        self.update_block_organizer_list()
924
        self._pe.block_organizer_TableWidget.blockSignals(False)
925
        # after everything is fine, perform the update:
926
        self._update_current_pulse_block_ensemble()
927
928
    def _determine_needed_parameters(self):
929
        """ Determine the maximal number of needed parameters for desired functions.
930
931
        @return ('<biggest_func_name>, number_of_parameters)
932
        """
933
934
        # FIXME: Reimplement this function such that it will return the
935
        #       parameters of all needed functions and not take only the
936
        #       parameters of the biggest function. Then the return should be
937
        #       not the biggest function, but a set of all the needed
938
        #       parameters which is obtained from get_func_config()!
939
940
941
        curr_func_list = self.get_current_function_list()
942
        complete_func_config = self._seq_gen_logic.func_config
943
944
        num_max_param = 0
945
        biggest_func = ''
946
947
        for func in curr_func_list:
948
            if num_max_param < len(complete_func_config[func]):
949
                num_max_param = len(complete_func_config[func])
950
                biggest_func = func
951
952
        return (num_max_param, biggest_func)
953
954
    def _set_block_editor_columns(self):
955
        """ General function which creates the needed columns in Pulse Block
956
            Editor according to the currently set channel activation_config.
957
958
        Retreives the curently set activation_config from the sequence generator logic.
959
        Every time this function is executed all the table entries are erased
960
        and created again to prevent wrong delegation.
961
        """
962
963
        # get the currently chosen activation_config
964
        # config_name = self._seq_gen_logic.current_activation_config_name
965
        channel_active_config = self._seq_gen_logic.activation_config
966
967
        self._pe.block_editor_TableWidget.blockSignals(True)
968
969
        # Determine the function with the most parameters. Use also that
970
        # function as a construction plan to create all the needed columns for
971
        # the parameters.
972
        (num_max_param, biggest_func) = self._determine_needed_parameters()
973
974
        # Erase the delegate from the column, pass a None reference:
975
        for column in range(self._pe.block_editor_TableWidget.columnCount()):
976
            self._pe.block_editor_TableWidget.setItemDelegateForColumn(column, None)
977
978
        # clear the number of columns:
979
        self._pe.block_editor_TableWidget.setColumnCount(0)
980
981
        # total number of analog and digital channels:
982
        num_of_columns = 0
983
        for channel in channel_active_config:
984
            if 'd_ch' in channel:
985
                num_of_columns += 1
986
            elif 'a_ch' in channel:
987
                num_of_columns += num_max_param + 1
988
989
        self._pe.block_editor_TableWidget.setColumnCount(num_of_columns)
990
991
        column_count = 0
992
        for channel in channel_active_config:
993
            if 'a_ch' in channel:
994
                self._pe.block_editor_TableWidget.setHorizontalHeaderItem(column_count,
995
                    QtWidgets.QTableWidgetItem())
996
                self._pe.block_editor_TableWidget.horizontalHeaderItem(column_count).setText(
997
                    'ACh{0}\nfunction'.format(channel.split('ch')[-1]))
998
                self._pe.block_editor_TableWidget.setColumnWidth(column_count, 70)
999
1000
                item_dict = {}
1001
                item_dict['get_list_method'] = self.get_current_function_list
1002
1003
                delegate = ComboBoxDelegate(self._pe.block_editor_TableWidget, item_dict)
1004
                self._pe.block_editor_TableWidget.setItemDelegateForColumn(column_count, delegate)
1005
1006
                column_count += 1
1007
1008
                # fill here all parameter columns for the current analogue channel
1009
                for parameter in self._seq_gen_logic.func_config[biggest_func]:
1010
                    # initial block:
1011
1012
                    item_dict = self._seq_gen_logic.func_config[biggest_func][parameter]
1013
1014
                    unit_text = item_dict['unit_prefix'] + item_dict['unit']
1015
1016
                    self._pe.block_editor_TableWidget.setHorizontalHeaderItem(
1017
                        column_count, QtWidgets.QTableWidgetItem())
1018
                    self._pe.block_editor_TableWidget.horizontalHeaderItem(column_count).setText(
1019
                        'ACh{0}\n{1} ({2})'.format(channel.split('ch')[-1], parameter,
1020
                                                     unit_text))
1021
                    self._pe.block_editor_TableWidget.setColumnWidth(column_count, 100)
1022
1023
                    # add the new properties to the whole column through delegate:
1024
1025
                    # extract the classname from the _param_a_ch list to be able to deligate:
1026
                    delegate = DoubleSpinBoxDelegate(self._pe.block_editor_TableWidget, item_dict)
1027
                    self._pe.block_editor_TableWidget.setItemDelegateForColumn(column_count,
1028
                        delegate)
1029
                    column_count += 1
1030
1031
            elif 'd_ch' in channel:
1032
                self._pe.block_editor_TableWidget.setHorizontalHeaderItem(column_count,
1033
                    QtWidgets.QTableWidgetItem())
1034
                self._pe.block_editor_TableWidget.horizontalHeaderItem(column_count).setText(
1035
                    'DCh{0}'.format(channel.split('ch')[-1]))
1036
                self._pe.block_editor_TableWidget.setColumnWidth(column_count, 40)
1037
1038
                # itemlist for checkbox
1039
                item_dict = {}
1040
                item_dict['init_val'] = QtCore.Qt.Unchecked
1041
                checkDelegate = CheckBoxDelegate(self._pe.block_editor_TableWidget, item_dict)
1042
                self._pe.block_editor_TableWidget.setItemDelegateForColumn(column_count, checkDelegate)
1043
1044
                column_count += 1
1045
1046
        # Insert the additional parameters given in the add_pbe_param dictionary (length etc.)
1047
        for column, parameter in enumerate(self._add_pbe_param):
1048
            # add the new properties to the whole column through delegate:
1049
            item_dict = self._add_pbe_param[parameter]
1050
1051
            if 'unit_prefix' in item_dict.keys():
1052
                unit_text = item_dict['unit_prefix'] + item_dict['unit']
1053
            else:
1054
                unit_text = item_dict['unit']
1055
1056
            self._pe.block_editor_TableWidget.insertColumn(num_of_columns + column)
1057
            self._pe.block_editor_TableWidget.setHorizontalHeaderItem(num_of_columns + column,
1058
                                                                      QtWidgets.QTableWidgetItem())
1059
            self._pe.block_editor_TableWidget.horizontalHeaderItem(
1060
                num_of_columns + column).setText('{0} ({1})'.format(parameter, unit_text))
1061
            self._pe.block_editor_TableWidget.setColumnWidth(num_of_columns + column, 90)
1062
1063
            # Use only DoubleSpinBox as delegate:
1064
            if item_dict['unit'] == 'bool':
1065
                delegate = CheckBoxDelegate(self._pe.block_editor_TableWidget, item_dict)
1066
            else:
1067
                delegate = DoubleSpinBoxDelegate(self._pe.block_editor_TableWidget, item_dict)
1068
            self._pe.block_editor_TableWidget.setItemDelegateForColumn(num_of_columns + column,
1069
                                                                       delegate)
1070
1071
            # initialize the whole row with default values:
1072
            for row_num in range(self._pe.block_editor_TableWidget.rowCount()):
1073
                # get the model, here are the data stored:
1074
                model = self._pe.block_editor_TableWidget.model()
1075
                # get the corresponding index of the current element:
1076
                index = model.index(row_num, num_of_columns + column)
1077
                # get the initial values of the delegate class which was
1078
                # uses for this column:
1079
                ini_values = delegate.get_initial_value()
1080
                # set initial values:
1081
                model.setData(index, ini_values[0], ini_values[1])
1082
1083
        self.initialize_cells_block_editor(0, self._pe.block_editor_TableWidget.rowCount())
1084
1085
        self.set_cfg_param_pbe()
1086
        self._pe.block_editor_TableWidget.blockSignals(False)
1087
        self._update_current_pulse_block()
1088
1089 View Code Duplication
    def initialize_cells_block_editor(self, start_row, stop_row=None,
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1090
                                      start_col=None, stop_col=None):
1091
1092
        """ Initialize the desired cells in the block editor table.
1093
1094
        @param start_row: int, index of the row, where the initialization
1095
                          should start
1096
        @param stop_row: int, optional, index of the row, where the
1097
                         initalization should end.
1098
        @param start_col: int, optional, index of the column where the
1099
                          initialization should start
1100
        @param stop_col: int, optional, index of the column, where the
1101
                         initalization should end.
1102
1103
        With this function it is possible to reinitialize specific elements or
1104
        part of a row or even the whole row. If start_row is set to 0 the whole
1105
        row is going to be initialzed to the default value.
1106
        """
1107
1108
        if stop_row is None:
1109
            stop_row = start_row + 1
1110
1111
        if start_col is None:
1112
            start_col = 0
1113
1114
        if stop_col is None:
1115
            stop_col = self._pe.block_editor_TableWidget.columnCount()
1116
1117
        for col_num in range(start_col, stop_col):
1118
1119
            for row_num in range(start_row, stop_row):
1120
                # get the model, here are the data stored:
1121
                model = self._pe.block_editor_TableWidget.model()
1122
                # get the corresponding index of the current element:
1123
                index = model.index(row_num, col_num)
1124
                # get the initial values of the delegate class which was
1125
                # uses for this column:
1126
                ini_values = self._pe.block_editor_TableWidget.itemDelegateForColumn(
1127
                    col_num).get_initial_value()
1128
                # set initial values:
1129
                model.setData(index, ini_values[0], ini_values[1])
1130
1131
    # -------------------------------------------------------------------------
1132
    #           Methods for the Pulse Block Organizer
1133
    # -------------------------------------------------------------------------
1134
1135
    def update_block_organizer_list(self):
1136
        """ If a Pulse_Block object has been deleted, update the list in
1137
            organizer.
1138
        """
1139
1140
        column = 0
1141
        for row in range(self._pe.block_organizer_TableWidget.rowCount()):
1142
            data = self.get_element_in_organizer_table(row, column)
1143
            if data not in self._seq_gen_logic.saved_pulse_blocks:
1144
                self.initialize_cells_block_organizer(start_row=row, stop_row=row+1,
1145
                                                      start_col=column,stop_col=column+1)
1146
1147
    def _update_current_pulse_block_ensemble(self):
1148
        length_mu = 0.0  # in microseconds
1149
        length_bin = 0
1150
        num_laser_pulses = 0
1151
        filesize_bytes = 0
1152
        pulse_block_col = self._cfg_param_pb['pulse_block']
1153
1154
        reps_col = self._cfg_param_pb['repetition']
1155
1156
        if len(self._seq_gen_logic.saved_pulse_blocks) > 0:
1157
            for row_ind in range(self._pe.block_organizer_TableWidget.rowCount()):
1158
                pulse_block_name = self.get_element_in_organizer_table(row_ind, pulse_block_col)
1159
1160
                block_obj = self._seq_gen_logic.get_pulse_block(pulse_block_name)
1161
1162
                reps = self.get_element_in_organizer_table(row_ind, reps_col)
1163
1164
                # Calculate the length via the gaussian summation formula:
1165
                length_bin = int(length_bin + block_obj.init_length_bins * (reps + 1) +
1166
                             ((reps + 1) * ((reps + 1) + 1) / 2) * block_obj.increment_bins)
1167
1168
                # Calculate the number of laser pulses
1169
                num_laser_pulses_block = 0
1170
                if self._seq_gen_logic.laser_channel is None:
1171
                    num_laser_pulses_block = 0
1172
                elif 'd_ch' in self._seq_gen_logic.laser_channel:
1173
                    # determine the laser channel index for the corresponding channel
1174
                    digital_chnl_list = [chnl for chnl in self._seq_gen_logic.activation_config if
1175
                                         'd_ch' in chnl]
1176
                    laser_index = digital_chnl_list.index(self._seq_gen_logic.laser_channel)
1177
                    # Iterate through the elements and count laser on state changes
1178
                    # (no double counting)
1179
                    laser_on = False
1180
                    for elem in block_obj.element_list:
1181
                        if laser_index >= len(elem.digital_high):
1182
                            break
1183
                        if elem.digital_high[laser_index] and not laser_on:
1184
                            num_laser_pulses_block += 1
1185
                            laser_on = True
1186
                        elif not elem.digital_high[laser_index]:
1187
                            laser_on = False
1188
                elif 'a_ch' in self._seq_gen_logic.laser_channel:
1189
                    # determine the laser channel index for the corresponding channel
1190
                    analog_chnl_list = [chnl for chnl in self._seq_gen_logic.activation_config if
1191
                                        'a_ch' in chnl]
1192
                    laser_index = analog_chnl_list.index(self._seq_gen_logic.laser_channel)
1193
                    # Iterate through the elements and count laser on state changes
1194
                    # (no double counting)
1195
                    laser_on = False
1196
                    for elem in block_obj.element_list:
1197
                        if laser_index >= len(elem.pulse_function):
1198
                            break
1199
                        if elem.pulse_function[laser_index] == 'DC' and not laser_on:
1200
                            num_laser_pulses_block += 1
1201
                            laser_on = True
1202
                        elif elem.pulse_function[laser_index] != 'DC':
1203
                            laser_on = False
1204
                num_laser_pulses += num_laser_pulses_block*(reps+1)
1205
1206
1207
            length_mu = (length_bin / self._seq_gen_logic.sample_rate) * 1e6  # in microns
1208
1209
        # get file format to determine the file size in bytes.
1210
        # This is just an estimate since it does not include file headers etc..
1211
        # FIXME: This is just a crude first try to implement this. Improvement required.
1212
        file_format = self._pulsed_meas_logic.get_pulser_constraints()['waveform_format']
1213
        if file_format == 'wfm':
1214
            num_ana_chnl = self._seq_gen_logic.analog_channels
1215
            filesize_bytes = num_ana_chnl * 5 * length_bin
1216
        elif file_format == 'wfmx':
1217
            chnl_config = self._seq_gen_logic.activation_config
1218
            analogue_chnl_num = [int(chnl.split('ch')[1]) for chnl in chnl_config if 'a_ch' in chnl]
1219
            digital_chnl_num= [int(chnl.split('ch')[1]) for chnl in chnl_config if 'd_ch' in chnl]
1220
            for ana_chnl in analogue_chnl_num:
1221
                if (ana_chnl*2-1) in digital_chnl_num or (ana_chnl*2) in digital_chnl_num:
1222
                    filesize_bytes += 5 * length_bin
1223
                else:
1224
                    filesize_bytes += 4 * length_bin
1225
        elif file_format == 'fpga':
1226
            filesize_bytes = length_bin
1227
        else:
1228
            filesize_bytes = 0
1229
1230
        self._pe.curr_ensemble_size_DSpinBox.setValue(filesize_bytes/(1024**2))
1231
        self._pe.curr_ensemble_length_DSpinBox.setValue(length_mu)
1232
        self._pe.curr_ensemble_bins_SpinBox.setValue(length_bin)
1233
        self._pe.curr_ensemble_laserpulses_SpinBox.setValue(num_laser_pulses)
1234
1235
1236
    def get_element_in_organizer_table(self, row, column):
1237
        """ Simplified wrapper function to get the data from a specific cell
1238
            in the organizer table.
1239
1240
        @param int row: row index
1241
        @param int column: column index
1242
        @return: the value of the corresponding cell, which can be a string, a
1243
                 float or an integer. Remember that the checkbox state
1244
                 unchecked corresponds to 0 and check to 2. That is Qt
1245
                 convention.
1246
1247
        Note that the order of the arguments in this function (first row index
1248
        and then column index) was taken from the Qt convention.
1249
        """
1250
1251
        tab = self._pe.block_organizer_TableWidget
1252
1253
        # Get from the corresponding delegate the data access model
1254
        access = tab.itemDelegateForColumn(column).model_data_access
1255
        data = tab.model().index(row, column).data(access)
1256
        return data
1257
1258 View Code Duplication
    def set_element_in_organizer_table(self, row, column, value):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1259
        """ Simplified wrapper function to set the data to a specific cell
1260
            in the block organizer table.
1261
1262
        @param int row: row index
1263
        @param int column: column index
1264
1265
        Note that the order of the arguments in this function (first row index
1266
        and then column index) was taken from the Qt convention.
1267
        A type check will be performed for the passed value argument. If the
1268
        type does not correspond to the delegate, then the value will not be
1269
        changed. You have to ensure that
1270
        """
1271
1272
        tab = self._pe.block_organizer_TableWidget
1273
        model = tab.model()
1274
        access = tab.itemDelegateForColumn(column).model_data_access
1275
        data = tab.model().index(row, column).data(access)
1276
1277
        if type(data) == type(value):
1278
            model.setData(model.index(row,column), value, access)
1279
        else:
1280
            self.log.warning('The cell ({0},{1}) in block organizer table '
1281
                    'could not be assigned with the value="{2}", since the '
1282
                    'type "{3}" of the cell from the delegated type differs '
1283
                    'from "{4}" of the value!\nPrevious value will be '
1284
                    'kept.'.format(row, column, value, type(data),
1285
                        type(value)))
1286
1287 View Code Duplication
    def get_organizer_table(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1288
        """ Convert organizer table data to numpy array.
1289
1290
        @return: np.array[rows][columns] which has a structure, i.e. strings
1291
                 integer and float values are represented by this array.
1292
                 The structure was taken according to the init table itself.
1293
        """
1294
1295
        tab = self._pe.block_organizer_TableWidget
1296
1297
        # create a structure for the output numpy array:
1298
        structure = ''
1299
        for column in range(tab.columnCount()):
1300
            elem = self.get_element_in_organizer_table(0,column)
1301
            if type(elem) is str:
1302
                structure = structure + '|S20, '
1303
            elif type(elem) is int:
1304
                structure = structure + '|i4, '
1305
            elif type(elem) is float:
1306
                structure = structure + '|f4, '
1307
            else:
1308
                self.log.error('Type definition not found in the organizer '
1309
                        'table.'
1310
                        '\nType is neither a string, integer or float. '
1311
                        'Include that type in the get_organizer_table '
1312
                        'method!')
1313
1314
        # remove the last two elements since these are a comma and a space:
1315
        structure = structure[:-2]
1316
        table = np.zeros(tab.rowCount(), dtype=structure)
1317
1318
        # fill the table:
1319
        for column in range(tab.columnCount()):
1320
            for row in range(tab.rowCount()):
1321
                table[row][column] = self.get_element_in_organizer_table(row, column)
1322
1323
        return table
1324
1325 View Code Duplication
    def block_editor_add_row_before_selected(self, insert_rows=1):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1326
        """ Add row before selected element. """
1327
1328
        self._pe.block_editor_TableWidget.blockSignals(True)
1329
1330
        selected_row = self._pe.block_editor_TableWidget.currentRow()
1331
1332
        # the signal passes a boolean value, which overwrites the insert_rows
1333
        # parameter. Check that here and use the actual default value:
1334
        if type(insert_rows) is bool:
1335
            insert_rows = 1
1336
1337
        for rows in range(insert_rows):
1338
            self._pe.block_editor_TableWidget.insertRow(selected_row)
1339
        self.initialize_cells_block_editor(start_row=selected_row,
1340
                                           stop_row=selected_row + insert_rows)
1341
1342
        self._pe.block_editor_TableWidget.blockSignals(False)
1343
1344 View Code Duplication
    def block_editor_add_row_after_last(self, insert_rows=1):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1345
        """ Add row after last row in the block editor. """
1346
1347
        self._pe.block_editor_TableWidget.blockSignals(True)
1348
1349
        # the signal passes a boolean value, which overwrites the insert_rows
1350
        # parameter. Check that here and use the actual default value:
1351
        if type(insert_rows) is bool:
1352
            insert_rows = 1
1353
1354
        number_of_rows = self._pe.block_editor_TableWidget.rowCount()
1355
1356
        self._pe.block_editor_TableWidget.setRowCount(
1357
            number_of_rows + insert_rows)
1358
        self.initialize_cells_block_editor(start_row=number_of_rows,
1359
                                           stop_row=number_of_rows + insert_rows)
1360
1361
        self._pe.block_editor_TableWidget.blockSignals(False)
1362
1363
    def block_editor_delete_row_selected(self):
1364
        """ Delete row of selected element. """
1365
1366
        # get the row number of the selected item(s). That will return the
1367
        # lowest selected row
1368
        row_to_remove = self._pe.block_editor_TableWidget.currentRow()
1369
        self._pe.block_editor_TableWidget.removeRow(row_to_remove)
1370
1371
    def block_editor_delete_row_last(self):
1372
        """ Delete the last row in the block editor. """
1373
1374
        number_of_rows = self._pe.block_editor_TableWidget.rowCount()
1375
        # remember, the row index is started to count from 0 and not from 1,
1376
        # therefore one has to reduce the value by 1:
1377
        self._pe.block_editor_TableWidget.removeRow(number_of_rows - 1)
1378
1379
    def block_editor_clear_table(self):
1380
        """ Delete all rows in the block editor table. """
1381
1382
        self._pe.block_editor_TableWidget.blockSignals(True)
1383
1384
        self._pe.block_editor_TableWidget.setRowCount(1)
1385
        self._pe.block_editor_TableWidget.clearContents()
1386
1387
        self.initialize_cells_block_editor(start_row=0)
1388
        self._pe.block_editor_TableWidget.blockSignals(False)
1389
1390
    def load_pulse_block_ensemble_clicked(self, ensemble_name=None):
1391
        """ Loads the current selected Pulse_Block_Ensemble object from the
1392
            logic into the editor or a specified object with name ensemble_name.
1393
1394
        @param str ensemble_name: optional, name of the Pulse_Block_Element
1395
                                  object, which should be loaded in the GUI
1396
                                  Block Organizer. If no name passed, the
1397
                                  current Pulse_Block_Ensemble from the Logic is
1398
                                  taken to be loaded.
1399
1400
        Unfortuanetly this method needs to know how Pulse_Block_Ensemble objects
1401
        are looking like and cannot be that general.
1402
        """
1403
1404
        # NOTE: This method will be connected to the CLICK event of a
1405
        #       QPushButton, which passes as an optional argument as a bool
1406
        #       value depending on the checked state of the QPushButton. The
1407
        #       passed boolean value has to be handled in addition!
1408
1409
        if (ensemble_name is not None) and (type(ensemble_name) is not bool):
1410
            current_ensemble_name = ensemble_name
1411
        else:
1412
            current_ensemble_name = self._pe.saved_ensembles_ComboBox.currentText()
1413
1414
        # get the ensemble object and set as current ensemble
1415
        ensemble = self._seq_gen_logic.get_pulse_block_ensemble(current_ensemble_name,
1416
                                                    set_as_current_ensemble=True)
1417
1418
        # Check whether an ensemble is found, otherwise there will be None:
1419
        if ensemble is None:
1420
            return
1421
1422
        # set the activation_config to the one defined in the loaded ensemble
1423
        avail_configs = self._pulsed_meas_logic.get_pulser_constraints()['activation_config']
1424
        current_activation_config = self._seq_gen_logic.activation_config
1425
        activation_config_to_set = ensemble.activation_config
1426
        config_name_to_set = None
1427
        if current_activation_config != activation_config_to_set:
1428
            for config in avail_configs:
1429
                if activation_config_to_set == avail_configs[config]:
1430
                    config_name_to_set = config
1431
                    break
1432
            if config_name_to_set is not None:
1433
                index = self._pe.gen_activation_config_ComboBox.findText(config_name_to_set)
1434
                self._pe.gen_activation_config_ComboBox.setCurrentIndex(index)
1435
            self.log.info('Current generator channel activation config '
1436
                    'did not match the activation config of the '
1437
                    'Pulse_Block_Ensemble to load. Changed config to "{0}".'
1438
                    ''.format(config_name_to_set))
1439
1440
        # set the sample rate to the one defined in the loaded ensemble
1441
        current_sample_rate = self._seq_gen_logic.sample_rate
1442
        sample_rate_to_set = ensemble.sample_rate
1443
        if current_sample_rate != sample_rate_to_set:
1444
            self._pe.gen_sample_freq_DSpinBox.setValue(sample_rate_to_set/1e6)
1445
            self.generator_sample_rate_changed()
1446
            self.log.info('Current generator sample rate did not match the '
1447
                    'sample rate of the Pulse_Block_Ensemble to load. '
1448
                    'Changed the sample rate to {0}Hz.'
1449
                    ''.format(sample_rate_to_set))
1450
1451
        # set the laser channel to the one defined in the loaded ensemble
1452
        current_laser_channel = self._seq_gen_logic.laser_channel
1453
        laser_channel_to_set = ensemble.laser_channel
1454
        if current_laser_channel != laser_channel_to_set and laser_channel_to_set is not None:
1455
            index = self._pe.gen_laserchannel_ComboBox.findText(laser_channel_to_set)
1456
            self._pe.gen_laserchannel_ComboBox.setCurrentIndex(index)
1457
            self.log.info('Current generator laser channel did not match the '
1458
                    'laser channel of the Pulse_Block_Ensemble to load. '
1459
                    'Changed the laser channel to "{0}".'
1460
                    ''.format(laser_channel_to_set))
1461
1462
        self.block_organizer_clear_table()  # clear the block organizer table
1463
        rows = len(ensemble.block_list)  # get amout of rows needed for display
1464
1465
        # add as many rows as there are blocks in the ensemble
1466
        # minus 1 because a single row is already present after clear
1467
        self.block_organizer_add_row_after_last(rows - 1)
1468
1469
        # This dictionary has the information which column number describes
1470
        # which object, it is a configuration dict between GUI and logic
1471
        organizer_config_dict = self._cfg_param_pb
1472
1473
        # run through all blocks in the block_elements block_list to fill in the
1474
        # row informations
1475
        for row_index, (pulse_block, repetitions) in enumerate(ensemble.block_list):
1476
            column = organizer_config_dict['pulse_block']
1477
            self.set_element_in_organizer_table(row_index, column, pulse_block.name)
1478
1479
            column = organizer_config_dict['repetition']
1480
            self.set_element_in_organizer_table(row_index, column, int(repetitions))
1481
1482
        # set the ensemble name LineEdit to the current ensemble
1483
        self._pe.curr_ensemble_name_LineEdit.setText(current_ensemble_name)
1484
1485
1486
1487
1488 View Code Duplication
    def block_organizer_add_row_before_selected(self,insert_rows=1):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1489
        """ Add row before selected element. """
1490
        self._pe.block_organizer_TableWidget.blockSignals(True)
1491
        selected_row = self._pe.block_organizer_TableWidget.currentRow()
1492
1493
        # the signal passes a boolean value, which overwrites the insert_rows
1494
        # parameter. Check that here and use the actual default value:
1495
        if type(insert_rows) is bool:
1496
            insert_rows = 1
1497
1498
        for rows in range(insert_rows):
1499
            self._pe.block_organizer_TableWidget.insertRow(selected_row)
1500
1501
        self.initialize_cells_block_organizer(start_row=selected_row)
1502
        self._pe.block_organizer_TableWidget.blockSignals(False)
1503
        self._update_current_pulse_block_ensemble()
1504
1505
1506 View Code Duplication
    def block_organizer_add_row_after_last(self, insert_rows=1):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1507
        """ Add row after last row in the block editor. """
1508
        self._pe.block_organizer_TableWidget.blockSignals(True)
1509
1510
        # the signal of a QPushButton passes an optional boolean value to this
1511
        # method, which overwrites the insert_rows parameter. Check that here
1512
        # and use the actual default value:
1513
        if type(insert_rows) is bool:
1514
            insert_rows = 1
1515
1516
        number_of_rows = self._pe.block_organizer_TableWidget.rowCount()
1517
        self._pe.block_organizer_TableWidget.setRowCount(number_of_rows+insert_rows)
1518
1519
        self.initialize_cells_block_organizer(start_row=number_of_rows,
1520
                                              stop_row=number_of_rows + insert_rows)
1521
1522
        self._pe.block_organizer_TableWidget.blockSignals(False)
1523
        self._update_current_pulse_block_ensemble()
1524
1525
    def block_organizer_delete_row_selected(self):
1526
        """ Delete row of selected element. """
1527
1528
        # get the row number of the selected item(s). That will return the
1529
        # lowest selected row
1530
        row_to_remove = self._pe.block_organizer_TableWidget.currentRow()
1531
        self._pe.block_organizer_TableWidget.removeRow(row_to_remove)
1532
        self._update_current_pulse_block_ensemble()
1533
1534
    def block_organizer_delete_row_last(self):
1535
        """ Delete the last row in the block editor. """
1536
1537
        number_of_rows = self._pe.block_organizer_TableWidget.rowCount()
1538
        # remember, the row index is started to count from 0 and not from 1,
1539
        # therefore one has to reduce the value by 1:
1540
        self._pe.block_organizer_TableWidget.removeRow(number_of_rows-1)
1541
        self._update_current_pulse_block_ensemble()
1542
1543
    def block_organizer_clear_table(self):
1544
        """ Delete all rows in the block editor table. """
1545
1546
1547
        self._pe.block_organizer_TableWidget.blockSignals(True)
1548
        self._pe.block_organizer_TableWidget.setRowCount(1)
1549
        self._pe.block_organizer_TableWidget.clearContents()
1550
        self.initialize_cells_block_organizer(start_row=0)
1551
        self._pe.block_organizer_TableWidget.blockSignals(False)
1552
        self._update_current_pulse_block_ensemble()
1553
1554
    def delete_pulse_block_ensemble_clicked(self):
1555
        """
1556
        Actions to perform when the delete button in the block organizer is clicked
1557
        """
1558
        name = self._pe.saved_ensembles_ComboBox.currentText()
1559
        self._seq_gen_logic.delete_ensemble(name)
1560
        self.update_ensemble_list()
1561
        return
1562
1563
1564
    def generate_pulse_block_ensemble_clicked(self):
1565
        """ Generate a Pulse_Block_ensemble object."""
1566
1567
        objectname = self._pe.curr_ensemble_name_LineEdit.text()
1568
        if objectname == '':
1569
            self.log.warning('No Name for Pulse_Block_Ensemble specified. '
1570
                        'Generation aborted!')
1571
            return
1572
        rotating_frame =  self._pe.curr_ensemble_rot_frame_CheckBox.isChecked()
1573
        self.generate_pulse_block_ensemble_object(objectname, self.get_organizer_table(),
1574
                                                  rotating_frame)
1575
1576
    def set_cfg_param_pbe(self):
1577
        """ Set the parameter configuration of the Pulse_Block_Elements
1578
        according to the current table configuration and updates the dict in
1579
        the logic.
1580
        """
1581
1582
        cfg_param_pbe = OrderedDict()
1583
        for column in range(self._pe.block_editor_TableWidget.columnCount()):
1584
            text = self._pe.block_editor_TableWidget.horizontalHeaderItem(column).text()
1585
            split_text = text.split()
1586
            if 'DCh' in split_text[0]:
1587
                cfg_param_pbe['digital_' + split_text[0][3]] = column
1588
            elif 'ACh' in split_text[0]:
1589
                cfg_param_pbe[split_text[1] + '_' + split_text[0][3]] = column
1590
            else:
1591
                cfg_param_pbe[split_text[0]] = column
1592
1593
        self._cfg_param_pbe = cfg_param_pbe
1594
1595
    def set_cfg_param_pb(self):
1596
        """ Set the parameter configuration of the Pulse_Block according to the
1597
        current table configuration and updates the dict in the logic.
1598
        """
1599
1600
        cfg_param_pb = OrderedDict()
1601
1602
        for column in range(self._pe.block_organizer_TableWidget.columnCount()):
1603
            text = self._pe.block_organizer_TableWidget.horizontalHeaderItem(column).text()
1604
            # split_text = text.split()
1605
            if 'Pulse Block' in text:
1606
                cfg_param_pb['pulse_block'] = column
1607
            elif 'length' in text:
1608
                cfg_param_pb['length'] = column
1609
            elif 'repetition' in text:
1610
                cfg_param_pb['repetition'] = column
1611
            else:
1612
                print('text:',text)
1613
                raise NotImplementedError
1614
        self._cfg_param_pb = cfg_param_pb
1615
1616 View Code Duplication
    def _set_organizer_columns(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1617
1618
        # Erase the delegate from the column, i.e. pass a None reference:
1619
        for column in range(self._pe.block_organizer_TableWidget.columnCount()):
1620
            self._pe.block_organizer_TableWidget.setItemDelegateForColumn(column, None)
1621
1622
        # clear the number of columns:
1623
        self._pe.block_organizer_TableWidget.setColumnCount(0)
1624
1625
        # total number columns in block organizer:
1626
        num_column = 1
1627
        self._pe.block_organizer_TableWidget.setColumnCount(num_column)
1628
1629
        column = 0
1630
        self._pe.block_organizer_TableWidget.setHorizontalHeaderItem(column, QtWidgets.QTableWidgetItem())
1631
        self._pe.block_organizer_TableWidget.horizontalHeaderItem(column).setText('Pulse Block')
1632
        self._pe.block_organizer_TableWidget.setColumnWidth(column, 100)
1633
1634
        item_dict = {}
1635
        item_dict['get_list_method'] = self.get_current_pulse_block_list
1636
1637
        comboDelegate = ComboBoxDelegate(self._pe.block_organizer_TableWidget, item_dict)
1638
        self._pe.block_organizer_TableWidget.setItemDelegateForColumn(column, comboDelegate)
1639
1640
        column = 1
1641
        insert_at_col_pos = column
1642
        for column, parameter in enumerate(self._add_pb_param):
1643
1644
            # add the new properties to the whole column through delegate:
1645
            item_dict = self._add_pb_param[parameter]
1646
1647
            unit_text = item_dict['unit_prefix'] + item_dict['unit']
1648
1649
            self._pe.block_organizer_TableWidget.insertColumn(insert_at_col_pos+column)
1650
            self._pe.block_organizer_TableWidget.setHorizontalHeaderItem(insert_at_col_pos+column, QtWidgets.QTableWidgetItem())
1651
            self._pe.block_organizer_TableWidget.horizontalHeaderItem(insert_at_col_pos+column).setText('{0} ({1})'.format(parameter,unit_text))
1652
            self._pe.block_organizer_TableWidget.setColumnWidth(insert_at_col_pos+column, 80)
1653
1654
            # Use only DoubleSpinBox  as delegate:
1655
            if item_dict['unit'] == 'bool':
1656
                delegate = CheckBoxDelegate(self._pe.block_organizer_TableWidget, item_dict)
1657
            elif parameter == 'repetition':
1658
                delegate = SpinBoxDelegate(self._pe.block_organizer_TableWidget, item_dict)
1659
            else:
1660
                delegate = DoubleSpinBoxDelegate(self._pe.block_organizer_TableWidget, item_dict)
1661
            self._pe.block_organizer_TableWidget.setItemDelegateForColumn(insert_at_col_pos+column, delegate)
1662
1663
            column += 1
1664
1665
        self.initialize_cells_block_organizer(start_row=0,
1666
                                              stop_row=self._pe.block_organizer_TableWidget.rowCount())
1667
1668
        self.set_cfg_param_pb()
1669
        self._update_current_pulse_block_ensemble()
1670
1671
1672 View Code Duplication
    def initialize_cells_block_organizer(self, start_row, stop_row=None,
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1673
                                         start_col=None, stop_col=None):
1674
        """ Initialize the desired cells in the block organizer table.
1675
1676
        @param start_row: int, index of the row, where the initialization
1677
                          should start
1678
        @param stop_row: int, optional, index of the row, where the
1679
                         initalization should end.
1680
        @param start_col: int, optional, index of the column where the
1681
                          initialization should start
1682
        @param stop_col: int, optional, index of the column, where the
1683
                         initalization should end.
1684
1685
        With this function it is possible to reinitialize specific elements or
1686
        part of a row or even the whole row. If start_row is set to 0 the whole
1687
        row is going to be initialzed to the default value.
1688
        """
1689
1690
        if stop_row is None:
1691
            stop_row = start_row +1
1692
1693
        if start_col is None:
1694
            start_col = 0
1695
1696
        if stop_col is None:
1697
            stop_col = self._pe.block_organizer_TableWidget.columnCount()
1698
1699
        for col_num in range(start_col, stop_col):
1700
1701
            for row_num in range(start_row,stop_row):
1702
                # get the model, here are the data stored:
1703
                model = self._pe.block_organizer_TableWidget.model()
1704
                # get the corresponding index of the current element:
1705
                index = model.index(row_num, col_num)
1706
                # get the initial values of the delegate class which was
1707
                # uses for this column:
1708
                ini_values = self._pe.block_organizer_TableWidget.itemDelegateForColumn(col_num).get_initial_value()
1709
                # set initial values:
1710
                model.setData(index, ini_values[0], ini_values[1])
1711
1712
    def _add_config_for_predefined_methods(self, parent, name):
1713
        """ Create the Config Elements for altering the Predefined Methods
1714
            display.
1715
        """
1716
        # one has to know that all the checkbox control elements are attached
1717
        # to the widget verticalLayout, accessible via self._pm_cfg.verticalLayout
1718
1719
        checkbox = self._create_QCheckBox(parent, default_val=True)
1720
1721
        checkbox.setText(name)
1722
        setattr(self._pm_cfg, name +'_CheckBox', checkbox)
1723
        self._pm_cfg.verticalLayout.addWidget(checkbox)
1724
1725
    def _get_ref_checkbox_predefined_methods_config(self, name):
1726
        """ Retrieve the reference to the CheckBox with the name of the predefined method
1727
1728
        @param str name: the name of the predefined method
1729
1730
        @return QtGui.QCheckBox: reference to the CheckBox widget.
1731
        """
1732
1733
        return getattr(self._pm_cfg, name+'_CheckBox')
1734
1735
    def _create_control_for_predefined_methods(self):
1736
        """ Create the Control Elements in the Predefined Windows, depending
1737
            on the methods of the logic.
1738
1739
        The following procedure was chosen:
1740
            1. At first the method is inspected and all the parameters are
1741
              investigated. Depending on the type of the default value of the
1742
              keyword, a ControlBox (CheckBox, DoubleSpinBox, ...) is created.laserchannel_ComboBox
1743
                _<method_name>_generate()
1744
                which are connected to the generate button and passes all the
1745
                parameters to the method in the logic.
1746
            3. An additional method is created as
1747
                _<method_name>_generate_upload()
1748
                which generates and uploads the current values to the device.
1749
        """
1750
        method_list = self._seq_gen_logic.predefined_method_list
1751
1752
        for method in method_list:
1753
            inspected = inspect.signature(method)
1754
1755
1756
            gridLayout = QtWidgets.QGridLayout()
1757
            groupBox = QtWidgets.QGroupBox(self._pm)
1758
1759
            obj_list = []
1760
1761
            # go through the parameter list and check the type of the default
1762
            # parameter
1763
            for index, param_name in enumerate(inspected.parameters):
1764
1765
                label_obj = self._create_QLabel(groupBox, param_name)
1766
1767
                default_value = inspected.parameters[param_name].default
1768
1769
                if default_value is inspect._empty:
1770
                    self.log.error('The method "{0}" in the logic has an '
1771
                                'argument "{1}" without a default value!\n'
1772
                                'Assign a default value to that, otherwise a '
1773
                                'type estimation is not possible!\n'
1774
                                'Creation of the viewbox '
1775
                                'aborted.'.format(method.__name__, param_name))
1776
                    return
1777
1778
                if type(default_value) is bool:
1779
                    view_obj = self._create_QCheckBox(groupBox, default_value)
1780
                elif type(default_value) is float:
1781
                    view_obj = self._create_QDoubleSpinBox(groupBox, default_value)
1782
                elif type(default_value) is int:
1783
                    view_obj = self._create_QSpinBox(groupBox, default_value)
1784
                elif type(default_value) is str:
1785
                    view_obj = self._create_QLineEdit(groupBox, default_value)
1786
                else:
1787
                    self.log.error('The method "{0}" in the logic has an '
1788
                            'argument "{1}" with is not of the valid types'
1789
                            'str, float int or bool!\n'
1790
                            'Choose one of those default values! Creation '
1791
                            'of the viewbox aborted.'.format(
1792
                                method.__name__, param_name))
1793
1794
                obj_list.append(view_obj)
1795
                gridLayout.addWidget(label_obj, 0, index, 1, 1)
1796
                gridLayout.addWidget(view_obj, 1, index, 1, 1)
1797
1798
            gen_button = self._create_QPushButton(groupBox, 'Generate')
1799
            # Create with the function builder a method, which will be
1800
            # connected to the click event of the pushbutton generate:
1801
            func_name = '_'+ method.__name__ + '_generate'
1802
            setattr(self, func_name, self._function_builder_generate(func_name, obj_list, method) )
1803
            gen_func = getattr(self, func_name)
1804
            gen_button.clicked.connect(gen_func)
1805
1806
1807
            gen_upload_button = self._create_QPushButton(groupBox, 'Gen. & Upload')
1808
            # Create with the function builder a method, which will be
1809
            # connected to the click event of the pushbutton generate & upload:
1810
            func_name = '_'+ method.__name__ + '_generate_upload'
1811
            setattr(self, func_name, self._function_builder_generate_upload(func_name, gen_func) )
1812
            gen_upload_func = getattr(self, func_name)
1813
            gen_upload_button.clicked.connect(gen_upload_func)
1814
1815
            # position the buttons in the groupbox:
1816
            pos = len(inspected.parameters)
1817
            gridLayout.addWidget(gen_button, 0, pos, 1, 1)
1818
            gridLayout.addWidget(gen_upload_button, 1, pos, 1, 1)
1819
1820
            horizontalLayout = QtWidgets.QHBoxLayout(groupBox)
1821
1822
            horizontalLayout.addLayout(gridLayout)
1823
1824
            groupBox.setTitle(method.__name__.replace('_',' '))
1825
1826
            # attach the GroupBox widget to the predefined methods widget.
1827
            setattr(self._pm, method.__name__+'_GroupBox', groupBox)
1828
1829
            # Since a Scroll Widget is used, you need you pass the
1830
            # scrollAreaWidgetContents as the parent widget.
1831
            self._add_config_for_predefined_methods(self._pm_cfg.scrollAreaWidgetContents, method.__name__)
1832
1833
            # add the name of the predefined method to a local list to keep
1834
            # track of the method:
1835
            self._predefined_methods_list.append(method.__name__)
1836
1837
            self._pm.verticalLayout.addWidget(groupBox)
1838
1839
    def _get_ref_groupbox_predefined_methods(self, name):
1840
        """ Retrieve the reference to the GroupBox with the name of the predefined method
1841
1842
        @param str name: the name of the predefined method
1843
1844
        @return QtGui.QGroupBox: reference to the groupbox widget containing all
1845
                                 elements for the predefined methods.
1846
        """
1847
1848
        return getattr(self._pm, name+'_GroupBox')
1849
1850
1851
    def _create_QLabel(self, parent, label_name):
1852
        """ Helper method for _create_control_for_predefined_methods.
1853
1854
        @param parent: The parent QWidget, which should own that object
1855
        @param str label_name: the display name for the QLabel Widget.
1856
1857
        @return QtGui.QLabel: a predefined label for the GUI.
1858
        """
1859
1860
        label = QtWidgets.QLabel(parent)
1861
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
1862
        sizePolicy.setHorizontalStretch(0)
1863
        sizePolicy.setVerticalStretch(0)
1864
        sizePolicy.setHeightForWidth(label.sizePolicy().hasHeightForWidth())
1865
        label.setSizePolicy(sizePolicy)
1866
        label.setText(label_name)
1867
        return label
1868
1869
    def _create_QDoubleSpinBox(self, parent, default_val=0.0):
1870
        """ Helper method for _create_control_for_predefined_methods.
1871
1872
        @param parent: The parent QWidget, which should own that object
1873
        @param float default_val: a default value for the QDoubleSpinBox.
1874
1875
        @return QtGui.QDoubleSpinBox: a predefined QDoubleSpinBox for the GUI.
1876
        """
1877
1878
        doublespinbox = QtWidgets.QDoubleSpinBox(parent)
1879
        doublespinbox.setMaximum(np.inf)
1880
        doublespinbox.setMinimum(-np.inf)
1881
1882
        # set a size for vertivcal an horizontal dimensions
1883
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
1884
        sizePolicy.setHorizontalStretch(0)
1885
        sizePolicy.setVerticalStretch(0)
1886
        sizePolicy.setHeightForWidth(doublespinbox.sizePolicy().hasHeightForWidth())
1887
1888
        doublespinbox.setMinimumSize(QtCore.QSize(80, 0))
1889
        doublespinbox.setValue(default_val)
1890
        return doublespinbox
1891
1892
    def _create_QSpinBox(self, parent, default_val=0):
1893
        """ Helper method for _create_control_for_predefined_methods.
1894
1895
        @param parent: The parent QWidget, which should own that object
1896
        @param int default_val: a default value for the QSpinBox.
1897
1898
        @return QtGui.QSpinBox: a predefined QSpinBox for the GUI.
1899
        """
1900
1901
        spinBox = QtWidgets.QSpinBox(parent)
1902
        spinBox.setMaximum(2**31 -1)
1903
        spinBox.setMinimum(-2**31 +1)
1904
        spinBox.setValue(default_val)
1905
        return spinBox
1906
1907
    def _create_QCheckBox(self, parent, default_val=False):
1908
        """ Helper method for _create_control_for_predefined_methods.
1909
1910
        @param parent: The parent QWidget, which should own that object
1911
        @param bool default_val: a default value for the QCheckBox.
1912
1913
        @return QtGui.QCheckBox: a predefined QCheckBox for the GUI.
1914
        """
1915
1916
        checkBox = QtWidgets.QCheckBox(parent)
1917
        checkBox.setChecked(default_val)
1918
        return checkBox
1919
1920
    def _create_QLineEdit(self, parent, default_val=''):
1921
        """ Helper method for _create_control_for_predefined_methods.
1922
1923
        @param parent: The parent QWidget, which should own that object
1924
        @param str default_val: a default value for the QLineEdit.
1925
1926
        @return QtGui.QLineEdit: a predefined QLineEdit for the GUI.
1927
        """
1928
1929
        lineedit = QtWidgets.QLineEdit(parent)
1930
        lineedit.setText(default_val)
1931
1932
        # set a size for vertivcal an horizontal dimensions
1933
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
1934
        sizePolicy.setHorizontalStretch(0)
1935
        sizePolicy.setVerticalStretch(0)
1936
        sizePolicy.setHeightForWidth(lineedit.sizePolicy().hasHeightForWidth())
1937
1938
        lineedit.setMinimumSize(QtCore.QSize(80, 0))
1939
1940
        lineedit.setSizePolicy(sizePolicy)
1941
        return lineedit
1942
1943
    def _create_QPushButton(self, parent, text='Generate'):
1944
        """ Helper method for _create_control_for_predefined_methods.
1945
1946
        @param parent: The parent QWidget, which should own that object
1947
        @param str text: a display text for the QPushButton.
1948
1949
        @return QtGui.QPushButton: a predefined QPushButton for the GUI.
1950
        """
1951
1952
        pushbutton = QtWidgets.QPushButton(parent)
1953
        pushbutton.setText(text)
1954
        return pushbutton
1955
1956
    def _function_builder_generate(self, func_name, obj_list, ref_logic_gen ):
1957
        """ Create a function/method which is called by the generate button.
1958
1959
        @param str func_name: name of the function, which will be append to self
1960
        @param list obj_list: list of objects, which where the value will be
1961
                              retrieved
1962
        @param method ref_logic_gen: reference to method in logic
1963
1964
        @return: a function, which can be called with func_name
1965
        """
1966
1967
        def func_dummy_name():
1968
            object_list = obj_list
1969
1970
            ensemble_name = ''
1971
            parameters = [None]*len(object_list)
1972
            for index, obj in enumerate(object_list):
1973
                if hasattr(obj,'isChecked'):
1974
                    parameters[index] = obj.isChecked()
1975
                elif hasattr(obj,'value'):
1976
                    parameters[index] = obj.value()
1977
                elif hasattr(obj,'text'):
1978
1979
                    parameters[index] = obj.text()
1980
                    ensemble_name = obj.text()
1981
                else:
1982
                    self.log.error('Not possible to get the value from the '
1983
                            'viewbox, since it does not have one of the'
1984
                            'possible access methods!')
1985
1986
            # the * operator unpacks the list
1987
            ref_logic_gen(*parameters)
1988
            return ensemble_name
1989
1990
        # assign now a new name to that function, this name will be used to
1991
        # bound the function as attribute to the main object.
1992
        func_dummy_name.__name__ = func_name
1993
        return func_dummy_name
1994
1995
1996
1997
1998
1999