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
@@ -1,6 +1,9 @@
1
+ import numbers
2
+
1
3
  from time import perf_counter
2
4
  from typing import Union, List, Dict, TYPE_CHECKING, Optional
3
5
  from numbers import Number
6
+ from collections.abc import Iterable
4
7
 
5
8
  from easydict import EasyDict as edict
6
9
  import numpy as np
@@ -16,11 +19,20 @@ from pymodaq_utils.logger import set_logger, get_module_name
16
19
  import pymodaq_gui.parameter.utils as putils
17
20
  from pymodaq_gui.parameter import Parameter
18
21
  from pymodaq_gui.parameter import ioxml
22
+ from pymodaq_gui.utils.utils import mkQApp
19
23
 
20
24
  from pymodaq.utils.tcp_ip.tcp_server_client import TCPServer, tcp_parameters
25
+
26
+ from pymodaq_data.data import DataUnitError, Q_
27
+
28
+ from pymodaq.utils.messenger import deprecation_msg
21
29
  from pymodaq.utils.data import DataActuator
22
- from pymodaq.utils.tcp_ip.mysocket import Socket
23
- from pymodaq.utils.tcp_ip.serializer import DeSerializer, Serializer
30
+ from pymodaq_utils.enums import BaseEnum, enum_checker
31
+
32
+ from pymodaq_utils.serialize.mysocket import Socket
33
+ from pymodaq_utils.serialize.serializer_legacy import DeSerializer, Serializer
34
+ from pymodaq import Unit
35
+ from pint.errors import OffsetUnitCalculusError
24
36
 
25
37
  if TYPE_CHECKING:
26
38
  from pymodaq.control_modules.daq_move import DAQ_Move_Hardware
@@ -29,13 +41,45 @@ logger = set_logger(get_module_name(__file__))
29
41
  config = configmod.Config()
30
42
 
31
43
 
44
+ def check_units(dwa: DataActuator, units: str):
45
+ """ Check if dwa units is compatible with the units argument
46
+
47
+ If it is incompatible and has dimensionless units, brute force change the dwa units to units,
48
+ otherwise raise a DataUnitError
49
+
50
+ Parameters
51
+ ----------
52
+ dwa: DataActuator
53
+ units: str
54
+
55
+ Returns
56
+ -------
57
+ DataActuator
58
+ """
59
+ if Unit(dwa.units).is_compatible_with(units):
60
+ return dwa
61
+ elif Unit(dwa.units).dimensionless: # dimensionless
62
+ dwa.force_units(units)
63
+ return dwa
64
+ else:
65
+ raise DataUnitError(f'Units incompatibility between {dwa} and "{units}" units')
66
+
67
+
32
68
  class DataActuatorType(BaseEnum):
33
69
  """Enum for new or old style holding the value of the actuator"""
34
70
  float = 0
35
71
  DataActuator = 1
36
72
 
37
73
 
38
- def comon_parameters(epsilon=config('actuator', 'epsilon_default')):
74
+ def comon_parameters(epsilon=config('actuator', 'epsilon_default'),
75
+ epsilons=None):
76
+ if epsilons is not None:
77
+ epsilon = epsilons
78
+ if isinstance(epsilon, list):
79
+ epsilon = epsilon[0]
80
+ elif isinstance(epsilon, dict):
81
+ epsilon = epsilon[list[epsilon.keys()][0]]
82
+
39
83
  return [{'title': 'Units:', 'name': 'units', 'type': 'str', 'value': '', 'readonly': True},
40
84
  {'title': 'Epsilon:', 'name': 'epsilon', 'type': 'float',
41
85
  'value': epsilon,
@@ -78,28 +122,52 @@ class MoveCommand:
78
122
  self.value = value
79
123
 
80
124
 
81
- def comon_parameters_fun(is_multiaxes=False, axes_names=[], axis_names=[], master=True, epsilon=config('actuator', 'epsilon_default')):
125
+ def comon_parameters_fun(is_multiaxes=False, axes_names=None,
126
+ axis_names: Union[List, Dict] = [],
127
+ master=True,
128
+ epsilon: float = config('actuator', 'epsilon_default')):
129
+
82
130
  """Function returning the common and mandatory parameters that should be on the actuator plugin level
83
131
 
84
132
  Parameters
85
133
  ----------
86
134
  is_multiaxes: bool
87
135
  If True, display the particular settings to define which axis the controller is driving
88
- axis_names: list of str
136
+ axes_names: deprecated, use axis_names
137
+ axis_names: list of str or dictionnary of string as key and integer as value
89
138
  The string identifier of every axis the controller can drive
90
139
  master: bool
91
140
  If True consider this plugin has to init the controller, otherwise use an already initialized instance
141
+ epsilon: float
142
+ deprecated (< 5.0.0) no more used here
143
+
92
144
  """
93
- if axis_names == [] and len(axes_names) != 0:
145
+ if axes_names is not None and len(axis_names) == 0:
146
+ if len(axes_names) == 0:
147
+ axes_names = ['']
94
148
  axis_names = axes_names
95
149
 
150
+ is_multiaxes = len(axis_names) > 1 or is_multiaxes
151
+ if isinstance(axis_names, list):
152
+ if len(axis_names) > 0:
153
+ axis_name = axis_names[0]
154
+ else:
155
+ axis_names = ['']
156
+ axis_name = ''
157
+ elif isinstance(axis_names, dict):
158
+ axis_name = axis_names[list(axis_names.keys())[0]]
159
+ else:
160
+ raise ValueError('axis_names should be either a list of string or a dict with strings '
161
+ 'as keys')
96
162
  params = [
97
- {'title': 'MultiAxes:', 'name': 'multiaxes', 'type': 'group', 'visible': is_multiaxes, 'children': [
98
- {'title': 'is Multiaxes:', 'name': 'ismultiaxes', 'type': 'bool', 'value': is_multiaxes,
99
- 'default': False},
163
+ {'title': 'MultiAxes:', 'name': 'multiaxes', 'type': 'group',
164
+ 'visible': True, 'children': [
165
+ {'title': 'Controller ID:', 'name': 'controller_ID', 'type': 'int', 'value': 0,
166
+ 'default': 0},
100
167
  {'title': 'Status:', 'name': 'multi_status', 'type': 'list',
101
168
  'value': 'Master' if master else 'Slave', 'limits': ['Master', 'Slave']},
102
- {'title': 'Axis:', 'name': 'axis', 'type': 'list', 'limits': axis_names},
169
+ {'title': 'Axis:', 'name': 'axis', 'type': 'list', 'limits': axis_names,
170
+ 'value': axis_name},
103
171
  ]},
104
172
  ] + comon_parameters(epsilon)
105
173
  return params
@@ -110,7 +178,7 @@ params = [
110
178
  {'title': 'Actuator type:', 'name': 'move_type', 'type': 'str', 'value': '', 'readonly': True},
111
179
  {'title': 'Actuator name:', 'name': 'module_name', 'type': 'str', 'value': '', 'readonly': True},
112
180
  {'title': 'Plugin Config:', 'name': 'plugin_config', 'type': 'bool_push', 'label': 'Show Config', },
113
- {'title': 'Controller ID:', 'name': 'controller_ID', 'type': 'int', 'value': 0, 'default': 0},
181
+
114
182
  {'title': 'Refresh value (ms):', 'name': 'refresh_timeout', 'type': 'int',
115
183
  'value': config('actuator', 'refresh_timeout_ms')},
116
184
  {'title': 'TCP/IP options:', 'name': 'tcpip', 'type': 'group', 'visible': True, 'expanded': False,
@@ -147,13 +215,13 @@ def main(plugin_file, init=True, title='test'):
147
215
  from qtpy import QtWidgets
148
216
  from pymodaq.control_modules.daq_move import DAQ_Move
149
217
  from pathlib import Path
150
- app = QtWidgets.QApplication(sys.argv)
151
- if config('style', 'darkstyle'):
152
- import qdarkstyle
153
- app.setStyleSheet(qdarkstyle.load_stylesheet())
218
+
219
+ act = Path(plugin_file).stem.split('daq_move_')[1]
220
+
221
+ app = mkQApp("PyMoDAQ Viewer")
154
222
 
155
223
  widget = QtWidgets.QWidget()
156
- prog = DAQ_Move(widget, title=title,)
224
+ prog = DAQ_Move(widget, title=title, actuator=act)
157
225
  widget.show()
158
226
  prog.actuator = Path(plugin_file).stem[9:]
159
227
  if init:
@@ -199,11 +267,17 @@ class DAQ_Move_base(QObject):
199
267
 
200
268
  move_done_signal = Signal(DataActuator)
201
269
  is_multiaxes = False
202
- stage_names = []
270
+ stage_names = [] # deprecated
271
+
272
+ _axis_names: Union[list, Dict[str, int]] = None
273
+ _controller_units: Union[str, List[str], Dict[str, int]] = ''
274
+ _epsilons: Union[float, List[float], Dict[str, float]] = None
275
+ _epsilon = 1.0 # deprecated
276
+
277
+
203
278
  params = []
204
- _controller_units = ''
205
- _epsilon = 1
206
- data_actuator_type = DataActuatorType['float']
279
+
280
+ data_actuator_type = DataActuatorType.float
207
281
  data_shape = (1, ) # expected shape of the underlying actuator's value (in general a float so shape = (1, ))
208
282
 
209
283
  def __init__(self, parent: Optional['DAQ_Move_Hardware'] = None,
@@ -212,6 +286,7 @@ class DAQ_Move_base(QObject):
212
286
  self.move_is_done = False
213
287
  self.parent = parent
214
288
  self.stage = None
289
+ self.controller = None
215
290
  self.status = edict(info="", controller=None, stage=None, initialized=False)
216
291
 
217
292
  self._ispolling = True
@@ -225,17 +300,25 @@ class DAQ_Move_base(QObject):
225
300
  self.settings.restoreState(params_state.saveState())
226
301
 
227
302
  self.settings.sigTreeStateChanged.connect(self.send_param_status)
228
- # self.settings.child('multiaxes',
229
- # 'axis').sigLimitsChanged.connect(lambda param,
230
- # limits: self.send_param_status(
231
- # param, [(param, 'limits', None)]))
303
+
232
304
  if parent is not None:
233
305
  self._title = parent.title
234
306
  else:
235
307
  self._title = "myactuator"
236
- self._current_value = DataActuator(self._title, data=[np.zeros(self.data_shape, dtype=float)])
237
- self._target_value = DataActuator(self._title, data=[np.zeros(self.data_shape, dtype=float)])
238
- self.controller_units = self._controller_units
308
+
309
+ self._axis_units: Union[Dict[str, str], List[str]] = None
310
+ self.axis_units = self._controller_units
311
+ if self._epsilons is None:
312
+ self._epsilons = self._epsilon
313
+ self.epsilons = self._epsilons
314
+ self.axis_name = self.axis_name # to trigger some actions on units and epsilons
315
+
316
+ self._current_value = DataActuator(self._title,
317
+ data=[np.zeros(self.data_shape, dtype=float)],
318
+ units=self.axis_unit)
319
+ self._target_value = DataActuator(self._title,
320
+ data=[np.zeros(self.data_shape, dtype=float)],
321
+ units=self.axis_unit)
239
322
 
240
323
  self.poll_timer = QTimer()
241
324
  self.poll_timer.setInterval(config('actuator', 'polling_interval_ms'))
@@ -245,7 +328,108 @@ class DAQ_Move_base(QObject):
245
328
  self.ini_attributes()
246
329
 
247
330
  @property
248
- def axis_name(self) -> Union[str, object]:
331
+ def axis_unit(self) -> str:
332
+ """ Get/set the unit of the currently chosen axis
333
+
334
+ Will update the printed controller unit in the UI
335
+
336
+ New in 4.4.0
337
+ """
338
+ return self.axis_units[self.axis_index_key]
339
+
340
+ @axis_unit.setter
341
+ def axis_unit(self, unit: str):
342
+ self.axis_units[self.axis_index_key] = unit
343
+ self.settings.child('units').setValue(unit)
344
+ self.emit_status(ThreadCommand('units', unit))
345
+
346
+ @property
347
+ def axis_units(self) -> Union[List[str], Dict[str, str]]:
348
+ """ Get/Set the units for each axis of the controller
349
+
350
+ New in 4.4.0
351
+ """
352
+ return self._axis_units
353
+
354
+ @axis_units.setter
355
+ def axis_units(self, units: Union[str, List[str], Dict[str, str]]):
356
+ if isinstance(units, str):
357
+ if isinstance(self.axis_names, list):
358
+ units_tmp = [units for _ in range(len(self.axis_names))]
359
+ else:
360
+ units_tmp = {}
361
+ for key in self.axis_names:
362
+ units_tmp[key] = units
363
+ else:
364
+ if not isinstance(units, type(self.axis_names)):
365
+ raise TypeError('units should be defined just like axis_names: a str, list of string or'
366
+ 'dict of string')
367
+ if len(units) != len(self.axis_names):
368
+ raise ValueError('Units should be defined either as a single str or a list/dict with'
369
+ 'a str defined for each axis')
370
+ units_tmp = units
371
+ self._axis_units = units_tmp
372
+
373
+ @property
374
+ def epsilon(self) -> float:
375
+ """ Get/Set the epsilon of the currently chosen axis
376
+
377
+ New in 4.4.0
378
+ """
379
+ return self.epsilons[self.axis_index_key]
380
+
381
+ @epsilon.setter
382
+ def epsilon(self, eps: float):
383
+ self.epsilons[self.axis_index_key] = eps
384
+
385
+ @property
386
+ def epsilons(self) -> Union[List[float], Dict[str, float]]:
387
+ """ Get/Set the epsilon for each axis of the controller
388
+
389
+ New in 4.4.0
390
+ """
391
+ return self._epsilons
392
+
393
+ @epsilons.setter
394
+ def epsilons(self, epsilons: Union[float, List[float], Dict[str, float]]):
395
+ if isinstance(epsilons, numbers.Number):
396
+ if isinstance(self.axis_names, list):
397
+ epsilons_tmp = [epsilons for _ in range(len(self.axis_names))]
398
+ else:
399
+ epsilons_tmp = {}
400
+ for key in self.axis_names:
401
+ epsilons_tmp[key] = epsilons
402
+ else:
403
+ if not isinstance(epsilons, type(self.axis_names)):
404
+ raise TypeError('units should be defined just like axis_names: a float, list of '
405
+ 'float or dict of float')
406
+ if len(epsilons) != len(self.axis_names):
407
+ raise ValueError('epsilons should be defined either as a single float or a '
408
+ 'list/dict with'
409
+ 'a float defined for each axis')
410
+ epsilons_tmp = epsilons
411
+ self._epsilons = epsilons_tmp
412
+
413
+ @property
414
+ def controller_units(self):
415
+ """ Get/Set the units of the currently chosen axis of the controller
416
+
417
+ Deprecated with pymodaq >= 4.4.0
418
+
419
+ The property controller_units is deprecated please use the axis_unit property
420
+ """
421
+ deprecation_msg('The property controller_units is deprecated please use the'
422
+ 'axis_unit property.')
423
+ return self.axis_unit
424
+
425
+ @controller_units.setter
426
+ def controller_units(self, units: str = ''):
427
+ deprecation_msg('The property controller_units is deprecated please use the'
428
+ 'axis_unit property.')
429
+ self._axis_units[self.axis_index_key] = units
430
+
431
+ @property
432
+ def axis_name(self) -> Union[str]:
249
433
  """Get/Set the current axis using its string identifier"""
250
434
  limits = self.settings.child('multiaxes', 'axis').opts['limits']
251
435
  if isinstance(limits, list):
@@ -262,6 +446,10 @@ class DAQ_Move_base(QObject):
262
446
  elif isinstance(limits, dict):
263
447
  self.settings.child('multiaxes', 'axis').setValue(limits[name])
264
448
  QtWidgets.QApplication.processEvents()
449
+ self.axis_unit = self.axis_unit
450
+ self.settings.child('epsilon').setValue(self.epsilon)
451
+ if self.controller is not None:
452
+ self._current_value = self.get_actuator_value()
265
453
 
266
454
  @property
267
455
  def axis_names(self) -> Union[List, Dict]:
@@ -279,15 +467,35 @@ class DAQ_Move_base(QObject):
279
467
  QtWidgets.QApplication.processEvents()
280
468
 
281
469
  @property
282
- def axis_value(self) -> object:
283
- """Get the current value selected from the current axis"""
284
- return self.settings['multiaxes', 'axis']
470
+ def axis_value(self) -> int:
471
+ """Get the current value selected from the current axis
472
+
473
+ In case axis_names is a list, return the element of the list: self.axis_name
474
+ In case axis_names is a dict, return the value of the dict self.axis_names[self.axis_name]
475
+ """
476
+ if isinstance(self.axis_names, list):
477
+ return self.axis_name
478
+ else:
479
+ return self.axis_names[self.axis_name]
480
+
481
+ @property
482
+ def axis_index_key(self) -> Union[int, str]:
483
+ """ Get the current index or key correspondingto the current axis
484
+
485
+ In case axis_names is a list, return the index wihtin the list
486
+ In case axis_names is a dict, return the key of the dict self.axis_name
487
+
488
+ """
489
+ if isinstance(self.axis_names, list):
490
+ return self.axis_names.index(self.axis_name)
491
+ else:
492
+ return self.axis_name
285
493
 
286
494
  def ini_attributes(self):
287
495
  """ To be subclassed, in order to init specific attributes needed by the real implementation"""
288
496
  self.controller = None
289
497
 
290
- def ini_stage_init(self, old_controller=None, new_controller=None):
498
+ def ini_stage_init(self, old_controller=None, new_controller=None, slave_controller=None):
291
499
  """Manage the Master/Slave controller issue
292
500
 
293
501
  First initialize the status dictionary
@@ -305,10 +513,14 @@ class DAQ_Move_base(QObject):
305
513
  The particular object that allow the communication with the hardware, in general a python wrapper around the
306
514
  hardware library. In case of Master it is the new instance of your plugin controller
307
515
  """
516
+ if old_controller is None and slave_controller is not None:
517
+ old_controller = slave_controller
518
+
308
519
  self.status.update(edict(info="", controller=None, initialized=False))
309
- if self.settings['multiaxes', 'ismultiaxes'] and self.settings['multiaxes', 'multi_status'] == "Slave":
520
+ if not self.is_master:
310
521
  if old_controller is None:
311
- raise Exception('no controller has been defined externally while this axe is a slave one')
522
+ raise Exception('no controller has been defined externally while this axe '
523
+ 'is a slave one')
312
524
  else:
313
525
  controller = old_controller
314
526
  else: # Master stage
@@ -318,30 +530,40 @@ class DAQ_Move_base(QObject):
318
530
 
319
531
  @property
320
532
  def current_value(self):
321
- if self.data_actuator_type.name == 'float':
533
+ if self.data_actuator_type == self.data_actuator_type.float:
322
534
  return self._current_value.value()
323
535
  else:
324
536
  return self._current_value
325
537
 
326
538
  @current_value.setter
327
- def current_value(self, value: Union[float, DataActuator]):
328
- if not isinstance(value, DataActuator):
329
- self._current_value = DataActuator(self._title, data=value)
539
+ def current_value(self, value: Union[float, np.ndarray, DataActuator]):
540
+ if isinstance(value, numbers.Number) or isinstance(value, np.ndarray):
541
+ self._current_value = DataActuator(self._title, data=value,
542
+ units=self.axis_unit)
330
543
  else:
544
+ if (not Unit(self.axis_unit).is_compatible_with(
545
+ Unit(value.units)) and
546
+ value.units == ''):
547
+ value.force_units(self.axis_unit)
331
548
  self._current_value = value
332
549
 
333
550
  @property
334
551
  def target_value(self):
335
- if self.data_actuator_type.name == 'float':
552
+ if self.data_actuator_type.name == self.data_actuator_type.float:
336
553
  return self._target_value.value()
337
554
  else:
338
555
  return self._target_value
339
556
 
340
557
  @target_value.setter
341
- def target_value(self, value: Union[float, DataActuator]):
342
- if not isinstance(value, DataActuator):
343
- self._target_value = DataActuator(self._title, data=value)
558
+ def target_value(self, value: Union[numbers.Number, DataActuator]):
559
+ if isinstance(value, numbers.Number):
560
+ self._target_value = DataActuator(self._title, data=value,
561
+ units=self.axis_unit)
344
562
  else:
563
+ if (not Unit(self.axis_unit).is_compatible_with(
564
+ Unit(value.units)) and
565
+ value.units == ''):
566
+ value.force_units(self.axis_unit)
345
567
  self._target_value = value
346
568
 
347
569
  @property
@@ -370,20 +592,6 @@ class DAQ_Move_base(QObject):
370
592
  """
371
593
  return self.settings['multiaxes', 'multi_status'] == 'Master'
372
594
 
373
- @property
374
- def controller_units(self):
375
- """ Get/Set the units of this plugin"""
376
- return self._controller_units
377
-
378
- @controller_units.setter
379
- def controller_units(self, units: str = ''):
380
- self._controller_units = units
381
- try:
382
- self.settings.child('units').setValue(units)
383
- self.emit_status(ThreadCommand('units', units))
384
- except Exception:
385
- pass
386
-
387
595
  @property
388
596
  def ispolling(self):
389
597
  """ Get/Set the polling status"""
@@ -398,13 +606,11 @@ class DAQ_Move_base(QObject):
398
606
 
399
607
  Return the new position eventually coerced within the bounds
400
608
  """
401
- if self.settings.child('bounds', 'is_bounds').value():
402
- if position > self.settings.child('bounds', 'max_bound').value():
403
- position = DataActuator(self._title, data=self.settings.child('bounds', 'max_bound').value())
404
- self.emit_status(ThreadCommand('outofbounds', []))
405
- elif position < self.settings.child('bounds', 'min_bound').value():
406
- position = DataActuator(self._title, data=self.settings.child('bounds', 'min_bound').value())
407
- self.emit_status(ThreadCommand('outofbounds', []))
609
+ if self.settings['bounds', 'is_bounds']:
610
+ for data_array in position:
611
+ data_array[data_array > self.settings['bounds', 'max_bound']] = self.settings['bounds', 'max_bound']
612
+ data_array[data_array < self.settings['bounds', 'min_bound']] = self.settings['bounds', 'min_bound']
613
+ self.emit_status(ThreadCommand('outofbounds', []))
408
614
  return position
409
615
 
410
616
  def get_actuator_value(self):
@@ -414,6 +620,10 @@ class DAQ_Move_base(QObject):
414
620
  else:
415
621
  raise NotImplementedError
416
622
 
623
+
624
+ def close(self):
625
+ raise NotImplementedError
626
+
417
627
  def move_abs(self, value: Union[float, DataActuator]):
418
628
  if hasattr(self, 'move_Abs'):
419
629
  deprecation_msg('move_Abs method in plugins is deprecated, use move_abs', 3)
@@ -470,12 +680,14 @@ class DAQ_Move_base(QObject):
470
680
  """
471
681
  if position is None:
472
682
  if self.data_actuator_type.name == 'float':
473
- position = DataActuator(self._title, data=self.get_actuator_value())
683
+ position = DataActuator(self._title, data=self.get_actuator_value(),
684
+ units=self.axis_unit)
474
685
  else:
475
686
  position = self.get_actuator_value()
476
687
  if position.name != self._title: # make sure the emitted DataActuator has the name of the real implementation
477
688
  #of the plugin
478
- position = DataActuator(self._title, data=position.value())
689
+ position = DataActuator(self._title, data=position.value(),
690
+ units=self.axis_unit)
479
691
  self.move_done_signal.emit(position)
480
692
  self.move_is_done = True
481
693
 
@@ -491,31 +703,74 @@ class DAQ_Move_base(QObject):
491
703
  if self.ispolling:
492
704
  self.poll_timer.start()
493
705
  else:
494
- if self.data_actuator_type.name == 'float':
495
- self._current_value = DataActuator(data=self.get_actuator_value())
706
+ if self.data_actuator_type == DataActuatorType.float:
707
+ self._current_value = DataActuator(data=self.get_actuator_value(),
708
+ units=self.axis_unit)
496
709
  else:
497
710
  self._current_value = self.get_actuator_value()
711
+ if (not Unit(self.axis_unit).is_compatible_with(
712
+ Unit(self._current_value.units)) and
713
+ self._current_value.units == ''):
714
+ # this happens if the units have not been specified in
715
+ # the plugin
716
+ self._current_value.force_units(self.axis_unit)
717
+
498
718
  logger.debug(f'Current position: {self._current_value}')
499
719
  self.move_done(self._current_value)
500
720
 
501
- def check_target_reached(self):
502
- # if not isinstance(self._current_value, DataActuator):
503
- # self._current_value = DataActuator(data=self._current_value)
504
- # if not isinstance(self._target_value, DataActuator):
505
- # self._target_value = DataActuator(data=self._target_value)
721
+ def _condition_to_reach_target(self) -> bool:
722
+ """ Implement the condition for exiting the polling mechanism and specifying that the
723
+ target value has been reached
724
+
725
+ Returns
726
+ -------
727
+ bool: if True, PyMoDAQ considers the target value has been reached at epsilon
728
+
729
+ See Also
730
+ --------
731
+ user_condition_to_reach_target
732
+ """
733
+ try:
734
+ epsilon_calculated = (
735
+ self._current_value - self._target_value).abs().value(self.axis_unit)
736
+ except DataUnitError as e:
737
+ epsilon_calculated = abs(self._current_value.value() - self._target_value.value())
738
+ logger.warning(f'Unit issue when calculating epsilon, units are not compatible between'
739
+ f'target and current values')
740
+ except OffsetUnitCalculusError as e:
741
+ if '°C' in self.axis_unit or 'celcius' in self.axis_unit.lower():
742
+ epsilon_calculated = (
743
+ self._current_value.to_base_units() -
744
+ self._target_value.to_base_units()).abs().value()
745
+ else:
746
+ raise e
506
747
 
507
- logger.debug(f"epsilon value is {self.settings['epsilon']}")
748
+ return (epsilon_calculated < self.epsilon) and self.user_condition_to_reach_target()
749
+
750
+ def user_condition_to_reach_target(self) -> bool:
751
+ """ Implement a user defined condition for exiting the polling mechanism and specifying
752
+ that the target value has been reached (on top of the existing epsilon mechanism)
753
+
754
+ Should be reimplemented in plugins to implement other conditions
755
+
756
+ Returns
757
+ -------
758
+ bool: if True, PyMoDAQ considers the target value has been reached
759
+ """
760
+ return True
761
+
762
+ def check_target_reached(self):
763
+ logger.debug(f"epsilon value is {self.epsilon}")
508
764
  logger.debug(f"current_value value is {self._current_value}")
509
765
  logger.debug(f"target_value value is {self._target_value}")
510
766
 
511
- if not (self._current_value - self._target_value).abs() < self.settings['epsilon']:
767
+ if not self._condition_to_reach_target():
512
768
 
513
769
  logger.debug(f'Check move_is_done: {self.move_is_done}')
514
770
  if self.move_is_done:
515
771
  self.emit_status(ThreadCommand('Move has been stopped', ))
516
772
  logger.info('Move has been stopped')
517
773
  self.current_value = self.get_actuator_value()
518
-
519
774
  self.emit_value(self._current_value)
520
775
  logger.debug(f'Current value: {self._current_value}')
521
776
 
@@ -525,6 +780,7 @@ class DAQ_Move_base(QObject):
525
780
  logger.info('Timeout activated')
526
781
  else:
527
782
  self.poll_timer.stop()
783
+ self.current_value = self.get_actuator_value()
528
784
  logger.debug(f'Current value: {self._current_value}')
529
785
  self.move_done(self._current_value)
530
786
 
@@ -605,6 +861,11 @@ class DAQ_Move_base(QObject):
605
861
  self.commit_common_settings(param)
606
862
  self.commit_settings(param)
607
863
 
864
+ if param.name() == 'axis':
865
+ self.axis_name = param.value()
866
+ elif param.name() == 'epsilon':
867
+ self.epsilon = param.value()
868
+
608
869
 
609
870
  class DAQ_Move_TCP_server(DAQ_Move_base, TCPServer):
610
871
  """
@@ -622,12 +883,12 @@ class DAQ_Move_TCP_server(DAQ_Move_base, TCPServer):
622
883
  """
623
884
  params_client = [] # parameters of a client grabber
624
885
  command_server = Signal(list)
625
- data_actuator_type = DataActuatorType['DataActuator']
886
+ data_actuator_type = DataActuatorType.DataActuator
626
887
 
627
888
  message_list = ["Quit", "Status", "Done", "Server Closed", "Info", "Infos", "Info_xml", "move_abs",
628
889
  'move_home', 'move_rel', 'get_actuator_value', 'stop_motion', 'position_is', 'move_done']
629
890
  socket_types = ["ACTUATOR"]
630
- params = comon_parameters() + tcp_parameters
891
+ params = comon_parameters_fun() + tcp_parameters
631
892
 
632
893
  def __init__(self, parent=None, params_state=None):
633
894
  """
@@ -654,13 +915,13 @@ class DAQ_Move_TCP_server(DAQ_Move_base, TCPServer):
654
915
 
655
916
  pos = self.get_position_with_scaling(pos)
656
917
  self._current_value = pos
657
- self.emit_status(ThreadCommand('get_actuator_value', [pos]))
918
+ self.emit_status(ThreadCommand('get_actuator_value', pos))
658
919
 
659
920
  elif command == 'move_done':
660
921
  pos = DeSerializer(sock).dwa_deserialization()
661
922
  pos = self.get_position_with_scaling(pos)
662
923
  self._current_value = pos
663
- self.emit_status(ThreadCommand('move_done', [pos]))
924
+ self.emit_status(ThreadCommand('move_done', pos))
664
925
  else:
665
926
  self.send_command(sock, command)
666
927
 
@@ -699,6 +960,12 @@ class DAQ_Move_TCP_server(DAQ_Move_base, TCPServer):
699
960
  initialized = True
700
961
  return info, initialized
701
962
 
963
+ def read_infos(self, sock: Socket = None, infos=''):
964
+ """Reimplemented to get the units"""
965
+ super().read_infos(sock, infos)
966
+
967
+ self.axis_unit = self.settings['settings_client', 'units']
968
+
702
969
  def close(self):
703
970
  """
704
971
  Should be used to uninitialize hardware.