pymodaq 5.1.6__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.
Files changed (154) hide show
  1. pymodaq/__init__.py +98 -0
  2. pymodaq/control_modules/__init__.py +1 -0
  3. pymodaq/control_modules/daq_move.py +1238 -0
  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/ui_base.py +359 -0
  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 +1517 -0
  13. pymodaq/control_modules/daq_viewer_ui.py +407 -0
  14. pymodaq/control_modules/mocks.py +57 -0
  15. pymodaq/control_modules/move_utility_classes.py +1141 -0
  16. pymodaq/control_modules/thread_commands.py +137 -0
  17. pymodaq/control_modules/ui_utils.py +72 -0
  18. pymodaq/control_modules/utils.py +591 -0
  19. pymodaq/control_modules/viewer_utility_classes.py +670 -0
  20. pymodaq/daq_utils/__init__.py +0 -0
  21. pymodaq/daq_utils/daq_utils.py +6 -0
  22. pymodaq/dashboard.py +2396 -0
  23. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Client.aliases +3 -0
  24. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Client.lvlps +3 -0
  25. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Client.lvproj +32 -0
  26. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Client.vi +0 -0
  27. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Server_1Dgaussian.vi +0 -0
  28. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Server_2Dgaussian.vi +0 -0
  29. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_read_cmd.vi +0 -0
  30. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_read_float.vi +0 -0
  31. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_read_int.vi +0 -0
  32. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_send_data.vi +0 -0
  33. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_send_int.vi +0 -0
  34. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_send_scalar.vi +0 -0
  35. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_send_string.vi +0 -0
  36. pymodaq/examples/Labview_TCP_Client/client_state.ctl +0 -0
  37. pymodaq/examples/Labview_TCP_Client/cmd_types.ctl +0 -0
  38. pymodaq/examples/__init__.py +0 -0
  39. pymodaq/examples/function_plotter.py +160 -0
  40. pymodaq/examples/nonlinearscanner.py +126 -0
  41. pymodaq/examples/qt_less_standalone_module.py +165 -0
  42. pymodaq/examples/tcp_client.py +97 -0
  43. pymodaq/extensions/__init__.py +25 -0
  44. pymodaq/extensions/adaptive/__init__.py +2 -0
  45. pymodaq/extensions/adaptive/adaptive_optimization.py +179 -0
  46. pymodaq/extensions/adaptive/loss_function/_1d_loss_functions.py +73 -0
  47. pymodaq/extensions/adaptive/loss_function/_2d_loss_functions.py +73 -0
  48. pymodaq/extensions/adaptive/loss_function/__init__.py +3 -0
  49. pymodaq/extensions/adaptive/loss_function/loss_factory.py +110 -0
  50. pymodaq/extensions/adaptive/utils.py +123 -0
  51. pymodaq/extensions/bayesian/__init__.py +2 -0
  52. pymodaq/extensions/bayesian/acquisition/__init__.py +2 -0
  53. pymodaq/extensions/bayesian/acquisition/acquisition_function_factory.py +80 -0
  54. pymodaq/extensions/bayesian/acquisition/base_acquisition_function.py +105 -0
  55. pymodaq/extensions/bayesian/bayesian_optimization.py +143 -0
  56. pymodaq/extensions/bayesian/utils.py +180 -0
  57. pymodaq/extensions/console.py +73 -0
  58. pymodaq/extensions/daq_logger/__init__.py +1 -0
  59. pymodaq/extensions/daq_logger/abstract.py +52 -0
  60. pymodaq/extensions/daq_logger/daq_logger.py +519 -0
  61. pymodaq/extensions/daq_logger/db/__init__.py +0 -0
  62. pymodaq/extensions/daq_logger/db/db_logger.py +300 -0
  63. pymodaq/extensions/daq_logger/db/db_logger_models.py +100 -0
  64. pymodaq/extensions/daq_logger/h5logging.py +84 -0
  65. pymodaq/extensions/daq_scan.py +1218 -0
  66. pymodaq/extensions/daq_scan_ui.py +241 -0
  67. pymodaq/extensions/data_mixer/__init__.py +0 -0
  68. pymodaq/extensions/data_mixer/daq_0Dviewer_DataMixer.py +97 -0
  69. pymodaq/extensions/data_mixer/data_mixer.py +262 -0
  70. pymodaq/extensions/data_mixer/model.py +108 -0
  71. pymodaq/extensions/data_mixer/models/__init__.py +0 -0
  72. pymodaq/extensions/data_mixer/models/equation_model.py +91 -0
  73. pymodaq/extensions/data_mixer/models/gaussian_fit_model.py +65 -0
  74. pymodaq/extensions/data_mixer/parser.py +53 -0
  75. pymodaq/extensions/data_mixer/utils.py +23 -0
  76. pymodaq/extensions/h5browser.py +9 -0
  77. pymodaq/extensions/optimizers_base/__init__.py +0 -0
  78. pymodaq/extensions/optimizers_base/optimizer.py +1016 -0
  79. pymodaq/extensions/optimizers_base/thread_commands.py +22 -0
  80. pymodaq/extensions/optimizers_base/utils.py +427 -0
  81. pymodaq/extensions/pid/__init__.py +16 -0
  82. pymodaq/extensions/pid/actuator_controller.py +14 -0
  83. pymodaq/extensions/pid/daq_move_PID.py +154 -0
  84. pymodaq/extensions/pid/pid_controller.py +1016 -0
  85. pymodaq/extensions/pid/utils.py +189 -0
  86. pymodaq/extensions/utils.py +111 -0
  87. pymodaq/icon.ico +0 -0
  88. pymodaq/post_treatment/__init__.py +6 -0
  89. pymodaq/post_treatment/load_and_plot.py +352 -0
  90. pymodaq/resources/__init__.py +0 -0
  91. pymodaq/resources/config_template.toml +57 -0
  92. pymodaq/resources/preset_default.xml +1 -0
  93. pymodaq/resources/setup_plugin.py +73 -0
  94. pymodaq/splash.png +0 -0
  95. pymodaq/utils/__init__.py +0 -0
  96. pymodaq/utils/array_manipulation.py +6 -0
  97. pymodaq/utils/calibration_camera.py +180 -0
  98. pymodaq/utils/chrono_timer.py +203 -0
  99. pymodaq/utils/config.py +53 -0
  100. pymodaq/utils/conftests.py +5 -0
  101. pymodaq/utils/daq_utils.py +158 -0
  102. pymodaq/utils/data.py +128 -0
  103. pymodaq/utils/enums.py +6 -0
  104. pymodaq/utils/exceptions.py +38 -0
  105. pymodaq/utils/gui_utils/__init__.py +10 -0
  106. pymodaq/utils/gui_utils/loader_utils.py +75 -0
  107. pymodaq/utils/gui_utils/utils.py +18 -0
  108. pymodaq/utils/gui_utils/widgets/lcd.py +8 -0
  109. pymodaq/utils/h5modules/__init__.py +2 -0
  110. pymodaq/utils/h5modules/module_saving.py +526 -0
  111. pymodaq/utils/leco/__init__.py +25 -0
  112. pymodaq/utils/leco/daq_move_LECODirector.py +217 -0
  113. pymodaq/utils/leco/daq_xDviewer_LECODirector.py +163 -0
  114. pymodaq/utils/leco/director_utils.py +74 -0
  115. pymodaq/utils/leco/leco_director.py +166 -0
  116. pymodaq/utils/leco/pymodaq_listener.py +364 -0
  117. pymodaq/utils/leco/rpc_method_definitions.py +43 -0
  118. pymodaq/utils/leco/utils.py +74 -0
  119. pymodaq/utils/logger.py +6 -0
  120. pymodaq/utils/managers/__init__.py +0 -0
  121. pymodaq/utils/managers/batchscan_manager.py +346 -0
  122. pymodaq/utils/managers/modules_manager.py +589 -0
  123. pymodaq/utils/managers/overshoot_manager.py +242 -0
  124. pymodaq/utils/managers/preset_manager.py +229 -0
  125. pymodaq/utils/managers/preset_manager_utils.py +262 -0
  126. pymodaq/utils/managers/remote_manager.py +484 -0
  127. pymodaq/utils/math_utils.py +6 -0
  128. pymodaq/utils/messenger.py +6 -0
  129. pymodaq/utils/parameter/__init__.py +10 -0
  130. pymodaq/utils/parameter/utils.py +6 -0
  131. pymodaq/utils/scanner/__init__.py +5 -0
  132. pymodaq/utils/scanner/scan_config.py +16 -0
  133. pymodaq/utils/scanner/scan_factory.py +259 -0
  134. pymodaq/utils/scanner/scan_selector.py +477 -0
  135. pymodaq/utils/scanner/scanner.py +324 -0
  136. pymodaq/utils/scanner/scanners/_1d_scanners.py +174 -0
  137. pymodaq/utils/scanner/scanners/_2d_scanners.py +299 -0
  138. pymodaq/utils/scanner/scanners/__init__.py +1 -0
  139. pymodaq/utils/scanner/scanners/sequential.py +224 -0
  140. pymodaq/utils/scanner/scanners/tabular.py +319 -0
  141. pymodaq/utils/scanner/utils.py +110 -0
  142. pymodaq/utils/svg/__init__.py +6 -0
  143. pymodaq/utils/svg/svg_renderer.py +20 -0
  144. pymodaq/utils/svg/svg_view.py +35 -0
  145. pymodaq/utils/svg/svg_viewer2D.py +50 -0
  146. pymodaq/utils/tcp_ip/__init__.py +6 -0
  147. pymodaq/utils/tcp_ip/mysocket.py +12 -0
  148. pymodaq/utils/tcp_ip/serializer.py +13 -0
  149. pymodaq/utils/tcp_ip/tcp_server_client.py +772 -0
  150. pymodaq-5.1.6.dist-info/METADATA +238 -0
  151. pymodaq-5.1.6.dist-info/RECORD +154 -0
  152. pymodaq-5.1.6.dist-info/WHEEL +4 -0
  153. pymodaq-5.1.6.dist-info/entry_points.txt +7 -0
  154. pymodaq-5.1.6.dist-info/licenses/LICENSE +21 -0
pymodaq/dashboard.py ADDED
@@ -0,0 +1,2396 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import sys
5
+ import datetime
6
+ import subprocess
7
+ import logging
8
+ from pathlib import Path
9
+ from importlib import import_module
10
+ from packaging import version as version_mod
11
+ from typing import Tuple, Union, List, Any, TYPE_CHECKING, Sequence
12
+ import argparse
13
+
14
+
15
+ from qtpy import QtGui, QtWidgets, QtCore
16
+ from qtpy.QtCore import Qt, QThread, Signal, QSize
17
+ from qtpy.QtWidgets import (
18
+ QTableWidget,
19
+ QTableWidgetItem,
20
+ QLabel,
21
+ QDialogButtonBox,
22
+ QMessageBox,
23
+ )
24
+ from time import perf_counter
25
+ import numpy as np
26
+
27
+ from pymodaq_plugin_manager.manager import PluginManager
28
+ from pymodaq_plugin_manager.validate import get_pypi_pymodaq
29
+
30
+ from pymodaq_utils.logger import set_logger, get_module_name
31
+ from pymodaq_utils import utils
32
+ from pymodaq_utils.utils import get_version, find_dict_in_list_from_key_val
33
+ from pymodaq_utils import config as configmod
34
+ from pymodaq_utils.enums import BaseEnum
35
+
36
+ from pymodaq_gui.parameter import ParameterTree, Parameter
37
+ from pymodaq_gui.utils import DockArea, Dock, select_file
38
+ import pymodaq_gui.utils.layout as layout_mod
39
+ from pymodaq_gui.messenger import messagebox
40
+ from pymodaq_gui.parameter import utils as putils
41
+ from pymodaq_gui.managers.roi_manager import ROISaver
42
+ from pymodaq_gui.utils.custom_app import CustomApp
43
+
44
+ from pymodaq.utils.managers.modules_manager import ModulesManager
45
+ from pymodaq.utils.managers.preset_manager import PresetManager
46
+ from pymodaq.utils.managers.overshoot_manager import OvershootManager
47
+ from pymodaq.utils.managers.remote_manager import RemoteManager
48
+ from pymodaq.utils.exceptions import DetectorError, ActuatorError, MasterSlaveError
49
+ from pymodaq.utils.daq_utils import get_instrument_plugins
50
+ from pymodaq.utils.leco.utils import start_coordinator
51
+ from pymodaq.utils import config as config_mod_pymodaq
52
+
53
+ from pymodaq.control_modules.daq_move import DAQ_Move
54
+ from pymodaq.control_modules.daq_viewer import DAQ_Viewer
55
+ from pymodaq.control_modules.daq_move_ui.factory import ActuatorUIFactory
56
+ from pymodaq_gui.utils.splash import get_splash_sc
57
+ from pymodaq import extensions as extmod
58
+ from pymodaq.utils.config import Config as ControlModulesConfig
59
+
60
+
61
+ logger = set_logger(get_module_name(__file__))
62
+
63
+ config_utils = configmod.Config()
64
+ config = ControlModulesConfig()
65
+
66
+
67
+ get_instrument_plugins()
68
+ extensions = extmod.get_extensions()
69
+
70
+
71
+ local_path = configmod.get_set_local_dir()
72
+ now = datetime.datetime.now()
73
+ preset_path = config_mod_pymodaq.get_set_preset_path()
74
+ log_path = configmod.get_set_log_path()
75
+ layout_path = config_mod_pymodaq.get_set_layout_path()
76
+ overshoot_path = config_mod_pymodaq.get_set_overshoot_path()
77
+ roi_path = config_mod_pymodaq.get_set_roi_path()
78
+ remote_path = config_mod_pymodaq.get_set_remote_path()
79
+
80
+
81
+ class ManagerEnums(BaseEnum):
82
+ preset = 0
83
+ remote = 1
84
+ overshoot = 2
85
+ roi = 3
86
+
87
+
88
+ class PymodaqUpdateTableWidget(QTableWidget):
89
+ """
90
+ A class to represent PyMoDAQ and its subpackages'
91
+ available updates as a table.
92
+ """
93
+
94
+ def __init__(self):
95
+ super().__init__()
96
+ self._row = 0
97
+
98
+ def setHorizontalHeaderLabels(self, labels):
99
+ super().setHorizontalHeaderLabels(labels)
100
+ self.setColumnCount(len(labels))
101
+
102
+ def append_row(self, package, current_version, available_version):
103
+ # Add labels
104
+ self.setItem(self._row, 0, QTableWidgetItem(str(package)))
105
+ self.setItem(self._row, 1, QTableWidgetItem(str(current_version)))
106
+ self.setItem(self._row, 2, QTableWidgetItem(str(available_version)))
107
+
108
+ self._row += 1
109
+
110
+ def sizeHint(self):
111
+ self.resizeColumnsToContents()
112
+ self.resizeRowsToContents()
113
+
114
+ # Compute the size to adapt the window (header + borders + sum of all the elements)
115
+ width = (
116
+ self.verticalHeader().width()
117
+ + self.frameWidth() * 2
118
+ + sum([self.columnWidth(i) for i in range(self.columnCount())])
119
+ )
120
+
121
+ height = (
122
+ self.horizontalHeader().height()
123
+ + self.frameWidth() * 2
124
+ + sum([self.rowHeight(i) for i in range(self.rowCount())])
125
+ )
126
+
127
+ return QSize(width, height)
128
+
129
+
130
+ class DashBoard(CustomApp):
131
+ """
132
+ Main class initializing a DashBoard interface to display det and move modules and logger"""
133
+
134
+ status_signal = Signal(str)
135
+ preset_loaded_signal = Signal(bool)
136
+ new_preset_created = Signal()
137
+
138
+ settings_name = "dashboard_settings"
139
+ _splash_sc = None
140
+
141
+ params = [
142
+ {
143
+ "title": "Log level",
144
+ "name": "log_level",
145
+ "type": "list",
146
+ "value": config_utils("general", "debug_levels")[0],
147
+ "limits": config_utils("general", "debug_levels"),
148
+ },
149
+ {
150
+ "title": "Loaded presets",
151
+ "name": "loaded_files",
152
+ "type": "group",
153
+ "children": [
154
+ {
155
+ "title": "Preset file",
156
+ "name": "preset_file",
157
+ "type": "str",
158
+ "value": "",
159
+ "readonly": True,
160
+ },
161
+ {
162
+ "title": "Overshoot file",
163
+ "name": "overshoot_file",
164
+ "type": "str",
165
+ "value": "",
166
+ "readonly": True,
167
+ },
168
+ {
169
+ "title": "Layout file",
170
+ "name": "layout_file",
171
+ "type": "str",
172
+ "value": "",
173
+ "readonly": True,
174
+ },
175
+ {
176
+ "title": "ROI file",
177
+ "name": "roi_file",
178
+ "type": "str",
179
+ "value": "",
180
+ "readonly": True,
181
+ },
182
+ {
183
+ "title": "Remote file",
184
+ "name": "remote_file",
185
+ "type": "str",
186
+ "value": "",
187
+ "readonly": True,
188
+ },
189
+ ],
190
+ },
191
+ {
192
+ "title": "Actuators Init.",
193
+ "name": "actuators",
194
+ "type": "group",
195
+ "children": [],
196
+ },
197
+ {
198
+ "title": "Detectors Init.",
199
+ "name": "detectors",
200
+ "type": "group",
201
+ "children": [],
202
+ },
203
+ ]
204
+
205
+ def __init__(self, dockarea):
206
+ """
207
+
208
+ Parameters
209
+ ----------
210
+ parent: (dockarea) instance of the modified pyqtgraph Dockarea (see daq_utils)
211
+ """
212
+
213
+ super().__init__(dockarea)
214
+
215
+ logger.info("Initializing Dashboard")
216
+ self.extra_params = []
217
+ self.preset_path = preset_path
218
+ self.wait_time = 1000
219
+ self.scan_module = None
220
+ self.log_module = None
221
+ self.pid_module = None
222
+ self.pid_window = None
223
+ self.retriever_module = None
224
+ self.database_module = None
225
+ self.extensions = dict([])
226
+ self.extension_windows = []
227
+
228
+ self.dockarea.dock_signal.connect(self.save_layout_state_auto)
229
+
230
+ self.title = ""
231
+
232
+ self.overshoot_manager = None
233
+ self.preset_manager = None
234
+ self.roi_saver: ROISaver = None
235
+
236
+ self.remote_timer = QtCore.QTimer()
237
+ self.remote_manager = None
238
+ self.shortcuts = dict([])
239
+ self.joysticks = dict([])
240
+ self.ispygame_init = False
241
+
242
+ self.modules_manager: ModulesManager = None
243
+
244
+ self.overshoot = False
245
+ self.preset_file = None
246
+ self.actuators_modules = []
247
+ self.detector_modules = []
248
+
249
+ self.compact_actuator_dock: Dock = None
250
+
251
+ self.setup_ui()
252
+
253
+ self.mainwindow.setVisible(True)
254
+
255
+ logger.info("Dashboard Initialized")
256
+
257
+ if config_utils("general", "check_version"):
258
+ if self.check_update(show=False):
259
+ sys.exit(0)
260
+
261
+ @property
262
+ def splash_sc(self) -> QtWidgets.QSplashScreen:
263
+ if not hasattr(self, "_splash_sc") or self._splash_sc is None:
264
+ self._splash_sc = get_splash_sc()
265
+ return self._splash_sc
266
+
267
+ def set_preset_path(self, path):
268
+ self.preset_path = path
269
+ self.set_extra_preset_params(self.extra_params)
270
+ self.create_menu(self.menubar)
271
+
272
+ def set_extra_preset_params(self, params, param_options=[]):
273
+ self.extra_params = params
274
+ self.preset_manager = PresetManager(
275
+ path=self.preset_path, extra_params=params, param_options=param_options
276
+ )
277
+
278
+ def add_status(self, txt):
279
+ """
280
+ Add the QListWisgetItem initialized with txt informations to the User Interface
281
+ logger_list and to the save_parameters.logger array.
282
+
283
+ =============== =========== ======================
284
+ **Parameters** **Type** **Description**
285
+ *txt* string the log info to add.
286
+ =============== =========== ======================
287
+ """
288
+ try:
289
+ now = datetime.datetime.now()
290
+ new_item = QtWidgets.QListWidgetItem(
291
+ now.strftime("%Y/%m/%d %H:%M:%S") + ": " + txt
292
+ )
293
+ self.logger_list.addItem(new_item)
294
+
295
+ except Exception as e:
296
+ logger.exception(str(e))
297
+
298
+ def remove_detectors(self, detector_modules: List[DAQ_Viewer] = None):
299
+ """
300
+ Remove the given list of detectors from the dashboard.
301
+ Parameters
302
+ ----------
303
+ detector_modules: List[DAQ_Viewer]
304
+ List of DAQ_Viewer instances to be removed.
305
+ """
306
+ if detector_modules is None:
307
+ detector_modules = []
308
+ try:
309
+ for detector_module in detector_modules:
310
+ if detector_module in self.detector_modules:
311
+ self.detector_modules.remove(detector_module)
312
+ detector_module.quit_fun()
313
+ dock = self.dockarea.docks.get(
314
+ f"{detector_module.title} settings", None
315
+ )
316
+ if dock:
317
+ dock.close()
318
+ dock = self.dockarea.docks.get(f"{detector_module.title} viewer", None)
319
+ if dock:
320
+ dock.close()
321
+ self.update_module_manager()
322
+ except Exception as e:
323
+ logger.exception(str(e))
324
+
325
+ def remove_actuators(self, actuator_modules: List[DAQ_Move] = None):
326
+ """
327
+ Remove the given list of actuators from the dashboard.
328
+ Parameters
329
+ ----------
330
+ actuator_modules: List[DAQ_Move]
331
+ List of DAQ_Move instances to be removed.
332
+ """
333
+ if actuator_modules is None:
334
+ actuator_modules = []
335
+ try:
336
+ for actuator_module in actuator_modules:
337
+ if actuator_module in self.actuators_modules:
338
+ self.actuators_modules.remove(actuator_module)
339
+ actuator_module.quit_fun()
340
+ dock = self.dockarea.docks.get(actuator_module.title, None)
341
+ if dock:
342
+ dock.close()
343
+ self.update_module_manager()
344
+ except Exception as e:
345
+ logger.exception(str(e))
346
+
347
+ def get_docks_from_modules(
348
+ self, modules: Sequence[Union["DAQ_Move", "DAQ_Viewer"]]
349
+ ) -> List[Dock]:
350
+ """
351
+ Get a list of Dock instances from the given modules.
352
+
353
+ Parameters
354
+ ----------
355
+ modules: Sequence[DAQ_Move/DAQ_Viewer]
356
+ Sequence of DAQ_Move or DAQ_Viewer instances.
357
+
358
+ Returns
359
+ -------
360
+ List[Dock]
361
+ List of Dock instances corresponding to the given modules.
362
+ """
363
+ docks = []
364
+ for module in modules:
365
+ if hasattr(module, "dock"):
366
+ docks.append(module.dock)
367
+ return docks
368
+
369
+ def remove_modules(
370
+ self, modules: List[Union["DAQ_Move", "DAQ_Viewer", "str"]] = None
371
+ ):
372
+ """
373
+ Remove the given list of actuators/detectors from the dashboard.
374
+
375
+ Parameters
376
+ ----------
377
+ modules: List[DAQ_Move/DAQ_Viewer]
378
+ List of DAQ_Move/DAQ_Viewer instances to be removed.
379
+ """
380
+ if modules is None:
381
+ modules = []
382
+ try:
383
+ actuators_modules = []
384
+ detector_modules = []
385
+ for module in modules:
386
+ if isinstance(
387
+ module, DAQ_Move
388
+ ): # Test if module is an instance of DAQ_Move
389
+ actuators_modules.append(module)
390
+ elif isinstance(
391
+ module, DAQ_Viewer
392
+ ): # Test if module is an instance of DAQ_Viewer
393
+ detector_modules.append(module)
394
+ if isinstance(
395
+ module, str
396
+ ): # Test if module is a string (name of the module)
397
+ actuators_modules.extend(
398
+ self.modules_manager.get_mods_from_names(
399
+ [
400
+ module,
401
+ ],
402
+ "act",
403
+ ) # For actuators
404
+ )
405
+ detector_modules.extend(
406
+ self.modules_manager.get_mods_from_names(
407
+ [
408
+ module,
409
+ ],
410
+ "det",
411
+ ) # For detectors
412
+ )
413
+ if (hasattr(self, "actuators_modules")) & (
414
+ self.actuators_modules is not None
415
+ ): # Remove actuators
416
+ self.remove_actuators(actuators_modules)
417
+ if (hasattr(self, "detector_modules")) & (
418
+ self.detector_modules is not None
419
+ ): # Remove detectors
420
+ self.remove_detectors(detector_modules)
421
+ except Exception as e:
422
+ logger.exception(str(e))
423
+
424
+ def clear_move_det_controllers(self):
425
+ """
426
+ Remove all docks containing Moves or Viewers.
427
+
428
+ See Also
429
+ --------
430
+ quit_fun, update_status
431
+ """
432
+ try:
433
+ # remove all docks containing Moves or Viewers
434
+ if hasattr(self, "actuators_modules") & (
435
+ self.actuators_modules is not None
436
+ ):
437
+ for module in self.actuators_modules:
438
+ module.quit_fun()
439
+ self.actuators_modules = []
440
+
441
+ if hasattr(self, "detector_modules") & (self.detector_modules is not None):
442
+ for module in self.detector_modules:
443
+ module.quit_fun()
444
+ self.detector_modules = []
445
+ except Exception as e:
446
+ logger.exception(str(e))
447
+
448
+ def load_scan_module(self, win=None):
449
+ if win is None:
450
+ win = QtWidgets.QMainWindow()
451
+ area = DockArea()
452
+ win.setWindowFlags(
453
+ Qt.Window
454
+ | Qt.WindowTitleHint
455
+ | Qt.WindowMinimizeButtonHint
456
+ | Qt.WindowMaximizeButtonHint
457
+ )
458
+ win.setCentralWidget(area)
459
+ win.setWindowTitle("Scanner")
460
+ self.scan_module = extmod.DAQScan(dockarea=area, dashboard=self)
461
+ self.extensions["DAQScan"] = self.scan_module
462
+ self.scan_module.status_signal.connect(self.add_status)
463
+ # win.setWindowTitle("DAQScan")
464
+ win.show()
465
+ return self.scan_module
466
+
467
+ def load_log_module(self, win=None):
468
+ if win is None:
469
+ win = QtWidgets.QMainWindow()
470
+ area = DockArea()
471
+ win.setWindowFlags(
472
+ Qt.Window
473
+ | Qt.WindowTitleHint
474
+ | Qt.WindowMinimizeButtonHint
475
+ | Qt.WindowMaximizeButtonHint
476
+ )
477
+ win.setCentralWidget(area)
478
+ win.setWindowTitle("Logger")
479
+ self.log_module = extmod.DAQ_Logger(dockarea=area, dashboard=self)
480
+ self.extensions["DAQ_Logger"] = self.log_module
481
+ self.log_module.status_signal.connect(self.add_status)
482
+ win.show()
483
+ return self.log_module
484
+
485
+ def load_pid_module(self, win=None):
486
+ if win is None:
487
+ self.pid_window = QtWidgets.QMainWindow()
488
+ else:
489
+ self.pid_window = win
490
+ self.pid_window.setWindowFlags(
491
+ Qt.Window
492
+ | Qt.WindowTitleHint
493
+ | Qt.WindowMinimizeButtonHint
494
+ | Qt.WindowMaximizeButtonHint
495
+ )
496
+ dockarea = DockArea()
497
+ self.pid_window.setCentralWidget(dockarea)
498
+ self.pid_window.setWindowTitle("PID Controller")
499
+ self.pid_module = extmod.DAQ_PID(dockarea=dockarea, dashboard=self)
500
+ self.extensions["DAQ_PID"] = self.pid_module
501
+ self.pid_window.show()
502
+ return self.pid_module
503
+
504
+ def load_console(self):
505
+ dock_console = Dock("QTConsole")
506
+ self.dockarea.addDock(dock_console, "bottom")
507
+ qtconsole = extmod.QtConsole(
508
+ style_sheet=config_utils("style", "syntax_highlighting"),
509
+ syntax_style=config_utils("style", "syntax_highlighting"),
510
+ custom_banner=extmod.console.BANNER,
511
+ )
512
+ dock_console.addWidget(qtconsole)
513
+ self.extensions["qtconsole"] = qtconsole
514
+
515
+ qtconsole.push_variables(dict(dashboard=self, mods=self.modules_manager, np=np))
516
+
517
+ return qtconsole
518
+
519
+ def load_bayesian(self, win=None):
520
+ if win is None:
521
+ self.bayesian_window = QtWidgets.QMainWindow()
522
+ else:
523
+ self.bayesian_window = win
524
+ self.bayesian_window.setWindowFlags(
525
+ Qt.Window
526
+ | Qt.WindowTitleHint
527
+ | Qt.WindowMinimizeButtonHint
528
+ | Qt.WindowMaximizeButtonHint
529
+ )
530
+ dockarea = DockArea()
531
+ self.bayesian_window.setCentralWidget(dockarea)
532
+ self.bayesian_window.setWindowTitle("Bayesian Optimiser")
533
+ self.bayesian_module = extmod.BayesianOptimization(
534
+ dockarea=dockarea, dashboard=self
535
+ )
536
+ self.extensions["bayesian"] = self.bayesian_module
537
+
538
+ if self.bayesian_module.validate_config():
539
+ self.bayesian_window.show()
540
+ else:
541
+ messagebox(
542
+ severity="critical",
543
+ title="Bayesian Optimisation error",
544
+ text=f"""
545
+ <p>Saved Bayesian Optimisation configuration file is not compatible anymore.</p>
546
+ <p>Please delete the file at <b>{self.bayesian_module.config_path}</b>.</p>
547
+ """,
548
+ )
549
+ self.bayesian_module.quit()
550
+ return self.bayesian_module
551
+
552
+ def load_adaptive(self, win=None):
553
+ if win is None:
554
+ self.adaptive_window = QtWidgets.QMainWindow()
555
+ else:
556
+ self.adaptive_window = win
557
+ self.adaptive_window.setWindowFlags(
558
+ Qt.Window
559
+ | Qt.WindowTitleHint
560
+ | Qt.WindowMinimizeButtonHint
561
+ | Qt.WindowMaximizeButtonHint
562
+ )
563
+ dockarea = DockArea()
564
+ self.adaptive_window.setCentralWidget(dockarea)
565
+ self.adaptive_window.setWindowTitle("Adaptive Scan")
566
+ self.adaptive_module = extmod.AdaptiveOptimisation(
567
+ dockarea=dockarea, dashboard=self
568
+ )
569
+ self.extensions["adaptive"] = self.adaptive_module
570
+
571
+ if self.adaptive_module.validate_config():
572
+ self.adaptive_window.show()
573
+ else:
574
+ messagebox(
575
+ severity="critical",
576
+ title="Adaptive Optimisation error",
577
+ text=f"""
578
+ <p>Saved Adaptive Optimisation configuration file is not compatible anymore.</p>
579
+ <p>Please delete the file at <b>{self.adaptive_module.config_path}</b>.</p>
580
+ """,
581
+ )
582
+ self.adaptive_module.quit()
583
+ return self.adaptive_module
584
+
585
+ def load_datamixer(self, win=None):
586
+ if win is None:
587
+ self.datamixer_window = QtWidgets.QMainWindow()
588
+ else:
589
+ self.datamixer_window = win
590
+ self.datamixer_window.setWindowFlags(
591
+ Qt.Window
592
+ | Qt.WindowTitleHint
593
+ | Qt.WindowMinimizeButtonHint
594
+ | Qt.WindowMaximizeButtonHint
595
+ )
596
+ dockarea = DockArea()
597
+ self.datamixer_window.setCentralWidget(dockarea)
598
+ self.datamixer_window.setWindowTitle("DataMixer")
599
+ self.datamixer_module = extmod.DataMixer(
600
+ parent=dockarea, dashboard=self
601
+ )
602
+ self.extensions["datamixer"] = self.datamixer_module
603
+
604
+ if self.datamixer_module.validate_config():
605
+ self.datamixer_window.show()
606
+ else:
607
+ messagebox(
608
+ severity="critical",
609
+ title="DataMixer error",
610
+ text=f"""
611
+ <p>Saved DataMixer configuration file is not compatible anymore.</p>
612
+ <p>Please delete the file at <b>{self.datamixer_module.config_path}</b>.</p>
613
+ """,
614
+ )
615
+ self.datamixer_module.quit()
616
+ return self.datamixer_module
617
+
618
+
619
+ def load_extension_from_name(self, name: str) -> dict:
620
+ return self.load_extensions_module(
621
+ find_dict_in_list_from_key_val(extensions, "name", name)
622
+ )
623
+
624
+ def load_extensions_module(self, ext: dict):
625
+ """Init and load an extension from a plugin package
626
+
627
+ ext: dict
628
+ dictionary containing info on the extension plugin package and class to be loaded,
629
+ it contains four
630
+ keys:
631
+
632
+ * pkg: the name of the plugin package
633
+ * module: the module name where your extension class is defined
634
+ * class_name: the name of the class defining the extension
635
+ * name: a nice name for your extension to be displayed in the menu
636
+
637
+ See Also
638
+ --------
639
+ pymodaq.extensions.utils.get_extensions
640
+ """
641
+
642
+ self.extension_windows.append(QtWidgets.QMainWindow())
643
+ area = DockArea()
644
+ self.extension_windows[-1].setCentralWidget(area)
645
+ self.extension_windows[-1].resize(1000, 500)
646
+ self.extension_windows[-1].setWindowTitle(ext["name"])
647
+ module = import_module(f"{ext['pkg']}.extensions.{ext['module']}")
648
+ klass = getattr(module, ext["class_name"])
649
+ self.extensions[ext["class_name"]] = klass(area, dashboard=self)
650
+ self.extension_windows[-1].show()
651
+ return self.extensions[ext["class_name"]]
652
+
653
+ def setup_actions(self):
654
+ self.add_action(
655
+ "log", "Log File", "", "Show Log File in default editor", auto_toolbar=False
656
+ )
657
+ self.add_action("quit", "Quit", "close2", "Quit program")
658
+ self.toolbar.addSeparator()
659
+ self.add_action(
660
+ "config_utils",
661
+ "Utils Config.",
662
+ "tree",
663
+ tip="Show utility configuration file",
664
+ )
665
+ self.add_action(
666
+ "config",
667
+ "Controls/Extensions Config.",
668
+ "tree",
669
+ tip="Show Control Modules and Extensions configuration file",
670
+ )
671
+ self.add_action(
672
+ "restart", "Restart", "", "Restart the Dashboard", auto_toolbar=False
673
+ )
674
+ self.add_action(
675
+ "leco",
676
+ "Run Leco Coordinator",
677
+ "",
678
+ "Run a Coordinator on this localhost",
679
+ auto_toolbar=False,
680
+ )
681
+ self.add_action(
682
+ "load_layout",
683
+ "Load Layout",
684
+ "",
685
+ "Load the Saved Docks layout corresponding to the current preset",
686
+ auto_toolbar=False,
687
+ )
688
+ self.add_action(
689
+ "save_layout",
690
+ "Save Layout",
691
+ "",
692
+ "Save the Saved Docks layout corresponding to the current preset",
693
+ auto_toolbar=False,
694
+ )
695
+ self.add_action(
696
+ "log_window", "Show/hide log window", "", checkable=True, auto_toolbar=False
697
+ )
698
+ self.add_action(
699
+ "new_preset",
700
+ "New Preset",
701
+ "",
702
+ 'Create a new experimental setup configuration file: a "preset"',
703
+ auto_toolbar=False,
704
+ )
705
+ self.add_action(
706
+ "modify_preset",
707
+ "Modify Preset",
708
+ "",
709
+ 'Modify an existing experimental setup configuration file: a "preset"',
710
+ auto_toolbar=False,
711
+ )
712
+
713
+ self.add_widget(
714
+ "preset_list",
715
+ QtWidgets.QComboBox,
716
+ toolbar=self.toolbar,
717
+ signal_str="currentTextChanged",
718
+ slot=self.update_preset_action,
719
+ )
720
+ self.add_action("load_preset", "LOAD", "Open", tip="Load the selected Preset: ")
721
+ self.update_preset_action_list()
722
+
723
+ self.add_action(
724
+ "new_overshoot",
725
+ "New Overshoot",
726
+ "",
727
+ "Create a new experimental setup overshoot configuration file",
728
+ auto_toolbar=False,
729
+ )
730
+ self.add_action(
731
+ "modify_overshoot",
732
+ "Modify Overshoot",
733
+ "",
734
+ "Modify an existing experimental setup overshoot configuration file",
735
+ auto_toolbar=False,
736
+ )
737
+
738
+ for ind_file, file in enumerate(
739
+ config_mod_pymodaq.get_set_overshoot_path().iterdir()
740
+ ):
741
+ if file.suffix == ".xml":
742
+ self.add_action(
743
+ self.get_action_from_file(file, ManagerEnums.overshoot),
744
+ file.stem,
745
+ auto_toolbar=False,
746
+ )
747
+
748
+ self.add_action("save_roi", "Save ROIs as a file", "", auto_toolbar=False)
749
+ self.add_action("modify_roi", "Modify ROI file", "", auto_toolbar=False)
750
+
751
+ for ind_file, file in enumerate(
752
+ config_mod_pymodaq.get_set_roi_path().iterdir()
753
+ ):
754
+ if file.suffix == ".xml":
755
+ self.add_action(
756
+ self.get_action_from_file(file, ManagerEnums.roi),
757
+ file.stem,
758
+ "",
759
+ auto_toolbar=False,
760
+ )
761
+
762
+ self.add_action("new_remote", "Create New Remote", "", auto_toolbar=False)
763
+ self.add_action("modify_remote", "Modify Remote file", "", auto_toolbar=False)
764
+ for ind_file, file in enumerate(
765
+ config_mod_pymodaq.get_set_remote_path().iterdir()
766
+ ):
767
+ if file.suffix == ".xml":
768
+ self.add_action(
769
+ self.get_action_from_file(file, ManagerEnums.remote),
770
+ file.stem,
771
+ "",
772
+ auto_toolbar=False,
773
+ )
774
+ self.add_action(
775
+ "activate_overshoot",
776
+ "Activate overshoot",
777
+ "Error",
778
+ tip="if activated, apply an overshoot if one is configured",
779
+ checkable=True,
780
+ enabled=False,
781
+ )
782
+ self.toolbar.addSeparator()
783
+ self.add_action(
784
+ "do_scan",
785
+ "Do Scans",
786
+ "surfacePlot",
787
+ tip="Open the DAQ Scan extension to acquire data as a function of "
788
+ "one or more parameter",
789
+ )
790
+ self.toolbar.addSeparator()
791
+ self.add_action("do_log", "Log data", "", auto_toolbar=False)
792
+ self.add_action("do_pid", "PID module", auto_toolbar=False)
793
+ self.add_action("console", "IPython Console", auto_toolbar=False)
794
+ self.add_action("bayesian", "Bayesian Optimisation", auto_toolbar=False)
795
+ self.add_action("adaptive", "Adaptive Scan", auto_toolbar=False)
796
+ self.add_action("datamixer", "DataMixer", auto_toolbar=False)
797
+
798
+ self.add_action("about", "About", "information2")
799
+ self.add_action("help", "Help", "help1")
800
+ self.get_action("help").setShortcut(QtGui.QKeySequence("F1"))
801
+ self.add_action("check_update", "Check Updates", "", auto_toolbar=False)
802
+ self.toolbar.addSeparator()
803
+ self.add_action("plugin_manager", "Plugin Manager", "")
804
+
805
+ def update_preset_action_list(self):
806
+ presets = []
807
+ self.get_action("preset_list").clear()
808
+ for ind_file, file in enumerate(self.preset_path.iterdir()):
809
+ if file.suffix == ".xml":
810
+ filestem = file.stem
811
+ if not self.has_action(
812
+ self.get_action_from_file(file, ManagerEnums.preset)
813
+ ):
814
+ self.add_action(
815
+ self.get_action_from_file(file, ManagerEnums.preset),
816
+ filestem,
817
+ "",
818
+ f"Load the {filestem}.xml preset",
819
+ auto_toolbar=False,
820
+ )
821
+ presets.append(filestem)
822
+
823
+ self.get_action("preset_list").addItems(presets)
824
+
825
+ def update_preset_action(self, preset_name: str):
826
+ self.get_action("load_preset").setToolTip(
827
+ f"Load the {preset_name}.xml preset file!"
828
+ )
829
+
830
+ def connect_things(self):
831
+ self.status_signal[str].connect(self.add_status)
832
+ self.connect_action("log", self.show_log)
833
+ self.connect_action("config_utils", lambda: self.show_config(config_utils))
834
+ self.connect_action("config", lambda: self.show_config(config))
835
+ self.connect_action("quit", self.quit_fun)
836
+ self.connect_action("restart", self.restart_fun)
837
+ self.connect_action("leco", start_coordinator)
838
+ self.connect_action("load_layout", self.load_layout_state)
839
+ self.connect_action("save_layout", self.save_layout_state)
840
+ self.connect_action("log_window", self.logger_dock.setVisible)
841
+ self.connect_action("new_preset", self.create_preset)
842
+ self.connect_action("modify_preset", self.modify_preset)
843
+
844
+ for ind_file, file in enumerate(self.preset_path.iterdir()):
845
+ if file.suffix == ".xml":
846
+ self.connect_action(
847
+ self.get_action_from_file(file, ManagerEnums.preset),
848
+ self.create_menu_slot(self.preset_path.joinpath(file)),
849
+ )
850
+ self.connect_action(
851
+ "load_preset",
852
+ lambda: self.set_preset_mode(
853
+ self.preset_path.joinpath(
854
+ f"{self.get_action('preset_list').currentText()}.xml"
855
+ )
856
+ ),
857
+ )
858
+ self.connect_action("new_overshoot", self.create_overshoot)
859
+ self.connect_action("modify_overshoot", self.modify_overshoot)
860
+ self.connect_action("activate_overshoot", self.activate_overshoot)
861
+
862
+ for ind_file, file in enumerate(
863
+ config_mod_pymodaq.get_set_overshoot_path().iterdir()
864
+ ):
865
+ if file.suffix == ".xml":
866
+ self.connect_action(
867
+ self.get_action_from_file(file, ManagerEnums.overshoot),
868
+ self.create_menu_slot_over(
869
+ config_mod_pymodaq.get_set_overshoot_path().joinpath(file)
870
+ ),
871
+ )
872
+
873
+ self.connect_action("save_roi", self.create_roi_file)
874
+ self.connect_action("modify_roi", self.modify_roi)
875
+
876
+ for ind_file, file in enumerate(
877
+ config_mod_pymodaq.get_set_roi_path().iterdir()
878
+ ):
879
+ if file.suffix == ".xml":
880
+ self.connect_action(
881
+ self.get_action_from_file(file, ManagerEnums.roi),
882
+ self.create_menu_slot_roi(
883
+ config_mod_pymodaq.get_set_roi_path().joinpath(file)
884
+ ),
885
+ )
886
+
887
+ self.connect_action("new_remote", self.create_remote)
888
+ self.connect_action("modify_remote", self.modify_remote)
889
+ for ind_file, file in enumerate(
890
+ config_mod_pymodaq.get_set_remote_path().iterdir()
891
+ ):
892
+ if file.suffix == ".xml":
893
+ self.connect_action(
894
+ self.get_action_from_file(file, ManagerEnums.remote),
895
+ self.create_menu_slot_remote(
896
+ config_mod_pymodaq.get_set_remote_path().joinpath(file)
897
+ ),
898
+ )
899
+
900
+ self.connect_action("do_scan", lambda: self.load_scan_module())
901
+ self.connect_action("do_log", lambda: self.load_log_module())
902
+ self.connect_action("do_pid", lambda: self.load_pid_module())
903
+ self.connect_action("console", lambda: self.load_console())
904
+ self.connect_action("bayesian", lambda: self.load_bayesian())
905
+ self.connect_action("adaptive", lambda: self.load_adaptive())
906
+ self.connect_action("datamixer", lambda: self.load_datamixer())
907
+
908
+ self.connect_action("about", self.show_about)
909
+ self.connect_action("help", self.show_help)
910
+ self.connect_action("check_update", lambda: self.check_update(True))
911
+ self.connect_action("plugin_manager", self.start_plugin_manager)
912
+
913
+ def setup_menu(self, menubar: QtWidgets.QMenuBar = None):
914
+ """
915
+ Create the menubar object looking like :
916
+ """
917
+ menubar.clear()
918
+
919
+ # %% create Settings menu
920
+ self.file_menu = menubar.addMenu("File")
921
+ self.file_menu.addAction(self.get_action("log"))
922
+ self.file_menu.addAction(self.get_action("config_utils"))
923
+ self.file_menu.addAction(self.get_action("config"))
924
+ self.file_menu.addSeparator()
925
+ self.file_menu.addAction(self.get_action("quit"))
926
+ self.file_menu.addAction(self.get_action("restart"))
927
+
928
+ self.settings_menu = menubar.addMenu("Settings")
929
+ self.settings_menu.addAction(self.get_action("leco"))
930
+ docked_menu = self.settings_menu.addMenu("Docked windows")
931
+ docked_menu.addAction(self.get_action("load_layout"))
932
+ docked_menu.addAction(self.get_action("save_layout"))
933
+
934
+ docked_menu.addSeparator()
935
+ docked_menu.addAction(self.get_action("log_window"))
936
+
937
+ self.preset_menu = menubar.addMenu("Preset Modes")
938
+ self.preset_menu.addAction(self.get_action("new_preset"))
939
+ self.preset_menu.addAction(self.get_action("modify_preset"))
940
+ self.preset_menu.addSeparator()
941
+ self.load_preset_menu = self.preset_menu.addMenu("Load presets")
942
+
943
+ for ind_file, file in enumerate(self.preset_path.iterdir()):
944
+ if file.suffix == ".xml":
945
+ self.load_preset_menu.addAction(
946
+ self.get_action(
947
+ self.get_action_from_file(file, ManagerEnums.preset)
948
+ )
949
+ )
950
+
951
+ self.overshoot_menu = menubar.addMenu("Overshoot Modes")
952
+ self.overshoot_menu.addAction(self.get_action("new_overshoot"))
953
+ self.overshoot_menu.addAction(self.get_action("modify_overshoot"))
954
+ self.overshoot_menu.addAction(self.get_action("activate_overshoot"))
955
+ self.overshoot_menu.addSeparator()
956
+ load_overshoot_menu = self.overshoot_menu.addMenu("Load Overshoots")
957
+
958
+ for ind_file, file in enumerate(
959
+ config_mod_pymodaq.get_set_overshoot_path().iterdir()
960
+ ):
961
+ if file.suffix == ".xml":
962
+ load_overshoot_menu.addAction(
963
+ self.get_action(
964
+ self.get_action_from_file(file, ManagerEnums.overshoot)
965
+ )
966
+ )
967
+
968
+ self.roi_menu = menubar.addMenu("ROI Modes")
969
+ self.roi_menu.addAction(self.get_action("save_roi"))
970
+ self.roi_menu.addAction(self.get_action("modify_roi"))
971
+ self.roi_menu.addSeparator()
972
+ load_roi_menu = self.roi_menu.addMenu("Load roi configs")
973
+
974
+ for ind_file, file in enumerate(
975
+ config_mod_pymodaq.get_set_roi_path().iterdir()
976
+ ):
977
+ if file.suffix == ".xml":
978
+ load_roi_menu.addAction(
979
+ self.get_action(self.get_action_from_file(file, ManagerEnums.roi))
980
+ )
981
+
982
+ self.remote_menu = menubar.addMenu("Remote/Shortcuts Control")
983
+ self.remote_menu.addAction("New remote config.", self.create_remote)
984
+ self.remote_menu.addAction("Modify remote config.", self.modify_remote)
985
+ self.remote_menu.addSeparator()
986
+ load_remote_menu = self.remote_menu.addMenu("Load remote config.")
987
+
988
+ for ind_file, file in enumerate(
989
+ config_mod_pymodaq.get_set_remote_path().iterdir()
990
+ ):
991
+ if file.suffix == ".xml":
992
+ load_remote_menu.addAction(
993
+ self.get_action(
994
+ self.get_action_from_file(file, ManagerEnums.remote)
995
+ )
996
+ )
997
+
998
+ # extensions menu
999
+ self.extensions_menu = menubar.addMenu("Extensions")
1000
+ self.extensions_menu.addAction(self.get_action("do_scan"))
1001
+ self.extensions_menu.addAction(self.get_action("do_log"))
1002
+ self.extensions_menu.addAction(self.get_action("do_pid"))
1003
+ self.extensions_menu.addAction(self.get_action("console"))
1004
+ self.extensions_menu.addAction(self.get_action("bayesian"))
1005
+ self.extensions_menu.addAction(self.get_action("adaptive"))
1006
+ self.extensions_menu.addAction(self.get_action("datamixer"))
1007
+
1008
+ # extensions from plugins
1009
+ extensions_actions = []
1010
+ for ext in extensions:
1011
+ extensions_actions.append(self.extensions_menu.addAction(ext["name"]))
1012
+ extensions_actions[-1].triggered.connect(self.create_menu_slot_ext(ext))
1013
+
1014
+ # help menu
1015
+ help_menu = menubar.addMenu("?")
1016
+ help_menu.addAction(self.get_action("about"))
1017
+ help_menu.addAction(self.get_action("help"))
1018
+ help_menu.addSeparator()
1019
+ help_menu.addAction(self.get_action("check_update"))
1020
+ help_menu.addAction(self.get_action("plugin_manager"))
1021
+
1022
+ status = self.preset_file is None
1023
+
1024
+ self.overshoot_menu.setEnabled(not status)
1025
+ self.roi_menu.setEnabled(not status)
1026
+ self.remote_menu.setEnabled(not status)
1027
+ self.extensions_menu.setEnabled(not status)
1028
+ self.file_menu.setEnabled(True)
1029
+ self.settings_menu.setEnabled(True)
1030
+ self.preset_menu.setEnabled(status)
1031
+
1032
+ def start_plugin_manager(self):
1033
+ self.win_plug_manager = QtWidgets.QMainWindow()
1034
+ self.win_plug_manager.setWindowTitle("PyMoDAQ Plugin Manager")
1035
+ widget = QtWidgets.QWidget()
1036
+ self.win_plug_manager.setCentralWidget(widget)
1037
+ self.plugin_manager = PluginManager(widget)
1038
+ self.plugin_manager.quit_signal.connect(self.quit_fun)
1039
+ self.plugin_manager.restart_signal.connect(self.restart_fun)
1040
+ self.win_plug_manager.show()
1041
+
1042
+ def create_menu_slot(self, filename):
1043
+ return lambda: self.set_preset_mode(filename)
1044
+
1045
+ def create_menu_slot_ext(self, ext):
1046
+ return lambda: self.load_extensions_module(ext)
1047
+
1048
+ def create_menu_slot_roi(self, filename):
1049
+ return lambda: self.set_roi_configuration(filename)
1050
+
1051
+ def create_menu_slot_over(self, filename):
1052
+ return lambda: self.set_overshoot_configuration(filename)
1053
+
1054
+ def create_menu_slot_remote(self, filename):
1055
+ return lambda: self.set_remote_configuration(filename)
1056
+
1057
+ def create_roi_file(self):
1058
+ try:
1059
+ if self.preset_file is not None:
1060
+ self.roi_saver.set_new_roi(self.preset_file.stem)
1061
+ self.add_action(
1062
+ self.get_action_from_file(self.preset_file, ManagerEnums.roi),
1063
+ self.preset_file.stem,
1064
+ "",
1065
+ )
1066
+ self.setup_menu(self.menubar)
1067
+ self.connect_action(
1068
+ self.get_action_from_file(self.preset_file, ManagerEnums.roi),
1069
+ self.create_menu_slot_roi(
1070
+ config_mod_pymodaq.get_set_roi_path().joinpath(self.preset_file.name)
1071
+ ),
1072
+ )
1073
+
1074
+
1075
+ except Exception as e:
1076
+ logger.exception(str(e))
1077
+
1078
+ def create_remote(self):
1079
+ try:
1080
+ if self.preset_file is not None:
1081
+ self.remote_manager.set_new_remote(self.preset_file.stem)
1082
+ self.add_action(
1083
+ self.get_action_from_file(self.preset_file, ManagerEnums.remote),
1084
+ self.preset_file.stem,
1085
+ "",
1086
+ )
1087
+ self.setup_menu(self.menubar)
1088
+ self.connect_action(
1089
+ self.get_action_from_file(self.preset_file, ManagerEnums.remote),
1090
+ self.create_menu_slot_remote(
1091
+ config_mod_pymodaq.get_set_remote_path().joinpath(self.preset_file.name)
1092
+ ),
1093
+ )
1094
+
1095
+ except Exception as e:
1096
+ logger.exception(str(e))
1097
+
1098
+ def create_overshoot(self):
1099
+ try:
1100
+ if self.preset_file is not None:
1101
+ self.overshoot_manager.set_new_overshoot(self.preset_file.stem)
1102
+ self.add_action(
1103
+ self.get_action_from_file(self.preset_file, ManagerEnums.overshoot),
1104
+ self.preset_file.stem,
1105
+ "",
1106
+ )
1107
+ self.setup_menu(self.menubar)
1108
+ self.connect_action(
1109
+ self.get_action_from_file(self.preset_file, ManagerEnums.overshoot),
1110
+ self.create_menu_slot_over(
1111
+ config_mod_pymodaq.get_set_overshoot_path().joinpath(self.preset_file.name)
1112
+ ),
1113
+ )
1114
+ except Exception as e:
1115
+ logger.exception(str(e))
1116
+
1117
+ def create_preset(self):
1118
+ try:
1119
+ status = self.preset_manager.set_new_preset()
1120
+ if status:
1121
+ self.update_preset_action_list()
1122
+ self.setup_menu(self.menubar)
1123
+ self.new_preset_created.emit()
1124
+ except Exception as e:
1125
+ logger.exception(str(e))
1126
+
1127
+ @staticmethod
1128
+ def get_action_from_file(file: Path, manager: ManagerEnums):
1129
+ return f"{file.stem}_{manager.name}"
1130
+
1131
+ def modify_remote(self):
1132
+ try:
1133
+ path = select_file(
1134
+ start_path=config_mod_pymodaq.get_set_remote_path(),
1135
+ save=False,
1136
+ ext="xml",
1137
+ )
1138
+ if path != "":
1139
+ self.remote_manager.set_file_remote(path)
1140
+
1141
+ else: # cancel
1142
+ pass
1143
+ except Exception as e:
1144
+ logger.exception(str(e))
1145
+
1146
+ def modify_overshoot(self):
1147
+ try:
1148
+ path = select_file(
1149
+ start_path=config_mod_pymodaq.get_set_overshoot_path(),
1150
+ save=False,
1151
+ ext="xml",
1152
+ )
1153
+ if path != "":
1154
+ self.overshoot_manager.set_file_overshoot(path)
1155
+
1156
+ else: # cancel
1157
+ pass
1158
+ except Exception as e:
1159
+ logger.exception(str(e))
1160
+
1161
+ def modify_roi(self):
1162
+ try:
1163
+ path = select_file(
1164
+ start_path=config_mod_pymodaq.get_set_roi_path(), save=False, ext="xml"
1165
+ )
1166
+ if path != "":
1167
+ self.roi_saver.set_file_roi(path)
1168
+
1169
+ else: # cancel
1170
+ pass
1171
+ except Exception as e:
1172
+ logger.exception(str(e))
1173
+
1174
+ def modify_preset(self):
1175
+ try:
1176
+ path = select_file(start_path=self.preset_path, save=False, ext="xml")
1177
+ if path != "":
1178
+ modified = self.preset_manager.set_file_preset(path)
1179
+
1180
+ if modified:
1181
+ self.remove_preset_related_files(path.name)
1182
+ if self.detector_modules:
1183
+ mssg = QMessageBox()
1184
+ mssg.setText(
1185
+ "You have to restart the application to take the modifications"
1186
+ " into account!\n\n"
1187
+ "The related files: ROI, Layout, Overshoot and Remote will be"
1188
+ " deleted if existing!\n\n"
1189
+ "Quitting the application..."
1190
+ )
1191
+ mssg.exec()
1192
+ self.restart_fun()
1193
+
1194
+ else: # cancel
1195
+ pass
1196
+ except Exception as e:
1197
+ logger.exception(str(e))
1198
+
1199
+ def remove_preset_related_files(self, name):
1200
+ config_mod_pymodaq.get_set_roi_path().joinpath(name).unlink(missing_ok=True)
1201
+ config_mod_pymodaq.get_set_layout_path().joinpath(name).unlink(missing_ok=True)
1202
+ config_mod_pymodaq.get_set_overshoot_path().joinpath(name).unlink(
1203
+ missing_ok=True
1204
+ )
1205
+ config_mod_pymodaq.get_set_remote_path().joinpath(name).unlink(missing_ok=True)
1206
+
1207
+ def quit_fun(self):
1208
+ """
1209
+ Quit the current instance of DAQ_scan and close on cascade move and detector modules.
1210
+
1211
+ See Also
1212
+ --------
1213
+ quit_fun
1214
+ """
1215
+ try:
1216
+ self.remote_timer.stop()
1217
+
1218
+ for ext in self.extensions:
1219
+ if hasattr(self.extensions[ext], "quit_fun"):
1220
+ self.extensions[ext].quit_fun()
1221
+ for mov in self.actuators_modules:
1222
+ try:
1223
+ mov.init_signal.disconnect(self.update_init_tree)
1224
+ except TypeError:
1225
+ pass
1226
+ for det in self.detector_modules:
1227
+ try:
1228
+ det.init_signal.disconnect(self.update_init_tree)
1229
+ except TypeError:
1230
+ pass
1231
+
1232
+ for module in self.actuators_modules:
1233
+ try:
1234
+ module.quit_fun()
1235
+ QtWidgets.QApplication.processEvents()
1236
+ QThread.msleep(1000)
1237
+ QtWidgets.QApplication.processEvents()
1238
+ except Exception:
1239
+ pass
1240
+
1241
+ for module in self.detector_modules:
1242
+ try:
1243
+ module.quit_fun()
1244
+ QtWidgets.QApplication.processEvents()
1245
+ QThread.msleep(1000)
1246
+ QtWidgets.QApplication.processEvents()
1247
+ except Exception:
1248
+ pass
1249
+ areas = self.dockarea.tempAreas[:]
1250
+ for area in areas:
1251
+ area.win.close()
1252
+ QtWidgets.QApplication.processEvents()
1253
+ QThread.msleep(1000)
1254
+ QtWidgets.QApplication.processEvents()
1255
+
1256
+ if hasattr(self, "mainwindow"):
1257
+ self.mainwindow.close()
1258
+
1259
+ if self.pid_window is not None:
1260
+ self.pid_window.close()
1261
+
1262
+ except Exception as e:
1263
+ logger.exception(str(e))
1264
+
1265
+ def restart_fun(self, ask=False):
1266
+ ret = False
1267
+ mssg = QMessageBox()
1268
+ if ask:
1269
+ mssg.setText(
1270
+ "You have to restart the application to take the"
1271
+ " modifications into account!"
1272
+ )
1273
+ mssg.setInformativeText("Do you want to restart?")
1274
+ mssg.setStandardButtons(QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel)
1275
+ ret = mssg.exec()
1276
+
1277
+ if ret == QMessageBox.StandardButton.Ok or not ask:
1278
+ self.quit_fun()
1279
+ subprocess.call([sys.executable, __file__])
1280
+
1281
+ def load_layout_state(self, file=None):
1282
+ """
1283
+ Load and restore a layout state from the select_file obtained pathname file.
1284
+
1285
+ See Also
1286
+ --------
1287
+ utils.select_file
1288
+ """
1289
+ try:
1290
+ file = layout_mod.load_layout_state(self.dockarea, file)
1291
+ self.settings.child("loaded_files", "layout_file").setValue(file)
1292
+ except Exception as e:
1293
+ logger.exception(str(e))
1294
+
1295
+ def save_layout_state(self, file=None):
1296
+ """
1297
+ Save the current layout state in the select_file obtained pathname file.
1298
+ Once done dump the pickle.
1299
+
1300
+ See Also
1301
+ --------
1302
+ utils.select_file
1303
+ """
1304
+ try:
1305
+ layout_mod.save_layout_state(self.dockarea, file)
1306
+ except Exception as e:
1307
+ logger.exception(str(e))
1308
+
1309
+ def save_layout_state_auto(self):
1310
+ if self.preset_file is not None:
1311
+ path = layout_path.joinpath(self.preset_file.stem + ".dock")
1312
+ self.save_layout_state(path)
1313
+
1314
+ def add_move(
1315
+ self,
1316
+ plug_name: str = None,
1317
+ plug_settings: Parameter = None,
1318
+ plug_type: str = None,
1319
+ move_docks: list[Dock] = None,
1320
+ move_forms: list[QtWidgets.QWidget] = None,
1321
+ actuators_modules: list[DAQ_Move] = None,
1322
+ ui_identifier: str = None,
1323
+ **kwargs
1324
+ ) -> DAQ_Move:
1325
+ if move_docks is None:
1326
+ move_docks = []
1327
+ if move_forms is None:
1328
+ move_forms = []
1329
+ if actuators_modules is None:
1330
+ actuators_modules = []
1331
+
1332
+ if ui_identifier is not None:
1333
+ pass
1334
+ elif plug_settings is None:
1335
+ ui_identifier = config("actuator", "ui")
1336
+ else:
1337
+ try:
1338
+ ui_identifier = plug_settings["main_settings", "ui_type"]
1339
+ except KeyError:
1340
+ ui_identifier = config("actuator", "ui")
1341
+
1342
+ is_compact = (
1343
+ ActuatorUIFactory.get(ui_identifier).is_compact
1344
+ if ui_identifier is not None
1345
+ else False
1346
+ )
1347
+
1348
+ if is_compact:
1349
+ if self.compact_actuator_dock is None:
1350
+ self.compact_actuator_dock = Dock("Simple Actuators")
1351
+ self.compact_actuator_dock.layout.setSpacing(0)
1352
+ self.compact_actuator_dock.layout.setContentsMargins(0, 0, 0, 0)
1353
+
1354
+ dock = self.compact_actuator_dock
1355
+ self.logger_dock.area.addDock(dock, "bottom")
1356
+ else:
1357
+ dock = Dock(plug_name, size=(150, 250))
1358
+ move_docks.append(dock)
1359
+
1360
+ if len(move_docks) == 1:
1361
+ self.dockarea.addDock(dock, "right", self.logger_dock)
1362
+ else:
1363
+ self.dockarea.addDock(dock, "above", move_docks[-2])
1364
+
1365
+ move_forms.append(QtWidgets.QWidget())
1366
+ mov_mod_tmp = DAQ_Move(move_forms[-1], plug_name, ui_identifier=ui_identifier)
1367
+
1368
+ mov_mod_tmp.actuator = plug_type
1369
+ QtWidgets.QApplication.processEvents()
1370
+ mov_mod_tmp.manage_ui_actions("quit", "setEnabled", False)
1371
+
1372
+ if plug_settings is not None:
1373
+ try:
1374
+ putils.set_param_from_param(mov_mod_tmp.settings, plug_settings)
1375
+ except KeyError as e:
1376
+ mssg = (
1377
+ f"Could not set this setting: {str(e)}\n"
1378
+ f"The Preset is no more compatible with the plugin {plug_type}"
1379
+ )
1380
+ logger.warning(mssg)
1381
+ self.splash_sc.showMessage(mssg)
1382
+ QtWidgets.QApplication.processEvents()
1383
+
1384
+ mov_mod_tmp.bounds_signal[bool].connect(self.do_stuff_from_out_bounds)
1385
+ dock.addWidget(move_forms[-1])
1386
+
1387
+ actuators_modules.append(mov_mod_tmp)
1388
+ return mov_mod_tmp
1389
+
1390
+ def add_move_from_extension(
1391
+ self, name: str, instrument_name: str, instrument_controller: Any,
1392
+ ui_identifier = None,
1393
+ **kwargs
1394
+ ):
1395
+ """Specific method to add a DAQ_Move within the Dashboard. This Particular actuator
1396
+ should be defined in the plugin of the extension and is used to mimic an actuator while
1397
+ move_abs is actually triggering an action on the extension which loaded it
1398
+
1399
+ For an exemple, see the PyMoDAQ builtin PID extension
1400
+
1401
+ Parameters
1402
+ ----------
1403
+ name: str
1404
+ The name to print on the UI title
1405
+ instrument_name: str
1406
+ The name of the instrument class, for instance PID for the daq_move_PID
1407
+ module and the DAQ_Move_PID instrument class
1408
+ instrument_controller: object
1409
+ whatever object is used to communicate between the instrument module and the extension
1410
+ which created it
1411
+ ui_identifier: str
1412
+ One of the possible registered UI
1413
+ kwargs: named arguments to be passed to add_move
1414
+ """
1415
+ actuator = self.add_move(name, None, instrument_name, [], [], [],
1416
+ ui_identifier=ui_identifier,
1417
+ **kwargs)
1418
+ actuator.controller = instrument_controller
1419
+ actuator.master = False
1420
+ actuator.init_hardware_ui()
1421
+ QtWidgets.QApplication.processEvents()
1422
+ self.poll_init(actuator)
1423
+ QtWidgets.QApplication.processEvents()
1424
+
1425
+ # Update actuators modules and module manager
1426
+ self.actuators_modules.append(actuator)
1427
+ self.update_module_manager()
1428
+
1429
+ def add_det(
1430
+ self,
1431
+ plug_name,
1432
+ plug_settings,
1433
+ det_docks_settings,
1434
+ det_docks_viewer,
1435
+ detector_modules,
1436
+ plug_type: str = None,
1437
+ plug_subtype: str = None,
1438
+ ) -> DAQ_Viewer:
1439
+ if plug_type is None:
1440
+ plug_type = plug_settings.child("main_settings", "DAQ_type").value()
1441
+ if plug_subtype is None:
1442
+ plug_subtype = plug_settings.child("main_settings", "detector_type").value()
1443
+ det_docks_settings.append(Dock(plug_name + " settings", size=(150, 250)))
1444
+ det_docks_viewer.append(Dock(plug_name + " viewer", size=(350, 350)))
1445
+ if len(detector_modules) == 0:
1446
+ self.logger_dock.area.addDock(det_docks_settings[-1], "bottom")
1447
+ # dockarea of the logger dock
1448
+ else:
1449
+ self.dockarea.addDock(
1450
+ det_docks_settings[-1], "right", detector_modules[-1].viewer_docks[-1]
1451
+ )
1452
+ self.dockarea.addDock(det_docks_viewer[-1], "right", det_docks_settings[-1])
1453
+ det_mod_tmp = DAQ_Viewer(
1454
+ self.dockarea,
1455
+ title=plug_name,
1456
+ daq_type=plug_type,
1457
+ dock_settings=det_docks_settings[-1],
1458
+ dock_viewer=det_docks_viewer[-1],
1459
+ )
1460
+ QtWidgets.QApplication.processEvents()
1461
+ det_mod_tmp.detector = plug_subtype
1462
+ QtWidgets.QApplication.processEvents()
1463
+ det_mod_tmp.manage_ui_actions("quit", "setEnabled", False)
1464
+
1465
+ if plug_settings is not None:
1466
+ try:
1467
+ putils.set_param_from_param(det_mod_tmp.settings, plug_settings)
1468
+ except KeyError as e:
1469
+ mssg = (
1470
+ f"Could not set this setting: {str(e)}\n"
1471
+ f"The Preset is no more compatible with the plugin {plug_subtype}"
1472
+ )
1473
+ logger.warning(mssg)
1474
+ self.splash_sc.showMessage(mssg)
1475
+
1476
+ detector_modules.append(det_mod_tmp)
1477
+ return det_mod_tmp
1478
+
1479
+ def override_det_from_extension(self, overriden_grabbers: Sequence[str] = None):
1480
+ """(Experimental) If an extension adding detectors within the Dashboard need to,
1481
+ it could call this method.
1482
+
1483
+ Then if some other extension trigger a grab from it, the request of a grab won't be done twice
1484
+
1485
+ Parameters
1486
+ ----------
1487
+ overriden_grabbers: Sequence[str]
1488
+ sequence of detector names whose corresponding modules should set their
1489
+ attribute override_grab_from_extension to True.
1490
+ """
1491
+ if overriden_grabbers is not None:
1492
+ for mod_name in overriden_grabbers:
1493
+ mod = self.modules_manager.get_mod_from_name(mod_name, "det")
1494
+ if mod is not None:
1495
+ mod.override_grab_from_extension = True
1496
+
1497
+ def add_det_from_extension(
1498
+ self, name: str, daq_type: str, instrument_name: str, instrument_controller: Any
1499
+ ):
1500
+ """Specific method to add a DAQ_Viewer within the Dashboard. This Particular detector
1501
+ should be defined in the plugin of the extension and is used to mimic a grab while data
1502
+ are actually coming from the extension which loaded it
1503
+
1504
+ For an exemple, see the pymodaq_plugins_datamixer plugin and its DataMixer extension
1505
+ or the DAQ_PID extension
1506
+
1507
+ Parameters
1508
+ ----------
1509
+ name: str
1510
+ The name to print on the UI title
1511
+ daq_type: str
1512
+ either DAQ0D, DAQ1D, DAQ2D or DAQND depending the type of the instrument
1513
+ instrument_name: str
1514
+ The name of the instrument class, for instance DataMixer for the daq_0Dviewer_DataMixer
1515
+ module and the DAQ_0DViewer_DataMixer instrument class
1516
+ instrument_controller: object
1517
+ whatever object is used to communicate between the instrument module and the extension
1518
+ which created it
1519
+ """
1520
+ detector = self.add_det(
1521
+ name, None, [], [], [], plug_type=daq_type, plug_subtype=instrument_name
1522
+ )
1523
+ detector.controller = instrument_controller
1524
+ detector.master = False
1525
+ detector.init_hardware_ui()
1526
+ QtWidgets.QApplication.processEvents()
1527
+ self.poll_init(detector)
1528
+ QtWidgets.QApplication.processEvents()
1529
+
1530
+ # Update actuators modules and module manager
1531
+ self.detector_modules.append(detector)
1532
+ self.update_module_manager()
1533
+
1534
+ def update_module_manager(self):
1535
+ if self.modules_manager is None:
1536
+ self.modules_manager = ModulesManager(
1537
+ self.detector_modules, self.actuators_modules, parent_name="Dashboard"
1538
+ )
1539
+ else:
1540
+ self.modules_manager.actuators_all = self.actuators_modules
1541
+ self.modules_manager.detectors_all = self.detector_modules
1542
+
1543
+ def set_file_preset(self, filename) -> Tuple[List[DAQ_Move], List[DAQ_Viewer]]:
1544
+ """
1545
+ Set a file managers from the converted xml file given by the filename parameter.
1546
+
1547
+
1548
+ =============== =========== ===================================================
1549
+ **Parameters** **Type** **Description**
1550
+ *filename* string the name of the xml file to be converted/treated
1551
+ =============== =========== ===================================================
1552
+
1553
+ Returns
1554
+ -------
1555
+ (Object list, Object list) tuple
1556
+ The updated (Move modules list, Detector modules list).
1557
+
1558
+ """
1559
+ actuators_modules = []
1560
+ detector_modules = []
1561
+ if not isinstance(filename, Path):
1562
+ filename = Path(filename)
1563
+
1564
+ if filename.suffix == ".xml":
1565
+ self.preset_file = filename
1566
+ self.preset_manager.set_file_preset(filename, show=False)
1567
+ move_docks = []
1568
+ det_docks_settings = []
1569
+ det_docks_viewer = []
1570
+ move_forms = []
1571
+
1572
+ # ################################################################
1573
+ # ##### sort plugins by IDs and within the same IDs by Master and Slave status
1574
+ plugins = []
1575
+ plugins += [
1576
+ {"type": "move", "value": child}
1577
+ for child in self.preset_manager.preset_params.child("Moves").children()
1578
+ ]
1579
+ plugins += [
1580
+ {"type": "det", "value": child}
1581
+ for child in self.preset_manager.preset_params.child(
1582
+ "Detectors"
1583
+ ).children()
1584
+ ]
1585
+ for plug in plugins:
1586
+ if plug["type"] == "det":
1587
+ try:
1588
+ plug["ID"] = plug["value"][
1589
+ "params", "detector_settings", "controller_ID"
1590
+ ]
1591
+ plug["status"] = plug["value"][
1592
+ "params", "detector_settings", "controller_status"
1593
+ ]
1594
+ except KeyError as e:
1595
+ raise DetectorError
1596
+ else:
1597
+ try:
1598
+ plug["ID"] = plug["value"][
1599
+ "params", "move_settings", "multiaxes", "controller_ID"
1600
+ ]
1601
+ plug["status"] = plug["value"][
1602
+ "params", "move_settings", "multiaxes", "multi_status"
1603
+ ]
1604
+ except KeyError as e:
1605
+ raise ActuatorError
1606
+
1607
+ IDs = list(set([plug["ID"] for plug in plugins]))
1608
+ # %%
1609
+ plugins_sorted = []
1610
+ for id in IDs:
1611
+ plug_Ids = []
1612
+ for plug in plugins:
1613
+ if plug["ID"] == id:
1614
+ plug_Ids.append(plug)
1615
+ plug_Ids.sort(key=lambda status: status["status"])
1616
+ plugins_sorted.append(plug_Ids)
1617
+ #################################################################
1618
+ #######################
1619
+
1620
+ ind_det = -1
1621
+ for plug_IDs in plugins_sorted:
1622
+ for ind_plugin, plugin in enumerate(plug_IDs):
1623
+ plug_name = plugin["value"].child("name").value()
1624
+ plug_init = plugin["value"].child("init").value()
1625
+ plug_settings = plugin["value"].child("params")
1626
+ self.splash_sc.showMessage(
1627
+ "Loading {:s} module: {:s}".format(plugin["type"], plug_name)
1628
+ )
1629
+
1630
+ if plugin["type"] == "move":
1631
+ plug_type = plug_settings.child(
1632
+ "main_settings", "move_type"
1633
+ ).value()
1634
+ self.add_move(
1635
+ plug_name,
1636
+ plug_settings,
1637
+ plug_type,
1638
+ move_docks,
1639
+ move_forms,
1640
+ actuators_modules,
1641
+ )
1642
+
1643
+ if ind_plugin == 0: # should be a master type plugin
1644
+ if plugin["status"] != "Master":
1645
+ raise MasterSlaveError(
1646
+ f"The instrument {plug_name} should"
1647
+ f" be defined as Master"
1648
+ )
1649
+ if plug_init:
1650
+ actuators_modules[-1].init_hardware_ui()
1651
+ QtWidgets.QApplication.processEvents()
1652
+ self.poll_init(actuators_modules[-1])
1653
+ QtWidgets.QApplication.processEvents()
1654
+ master_controller = actuators_modules[-1].controller
1655
+ elif plugin["status"] == "Master" and len(plug_IDs) > 1:
1656
+ raise MasterSlaveError(
1657
+ f"The instrument {plug_name} defined as Master has to be "
1658
+ f"initialized (init checked in the preset) in order to init "
1659
+ f"its associated slave instrument"
1660
+ )
1661
+ else:
1662
+ if plugin["status"] != "Slave":
1663
+ raise MasterSlaveError(
1664
+ f"The instrument {plug_name} should"
1665
+ f" be defined as slave"
1666
+ )
1667
+ if plug_init:
1668
+ actuators_modules[-1].controller = master_controller
1669
+ actuators_modules[-1].init_hardware_ui()
1670
+ QtWidgets.QApplication.processEvents()
1671
+ self.poll_init(actuators_modules[-1])
1672
+ QtWidgets.QApplication.processEvents()
1673
+ else:
1674
+ ind_det += 1
1675
+ self.add_det(
1676
+ plug_name,
1677
+ plug_settings,
1678
+ det_docks_settings,
1679
+ det_docks_viewer,
1680
+ detector_modules,
1681
+ )
1682
+ QtWidgets.QApplication.processEvents()
1683
+
1684
+ if ind_plugin == 0: # should be a master type plugin
1685
+ if plugin["status"] != "Master":
1686
+ raise MasterSlaveError(
1687
+ f"The instrument {plug_name} should"
1688
+ f" be defined as Master"
1689
+ )
1690
+ if plug_init:
1691
+ detector_modules[-1].init_hardware_ui()
1692
+ QtWidgets.QApplication.processEvents()
1693
+ self.poll_init(detector_modules[-1])
1694
+ QtWidgets.QApplication.processEvents()
1695
+ master_controller = detector_modules[-1].controller
1696
+ elif plugin["status"] == "Master" and len(plug_IDs) > 1:
1697
+ raise MasterSlaveError(
1698
+ f"The instrument {plug_name} defined as Master has to be "
1699
+ f"initialized (init checked in the preset) in order to init "
1700
+ f"its associated slave instrument"
1701
+ )
1702
+ else:
1703
+ if plugin["status"] != "Slave":
1704
+ raise MasterSlaveError(
1705
+ f"The instrument {plug_name} should"
1706
+ f" be defined as Slave"
1707
+ )
1708
+ if plug_init:
1709
+ detector_modules[-1].controller = master_controller
1710
+ detector_modules[-1].init_hardware_ui()
1711
+ QtWidgets.QApplication.processEvents()
1712
+ self.poll_init(detector_modules[-1])
1713
+ QtWidgets.QApplication.processEvents()
1714
+
1715
+ detector_modules[-1].settings.child(
1716
+ "main_settings", "overshoot"
1717
+ ).show()
1718
+ detector_modules[-1].overshoot_signal[bool].connect(
1719
+ self.stop_moves_from_overshoot
1720
+ )
1721
+
1722
+ QtWidgets.QApplication.processEvents()
1723
+ # restore dock state if saved
1724
+
1725
+ self.title = self.preset_file.stem
1726
+ path = layout_path.joinpath(self.title + ".dock")
1727
+ if path.is_file():
1728
+ self.load_layout_state(path)
1729
+
1730
+ self.mainwindow.setWindowTitle(f"PyMoDAQ Dashboard: {self.title}")
1731
+ if self.pid_module is not None:
1732
+ self.pid_module.set_module_manager(detector_modules, actuators_modules)
1733
+ return actuators_modules, detector_modules
1734
+ else:
1735
+ logger.error("Invalid file selected")
1736
+ return actuators_modules, detector_modules
1737
+
1738
+ def poll_init(self, module):
1739
+ is_init = False
1740
+ tstart = perf_counter()
1741
+ while not is_init:
1742
+ QThread.msleep(1000)
1743
+ QtWidgets.QApplication.processEvents()
1744
+ is_init = module.initialized_state
1745
+ if perf_counter() - tstart > 60: # timeout of 60sec
1746
+ break
1747
+ return is_init
1748
+
1749
+ def set_roi_configuration(self, filename):
1750
+ if not isinstance(filename, Path):
1751
+ filename = Path(filename)
1752
+ try:
1753
+ if filename.suffix == ".xml":
1754
+ file = filename.stem
1755
+ self.settings.child("loaded_files", "roi_file").setValue(file)
1756
+ self.update_status(
1757
+ "ROI configuration ({}) has been loaded".format(file),
1758
+ log_type="log",
1759
+ )
1760
+ self.roi_saver.set_file_roi(filename, show=False)
1761
+
1762
+ except Exception as e:
1763
+ logger.exception(str(e))
1764
+
1765
+ def set_remote_configuration(self, filename):
1766
+ if not isinstance(filename, Path):
1767
+ filename = Path(filename)
1768
+ ext = filename.suffix
1769
+ if ext == ".xml":
1770
+ self.remote_file = filename
1771
+ self.remote_manager.remote_changed.connect(self.activate_remote)
1772
+ self.remote_manager.set_file_remote(filename, show=False)
1773
+ self.settings.child("loaded_files", "remote_file").setValue(filename)
1774
+ self.remote_manager.set_remote_configuration()
1775
+ self.remote_dock.addWidget(self.remote_manager.remote_settings_tree)
1776
+ self.remote_dock.setVisible(True)
1777
+
1778
+ def activate_remote(self, remote_action, activate_all=False):
1779
+ """
1780
+ remote_action = dict(action_type='shortcut' or 'joystick',
1781
+ action_name='blabla',
1782
+ action_dict= either:
1783
+ dict(shortcut=action.child(('shortcut')).value(), activated=True,
1784
+ name=f'action{ind:02d}', action=action.child(('action')).value(),
1785
+ module_name=module, module_type=module_type)
1786
+
1787
+ or:
1788
+ dict(joystickID=action.child(('joystickID')).value(),
1789
+ actionner_type=action.child(('actionner_type')).value(),
1790
+ actionnerID=action.child(('actionnerID')).value(),
1791
+ activated=True, name=f'action{ind:02d}',
1792
+ module_name=module, module_type=module_type)
1793
+
1794
+ """
1795
+ if remote_action["action_type"] == "shortcut":
1796
+ if remote_action["action_name"] not in self.shortcuts:
1797
+ self.shortcuts[remote_action["action_name"]] = QtWidgets.QShortcut(
1798
+ QtGui.QKeySequence(remote_action["action_dict"]["shortcut"]),
1799
+ self.dockarea,
1800
+ )
1801
+ self.activate_shortcut(
1802
+ self.shortcuts[remote_action["action_name"]],
1803
+ remote_action["action_dict"],
1804
+ activate=remote_action["action_dict"]["activated"],
1805
+ )
1806
+
1807
+ elif remote_action["action_type"] == "joystick":
1808
+ if not self.ispygame_init:
1809
+ self.init_pygame()
1810
+
1811
+ if remote_action["action_name"] not in self.joysticks:
1812
+ self.joysticks[remote_action["action_name"]] = remote_action[
1813
+ "action_dict"
1814
+ ]
1815
+
1816
+ def init_pygame(self):
1817
+ try:
1818
+ import pygame
1819
+
1820
+ self.pygame = pygame
1821
+ pygame.init()
1822
+ pygame.joystick.init()
1823
+ joystick_count = pygame.joystick.get_count()
1824
+ self.joysticks_obj = []
1825
+ for ind in range(joystick_count):
1826
+ self.joysticks_obj.append(dict(obj=pygame.joystick.Joystick(ind)))
1827
+ self.joysticks_obj[-1]["obj"].init()
1828
+ self.joysticks_obj[-1]["id"] = self.joysticks_obj[-1]["obj"].get_id()
1829
+
1830
+ self.remote_timer.timeout.connect(self.pygame_loop)
1831
+ self.ispygame_init = True
1832
+ self.remote_timer.start(10)
1833
+
1834
+ except ImportError as e:
1835
+ logger.warning("No pygame module installed. Needed for joystick control")
1836
+
1837
+ def pygame_loop(self):
1838
+ """
1839
+ check is event correspond to any
1840
+ dict(joystickID=action.child(('joystickID')).value(),
1841
+ actionner_type=action.child(('actionner_type')).value(),
1842
+ actionnerID=action.child(('actionnerID')).value(),
1843
+ activated=True, name=f'action{ind:02d}',
1844
+ module_name=module, module_type=module_type)
1845
+ contained in self.joysticks
1846
+ """
1847
+
1848
+ for action_dict in self.joysticks.values():
1849
+ if (
1850
+ action_dict["activated"]
1851
+ and action_dict["actionner_type"].lower() == "axis"
1852
+ ):
1853
+ if action_dict["module_type"] == "act":
1854
+ joy = utils.find_dict_in_list_from_key_val(
1855
+ self.joysticks_obj, "id", action_dict["joystickID"]
1856
+ )
1857
+ val = joy["obj"].get_axis(action_dict["actionnerID"])
1858
+ if abs(val) > 1e-4:
1859
+ module = self.modules_manager.get_mod_from_name(
1860
+ action_dict["module_name"], mod=action_dict["module_type"]
1861
+ )
1862
+ action = getattr(module, action_dict["action"])
1863
+ if module.move_done_bool:
1864
+ action(
1865
+ val
1866
+ * 1
1867
+ * module.settings.child(
1868
+ "move_settings", "epsilon"
1869
+ ).value()
1870
+ )
1871
+
1872
+ # # For other actions use the event loop
1873
+ for event in self.pygame.event.get(): # User did something.
1874
+ selection = dict([])
1875
+ if "joy" in event.dict:
1876
+ selection.update(dict(joy=event.joy))
1877
+ if event.type == self.pygame.JOYBUTTONDOWN:
1878
+ selection.update(dict(button=event.button))
1879
+ elif event.type == self.pygame.JOYAXISMOTION:
1880
+ selection.update(dict(axis=event.axis, value=event.value))
1881
+ elif event.type == self.pygame.JOYHATMOTION:
1882
+ selection.update(dict(hat=event.hat, value=event.value))
1883
+ if len(selection) > 1:
1884
+ for action_dict in self.joysticks.values():
1885
+ if action_dict["activated"]:
1886
+ module = self.modules_manager.get_mod_from_name(
1887
+ action_dict["module_name"], mod=action_dict["module_type"]
1888
+ )
1889
+ if action_dict["module_type"] == "det":
1890
+ action = getattr(module, action_dict["action"])
1891
+ else:
1892
+ action = getattr(module, action_dict["action"])
1893
+
1894
+ if action_dict["joystickID"] == selection["joy"]:
1895
+ if (
1896
+ action_dict["actionner_type"].lower() == "button"
1897
+ and "button" in selection
1898
+ ):
1899
+ if action_dict["actionnerID"] == selection["button"]:
1900
+ action()
1901
+ elif (
1902
+ action_dict["actionner_type"].lower() == "hat"
1903
+ and "hat" in selection
1904
+ ):
1905
+ if action_dict["actionnerID"] == selection["hat"]:
1906
+ action(selection["value"])
1907
+
1908
+ QtWidgets.QApplication.processEvents()
1909
+
1910
+ def activate_shortcut(self, shortcut, action=None, activate=True):
1911
+ """
1912
+ action = dict(shortcut=action.child(('shortcut')).value(), activated=True,
1913
+ name=f'action{ind:02d}',
1914
+ action=action.child(('action')).value(), module_name=module)
1915
+ Parameters
1916
+ ----------
1917
+ shortcut
1918
+ action
1919
+ activate
1920
+
1921
+ Returns
1922
+ -------
1923
+
1924
+ """
1925
+ if activate:
1926
+ shortcut.activated.connect(self.create_activated_shortcut(action))
1927
+ else:
1928
+ try:
1929
+ shortcut.activated.disconnect()
1930
+ except Exception:
1931
+ pass
1932
+
1933
+ def create_activated_shortcut(self, action):
1934
+ module = self.modules_manager.get_mod_from_name(
1935
+ action["module_name"], mod=action["module_type"]
1936
+ )
1937
+ if action["module_type"] == "det":
1938
+ return lambda: getattr(module, action["action"])()
1939
+ else:
1940
+ return lambda: getattr(module, action["action"])()
1941
+
1942
+ def set_overshoot_configuration(self, filename):
1943
+ try:
1944
+ if not isinstance(filename, Path):
1945
+ filename = Path(filename)
1946
+
1947
+ if filename.suffix == ".xml":
1948
+ file = filename.stem
1949
+ self.settings.child("loaded_files", "overshoot_file").setValue(file)
1950
+ self.update_status(
1951
+ "Overshoot configuration ({}) has been loaded".format(file),
1952
+ log_type="log",
1953
+ )
1954
+ self.overshoot_manager.set_file_overshoot(filename, show=False)
1955
+ self.set_action_enabled("activate_overshoot", True)
1956
+ self.set_action_checked("activate_overshoot", False)
1957
+ self.get_action("activate_overshoot").trigger()
1958
+
1959
+ except Exception as e:
1960
+ logger.exception(str(e))
1961
+
1962
+ def activate_overshoot(self, status: bool):
1963
+ try:
1964
+ self.overshoot_manager.activate_overshoot(
1965
+ self.detector_modules, self.actuators_modules, status
1966
+ )
1967
+ except Exception as e:
1968
+ logger.warning(f"Could not load the overshoot file:\n{str(e)}")
1969
+ self.set_action_checked("activate_overshoot", False)
1970
+ self.set_action_enabled("activate_overshoot", False)
1971
+
1972
+ @property
1973
+ def move_modules(self):
1974
+ """
1975
+ for back compatibility
1976
+ """
1977
+ return self.actuators_modules
1978
+
1979
+ def set_preset_mode(self, filename):
1980
+ """
1981
+ | Set the managers mode from the given filename.
1982
+ |
1983
+ | In case of "mock" or "canon" move, set the corresponding managers calling
1984
+ set_(*)_preset procedure.
1985
+ |
1986
+ | Else set the managers file using set_file_preset function.
1987
+ | Once done connect the move and detector modules to logger to recipe/transmit
1988
+ informations.
1989
+
1990
+ Finally update DAQ_scan_settings tree with :
1991
+ * Detectors
1992
+ * Move
1993
+ * plot_form.
1994
+
1995
+ =============== =========== =============================================
1996
+ **Parameters** **Type** **Description**
1997
+ *filename* string the name of the managers file to be treated
1998
+ =============== =========== =============================================
1999
+
2000
+ See Also
2001
+ --------
2002
+ set_Mock_preset, set_canon_preset, set_file_preset, add_status, update_status
2003
+ """
2004
+ try:
2005
+ if not isinstance(filename, Path):
2006
+ filename = Path(filename)
2007
+
2008
+ self.get_action("preset_list").setCurrentText(filename.stem)
2009
+
2010
+ self.mainwindow.setVisible(False)
2011
+ for area in self.dockarea.tempAreas:
2012
+ area.window().setVisible(False)
2013
+
2014
+ self.splash_sc.show()
2015
+ QtWidgets.QApplication.processEvents()
2016
+ self.splash_sc.raise_()
2017
+ self.splash_sc.showMessage("Loading Modules, please wait")
2018
+ QtWidgets.QApplication.processEvents()
2019
+ self.clear_move_det_controllers()
2020
+ QtWidgets.QApplication.processEvents()
2021
+
2022
+ logger.info(f"Loading Preset file: {filename}")
2023
+
2024
+ try:
2025
+ actuators_modules, detector_modules = self.set_file_preset(filename)
2026
+ except (ActuatorError, DetectorError, MasterSlaveError) as error:
2027
+ self.splash_sc.close()
2028
+ self.mainwindow.setVisible(True)
2029
+ for area in self.dockarea.tempAreas:
2030
+ area.window().setVisible(True)
2031
+ messagebox(
2032
+ severity="critical",
2033
+ title="Preset loading error",
2034
+ text=f"""
2035
+ <p>{error}</p>
2036
+ <p>This error may be related to:</p>
2037
+ <p>Saved preset file is not compatible anymore.</p>
2038
+ <p>Please recreate the preset at <b>{filename}</b>.</p>
2039
+ """,
2040
+ )
2041
+ logger.exception(str(error))
2042
+
2043
+ self.quit_fun()
2044
+ return
2045
+
2046
+ if not (not actuators_modules and not detector_modules):
2047
+ self.update_status(
2048
+ "Preset mode ({}) has been loaded".format(filename.name),
2049
+ log_type="log",
2050
+ )
2051
+ self.settings.child("loaded_files", "preset_file").setValue(
2052
+ filename.name
2053
+ )
2054
+ self.actuators_modules = actuators_modules
2055
+ self.detector_modules = detector_modules
2056
+
2057
+ self.update_module_manager()
2058
+
2059
+ #####################################################
2060
+ self.overshoot_manager = OvershootManager(
2061
+ det_modules=[det.title for det in detector_modules],
2062
+ actuators_modules=[move.title for move in actuators_modules],
2063
+ )
2064
+ # load overshoot if present
2065
+ file = filename.name
2066
+ path = overshoot_path.joinpath(file)
2067
+ if path.is_file():
2068
+ self.set_overshoot_configuration(path)
2069
+
2070
+ self.remote_manager = RemoteManager(
2071
+ actuators=[move.title for move in actuators_modules],
2072
+ detectors=[det.title for det in detector_modules],
2073
+ )
2074
+ # load remote file if present
2075
+ file = filename.name
2076
+ path = remote_path.joinpath(file)
2077
+ if path.is_file():
2078
+ self.set_remote_configuration(path)
2079
+
2080
+ self.roi_saver = ROISaver(det_modules=detector_modules)
2081
+ # load roi saver if present
2082
+ path = roi_path.joinpath(file)
2083
+ if path.is_file():
2084
+ self.set_roi_configuration(path)
2085
+
2086
+ # connecting to logger
2087
+ for mov in actuators_modules:
2088
+ mov.init_signal.connect(self.update_init_tree)
2089
+ for det in detector_modules:
2090
+ det.init_signal.connect(self.update_init_tree)
2091
+
2092
+ self.splash_sc.close()
2093
+ self.mainwindow.setVisible(True)
2094
+ for area in self.dockarea.tempAreas:
2095
+ area.window().setVisible(True)
2096
+ if self.pid_window is not None:
2097
+ self.pid_window.show()
2098
+
2099
+ self.load_preset_menu.setEnabled(False)
2100
+ self.set_action_enabled("load_preset", False)
2101
+ self.set_action_enabled("preset_list", False)
2102
+ self.overshoot_menu.setEnabled(True)
2103
+ self.roi_menu.setEnabled(True)
2104
+ self.remote_menu.setEnabled(True)
2105
+ self.extensions_menu.setEnabled(True)
2106
+ self.file_menu.setEnabled(True)
2107
+ self.settings_menu.setEnabled(True)
2108
+ self.update_init_tree()
2109
+
2110
+ self.preset_loaded_signal.emit(True)
2111
+
2112
+ logger.info(f"Preset file: {filename} has been loaded")
2113
+
2114
+ except Exception as e:
2115
+ logger.exception(str(e))
2116
+
2117
+ def update_init_tree(self):
2118
+ for act in self.actuators_modules:
2119
+ name = "".join(act.title.split()) # remove empty spaces
2120
+ if act.title not in [
2121
+ ac.title()
2122
+ for ac in putils.iter_children_params(
2123
+ self.settings.child("actuators"), []
2124
+ )
2125
+ ]:
2126
+ self.settings.child("actuators").addChild(
2127
+ {"title": act.title, "name": name, "type": "led", "value": False}
2128
+ )
2129
+ QtWidgets.QApplication.processEvents()
2130
+ self.settings.child("actuators", name).setValue(act.initialized_state)
2131
+
2132
+ for det in self.detector_modules:
2133
+ name = "".join(det.title.split()) # remove empty spaces
2134
+ if det.title not in [
2135
+ de.title()
2136
+ for de in putils.iter_children_params(
2137
+ self.settings.child("detectors"), []
2138
+ )
2139
+ ]:
2140
+ self.settings.child("detectors").addChild(
2141
+ {"title": det.title, "name": name, "type": "led", "value": False}
2142
+ )
2143
+ QtWidgets.QApplication.processEvents()
2144
+ self.settings.child("detectors", name).setValue(det.initialized_state)
2145
+
2146
+ def do_stuff_from_out_bounds(self, out_of_bounds: bool):
2147
+ if out_of_bounds:
2148
+ logger.warning(f"Some actuators reached their bounds")
2149
+ if self.scan_module is not None:
2150
+ logger.warning(f"Stopping the DAQScan for out of bounds")
2151
+ self.scan_module.stop_scan()
2152
+
2153
+ def stop_moves_from_overshoot(self, overshoot):
2154
+ self.overshoot = overshoot
2155
+ self.stop_moves()
2156
+
2157
+ def stop_moves(self, *args, **kwargs):
2158
+ """
2159
+ Foreach module of the move module object list, stop motion.
2160
+
2161
+ See Also
2162
+ --------
2163
+ stop_scan, DAQ_Move_main.daq_move.stop_motion
2164
+ """
2165
+ if self.scan_module is not None:
2166
+ self.scan_module.stop_scan()
2167
+
2168
+ for mod in self.actuators_modules:
2169
+ mod.stop_motion()
2170
+
2171
+ def show_log(self):
2172
+ import webbrowser
2173
+
2174
+ webbrowser.open(logging.getLogger("pymodaq").handlers[0].baseFilename)
2175
+
2176
+ def show_config(self, config):
2177
+ from pymodaq_gui.utils.widgets.tree_toml import TreeFromToml
2178
+
2179
+ config_tree = TreeFromToml(config)
2180
+ config_tree.show_dialog()
2181
+
2182
+ def setup_docks(self):
2183
+ # %% create logger dock
2184
+ self.logger_dock = Dock("Logger")
2185
+ self.logger_list = QtWidgets.QListWidget()
2186
+ self.logger_list.setMinimumWidth(300)
2187
+
2188
+ splitter = QtWidgets.QSplitter(Qt.Vertical)
2189
+ splitter.addWidget(self.settings_tree)
2190
+ splitter.addWidget(self.logger_list)
2191
+ self.logger_dock.addWidget(splitter)
2192
+
2193
+ self.remote_dock = Dock("Remote controls")
2194
+ self.dockarea.addDock(self.remote_dock, "top")
2195
+ self.dockarea.addDock(self.logger_dock, "above", self.remote_dock)
2196
+ self.logger_dock.setVisible(True)
2197
+
2198
+ self.remote_dock.setVisible(False)
2199
+ self.preset_manager = PresetManager(
2200
+ path=self.preset_path, extra_params=self.extra_params
2201
+ )
2202
+
2203
+ @property
2204
+ def menubar(self):
2205
+ return self._menubar
2206
+
2207
+ def parameter_tree_changed(self, param, changes):
2208
+ """
2209
+ Foreach value changed, update :
2210
+ * Viewer in case of **DAQ_type** parameter name
2211
+ * visibility of button in case of **show_averaging** parameter name
2212
+ * visibility of naverage in case of **live_averaging** parameter name
2213
+ * scale of axis **else** (in 2D pymodaq type)
2214
+
2215
+ Once done emit the update settings signal to link the commit.
2216
+
2217
+
2218
+ """
2219
+
2220
+ for param, change, data in changes:
2221
+ path = self.settings.childPath(param)
2222
+ if path is not None:
2223
+ childName = ".".join(path)
2224
+ else:
2225
+ childName = param.name()
2226
+ if change == "childAdded":
2227
+ pass
2228
+ elif change == "value":
2229
+ if param.name() == "log_level":
2230
+ logger.setLevel(param.value())
2231
+ elif change == "parent":
2232
+ pass
2233
+
2234
+ def show_about(self):
2235
+ self.splash_sc.setVisible(True)
2236
+ self.splash_sc.showMessage(
2237
+ f"PyMoDAQ version {get_version('pymodaq')}\n"
2238
+ f"Modular Acquisition with Python\n"
2239
+ f"Written by Sébastien Weber"
2240
+ )
2241
+
2242
+ def check_update(self, show=True):
2243
+ try:
2244
+ packages = ["pymodaq_utils", "pymodaq_data", "pymodaq_gui", "pymodaq"]
2245
+ current_versions = [version_mod.parse(get_version(p)) for p in packages]
2246
+ available_versions = [
2247
+ version_mod.parse(get_pypi_pymodaq(p)["version"]) for p in packages
2248
+ ]
2249
+ new_versions = np.greater(available_versions, current_versions)
2250
+ # Combine package and version information and select only the ones with a newer version available
2251
+
2252
+ packages_data = np.array(
2253
+ list(zip(packages, current_versions, available_versions))
2254
+ )[new_versions]
2255
+
2256
+ if len(packages_data) > 0:
2257
+ # Create a QDialog window and different graphical components
2258
+ dialog = QtWidgets.QDialog()
2259
+ dialog.setWindowTitle("Update check")
2260
+
2261
+ vlayout = QtWidgets.QVBoxLayout()
2262
+
2263
+ message_label = QLabel(
2264
+ "New versions of PyMoDAQ packages available!\nUse your package manager to update."
2265
+ )
2266
+ message_label.setAlignment(Qt.AlignCenter)
2267
+
2268
+ table = PymodaqUpdateTableWidget()
2269
+ table.setRowCount(len(packages_data))
2270
+ table.setColumnCount(3)
2271
+ table.setHorizontalHeaderLabels(
2272
+ ["Package", "Current version", "New version"]
2273
+ )
2274
+
2275
+ for p in packages_data:
2276
+ table.append_row(p[0], p[1], p[2])
2277
+
2278
+ # The vlayout contains the message, the table and the buttons
2279
+ # and is connected to the dialog window
2280
+ vlayout.addWidget(message_label)
2281
+ vlayout.addWidget(table)
2282
+ dialog.setLayout(vlayout)
2283
+
2284
+ ret = dialog.exec()
2285
+
2286
+ else:
2287
+ if show:
2288
+ msgBox = QMessageBox()
2289
+ msgBox.setWindowTitle("Update check")
2290
+ msgBox.setText("Everything is up to date!")
2291
+ ret = msgBox.exec()
2292
+ except Exception as e:
2293
+ logger.exception("Error while checking the available PyMoDAQ version")
2294
+
2295
+ return False
2296
+
2297
+ def show_file_attributes(self, type_info="dataset"):
2298
+ """
2299
+ Switch the type_info value.
2300
+
2301
+ In case of :
2302
+ * *scan* : Set parameters showing top false
2303
+ * *dataset* : Set parameters showing top false
2304
+ * *managers* : Set parameters showing top false.
2305
+ Add the save/cancel buttons to the accept/reject dialog
2306
+ (to save managers parameters in a xml file).
2307
+
2308
+ Finally, in case of accepted managers type info,
2309
+ save the managers parameters in a xml file.
2310
+
2311
+ =============== =========== ====================================
2312
+ **Parameters** **Type** **Description**
2313
+ *type_info* string The file type information between
2314
+ * scan
2315
+ * dataset
2316
+ * managers
2317
+ =============== =========== ====================================
2318
+ """
2319
+ dialog = QtWidgets.QDialog()
2320
+ vlayout = QtWidgets.QVBoxLayout()
2321
+ tree = ParameterTree()
2322
+ tree.setMinimumWidth(400)
2323
+ tree.setMinimumHeight(500)
2324
+ if type_info == "scan":
2325
+ tree.setParameters(self.scan_attributes, showTop=False)
2326
+ elif type_info == "dataset":
2327
+ tree.setParameters(self.dataset_attributes, showTop=False)
2328
+
2329
+ vlayout.addWidget(tree)
2330
+ dialog.setLayout(vlayout)
2331
+ buttonBox = QDialogButtonBox(parent=dialog)
2332
+ buttonBox.addButton("Cancel", QDialogButtonBox.ButtonRole.RejectRole)
2333
+ buttonBox.addButton("Apply", QDialogButtonBox.ButtonRole.AcceptRole)
2334
+ buttonBox.rejected.connect(dialog.reject)
2335
+ buttonBox.accepted.connect(dialog.accept)
2336
+
2337
+ vlayout.addWidget(buttonBox)
2338
+ dialog.setWindowTitle("Fill in information about this {}".format(type_info))
2339
+ res = dialog.exec()
2340
+ return res
2341
+
2342
+ def show_help(self):
2343
+ QtGui.QDesktopServices.openUrl(QtCore.QUrl("http://pymodaq.cnrs.fr"))
2344
+
2345
+ def update_status(self, txt, wait_time=0, log_type=None):
2346
+ """
2347
+ Show the txt message in the status bar with a delay of wait_time ms.
2348
+
2349
+ =============== =========== =======================
2350
+ **Parameters** **Type** **Description**
2351
+ *txt* string The message to show
2352
+ *wait_time* int the delay of showing
2353
+ *log_type* string the type of the log
2354
+ =============== =========== =======================
2355
+ """
2356
+ try:
2357
+ if log_type is not None:
2358
+ self.status_signal.emit(txt)
2359
+ logging.info(txt)
2360
+ except Exception as e:
2361
+ pass
2362
+
2363
+
2364
+ def main():
2365
+ from pymodaq_gui.utils.utils import mkQApp
2366
+ from pymodaq.utils.gui_utils.loader_utils import load_dashboard_with_preset
2367
+
2368
+ # Create application and main window
2369
+ app = mkQApp('Dashboard')
2370
+
2371
+ win = QtWidgets.QMainWindow()
2372
+ area = DockArea()
2373
+ win.setCentralWidget(area)
2374
+ win.resize(1000, 500)
2375
+ win.setWindowTitle("PyMoDAQ Dashboard")
2376
+
2377
+ # Command-line argument parsing
2378
+ parser = argparse.ArgumentParser(prog="dashboard", description="PyMoDAQ dashboard")
2379
+ parser.add_argument("-p", "--preset", metavar="PRESET_NAME", help="preset name to load")
2380
+ args = parser.parse_args()
2381
+
2382
+ # If preset name is supplied, load dashboard with this preset
2383
+ if args.preset:
2384
+ load_dashboard_with_preset(args.preset)
2385
+
2386
+ # If no command-line arguments are supplied, start empty
2387
+ else:
2388
+ prog = DashBoard(area)
2389
+ win.show()
2390
+
2391
+ # Run application
2392
+ app.exec()
2393
+
2394
+
2395
+ if __name__ == "__main__":
2396
+ main()