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,1141 @@
1
+ import numbers
2
+
3
+ from abc import abstractmethod
4
+ from time import perf_counter
5
+ from typing import Union, List, Dict, TYPE_CHECKING, Optional, TypeVar
6
+ from numbers import Number
7
+ from collections.abc import Iterable
8
+
9
+ from easydict import EasyDict as edict
10
+ import numpy as np
11
+ from qtpy import QtWidgets
12
+ from qtpy.QtCore import QObject, Slot, Signal, QTimer
13
+
14
+ from pymodaq_utils.utils import ThreadCommand, find_keys_from_val
15
+ from pymodaq_utils import config as configmod
16
+ from pymodaq_utils.warnings import deprecation_msg
17
+ from pymodaq_utils.enums import BaseEnum, enum_checker
18
+ from pymodaq_utils.logger import set_logger, get_module_name
19
+
20
+ import pymodaq_gui.parameter.utils as putils
21
+ from pymodaq_gui.parameter import Parameter
22
+ from pymodaq_gui.parameter import ioxml
23
+ from pymodaq_gui.utils.utils import mkQApp
24
+
25
+ from pymodaq.utils.tcp_ip.tcp_server_client import TCPServer, tcp_parameters
26
+
27
+ from pymodaq_data.data import DataUnitError, Q_
28
+
29
+ from pymodaq.utils.messenger import deprecation_msg
30
+ from pymodaq.utils.data import DataActuator
31
+ from pymodaq_utils.enums import BaseEnum, enum_checker
32
+
33
+ from pymodaq_utils.serialize.mysocket import Socket
34
+ from pymodaq_utils.serialize.serializer_legacy import DeSerializer, Serializer
35
+ from pymodaq import Unit
36
+ from pint.errors import OffsetUnitCalculusError
37
+
38
+ from pymodaq.control_modules.thread_commands import ThreadStatus, ThreadStatusMove
39
+ from pymodaq.utils.config import Config as ControlModulesConfig
40
+ from pymodaq.control_modules.daq_move_ui.factory import ActuatorUIFactory
41
+
42
+ if TYPE_CHECKING:
43
+ from pymodaq.control_modules.daq_move import DAQ_Move_Hardware
44
+
45
+ logger = set_logger(get_module_name(__file__))
46
+
47
+ config_utils = configmod.Config()
48
+ config = ControlModulesConfig()
49
+
50
+ HardwareController = TypeVar("HardwareController")
51
+
52
+
53
+ def check_units(dwa: DataActuator, units: str):
54
+ """ Check if dwa units is compatible with the units argument
55
+
56
+ If it is incompatible and has dimensionless units, brute force change the dwa units to units,
57
+ otherwise raise a DataUnitError
58
+
59
+ Parameters
60
+ ----------
61
+ dwa: DataActuator
62
+ units: str
63
+
64
+ Returns
65
+ -------
66
+ DataActuator
67
+ """
68
+ if Unit(dwa.units).is_compatible_with(units):
69
+ return dwa
70
+ elif Unit(dwa.units).dimensionless: # dimensionless
71
+ dwa.force_units(units)
72
+ return dwa
73
+ else:
74
+ raise DataUnitError(f'Units incompatibility between {dwa} and "{units}" units')
75
+
76
+
77
+ class DataActuatorType(BaseEnum):
78
+ """Enum for new or old style holding the value of the actuator"""
79
+ float = 0
80
+ DataActuator = 1
81
+
82
+
83
+ def comon_parameters(epsilon=config('actuator', 'epsilon_default'),
84
+ epsilons=None):
85
+ if epsilons is not None:
86
+ epsilon = epsilons
87
+ if isinstance(epsilon, list):
88
+ epsilon = epsilon[0]
89
+ elif isinstance(epsilon, dict):
90
+ epsilon = epsilon[list(epsilon.keys())[0]]
91
+
92
+ return [{'title': 'Units:', 'name': 'units', 'type': 'str', 'value': '', 'readonly': True},
93
+ {'title': 'Epsilon:', 'name': 'epsilon', 'type': 'float',
94
+ 'value': epsilon,
95
+ 'tip': 'Differential Value at which the controller considers it reached the target position'},
96
+ {'title': 'Timeout (s):', 'name': 'timeout', 'type': 'int',
97
+ 'value': config('actuator', 'polling_timeout_s')},
98
+ {'title': 'Bounds:', 'name': 'bounds', 'type': 'group', 'children': [
99
+ {'title': 'Set Bounds:', 'name': 'is_bounds', 'type': 'bool', 'value': False},
100
+ {'title': 'Min:', 'name': 'min_bound', 'type': 'float', 'value': 0, 'default': 0},
101
+ {'title': 'Max:', 'name': 'max_bound', 'type': 'float', 'value': 1, 'default': 1}, ]},
102
+ {'title': 'Scaling:', 'name': 'scaling', 'type': 'group', 'children': [
103
+ {'title': 'Use scaling:', 'name': 'use_scaling', 'type': 'bool', 'value': False,
104
+ 'default': False},
105
+ {'title': 'Scaling factor:', 'name': 'scaling', 'type': 'float', 'value': 1., 'default': 1.},
106
+ {'title': 'Offset factor:', 'name': 'offset', 'type': 'float', 'value': 0., 'default': 0.}]}]
107
+
108
+
109
+ MOVE_COMMANDS = ['abs', 'rel', 'home']
110
+
111
+
112
+ class MoveCommand:
113
+ """Utility class to contain a given move type and value
114
+
115
+ Attributes
116
+ ----------
117
+ move_type: str
118
+ either:
119
+
120
+ * 'abs': performs an absolute action
121
+ * 'rel': performs a relative action
122
+ * 'home': find the actuator's home
123
+ value: float
124
+ the value the move should reach
125
+
126
+ """
127
+
128
+ def __init__(self, move_type, value=0):
129
+ if move_type not in MOVE_COMMANDS:
130
+ raise ValueError(f'The allowed move types fro an actuator are {MOVE_COMMANDS}')
131
+ self.move_type = move_type
132
+ self.value = value
133
+
134
+
135
+ def comon_parameters_fun(is_multiaxes=False, axes_names=None,
136
+ axis_names: Union[List, Dict] = [],
137
+ master=True,
138
+ epsilon: float = config('actuator', 'epsilon_default')):
139
+ """Function returning the common and mandatory parameters that should be on the actuator plugin level
140
+
141
+ Parameters
142
+ ----------
143
+ is_multiaxes: bool
144
+ If True, display the particular settings to define which axis the controller is driving
145
+ axes_names: deprecated, use axis_names
146
+ axis_names: list of str or dictionnary of string as key and integer as value
147
+ The string identifier of every axis the controller can drive
148
+ master: bool
149
+ If True consider this plugin has to init the controller, otherwise use an already initialized instance
150
+ epsilon: float
151
+ deprecated (< 5.0.0) no more used here
152
+
153
+ """
154
+ if axes_names is not None and len(axis_names) == 0:
155
+ if len(axes_names) == 0:
156
+ axes_names = ['']
157
+ axis_names = axes_names
158
+
159
+ is_multiaxes = len(axis_names) > 1 or is_multiaxes
160
+ if isinstance(axis_names, list):
161
+ if len(axis_names) > 0:
162
+ axis_name = axis_names[0]
163
+ else:
164
+ axis_names = ['']
165
+ axis_name = ''
166
+ elif isinstance(axis_names, dict):
167
+ axis_name = axis_names[list(axis_names.keys())[0]]
168
+ else:
169
+ raise ValueError('axis_names should be either a list of string or a dict with strings '
170
+ 'as keys')
171
+ params = [
172
+ {'title': 'MultiAxes:', 'name': 'multiaxes', 'type': 'group',
173
+ 'visible': True, 'children': [
174
+ {'title': 'Controller ID:', 'name': 'controller_ID', 'type': 'int', 'value': 0,
175
+ 'default': 0},
176
+ {'title': 'Status:', 'name': 'multi_status', 'type': 'list',
177
+ 'value': 'Master' if master else 'Slave', 'limits': ['Master', 'Slave']},
178
+ {'title': 'Axis:', 'name': 'axis', 'type': 'list', 'limits': axis_names.copy(),
179
+ 'value': axis_name},
180
+ ]},
181
+ ] + comon_parameters(epsilon)
182
+ return params
183
+
184
+
185
+ params = [
186
+ {'title': 'Main Settings:', 'name': 'main_settings', 'type': 'group', 'children': [
187
+ {'title': 'Actuator type:', 'name': 'move_type', 'type': 'str', 'value': '', 'readonly': True},
188
+ {'title': 'Actuator name:', 'name': 'module_name', 'type': 'str', 'value': '', 'readonly': True},
189
+ {'title': 'UI type:', 'name': 'ui_type', 'type': 'list',
190
+ 'value': config('actuator', 'ui') if config('actuator', 'ui') in ActuatorUIFactory.keys() else
191
+ ActuatorUIFactory.keys()[0],
192
+ 'limits': ActuatorUIFactory.keys()},
193
+ {'title': 'Plugin Config:', 'name': 'plugin_config', 'type': 'bool_push', 'label': 'Show Config', },
194
+
195
+ {'title': 'Refresh value (ms):', 'name': 'refresh_timeout', 'type': 'int',
196
+ 'value': config('actuator', 'refresh_timeout_ms')},
197
+ {'title': 'Continuous saving:', 'name': 'continuous_saving_opt', 'type': 'bool', 'default': False,
198
+ 'value': False},
199
+ {'title': 'TCP/IP options:', 'name': 'tcpip', 'type': 'group', 'visible': True, 'expanded': False,
200
+ 'children': [
201
+ {'title': 'Connect to server:', 'name': 'connect_server', 'type': 'bool_push', 'label': 'Connect',
202
+ 'value': False},
203
+ {'title': 'Connected?:', 'name': 'tcp_connected', 'type': 'led', 'value': False},
204
+ {'title': 'IP address:', 'name': 'ip_address', 'type': 'str',
205
+ 'value': config_utils('network', 'tcp-server', 'ip')},
206
+ {'title': 'Port:', 'name': 'port', 'type': 'int', 'value': config_utils('network', 'tcp-server', 'port')},
207
+ ]},
208
+ {'title': 'LECO options:', 'name': 'leco', 'type': 'group', 'visible': True, 'expanded': False,
209
+ 'children': [
210
+ {'title': 'Connect:', 'name': 'connect_leco_server', 'type': 'bool_push', 'label': 'Connect',
211
+ 'value': False},
212
+ {'title': 'Connected?:', 'name': 'leco_connected', 'type': 'led', 'value': False},
213
+ {'title': 'Name', 'name': 'leco_name', 'type': 'str', 'value': "", 'default': ""},
214
+ {'title': 'Host:', 'name': 'host', 'type': 'str', 'value': config_utils('network', "leco-server", "host"),
215
+ "default": "localhost"},
216
+ {'title': 'Port:', 'name': 'port', 'type': 'int', 'value': config_utils('network', 'leco-server', 'port')},
217
+ ]},
218
+ ]},
219
+ {'title': 'Actuator Settings:', 'name': 'move_settings', 'type': 'group'}
220
+ ]
221
+
222
+
223
+ def main(plugin_file, init=True, title='test'):
224
+ """
225
+ this method start a DAQ_Move object with this defined plugin as actuator
226
+ Returns
227
+ -------
228
+
229
+ """
230
+ import sys
231
+ from qtpy import QtWidgets
232
+ from pymodaq.control_modules.daq_move import DAQ_Move
233
+ from pathlib import Path
234
+
235
+ act = Path(plugin_file).stem.split('daq_move_')[1]
236
+
237
+ app = mkQApp("PyMoDAQ Viewer")
238
+
239
+ widget = QtWidgets.QWidget()
240
+ prog = DAQ_Move(widget, title=title, actuator=act)
241
+ widget.show()
242
+ prog.actuator = Path(plugin_file).stem[9:]
243
+ if init:
244
+ prog.init_hardware_ui()
245
+
246
+ sys.exit(app.exec_())
247
+
248
+
249
+ class DAQ_Move_base(QObject):
250
+ """ The base class to be inherited by all actuator modules
251
+
252
+ This base class implements all necessary parameters and methods for the plugin to communicate with its parent (the
253
+ DAQ_Move module)
254
+
255
+ Parameters
256
+ ----------
257
+ parent : DAQ_Move_Hardware
258
+ params_state : Parameter
259
+ pyqtgraph Parameter instance from which the module will get the initial settings (as defined in the preset)
260
+ Attributes
261
+ ----------
262
+ move_done_signal: Signal
263
+ signal represented by a float. Is emitted each time the hardware reached the target position within the epsilon
264
+ precision (see comon_parameters variable)
265
+ controller: object
266
+ the object representing the hardware in the plugin. Used to access hardware functionality
267
+ settings: Parameter
268
+ instance representing the hardware settings defined from the params attribute. Modifications on the GUI settings
269
+ will be transferred to this attribute. It stores at all times the current state of the hardware/plugin settings
270
+ params: List of dict used to create a Parameter object.
271
+ Its definition on the class level enable the automatic update of the GUI settings when changing plugins
272
+ (even in managers mode creation). To be populated on the plugin level as the base class does't represents a
273
+ real hardware
274
+ is_multiaxes: bool
275
+ class level attribute. Defines if the plugin controller controls multiple axes. If True, one has to define
276
+ a Master instance of this plugin and slave instances of this plugin (all sharing the same controller_ID
277
+ parameter)
278
+ current_value: DataActuator
279
+ stores the current position after each call to the get_actuator_value in the plugin
280
+ target_value: DataActuator
281
+ stores the target position the controller should reach within epsilon
282
+ """
283
+
284
+ move_done_signal = Signal(DataActuator)
285
+ is_multiaxes = False
286
+ stage_names = [] # deprecated
287
+
288
+ _axis_names: Union[list, Dict[str, int]] = None
289
+ _controller_units: Union[str, List[str], Dict[str, int]] = ''
290
+ _epsilons: Union[float, List[float], Dict[str, float]] = None
291
+ _epsilon = 1.0 # deprecated
292
+
293
+ params = []
294
+
295
+ data_actuator_type = DataActuatorType.float
296
+ data_shape = (1,) # expected shape of the underlying actuator's value (in general a float so shape = (1, ))
297
+
298
+ def __init__(self, parent: Optional['DAQ_Move_Hardware'] = None,
299
+ params_state: Optional[dict] = None,
300
+ **kwargs):
301
+ QObject.__init__(self) # to make sure this is the parent class
302
+ self.move_is_done = False
303
+ self.parent = parent
304
+ self.stage = None
305
+ self.controller = None
306
+ self.status = edict(info="", controller=None, stage=None, initialized=False)
307
+
308
+ self._ispolling = True
309
+ self.parent_parameters_path = [] # this is to be added in the send_param_status to take into account when the
310
+ # current class instance parameter list is a child of some other class
311
+ self.settings = Parameter.create(name='Settings', type='group', children=self.params)
312
+ if params_state is not None:
313
+ if isinstance(params_state, dict):
314
+ self.settings.restoreState(params_state)
315
+ elif isinstance(params_state, Parameter):
316
+ self.settings.restoreState(params_state.saveState())
317
+
318
+ self.settings.sigTreeStateChanged.connect(self.send_param_status)
319
+
320
+ if parent is not None:
321
+ self._title = parent.title
322
+ else:
323
+ self._title = "myactuator"
324
+
325
+ self._axis_units: Union[Dict[str, str], List[str]] = None
326
+ if isinstance(self._controller_units, str):
327
+ self.axis_units = self._controller_units
328
+ else:
329
+ self.axis_units = self._controller_units.copy()
330
+ if self._epsilons is None:
331
+ self._epsilons = self._epsilon
332
+ self.epsilons = self._epsilons
333
+ self.axis_name = self.axis_name # to trigger some actions on units and epsilons
334
+
335
+ self._current_value = DataActuator(self._title,
336
+ data=[np.zeros(self.data_shape, dtype=float)],
337
+ units=self.axis_unit)
338
+ self._target_value = DataActuator(self._title,
339
+ data=[np.zeros(self.data_shape, dtype=float)],
340
+ units=self.axis_unit)
341
+
342
+ self.poll_timer = QTimer()
343
+ self.poll_timer.setInterval(config('actuator', 'polling_interval_ms'))
344
+ self._poll_timeout = config('actuator', 'polling_timeout_s')
345
+ self.poll_timer.timeout.connect(self.check_target_reached)
346
+
347
+ self.ini_attributes()
348
+
349
+ @property
350
+ def axis_unit(self) -> str:
351
+ """ Get/set the unit of the currently chosen axis
352
+
353
+ Will update the printed controller unit in the UI
354
+
355
+ New in 4.4.0
356
+ """
357
+ return self.axis_units[self.axis_index_key]
358
+
359
+ @axis_unit.setter
360
+ def axis_unit(self, unit: str):
361
+ self.axis_units[self.axis_index_key] = unit
362
+ self.settings.child('units').setValue(unit)
363
+ self.emit_status(ThreadCommand(ThreadStatusMove.UNITS, unit))
364
+
365
+ @property
366
+ def axis_units(self) -> Union[List[str], Dict[str, str]]:
367
+ """ Get/Set the units for each axis of the controller
368
+
369
+ New in 4.4.0
370
+ """
371
+ return self._axis_units
372
+
373
+ @axis_units.setter
374
+ def axis_units(self, units: Union[str, List[str], Dict[str, str]]):
375
+ if isinstance(units, str):
376
+ if isinstance(self.axis_names, list):
377
+ units_tmp = [units for _ in range(len(self.axis_names))]
378
+ else:
379
+ units_tmp = {}
380
+ for key in self.axis_names:
381
+ units_tmp[key] = units
382
+ else:
383
+ if not isinstance(units, type(self.axis_names)):
384
+ raise TypeError('units should be defined just like axis_names: a str, list of string or'
385
+ 'dict of string')
386
+ if len(units) != len(self.axis_names):
387
+ raise ValueError('Units should be defined either as a single str or a list/dict with'
388
+ 'a str defined for each axis')
389
+ units_tmp = units
390
+ self._axis_units = units_tmp
391
+
392
+ @property
393
+ def epsilon(self) -> float:
394
+ """ Get/Set the epsilon of the currently chosen axis
395
+
396
+ New in 4.4.0
397
+ """
398
+ return self.epsilons[self.axis_index_key]
399
+
400
+ @epsilon.setter
401
+ def epsilon(self, eps: float):
402
+ self.epsilons[self.axis_index_key] = eps
403
+
404
+ @property
405
+ def epsilons(self) -> Union[List[float], Dict[str, float]]:
406
+ """ Get/Set the epsilon for each axis of the controller
407
+
408
+ New in 4.4.0
409
+ """
410
+ return self._epsilons
411
+
412
+ @epsilons.setter
413
+ def epsilons(self, epsilons: Union[float, List[float], Dict[str, float]]):
414
+ if isinstance(epsilons, numbers.Number):
415
+ if isinstance(self.axis_names, list):
416
+ epsilons_tmp = [epsilons for _ in range(len(self.axis_names))]
417
+ else:
418
+ epsilons_tmp = {}
419
+ for key in self.axis_names:
420
+ epsilons_tmp[key] = epsilons
421
+ else:
422
+ if not isinstance(epsilons, type(self.axis_names)):
423
+ raise TypeError('units should be defined just like axis_names: a float, list of '
424
+ 'float or dict of float')
425
+ if len(epsilons) != len(self.axis_names):
426
+ raise ValueError('epsilons should be defined either as a single float or a '
427
+ 'list/dict with'
428
+ 'a float defined for each axis')
429
+ epsilons_tmp = epsilons
430
+ self._epsilons = epsilons_tmp
431
+
432
+ @property
433
+ def controller_units(self):
434
+ """ Get/Set the units of the currently chosen axis of the controller
435
+
436
+ Deprecated with pymodaq >= 4.4.0
437
+
438
+ The property controller_units is deprecated please use the axis_unit property
439
+ """
440
+ deprecation_msg('The property controller_units is deprecated please use the'
441
+ 'axis_unit property.')
442
+ return self.axis_unit
443
+
444
+ @controller_units.setter
445
+ def controller_units(self, units: str = ''):
446
+ deprecation_msg('The property controller_units is deprecated please use the'
447
+ 'axis_unit property.')
448
+ self._axis_units[self.axis_index_key] = units
449
+
450
+ @property
451
+ def axis_name(self) -> Union[str]:
452
+ """Get/Set the current axis using its string identifier"""
453
+ limits = self.settings.child('multiaxes', 'axis').opts['limits']
454
+ if isinstance(limits, list):
455
+ return self.settings['multiaxes', 'axis']
456
+ elif isinstance(limits, dict):
457
+ return find_keys_from_val(limits, val=self.settings['multiaxes', 'axis'])[0]
458
+ else:
459
+ return ''
460
+
461
+ @axis_name.setter
462
+ def axis_name(self, name: str):
463
+ limits = self.settings.child('multiaxes', 'axis').opts['limits']
464
+ if name in limits:
465
+ if isinstance(limits, list):
466
+ self.settings.child('multiaxes', 'axis').setValue(name)
467
+ elif isinstance(limits, dict):
468
+ self.settings.child('multiaxes', 'axis').setValue(limits[name])
469
+ QtWidgets.QApplication.processEvents()
470
+ self.axis_unit = self.axis_unit
471
+ self.settings.child('epsilon').setValue(self.epsilon)
472
+ if self.controller is not None:
473
+ self._current_value = self.get_actuator_value()
474
+
475
+ @property
476
+ def axis_names(self) -> Union[List, Dict]:
477
+ """ Get/Set the names of all axes controlled by this instrument plugin
478
+
479
+ Returns
480
+ -------
481
+ List of string or dictionary mapping names to integers
482
+ """
483
+ return self.settings.child('multiaxes', 'axis').opts['limits']
484
+
485
+ @axis_names.setter
486
+ def axis_names(self, names: Union[List, Dict]):
487
+ self.settings.child('multiaxes', 'axis').setLimits(names)
488
+ QtWidgets.QApplication.processEvents()
489
+
490
+ @property
491
+ def axis_value(self) -> int:
492
+ """Get the current value selected from the current axis
493
+
494
+ In case axis_names is a list, return the element of the list: self.axis_name
495
+ In case axis_names is a dict, return the value of the dict self.axis_names[self.axis_name]
496
+ """
497
+ if isinstance(self.axis_names, list):
498
+ return self.axis_name
499
+ else:
500
+ return self.axis_names[self.axis_name]
501
+
502
+ @property
503
+ def axis_index_key(self) -> Union[int, str]:
504
+ """ Get the current index or key correspondingto the current axis
505
+
506
+ In case axis_names is a list, return the index wihtin the list
507
+ In case axis_names is a dict, return the key of the dict self.axis_name
508
+
509
+ """
510
+ if isinstance(self.axis_names, list):
511
+ return self.axis_names.index(self.axis_name)
512
+ else:
513
+ return self.axis_name
514
+
515
+ def ini_attributes(self):
516
+ """ To be subclassed, in order to init specific attributes needed by the real implementation"""
517
+ self.controller = None
518
+
519
+ def ini_stage_init(
520
+ self,
521
+ old_controller: Optional[HardwareController] = None,
522
+ new_controller: Optional[HardwareController] = None,
523
+ slave_controller: Optional[HardwareController] = None,
524
+ ) -> Optional[HardwareController]:
525
+ """Manage the Master/Slave controller issue
526
+
527
+ First initialize the status dictionary
528
+ Then check whether this stage is controlled by a multiaxe controller (to be defined for each plugin)
529
+ if it is a multiaxes controller then:
530
+ * if it is Master: init the controller here
531
+ * if it is Slave: use an already initialized controller (defined in the preset of the dashboard)
532
+
533
+ Parameters
534
+ ----------
535
+ old_controller: object
536
+ The particular object that allow the communication with the hardware, in general a python wrapper around the
537
+ hardware library. In case of Slave this one comes from a previously initialized plugin
538
+ new_controller: object
539
+ The particular object that allow the communication with the hardware, in general a python wrapper around the
540
+ hardware library. In case of Master it is the new instance of your plugin controller
541
+ """
542
+ if old_controller is None and slave_controller is not None:
543
+ old_controller = slave_controller
544
+
545
+ self.status.update(edict(info="", controller=None, initialized=False))
546
+ if not self.is_master:
547
+ if old_controller is None:
548
+ raise Exception('no controller has been defined externally while this axe '
549
+ 'is a slave one')
550
+ else:
551
+ controller = old_controller
552
+ else: # Master stage
553
+ controller = new_controller
554
+ self.controller = controller
555
+ return controller
556
+
557
+ @property
558
+ def current_value(self):
559
+ if self.data_actuator_type == self.data_actuator_type.float:
560
+ return self._current_value.value()
561
+ else:
562
+ return self._current_value
563
+
564
+ @current_value.setter
565
+ def current_value(self, value: Union[float, np.ndarray, DataActuator]):
566
+ if isinstance(value, numbers.Number) or isinstance(value, np.ndarray):
567
+ self._current_value = DataActuator(self._title, data=value,
568
+ units=self.axis_unit)
569
+ else:
570
+ if (not Unit(self.axis_unit).is_compatible_with(
571
+ Unit(value.units)) and
572
+ value.units == ''):
573
+ value.force_units(self.axis_unit)
574
+ self._current_value = value
575
+
576
+ @property
577
+ def target_value(self):
578
+ if self.data_actuator_type.name == self.data_actuator_type.float:
579
+ return self._target_value.value()
580
+ else:
581
+ return self._target_value
582
+
583
+ @target_value.setter
584
+ def target_value(self, value: Union[numbers.Number, DataActuator]):
585
+ if isinstance(value, numbers.Number):
586
+ self._target_value = DataActuator(self._title, data=value,
587
+ units=self.axis_unit)
588
+ else:
589
+ if (not Unit(self.axis_unit).is_compatible_with(
590
+ Unit(value.units)) and
591
+ value.units == ''):
592
+ value.force_units(self.axis_unit)
593
+ self._target_value = value
594
+
595
+ @property
596
+ def current_position(self):
597
+ deprecation_msg('current_position attribute should not be used, use current_value')
598
+ return self.current_value
599
+
600
+ @current_position.setter
601
+ def current_position(self, value):
602
+ self.current_value = value
603
+
604
+ @property
605
+ def target_position(self):
606
+ deprecation_msg('target_position attribute should not be used, use target_value')
607
+ return self.target_value
608
+
609
+ @target_position.setter
610
+ def target_position(self, value):
611
+ self.target_value = value
612
+
613
+ @property
614
+ def is_master(self) -> bool:
615
+ """ Get the controller master/slave status
616
+
617
+ new in version 4.3.0
618
+ """
619
+ return self.settings['multiaxes', 'multi_status'] == 'Master'
620
+
621
+ @property
622
+ def ispolling(self):
623
+ """ Get/Set the polling status"""
624
+ return self._ispolling
625
+
626
+ @ispolling.setter
627
+ def ispolling(self, polling=True):
628
+ self._ispolling = polling
629
+
630
+ def check_bound(self, position: DataActuator) -> DataActuator:
631
+ """ Check if the current position is within the software bounds
632
+
633
+ Return the new position eventually coerced within the bounds
634
+ """
635
+ if self.settings['bounds', 'is_bounds']:
636
+ if self.data_actuator_type == DataActuatorType.DataActuator:
637
+ for data_array in position:
638
+ if np.any(data_array > self.settings['bounds', 'max_bound']) or \
639
+ np.any(data_array < self.settings['bounds', 'min_bound']):
640
+ self.emit_status(ThreadCommand('outofbounds'))
641
+ data_array[data_array > self.settings['bounds', 'max_bound']] = self.settings['bounds', 'max_bound']
642
+ data_array[data_array < self.settings['bounds', 'min_bound']] = self.settings['bounds', 'min_bound']
643
+
644
+ else:
645
+ if position > self.settings['bounds', 'max_bound']:
646
+ self.emit_status(ThreadCommand('outofbounds'))
647
+ position = self.settings['bounds', 'max_bound']
648
+ elif position < self.settings['bounds', 'min_bound']:
649
+ self.emit_status(ThreadCommand('outofbounds'))
650
+ position = self.settings['bounds', 'min_bound']
651
+ return position
652
+
653
+ @abstractmethod
654
+ def get_actuator_value(self):
655
+ if hasattr(self, 'check_position'):
656
+ deprecation_msg('check_position method in plugins is deprecated, use get_actuator_value',3)
657
+ return self.check_position() # type: ignore
658
+ else:
659
+ raise NotImplementedError
660
+
661
+ @abstractmethod
662
+ def close(self) -> None:
663
+ raise NotImplementedError
664
+
665
+ def move_abs(self, value: Union[float, DataActuator]):
666
+ if hasattr(self, 'move_Abs'):
667
+ deprecation_msg('move_Abs method in plugins is deprecated, use move_abs', 3)
668
+ self.move_Abs(value) # type: ignore
669
+ else:
670
+ raise NotImplementedError
671
+
672
+ def move_rel(self, value: Union[float, DataActuator]):
673
+ if hasattr(self, 'move_Rel'):
674
+ deprecation_msg('move_Rel method in plugins is deprecated, use move_rel', 3)
675
+ self.move_Rel(value) # type: ignore
676
+ else:
677
+ raise NotImplementedError
678
+
679
+ def move_home(self, value: Union[float, DataActuator]):
680
+ if hasattr(self, 'move_Home'):
681
+ deprecation_msg('move_Home method in plugins is deprecated, use move_home', 3)
682
+ self.move_Home() # type: ignore
683
+ else:
684
+ raise NotImplementedError
685
+
686
+ def emit_status(self, status: ThreadCommand):
687
+ """ Emit the status_sig signal with the given status ThreadCommand back to the main GUI.
688
+ """
689
+ if self.parent is not None:
690
+ self.parent.status_sig.emit(status)
691
+ QtWidgets.QApplication.processEvents()
692
+ else:
693
+ print(status)
694
+
695
+ def emit_value(self, pos: DataActuator):
696
+ """Convenience method to emit the current actuator value back to the UI"""
697
+
698
+ self.emit_status(ThreadCommand(ThreadStatusMove.GET_ACTUATOR_VALUE, pos))
699
+
700
+ def commit_settings(self, param: Parameter):
701
+ """
702
+ to subclass to transfer parameters to hardware
703
+ """
704
+
705
+ def commit_common_settings(self, param):
706
+ pass
707
+
708
+ def move_done(self, position: Optional[
709
+ DataActuator] = None): # the position argument is just there to match some signature of child classes
710
+ """
711
+ | Emit a move done signal transmitting the float position to hardware.
712
+ | The position argument is just there to match some signature of child classes.
713
+
714
+ =============== ========== =============================================================================
715
+ **Arguments** **Type** **Description**
716
+ *position* float The position argument is just there to match some signature of child classes
717
+ =============== ========== =============================================================================
718
+
719
+ """
720
+ if position is None:
721
+ if self.data_actuator_type.name == 'float':
722
+ position = DataActuator(self._title, data=self.get_actuator_value(),
723
+ units=self.axis_unit)
724
+ else:
725
+ position = self.get_actuator_value()
726
+ if position.name != self._title: # make sure the emitted DataActuator has the name of the real implementation
727
+ #of the plugin
728
+ position = DataActuator(self._title, data=position.value(self.axis_unit),
729
+ units=self.axis_unit)
730
+ self.move_done_signal.emit(position)
731
+ self.move_is_done = True
732
+
733
+ def poll_moving(self):
734
+ """ Poll the current moving. In case of timeout emit the raise timeout Thread command.
735
+
736
+ See Also
737
+ --------
738
+ DAQ_utils.ThreadCommand, move_done
739
+ """
740
+ if not ('TCPServer' in self.__class__.__name__ or
741
+ 'LECODirector' in self.__class__.__name__):
742
+ self.start_time = perf_counter()
743
+ if self.ispolling:
744
+ self.poll_timer.start()
745
+ else:
746
+ if self.data_actuator_type == DataActuatorType.float:
747
+ self._current_value = DataActuator(data=self.get_actuator_value(),
748
+ units=self.axis_unit)
749
+ else:
750
+ self._current_value = self.get_actuator_value()
751
+ if (not Unit(self.axis_unit).is_compatible_with(
752
+ Unit(self._current_value.units)) and
753
+ self._current_value.units == ''):
754
+ # this happens if the units have not been specified in
755
+ # the plugin
756
+ self._current_value.force_units(self.axis_unit)
757
+
758
+ logger.debug(f'Current position: {self._current_value}')
759
+ self.move_done(self._current_value)
760
+
761
+ def _condition_to_reach_target(self, check_absolute_difference=True,) -> bool:
762
+ """Implement the condition for exiting the polling mechanism and specifying that the
763
+ target value has been reached
764
+
765
+ Returns
766
+ -------
767
+ bool: if True, PyMoDAQ considers the target value has been reached
768
+ See Also
769
+ --------
770
+ absolute_difference_condition_to_reach_target
771
+ user_condition_to_reach_target
772
+ """
773
+ cond = True
774
+ if check_absolute_difference:
775
+ cond = self.absolute_difference_condition_to_reach_target()
776
+ return cond and self.user_condition_to_reach_target()
777
+
778
+ def absolute_difference_condition_to_reach_target(self) -> bool:
779
+ """ Implement the condition for exiting the polling mechanism and specifying that the
780
+ target value has been reached
781
+
782
+ Returns
783
+ -------
784
+ bool: if True, PyMoDAQ considers the target value has been reached at epsilon
785
+
786
+ See Also
787
+ --------
788
+ user_condition_to_reach_target
789
+ """
790
+ try:
791
+ epsilon_calculated = (
792
+ self._current_value - self._target_value).abs().value(self.axis_unit)
793
+ except DataUnitError as e:
794
+ epsilon_calculated = abs(self._current_value.value() - self._target_value.value())
795
+ logger.warning(f'Unit issue when calculating epsilon, units are not compatible between'
796
+ f'target and current values')
797
+ except OffsetUnitCalculusError as e:
798
+ if '°C' in self.axis_unit or 'celcius' in self.axis_unit.lower():
799
+ epsilon_calculated = (
800
+ self._current_value.to_base_units() -
801
+ self._target_value.to_base_units()).abs().value()
802
+ else:
803
+ raise e
804
+
805
+ return (epsilon_calculated < self.epsilon)
806
+
807
+ def user_condition_to_reach_target(self) -> bool:
808
+ """ Implement a user defined condition for exiting the polling mechanism and specifying
809
+ that the target value has been reached (on top of the existing epsilon mechanism)
810
+
811
+ Should be reimplemented in plugins to implement other conditions
812
+
813
+ Returns
814
+ -------
815
+ bool: if True, PyMoDAQ considers the target value has been reached
816
+ """
817
+ return True
818
+
819
+ def check_target_reached(self):
820
+ logger.debug(f"epsilon value is {self.epsilon}")
821
+ logger.debug(f"current_value value is {self._current_value}")
822
+ logger.debug(f"target_value value is {self._target_value}")
823
+
824
+ if not self._condition_to_reach_target():
825
+
826
+ logger.debug(f'Check move_is_done: {self.move_is_done}')
827
+ if self.move_is_done:
828
+ self.emit_status(ThreadCommand(ThreadStatus.UPDATE_STATUS, 'Move has been stopped', ))
829
+ logger.info('Move has been stopped')
830
+ self.current_value = self.get_actuator_value()
831
+ self.emit_value(self._current_value)
832
+ logger.debug(f'Current value: {self._current_value}')
833
+
834
+ if perf_counter() - self.start_time >= self.settings['timeout']:
835
+ self.poll_timer.stop()
836
+ self.emit_status(ThreadCommand(ThreadStatus.RAISE_TIMEOUT, ))
837
+ logger.info('Timeout activated')
838
+ else:
839
+ self.poll_timer.stop()
840
+ self.current_value = self.get_actuator_value()
841
+ logger.debug(f'Current value: {self._current_value}')
842
+ self.move_done(self._current_value)
843
+
844
+ def send_param_status(self, param, changes):
845
+ """ Send changes value updates to the gui to update consequently the User Interface
846
+
847
+ The message passing is made via the ThreadCommand "update_settings".
848
+ """
849
+
850
+ for param, change, data in changes:
851
+ path = self.settings.childPath(param)
852
+ if change == 'childAdded':
853
+ self.emit_status(ThreadCommand(ThreadStatus.UPDATE_SETTINGS,
854
+ [self.parent_parameters_path + path, [data[0].saveState(), data[1]],
855
+ change])) # send parameters values/limits back to the GUI. Send kind of a copy back the GUI otherwise the child reference will be the same in both th eUI and the plugin so one of them will be removed
856
+ elif change == 'value' or change == 'limits' or change == 'options':
857
+ self.emit_status(ThreadCommand(ThreadStatus.UPDATE_SETTINGS,
858
+ [self.parent_parameters_path + path, data,
859
+ change])) # send parameters values/limits back to the GUI
860
+ elif change == 'parent':
861
+ pass
862
+ elif change == 'limits':
863
+ self.emit_status(ThreadCommand(ThreadStatus.UPDATE_SETTINGS,
864
+ [self.parent_parameters_path + path, data,
865
+ change]))
866
+
867
+ def get_position_with_scaling(self, pos: DataActuator) -> DataActuator:
868
+ """ Get the current position from the hardware with scaling conversion.
869
+ """
870
+ if self.settings['scaling', 'use_scaling']:
871
+ if self.data_actuator_type == DataActuatorType.float:
872
+ pos = (pos - self.settings['scaling', 'offset']) * self.settings['scaling', 'scaling']
873
+ else:
874
+ pos = (pos - Q_(self.settings['scaling', 'offset'],
875
+ self.axis_unit)) * self.settings['scaling', 'scaling']
876
+ return pos
877
+
878
+ def set_position_with_scaling(self, pos: DataActuator) -> DataActuator:
879
+ """ Set the current position from the parameter and hardware with scaling conversion.
880
+ """
881
+ if self.settings['scaling', 'use_scaling']:
882
+ if self.data_actuator_type == DataActuatorType.DataActuator:
883
+ pos = pos / self.settings['scaling', 'scaling'] + Q_(self.settings['scaling', 'offset'], self.axis_unit)
884
+ else:
885
+ pos = pos / self.settings['scaling', 'scaling'] + self.settings['scaling', 'offset']
886
+ return pos
887
+
888
+ def set_position_relative_with_scaling(self, pos: DataActuator) -> DataActuator:
889
+ """ Set the scaled positions in case of relative moves
890
+ """
891
+ if self.settings['scaling', 'use_scaling']:
892
+ pos = pos / self.settings['scaling', 'scaling']
893
+ return pos
894
+
895
+ @Slot(edict)
896
+ def update_settings(self, settings_parameter_dict): # settings_parameter_dict=edict(path=path,param=param)
897
+ """ Receive the settings_parameter signal from the param_tree_changed method and make hardware updates of
898
+ modified values.
899
+ """
900
+ path = settings_parameter_dict['path']
901
+ param = settings_parameter_dict['param']
902
+ change = settings_parameter_dict['change']
903
+ apply_settings = True
904
+ try:
905
+ self.settings.sigTreeStateChanged.disconnect(self.send_param_status)
906
+ except Exception:
907
+ pass
908
+ if change == 'value':
909
+ self.settings.child(*path[1:]).setValue(param.value()) # blocks signal back to main UI
910
+ elif change == 'childAdded':
911
+ try:
912
+ child = Parameter.create(name='tmp')
913
+ child.restoreState(param)
914
+ param = child
915
+ self.settings.child(*path[1:]).addChild(child) # blocks signal back to main UI
916
+ except ValueError:
917
+ apply_settings = False
918
+ elif change == 'parent':
919
+ try:
920
+ children = putils.get_param_from_name(self.settings, param.name())
921
+
922
+ if children is not None:
923
+ path = putils.get_param_path(children)
924
+ self.settings.child(*path[1:-1]).removeChild(children)
925
+ except IndexError:
926
+ logger.debug(f'Could not remove children from {param.name()}')
927
+ self.settings.sigTreeStateChanged.connect(self.send_param_status)
928
+ if apply_settings:
929
+ self.commit_common_settings(param)
930
+ self.commit_settings(param)
931
+
932
+ if param.name() == 'axis':
933
+ self.axis_name = param.value()
934
+ elif param.name() == 'epsilon':
935
+ self.epsilon = param.value()
936
+
937
+ # abstract methods to be overwritten by the concrete implementations
938
+ @abstractmethod
939
+ def ini_stage(self, controller: Optional[HardwareController] = None) -> tuple[str, bool]:
940
+ """Actuator communication initialization
941
+
942
+ Parameters
943
+ ----------
944
+ controller: (object)
945
+ custom object of a PyMoDAQ plugin (Slave case). None if only one actuator by controller (Master case)
946
+
947
+ Returns
948
+ -------
949
+ info: str
950
+ initialized: bool
951
+ False if initialization failed otherwise True
952
+ """
953
+ pass
954
+
955
+ @abstractmethod
956
+ def stop_motion(self, value: DataActuator) -> None:
957
+ """Stop the actuator and emit move_done signal."""
958
+ pass
959
+
960
+
961
+ class DAQ_Move_TCP_server(DAQ_Move_base, TCPServer):
962
+ """
963
+ ================= ==============================
964
+ **Attributes** **Type**
965
+ *command_server* instance of Signal
966
+ *x_axis* 1D numpy array
967
+ *y_axis* 1D numpy array
968
+ *data* double precision float array
969
+ ================= ==============================
970
+
971
+ See Also
972
+ --------
973
+ utility_classes.DAQ_TCP_server
974
+ """
975
+ params_client = [] # parameters of a client grabber
976
+ command_server = Signal(list)
977
+ data_actuator_type = DataActuatorType.DataActuator
978
+
979
+ message_list = ["Quit", "Status", "Done", "Server Closed", "Info", "Infos", "Info_xml", "move_abs",
980
+ 'move_home', 'move_rel', 'get_actuator_value', 'stop_motion', 'position_is', 'move_done']
981
+ socket_types = ["ACTUATOR"]
982
+ params = comon_parameters_fun() + tcp_parameters
983
+
984
+ def __init__(self, parent=None, params_state=None):
985
+ """
986
+
987
+ Parameters
988
+ ----------
989
+ parent
990
+ params_state
991
+ """
992
+ self.client_type = "ACTUATOR"
993
+ DAQ_Move_base.__init__(self, parent, params_state) # initialize base class with commom attribute and methods
994
+ self.settings.child('bounds').hide()
995
+ self.settings.child('scaling').hide()
996
+ self.settings.child('epsilon').setValue(1)
997
+
998
+ TCPServer.__init__(self, self.client_type)
999
+
1000
+ def command_to_from_client(self, command):
1001
+ sock: Socket = self.find_socket_within_connected_clients(self.client_type)
1002
+ if sock is not None: # if client 'ACTUATOR' is connected then send it the command
1003
+
1004
+ if command == 'position_is':
1005
+ pos = DeSerializer(sock).dwa_deserialization()
1006
+
1007
+ pos = self.get_position_with_scaling(pos)
1008
+ self._current_value = pos
1009
+ self.emit_status(ThreadCommand(ThreadStatusMove.GET_ACTUATOR_VALUE, pos))
1010
+
1011
+ elif command == 'move_done':
1012
+ pos = DeSerializer(sock).dwa_deserialization()
1013
+ pos = self.get_position_with_scaling(pos)
1014
+ self._current_value = pos
1015
+ self.emit_status(ThreadCommand(ThreadStatusMove.MOVE_DONE, pos))
1016
+ else:
1017
+ self.send_command(sock, command)
1018
+
1019
+ def commit_settings(self, param):
1020
+
1021
+ if param.name() in putils.iter_children(self.settings.child('settings_client'), []):
1022
+ actuator_socket: Socket = \
1023
+ [client['socket'] for client in self.connected_clients if client['type'] == 'ACTUATOR'][0]
1024
+ actuator_socket.check_sended_with_serializer('set_info')
1025
+ path = putils.get_param_path(param)[2:]
1026
+ # get the path of this param as a list starting at parent 'infos'
1027
+
1028
+ actuator_socket.check_sended_with_serializer(path)
1029
+
1030
+ # send value
1031
+ data = ioxml.parameter_to_xml_string(param)
1032
+ actuator_socket.check_sended_with_serializer(data)
1033
+
1034
+ def ini_stage(self, controller=None):
1035
+ """
1036
+ | Initialisation procedure of the detector updating the status dictionary.
1037
+ |
1038
+ | Init axes from image , here returns only None values (to tricky to di it with the server and not really necessary for images anyway)
1039
+
1040
+ See Also
1041
+ --------
1042
+ utility_classes.DAQ_TCP_server.init_server, get_xaxis, get_yaxis
1043
+ """
1044
+ self.settings.child('infos').addChildren(self.params_client)
1045
+
1046
+ self.init_server()
1047
+ self.controller = self.serversocket
1048
+ self.settings.child('units').hide()
1049
+ self.settings.child('epsilon').hide()
1050
+
1051
+ info = 'TCP Server actuator'
1052
+ initialized = True
1053
+ return info, initialized
1054
+
1055
+ def read_infos(self, sock: Socket = None, infos=''):
1056
+ """Reimplemented to get the units"""
1057
+ super().read_infos(sock, infos)
1058
+
1059
+ self.axis_unit = self.settings['settings_client', 'units']
1060
+
1061
+ def close(self):
1062
+ """
1063
+ Should be used to uninitialize hardware.
1064
+
1065
+ See Also
1066
+ --------
1067
+ utility_classes.DAQ_TCP_server.close_server
1068
+ """
1069
+ self.listening = False
1070
+ self.close_server()
1071
+
1072
+ def move_abs(self, position: DataActuator):
1073
+ """
1074
+
1075
+ """
1076
+ position = self.check_bound(position)
1077
+ self.target_value = position
1078
+
1079
+ position = self.set_position_with_scaling(position)
1080
+
1081
+ sock = self.find_socket_within_connected_clients(self.client_type)
1082
+ if sock is not None: # if client self.client_type is connected then send it the command
1083
+ sock.check_sended_with_serializer('move_abs')
1084
+ sock.check_sended_with_serializer(position)
1085
+
1086
+ def move_rel(self, position: DataActuator):
1087
+ position = self.check_bound(self.current_value + position) - self.current_value
1088
+ self.target_value = position + self.current_value
1089
+
1090
+ position = self.set_position_relative_with_scaling(position)
1091
+ sock = self.find_socket_within_connected_clients(self.client_type)
1092
+ if sock is not None: # if client self.client_type is connected then send it the command
1093
+ sock.check_sended_with_serializer('move_rel')
1094
+ sock.check_sended_with_serializer(position)
1095
+
1096
+ def move_home(self):
1097
+ """
1098
+ Make the absolute move to original position (0).
1099
+
1100
+ See Also
1101
+ --------
1102
+ move_Abs
1103
+ """
1104
+ sock = self.find_socket_within_connected_clients(self.client_type)
1105
+ if sock is not None: # if client self.client_type is connected then send it the command
1106
+ sock.check_sended_with_serializer('move_home')
1107
+
1108
+ def get_actuator_value(self):
1109
+ """
1110
+ Get the current hardware position with scaling conversion given by get_position_with_scaling.
1111
+
1112
+ See Also
1113
+ --------
1114
+ daq_move_base.get_position_with_scaling, daq_utils.ThreadCommand
1115
+ """
1116
+ sock = self.find_socket_within_connected_clients(self.client_type)
1117
+ if sock is not None: # if client self.client_type is connected then send it the command
1118
+ self.send_command(sock, 'get_actuator_value')
1119
+
1120
+ return self._current_value
1121
+
1122
+ def stop_motion(self):
1123
+ """
1124
+ See Also
1125
+ --------
1126
+ daq_move_base.move_done
1127
+ """
1128
+ sock = self.find_socket_within_connected_clients(self.client_type)
1129
+ if sock is not None: # if client self.client_type is connected then send it the command
1130
+ self.send_command(sock, 'stop_motion')
1131
+
1132
+ def stop(self):
1133
+ """
1134
+ not implemented.
1135
+ """
1136
+ pass
1137
+ return ""
1138
+
1139
+
1140
+ if __name__ == '__main__':
1141
+ test = DAQ_Move_base()