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
@@ -0,0 +1,591 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created the 03/10/2022
4
+
5
+ @author: Sebastien Weber
6
+ """
7
+ from random import randint
8
+ from typing import Optional, Type, Union
9
+ from easydict import EasyDict as edict
10
+
11
+ from qtpy.QtCore import Signal, QObject, Qt, Slot, QThread
12
+
13
+ from pymodaq.control_modules.thread_commands import ThreadStatus
14
+ from pymodaq_utils.utils import ThreadCommand, find_dict_in_list_from_key_val
15
+ from pymodaq_utils.config import Config
16
+ from pymodaq_utils.enums import BaseEnum
17
+ from pymodaq_utils.logger import get_base_logger, set_logger, get_module_name
18
+
19
+ from pymodaq_gui.parameter import Parameter, ioxml
20
+ from pymodaq_gui.parameter.utils import ParameterWithPath
21
+ from pymodaq_gui.managers.parameter_manager import ParameterManager
22
+ from pymodaq_gui.plotting.data_viewers import ViewersEnum
23
+ from pymodaq_gui.h5modules.saving import H5Saver
24
+
25
+ from pymodaq.utils.tcp_ip.tcp_server_client import TCPClient
26
+ from pymodaq.utils.exceptions import DetectorError
27
+ from pymodaq.utils.leco.pymodaq_listener import ActorListener, LECOClientCommands, LECOCommands
28
+
29
+ from pymodaq.utils.daq_utils import get_plugins
30
+ from pymodaq.utils.h5modules.module_saving import DetectorSaver, ActuatorSaver
31
+ from pymodaq.utils.config import Config as ControlModulesConfig
32
+
33
+
34
+ class DAQTypesEnum(BaseEnum):
35
+ """enum relating a given DAQType and a viewer type
36
+ See Also
37
+ --------
38
+ pymodaq.utils.plotting.data_viewers.viewer.ViewersEnum
39
+ """
40
+ DAQ0D = 'Viewer0D'
41
+ DAQ1D = 'Viewer1D'
42
+ DAQ2D = 'Viewer2D'
43
+ DAQND = 'ViewerND'
44
+
45
+ def to_data_type(self):
46
+ return ViewersEnum[self.value].value
47
+
48
+ def to_viewer_type(self):
49
+ return self.value
50
+
51
+ def to_daq_type(self):
52
+ return self.name
53
+
54
+ def increase_dim(self, ndim: int):
55
+ dim = self.get_dim()
56
+ if dim != 'N':
57
+ dim_as_int = int(dim) + ndim
58
+ if dim_as_int > 2:
59
+ dim = 'N'
60
+ else:
61
+ dim = str(dim_as_int)
62
+ else:
63
+ dim = 'N'
64
+ return DAQTypesEnum(f'Viewer{dim}D')
65
+
66
+ def get_dim(self):
67
+ return self.value.split('Viewer')[1].split('D')[0]
68
+
69
+
70
+ DAQ_TYPES = DAQTypesEnum
71
+
72
+ DET_TYPES = {'DAQ0D': get_plugins('daq_0Dviewer'),
73
+ 'DAQ1D': get_plugins('daq_1Dviewer'),
74
+ 'DAQ2D': get_plugins('daq_2Dviewer'),
75
+ 'DAQND': get_plugins('daq_NDviewer'),
76
+ }
77
+
78
+ if len(DET_TYPES['DAQ0D']) == 0:
79
+ raise DetectorError('No installed Detector')
80
+
81
+
82
+ config_utils = Config()
83
+ config = ControlModulesConfig()
84
+ logger = set_logger(get_module_name(__file__))
85
+
86
+
87
+ class ViewerError(Exception):
88
+ pass
89
+
90
+
91
+ def get_viewer_plugins(daq_type, det_name):
92
+ parent_module = find_dict_in_list_from_key_val(DET_TYPES[daq_type], 'name', det_name)
93
+ match_name = daq_type.lower()
94
+ match_name = f'{match_name[0:3]}_{match_name[3:].upper()}viewer_'
95
+ obj = getattr(getattr(parent_module['module'], match_name + det_name),
96
+ f'{match_name[0:7].upper()}{match_name[7:]}{det_name}')
97
+ params = getattr(obj, 'params')
98
+ det_params = Parameter.create(name='Det Settings', type='group', children=params)
99
+ return det_params, obj
100
+
101
+
102
+ class ControlModule(QObject):
103
+ """Abstract Base class common to both DAQ_Move and DAQ_Viewer control modules
104
+
105
+ Attributes
106
+ ----------
107
+ init_signal : Signal[bool]
108
+ This signal is emitted when the chosen hardware is correctly initialized
109
+ command_hardware : Signal[ThreadCommand]
110
+ This signal is used to communicate with the instrument plugin within a separate thread
111
+ command_tcpip : Signal[ThreadCommand]
112
+ This signal is used to communicate through the TCP/IP Network
113
+ quit_signal : Signal[]
114
+ This signal is emitted when the user requested to stop the module
115
+ """
116
+ init_signal = Signal(bool)
117
+ command_hardware = Signal(ThreadCommand)
118
+ _command_tcpip = Signal(ThreadCommand)
119
+ quit_signal = Signal()
120
+ _update_settings_signal = Signal(edict)
121
+ status_sig = Signal(str)
122
+ custom_sig = Signal(ThreadCommand)
123
+ ui = None
124
+
125
+ def __init__(self):
126
+ super().__init__()
127
+ self._title = ""
128
+ self.config = config
129
+ # the hardware controller instance set after initialization and to be used by other modules if they share the
130
+ # same controller
131
+ self.controller = None
132
+ self._initialized_state = False
133
+ self._send_to_tcpip = False
134
+ self._tcpclient_thread = None
135
+ self._hardware_thread = None
136
+
137
+ self.plugin_config: Optional[Config] = None
138
+
139
+ self._h5saver: Optional[H5Saver] = None
140
+ self._module_and_data_saver = None
141
+
142
+ def __repr__(self):
143
+ return f'{self.__class__.__name__}: {self.title}'
144
+
145
+ def create_new_file(self, new_file: bool):
146
+ if new_file:
147
+ self.close_file()
148
+
149
+ self.module_and_data_saver.h5saver = self.h5saver
150
+ return True
151
+
152
+ @property
153
+ def h5saver(self):
154
+ if self._h5saver is None:
155
+ self._h5saver = H5Saver(backend=config_utils('general', 'hdf5_backend'))
156
+ if self._h5saver.h5_file is None:
157
+ self._h5saver.init_file(update_h5=True)
158
+ if not self._h5saver.isopen():
159
+ self._h5saver.init_file(addhoc_file_path=self._h5saver.settings['current_h5_file'])
160
+ return self._h5saver
161
+
162
+ @h5saver.setter
163
+ def h5saver(self, h5saver_temp: H5Saver):
164
+ self._h5saver = h5saver_temp
165
+
166
+ def close_file(self):
167
+ self.h5saver.close_file()
168
+
169
+ @property
170
+ def module_and_data_saver(self):
171
+ if self._module_and_data_saver.h5saver is None or not self._module_and_data_saver.h5saver.isopen():
172
+ self._module_and_data_saver.h5saver = self.h5saver
173
+ return self._module_and_data_saver
174
+
175
+ @module_and_data_saver.setter
176
+ def module_and_data_saver(self, mod: Union[DetectorSaver, ActuatorSaver]):
177
+ self._module_and_data_saver = mod
178
+ self._module_and_data_saver.h5saver = self.h5saver
179
+
180
+ def custom_command(self, command: str, **kwargs):
181
+ self.command_hardware.emit(ThreadCommand(command, kwargs))
182
+
183
+ def thread_status(self, status: ThreadCommand, control_module_type='detector'):
184
+ """Get back info (using the ThreadCommand object) from the hardware
185
+
186
+ And re-emit this ThreadCommand using the custom_sig signal if it should be used in a higher level module
187
+
188
+
189
+ Parameters
190
+ ----------
191
+ status: ThreadCommand
192
+ The info returned from the hardware, the command (str) can be either:
193
+ * Update_Status: display messages and log info (deprecated)
194
+ * update_status: display info on the UI status bar
195
+ * close: close the current thread and delete corresponding attribute on cascade.
196
+ * update_settings: Update the "detector setting" node in the settings tree.
197
+ * update_main_settings: update the "main setting" node in the settings tree
198
+ * raise_timeout:
199
+ * show_splash: Display the splash screen with attribute as message
200
+ * close_splash
201
+ * show_config: display the plugin configuration
202
+ """
203
+
204
+ if status.command == "Update_Status":
205
+ # legacy
206
+ if len(status.attribute) > 1:
207
+ self.update_status(status.attribute[0], log=status.attribute[1])
208
+ else:
209
+ self.update_status(status.attribute[0])
210
+
211
+ elif status.command == ThreadStatus.UPDATE_STATUS:
212
+ self.update_status(status.attribute)
213
+
214
+ elif status.command == ThreadStatus.CLOSE:
215
+ try:
216
+ self.update_status(status.attribute[0])
217
+ self._hardware_thread.quit()
218
+ terminated = self._hardware_thread.wait(5000)
219
+ if not terminated:
220
+ self._hardware_thread.terminate()
221
+ self._hardware_thread.wait()
222
+ self.update_status('thread is locked?!', 'log')
223
+ except Exception as e:
224
+ logger.exception(f'Wrong call to the "close" command: \n{str(e)}')
225
+
226
+ self._initialized_state = False
227
+ self.init_signal.emit(self._initialized_state)
228
+
229
+ elif status.command == ThreadStatus.UPDATE_MAIN_SETTINGS:
230
+ # this is a way for the plugins to update main settings of the ui (solely values, limits and options)
231
+ try:
232
+ if status.attribute[2] == 'value':
233
+ self.settings.child('main_settings', *status.attribute[0]).setValue(status.attribute[1])
234
+ elif status.attribute[2] == 'limits':
235
+ self.settings.child('main_settings', *status.attribute[0]).setLimits(status.attribute[1])
236
+ elif status.attribute[2] == 'options':
237
+ self.settings.child('main_settings', *status.attribute[0]).setOpts(**status.attribute[1])
238
+ except Exception as e:
239
+ logger.exception(f'Wrong call to the "update_main_settings" command: \n{str(e)}')
240
+
241
+ elif status.command == ThreadStatus.UPDATE_SETTINGS:
242
+ # using this the settings shown in the UI for the plugin reflects the real plugin settings
243
+ try:
244
+ self.settings.sigTreeStateChanged.disconnect(
245
+ self.parameter_tree_changed) # any changes on the detcetor settings will update accordingly the gui
246
+ except Exception as e:
247
+ logger.exception(str(e))
248
+ try:
249
+ if status.attribute[2] == 'value':
250
+ self.settings.child(f'{control_module_type}_settings',
251
+ *status.attribute[0]).setValue(status.attribute[1])
252
+ elif status.attribute[2] == 'limits':
253
+ self.settings.child(f'{control_module_type}_settings',
254
+ *status.attribute[0]).setLimits(status.attribute[1])
255
+
256
+ elif status.attribute[2] == 'options':
257
+ self.settings.child(f'{control_module_type}_settings',
258
+ *status.attribute[0]).setOpts(**status.attribute[1])
259
+ elif status.attribute[2] == 'childAdded':
260
+ child = Parameter.create(name='tmp')
261
+ child.restoreState(status.attribute[1][0])
262
+ self.settings.child(f'{control_module_type}_settings',
263
+ *status.attribute[0]).addChild(status.attribute[1][0])
264
+
265
+ except Exception as e:
266
+ logger.exception(f'Wrong call to the "update_settings" command: \n{str(e)}')
267
+ self.settings.sigTreeStateChanged.connect(self.parameter_tree_changed)
268
+
269
+ elif status.command == ThreadStatus.UPDATE_UI:
270
+ try:
271
+ if self.ui is not None:
272
+ if hasattr(self.ui, status.attribute):
273
+ getattr(self.ui, status.attribute)(*status.args,
274
+ **status.kwargs)
275
+ except Exception as e:
276
+ logger.info(f'Wrong call to the "update_ui" command: \n{str(e)}')
277
+
278
+ elif status.command == ThreadStatus.RAISE_TIMEOUT:
279
+ self.raise_timeout()
280
+
281
+ elif status.command == ThreadStatus.SHOW_SPLASH:
282
+ self.settings_tree.setEnabled(False)
283
+ self.splash_sc.show()
284
+ self.splash_sc.raise_()
285
+ self.splash_sc.showMessage(status.attribute, color=Qt.white)
286
+
287
+ elif status.command == ThreadStatus.CLOSE_SPLASH:
288
+ self.splash_sc.close()
289
+ self.settings_tree.setEnabled(True)
290
+
291
+ self.custom_sig.emit(status) # to be used if needed in custom application connected to this module
292
+
293
+ @property
294
+ def module_type(self):
295
+ """str: Get the module type, either DAQ_Move or DAQ_viewer"""
296
+ return type(self).__name__
297
+
298
+ @property
299
+ def initialized_state(self):
300
+ """bool: Check if the module is initialized"""
301
+ return self._initialized_state
302
+
303
+ @property
304
+ def title(self):
305
+ """str: get the title of the module"""
306
+ return self._title
307
+
308
+ def grab(self):
309
+ """Programmatic entry to grab data from detectors or current value from actuator"""
310
+ raise NotImplementedError
311
+
312
+ def stop_grab(self):
313
+ """Programmatic entry to stop data grabbing from detectors or current value polling from actuator"""
314
+ raise NotImplementedError
315
+
316
+ def _add_data_to_saver(self, *args, **kwargs):
317
+ raise NotImplementedError
318
+
319
+ def append_data(self, *args, **kwargs):
320
+ raise NotImplementedError
321
+
322
+ def insert_data(self, *args, **kwargs):
323
+ raise NotImplementedError
324
+
325
+ def quit_fun(self):
326
+ """Programmatic entry to quit the control module"""
327
+ raise NotImplementedError
328
+
329
+ def init_hardware(self, do_init=True):
330
+ """Programmatic entry to initialize/deinitialize the control module
331
+
332
+ Parameters
333
+ ----------
334
+ do_init : bool
335
+ if True initialize the selected hardware else deinitialize it
336
+
337
+ See Also
338
+ --------
339
+ :meth:`init_hardware_ui`
340
+ """
341
+ raise NotImplementedError
342
+
343
+ def init_hardware_ui(self, do_init=True):
344
+ """Programmatic entry to simulate a click on the user interface init button
345
+
346
+ Parameters
347
+ ----------
348
+ do_init : bool
349
+ if True initialize the selected hardware else deinitialize it
350
+
351
+ Notes
352
+ -----
353
+ This method should be preferred to :meth:`init_hardware`
354
+ """
355
+ if self.ui is not None:
356
+ self.ui.do_init(do_init)
357
+
358
+ def show_log(self):
359
+ """Open the log file in the default text editor"""
360
+ import webbrowser
361
+ webbrowser.open(get_base_logger(logger).handlers[0].baseFilename)
362
+
363
+ def show_config(self, config: Config) -> Config:
364
+ """ Display in a tree the current configuration"""
365
+ if config is not None:
366
+ from pymodaq_gui.utils.widgets.tree_toml import TreeFromToml
367
+ config_tree = TreeFromToml(config)
368
+ config_tree.show_dialog()
369
+
370
+ return ControlModulesConfig()
371
+
372
+ def update_status(self, txt: str, log=True):
373
+ """Display a message in the ui status bar and eventually log the message
374
+
375
+ Parameters
376
+ ----------
377
+ txt : str
378
+ message to display
379
+ log : bool
380
+ if True, log the message in the logger
381
+ """
382
+ if self.ui is not None:
383
+ self.ui.display_status(txt)
384
+ self.status_sig.emit(txt)
385
+ if log:
386
+ logger.info(txt)
387
+
388
+ def manage_ui_actions(self, action_name: str, attribute: str, value):
389
+ """Method to manage actions for the UI (if any).
390
+
391
+ Will try to apply the given value to the given attribute of the corresponding action
392
+
393
+ Parameters
394
+ ----------
395
+ action_name: str
396
+ attribute: method signature or attribute
397
+ value: object
398
+ actual type and value depend on the triggered attribute
399
+
400
+ Examples
401
+ --------
402
+ >>>manage_ui_actions('quit', 'setEnabled', False)
403
+ # will disable the quit action (button) on the UI
404
+ """
405
+ if self.ui is not None:
406
+ if self.ui.has_action(action_name):
407
+ action = self.ui.get_action(action_name)
408
+ if hasattr(action, attribute):
409
+ attr = getattr(action, attribute)
410
+ if callable(attr):
411
+ attr(value)
412
+ else:
413
+ attr = value
414
+
415
+
416
+ class ParameterControlModule(ParameterManager, ControlModule):
417
+ """Base class for a control module with parameters."""
418
+
419
+ _update_settings_signal = Signal(edict)
420
+
421
+ listener_class: Type[ActorListener] = ActorListener
422
+
423
+ def __init__(self, **kwargs):
424
+ ParameterManager.__init__(self, action_list=('save', 'update'))
425
+ ControlModule.__init__(self)
426
+
427
+ def value_changed(self, param: Parameter) -> Optional[Parameter]:
428
+ """ParameterManager subclassed method. Process events from value changed by user in the UI Settings
429
+
430
+ Parameters
431
+ ----------
432
+ param: Parameter
433
+ a given parameter whose value has been changed by user
434
+ """
435
+ if param.name() == 'plugin_config':
436
+ self.show_config(self.plugin_config)
437
+
438
+ elif param.name() == 'connect_server':
439
+ if param.value():
440
+ self.connect_tcp_ip()
441
+ else:
442
+ self._command_tcpip.emit(ThreadCommand('quit', ))
443
+
444
+ elif param.name() == 'ip_address' or param.name == 'port':
445
+ self._command_tcpip.emit(
446
+ ThreadCommand('update_connection',
447
+ dict(ipaddress=self.settings['main_settings', 'tcpip', 'ip_address'],
448
+ port=self.settings['main_settings', 'tcpip', 'port'])))
449
+
450
+ elif param.name() == 'connect_leco_server':
451
+ self.connect_leco(param.value())
452
+
453
+ elif param.name() == "name":
454
+ name = param.value()
455
+ try:
456
+ self._leco_client.name = name
457
+ except AttributeError:
458
+ pass
459
+
460
+ else:
461
+ # not handled
462
+ return param
463
+
464
+ def _update_settings(self, param: Parameter):
465
+ # I do not understand what it does
466
+ path = self.settings.childPath(param)
467
+ if path is not None:
468
+ if 'main_settings' not in path:
469
+ self._update_settings_signal.emit(edict(path=path, param=param, change='value'))
470
+ if self.settings.child('main_settings', 'tcpip', 'tcp_connected').value():
471
+ self._command_tcpip.emit(ThreadCommand('send_info', dict(path=path, param=param)))
472
+ if self.settings.child('main_settings', 'leco', 'leco_connected').value():
473
+ self._command_tcpip.emit(
474
+ ThreadCommand(LECOCommands.SEND_INFO,
475
+ ParameterWithPath(param, path)))
476
+
477
+ def connect_tcp_ip(self, params_state=None, client_type: str = "GRABBER") -> None:
478
+ """Init a TCPClient in a separated thread to communicate with a distant TCp/IP Server
479
+
480
+ Use the settings: ip_address and port to specify the connection
481
+
482
+ See Also
483
+ --------
484
+ TCPServer
485
+ """
486
+ if self.settings.child('main_settings', 'tcpip', 'connect_server').value():
487
+ self._tcpclient_thread = QThread()
488
+
489
+ tcpclient = TCPClient(self.settings.child('main_settings', 'tcpip', 'ip_address').value(),
490
+ self.settings.child('main_settings', 'tcpip', 'port').value(),
491
+ params_state=params_state,
492
+ client_type=client_type)
493
+ tcpclient.moveToThread(self._tcpclient_thread)
494
+ self._tcpclient_thread.tcpclient = tcpclient
495
+ tcpclient.cmd_signal.connect(self.process_tcpip_cmds)
496
+
497
+ self._command_tcpip[ThreadCommand].connect(tcpclient.queue_command)
498
+ self._tcpclient_thread.started.connect(tcpclient.init_connection)
499
+
500
+ self._tcpclient_thread.start()
501
+
502
+ def get_leco_name(self) -> str:
503
+ name = self.settings["main_settings", "leco", "leco_name"]
504
+ if name == '':
505
+ # take the module name as alternative
506
+ name = self.settings["main_settings", "module_name"]
507
+ if name == '':
508
+ # a name is required, invent one
509
+ name = f"viewer_{randint(0, 10000)}"
510
+ name = self.settings.child("main_settings", "leco", "leco_name").setValue(name)
511
+ return name
512
+
513
+ def get_leco_host_port(self) -> tuple:
514
+ host = self.settings["main_settings", "leco", "host"]
515
+ port = self.settings["main_settings", "leco", "port"]
516
+ if host == '':
517
+ # take the localhost as default
518
+ host = 'localhost'
519
+ if port == '':
520
+ # take the default port as 12300
521
+ port = 12300
522
+ return (host, port)
523
+
524
+ def connect_leco(self, connect: bool) -> None:
525
+ if connect:
526
+ name = self.get_leco_name()
527
+ host, port = self.get_leco_host_port()
528
+ try:
529
+ self._leco_client.name = name
530
+ except AttributeError:
531
+ self._leco_client = self.listener_class(name=name, host=host, port=port)
532
+ self._leco_client.cmd_signal.connect(self.process_tcpip_cmds)
533
+ self._command_tcpip[ThreadCommand].connect(self._leco_client.queue_command)
534
+ self._leco_client.start_listen()
535
+ # self._leco_client.cmd_signal.emit(ThreadCommand(LECOCommands.SET_INFO, attribute=["detector_settings", ""]))
536
+ else:
537
+ self._command_tcpip.emit(ThreadCommand(LECOCommands.QUIT, ))
538
+ try:
539
+ self._command_tcpip[ThreadCommand].disconnect(self._leco_client.queue_command)
540
+ except TypeError:
541
+ pass # already disconnected
542
+
543
+ @Slot(ThreadCommand)
544
+ def process_tcpip_cmds(self, status: ThreadCommand) -> Optional[ThreadCommand]:
545
+ if status.command == 'connected':
546
+ self.settings.child('main_settings', 'tcpip', 'tcp_connected').setValue(True)
547
+
548
+ elif status.command == 'disconnected':
549
+ self.settings.child('main_settings', 'tcpip', 'tcp_connected').setValue(False)
550
+
551
+ elif status.command == LECOClientCommands.LECO_CONNECTED:
552
+ self.settings.child('main_settings', 'leco', 'leco_connected').setValue(True)
553
+
554
+ elif status.command == LECOClientCommands.LECO_DISCONNECTED:
555
+ self.settings.child('main_settings', 'leco', 'leco_connected').setValue(False)
556
+
557
+ elif status.command == 'Update_Status':
558
+ self.thread_status(status)
559
+
560
+ elif status.command == 'set_info':
561
+ """ The Director sent a parameter to be updated"""
562
+ path_in_settings = status.attribute.path
563
+ if 'move' in self.__class__.__name__.lower():
564
+ common_param = 'move_settings'
565
+ else:
566
+ common_param = 'detector_settings'
567
+ if common_param in path_in_settings:
568
+ param = self.settings.child(*path_in_settings)
569
+ elif 'settings_client' in path_in_settings:
570
+ param = self.settings.child(common_param, *path_in_settings[1:])
571
+ else:
572
+ param = self.settings.child(common_param, *path_in_settings)
573
+
574
+ param.setValue(status.attribute.parameter.value())
575
+
576
+ elif status.command == LECOCommands.GET_SETTINGS:
577
+ """ The Director requested the content of the actuator settings"""
578
+ if 'move' in self.__class__.__name__.lower():
579
+ common_param = 'move_settings'
580
+ else:
581
+ common_param = 'detector_settings'
582
+ self._command_tcpip.emit(
583
+ ThreadCommand(LECOCommands.SET_DIRECTOR_SETTINGS,
584
+ ioxml.parameter_to_xml_string(
585
+ self.settings.child(common_param))))
586
+
587
+ else:
588
+ # not handled
589
+ return status
590
+
591
+