pymodaq 5.0.0__py3-none-any.whl → 5.0.2__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 (63) hide show
  1. pymodaq/__init__.py +55 -89
  2. pymodaq/control_modules/daq_move.py +129 -55
  3. pymodaq/control_modules/daq_move_ui.py +42 -11
  4. pymodaq/control_modules/daq_viewer.py +32 -13
  5. pymodaq/control_modules/move_utility_classes.py +346 -79
  6. pymodaq/control_modules/utils.py +26 -9
  7. pymodaq/control_modules/viewer_utility_classes.py +51 -14
  8. pymodaq/daq_utils/daq_utils.py +6 -0
  9. pymodaq/dashboard.py +532 -263
  10. pymodaq/examples/qt_less_standalone_module.py +128 -0
  11. pymodaq/extensions/bayesian/bayesian_optimisation.py +30 -21
  12. pymodaq/extensions/bayesian/utils.py +6 -3
  13. pymodaq/extensions/daq_logger/__init__.py +1 -0
  14. pymodaq/extensions/daq_logger/daq_logger.py +4 -5
  15. pymodaq/extensions/daq_scan.py +28 -8
  16. pymodaq/extensions/daq_scan_ui.py +7 -9
  17. pymodaq/extensions/pid/__init__.py +0 -1
  18. pymodaq/extensions/pid/actuator_controller.py +13 -0
  19. pymodaq/extensions/pid/daq_move_PID.py +25 -46
  20. pymodaq/extensions/pid/pid_controller.py +49 -41
  21. pymodaq/extensions/pid/utils.py +7 -31
  22. pymodaq/extensions/utils.py +41 -7
  23. pymodaq/post_treatment/load_and_plot.py +43 -10
  24. pymodaq/resources/setup_plugin.py +1 -0
  25. pymodaq/updater.py +107 -0
  26. pymodaq/utils/chrono_timer.py +6 -7
  27. pymodaq/utils/daq_utils.py +6 -3
  28. pymodaq/utils/data.py +21 -17
  29. pymodaq/utils/enums.py +6 -0
  30. pymodaq/utils/gui_utils/loader_utils.py +29 -2
  31. pymodaq/utils/gui_utils/utils.py +9 -12
  32. pymodaq/utils/gui_utils/widgets/lcd.py +8 -0
  33. pymodaq/utils/h5modules/module_saving.py +5 -2
  34. pymodaq/utils/leco/daq_move_LECODirector.py +22 -16
  35. pymodaq/utils/leco/daq_xDviewer_LECODirector.py +15 -9
  36. pymodaq/utils/leco/leco_director.py +4 -3
  37. pymodaq/utils/leco/pymodaq_listener.py +9 -13
  38. pymodaq/utils/leco/utils.py +40 -7
  39. pymodaq/utils/managers/modules_manager.py +22 -12
  40. pymodaq/utils/managers/overshoot_manager.py +45 -1
  41. pymodaq/utils/managers/preset_manager.py +22 -46
  42. pymodaq/utils/managers/preset_manager_utils.py +17 -13
  43. pymodaq/utils/managers/remote_manager.py +1 -1
  44. pymodaq/utils/messenger.py +6 -0
  45. pymodaq/utils/parameter/__init__.py +5 -1
  46. pymodaq/utils/tcp_ip/mysocket.py +4 -110
  47. pymodaq/utils/tcp_ip/serializer.py +4 -769
  48. pymodaq/utils/tcp_ip/tcp_server_client.py +5 -5
  49. pymodaq-5.0.2.dist-info/METADATA +242 -0
  50. {pymodaq-5.0.0.dist-info → pymodaq-5.0.2.dist-info}/RECORD +54 -55
  51. {pymodaq-5.0.0.dist-info → pymodaq-5.0.2.dist-info}/WHEEL +1 -1
  52. {pymodaq-5.0.0.dist-info → pymodaq-5.0.2.dist-info}/entry_points.txt +1 -0
  53. pymodaq/examples/custom_app.py +0 -255
  54. pymodaq/examples/custom_viewer.py +0 -112
  55. pymodaq/examples/parameter_ex.py +0 -158
  56. pymodaq/examples/preset_MockCamera.xml +0 -1
  57. pymodaq/post_treatment/daq_measurement/daq_measurement_GUI.py +0 -142
  58. pymodaq/post_treatment/daq_measurement/daq_measurement_GUI.ui +0 -232
  59. pymodaq/post_treatment/daq_measurement/daq_measurement_main.py +0 -391
  60. pymodaq/post_treatment/daq_measurement/process_from_QtDesigner_DAQ_Measurement_GUI.bat +0 -2
  61. pymodaq-5.0.0.dist-info/METADATA +0 -166
  62. /pymodaq/{post_treatment/daq_measurement → daq_utils}/__init__.py +0 -0
  63. {pymodaq-5.0.0.dist-info → pymodaq-5.0.2.dist-info}/licenses/LICENSE +0 -0
pymodaq/dashboard.py CHANGED
@@ -8,8 +8,12 @@ import logging
8
8
  from pathlib import Path
9
9
  from importlib import import_module
10
10
  from packaging import version as version_mod
11
+ from typing import Tuple, List, Any, TYPE_CHECKING
12
+
13
+
11
14
  from qtpy import QtGui, QtWidgets, QtCore
12
- from qtpy.QtCore import Qt, QObject, Slot, QThread, Signal
15
+ from qtpy.QtCore import Qt, QObject, Slot, QThread, Signal, QSize
16
+ from qtpy.QtWidgets import QTableWidget, QTableWidgetItem, QCheckBox, QWidget, QLabel, QDialogButtonBox, QDialog
13
17
  from time import perf_counter
14
18
  import numpy as np
15
19
 
@@ -20,6 +24,7 @@ from pymodaq_utils.logger import set_logger, get_module_name
20
24
  from pymodaq_utils import utils
21
25
  from pymodaq_utils.utils import get_version, find_dict_in_list_from_key_val
22
26
  from pymodaq_utils import config as configmod
27
+ from pymodaq_utils.enums import BaseEnum
23
28
 
24
29
  from pymodaq_gui.parameter import ParameterTree, Parameter
25
30
  from pymodaq_gui.utils import DockArea, Dock, select_file
@@ -27,25 +32,29 @@ import pymodaq_gui.utils.layout as layout_mod
27
32
  from pymodaq_gui.messenger import messagebox
28
33
  from pymodaq_gui.parameter import utils as putils
29
34
  from pymodaq_gui.managers.roi_manager import ROISaver
35
+ from pymodaq_gui.utils.custom_app import CustomApp
30
36
 
31
37
  from pymodaq.utils.managers.modules_manager import ModulesManager
32
38
  from pymodaq.utils.managers.preset_manager import PresetManager
33
39
  from pymodaq.utils.managers.overshoot_manager import OvershootManager
34
40
  from pymodaq.utils.managers.remote_manager import RemoteManager
35
- from pymodaq.utils.exceptions import DetectorError, ActuatorError, PIDError, MasterSlaveError
41
+ from pymodaq.utils.exceptions import DetectorError, ActuatorError, MasterSlaveError
36
42
  from pymodaq.utils.daq_utils import get_instrument_plugins
37
43
  from pymodaq.utils.leco.utils import start_coordinator
38
44
  from pymodaq.utils import config as config_mod_pymodaq
45
+
39
46
  from pymodaq.control_modules.daq_move import DAQ_Move
40
47
  from pymodaq.control_modules.daq_viewer import DAQ_Viewer
48
+ from pymodaq_gui.utils.splash import get_splash_sc
49
+
41
50
  from pymodaq import extensions as extmod
42
51
 
52
+ logger = set_logger(get_module_name(__file__))
53
+ config = configmod.Config()
43
54
 
44
55
  get_instrument_plugins()
56
+ extensions = extmod.get_extensions()
45
57
 
46
- logger = set_logger(get_module_name(__file__))
47
-
48
- config = configmod.Config()
49
58
 
50
59
  local_path = configmod.get_set_local_dir()
51
60
  now = datetime.datetime.now()
@@ -56,16 +65,105 @@ overshoot_path = config_mod_pymodaq.get_set_overshoot_path()
56
65
  roi_path = config_mod_pymodaq.get_set_roi_path()
57
66
  remote_path = config_mod_pymodaq.get_set_remote_path()
58
67
 
59
- extensions = extmod.get_extensions()
68
+
69
+ class ManagerEnums(BaseEnum):
70
+ preset = 0
71
+ remote = 1
72
+ overshoot = 2
73
+ roi = 3
74
+
75
+ class PymodaqUpdateTableWidget(QTableWidget):
76
+ '''
77
+ A class to represent PyMoDAQ and its subpackages'
78
+ available updates as a table.
79
+ '''
80
+ def __init__(self):
81
+ super().__init__()
82
+
83
+ self._checkboxes = []
84
+ self._package_versions = []
85
+
86
+ def setHorizontalHeaderLabels(self, labels):
87
+ super().setHorizontalHeaderLabels(labels)
88
+ self.setColumnCount(len(labels))
89
+
90
+ def append_row(self, checkbox, package, current_version, available_version):
91
+ row = len(self._checkboxes)
92
+
93
+ self._checkboxes.append(checkbox)
94
+ self._package_versions.append(f'{package}=={available_version}')
95
+
96
+ checkbox_widget = QWidget()
97
+
98
+ checkbox.setChecked(True)
99
+ checkbox.setToolTip("Check to install update")
100
+
101
+ checkbox_layout = QtWidgets.QHBoxLayout()
102
+ checkbox_layout.addWidget(checkbox)
103
+ checkbox_layout.setAlignment(Qt.AlignCenter)
104
+ checkbox_layout.setContentsMargins(0, 0, 0, 0)
105
+
106
+ checkbox_widget.setLayout(checkbox_layout)
107
+
108
+ # Add the checkbox widget to the table
109
+ self.setCellWidget(row, 0, checkbox_widget)
110
+
111
+ # Add labels in the other columns
112
+ self.setItem(row, 1, QTableWidgetItem(str(package)))
113
+ self.setItem(row, 2, QTableWidgetItem(str(current_version)))
114
+ self.setItem(row, 3, QTableWidgetItem(str(available_version)))
60
115
 
61
116
 
62
- class DashBoard(QObject):
117
+ def get_checked_data(self):
118
+ checked = list(map(lambda c : c.isChecked(), self._checkboxes))
119
+ return list(np.array(self._package_versions)[checked])
120
+
121
+ def sizeHint(self):
122
+ self.resizeColumnsToContents()
123
+ self.resizeRowsToContents()
124
+
125
+ # Compute the size to adapt the window (header + borders + sum of all the elements)
126
+ width = self.verticalHeader().width() \
127
+ + self.frameWidth() * 2 \
128
+ + sum([self.columnWidth(i) for i in range(self.columnCount())])
129
+
130
+ height = self.horizontalHeader().height() \
131
+ + self.frameWidth() * 2 \
132
+ + sum([self.rowHeight(i) for i in range(self.rowCount())])
133
+
134
+ return QSize(width, height)
135
+
136
+ class DashBoard(CustomApp):
63
137
  """
64
138
  Main class initializing a DashBoard interface to display det and move modules and logger """
65
139
  status_signal = Signal(str)
66
140
  preset_loaded_signal = Signal(bool)
67
141
  new_preset_created = Signal()
68
142
 
143
+ settings_name = 'dashboard_settings'
144
+ _splash_sc = None
145
+
146
+ params = [
147
+ {'title': 'Log level', 'name': 'log_level', 'type': 'list',
148
+ 'value': config('general', 'debug_level'),
149
+ 'limits': config('general', 'debug_levels')},
150
+
151
+ {'title': 'Loaded presets', 'name': 'loaded_files', 'type': 'group', 'children': [
152
+ {'title': 'Preset file', 'name': 'preset_file', 'type': 'str', 'value': '',
153
+ 'readonly': True},
154
+ {'title': 'Overshoot file', 'name': 'overshoot_file', 'type': 'str', 'value': '',
155
+ 'readonly': True},
156
+ {'title': 'Layout file', 'name': 'layout_file', 'type': 'str', 'value': '',
157
+ 'readonly': True},
158
+ {'title': 'ROI file', 'name': 'roi_file', 'type': 'str', 'value': '',
159
+ 'readonly': True},
160
+ {'title': 'Remote file', 'name': 'remote_file', 'type': 'str', 'value': '',
161
+ 'readonly': True},
162
+ ]},
163
+ {'title': 'Actuators Init.', 'name': 'actuators', 'type': 'group', 'children': []},
164
+ {'title': 'Detectors Init.', 'name': 'detectors', 'type': 'group', 'children': []},
165
+ ]
166
+
69
167
  def __init__(self, dockarea):
70
168
  """
71
169
 
@@ -74,7 +172,8 @@ class DashBoard(QObject):
74
172
  parent: (dockarea) instance of the modified pyqtgraph Dockarea (see daq_utils)
75
173
  """
76
174
 
77
- super().__init__()
175
+ super().__init__(dockarea)
176
+
78
177
  logger.info('Initializing Dashboard')
79
178
  self.extra_params = []
80
179
  self.preset_path = preset_path
@@ -88,14 +187,10 @@ class DashBoard(QObject):
88
187
  self.extensions = dict([])
89
188
  self.extension_windows = []
90
189
 
91
- self.dockarea = dockarea
92
190
  self.dockarea.dock_signal.connect(self.save_layout_state_auto)
93
- self.mainwindow = dockarea.parent()
191
+
94
192
  self.title = ''
95
- splash_path = Path(__file__).parent.joinpath('splash.png')
96
193
 
97
- splash = QtGui.QPixmap(str(splash_path))
98
- self.splash_sc = QtWidgets.QSplashScreen(splash, Qt.WindowStaysOnTopHint)
99
194
  self.overshoot_manager = None
100
195
  self.preset_manager = None
101
196
  self.roi_saver: ROISaver = None
@@ -112,12 +207,23 @@ class DashBoard(QObject):
112
207
  self.preset_file = None
113
208
  self.actuators_modules = []
114
209
  self.detector_modules = []
115
- self.setupUI()
210
+
211
+ self.setup_ui()
212
+
213
+ self.mainwindow.setVisible(True)
116
214
 
117
215
  logger.info('Dashboard Initialized')
118
216
 
119
217
  if config('general', 'check_version'):
120
- self.check_version(show=False)
218
+ if self.check_update(show=False):
219
+ sys.exit(0)
220
+
221
+ @classmethod
222
+ @property
223
+ def splash_sc(cls) -> QtWidgets.QSplashScreen:
224
+ if cls._splash_sc is None:
225
+ cls._splash_sc = get_splash_sc()
226
+ return cls._splash_sc
121
227
 
122
228
  def set_preset_path(self, path):
123
229
  self.preset_path = path
@@ -179,6 +285,7 @@ class DashBoard(QObject):
179
285
  self.scan_module = extmod.DAQScan(dockarea=area, dashboard=self)
180
286
  self.extensions['DAQScan'] = self.scan_module
181
287
  self.scan_module.status_signal.connect(self.add_status)
288
+ #win.setWindowTitle("DAQScan")
182
289
  win.show()
183
290
  return self.scan_module
184
291
 
@@ -264,7 +371,159 @@ class DashBoard(QObject):
264
371
  self.extension_windows[-1].show()
265
372
  return self.extensions[ext['class_name']]
266
373
 
267
- def create_menu(self, menubar):
374
+ def setup_actions(self):
375
+ self.add_action('log', 'Log File', '', "Show Log File in default editor",
376
+ auto_toolbar=False)
377
+ self.add_action('quit', 'Quit', 'close2', "Quit program")
378
+ self.toolbar.addSeparator()
379
+ self.add_action('config', 'Configuration file', 'tree', "General Settings")
380
+ self.add_action('restart', 'Restart', '', "Restart the Dashboard",
381
+ auto_toolbar=False)
382
+ self.add_action('leco', 'Run Leco Coordinator', '', 'Run a Coordinator on this localhost',
383
+ auto_toolbar=False)
384
+ self.add_action('load_layout', 'Load Layout', '',
385
+ 'Load the Saved Docks layout corresponding to the current preset',
386
+ auto_toolbar=False)
387
+ self.add_action('save_layout', 'Save Layout', '',
388
+ 'Save the Saved Docks layout corresponding to the current preset',
389
+ auto_toolbar=False)
390
+ self.add_action('log_window', 'Show/hide log window', '', checkable=True,
391
+ auto_toolbar=False)
392
+ self.add_action('new_preset', 'New Preset', '',
393
+ 'Create a new experimental setup configuration file: a "preset"',
394
+ auto_toolbar=False)
395
+ self.add_action('modify_preset', 'Modify Preset', '',
396
+ 'Modify an existing experimental setup configuration file: a "preset"',
397
+ auto_toolbar=False)
398
+
399
+ self.add_widget('preset_list', QtWidgets.QComboBox, toolbar=self.toolbar,
400
+ signal_str='currentTextChanged', slot=self.update_preset_action)
401
+ self.add_action('load_preset', 'LOAD', 'Open',
402
+ tip='Load the selected Preset: ')
403
+ self.update_preset_action_list()
404
+
405
+ self.add_action('new_overshoot', 'New Overshoot', '',
406
+ 'Create a new experimental setup overshoot configuration file',
407
+ auto_toolbar=False)
408
+ self.add_action('modify_overshoot', 'Modify Overshoot', '',
409
+ 'Modify an existing experimental setup overshoot configuration file',
410
+ auto_toolbar=False)
411
+
412
+ for ind_file, file in enumerate(config_mod_pymodaq.get_set_overshoot_path().iterdir()):
413
+ if file.suffix == '.xml':
414
+ self.add_action(self.get_action_from_file(file, ManagerEnums.overshoot), file.stem,
415
+ auto_toolbar=False)
416
+
417
+ self.add_action('save_roi', 'Save ROIs as a file', '', auto_toolbar=False)
418
+ self.add_action('modify_roi', 'Modify ROI file', '', auto_toolbar=False)
419
+
420
+ for ind_file, file in enumerate(config_mod_pymodaq.get_set_roi_path().iterdir()):
421
+ if file.suffix == '.xml':
422
+ self.add_action(self.get_action_from_file(file, ManagerEnums.roi), file.stem,
423
+ '', auto_toolbar=False)
424
+
425
+ self.add_action('new_remote', 'Create New Remote', '', auto_toolbar=False)
426
+ self.add_action('modify_remote', 'Modify Remote file', '', auto_toolbar=False)
427
+ for ind_file, file in enumerate(config_mod_pymodaq.get_set_remote_path().iterdir()):
428
+ if file.suffix == '.xml':
429
+ self.add_action(self.get_action_from_file(file, ManagerEnums.remote),
430
+ file.stem, '', auto_toolbar=False)
431
+ self.add_action('activate_overshoot', 'Activate overshoot', 'Error',
432
+ tip='if activated, apply an overshoot if one is configured',
433
+ checkable=True, enabled=False)
434
+ self.toolbar.addSeparator()
435
+ self.add_action('do_scan', 'Do Scans', 'surfacePlot',
436
+ tip='Open the DAQ Scan extension to acquire data as a function of '
437
+ 'one or more parameter')
438
+ self.toolbar.addSeparator()
439
+ self.add_action('do_log', 'Log data', '', auto_toolbar=False)
440
+ self.add_action('do_pid', 'PID module', auto_toolbar=False)
441
+ self.add_action('console', 'IPython Console', auto_toolbar=False)
442
+ self.add_action('bayesian', 'Bayesian Optimisation', auto_toolbar=False)
443
+
444
+ self.add_action('about', 'About', 'information2')
445
+ self.add_action('help', 'Help', 'help1')
446
+ self.get_action('help').setShortcut(QtGui.QKeySequence('F1'))
447
+ self.add_action('check_update', 'Check Updates', '', auto_toolbar=False)
448
+ self.toolbar.addSeparator()
449
+ self.add_action('plugin_manager', 'Plugin Manager', '')
450
+
451
+ def update_preset_action_list(self):
452
+ presets = []
453
+ self.get_action('preset_list').clear()
454
+ for ind_file, file in enumerate(self.preset_path.iterdir()):
455
+ if file.suffix == '.xml':
456
+ filestem = file.stem
457
+ if not self.has_action(self.get_action_from_file(file, ManagerEnums.preset)):
458
+ self.add_action(self.get_action_from_file(file, ManagerEnums.preset),
459
+ filestem, '', f'Load the {filestem}.xml preset',
460
+ auto_toolbar=False)
461
+ presets.append(filestem)
462
+
463
+ self.get_action('preset_list').addItems(presets)
464
+
465
+ def update_preset_action(self, preset_name: str):
466
+ self.get_action('load_preset').setToolTip(f'Load the {preset_name}.xml preset file!')
467
+
468
+ def connect_things(self):
469
+ self.status_signal[str].connect(self.add_status)
470
+ self.connect_action('log', self.show_log)
471
+ self.connect_action('config', self.show_config)
472
+ self.connect_action('quit', self.quit_fun)
473
+ self.connect_action('restart', self.restart_fun)
474
+ self.connect_action('leco', start_coordinator)
475
+ self.connect_action('load_layout', self.load_layout_state)
476
+ self.connect_action('save_layout', self.save_layout_state)
477
+ self.connect_action('log_window', self.logger_dock.setVisible)
478
+ self.connect_action('new_preset', self.create_preset)
479
+ self.connect_action('modify_preset', self.modify_preset)
480
+
481
+ for ind_file, file in enumerate(self.preset_path.iterdir()):
482
+ if file.suffix == '.xml':
483
+ self.connect_action(self.get_action_from_file(file, ManagerEnums.preset),
484
+ self.create_menu_slot(self.preset_path.joinpath(file)))
485
+ self.connect_action('load_preset',
486
+ lambda: self.set_preset_mode(
487
+ self.preset_path.joinpath(
488
+ f"{self.get_action('preset_list').currentText()}.xml")))
489
+ self.connect_action('new_overshoot', self.create_overshoot)
490
+ self.connect_action('modify_overshoot', self.modify_overshoot)
491
+ self.connect_action('activate_overshoot', self.activate_overshoot)
492
+
493
+ for ind_file, file in enumerate(config_mod_pymodaq.get_set_overshoot_path().iterdir()):
494
+ if file.suffix == '.xml':
495
+ self.connect_action(self.get_action_from_file(file, ManagerEnums.overshoot),
496
+ self.create_menu_slot_over(
497
+ config_mod_pymodaq.get_set_overshoot_path().joinpath(file)))
498
+
499
+ self.connect_action('save_roi', self.create_roi_file)
500
+ self.connect_action('modify_roi', self.modify_roi)
501
+
502
+ for ind_file, file in enumerate(config_mod_pymodaq.get_set_roi_path().iterdir()):
503
+ if file.suffix == '.xml':
504
+ self.connect_action(self.get_action_from_file(file, ManagerEnums.roi),
505
+ self.create_menu_slot_roi(config_mod_pymodaq.get_set_roi_path().joinpath(file)))
506
+
507
+ self.connect_action('new_remote', self.create_remote)
508
+ self.connect_action('modify_remote', self.modify_remote)
509
+ for ind_file, file in enumerate(config_mod_pymodaq.get_set_remote_path().iterdir()):
510
+ if file.suffix == '.xml':
511
+ self.connect_action(self.get_action_from_file(file, ManagerEnums.remote),
512
+ self.create_menu_slot_remote(
513
+ config_mod_pymodaq.get_set_remote_path().joinpath(file)))
514
+
515
+ self.connect_action('do_scan', lambda: self.load_scan_module())
516
+ self.connect_action('do_log', lambda: self.load_log_module())
517
+ self.connect_action('do_pid', lambda: self.load_pid_module())
518
+ self.connect_action('console', lambda: self.load_console())
519
+ self.connect_action('bayesian', lambda: self.load_bayesian())
520
+
521
+ self.connect_action('about', self.show_about)
522
+ self.connect_action('help', self.show_help)
523
+ self.connect_action('check_update', lambda: self.check_update(True))
524
+ self.connect_action('plugin_manager', self.start_plugin_manager)
525
+
526
+ def setup_menu(self, menubar: QtWidgets.QMenuBar = None):
268
527
  """
269
528
  Create the menubar object looking like :
270
529
  """
@@ -272,128 +531,95 @@ class DashBoard(QObject):
272
531
 
273
532
  # %% create Settings menu
274
533
  self.file_menu = menubar.addMenu('File')
275
- self.file_menu.addAction('Show log file', self.show_log)
276
- self.file_menu.addAction('Show configuration file', self.show_config)
534
+ self.file_menu.addAction(self.get_action('log'))
535
+ self.file_menu.addAction(self.get_action('config'))
277
536
  self.file_menu.addSeparator()
278
- quit_action = self.file_menu.addAction('Quit')
279
- restart_action = self.file_menu.addAction('Restart')
280
- quit_action.triggered.connect(self.quit_fun)
281
- restart_action.triggered.connect(self.restart_fun)
537
+ self.file_menu.addAction(self.get_action('quit'))
538
+ self.file_menu.addAction(self.get_action('restart'))
282
539
 
283
540
  self.settings_menu = menubar.addMenu('Settings')
284
- action_leco = self.settings_menu.addAction('Run Leco Coordinator')
285
- action_leco.triggered.connect(start_coordinator)
541
+ self.settings_menu.addAction(self.get_action('leco'))
286
542
  docked_menu = self.settings_menu.addMenu('Docked windows')
287
- action_load = docked_menu.addAction('Load Layout')
288
- action_save = docked_menu.addAction('Save Layout')
289
-
290
- action_load.triggered.connect(self.load_layout_state)
291
- action_save.triggered.connect(self.save_layout_state)
543
+ docked_menu.addAction(self.get_action('load_layout'))
544
+ docked_menu.addAction(self.get_action('save_layout'))
292
545
 
293
546
  docked_menu.addSeparator()
294
- action_show_log = docked_menu.addAction('Show/hide log window')
295
- action_show_log.setCheckable(True)
296
- action_show_log.toggled.connect(self.logger_dock.setVisible)
547
+ docked_menu.addAction(self.get_action('log_window'))
297
548
 
298
549
  self.preset_menu = menubar.addMenu('Preset Modes')
299
- action_new_preset = self.preset_menu.addAction('New Preset')
300
- # action.triggered.connect(lambda: self.show_file_attributes(type_info='managers'))
301
- action_new_preset.triggered.connect(self.create_preset)
302
- action_modify_preset = self.preset_menu.addAction('Modify Preset')
303
- action_modify_preset.triggered.connect(self.modify_preset)
550
+ self.preset_menu.addAction(self.get_action('new_preset'))
551
+ self.preset_menu.addAction(self.get_action('modify_preset'))
304
552
  self.preset_menu.addSeparator()
305
- self.load_preset = self.preset_menu.addMenu('Load presets')
553
+ self.load_preset_menu = self.preset_menu.addMenu('Load presets')
306
554
 
307
- slots = dict([])
308
555
  for ind_file, file in enumerate(self.preset_path.iterdir()):
309
556
  if file.suffix == '.xml':
310
- filestem = file.stem
311
- slots[filestem] = self.load_preset.addAction(filestem)
312
- slots[filestem].triggered.connect(
313
- self.create_menu_slot(self.preset_path.joinpath(file)))
557
+ self.load_preset_menu.addAction(
558
+ self.get_action(self.get_action_from_file(file, ManagerEnums.preset)))
314
559
 
315
560
  self.overshoot_menu = menubar.addMenu('Overshoot Modes')
316
- action_new_overshoot = self.overshoot_menu.addAction('New Overshoot')
317
- # action.triggered.connect(lambda: self.show_file_attributes(type_info='managers'))
318
- action_new_overshoot.triggered.connect(self.create_overshoot)
319
- action_modify_overshoot = self.overshoot_menu.addAction('Modify Overshoot')
320
- action_modify_overshoot.triggered.connect(self.modify_overshoot)
561
+ self.overshoot_menu.addAction(self.get_action('new_overshoot'))
562
+ self.overshoot_menu.addAction(self.get_action('modify_overshoot'))
563
+ self.overshoot_menu.addAction(self.get_action('activate_overshoot'))
321
564
  self.overshoot_menu.addSeparator()
322
- load_overshoot = self.overshoot_menu.addMenu('Load Overshoots')
565
+ load_overshoot_menu = self.overshoot_menu.addMenu('Load Overshoots')
323
566
 
324
- slots_over = dict([])
325
567
  for ind_file, file in enumerate(config_mod_pymodaq.get_set_overshoot_path().iterdir()):
326
568
  if file.suffix == '.xml':
327
- filestem = file.stem
328
- slots_over[filestem] = load_overshoot.addAction(filestem)
329
- slots_over[filestem].triggered.connect(
330
- self.create_menu_slot_over(
331
- config_mod_pymodaq.get_set_overshoot_path().joinpath(file)))
569
+ load_overshoot_menu.addAction(
570
+ self.get_action(self.get_action_from_file(file, ManagerEnums.overshoot)))
332
571
 
333
572
  self.roi_menu = menubar.addMenu('ROI Modes')
334
- action_new_roi = self.roi_menu.addAction('Save Current ROIs as a file')
335
- action_new_roi.triggered.connect(self.create_roi_file)
336
- action_modify_roi = self.roi_menu.addAction('Modify roi config')
337
- action_modify_roi.triggered.connect(self.modify_roi)
573
+ self.roi_menu.addAction(self.get_action('save_roi'))
574
+ self.roi_menu.addAction(self.get_action('modify_roi'))
338
575
  self.roi_menu.addSeparator()
339
- load_roi = self.roi_menu.addMenu('Load roi configs')
576
+ load_roi_menu = self.roi_menu.addMenu('Load roi configs')
340
577
 
341
- slots = dict([])
342
578
  for ind_file, file in enumerate(config_mod_pymodaq.get_set_roi_path().iterdir()):
343
579
  if file.suffix == '.xml':
344
- filestem = file.stem
345
- slots[filestem] = load_roi.addAction(filestem)
346
- slots[filestem].triggered.connect(
347
- self.create_menu_slot_roi(config_mod_pymodaq.get_set_roi_path().joinpath(file)))
580
+ load_roi_menu.addAction(
581
+ self.get_action(self.get_action_from_file(file, ManagerEnums.roi)))
348
582
 
349
583
  self.remote_menu = menubar.addMenu('Remote/Shortcuts Control')
350
584
  self.remote_menu.addAction('New remote config.', self.create_remote)
351
585
  self.remote_menu.addAction('Modify remote config.', self.modify_remote)
352
586
  self.remote_menu.addSeparator()
353
- load_remote = self.remote_menu.addMenu('Load remote config.')
587
+ load_remote_menu = self.remote_menu.addMenu('Load remote config.')
354
588
 
355
- slots = dict([])
356
589
  for ind_file, file in enumerate(config_mod_pymodaq.get_set_remote_path().iterdir()):
357
590
  if file.suffix == '.xml':
358
- filestem = file.stem
359
- slots[filestem] = load_remote.addAction(filestem)
360
- slots[filestem].triggered.connect(
361
- self.create_menu_slot_remote(
362
- config_mod_pymodaq.get_set_remote_path().joinpath(file)))
363
-
364
- # actions menu
365
- self.actions_menu = menubar.addMenu('Extensions')
366
- action_scan = self.actions_menu.addAction('Do Scans')
367
- action_scan.triggered.connect(lambda: self.load_scan_module())
368
- action_log = self.actions_menu.addAction('Log data')
369
- action_log.triggered.connect(lambda: self.load_log_module())
370
- action_pid = self.actions_menu.addAction('PID module')
371
- action_pid.triggered.connect(lambda: self.load_pid_module())
372
- action_console = self.actions_menu.addAction('IPython Console')
373
- action_console.triggered.connect(lambda: self.load_console())
374
- action_bayesian = self.actions_menu.addAction('Bayesian Optimisation')
375
- action_bayesian.triggered.connect(lambda: self.load_bayesian())
376
-
591
+ load_remote_menu.addAction(
592
+ self.get_action(self.get_action_from_file(file, ManagerEnums.remote)))
593
+
594
+ # extensions menu
595
+ self.extensions_menu = menubar.addMenu('Extensions')
596
+ self.extensions_menu.addAction(self.get_action('do_scan'))
597
+ self.extensions_menu.addAction(self.get_action('do_log'))
598
+ self.extensions_menu.addAction(self.get_action('do_pid'))
599
+ self.extensions_menu.addAction(self.get_action('console'))
600
+ self.extensions_menu.addAction(self.get_action('bayesian'))
601
+
602
+ # extensions from plugins
377
603
  extensions_actions = []
378
604
  for ext in extensions:
379
- extensions_actions.append(self.actions_menu.addAction(ext['name']))
605
+ extensions_actions.append(self.extensions_menu.addAction(ext['name']))
380
606
  extensions_actions[-1].triggered.connect(self.create_menu_slot_ext(ext))
381
607
 
382
-
383
608
  # help menu
384
609
  help_menu = menubar.addMenu('?')
385
- action_about = help_menu.addAction('About')
386
- action_about.triggered.connect(self.show_about)
387
- action_help = help_menu.addAction('Help')
388
- action_help.triggered.connect(self.show_help)
389
- action_help.setShortcut(QtGui.QKeySequence('F1'))
390
-
610
+ help_menu.addAction(self.get_action('about'))
611
+ help_menu.addAction(self.get_action('help'))
391
612
  help_menu.addSeparator()
392
- action_update = help_menu.addAction('Check Version')
393
- action_update.triggered.connect(lambda: self.check_version(True))
613
+ help_menu.addAction(self.get_action('check_update'))
614
+ help_menu.addAction(self.get_action('plugin_manager'))
394
615
 
395
- action_plugin_manager = help_menu.addAction('Plugin Manager')
396
- action_plugin_manager.triggered.connect(self.start_plugin_manager)
616
+ self.overshoot_menu.setEnabled(False)
617
+ self.roi_menu.setEnabled(False)
618
+ self.remote_menu.setEnabled(False)
619
+ self.extensions_menu.setEnabled(False)
620
+ self.file_menu.setEnabled(True)
621
+ self.settings_menu.setEnabled(True)
622
+ self.preset_menu.setEnabled(True)
397
623
 
398
624
  def start_plugin_manager(self):
399
625
  self.win_plug_manager = QtWidgets.QMainWindow()
@@ -424,7 +650,10 @@ class DashBoard(QObject):
424
650
  try:
425
651
  if self.preset_file is not None:
426
652
  self.roi_saver.set_new_roi(self.preset_file.stem)
427
- self.create_menu(self.menubar)
653
+ self.add_action(self.get_action_from_file(self.preset_file,
654
+ ManagerEnums.roi),
655
+ self.preset_file.stem, '')
656
+ self.setup_menu(self.menubar)
428
657
 
429
658
  except Exception as e:
430
659
  logger.exception(str(e))
@@ -433,7 +662,10 @@ class DashBoard(QObject):
433
662
  try:
434
663
  if self.preset_file is not None:
435
664
  self.remote_manager.set_new_remote(self.preset_file.stem)
436
- self.create_menu(self.menubar)
665
+ self.add_action(self.get_action_from_file(self.preset_file,
666
+ ManagerEnums.remote),
667
+ self.preset_file.stem, '')
668
+ self.setup_menu(self.menubar)
437
669
 
438
670
  except Exception as e:
439
671
  logger.exception(str(e))
@@ -442,18 +674,27 @@ class DashBoard(QObject):
442
674
  try:
443
675
  if self.preset_file is not None:
444
676
  self.overshoot_manager.set_new_overshoot(self.preset_file.stem)
445
- self.create_menu(self.menubar)
677
+ self.add_action(self.get_action_from_file(self.preset_file,
678
+ ManagerEnums.overshoot),
679
+ self.preset_file.stem, '')
680
+ self.setup_menu(self.menubar)
446
681
  except Exception as e:
447
682
  logger.exception(str(e))
448
683
 
449
684
  def create_preset(self):
450
685
  try:
451
- self.preset_manager.set_new_preset()
452
- self.create_menu(self.menubar)
453
- self.new_preset_created.emit()
686
+ status = self.preset_manager.set_new_preset()
687
+ if status:
688
+ self.update_preset_action_list()
689
+ self.setup_menu(self.menubar)
690
+ self.new_preset_created.emit()
454
691
  except Exception as e:
455
692
  logger.exception(str(e))
456
693
 
694
+ @staticmethod
695
+ def get_action_from_file(file: Path, manager: ManagerEnums):
696
+ return f'{file.stem}_{manager.name}'
697
+
457
698
  def modify_remote(self):
458
699
  try:
459
700
  path = select_file(start_path=config_mod_pymodaq.get_set_remote_path(), save=False,
@@ -619,7 +860,7 @@ class DashBoard(QObject):
619
860
  self.save_layout_state(path)
620
861
 
621
862
  def add_move(self, plug_name, plug_settings, plug_type, move_docks, move_forms,
622
- actuators_modules):
863
+ actuators_modules) -> DAQ_Move:
623
864
 
624
865
  move_docks.append(Dock(plug_name, size=(150, 250)))
625
866
  if len(move_docks) == 1:
@@ -640,18 +881,51 @@ class DashBoard(QObject):
640
881
  mssg = f'Could not set this setting: {str(e)}\n' \
641
882
  f'The Preset is no more compatible with the plugin {plug_type}'
642
883
  logger.warning(mssg)
643
- self.splash_sc.showMessage(mssg, color=Qt.white)
884
+ self.splash_sc.showMessage(mssg)
644
885
  QtWidgets.QApplication.processEvents()
645
886
 
646
887
  mov_mod_tmp.bounds_signal[bool].connect(self.stop_moves)
647
888
  move_docks[-1].addWidget(move_forms[-1])
648
889
  actuators_modules.append(mov_mod_tmp)
890
+ return mov_mod_tmp
649
891
 
650
- def add_det(self, plug_name, plug_settings, det_docks_settings, det_docks_viewer,
651
- detector_modules):
892
+ def add_move_from_extension(self, name: str, instrument_name: str,
893
+ instrument_controller: Any):
894
+ """ Specific method to add a DAQ_Move within the Dashboard. This Particular actuator
895
+ should be defined in the plugin of the extension and is used to mimic an actuator while
896
+ move_abs is actually triggering an action on the extension which loaded it
652
897
 
653
- plug_type = plug_settings.child('main_settings', 'DAQ_type').value()
654
- plug_subtype = plug_settings.child('main_settings', 'detector_type').value()
898
+ For an exemple, see the PyMoDAQ builtin PID extension
899
+
900
+ Parameters
901
+ ----------
902
+ name: str
903
+ The name to print on the UI title
904
+ instrument_name: str
905
+ The name of the instrument class, for instance PID for the daq_move_PID
906
+ module and the DAQ_Move_PID instrument class
907
+ instrument_controller: object
908
+ whatever object is used to communicate between the instrument module and the extension
909
+ which created it
910
+ """
911
+ actuator = self.add_move(name, None, instrument_name, [], [], [])
912
+ actuator.controller = instrument_controller
913
+ actuator.master = False
914
+ actuator.init_hardware_ui()
915
+ QtWidgets.QApplication.processEvents()
916
+ self.poll_init(actuator)
917
+ QtWidgets.QApplication.processEvents()
918
+
919
+ # Update actuators modules and module manager
920
+ self.actuators_modules.append(actuator)
921
+ self.update_module_manager()
922
+
923
+ def add_det(self, plug_name, plug_settings, det_docks_settings, det_docks_viewer,
924
+ detector_modules, plug_type: str = None, plug_subtype: str = None) -> DAQ_Viewer:
925
+ if plug_type is None:
926
+ plug_type = plug_settings.child('main_settings', 'DAQ_type').value()
927
+ if plug_subtype is None:
928
+ plug_subtype = plug_settings.child('main_settings', 'detector_type').value()
655
929
  det_docks_settings.append(Dock(plug_name + " settings", size=(150, 250)))
656
930
  det_docks_viewer.append(Dock(plug_name + " viewer", size=(350, 350)))
657
931
  if len(detector_modules) == 0:
@@ -676,11 +950,54 @@ class DashBoard(QObject):
676
950
  mssg = f'Could not set this setting: {str(e)}\n' \
677
951
  f'The Preset is no more compatible with the plugin {plug_subtype}'
678
952
  logger.warning(mssg)
679
- self.splash_sc.showMessage(mssg, color=Qt.white)
953
+ self.splash_sc.showMessage(mssg)
680
954
 
681
955
  detector_modules.append(det_mod_tmp)
956
+ return det_mod_tmp
682
957
 
683
- def set_file_preset(self, filename):
958
+ def add_det_from_extension(self, name: str, daq_type: str, instrument_name: str,
959
+ instrument_controller: Any):
960
+ """ Specific method to add a DAQ_Viewer within the Dashboard. This Particular detector
961
+ should be defined in the plugin of the extension and is used to mimic a grab while data
962
+ are actually coming from the extension which loaded it
963
+
964
+ For an exemple, see the pymodaq_plugins_datamixer plugin and its DataMixer extension
965
+
966
+ Parameters
967
+ ----------
968
+ name: str
969
+ The name to print on the UI title
970
+ daq_type: str
971
+ either DAQ0D, DAQ1D, DAQ2D or DAQND depending the type of the instrument
972
+ instrument_name: str
973
+ The name of the instrument class, for instance DataMixer for the daq_0Dviewer_DataMixer
974
+ module and the DAQ_0DViewer_DataMixer instrument class
975
+ instrument_controller: object
976
+ whatever object is used to communicate between the instrument module and the extension
977
+ which created it
978
+ """
979
+ detector = self.add_det(name, None, [], [], [],
980
+ plug_type=daq_type,
981
+ plug_subtype=instrument_name)
982
+ detector.controller = instrument_controller
983
+ detector.master = False
984
+ detector.init_hardware_ui()
985
+ QtWidgets.QApplication.processEvents()
986
+ self.poll_init(detector)
987
+ QtWidgets.QApplication.processEvents()
988
+
989
+ # Update actuators modules and module manager
990
+ self.detector_modules.append(detector)
991
+ self.update_module_manager()
992
+
993
+ def update_module_manager(self):
994
+ if self.modules_manager is None:
995
+ self.modules_manager = ModulesManager(self.detector_modules, self.actuators_modules)
996
+ else:
997
+ self.modules_manager.actuators_all = self.actuators_modules
998
+ self.modules_manager.detectors_all = self.detector_modules
999
+
1000
+ def set_file_preset(self, filename) -> Tuple[List[DAQ_Move], List[DAQ_Viewer]]:
684
1001
  """
685
1002
  Set a file managers from the converted xml file given by the filename parameter.
686
1003
 
@@ -718,17 +1035,16 @@ class DashBoard(QObject):
718
1035
  self.preset_manager.preset_params.child('Detectors').children()]
719
1036
 
720
1037
  for plug in plugins:
721
- plug['ID'] = plug['value'].child('params', 'main_settings', 'controller_ID').value()
722
1038
  if plug["type"] == 'det':
723
- plug['status'] = plug['value'].child(
724
- 'params', 'detector_settings', 'controller_status').value()
1039
+ plug['ID'] = plug['value']['params', 'detector_settings', 'controller_ID']
1040
+ plug['status'] = plug['value']['params', 'detector_settings',
1041
+ 'controller_status']
725
1042
  else:
726
- if 'multiaxes' in [child.name() for child in plug['value'].child(
727
- 'params', 'move_settings').children()]:
728
- plug['status'] = plug['value'].child(
729
- 'params', 'move_settings', 'multiaxes', 'multi_status').value()
730
- else:
731
- plug['status'] = 'Master'
1043
+ plug['ID'] = plug['value']['params', 'move_settings',
1044
+ 'multiaxes', 'controller_ID']
1045
+ plug['status'] = plug['value'][
1046
+ 'params', 'move_settings', 'multiaxes', 'multi_status']
1047
+
732
1048
 
733
1049
  IDs = list(set([plug['ID'] for plug in plugins]))
734
1050
  # %%
@@ -750,8 +1066,7 @@ class DashBoard(QObject):
750
1066
  plug_init = plugin['value'].child('init').value()
751
1067
  plug_settings = plugin['value'].child('params')
752
1068
  self.splash_sc.showMessage(
753
- 'Loading {:s} module: {:s}'.format(plugin['type'], plug_name),
754
- color=Qt.white)
1069
+ 'Loading {:s} module: {:s}'.format(plugin['type'], plug_name))
755
1070
 
756
1071
  if plugin['type'] == 'move':
757
1072
  plug_type = plug_settings.child('main_settings', 'move_type').value()
@@ -1030,33 +1345,22 @@ class DashBoard(QObject):
1030
1345
  self.update_status('Overshoot configuration ({}) has been loaded'.format(file),
1031
1346
  log_type='log')
1032
1347
  self.overshoot_manager.set_file_overshoot(filename, show=False)
1033
-
1034
- det_titles = [det.title for det in self.detector_modules]
1035
- move_titles = [move.title for move in self.actuators_modules]
1036
-
1037
- for det_param in self.overshoot_manager.overshoot_params.child(
1038
- 'Detectors').children():
1039
- if det_param.child(('trig_overshoot')).value():
1040
- det_index = det_titles.index(det_param.opts['title'])
1041
- det_module = self.detector_modules[det_index]
1042
- det_module.settings.child(
1043
- 'main_settings', 'overshoot', 'stop_overshoot').setValue(True)
1044
- det_module.settings.child(
1045
- 'main_settings', 'overshoot', 'overshoot_value').setValue(
1046
- det_param['overshoot_value'])
1047
- for move_param in det_param.child(('params')).children():
1048
- if move_param['move_overshoot']:
1049
- move_index = move_titles.index(move_param.opts['title'])
1050
- move_module = self.actuators_modules[move_index]
1051
- det_module.overshoot_signal.connect(
1052
- self.create_overshoot_fun(
1053
- move_module, move_param['position']))
1348
+ self.set_action_enabled('activate_overshoot', True)
1349
+ self.set_action_checked('activate_overshoot', False)
1350
+ self.get_action('activate_overshoot').trigger()
1054
1351
 
1055
1352
  except Exception as e:
1056
1353
  logger.exception(str(e))
1057
1354
 
1058
- def create_overshoot_fun(self, move_module, position):
1059
- return lambda: move_module.move_abs(position)
1355
+ def activate_overshoot(self, status: bool):
1356
+ try:
1357
+ self.overshoot_manager.activate_overshoot(self.detector_modules,
1358
+ self.actuators_modules,
1359
+ status)
1360
+ except Exception as e:
1361
+ logger.warning(f'Could not load the overshoot file:\n{str(e)}')
1362
+ self.set_action_checked('activate_overshoot', False)
1363
+ self.set_action_enabled('activate_overshoot', False)
1060
1364
 
1061
1365
  @property
1062
1366
  def move_modules(self):
@@ -1093,6 +1397,9 @@ class DashBoard(QObject):
1093
1397
  try:
1094
1398
  if not isinstance(filename, Path):
1095
1399
  filename = Path(filename)
1400
+
1401
+ self.get_action('preset_list').setCurrentText(filename.stem)
1402
+
1096
1403
  self.mainwindow.setVisible(False)
1097
1404
  for area in self.dockarea.tempAreas:
1098
1405
  area.window().setVisible(False)
@@ -1100,7 +1407,7 @@ class DashBoard(QObject):
1100
1407
  self.splash_sc.show()
1101
1408
  QtWidgets.QApplication.processEvents()
1102
1409
  self.splash_sc.raise_()
1103
- self.splash_sc.showMessage('Loading Modules, please wait', color=Qt.white)
1410
+ self.splash_sc.showMessage('Loading Modules, please wait')
1104
1411
  QtWidgets.QApplication.processEvents()
1105
1412
  self.clear_move_det_controllers()
1106
1413
  QtWidgets.QApplication.processEvents()
@@ -1109,7 +1416,7 @@ class DashBoard(QObject):
1109
1416
 
1110
1417
  try:
1111
1418
  actuators_modules, detector_modules = self.set_file_preset(filename)
1112
- except (ActuatorError, DetectorError, PIDError, MasterSlaveError) as error:
1419
+ except (ActuatorError, DetectorError, MasterSlaveError) as error:
1113
1420
  self.splash_sc.close()
1114
1421
  self.mainwindow.setVisible(True)
1115
1422
  for area in self.dockarea.tempAreas:
@@ -1128,55 +1435,7 @@ class DashBoard(QObject):
1128
1435
  self.actuators_modules = actuators_modules
1129
1436
  self.detector_modules = detector_modules
1130
1437
 
1131
- self.modules_manager = ModulesManager(self.detector_modules, self.actuators_modules)
1132
-
1133
- # Now that we have the module manager, load PID if it is checked in managers
1134
- try:
1135
- if self.preset_manager.preset_params.child('use_pid').value():
1136
- self.load_pid_module()
1137
-
1138
- self.pid_module.settings.child('models', 'model_class').setValue(
1139
- self.preset_manager.preset_params.child('pid_models').value())
1140
- QtWidgets.QApplication.processEvents()
1141
- self.pid_module.set_model()
1142
-
1143
- QtWidgets.QApplication.processEvents()
1144
-
1145
- for child in putils.iter_children_params(
1146
- self.preset_manager.preset_params.child('model_settings'),
1147
- []):
1148
- preset_path = self.preset_manager.preset_params.child(
1149
- 'model_settings').childPath(child)
1150
- path = ['models', 'model_params']
1151
- path.extend(preset_path)
1152
- self.pid_module.settings.child(*path).setValue(child.value())
1153
-
1154
- model_class = extmod.get_models(
1155
- self.preset_manager.preset_params.child('pid_models').value())['class']
1156
- for setp in model_class.setpoints_names:
1157
- self.add_move(setp, None, 'PID', [], [], actuators_modules)
1158
- actuators_modules[-1].controller = dict(
1159
- curr_point=self.pid_module.curr_points_signal,
1160
- setpoint=self.pid_module.setpoints_signal,
1161
- emit_curr_points=self.pid_module.emit_curr_points_sig)
1162
- actuators_modules[-1].init_hardware_ui()
1163
- QtWidgets.QApplication.processEvents()
1164
- self.poll_init(actuators_modules[-1])
1165
- QtWidgets.QApplication.processEvents()
1166
-
1167
- # Update actuators modules and module manager
1168
- self.actuators_modules = actuators_modules
1169
- self.modules_manager = ModulesManager(self.detector_modules,
1170
- self.actuators_modules)
1171
-
1172
- except Exception as e:
1173
- raise PIDError('Could not load the PID extension and create setpoints actuators'
1174
- f'{str(e)}')
1175
-
1176
- #
1177
- if self.pid_module is not None:
1178
- self.pid_module.get_action('ini_model').trigger()
1179
- # #
1438
+ self.update_module_manager()
1180
1439
 
1181
1440
  #####################################################
1182
1441
  self.overshoot_manager = OvershootManager(
@@ -1216,11 +1475,13 @@ class DashBoard(QObject):
1216
1475
  if self.pid_window is not None:
1217
1476
  self.pid_window.show()
1218
1477
 
1219
- self.load_preset.setEnabled(False)
1478
+ self.load_preset_menu.setEnabled(False)
1479
+ self.set_action_enabled('load_preset', False)
1480
+ self.set_action_enabled('preset_list', False)
1220
1481
  self.overshoot_menu.setEnabled(True)
1221
1482
  self.roi_menu.setEnabled(True)
1222
1483
  self.remote_menu.setEnabled(True)
1223
- self.actions_menu.setEnabled(True)
1484
+ self.extensions_menu.setEnabled(True)
1224
1485
  self.file_menu.setEnabled(True)
1225
1486
  self.settings_menu.setEnabled(True)
1226
1487
  self.update_init_tree()
@@ -1276,40 +1537,18 @@ class DashBoard(QObject):
1276
1537
  config_tree = TreeFromToml()
1277
1538
  config_tree.show_dialog()
1278
1539
 
1279
- def setupUI(self):
1540
+ def setup_docks(self):
1280
1541
 
1281
1542
  # %% create logger dock
1282
1543
  self.logger_dock = Dock("Logger")
1283
1544
  self.logger_list = QtWidgets.QListWidget()
1284
1545
  self.logger_list.setMinimumWidth(300)
1285
- self.init_tree = ParameterTree()
1286
- self.init_tree.setMinimumWidth(300)
1546
+
1287
1547
  splitter = QtWidgets.QSplitter(Qt.Vertical)
1288
- splitter.addWidget(self.init_tree)
1548
+ splitter.addWidget(self.settings_tree)
1289
1549
  splitter.addWidget(self.logger_list)
1290
1550
  self.logger_dock.addWidget(splitter)
1291
1551
 
1292
- self.settings = Parameter.create(name='init_settings', type='group', children=[
1293
- {'title': 'Log level', 'name': 'log_level', 'type': 'list',
1294
- 'value': config('general', 'debug_level'),
1295
- 'limits': config('general', 'debug_levels')},
1296
-
1297
- {'title': 'Loaded presets', 'name': 'loaded_files', 'type': 'group', 'children': [
1298
- {'title': 'Preset file', 'name': 'preset_file', 'type': 'str', 'value': '',
1299
- 'readonly': True},
1300
- {'title': 'Overshoot file', 'name': 'overshoot_file', 'type': 'str', 'value': '',
1301
- 'readonly': True},
1302
- {'title': 'Layout file', 'name': 'layout_file', 'type': 'str', 'value': '',
1303
- 'readonly': True},
1304
- {'title': 'ROI file', 'name': 'roi_file', 'type': 'str', 'value': '',
1305
- 'readonly': True},
1306
- {'title': 'Remote file', 'name': 'remote_file', 'type': 'str', 'value': '',
1307
- 'readonly': True},
1308
- ]},
1309
- {'title': 'Actuators Init.', 'name': 'actuators', 'type': 'group', 'children': []},
1310
- {'title': 'Detectors Init.', 'name': 'detectors', 'type': 'group', 'children': []},
1311
- ])
1312
- self.init_tree.setParameters(self.settings, showTop=False)
1313
1552
  self.remote_dock = Dock('Remote controls')
1314
1553
  self.dockarea.addDock(self.remote_dock, 'top')
1315
1554
  self.dockarea.addDock(self.logger_dock, 'above', self.remote_dock)
@@ -1318,21 +1557,9 @@ class DashBoard(QObject):
1318
1557
  self.remote_dock.setVisible(False)
1319
1558
  self.preset_manager = PresetManager(path=self.preset_path, extra_params=self.extra_params)
1320
1559
 
1321
- # creating the menubar
1322
- self.menubar = self.mainwindow.menuBar()
1323
- self.create_menu(self.menubar)
1324
- self.overshoot_menu.setEnabled(False)
1325
- self.roi_menu.setEnabled(False)
1326
- self.remote_menu.setEnabled(False)
1327
- self.actions_menu.setEnabled(False)
1328
- # connecting
1329
- self.status_signal[str].connect(self.add_status)
1330
-
1331
- self.file_menu.setEnabled(True)
1332
- # self.actions_menu.setEnabled(True)
1333
- self.settings_menu.setEnabled(True)
1334
- self.preset_menu.setEnabled(True)
1335
- self.mainwindow.setVisible(True)
1560
+ @property
1561
+ def menubar(self):
1562
+ return self._menubar
1336
1563
 
1337
1564
  def parameter_tree_changed(self, param, changes):
1338
1565
  """
@@ -1366,36 +1593,76 @@ class DashBoard(QObject):
1366
1593
  self.splash_sc.showMessage(
1367
1594
  f"PyMoDAQ version {get_version('pymodaq')}\n"
1368
1595
  f"Modular Acquisition with Python\n"
1369
- f"Written by Sébastien Weber",
1370
- QtCore.Qt.AlignRight, QtCore.Qt.white)
1596
+ f"Written by Sébastien Weber")
1597
+
1598
+ def check_update(self, show=True):
1371
1599
 
1372
- def check_version(self, show=True):
1373
1600
  try:
1374
- current_version = version_mod.parse(get_version())
1375
- available_version = version_mod.parse(get_pypi_pymodaq('pymodaq')['version'])
1376
- msgBox = QtWidgets.QMessageBox()
1377
- if available_version > current_version:
1378
- msgBox.setText(f"A new version of PyMoDAQ is available, {str(available_version)}!")
1379
- msgBox.setInformativeText("Do you want to install it?")
1380
- msgBox.setStandardButtons(msgBox.Ok | msgBox.Cancel)
1381
- msgBox.setDefaultButton(msgBox.Ok)
1382
-
1383
- ret = msgBox.exec()
1384
-
1385
- if ret == msgBox.Ok:
1386
- command = [sys.executable, '-m', 'pip', 'install',
1387
- f'pymodaq=={str(available_version)}']
1388
- subprocess.Popen(command)
1389
-
1390
- self.restart_fun()
1601
+ packages = ['pymodaq_utils', 'pymodaq_data', 'pymodaq_gui', 'pymodaq']
1602
+ current_versions = [version_mod.parse(get_version(p)) for p in packages]
1603
+ available_versions = [version_mod.parse(get_pypi_pymodaq(p)['version']) for p in packages]
1604
+ new_versions = np.greater(available_versions, current_versions)
1605
+ # Combine package and version information and select only the ones with a newer version available
1606
+
1607
+
1608
+ packages_data = np.array(list(zip(packages, current_versions, available_versions)))[new_versions]
1609
+
1610
+ #TODO: Remove `or True`
1611
+ if len(packages_data) > 0:
1612
+ #Create a QDialog window and different graphical components
1613
+ dialog = QtWidgets.QDialog()
1614
+ dialog.setWindowTitle("Update check")
1615
+
1616
+ vlayout = QtWidgets.QVBoxLayout()
1617
+
1618
+ message_label = QLabel("New versions of PyMoDAQ packages available!\nPlease select the ones you want to install:")
1619
+ message_label.setAlignment(Qt.AlignCenter)
1620
+
1621
+
1622
+ table = PymodaqUpdateTableWidget()
1623
+ table.setRowCount(len(packages_data))
1624
+ table.setColumnCount(4)
1625
+ table.setHorizontalHeaderLabels(["Select", "Package", "Current version", "New version"])
1626
+
1627
+ for p in packages_data:
1628
+ table.append_row(QCheckBox(), p[0], p[1], p[2])
1629
+
1630
+ button = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
1631
+ button.accepted.connect(dialog.accept)
1632
+ button.rejected.connect(dialog.reject)
1633
+
1634
+ # The vlayout contains the message, the table and the buttons
1635
+ # and is connected to the dialog window
1636
+ vlayout.addWidget(message_label)
1637
+ vlayout.addWidget(table)
1638
+ vlayout.addWidget(button)
1639
+ dialog.setLayout(vlayout)
1640
+
1641
+ ret = dialog.exec()
1642
+
1643
+ if ret == QDialog.Accepted:
1644
+ # If the update is accepted, the checked packages are extracted from the table
1645
+ # and send to the updater
1646
+ packages_to_update = table.get_checked_data()
1647
+ if len(packages_to_update) > 0:
1648
+ packages_to_update_str = ', '.join(packages_to_update)
1649
+ logger.info("Trying to update:")
1650
+ logger.info(f"\t {packages_to_update_str}")
1651
+ subprocess.Popen(['pymodaq_updater', '--wait', '--file', __file__] + packages_to_update, stdin=subprocess.PIPE)
1652
+ self.quit_fun()
1653
+ return True
1654
+ logger.info("Update found but no packages checked for update.")
1391
1655
  else:
1392
1656
  if show:
1393
- msgBox.setText(f"Your version of PyMoDAQ,"
1394
- f" {str(current_version)}, is up to date!")
1657
+ msgBox = QtWidgets.QMessageBox()
1658
+ msgBox.setWindowTitle("Update check")
1659
+ msgBox.setText("Everything is up to date!")
1395
1660
  ret = msgBox.exec()
1396
1661
  except Exception as e:
1397
1662
  logger.exception("Error while checking the available PyMoDAQ version")
1398
1663
 
1664
+ return False
1665
+
1399
1666
  def show_file_attributes(self, type_info='dataset'):
1400
1667
  """
1401
1668
  Switch the type_info value.
@@ -1465,6 +1732,7 @@ class DashBoard(QObject):
1465
1732
 
1466
1733
  def main():
1467
1734
  from pymodaq_gui.utils.utils import mkQApp
1735
+
1468
1736
  app = mkQApp('Dashboard')
1469
1737
 
1470
1738
  win = QtWidgets.QMainWindow()
@@ -1473,10 +1741,11 @@ def main():
1473
1741
  win.resize(1000, 500)
1474
1742
  win.setWindowTitle('PyMoDAQ Dashboard')
1475
1743
 
1476
- # win.setVisible(False)
1477
1744
  prog = DashBoard(area)
1745
+
1746
+ win.show()
1747
+
1478
1748
  app.exec()
1479
- return prog, win
1480
1749
 
1481
1750
 
1482
1751
  if __name__ == '__main__':