pymodaq 5.0.17__py3-none-any.whl → 5.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pymodaq might be problematic. Click here for more details.

Files changed (92) hide show
  1. pymodaq/__init__.py +23 -11
  2. pymodaq/control_modules/__init__.py +1 -0
  3. pymodaq/control_modules/daq_move.py +458 -246
  4. pymodaq/control_modules/daq_move_ui/__init__.py +0 -0
  5. pymodaq/control_modules/daq_move_ui/factory.py +48 -0
  6. pymodaq/control_modules/{daq_move_ui.py → daq_move_ui/ui_base.py} +168 -210
  7. pymodaq/control_modules/daq_move_ui/uis/__init__.py +0 -0
  8. pymodaq/control_modules/daq_move_ui/uis/binary.py +139 -0
  9. pymodaq/control_modules/daq_move_ui/uis/original.py +120 -0
  10. pymodaq/control_modules/daq_move_ui/uis/relative.py +124 -0
  11. pymodaq/control_modules/daq_move_ui/uis/simple.py +126 -0
  12. pymodaq/control_modules/daq_viewer.py +113 -101
  13. pymodaq/control_modules/daq_viewer_ui.py +41 -31
  14. pymodaq/control_modules/mocks.py +2 -2
  15. pymodaq/control_modules/move_utility_classes.py +113 -41
  16. pymodaq/control_modules/thread_commands.py +137 -0
  17. pymodaq/control_modules/ui_utils.py +72 -0
  18. pymodaq/control_modules/utils.py +107 -63
  19. pymodaq/control_modules/viewer_utility_classes.py +13 -17
  20. pymodaq/dashboard.py +1294 -625
  21. pymodaq/examples/qt_less_standalone_module.py +48 -11
  22. pymodaq/extensions/__init__.py +8 -3
  23. pymodaq/extensions/adaptive/__init__.py +2 -0
  24. pymodaq/extensions/adaptive/adaptive_optimization.py +179 -0
  25. pymodaq/extensions/adaptive/loss_function/_1d_loss_functions.py +73 -0
  26. pymodaq/extensions/adaptive/loss_function/_2d_loss_functions.py +73 -0
  27. pymodaq/extensions/adaptive/loss_function/__init__.py +3 -0
  28. pymodaq/extensions/adaptive/loss_function/loss_factory.py +110 -0
  29. pymodaq/extensions/adaptive/utils.py +123 -0
  30. pymodaq/extensions/bayesian/__init__.py +1 -1
  31. pymodaq/extensions/bayesian/acquisition/__init__.py +2 -0
  32. pymodaq/extensions/bayesian/acquisition/acquisition_function_factory.py +80 -0
  33. pymodaq/extensions/bayesian/acquisition/base_acquisition_function.py +105 -0
  34. pymodaq/extensions/bayesian/bayesian_optimization.py +143 -0
  35. pymodaq/extensions/bayesian/utils.py +71 -297
  36. pymodaq/extensions/daq_logger/daq_logger.py +7 -12
  37. pymodaq/extensions/daq_logger/h5logging.py +1 -1
  38. pymodaq/extensions/daq_scan.py +30 -55
  39. pymodaq/extensions/data_mixer/__init__.py +0 -0
  40. pymodaq/extensions/data_mixer/daq_0Dviewer_DataMixer.py +97 -0
  41. pymodaq/extensions/data_mixer/data_mixer.py +262 -0
  42. pymodaq/extensions/data_mixer/model.py +108 -0
  43. pymodaq/extensions/data_mixer/models/__init__.py +0 -0
  44. pymodaq/extensions/data_mixer/models/equation_model.py +91 -0
  45. pymodaq/extensions/data_mixer/models/gaussian_fit_model.py +65 -0
  46. pymodaq/extensions/data_mixer/parser.py +53 -0
  47. pymodaq/extensions/data_mixer/utils.py +23 -0
  48. pymodaq/extensions/h5browser.py +3 -34
  49. pymodaq/extensions/optimizers_base/__init__.py +0 -0
  50. pymodaq/extensions/optimizers_base/optimizer.py +1016 -0
  51. pymodaq/extensions/optimizers_base/thread_commands.py +22 -0
  52. pymodaq/extensions/optimizers_base/utils.py +427 -0
  53. pymodaq/extensions/pid/actuator_controller.py +3 -2
  54. pymodaq/extensions/pid/daq_move_PID.py +107 -30
  55. pymodaq/extensions/pid/pid_controller.py +613 -287
  56. pymodaq/extensions/pid/utils.py +8 -5
  57. pymodaq/extensions/utils.py +17 -2
  58. pymodaq/resources/config_template.toml +57 -0
  59. pymodaq/resources/preset_default.xml +1 -1
  60. pymodaq/utils/config.py +13 -4
  61. pymodaq/utils/daq_utils.py +14 -0
  62. pymodaq/utils/data.py +1 -0
  63. pymodaq/utils/gui_utils/loader_utils.py +25 -15
  64. pymodaq/utils/h5modules/module_saving.py +134 -22
  65. pymodaq/utils/leco/daq_move_LECODirector.py +123 -84
  66. pymodaq/utils/leco/daq_xDviewer_LECODirector.py +84 -97
  67. pymodaq/utils/leco/director_utils.py +32 -16
  68. pymodaq/utils/leco/leco_director.py +104 -27
  69. pymodaq/utils/leco/pymodaq_listener.py +186 -97
  70. pymodaq/utils/leco/rpc_method_definitions.py +43 -0
  71. pymodaq/utils/leco/utils.py +25 -25
  72. pymodaq/utils/managers/batchscan_manager.py +12 -11
  73. pymodaq/utils/managers/modules_manager.py +74 -33
  74. pymodaq/utils/managers/overshoot_manager.py +11 -10
  75. pymodaq/utils/managers/preset_manager.py +100 -64
  76. pymodaq/utils/managers/preset_manager_utils.py +163 -107
  77. pymodaq/utils/managers/remote_manager.py +21 -16
  78. pymodaq/utils/scanner/scan_factory.py +18 -4
  79. pymodaq/utils/scanner/scan_selector.py +1 -3
  80. pymodaq/utils/scanner/scanner.py +35 -6
  81. pymodaq/utils/scanner/scanners/_1d_scanners.py +15 -46
  82. pymodaq/utils/scanner/scanners/_2d_scanners.py +21 -68
  83. pymodaq/utils/scanner/scanners/sequential.py +50 -31
  84. pymodaq/utils/scanner/scanners/tabular.py +45 -28
  85. {pymodaq-5.0.17.dist-info → pymodaq-5.1.0.dist-info}/METADATA +7 -6
  86. pymodaq-5.1.0.dist-info/RECORD +154 -0
  87. {pymodaq-5.0.17.dist-info → pymodaq-5.1.0.dist-info}/entry_points.txt +0 -2
  88. pymodaq/extensions/bayesian/bayesian_optimisation.py +0 -685
  89. pymodaq/utils/leco/desktop.ini +0 -2
  90. pymodaq-5.0.17.dist-info/RECORD +0 -121
  91. {pymodaq-5.0.17.dist-info → pymodaq-5.1.0.dist-info}/WHEEL +0 -0
  92. {pymodaq-5.0.17.dist-info → pymodaq-5.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,7 @@
1
1
  import time
2
2
  from functools import partial # needed for the button to sync setpoint with currpoint
3
3
  from typing import Dict, List, TYPE_CHECKING
4
-
4
+ from collections import deque
5
5
  import numpy as np
6
6
 
7
7
  from qtpy import QtGui, QtWidgets
@@ -27,6 +27,8 @@ from pymodaq.utils.managers.modules_manager import ModulesManager
27
27
  from pymodaq.extensions.pid.utils import get_models
28
28
  from pymodaq.utils.data import DataActuator, DataToActuators
29
29
  from pymodaq.extensions.pid.actuator_controller import PIDController
30
+ from pymodaq.extensions.pid.utils import PIDModelGeneric
31
+
30
32
  from pymodaq.extensions.utils import CustomExt
31
33
 
32
34
  if TYPE_CHECKING:
@@ -37,18 +39,9 @@ config = Config()
37
39
  logger = set_logger(get_module_name(__file__))
38
40
 
39
41
 
40
- def convert_output_limits(lim_min=-10., min_status=False, lim_max=10., max_status=False):
41
- output = [None, None]
42
- if min_status:
43
- output[0] = lim_min
44
- if max_status:
45
- output[1] = lim_max
46
- return output
47
-
48
-
49
42
  class DAQ_PID(CustomExt):
50
- """
51
- """
43
+ """ """
44
+
52
45
  command_pid = Signal(ThreadCommand)
53
46
  curr_points_signal = Signal(dict)
54
47
  setpoints_signal = Signal(dict)
@@ -57,55 +50,204 @@ class DAQ_PID(CustomExt):
57
50
  models = get_models()
58
51
 
59
52
  params = [
60
- {'title': 'Models', 'name': 'models', 'type': 'group', 'expanded': True, 'visible': True, 'children': [
61
- {'title': 'Models class:', 'name': 'model_class', 'type': 'list',
62
- 'limits': [d['name'] for d in models]},
63
- {'title': 'Model params:', 'name': 'model_params', 'type': 'group', 'children': []},
64
- ]},
65
- {'title': 'Move settings:', 'name': 'move_settings', 'expanded': True, 'type': 'group', 'visible': False,
66
- 'children': [
67
- {'title': 'Units:', 'name': 'units', 'type': 'str', 'value': ''}]},
53
+ {
54
+ "title": "Models",
55
+ "name": "models",
56
+ "type": "group",
57
+ "expanded": True,
58
+ "visible": True,
59
+ "children": [
60
+ {
61
+ "title": "Models class:",
62
+ "name": "model_class",
63
+ "type": "list",
64
+ "limits": [d["name"] for d in models],
65
+ },
66
+ {
67
+ "title": "Model params:",
68
+ "name": "model_params",
69
+ "type": "group",
70
+ "children": [],
71
+ },
72
+ ],
73
+ },
74
+ {
75
+ "title": "Move settings:",
76
+ "name": "move_settings",
77
+ "expanded": True,
78
+ "type": "group",
79
+ "visible": False,
80
+ "children": [
81
+ {"title": "Units:", "name": "units", "type": "str", "value": ""}
82
+ ],
83
+ },
68
84
  # here only to be compatible with DAQ_Scan, the model could update it
69
-
70
- {'title': 'Main Settings:', 'name': 'main_settings', 'expanded': True, 'type': 'group', 'children': [
71
- {'title': 'Acquisition Timeout (ms):', 'name': 'timeout', 'type': 'int', 'value': 10000},
72
- {'title': 'epsilon', 'name': 'epsilon', 'type': 'float', 'value': 0.01,
73
- 'tooltip': 'Precision at which move is considered as done'},
74
- {'title': 'PID controls:', 'name': 'pid_controls', 'type': 'group', 'children': [
75
- {'title': 'Sample time (ms):', 'name': 'sample_time', 'type': 'int', 'value': 10},
76
- {'title': 'Refresh plot time (ms):', 'name': 'refresh_plot_time', 'type': 'int', 'value': 200},
77
- {'title': 'Output limits:', 'name': 'output_limits', 'expanded': True, 'type': 'group', 'children': [
78
- {'title': 'Output limit (min):', 'name': 'output_limit_min_enabled', 'type': 'bool',
79
- 'value': False},
80
- {'title': 'Output limit (min):', 'name': 'output_limit_min', 'type': 'float', 'value': 0},
81
- {'title': 'Output limit (max):', 'name': 'output_limit_max_enabled', 'type': 'bool',
82
- 'value': False},
83
- {'title': 'Output limit (max:', 'name': 'output_limit_max', 'type': 'float', 'value': 100},
84
- ]},
85
- {'title': 'Auto mode:', 'name': 'auto_mode', 'type': 'bool', 'value': False, 'readonly': True},
86
- {'title': 'Prop. on measurement:', 'name': 'proportional_on_measurement', 'type': 'bool',
87
- 'value': False},
88
- {'title': 'PID constants:', 'name': 'pid_constants', 'type': 'group', 'children': [
89
- {'title': 'Kp:', 'name': 'kp', 'type': 'float', 'value': 0.1, 'min': 0},
90
- {'title': 'Ki:', 'name': 'ki', 'type': 'float', 'value': 0.01, 'min': 0},
91
- {'title': 'Kd:', 'name': 'kd', 'type': 'float', 'value': 0.001, 'min': 0},
92
- ]},
93
-
94
- ]},
95
-
96
- ]},
85
+ {
86
+ "title": "Main Settings:",
87
+ "name": "main_settings",
88
+ "expanded": True,
89
+ "type": "group",
90
+ "children": [
91
+ {
92
+ "title": "Acquisition Timeout (ms):",
93
+ "name": "timeout",
94
+ "type": "int",
95
+ "value": 10000,
96
+ },
97
+ {
98
+ "title": "epsilon",
99
+ "name": "epsilon",
100
+ "type": "float",
101
+ "value": 0.01,
102
+ "tooltip": "Precision at which move is considered as done",
103
+ },
104
+ {
105
+ "title": "Queue length",
106
+ "name": "queue_length",
107
+ "type": "int",
108
+ "value": 25,
109
+ "tooltip": "Length of queue to calculate stability",
110
+ },
111
+ {
112
+ "title": "Weighting type",
113
+ "name": "queue_weighting",
114
+ "type": "int",
115
+ "value": 0,
116
+ "tooltip": """Type of weighting when calculating the stability. The weight goes as q**N where q is the qth element in the queue and N is the wanted order.
117
+ """,
118
+ },
119
+ {
120
+ "title": "Refresh queue",
121
+ "name": "refresh_queue",
122
+ "type": "bool_push",
123
+ "value": False,
124
+ "tooltip": "Refresh queues to zero length",
125
+ },
126
+ {
127
+ "title": "Refresh plot",
128
+ "name": "refresh_plot",
129
+ "type": "bool_push",
130
+ "value": False,
131
+ "tooltip": "Refresh both plots to zero length",
132
+ },
133
+ {
134
+ "title": "PID settings:",
135
+ "name": "pid_settings",
136
+ "type": "group",
137
+ "children": [
138
+ {
139
+ "title": "Time per loop (ms):",
140
+ "name": "effective_sample_time",
141
+ "type": "int",
142
+ "value": 0,
143
+ "readonly": True,
144
+ "tooltip": "Time elapsed in the PID thread for each loop, ",
145
+ },
146
+ {
147
+ "title": "Sample time (ms):",
148
+ "name": "sample_time",
149
+ "type": "int",
150
+ "value": 10,
151
+ "tooltip": "The minimum desired time between two PID calculations, ",
152
+ },
153
+ {
154
+ "title": "Refresh plot time (ms):",
155
+ "name": "refresh_plot_time",
156
+ "type": "int",
157
+ "value": 200,
158
+ "tooltip": "Update display every x ms, ",
159
+ },
160
+ {
161
+ "title": "Output limits:",
162
+ "name": "output_limits",
163
+ "expanded": True,
164
+ "type": "group",
165
+ "tooltip": "Limits on the output of the PID controller, ",
166
+ "children": [
167
+ {
168
+ "title": "Output limit (min):",
169
+ "name": "output_limit_min_enabled",
170
+ "type": "bool",
171
+ "value": False,
172
+ },
173
+ {
174
+ "title": "Output limit (min):",
175
+ "name": "output_limit_min",
176
+ "type": "float",
177
+ "value": 0,
178
+ },
179
+ {
180
+ "title": "Output limit (max):",
181
+ "name": "output_limit_max_enabled",
182
+ "type": "bool",
183
+ "value": False,
184
+ },
185
+ {
186
+ "title": "Output limit (max:",
187
+ "name": "output_limit_max",
188
+ "type": "float",
189
+ "value": 100,
190
+ },
191
+ ],
192
+ },
193
+ {
194
+ "title": "Prop. on measurement:",
195
+ "name": "proportional_on_measurement",
196
+ "type": "bool",
197
+ "value": False,
198
+ "tooltip": "If True, the PID will stabilize the value instead of reducing the error (see pid.py).",
199
+ },
200
+ {
201
+ "title": "PID constants:",
202
+ "name": "pid_constants",
203
+ "type": "group",
204
+ "children": [
205
+ {
206
+ "title": "Kp:",
207
+ "name": "kp",
208
+ "type": "float",
209
+ "value": 0.1,
210
+ "min": 0,
211
+ },
212
+ {
213
+ "title": "Ki:",
214
+ "name": "ki",
215
+ "type": "float",
216
+ "value": 0.01,
217
+ "min": 0,
218
+ },
219
+ {
220
+ "title": "Kd:",
221
+ "name": "kd",
222
+ "type": "float",
223
+ "value": 0.001,
224
+ "min": 0,
225
+ },
226
+ ],
227
+ },
228
+ ],
229
+ },
230
+ ],
231
+ },
97
232
  ]
98
233
 
99
234
  def __init__(self, dockarea, dashboard):
100
235
  super().__init__(dockarea, dashboard)
101
236
 
102
- self.settings = Parameter.create(title='PID settings', name='pid_settings', type='group', children=self.params)
103
- self.title = 'PyMoDAQ PID'
237
+ self.settings = Parameter.create(
238
+ title="PID settings",
239
+ name="pid_settings",
240
+ type="group",
241
+ children=self.params,
242
+ )
243
+ self.title = "PyMoDAQ PID"
104
244
 
105
245
  self.initialized_state = False
106
- self.model_class = None
246
+ self.model_class: PIDModelGeneric = None
107
247
  self._curr_points = dict([])
108
248
  self._setpoints = dict([])
249
+ self.queue_points = dict([])
250
+ self.build_weight_array()
109
251
 
110
252
  self.dock_area = dockarea
111
253
  self.check_moving = False
@@ -116,70 +258,104 @@ class DAQ_PID(CustomExt):
116
258
  self.enable_controls_pid_run(False)
117
259
 
118
260
  self.emit_curr_points_sig.connect(self.emit_curr_points)
119
-
261
+
120
262
  def ini_PID(self):
121
-
122
- if self.is_action_checked('ini_pid'):
123
- output_limits = [None, None]
124
- if self.settings['main_settings', 'pid_controls', 'output_limits', 'output_limit_min_enabled']:
125
- output_limits[0] = self.settings['main_settings', 'pid_controls', 'output_limits', 'output_limit_min']
126
- if self.settings['main_settings', 'pid_controls', 'output_limits', 'output_limit_max_enabled']:
127
- output_limits[1] = self.settings['main_settings', 'pid_controls', 'output_limits', 'output_limit_max']
128
-
129
- self.PIDThread = QThread()
130
- pid_runner = PIDRunner(self.model_class, self.modules_manager, setpoints=self.setpoints,
131
- params=dict(Kp=self.settings['main_settings', 'pid_controls', 'pid_constants',
132
- 'kp'],
133
- Ki=self.settings['main_settings', 'pid_controls', 'pid_constants',
134
- 'ki'],
135
- Kd=self.settings['main_settings', 'pid_controls', 'pid_constants',
136
- 'kd'],
137
- sample_time=self.settings['main_settings', 'pid_controls',
138
- 'sample_time'] / 1000,
139
- output_limits=output_limits,
140
- auto_mode=False),
141
- )
142
-
143
- self.PIDThread.pid_runner = pid_runner
263
+ if self.is_action_checked("ini_pid"):
264
+ output_limits = self.get_output_limits()
265
+ self.update_queues(refresh=True)
266
+ self.runner_thread = QThread()
267
+ pid_runner = PIDRunner(
268
+ self.model_class,
269
+ self.modules_manager,
270
+ setpoints=self.setpoints,
271
+ params=dict(
272
+ Kp=self.settings[
273
+ "main_settings", "pid_settings", "pid_constants", "kp"
274
+ ],
275
+ Ki=self.settings[
276
+ "main_settings", "pid_settings", "pid_constants", "ki"
277
+ ],
278
+ Kd=self.settings[
279
+ "main_settings", "pid_settings", "pid_constants", "kd"
280
+ ],
281
+ sample_time=self.settings[
282
+ "main_settings", "pid_settings", "sample_time"
283
+ ]
284
+ / 1000,
285
+ output_limits=output_limits,
286
+ auto_mode=False,
287
+ ),
288
+ )
289
+
290
+ self.runner_thread.pid_runner = pid_runner
144
291
  pid_runner.pid_output_signal.connect(self.process_output)
292
+ pid_runner.time_elapsed_signal.connect(self.display_time_elapsed)
145
293
  pid_runner.status_sig.connect(self.thread_status)
146
294
  self.command_pid.connect(pid_runner.queue_command)
147
295
 
148
- pid_runner.moveToThread(self.PIDThread)
296
+ pid_runner.moveToThread(self.runner_thread)
149
297
 
150
- self.PIDThread.start()
151
- self.get_action('pid_led').set_as_true()
298
+ self.runner_thread.start()
299
+ self.get_action("pid_led").set_as_true()
152
300
  self.enable_controls_pid_run(True)
153
301
 
154
302
  else:
155
- if hasattr(self, 'PIDThread'):
156
- if self.PIDThread.isRunning():
157
- try:
158
- self.PIDThread.quit()
159
- except Exception:
160
- pass
161
- self.get_action('pid_led').set_as_false()
303
+ if hasattr(self, "runner_thread"):
304
+ self.exit_runner_thread()
305
+ self.get_action("pid_led").set_as_false()
162
306
  self.enable_controls_pid_run(False)
163
307
 
164
308
  self.initialized_state = True
165
309
 
166
310
  def process_output(self, data: DataToExport):
167
- inputs: DataRaw = data.get_data_from_name('inputs')
168
- outputs: DataRaw = data.get_data_from_name('outputs')
169
- self.curr_points = [float(array[0]) for array in inputs]
311
+ outputs: DataRaw = data.get_data_from_name("outputs")
312
+ inputs: DataRaw = data.get_data_from_name("inputs")
313
+ self.curr_points = [float(_input[-1]) for _input in inputs.isig[-1]]
314
+ inputs_plot = DataRaw("inputs",data=[np.array([_curr_point,]) for _curr_point in self.curr_points],labels=self.model_class.setpoints_names)
315
+ for _input, _setpoint_name in zip(inputs, self.model_class.setpoints_names):
316
+ self.queue_points[_setpoint_name].extend(_input)
317
+
318
+ # Would that be useful to add too?
319
+ # self.mean_points = [
320
+ # np.mean(np.array(queue) - setpoint)
321
+ # for queue, setpoint in zip(self.queue_points.values(), self.setpoints)
322
+ # ]
323
+ # queues_length = len(list(self.queue_points.values())[0])
324
+ self.stab_points = [
325
+ np.sqrt(np.cov(_queue, aweights=self.queue_weight[:len(_queue)], ddof=0))
326
+ for _queue in self.queue_points.values()
327
+ ]
170
328
  self.output_viewer.show_data(outputs)
171
- self.input_viewer.show_data(inputs)
329
+ self.input_viewer.show_data(inputs_plot)
330
+ def build_weight_array(self):
331
+ # Build a weighting array of the form np.arange(1+queue_length)**(queue_weighting)
332
+ self.queue_weight = np.arange(1+
333
+ self.settings.child("main_settings","queue_length").value()
334
+ )**self.settings.child("main_settings","queue_weighting").value()
335
+
336
+ def get_output_limits(self):
337
+ output_limits = [None, None]
338
+ output_parameters = self.settings.child("main_settings", "pid_settings", "output_limits")
339
+ if output_parameters["output_limit_min_enabled"]:
340
+ output_limits[0] = output_parameters[ "output_limit_min"]
341
+ if output_parameters["output_limit_max_enabled"]:
342
+ output_limits[1] = output_parameters[ "output_limit_max"]
343
+ return output_limits
344
+
345
+ def display_time_elapsed(self, time_elapsed: float):
346
+ """Display the time elapsed in the PID thread"""
347
+ self.settings.child("main_settings", "pid_settings", "effective_sample_time").setValue(time_elapsed*1000)
172
348
 
173
349
  def enable_controls_pid(self, enable=False):
174
- self.set_action_enabled('ini_pid', enable)
350
+ self.set_action_enabled("ini_pid", enable)
175
351
  # self.setpoint_sb.setOpts(enabled=enable)
176
352
 
177
353
  def enable_controls_pid_run(self, enable=False):
178
- self.set_action_enabled('run', enable)
179
- self.set_action_enabled('pause', enable)
354
+ self.set_action_enabled("run", enable)
355
+ self.set_action_enabled("pause", enable)
180
356
 
181
357
  def setup_menu(self, menubar: QtWidgets.QMenuBar = None):
182
- '''
358
+ """
183
359
  to be subclassed
184
360
  create menu for actions contained into the self.actions_manager, for instance:
185
361
 
@@ -191,11 +367,11 @@ class DAQ_PID(CustomExt):
191
367
 
192
368
  file_menu.addSeparator()
193
369
  self.actions_manager.affect_to('quit', file_menu)
194
- '''
370
+ """
195
371
  pass
196
372
 
197
373
  def value_changed(self, param):
198
- ''' to be subclassed for actions to perform when one of the param's value in self.settings is changed
374
+ """to be subclassed for actions to perform when one of the param's value in self.settings is changed
199
375
 
200
376
  For instance:
201
377
  if param.name() == 'do_something':
@@ -206,81 +382,123 @@ class DAQ_PID(CustomExt):
206
382
  Parameters
207
383
  ----------
208
384
  param: (Parameter) the parameter whose value just changed
209
- '''
210
- if param.name() == 'model_class':
385
+ """
386
+ if param.name() == "model_class":
211
387
  self.get_set_model_params(param.value())
212
388
 
213
- elif param.name() == 'refresh_plot_time' or param.name() == 'timeout':
214
- self.command_pid.emit(ThreadCommand('update_timer', [param.name(), param.value()]))
389
+ elif param.name() == "refresh_plot_time" or param.name() == "timeout":
390
+ self.command_pid.emit(
391
+ ThreadCommand("update_timer", [param.name(), param.value()])
392
+ )
215
393
 
216
- elif param.name() == 'sample_time':
217
- self.command_pid.emit(ThreadCommand('update_options', dict(sample_time=param.value())))
394
+ elif param.name() == "sample_time":
395
+ self.command_pid.emit(
396
+ ThreadCommand("update_options", dict(sample_time=param.value()))
397
+ )
218
398
 
219
399
  elif param.name() in putils.iter_children(
220
- self.settings.child('main_settings', 'pid_controls', 'output_limits'), []):
221
-
222
- output_limits = convert_output_limits(
223
- self.settings['main_settings', 'pid_controls', 'output_limits',
224
- 'output_limit_min'],
225
- self.settings['main_settings', 'pid_controls', 'output_limits',
226
- 'output_limit_min_enabled'],
227
- self.settings['main_settings', 'pid_controls', 'output_limits',
228
- 'output_limit_max'],
229
- self.settings['main_settings', 'pid_controls', 'output_limits',
230
- 'output_limit_max_enabled'])
231
-
232
- self.command_pid.emit(ThreadCommand('update_options', dict(output_limits=output_limits)))
400
+ self.settings.child("main_settings", "pid_settings", "output_limits"), []
401
+ ):
402
+ output_limits = self.get_output_limits()
403
+ self.command_pid.emit(
404
+ ThreadCommand("update_options", dict(output_limits=output_limits))
405
+ )
233
406
 
234
407
  elif param.name() in putils.iter_children(
235
- self.settings.child('main_settings', 'pid_controls', 'pid_constants'), []):
236
- Kp = self.settings['main_settings', 'pid_controls', 'pid_constants', 'kp']
237
- Ki = self.settings['main_settings', 'pid_controls', 'pid_constants', 'ki']
238
- Kd = self.settings['main_settings', 'pid_controls', 'pid_constants', 'kd']
239
- self.command_pid.emit(ThreadCommand('update_options', dict(tunings=(Kp, Ki, Kd))))
408
+ self.settings.child("main_settings", "pid_settings", "pid_constants"), []
409
+ ):
410
+ Kp = self.settings["main_settings", "pid_settings", "pid_constants", "kp"]
411
+ Ki = self.settings["main_settings", "pid_settings", "pid_constants", "ki"]
412
+ Kd = self.settings["main_settings", "pid_settings", "pid_constants", "kd"]
413
+ self.command_pid.emit(
414
+ ThreadCommand("update_options", dict(tunings=(Kp, Ki, Kd)))
415
+ )
240
416
 
241
- elif param.name() in putils.iter_children(self.settings.child('models', 'model_params'), []):
417
+ elif param.name() in putils.iter_children(
418
+ self.settings.child("models", "model_params"), []
419
+ ):
242
420
  if self.model_class is not None:
243
421
  self.model_class.update_settings(param)
244
422
 
245
- elif param.name() == 'detector_modules':
423
+ elif param.name() == "detector_modules":
246
424
  self.model_class.update_detector_names()
247
-
425
+ elif param.name() == "queue_length":
426
+ self.update_queues()
427
+ elif param.name() == "queue_weighting":
428
+ self.build_weight_array()
429
+ elif param.name() == "refresh_queue":
430
+ if param.value():
431
+ self.update_queues(refresh=True)
432
+ self.settings.child("main_settings", param.name()).setValue(False)
433
+ elif param.name() == "refresh_plot":
434
+ if param.value():
435
+ self.input_viewer.view.data_displayer.clear_data()
436
+ self.output_viewer.view.data_displayer.clear_data()
437
+ self.settings.child('main_settings', param.name()).setValue(False)
438
+
248
439
  def connect_things(self):
249
- logger.debug('connecting actions and other')
250
- self.connect_action('quit', self.quit_fun, )
251
- self.connect_action('ini_model', self.ini_model)
252
- self.connect_action('create_setp_actuators', self.create_setp_actuators)
253
- self.connect_action('ini_pid', self.ini_PID)
254
- self.connect_action('run', self.run_PID)
255
- self.connect_action('pause', self.pause_PID)
256
- logger.debug('connecting done')
440
+ logger.debug("connecting actions and other")
441
+ self.connect_action(
442
+ "quit",
443
+ self.quit_fun,
444
+ )
445
+ self.connect_action("ini_model", self.ini_model)
446
+ self.connect_action("create_setp_actuators", self.create_setp_actuators)
447
+ self.connect_action("ini_pid", self.ini_PID)
448
+ self.connect_action("run", self.run_PID)
449
+ self.connect_action("pause", self.pause_PID)
450
+ logger.debug("connecting done")
257
451
 
258
452
  def setup_actions(self):
259
- logger.debug('setting actions')
260
- self.add_action('quit', 'Quit', 'close2', "Quit program")
261
- self.add_widget('model_label', QtWidgets.QLabel, 'Init Model:')
262
- self.add_action('ini_model', 'Init Model', 'ini', tip='Initialize the selected model: algo/data conversion')
263
- self.add_widget('model_led', QLED, toolbar=self.toolbar)
264
-
265
- self.add_action('create_setp_actuators', 'Create SetPoint Actuators', 'Add_Step',
266
- tip='Create a DAQ_Move Control Module for each SetPoint allowing to'
267
- 'control them from the DashBoard, therefore within other extensions')
268
-
269
- self.add_widget('model_label', QtWidgets.QLabel, 'Init PID Runner:')
270
- self.add_action('ini_pid', 'Init the PID loop', 'ini', tip='Init the PID thread',
271
- checkable=True)
272
- self.add_widget('pid_led', QLED, toolbar=self.toolbar)
273
- self.add_action('run', 'Run The PID loop', 'run2', tip='run or stop the pid loop',
274
- checkable=True)
275
- self.add_action('pause', 'Pause the PID loop', 'pause', tip='Pause the PID loop',
276
- checkable=True)
277
- self.set_action_checked('pause', True)
278
- self.set_action_enabled('create_setp_actuators', False)
279
- logger.debug('actions set')
453
+ logger.debug("setting actions")
454
+ self.add_action("quit", "Quit", "close2", "Quit program")
455
+ self.add_widget("model_label", QtWidgets.QLabel, "Init Model:")
456
+ self.add_action(
457
+ "ini_model",
458
+ "Init Model",
459
+ "ini",
460
+ tip="Initialize the selected model: algo/data conversion",
461
+ )
462
+ self.add_widget("model_led", QLED, toolbar=self.toolbar)
463
+
464
+ self.add_action(
465
+ "create_setp_actuators",
466
+ "Create SetPoint Actuators",
467
+ "Add_Step",
468
+ tip="Create a DAQ_Move Control Module for each SetPoint allowing to"
469
+ "control them from the DashBoard, therefore within other extensions",
470
+ )
471
+
472
+ self.add_widget("model_label", QtWidgets.QLabel, "Init PID Runner:")
473
+ self.add_action(
474
+ "ini_pid",
475
+ "Init the PID loop",
476
+ "ini",
477
+ tip="Init the PID thread",
478
+ checkable=True,
479
+ )
480
+ self.add_widget("pid_led", QLED, toolbar=self.toolbar)
481
+ self.add_action(
482
+ "run",
483
+ "Run The PID loop",
484
+ "run2",
485
+ tip="run or stop the pid loop",
486
+ checkable=True,
487
+ )
488
+ self.add_action(
489
+ "pause",
490
+ "Pause the PID loop",
491
+ "pause",
492
+ tip="Pause the PID loop",
493
+ checkable=True,
494
+ )
495
+ self.set_action_checked("pause", True)
496
+ self.set_action_enabled("create_setp_actuators", False)
497
+ logger.debug("actions set")
280
498
 
281
499
  def setup_docks(self):
282
- logger.debug('settings the extension docks')
283
- self.dock_pid = Dock('PID controller', self.dock_area)
500
+ logger.debug("settings the extension docks")
501
+ self.dock_pid = Dock("PID controller", self.dock_area)
284
502
  self.dock_area.addDock(self.dock_pid)
285
503
 
286
504
  widget = QtWidgets.QWidget()
@@ -290,28 +508,34 @@ class DAQ_PID(CustomExt):
290
508
  self.toolbar_layout = QtWidgets.QGridLayout()
291
509
  widget_toolbar.setLayout(self.toolbar_layout)
292
510
 
293
- logger.debug('settings the extension docks done')
511
+ logger.debug("settings the extension docks done")
294
512
 
295
- labmaj = QtWidgets.QLabel('Sync Value:')
513
+ labmaj = QtWidgets.QLabel("Setpoints:")
514
+ self.toolbar_layout.addWidget(labmaj, 3, 0, 1, 2)
515
+ labmaj = QtWidgets.QLabel("Value:")
516
+ self.toolbar_layout.addWidget(labmaj, 4, 0, 1, 2)
517
+ labmaj = QtWidgets.QLabel("Stability:")
296
518
  self.toolbar_layout.addWidget(labmaj, 5, 0, 1, 2)
519
+ labmaj = QtWidgets.QLabel("Sync Value:")
520
+ self.toolbar_layout.addWidget(labmaj, 6, 0, 1, 2)
297
521
 
298
522
  verlayout.addWidget(widget_toolbar)
299
523
  verlayout.addWidget(self.settings_tree)
300
524
 
301
- self.dock_output = Dock('PID output')
525
+ self.dock_output = Dock("PID output")
302
526
  widget_output = QtWidgets.QWidget()
303
527
  self.output_viewer = Viewer0D(widget_output)
304
528
  self.dock_output.addWidget(widget_output)
305
- self.dock_area.addDock(self.dock_output, 'right', self.dock_pid)
529
+ self.dock_area.addDock(self.dock_output, "right", self.dock_pid)
306
530
 
307
- self.dock_input = Dock('PID input')
531
+ self.dock_input = Dock("PID input")
308
532
  widget_input = QtWidgets.QWidget()
309
533
  self.input_viewer = Viewer0D(widget_input)
310
534
  self.dock_input.addWidget(widget_input)
311
- self.dock_area.addDock(self.dock_input, 'bottom', self.dock_output)
535
+ self.dock_area.addDock(self.dock_input, "bottom", self.dock_output)
312
536
 
313
537
  if len(self.models) != 0:
314
- self.get_set_model_params(self.models[0]['name'])
538
+ self.get_set_model_params(self.models[0]["name"])
315
539
 
316
540
  self.dock_pid.addWidget(widget)
317
541
 
@@ -319,72 +543,105 @@ class DAQ_PID(CustomExt):
319
543
  # Now that we have the module manager, load PID if it is checked in managers
320
544
  try:
321
545
  for setp in self.model_class.setpoints_names:
322
- self.dashboard.add_move_from_extension(setp, 'PID', PIDController(self))
323
- self.set_action_enabled('create_setp_actuators', False)
546
+ self.dashboard.add_move_from_extension(setp, "PID", PIDController(self, setp))
547
+ self.set_action_enabled("create_setp_actuators", False)
324
548
 
325
549
  except Exception as e:
326
- raise PIDError('Could not load the PID extension and create setpoints actuators'
327
- f'{str(e)}')
550
+ raise PIDError(
551
+ "Could not load the PID extension and create setpoints actuators"
552
+ f"{str(e)}"
553
+ )
328
554
 
329
555
  def get_set_model_params(self, model_name):
330
- self.settings.child('models', 'model_params').clearChildren()
556
+ self.settings.child("models", "model_params").clearChildren()
331
557
  models = get_models()
332
558
  if len(models) > 0:
333
- model_class = find_dict_in_list_from_key_val(models, 'name', model_name)['class']
334
- params = getattr(model_class, 'params')
335
- self.settings.child('models', 'model_params').addChildren(params)
559
+ model_class = find_dict_in_list_from_key_val(models, "name", model_name)[
560
+ "class"
561
+ ]
562
+ params = getattr(model_class, "params")
563
+ self.settings.child("models", "model_params").addChildren(params)
336
564
 
337
565
  def run_PID(self):
338
- if self.is_action_checked('run'):
339
- self.get_action('run').set_icon('stop')
340
- self.command_pid.emit(ThreadCommand('start_PID', []))
566
+ if self.is_action_checked("run"):
567
+ self.get_action("run").set_icon("stop")
568
+ self.command_pid.emit(ThreadCommand("start_PID", []))
341
569
  QtWidgets.QApplication.processEvents()
342
570
 
343
571
  QtWidgets.QApplication.processEvents()
344
572
 
345
- self.command_pid.emit(ThreadCommand('run_PID', [self.model_class.curr_output]))
573
+ self.command_pid.emit(
574
+ ThreadCommand("run_PID", [np.zeros_like(self.model_class.curr_output)])
575
+ )
346
576
  else:
347
- self.get_action('run').set_icon('run2')
348
- self.command_pid.emit(ThreadCommand('stop_PID'))
577
+ self.get_action("run").set_icon("run2")
578
+ self.command_pid.emit(ThreadCommand("stop_PID"))
349
579
 
350
580
  QtWidgets.QApplication.processEvents()
351
581
 
352
582
  def pause_PID(self):
353
583
  for setp in self.setpoints_sb:
354
- setp.setEnabled(not self.is_action_checked('pause'))
355
- self.command_pid.emit(ThreadCommand('pause_PID', [self.is_action_checked('pause')]))
584
+ setp.setEnabled(not self.is_action_checked("pause"))
585
+ self.command_pid.emit(
586
+ ThreadCommand("pause_PID", [self.is_action_checked("pause")])
587
+ )
356
588
 
357
589
  def stop_moves(self, overshoot):
358
590
  """
359
- Foreach module of the move module object list, stop motion.
591
+ Foreach module of the move module object list, stop motion.
360
592
 
361
- See Also
362
- --------
363
- stop_scan, DAQ_Move_main.daq_move.stop_Motion
593
+ See Also
594
+ --------
595
+ stop_scan, DAQ_Move_main.daq_move.stop_Motion
364
596
  """
365
597
  self.overshoot = overshoot
366
598
  for mod in self.modules_manager.actuators:
367
599
  mod.stop_Motion()
368
600
 
369
601
  def set_model(self):
370
- model_name = self.settings['models', 'model_class']
371
- self.model_class = find_dict_in_list_from_key_val(self.models, 'name', model_name)['class'](self)
602
+ model_name = self.settings["models", "model_class"]
603
+ self.model_class: PIDModelGeneric = find_dict_in_list_from_key_val(
604
+ self.models, "name", model_name
605
+ )["class"](self)
372
606
  self.set_setpoints_buttons()
607
+ self.update_queues(refresh=True)
373
608
  self.model_class.ini_model()
374
- self.settings.child('main_settings', 'epsilon').setValue(self.model_class.epsilon)
609
+ self.settings.child("main_settings", "epsilon").setValue(
610
+ self.model_class.epsilon
611
+ )
612
+
613
+ def init_queues(self):
614
+ self.queue_points = {
615
+ setpoint_name: deque(maxlen=self.settings.child("main_settings", "queue_length").value())
616
+ for setpoint_name in self.model_class.setpoints_names
617
+ }
618
+
619
+ def update_queues(self, refresh=False):
620
+ if refresh:
621
+ self.init_queues()
622
+ else:
623
+ self.queue_points = {
624
+ setpoint_name: deque(queue, maxlen=self.settings.child("main_settings", "queue_length").value())
625
+ for queue, setpoint_name in zip(self.queue_points.values(), self.model_class.setpoints_names)
626
+ }
627
+ self.build_weight_array()
375
628
 
376
629
  def ini_model(self):
377
630
  try:
378
631
  if self.model_class is None:
379
632
  self.set_model()
380
633
 
381
- self.modules_manager.selected_actuators_name = self.model_class.actuators_name
382
- self.modules_manager.selected_detectors_name = self.model_class.detectors_name
634
+ self.modules_manager.selected_actuators_name = (
635
+ self.model_class.actuators_name
636
+ )
637
+ self.modules_manager.selected_detectors_name = (
638
+ self.model_class.detectors_name
639
+ )
383
640
 
384
641
  self.enable_controls_pid(True)
385
- self.get_action('model_led').set_as_true()
386
- self.set_action_enabled('ini_model', False)
387
- self.set_action_enabled('create_setp_actuators', True)
642
+ self.get_action("model_led").set_as_true()
643
+ self.set_action_enabled("ini_model", False)
644
+ self.set_action_enabled("create_setp_actuators", True)
388
645
 
389
646
  except Exception as e:
390
647
  logger.exception(str(e))
@@ -397,11 +654,13 @@ class DAQ_PID(CustomExt):
397
654
  def setpoints(self, values):
398
655
  for ind, sp in enumerate(self.setpoints_sb):
399
656
  sp.setValue(values[ind])
657
+ self.update_queues(refresh=True) # Refresh queues when setpoints are updated
400
658
 
401
659
  def setpoints_external(self, values_dict: Dict[str, DataActuator]):
402
660
  for key in values_dict:
403
661
  index = self.model_class.setpoints_names.index(key)
404
662
  self.setpoints_sb[index].setValue(values_dict[key].value())
663
+ self.queue_points[key].clear() # Refresh queue when setpoint is updated
405
664
 
406
665
  @property
407
666
  def curr_points(self):
@@ -414,55 +673,84 @@ class DAQ_PID(CustomExt):
414
673
 
415
674
  def emit_curr_points(self):
416
675
  if self.model_class is not None:
417
- self.curr_points_signal.emit(dict(zip(self.model_class.setpoints_names, self.curr_points)))
676
+ self.curr_points_signal.emit(
677
+ dict(zip(self.model_class.setpoints_names, self.curr_points))
678
+ )
679
+
680
+ @property
681
+ def stab_points(self):
682
+ return [sp.value() for sp in self.stabpoints_sb]
683
+
684
+ @stab_points.setter
685
+ def stab_points(self, values):
686
+ for ind, sp in enumerate(self.stabpoints_sb):
687
+ sp.setValue(values[ind])
418
688
 
419
689
  def set_setpoints_buttons(self):
420
690
  self.setpoints_sb = []
421
691
  self.currpoints_sb = []
692
+ self.stabpoints_sb = []
693
+ # self.meanpoints_sb = []
422
694
  self.syncvalue_pb = []
423
695
  for ind_set in range(self.model_class.Nsetpoints):
696
+ label = LabelWithFont(
697
+ self.model_class.setpoints_names[ind_set],
698
+ font_name="Tahoma",
699
+ font_size=14,
700
+ isbold=True,
701
+ isitalic=True,
702
+ )
703
+ col_ind = 2 + ind_set
704
+
705
+ self.toolbar_layout.addWidget(label, 2, col_ind, 1, 1)
706
+
707
+ self.setpoints_sb.append(self.make_spinbox(no_button=False))
708
+ self.toolbar_layout.addWidget(
709
+ self.setpoints_sb[-1], 3, col_ind, 1, 1
710
+ )
424
711
 
425
- label = LabelWithFont(self.model_class.setpoints_names[ind_set],
426
- font_name="Tahoma", font_size=14, isbold=True, isitalic=True)
427
-
428
- self.setpoints_sb.append(SpinBox())
429
- self.setpoints_sb[-1].setMinimumHeight(40)
430
- font = self.setpoints_sb[-1].font()
431
- font.setPointSizeF(20)
432
- self.setpoints_sb[-1].setFont(font)
433
- self.setpoints_sb[-1].setDecimals(6)
434
- self.toolbar_layout.addWidget(label, 2, 2 + ind_set, 1, 1)
435
- self.toolbar_layout.addWidget(self.setpoints_sb[-1], 3, 2 + ind_set, 1, 1)
436
712
  self.setpoints_sb[-1].valueChanged.connect(self.update_runner_setpoints)
437
713
 
438
- self.currpoints_sb.append(SpinBox())
439
- self.currpoints_sb[-1].setMinimumHeight(40)
440
- self.currpoints_sb[-1].setReadOnly(True)
441
- self.currpoints_sb[-1].setDecimals(6)
442
- self.currpoints_sb[-1].setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
443
- font = self.currpoints_sb[-1].font()
444
- font.setPointSizeF(20)
445
- self.currpoints_sb[-1].setFont(font)
446
- self.toolbar_layout.addWidget(self.currpoints_sb[-1], 4, 2 + ind_set, 1, 1)
447
-
448
- self.syncvalue_pb.append(QtWidgets.QPushButton('Synchro {}'.format(ind_set)))
449
- self.syncvalue_pb[ind_set].clicked.connect(partial(self.currpoint_as_setpoint, ind_set))
450
- self.toolbar_layout.addWidget(self.syncvalue_pb[-1], 5, 2 + ind_set)
714
+ self.currpoints_sb.append(self.make_spinbox())
715
+ self.toolbar_layout.addWidget(
716
+ self.currpoints_sb[-1], 4, col_ind, 1, 1
717
+ )
718
+
719
+ self.stabpoints_sb.append(self.make_spinbox())
720
+ self.toolbar_layout.addWidget(self.stabpoints_sb[-1], 5, col_ind, 1, 1)
721
+ self.syncvalue_pb.append(
722
+ QtWidgets.QPushButton("Synchro {}".format(ind_set))
723
+ )
724
+ self.syncvalue_pb[ind_set].clicked.connect(
725
+ partial(self.currpoint_as_setpoint, ind_set)
726
+ )
727
+ self.toolbar_layout.addWidget(self.syncvalue_pb[-1], 6, col_ind, 1, 1)
451
728
  self.setpoints_signal.connect(self.setpoints_external)
452
729
 
730
+ def make_spinbox(self, no_button=True):
731
+ """ """
732
+ spinbox = SpinBox()
733
+ spinbox.setMinimumHeight(40)
734
+ font = spinbox.font()
735
+ font.setPointSizeF(20)
736
+ spinbox.setFont(font)
737
+ spinbox.setDecimals(6)
738
+ if no_button:
739
+ spinbox.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
740
+ return spinbox
741
+
453
742
  def currpoint_as_setpoint(self, i=0):
454
- '''
743
+ """
455
744
  Function used by the sync buttons. The button i will attribute the value of the i-th currpoint to the i-th setpoint.
456
- '''
745
+ """
457
746
  self.setpoints_sb[i].setValue(self.curr_points[i])
458
- self.update_runner_setpoints
747
+ self.update_runner_setpoints()
459
748
 
460
749
  def quit_fun(self):
461
- """
462
- """
750
+ """ """
463
751
  try:
464
752
  try:
465
- self.PIDThread.exit()
753
+ self.exit_runner_thread()
466
754
  except Exception as e:
467
755
  print(e)
468
756
 
@@ -473,27 +761,35 @@ class DAQ_PID(CustomExt):
473
761
  QThread.msleep(1000)
474
762
  QtWidgets.QApplication.processEvents()
475
763
 
476
- self.dock_area.parent().close()
764
+ self.dock_area.parent().close()
765
+ self.dashboard.remove_modules([setp for setp in self.model_class.setpoints_names])
477
766
 
478
767
  except Exception as e:
479
768
  print(e)
480
769
 
481
770
  def update_runner_setpoints(self):
482
- self.command_pid.emit(ThreadCommand('update_setpoints', self.setpoints))
771
+ self.command_pid.emit(ThreadCommand("update_setpoints", self.setpoints))
483
772
 
484
773
  @Slot(list)
485
- def thread_status(self, status): # general function to get datas/infos from all threads back to the main
486
- """
487
-
488
- """
774
+ def thread_status(
775
+ self, status
776
+ ): # general function to get datas/infos from all threads back to the main
777
+ """ """
489
778
  pass
490
779
 
491
780
 
492
781
  class PIDRunner(QObject):
493
782
  status_sig = Signal(list)
494
783
  pid_output_signal = Signal(DataToExport)
495
-
496
- def __init__(self, model_class, modules_manager: ModulesManager, setpoints=[], params=dict([])):
784
+ time_elapsed_signal = Signal(float)
785
+
786
+ def __init__(
787
+ self,
788
+ model_class: PIDModelGeneric,
789
+ modules_manager: ModulesManager,
790
+ setpoints=[],
791
+ params=dict([]),
792
+ ):
497
793
  """
498
794
  Init the PID instance with params as initial conditions
499
795
 
@@ -508,22 +804,37 @@ class PIDRunner(QObject):
508
804
  self.modules_manager = modules_manager
509
805
  Nsetpoints = model_class.Nsetpoints
510
806
  self.current_time = 0
511
- self.inputs_from_dets = DataToExport('inputs', data=[DataCalculated(self.model_class.setpoints_names[ind],
512
- data=[np.array([setpoints[ind]])])
513
- for ind in range(Nsetpoints)])
514
- self.outputs = [0. for _ in range(Nsetpoints)]
515
- self.outputs_to_actuators = DataToActuators('pid',
516
- mode='rel',
517
- data=[DataActuator(self.model_class.actuators_name[ind],
518
- data=self.outputs[ind])
519
- for ind in range(Nsetpoints)])
520
-
521
- if 'sample_time' in params:
522
- self.sample_time = params['sample_time']
807
+ self.time_elapsed = 0 # Time elapsed in the running loop
808
+ self.inputs_from_dets = DataToExport(
809
+ "inputs",
810
+ data=[
811
+ DataCalculated(
812
+ self.model_class.setpoints_names[ind],
813
+ data=[np.array([setpoints[ind]])],
814
+ )
815
+ for ind in range(Nsetpoints)
816
+ ],
817
+ )
818
+ self.outputs = [0.0 for _ in range(Nsetpoints)]
819
+ self.outputs_to_actuators = DataToActuators(
820
+ "pid",
821
+ mode="rel",
822
+ data=[
823
+ DataActuator(
824
+ self.model_class.actuators_name[ind], data=self.outputs[ind]
825
+ )
826
+ for ind in range(Nsetpoints)
827
+ ],
828
+ )
829
+
830
+ if "sample_time" in params:
831
+ self.sample_time = params["sample_time"]
523
832
  else:
524
833
  self.sample_time = 0.010 # in secs
525
834
 
526
- self.pids = [PID(setpoint=setpoints[0], **params) for ind in range(Nsetpoints)] # #PID(object):
835
+ self.pids = [
836
+ PID(setpoint=setpoints[0], **params) for ind in range(Nsetpoints)
837
+ ] # #PID(object):
527
838
  for pid in self.pids:
528
839
  pid.set_auto_mode(False)
529
840
  self.refreshing_ouput_time = 200
@@ -532,25 +843,29 @@ class PIDRunner(QObject):
532
843
 
533
844
  self.paused = True
534
845
 
846
+ self.refresh_queues() # Initialize queues with the desired length
847
+
848
+ [queue_input.append(output) for queue_input, output in zip(self.queue_inputs, self.outputs)] # Prefill queues with initial output
849
+ self.clear_queues = True # Clear queues on first iteration
850
+
535
851
  # self.timeout_timer = QtCore.QTimer()
536
852
  # self.timeout_timer.setInterval(10000)
537
853
  # self.timeout_scan_flag = False
538
854
  # self.timeout_timer.timeout.connect(self.timeout)
539
855
  #
540
856
  def timerEvent(self, event):
541
- outputs_dwa = self.outputs_to_actuators.merge_as_dwa('Data0D', name='outputs')
857
+ outputs_dwa = self.outputs_to_actuators.merge_as_dwa("Data0D", name="outputs")
542
858
  outputs_dwa.labels = self.modules_manager.selected_actuators_name
543
- dte = DataToExport('toplot', data=[outputs_dwa])
544
-
545
- inputs_dwa = self.inputs_from_dets.merge_as_dwa('Data0D', name='inputs')
546
- inputs_dwa.labels = self.modules_manager.selected_actuators_name
859
+ dte = DataToExport("toplot", data=[outputs_dwa])
860
+ inputs_dwa = DataRaw("inputs", data=[np.array(queue_input) for queue_input in self.queue_inputs], labels=self.modules_manager.selected_actuators_name)
547
861
  dte.append(inputs_dwa)
548
- self.pid_output_signal.emit(dte)
862
+ self.pid_output_signal.emit(dte)
863
+ self.time_elapsed_signal.emit(self.time_elapsed)
864
+ self.clear_queues = True
549
865
 
550
866
  @Slot(ThreadCommand)
551
867
  def queue_command(self, command: ThreadCommand):
552
- """
553
- """
868
+ """ """
554
869
  if command.command == "start_PID":
555
870
  self.start_PID(*command.attribute)
556
871
 
@@ -563,21 +878,23 @@ class PIDRunner(QObject):
563
878
  elif command.command == "stop_PID":
564
879
  self.stop_PID()
565
880
 
566
- elif command.command == 'update_options':
881
+ elif command.command == "update_options":
567
882
  self.set_option(**command.attribute)
568
883
 
569
- elif command.command == 'update_setpoints':
884
+ elif command.command == "update_setpoints":
570
885
  self.update_setpoints(command.attribute)
571
886
 
572
- elif command.command == 'input':
887
+ elif command.command == "input":
573
888
  self.update_input(*command.attribute)
574
889
 
575
- elif command.command == 'update_timer':
576
- if command.attribute[0] == 'refresh_plot_time':
890
+ elif command.command == "update_timer":
891
+ if command.attribute[0] == "refresh_plot_time":
577
892
  self.killTimer(self.timer)
578
893
  self.refreshing_ouput_time = command.attribute[1]
894
+ self.refresh_queues()
579
895
  self.timer = self.startTimer(self.refreshing_ouput_time)
580
- elif command.attribute[0] == 'timeout':
896
+
897
+ elif command.attribute[0] == "timeout":
581
898
  self.timeout_timer.setInterval(command.attribute[1])
582
899
 
583
900
  def update_input(self, measurements: DataToExport):
@@ -601,38 +918,47 @@ class PIDRunner(QObject):
601
918
  self.modules_manager.connect_actuators()
602
919
 
603
920
  self.current_time = time.perf_counter()
604
- logger.info('PID loop starting')
921
+ logger.info("PID loop starting")
605
922
  while self.running:
606
- # print('input: {}'.format(self.input))
607
923
  # # GRAB DATA FIRST AND WAIT ALL DETECTORS RETURNED
608
-
609
924
  self.det_done_datas: DataToExport = self.modules_manager.grab_datas()
610
925
 
611
- self.inputs_from_dets: DataToExport = self.model_class.convert_input(self.det_done_datas)
612
-
926
+ self.inputs_from_dets: DataToExport = self.model_class.convert_input(
927
+ self.det_done_datas
928
+ )
929
+ # # CHECK TIME ELAPSED FROM LAST LOOP
930
+ self.time_elapsed = (
931
+ time.perf_counter() - self.current_time
932
+ ) # Time elapsed since last loop
933
+ sleep_time = self.sample_time - self.time_elapsed
934
+ if sleep_time > 0:
935
+ QThread.msleep(int(sleep_time * 1000))
613
936
  # # EXECUTE THE PID
614
937
  self.outputs = []
615
938
  for ind, pid in enumerate(self.pids):
616
939
  self.outputs.append(pid(float(self.inputs_from_dets[ind][0][0])))
617
940
 
618
- # # APPLY THE PID OUTPUT TO THE ACTUATORS
619
- if self.outputs is None:
620
- self.outputs = [pid.setpoint for pid in self.pids]
941
+ self.current_time = time.perf_counter() # Update current time
621
942
 
622
- dt = time.perf_counter() - self.current_time
943
+ # # APPLY THE PID OUTPUT TO THE ACTUATORS
623
944
  self.outputs_to_actuators: DataToActuators = (
624
- self.model_class.convert_output(self.outputs, dt, stab=True))
945
+ self.model_class.convert_output(self.outputs, dt=None)
946
+ )
947
+ if self.clear_queues:
948
+ [queue_input.clear() for queue_input in self.queue_inputs]
949
+ self.clear_queues = False
950
+ for data_input, queue_input in zip(self.inputs_from_dets.data, self.queue_inputs):
951
+ queue_input.append(data_input[0][0])
625
952
 
626
953
  if not self.paused:
627
- self.modules_manager.move_actuators(self.outputs_to_actuators,
628
- self.outputs_to_actuators.mode,
629
- polling=False)
630
-
631
- self.current_time = time.perf_counter()
954
+ self.modules_manager.move_actuators(
955
+ self.outputs_to_actuators,
956
+ self.outputs_to_actuators.mode,
957
+ polling=False,
958
+ )
632
959
  QtWidgets.QApplication.processEvents()
633
- QThread.msleep(int(self.sample_time * 1000))
634
960
 
635
- logger.info('PID loop exiting')
961
+ logger.info("PID loop exiting")
636
962
  self.modules_manager.connect_actuators(False)
637
963
  self.modules_manager.connect_detectors(False)
638
964
 
@@ -643,17 +969,23 @@ class PIDRunner(QObject):
643
969
  for ind, pid in enumerate(self.pids):
644
970
  pid.setpoint = setpoints[ind]
645
971
 
972
+ def refresh_queues(self):
973
+ self.queue_length = 5 * int(self.refreshing_ouput_time * 1e-3 / self.sample_time) # Queue length is approximately output_time/sampling_time (*5 for safety)
974
+ self.queue_inputs = [deque(maxlen=self.queue_length) for ind in range(len(self.outputs))]
975
+
646
976
  def set_option(self, **option):
647
977
  for pid in self.pids:
648
978
  for key in option:
649
979
  if hasattr(pid, key):
650
- if key == 'sample_time':
651
- setattr(pid, key, option[key] / 1000)
980
+ if key == "sample_time":
981
+ self.sample_time = option[key] / 1000
982
+ setattr(pid, key, self.sample_time)
983
+ self.refresh_queues()
652
984
  else:
653
985
  setattr(pid, key, option[key])
654
986
 
655
987
  def run_PID(self, last_values):
656
- logger.info('Stabilization started')
988
+ logger.info("Stabilization started")
657
989
  for ind, pid in enumerate(self.pids):
658
990
  pid.set_auto_mode(True, last_values[ind])
659
991
 
@@ -661,30 +993,24 @@ class PIDRunner(QObject):
661
993
  for ind, pid in enumerate(self.pids):
662
994
  if pause_state:
663
995
  pid.set_auto_mode(False)
664
- logger.info('Stabilization paused')
996
+ logger.info("Stabilization paused")
665
997
  else:
666
998
  pid.set_auto_mode(True, self.outputs[ind])
667
- logger.info('Stabilization restarted from pause')
999
+ logger.info("Stabilization restarted from pause")
668
1000
  self.paused = pause_state
669
1001
 
670
1002
  def stop_PID(self):
671
1003
  self.running = False
672
- logger.info('PID loop exiting')
1004
+ logger.info("PID loop exiting")
673
1005
 
674
1006
 
675
- if __name__ == '__main__':
1007
+ if __name__ == "__main__":
676
1008
  from pymodaq_gui.utils.utils import mkQApp
677
1009
  from pymodaq.utils.gui_utils.loader_utils import load_dashboard_with_preset
678
1010
 
679
- app = mkQApp('DAQ_PID')
680
- preset_file_name = config('presets', f'default_preset_for_pid')
1011
+ app = mkQApp("DAQ_PID")
1012
+ preset_file_name = config("presets", f"default_preset_for_pid")
681
1013
 
682
- dashboard, extension, win = load_dashboard_with_preset(preset_file_name, 'DAQ_PID')
1014
+ dashboard, extension, win = load_dashboard_with_preset(preset_file_name, "DAQ_PID")
683
1015
 
684
1016
  app.exec()
685
-
686
-
687
-
688
-
689
-
690
-