pymodaq 4.3.6__py3-none-any.whl → 4.4.0__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.

pymodaq/__init__.py CHANGED
@@ -51,6 +51,7 @@ try:
51
51
  ureg.default_format = '~'
52
52
  Q_ = ureg.Quantity
53
53
  Unit = ureg.Unit
54
+ ureg.define('whatever = 1 = wr') # defining whatever as a dimensionless quantity
54
55
  logger.info('')
55
56
  logger.info('')
56
57
 
@@ -23,7 +23,8 @@ from easydict import EasyDict as edict
23
23
  from pymodaq.utils.logger import set_logger, get_module_name
24
24
  from pymodaq.control_modules.utils import ParameterControlModule
25
25
  from pymodaq.control_modules.daq_move_ui import DAQ_Move_UI, ThreadCommand
26
- from pymodaq.control_modules.move_utility_classes import MoveCommand, DAQ_Move_base, DataActuatorType
26
+ from pymodaq.control_modules.move_utility_classes import (MoveCommand, DAQ_Move_base,
27
+ DataActuatorType, check_units)
27
28
  from pymodaq.control_modules.move_utility_classes import params as daq_move_params
28
29
  from pymodaq.utils import daq_utils as utils
29
30
  from pymodaq.utils.parameter import utils as putils
@@ -32,7 +33,8 @@ from pymodaq.utils import config as config_mod
32
33
  from pymodaq.utils.exceptions import ActuatorError
33
34
  from pymodaq.utils.messenger import deprecation_msg
34
35
  from pymodaq.utils.h5modules import module_saving
35
- from pymodaq.utils.data import DataRaw, DataToExport, DataFromPlugins, DataActuator
36
+ from pymodaq.utils.data import (DataRaw, DataToExport, DataFromPlugins, DataActuator, Unit,
37
+ DataUnitError)
36
38
  from pymodaq.utils.h5modules.backends import Node
37
39
  from pymodaq.utils.parameter import ioxml, Parameter
38
40
 
@@ -161,11 +163,13 @@ class DAQ_Move(ParameterControlModule):
161
163
  self.stop_motion()
162
164
  elif cmd.command == 'move_abs':
163
165
  data_act: DataActuator = cmd.attribute
164
- data_act.force_units(self.units)
166
+ if not Unit(data_act.units).is_compatible_with(self.units) and data_act.units != '':
167
+ data_act.force_units(self.units)
165
168
  self.move_abs(data_act)
166
169
  elif cmd.command == 'move_rel':
167
170
  data_act: DataActuator = cmd.attribute
168
- data_act.force_units(self.units)
171
+ if not Unit(data_act.units).is_compatible_with(self.units) and data_act.units != '':
172
+ data_act.force_units(self.units)
169
173
  self.move_rel(data_act)
170
174
  elif cmd.command == 'show_log':
171
175
  self.show_log()
@@ -591,7 +595,14 @@ class DAQ_Move(ParameterControlModule):
591
595
  def units(self, unit: str):
592
596
  self.settings.child('move_settings', 'units').setValue(unit)
593
597
  if self.ui is not None and config('actuator', 'display_units'):
594
- self.ui.set_unit_as_suffix(unit)
598
+ if unit == '°':
599
+ # special cas as pint base unit for angles are radians
600
+ self.ui.set_unit_as_suffix(unit)
601
+ else:
602
+ # if the controller units are in mm the displayed unit will be m
603
+ # because m is the base unit
604
+ # then the user could ask for mm, km, µm...
605
+ self.ui.set_unit_as_suffix(str(Q_(1, unit).to_base_units().units))
595
606
 
596
607
  def update_settings(self):
597
608
 
@@ -668,8 +679,6 @@ class DAQ_Move_Hardware(QObject):
668
679
  self._title = title
669
680
  self.hardware: Optional[DAQ_Move_base] = None
670
681
  self.actuator_type = actuator_type
671
- self.current_position: DataActuator = position
672
- self._target_value: Optional[DataActuator] = None
673
682
  self.hardware_adress = None
674
683
  self.axis_address = None
675
684
  self.motion_stoped = False
@@ -692,9 +701,8 @@ class DAQ_Move_Hardware(QObject):
692
701
  """
693
702
  pos = self.hardware.get_actuator_value()
694
703
  if self.hardware.data_actuator_type == DataActuatorType.float:
695
- return DataActuator(self._title, data=pos, units=self.units)
696
- else:
697
- return pos
704
+ pos = DataActuator(self._title, data=pos, units=self.hardware.axis_unit)
705
+ return pos
698
706
 
699
707
  def check_position(self):
700
708
  """Get the current position checking the hardware position (deprecated)
@@ -743,6 +751,8 @@ class DAQ_Move_Hardware(QObject):
743
751
  status.initialized = infos[1]
744
752
  status.controller = self.hardware.controller
745
753
  self.hardware.move_done_signal.connect(self.move_done)
754
+ if status.initialized:
755
+ self.status_sig.emit(ThreadCommand('get_actuator_value', [self.get_actuator_value()]))
746
756
 
747
757
  return status
748
758
  except Exception as e:
@@ -753,15 +763,13 @@ class DAQ_Move_Hardware(QObject):
753
763
  """
754
764
 
755
765
  """
756
- # if isinstance(position, Number):
757
- # position = float(position) # because it may be a numpy float and could cause issues
758
- # # see https://github.com/pythonnet/pythonnet/issues/1833
759
- self._target_value = position
766
+ position = check_units(position, self.hardware.axis_unit)
760
767
  self.hardware.move_is_done = False
761
768
  self.hardware.ispolling = polling
762
769
  if self.hardware.data_actuator_type.name == 'float':
763
770
  self.hardware.move_abs(position.value())
764
771
  else:
772
+ position.units = self.hardware.axis_unit # convert to plugin controller current axis units
765
773
  self.hardware.move_abs(position)
766
774
  self.hardware.poll_moving()
767
775
 
@@ -769,14 +777,14 @@ class DAQ_Move_Hardware(QObject):
769
777
  """
770
778
 
771
779
  """
772
-
780
+ rel_position = check_units(rel_position, self.hardware.axis_unit)
773
781
  self.hardware.move_is_done = False
774
- self._target_value = self.current_position + rel_position
775
782
  self.hardware.ispolling = polling
776
783
 
777
784
  if self.hardware.data_actuator_type.name == 'float':
778
785
  self.hardware.move_rel(rel_position.value())
779
786
  else:
787
+ rel_position.units = self.hardware.axis_unit # convert to plugin current axis units
780
788
  self.hardware.move_rel(rel_position)
781
789
 
782
790
  self.hardware.poll_moving()
@@ -798,7 +806,6 @@ class DAQ_Move_Hardware(QObject):
798
806
 
799
807
  """
800
808
  self.hardware.move_is_done = False
801
- self._target_value = 0
802
809
  self.hardware.move_home()
803
810
 
804
811
  @Slot(DataActuator)
@@ -7,6 +7,7 @@ Created the 28/07/2022
7
7
 
8
8
  from typing import List
9
9
  import sys
10
+ from pint.errors import DimensionalityError
10
11
 
11
12
  from qtpy import QtWidgets
12
13
  from qtpy.QtCore import Signal, Qt
@@ -65,12 +66,17 @@ class DAQ_Move_UI(ControlModuleUI):
65
66
 
66
67
  super().__init__(parent)
67
68
  self.title = title
69
+ self._unit = ''
68
70
  self.setup_ui()
69
71
 
70
72
  self.enable_move_buttons(False)
71
73
 
72
74
  def display_value(self, value: DataActuator):
73
- self.current_value_sb.setValue(value.value())
75
+ try:
76
+ self.current_value_sb.setValue(value.value(self._unit))
77
+ except DimensionalityError as e:
78
+ value.force_units(self._unit)
79
+ self.current_value_sb.setValue(value.value())
74
80
 
75
81
  @property
76
82
  def actuator_init(self):
@@ -193,9 +199,9 @@ class DAQ_Move_UI(ControlModuleUI):
193
199
  self.main_ui.layout().addWidget(self.toolbar, 0, 0, 1, 2)
194
200
  self.main_ui.layout().addWidget(self.move_toolbar, 1, 0, 1, 2)
195
201
 
196
- self.abs_value_sb = SpinBox(step=0.1, dec=True)
202
+ self.abs_value_sb = SpinBox(step=0.1, dec=True, siPrefix=config('actuator', 'siprefix'))
197
203
  self.abs_value_sb.setStyleSheet("background-color : lightgreen; color: black")
198
- self.abs_value_sb_2 = SpinBox(step=0.1, dec=True)
204
+ self.abs_value_sb_2 = SpinBox(step=0.1, dec=True, siPrefix=config('actuator', 'siprefix'))
199
205
  self.abs_value_sb_2.setStyleSheet("background-color : lightcoral; color: black")
200
206
  self.move_toolbar.addWidget(self.abs_value_sb)
201
207
  self.move_toolbar.addWidget(self.abs_value_sb_2)
@@ -221,7 +227,7 @@ class DAQ_Move_UI(ControlModuleUI):
221
227
  self.control_ui.layout().addWidget(LabelWithFont('Abs. Value'), 0, 0)
222
228
  self.find_home_pb = PushButtonIcon('home2', 'Find Home')
223
229
  self.control_ui.layout().addWidget(self.find_home_pb, 0, 1)
224
- self.abs_value_sb_bis = SpinBox(step=0.1, dec=True)
230
+ self.abs_value_sb_bis = SpinBox(step=0.1, dec=True, siPrefix=config('actuator', 'siprefix'))
225
231
  self.control_ui.layout().addWidget(self.abs_value_sb_bis, 1, 0)
226
232
  self.move_abs_pb = PushButtonIcon('Move', 'Set Abs.',
227
233
  tip='Set the value of the actuator to the set absolute value')
@@ -230,7 +236,7 @@ class DAQ_Move_UI(ControlModuleUI):
230
236
  self.move_rel_plus_pb = PushButtonIcon('MoveUp', 'Set Rel. (+)')
231
237
  self.control_ui.layout().addWidget(self.move_rel_plus_pb, 2, 1)
232
238
 
233
- self.rel_value_sb = SpinBox(step=0.1, dec=True)
239
+ self.rel_value_sb = SpinBox(step=0.1, dec=True, siPrefix=config('actuator', 'siprefix'))
234
240
  self.control_ui.layout().addWidget(self.rel_value_sb, 3, 0)
235
241
  self.move_rel_minus_pb = PushButtonIcon('MoveDown', 'Set Rel. (-)')
236
242
  self.control_ui.layout().addWidget(self.move_rel_minus_pb, 3, 1)
@@ -252,6 +258,7 @@ class DAQ_Move_UI(ControlModuleUI):
252
258
 
253
259
  def set_unit_as_suffix(self, unit: str):
254
260
  """Will append the actuator units in the value display"""
261
+ self._unit = unit
255
262
  self.current_value_sb.setOpts(suffix=unit)
256
263
  self.abs_value_sb_bis.setOpts(suffix=unit)
257
264
  self.abs_value_sb.setOpts(suffix=unit)
@@ -328,12 +335,14 @@ class DAQ_Move_UI(ControlModuleUI):
328
335
 
329
336
  def emit_move_abs(self, spinbox):
330
337
  spinbox.editingFinished.emit()
331
- self.command_sig.emit(ThreadCommand('move_abs', DataActuator(data=spinbox.value())))
338
+ self.command_sig.emit(ThreadCommand('move_abs', DataActuator(data=spinbox.value(),
339
+ units=self._unit)))
332
340
 
333
341
  def emit_move_rel(self, sign):
334
- self.command_sig.emit(ThreadCommand('move_rel',
335
- DataActuator(data=self.rel_value_sb.value() * (1 if sign == '+'
336
- else -1))))
342
+ self.command_sig.emit(ThreadCommand(
343
+ 'move_rel',
344
+ DataActuator(data=self.rel_value_sb.value() * (1 if sign == '+' else -1),
345
+ units=self._unit)))
337
346
 
338
347
  def close(self):
339
348
  self.graph_ui.close()
@@ -1,6 +1,7 @@
1
1
  from time import perf_counter
2
2
  from typing import Union, List, Dict, TYPE_CHECKING, Optional
3
3
  from numbers import Number
4
+ from collections.abc import Iterable
4
5
 
5
6
  from easydict import EasyDict as edict
6
7
  import numpy as np
@@ -30,6 +31,30 @@ logger = set_logger(get_module_name(__file__))
30
31
  config = configmod.Config()
31
32
 
32
33
 
34
+ def check_units(dwa: DataActuator, units: str):
35
+ """ Check if dwa units is compatible with the units argument
36
+
37
+ If it is incompatible and has dimensionless units, brute force change the dwa units to units,
38
+ otherwise raise a DataUnitError
39
+
40
+ Parameters
41
+ ----------
42
+ dwa: DataActuator
43
+ units: str
44
+
45
+ Returns
46
+ -------
47
+ DataActuator
48
+ """
49
+ if Unit(dwa.units).is_compatible_with(units):
50
+ return dwa
51
+ elif Unit(dwa.units).dimensionless: # dimensionless
52
+ dwa.force_units(units)
53
+ return dwa
54
+ else:
55
+ raise DataUnitError(f'Units incompatibility between {dwa} and "{units}" units')
56
+
57
+
33
58
  class DataActuatorType(BaseEnum):
34
59
  """Enum for new or old style holding the value of the actuator"""
35
60
  float = 0
@@ -37,6 +62,8 @@ class DataActuatorType(BaseEnum):
37
62
 
38
63
 
39
64
  def comon_parameters(epsilon=config('actuator', 'epsilon_default')):
65
+ if isinstance(epsilon, list):
66
+ epsilon=epsilon[0]
40
67
  return [{'title': 'Units:', 'name': 'units', 'type': 'str', 'value': '', 'readonly': True},
41
68
  {'title': 'Epsilon:', 'name': 'epsilon', 'type': 'float',
42
69
  'value': epsilon,
@@ -80,10 +107,10 @@ class MoveCommand:
80
107
 
81
108
 
82
109
  def comon_parameters_fun(is_multiaxes = False,
83
- axes_names = [],
110
+ axes_names: Union[List[str], Dict[str, int]] = [],
84
111
  axis_names: Union[List, Dict] = [],
85
- master = True,
86
- epsilon = config('actuator', 'epsilon_default')):
112
+ master=True,
113
+ epsilon=config('actuator', 'epsilon_default')):
87
114
  """Function returning the common and mandatory parameters that should be on the actuator plugin level
88
115
 
89
116
  Parameters
@@ -206,8 +233,8 @@ class DAQ_Move_base(QObject):
206
233
  is_multiaxes = False
207
234
  stage_names = []
208
235
  params = []
209
- _controller_units = ''
210
- _epsilon = 1
236
+ _controller_units: Union[str, List[str]] = ''
237
+ _epsilon = 1.0
211
238
  data_actuator_type = DataActuatorType.float
212
239
  data_shape = (1, ) # expected shape of the underlying actuator's value (in general a float so shape = (1, ))
213
240
 
@@ -236,13 +263,20 @@ class DAQ_Move_base(QObject):
236
263
  self._title = parent.title
237
264
  else:
238
265
  self._title = "myactuator"
266
+
267
+ self._axis_units: List[str] = []
268
+ self.axis_units = self._controller_units
269
+ self._epsilons: List[float] = [] # self._epsilon if isinstance(self._epsilon, list) else\
270
+ # [self._epsilon for _ in range(len(self.axis_name))]
271
+ self.epsilons = self._epsilon
272
+ self.axis_name = self.axis_name # to trigger some actions on units and epsilons
273
+
239
274
  self._current_value = DataActuator(self._title,
240
275
  data=[np.zeros(self.data_shape, dtype=float)],
241
- units=self.controller_units)
276
+ units=self.axis_unit)
242
277
  self._target_value = DataActuator(self._title,
243
278
  data=[np.zeros(self.data_shape, dtype=float)],
244
- units=self.controller_units)
245
- self.controller_units = self._controller_units
279
+ units=self.axis_unit)
246
280
 
247
281
  self.poll_timer = QTimer()
248
282
  self.poll_timer.setInterval(config('actuator', 'polling_interval_ms'))
@@ -252,7 +286,81 @@ class DAQ_Move_base(QObject):
252
286
  self.ini_attributes()
253
287
 
254
288
  @property
255
- def axis_name(self) -> Union[str, object]:
289
+ def axis_unit(self) -> str:
290
+ """ Get/set the unit of the currently chosen axis
291
+
292
+ Will update the printed controller unit in the UI
293
+
294
+ New in 4.4.0
295
+ """
296
+ return self.axis_units[self.axis_value]
297
+
298
+ @axis_unit.setter
299
+ def axis_unit(self, unit: str):
300
+ self.axis_units[self.axis_value] = unit
301
+ self.settings.child('units').setValue(unit)
302
+ self.emit_status(ThreadCommand('units', unit))
303
+
304
+ @property
305
+ def axis_units(self) -> List[str]:
306
+ """ Get/Set the units for each axis of the controller
307
+
308
+ New in 4.4.0
309
+ """
310
+ return self._axis_units
311
+
312
+ @axis_units.setter
313
+ def axis_units(self, units: Union[str, List[str]]):
314
+ if isinstance(units, str):
315
+ units = [units for _ in range(len(self.axis_names))]
316
+ self._axis_units = units
317
+
318
+ @property
319
+ def epsilon(self) -> float:
320
+ """ Get/Set the epsilon of the currently chosen axis
321
+
322
+ New in 4.4.0
323
+ """
324
+ return self.epsilons[self.axis_value]
325
+
326
+ @epsilon.setter
327
+ def epsilon(self, eps: float):
328
+ self.epsilons[self.axis_value] = eps
329
+
330
+ @property
331
+ def epsilons(self) -> List[float]:
332
+ """ Get/Set the epsilon for each axis of the controller
333
+
334
+ New in 4.4.0
335
+ """
336
+ return self._epsilons
337
+
338
+ @epsilons.setter
339
+ def epsilons(self, epsilons: Union[float, List[float]]):
340
+ if not isinstance(epsilons, Iterable) and isinstance(epsilons, float):
341
+ epsilons = [epsilons for _ in range(len(self.axis_names))]
342
+ self._epsilons = epsilons
343
+
344
+ @property
345
+ def controller_units(self):
346
+ """ Get/Set the units of the currently chosen axis of the controller
347
+
348
+ Deprecated with pymodaq >= 4.4.0
349
+
350
+ The property controller_units is deprecated please use the axis_unit property
351
+ """
352
+ deprecation_msg('The property controller_units is deprecated please use the'
353
+ 'axis_unit property.')
354
+ return self.axis_unit
355
+
356
+ @controller_units.setter
357
+ def controller_units(self, units: str = ''):
358
+ deprecation_msg('The property controller_units is deprecated please use the'
359
+ 'axis_unit property.')
360
+ self._axis_units[self.axis_value] = units
361
+
362
+ @property
363
+ def axis_name(self) -> Union[str]:
256
364
  """Get/Set the current axis using its string identifier"""
257
365
  limits = self.settings.child('multiaxes', 'axis').opts['limits']
258
366
  if isinstance(limits, list):
@@ -269,6 +377,9 @@ class DAQ_Move_base(QObject):
269
377
  elif isinstance(limits, dict):
270
378
  self.settings.child('multiaxes', 'axis').setValue(limits[name])
271
379
  QtWidgets.QApplication.processEvents()
380
+ self.axis_unit = self.axis_unit
381
+ self.settings.child('epsilon').setValue(self.epsilon)
382
+
272
383
 
273
384
  @property
274
385
  def axis_names(self) -> Union[List, Dict]:
@@ -286,9 +397,12 @@ class DAQ_Move_base(QObject):
286
397
  QtWidgets.QApplication.processEvents()
287
398
 
288
399
  @property
289
- def axis_value(self) -> object:
400
+ def axis_value(self) -> int:
290
401
  """Get the current value selected from the current axis"""
291
- return self.settings['multiaxes', 'axis']
402
+ if isinstance(self.axis_names, list):
403
+ return self.axis_names.index(self.axis_name)
404
+ else:
405
+ return self.axis_names[self.axis_name]
292
406
 
293
407
  def ini_attributes(self):
294
408
  """ To be subclassed, in order to init specific attributes needed by the real implementation"""
@@ -337,12 +451,12 @@ class DAQ_Move_base(QObject):
337
451
  def current_value(self, value: Union[float, DataActuator]):
338
452
  if not isinstance(value, DataActuator):
339
453
  self._current_value = DataActuator(self._title, data=value,
340
- units=self.controller_units)
454
+ units=self.axis_unit)
341
455
  else:
342
- if (not Unit(self.controller_units).is_compatible_with(
456
+ if (not Unit(self.axis_unit).is_compatible_with(
343
457
  Unit(value.units)) and
344
458
  value.units == ''):
345
- value.force_units(self.controller_units)
459
+ value.force_units(self.axis_unit)
346
460
  self._current_value = value
347
461
 
348
462
  @property
@@ -356,12 +470,12 @@ class DAQ_Move_base(QObject):
356
470
  def target_value(self, value: Union[float, DataActuator]):
357
471
  if not isinstance(value, DataActuator):
358
472
  self._target_value = DataActuator(self._title, data=value,
359
- units=self.controller_units)
473
+ units=self.axis_unit)
360
474
  else:
361
- if (not Unit(self.controller_units).is_compatible_with(
475
+ if (not Unit(self.axis_unit).is_compatible_with(
362
476
  Unit(value.units)) and
363
477
  value.units == ''):
364
- value.force_units(self.controller_units)
478
+ value.force_units(self.axis_unit)
365
479
  self._target_value = value
366
480
 
367
481
  @property
@@ -390,19 +504,6 @@ class DAQ_Move_base(QObject):
390
504
  """
391
505
  return self.settings['multiaxes', 'multi_status'] == 'Master'
392
506
 
393
- @property
394
- def controller_units(self):
395
- """ Get/Set the units of this plugin"""
396
- return self._controller_units
397
-
398
- @controller_units.setter
399
- def controller_units(self, units: str = ''):
400
- self._controller_units = units
401
- try:
402
- self.settings.child('units').setValue(units)
403
- self.emit_status(ThreadCommand('units', units))
404
- except Exception:
405
- pass
406
507
 
407
508
  @property
408
509
  def ispolling(self):
@@ -422,12 +523,12 @@ class DAQ_Move_base(QObject):
422
523
  if position > self.settings.child('bounds', 'max_bound').value():
423
524
  position = DataActuator(self._title,
424
525
  data=self.settings.child('bounds', 'max_bound').value(),
425
- units = self.controller_units)
526
+ units=self.axis_unit)
426
527
  self.emit_status(ThreadCommand('outofbounds', []))
427
528
  elif position < self.settings.child('bounds', 'min_bound').value():
428
529
  position = DataActuator(self._title,
429
530
  data=self.settings.child('bounds', 'min_bound').value(),
430
- units=self.controller_units
531
+ units=self.axis_unit
431
532
  )
432
533
  self.emit_status(ThreadCommand('outofbounds', []))
433
534
  return position
@@ -496,13 +597,13 @@ class DAQ_Move_base(QObject):
496
597
  if position is None:
497
598
  if self.data_actuator_type.name == 'float':
498
599
  position = DataActuator(self._title, data=self.get_actuator_value(),
499
- units = self.controller_units)
600
+ units=self.axis_unit)
500
601
  else:
501
602
  position = self.get_actuator_value()
502
603
  if position.name != self._title: # make sure the emitted DataActuator has the name of the real implementation
503
604
  #of the plugin
504
605
  position = DataActuator(self._title, data=position.value(),
505
- units = self.controller_units)
606
+ units=self.axis_unit)
506
607
  self.move_done_signal.emit(position)
507
608
  self.move_is_done = True
508
609
 
@@ -520,32 +621,59 @@ class DAQ_Move_base(QObject):
520
621
  else:
521
622
  if self.data_actuator_type == DataActuatorType.float:
522
623
  self._current_value = DataActuator(data=self.get_actuator_value(),
523
- units=self.controller_units)
624
+ units=self.axis_unit)
524
625
  else:
525
626
  self._current_value = self.get_actuator_value()
526
- if (not Unit(self.controller_units).is_compatible_with(
627
+ if (not Unit(self.axis_unit).is_compatible_with(
527
628
  Unit(self._current_value.units)) and
528
629
  self._current_value.units == ''):
529
630
  # this happens if the units have not been specified in
530
631
  # the plugin
531
- self._current_value.force_units(self.controller_units)
632
+ self._current_value.force_units(self.axis_unit)
532
633
 
533
634
  logger.debug(f'Current position: {self._current_value}')
534
635
  self.move_done(self._current_value)
535
636
 
536
- def check_target_reached(self):
537
- logger.debug(f"epsilon value is {self.settings['epsilon']}")
538
- logger.debug(f"current_value value is {self._current_value}")
539
- logger.debug(f"target_value value is {self._target_value}")
637
+ def _condition_to_reach_target(self) -> bool:
638
+ """ Implement the condition for exiting the polling mechanism and specifying that the
639
+ target value has been reached
540
640
 
641
+ Returns
642
+ -------
643
+ bool: if True, PyMoDAQ considers the target value has been reached at epsilon
644
+
645
+ See Also
646
+ --------
647
+ user_condition_to_reach_target
648
+ """
541
649
  try:
542
- epsilon_calculated = (self._current_value - self._target_value).abs()
650
+ epsilon_calculated = (
651
+ self._current_value - self._target_value).abs().value(self.axis_unit)
543
652
  except DataUnitError as e:
544
653
  epsilon_calculated = abs(self._current_value.value() - self._target_value.value())
545
- logger.warning(f'Unit issue when calculating epsilon, units are not the same between'
654
+ logger.warning(f'Unit issue when calculating epsilon, units are not compatible between'
546
655
  f'target and current values')
547
656
 
548
- if not epsilon_calculated < self.settings['epsilon']:
657
+ return (epsilon_calculated < self.epsilon) and self.user_condition_to_reach_target()
658
+
659
+ def user_condition_to_reach_target(self) -> bool:
660
+ """ Implement a user defined condition for exiting the polling mechanism and specifying
661
+ that the target value has been reached (on top of the existing epsilon mechanism)
662
+
663
+ Should be reimplemented in plugins to implement other conditions
664
+
665
+ Returns
666
+ -------
667
+ bool: if True, PyMoDAQ considers the target value has been reached
668
+ """
669
+ return True
670
+
671
+ def check_target_reached(self):
672
+ logger.debug(f"epsilon value is {self.epsilon}")
673
+ logger.debug(f"current_value value is {self._current_value}")
674
+ logger.debug(f"target_value value is {self._target_value}")
675
+
676
+ if not self._condition_to_reach_target():
549
677
 
550
678
  logger.debug(f'Check move_is_done: {self.move_is_done}')
551
679
  if self.move_is_done:
@@ -641,6 +769,11 @@ class DAQ_Move_base(QObject):
641
769
  self.commit_common_settings(param)
642
770
  self.commit_settings(param)
643
771
 
772
+ if param.name() == 'axis':
773
+ self.axis_name = param.value()
774
+ elif param.name() == 'epsilon':
775
+ self.epsilon = param.value()
776
+
644
777
 
645
778
  class DAQ_Move_TCP_server(DAQ_Move_base, TCPServer):
646
779
  """
@@ -22,6 +22,8 @@ class ParameterEx(ParameterManager):
22
22
  {'title': 'Numbers:', 'name': 'numbers', 'type': 'group', 'children': [
23
23
  {'title': 'Standard float', 'name': 'afloat', 'type': 'float', 'value': 20., 'min': 1.,
24
24
  'tip': 'displays this text as a tooltip'},
25
+ {'title': 'Standard float with Si prefix', 'name': 'afloatprefix', 'type': 'float', 'value': 20.,
26
+ 'tip': 'displays this text as a tooltip', 'siPrefix': True, 'suffix': 'V'},
25
27
  {'title': 'Linear Slide float', 'name': 'linearslidefloat', 'type': 'slide', 'value': 50, 'default': 50,
26
28
  'min': 0,
27
29
  'max': 123, 'subtype': 'linear'},
pymodaq/resources/VERSION CHANGED
@@ -1,2 +1,2 @@
1
- version = '4.3.6'
1
+ version = '4.4.0'
2
2
 
pymodaq/utils/config.py CHANGED
@@ -20,7 +20,7 @@ except:
20
20
  USER = 'unknown_user'
21
21
 
22
22
  CONFIG_BASE_PATH = Path(environ['PROGRAMDATA']) if sys.platform == 'win32' else \
23
- Path('/Library/Application Support') if sys.platform == 'darwin' else Path('/etc')
23
+ Path('Library/Application Support') if sys.platform == 'darwin' else Path('/etc')
24
24
 
25
25
 
26
26
  KeyType = TypeVar('KeyType')
pymodaq/utils/data.py CHANGED
@@ -2413,21 +2413,48 @@ class DataActuator(DataRaw):
2413
2413
  else:
2414
2414
  return f'<{self.__class__.__name__} ({self.shape} {self.units})>'
2415
2415
 
2416
- def value(self) -> float:
2416
+ def value(self, units: str = None) -> float:
2417
2417
  """Returns the underlying float value (of the first elt in the data list) if this data
2418
- holds only a float otherwise returns a mean of the underlying data"""
2418
+ holds only a float otherwise returns a mean of the underlying data
2419
+
2420
+ Parameters
2421
+ ----------
2422
+
2423
+ units: str
2424
+ if unit is compatible with self.units, convert the data to these new units before
2425
+ getting the value
2426
+
2427
+
2428
+ """
2419
2429
  if self.length == 1 and self.size == 1:
2420
- return float(self.data[0][0])
2430
+ if units is not None:
2431
+ data = Q_(float(self.data[0][0]), self.units)
2432
+ return data.m_as(units)
2433
+ else:
2434
+ return float(self.data[0][0])
2421
2435
  else:
2422
- return float(np.mean(self.data))
2436
+ if units is not None:
2437
+ data = Q_(float(np.mean(self.data[0])), self.units)
2438
+ return data.m_as(units)
2439
+ else:
2440
+ return float(np.mean(self.data[0]))
2423
2441
 
2424
- def values(self) -> List[float]:
2442
+ def values(self, units: str = None) -> List[float]:
2425
2443
  """Returns the underlying float value (for each data array in the data list) if this data
2426
2444
  holds only a float otherwise returns a mean of the underlying data"""
2427
2445
  if self.length == 1 and self.size == 1:
2428
- return [float(data_array[0]) for data_array in self.data]
2446
+ if units is not None:
2447
+ return [float(Q_(data_array[0], self.units).m_as(units))
2448
+ for data_array in self.data]
2449
+ else:
2450
+ return [float(data_array[0])
2451
+ for data_array in self.data]
2429
2452
  else:
2430
- return [float(np.mean(data_array)) for data_array in self.data]
2453
+ if units is not None:
2454
+ return [float(Q_(np.mean(data_array), self.units).m_as(units))
2455
+ for data_array in self.data]
2456
+ else:
2457
+ return [float(np.mean(data_array)) for data_array in self.data]
2431
2458
 
2432
2459
 
2433
2460
  class DataFromPlugins(DataRaw):
@@ -12,7 +12,7 @@ def load_dashboard_with_preset(preset_name: str, extension_name: str):
12
12
  area = DockArea()
13
13
  win.setCentralWidget(area)
14
14
  win.resize(1000, 500)
15
- win.setWindowTitle('PyMoDAQ Dashboard')
15
+ win.setWindowTitle('extension_name')
16
16
  win.show()
17
17
 
18
18
  # win.setVisible(False)
@@ -420,6 +420,7 @@ class Serializer:
420
420
  * serialize the string type: 'DataWithAxes'
421
421
  * serialize the timestamp: float
422
422
  * serialize the name
423
+ * serialize the units
423
424
  * serialize the source enum as a string
424
425
  * serialize the dim enum as a string
425
426
  * serialize the distribution enum as a string
@@ -439,6 +440,7 @@ class Serializer:
439
440
  bytes_string += self.object_type_serialization(dwa)
440
441
  bytes_string += self.scalar_serialization(dwa.timestamp)
441
442
  bytes_string += self.string_serialization(dwa.name)
443
+ bytes_string += self.string_serialization(dwa.units)
442
444
  bytes_string += self.string_serialization(dwa.source.name)
443
445
  bytes_string += self.string_serialization(dwa.dim.name)
444
446
  bytes_string += self.string_serialization(dwa.distribution.name)
@@ -731,9 +733,11 @@ class DeSerializer:
731
733
  """
732
734
  class_name = self.string_deserialization()
733
735
  if class_name not in DwaType.names():
734
- raise TypeError(f'Attempting to deserialize a DataWithAxes flavor but got the bytes for a {class_name}')
736
+ raise TypeError(f'Attempting to deserialize a DataWithAxes '
737
+ f'flavor but got the bytes for a {class_name}')
735
738
  timestamp = self.scalar_deserialization()
736
739
  dwa = getattr(data_mod, class_name)(self.string_deserialization(),
740
+ units=self.string_deserialization(),
737
741
  source=self.string_deserialization(),
738
742
  dim=self.string_deserialization(),
739
743
  distribution=self.string_deserialization(),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pymodaq
3
- Version: 4.3.6
3
+ Version: 4.4.0
4
4
  Summary: Modular Data Acquisition with Python
5
5
  Project-URL: Homepage, http://pymodaq.cnrs.fr
6
6
  Project-URL: Source, https://github.com/PyMoDAQ/PyMoDAQ
@@ -77,7 +77,7 @@ PyMoDAQ
77
77
  :target: https://pymodaq.readthedocs.io/en/stable/?badge=latest
78
78
  :alt: Documentation Status
79
79
 
80
- .. image:: https://codecov.io/gh/PyMoDAQ/PyMoDAQ/branch/pymodaq-dev/graph/badge.svg?token=IQNJRCQDM2
80
+ .. image:: https://codecov.io/gh/PyMoDAQ/PyMoDAQ/branch/4.4.x/graph/badge.svg?token=IQNJRCQDM2
81
81
  :target: https://codecov.io/gh/PyMoDAQ/PyMoDAQ
82
82
 
83
83
  ====== ========== ======= ======
@@ -93,25 +93,25 @@ Python Qt Backend OS Passed
93
93
  ====== ========== ======= ======
94
94
 
95
95
 
96
- .. |38Qt5| image:: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp38pyqt5.yml/badge.svg?branch=4.3.x_dev
96
+ .. |38Qt5| image:: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp38pyqt5.yml/badge.svg?branch=4.4.x
97
97
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp38pyqt5.yml
98
98
 
99
- .. |39Qt5| image:: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp39pyqt5.yml/badge.svg?branch=4.3.x_dev
99
+ .. |39Qt5| image:: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp39pyqt5.yml/badge.svg?branch=4.4.x
100
100
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp39pyqt5.yml
101
101
 
102
- .. |310Qt5| image:: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp310pyqt5.yml/badge.svg?branch=4.3.x_dev
102
+ .. |310Qt5| image:: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp310pyqt5.yml/badge.svg?branch=4.4.x
103
103
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp310pyqt5.yml
104
104
 
105
- .. |311Qt5| image:: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp311pyqt5.yml/badge.svg?branch=4.3.x_dev
105
+ .. |311Qt5| image:: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp311pyqt5.yml/badge.svg?branch=4.4.x
106
106
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp311pyqt5.yml
107
107
 
108
- .. |38Qt5win| image:: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp38pyqt5_win.yml/badge.svg?branch=4.3.x_dev
108
+ .. |38Qt5win| image:: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp38pyqt5_win.yml/badge.svg?branch=4.4.x
109
109
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp38pyqt5_win.yml
110
110
 
111
- .. |38pyside| image:: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp38pyside2.yml/badge.svg?branch=4.3.x_dev
111
+ .. |38pyside| image:: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp38pyside2.yml/badge.svg?branch=4.4.x
112
112
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp38pyside2.yml
113
113
 
114
- .. |39Qt6| image:: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp39pyqt6.yml/badge.svg?branch=4.3.x_dev
114
+ .. |39Qt6| image:: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp39pyqt6.yml/badge.svg?branch=4.4.x
115
115
  :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/Testp39pyqt6.yml
116
116
 
117
117
 
@@ -1,14 +1,14 @@
1
- pymodaq/__init__.py,sha256=boHjxPnKEAQUmOHKbnxlI8DHP1eM5IKSrrnd1EqkvHg,4295
1
+ pymodaq/__init__.py,sha256=e4pcKgxrhCVtCuq3t-gcfkwCWtV72K6jhPa-0zAtRfs,4382
2
2
  pymodaq/dashboard.py,sha256=4fbV92erom0yWwqPMtx3KW1q-d6QYflV-EhOZMg24a4,64476
3
3
  pymodaq/icon.ico,sha256=hOHHfNDENKphQvG1WDleSEYcHukneR2eRFJu8isIlD4,74359
4
4
  pymodaq/splash.png,sha256=ow8IECF3tPRUMA4tf2tMu1aRiMaxx91_Y2ckVxkrmF0,53114
5
5
  pymodaq/control_modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- pymodaq/control_modules/daq_move.py,sha256=dq29GbNf-8BpquR3z8ZuqvKaP-0p14LLrAAVg75pIis,36590
7
- pymodaq/control_modules/daq_move_ui.py,sha256=euA8_utFfiscd9ahZxRTqqTiCSrNB4CKCLqgdbUKSlI,15307
6
+ pymodaq/control_modules/daq_move.py,sha256=L4lW_VfdBKmrlpLkKwbNitDeLHbFWk6Lbq4T5k-6xiM,37323
7
+ pymodaq/control_modules/daq_move_ui.py,sha256=IbqNAErwXGjKUbYEptvZUz3J8MapNBFIbQnUf9nQrMw,15753
8
8
  pymodaq/control_modules/daq_viewer.py,sha256=5CYmdWHGE7sQApeMfdWNV3zbPyoxxYtzFlQ1PaEw4fI,57883
9
9
  pymodaq/control_modules/daq_viewer_ui.py,sha256=FWP3jdIOR9vTgYqNaaodteGZ3dwgQ1GdWKrOpOAuSrs,15693
10
10
  pymodaq/control_modules/mocks.py,sha256=hh_xSWp9g1UV3NAQVD9Ft9tNWfTsSvKU0OU0trgzP2w,1956
11
- pymodaq/control_modules/move_utility_classes.py,sha256=u4GLi7SUqwJrw9etw-U33UnsW64TgCy2VCaJ0WkTsnI,35442
11
+ pymodaq/control_modules/move_utility_classes.py,sha256=LUO7kGb-1iQOP_6Wvp46xnx5Z9wNxUhRzOfz9cfXH-s,39664
12
12
  pymodaq/control_modules/utils.py,sha256=5YdSwq_lFJm7IalYWe_Hn1U4LUoUmo1gedvV9UguU0Y,20016
13
13
  pymodaq/control_modules/viewer_utility_classes.py,sha256=OHxwue1t3z2AXyeqNjnwPT2pMc8yXhnqyiWc9IdCI2c,26841
14
14
  pymodaq/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -16,7 +16,7 @@ pymodaq/examples/custom_app.py,sha256=2wQR0hlPWjZrWK0abNF6ASv8iQyJqRn2CKnBa_nAgN
16
16
  pymodaq/examples/custom_viewer.py,sha256=nUj5n6l7DSyh-qaXboNBfXKa9mAiXrHSiOcuFL1ayRE,4814
17
17
  pymodaq/examples/function_plotter.py,sha256=T-VT0Rd3jHP9GcR2h6Nao6lwZE06P8zWUbOlz8b8Rxk,6104
18
18
  pymodaq/examples/nonlinearscanner.py,sha256=x0R2_FP0YnuOCCAmYRiAiZ1jfUdRxu5RqIYLyGQMZ0U,3790
19
- pymodaq/examples/parameter_ex.py,sha256=AVzs9aeZFe3U5SkFvC9VZuRZPoB-UMCxYYEnFH6if40,8357
19
+ pymodaq/examples/parameter_ex.py,sha256=NmFUvUByNtm3j4leN_MkufQsKlNU8Rx5lmpsVG58IIM,8556
20
20
  pymodaq/examples/preset_MockCamera.xml,sha256=quQlMsX6YSoqqc9_9Y-9zu3TDM6Xvnuc2JSWwg9f948,15774
21
21
  pymodaq/examples/tcp_client.py,sha256=FSdPlb3R_rxxNIqPqHVU8PxJzNZeFk_93l4TqsB5SnA,2584
22
22
  pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Client.aliases,sha256=t0eKH9Uq_AMk4wQ-6Pm5mKUjGcCvfT9GtvMsvDhkCUk,47
@@ -58,7 +58,7 @@ pymodaq/post_treatment/daq_measurement/daq_measurement_GUI.py,sha256=1u7hWDaiwsZ
58
58
  pymodaq/post_treatment/daq_measurement/daq_measurement_GUI.ui,sha256=PyzbCWPMkh5oIYYteZczXyWMeHKW9EJmM1QlzXhnyTk,7037
59
59
  pymodaq/post_treatment/daq_measurement/daq_measurement_main.py,sha256=CAKwcWMOD86aXB8mbdxOK7e8nZRos5d59FzDtqK1QoY,17093
60
60
  pymodaq/post_treatment/daq_measurement/process_from_QtDesigner_DAQ_Measurement_GUI.bat,sha256=e1tu2A67MS9fk3jhriF6saQgRxWIucIvNW92iWXFP6E,164
61
- pymodaq/resources/VERSION,sha256=tropgff0ZsS5bHlAJDJj5zie4H5gkkIz3VojWABtDdI,19
61
+ pymodaq/resources/VERSION,sha256=x4JbXmXiNnoebdA-11hkfcpy5BKE5mBMlysMVDo9SRw,19
62
62
  pymodaq/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
63
  pymodaq/resources/config_template.toml,sha256=d3pofgIK5FxaRMELAI1qEsRcMD3GlYd87zZjDj9G9m0,3210
64
64
  pymodaq/resources/preset_default.xml,sha256=Dt8iWLwPPOPtcG00JCVP-mh-G7KC6B0YN8hd8RQdnNI,27256
@@ -308,10 +308,10 @@ pymodaq/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
308
308
  pymodaq/utils/array_manipulation.py,sha256=uMdiVVR2mU7j6Z4DKL5VGhUPqiWvFX2YK7RLMGdLyC8,10415
309
309
  pymodaq/utils/calibration_camera.py,sha256=d3aAu0izXOdeLs-vyBaFfBuvyDGT3O-SreTuyd0Kvs4,8921
310
310
  pymodaq/utils/chrono_timer.py,sha256=rwX8apS8B-IKhA0Cp2H9tLz0BRN7G3Pg5ptozvd3MKM,7244
311
- pymodaq/utils/config.py,sha256=u-Q4af-tgXG7h8YitMGSPkigDxGoOYSEMHERQCF9mAM,16203
311
+ pymodaq/utils/config.py,sha256=0QqoBJC4ECuIeh1UsvUQqhxkKl7Vfgi4iERp-6qNWAc,16202
312
312
  pymodaq/utils/conftests.py,sha256=3Ak8WEpa3EhAp73Yb1LLq8YFONhPqiL7gG9eSDIoTNc,58
313
313
  pymodaq/utils/daq_utils.py,sha256=0jTrbT0aaZr3KaTgeDicmK9FbVnu3iaWBmNHnNJpr3A,28050
314
- pymodaq/utils/data.py,sha256=QPPFs57Eu1cEvF_QYJamjrPmickBVgsggg_bSkbd0P0,110621
314
+ pymodaq/utils/data.py,sha256=V4F_H-sUPmCwrcNDuF7JxzcJtnOlQX7fChSoyXdd6ng,111572
315
315
  pymodaq/utils/enums.py,sha256=wpRipioUJkKcEfoaY2NrDQ2WhGxZTZiZoJty5f2Ljpc,2236
316
316
  pymodaq/utils/exceptions.py,sha256=wLO6VlofzfwWkOOWMN2B-3NEWMfpgygyeEdakIx_rAs,668
317
317
  pymodaq/utils/factory.py,sha256=QLqAPFnTZ93eUpmAAIr7kESDk2enD57RNSuFUsjxE4E,2311
@@ -333,7 +333,7 @@ pymodaq/utils/gui_utils/dock.py,sha256=6AcxC9HqM45bt20vJgP6QgGAhkW8mynuOuWhL7gep
333
333
  pymodaq/utils/gui_utils/file_io.py,sha256=mJI_30p986rLZk44FsPjesMazE1tq7HEFLenW-dnmOI,3367
334
334
  pymodaq/utils/gui_utils/layout.py,sha256=6oczLLGwwEN4EQ8yUDnz0-4Ue2wlyCRklKsVD1GNcz8,1099
335
335
  pymodaq/utils/gui_utils/list_picker.py,sha256=ddYnRTlRlgwdJSy0Q98IzYWHzIf2GS6ABl8XSS9kVXM,1190
336
- pymodaq/utils/gui_utils/loader_utils.py,sha256=7sfAvktORgF1DAcYCHsWrl57koaO8v4D4UCqXgUPxbU,1301
336
+ pymodaq/utils/gui_utils/loader_utils.py,sha256=Gg0d31fjkqDq3l1WuRMbLzKiKPOIYdbUrcfGOgoXRk0,1298
337
337
  pymodaq/utils/gui_utils/utils.py,sha256=aPxFCnG4skDp-0UuhAudq6BPZnEXoAf6FOEJyrhUjx0,6089
338
338
  pymodaq/utils/gui_utils/widgets/__init__.py,sha256=LThGzmbFKbp2FtTTF_L7pHjyBzfB7F_bhMF4rPTwrUY,195
339
339
  pymodaq/utils/gui_utils/widgets/label.py,sha256=C2MU8i_Yy_oVRW7yal_ghB1Y5Bj_a9o8IFZWW3br-KM,600
@@ -436,10 +436,10 @@ pymodaq/utils/svg/svg_view.py,sha256=bmXpDqnw9S-Bp3F8Hi_oeYB5Y9gebiCNsQWVJzCq-PA
436
436
  pymodaq/utils/svg/svg_viewer2D.py,sha256=LTJ3Ulb5zWXdRPr7vqcWumbpq7ZctzrYUMtD5QV3x60,1523
437
437
  pymodaq/utils/tcp_ip/__init__.py,sha256=1e_EK0AgvdoLAD_CSGGEaITZdy6OWCO7ih9IAIp7HT4,81
438
438
  pymodaq/utils/tcp_ip/mysocket.py,sha256=StAWj8dzHeMnbLj68Sel81uWFy-YkKVNRnVf7gXrESI,3452
439
- pymodaq/utils/tcp_ip/serializer.py,sha256=oTQ24JNln_vRX4YADitTYiJplwFIdsta1ZDp6TOGMCA,27562
439
+ pymodaq/utils/tcp_ip/serializer.py,sha256=htVQCE4saRBMeIcseEyxTt5G58A341m6OGkaJUA34Wk,27766
440
440
  pymodaq/utils/tcp_ip/tcp_server_client.py,sha256=xIMTNgVW_rKK0yTi4FDNFLf85-Akb27Jz2LdrvOrP68,30660
441
- pymodaq-4.3.6.dist-info/METADATA,sha256=Que8dxytqKUDEfyJnBVsvfu_0CbQOtOUM2Va17J7JyU,7657
442
- pymodaq-4.3.6.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
443
- pymodaq-4.3.6.dist-info/entry_points.txt,sha256=RAzdYNjvUT28I2eiCKki_g2NzXq0woWxhev6lwzwRv8,348
444
- pymodaq-4.3.6.dist-info/licenses/LICENSE,sha256=VKOejxexXAe3XwfhAhcFGqeXQ12irxVHdeAojZwFEI8,1108
445
- pymodaq-4.3.6.dist-info/RECORD,,
441
+ pymodaq-4.4.0.dist-info/METADATA,sha256=PGjrsAiJMph8il3hZKb9aJDvUShYG5CUrEYgTPsY8is,7623
442
+ pymodaq-4.4.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
443
+ pymodaq-4.4.0.dist-info/entry_points.txt,sha256=RAzdYNjvUT28I2eiCKki_g2NzXq0woWxhev6lwzwRv8,348
444
+ pymodaq-4.4.0.dist-info/licenses/LICENSE,sha256=VKOejxexXAe3XwfhAhcFGqeXQ12irxVHdeAojZwFEI8,1108
445
+ pymodaq-4.4.0.dist-info/RECORD,,