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

@@ -595,14 +595,33 @@ class DAQ_Move(ParameterControlModule):
595
595
  def units(self, unit: str):
596
596
  self.settings.child('move_settings', 'units').setValue(unit)
597
597
  if self.ui is not None and config('actuator', 'display_units'):
598
- if unit == '°' or unit == 'degree':
599
- # special cas as pint base unit for angles are radians
600
- self.ui.set_unit_as_suffix('°')
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_reduced_units().units))
598
+ self.ui.set_unit_as_suffix(self.get_unit_to_display(unit))
599
+
600
+ @staticmethod
601
+ def get_unit_to_display(unit: str) -> str:
602
+ """ Get the unit to be displayed in the UI
603
+
604
+ If the controller units are in mm the displayed unit will be m
605
+ because m is the base unit, then the user could ask for mm, km, µm...
606
+ only issue is when the usual displayed unit is not the base one, then add cases below
607
+
608
+ Parameters
609
+ ----------
610
+ unit: str
611
+
612
+ Returns
613
+ -------
614
+ str: the unit to be displayed on the ui
615
+ """
616
+ if ('°' in unit or 'degree' in unit) and not '°C' in unit:
617
+ # special cas as pint base unit for angles are radians
618
+ return '°'
619
+ elif 'W' in unit or 'watt' in unit:
620
+ return 'W'
621
+ elif '°C' in unit or 'Celsius' in unit:
622
+ return '°C'
623
+ else:
624
+ return str(Q_(1, unit).to_base_units().units)
606
625
 
607
626
  def update_settings(self):
608
627
 
@@ -25,6 +25,7 @@ from pymodaq.utils.enums import BaseEnum, enum_checker
25
25
  from pymodaq.utils.tcp_ip.mysocket import Socket
26
26
  from pymodaq.utils.tcp_ip.serializer import DeSerializer, Serializer
27
27
  from pymodaq import Unit
28
+ from pint.errors import OffsetUnitCalculusError
28
29
 
29
30
  if TYPE_CHECKING:
30
31
  from pymodaq.control_modules.daq_move import DAQ_Move_Hardware
@@ -437,7 +438,8 @@ class DAQ_Move_base(QObject):
437
438
  QtWidgets.QApplication.processEvents()
438
439
  self.axis_unit = self.axis_unit
439
440
  self.settings.child('epsilon').setValue(self.epsilon)
440
-
441
+ if self.controller is not None:
442
+ self._current_value = self.get_actuator_value()
441
443
 
442
444
  @property
443
445
  def axis_names(self) -> Union[List, Dict]:
@@ -728,6 +730,13 @@ class DAQ_Move_base(QObject):
728
730
  epsilon_calculated = abs(self._current_value.value() - self._target_value.value())
729
731
  logger.warning(f'Unit issue when calculating epsilon, units are not compatible between'
730
732
  f'target and current values')
733
+ except OffsetUnitCalculusError as e:
734
+ if '°C' in self.axis_unit or 'celcius' in self.axis_unit.lower():
735
+ epsilon_calculated = (
736
+ self._current_value.to_base_units() -
737
+ self._target_value.to_base_units()).abs().value()
738
+ else:
739
+ raise e
731
740
 
732
741
  return (epsilon_calculated < self.epsilon) and self.user_condition_to_reach_target()
733
742
 
@@ -0,0 +1,128 @@
1
+ """
2
+ Example how to create an actuator or detector module, which does not require Qt, nor any GUI functionality.
3
+
4
+ You can connect to this qtless module with a PyMoDAQ LECODirector module (the detector or actuator version, both are preinstalled),
5
+ as if it were any pymodaq module.
6
+
7
+ This example works best with an Actuator Director Module as it has fake movements, but does not return any detector value.
8
+ In this example, the name is "qt_less" (defined in the final if clause), which you have to give as the "actor" argument to the Director module.
9
+
10
+ Add any code in the methods defined below, for example instrument access and execute the file.
11
+ For remote control, you need to start a Coordinator, as described for remote control via LECO.
12
+ """
13
+
14
+ import logging
15
+ from time import sleep
16
+ from typing import List, Union
17
+
18
+ from pyleco.utils.listener import Listener
19
+
20
+
21
+ class QtLessModule:
22
+ """Some module doing things without Qt.
23
+
24
+ You can run an instance of this class anywhere in your LECO network.
25
+ Then you can control this instance with a PyMoDAQ LECODirectorModule (in mock modules) as if it were a PyMoDAQ module.
26
+
27
+ Just add any logic you wish to the methods below.
28
+ """
29
+
30
+ def __init__(self, name: str, host: str = "localhost", **kwargs) -> None:
31
+ super().__init__()
32
+ self.listener = Listener(name=name, host=host, timeout=1, **kwargs)
33
+ self._fake_position = 0
34
+ self.start_listen()
35
+ self._stored = []
36
+
37
+ def start_listen(self) -> None:
38
+ """Start to listen on incoming commands."""
39
+ self.listener.start_listen()
40
+ self.communicator = self.listener.get_communicator()
41
+ self.register_rpc_methods()
42
+
43
+ def register_rpc_methods(self) -> None:
44
+ """Make the following methods available via LECO."""
45
+ register_rpc_method = self.communicator.register_rpc_method
46
+ register_rpc_method(self.set_info)
47
+ register_rpc_method(self.send_data)
48
+ register_rpc_method(self.move_abs)
49
+ register_rpc_method(self.move_rel)
50
+ register_rpc_method(self.move_home)
51
+ register_rpc_method(self.get_actuator_value)
52
+ register_rpc_method(self.stop_motion)
53
+ register_rpc_method(self.set_remote_name)
54
+
55
+ def stop_listen(self) -> None:
56
+ """Stop to listen on incoming commands."""
57
+ self.listener.stop_listen()
58
+
59
+ # smethods for being remote controlled
60
+ # these methods are executed and cannot talk to the controlling module directly.
61
+ # if you need to send a response (for example with a value) you have to store the information and
62
+ # send it after these methods have been executed.
63
+ def set_remote_name(self, name: str) -> None:
64
+ """Define what the name of the remote for answers is."""
65
+ self.remote_name = name
66
+
67
+ # generic commands
68
+ def set_info(self, path: List[str], param_dict_str: str) -> None:
69
+ print("set_info", path, param_dict_str)
70
+
71
+ # detector commands
72
+ def send_data(self, grabber_type: str = "") -> None:
73
+ print("send_data")
74
+
75
+ # actuator commands
76
+ def move_abs(self, position: Union[float, str]) -> None:
77
+ print("move_abs", position)
78
+ self._fake_position = float(position)
79
+
80
+ def move_rel(self, position: Union[float, str]) -> None:
81
+ print("move_rel", position)
82
+ self._fake_position += float(position)
83
+
84
+ def move_home(self) -> None:
85
+ self._fake_position = 0
86
+ print("move_home")
87
+
88
+ def get_actuator_value(self) -> None:
89
+ """Request that the actuator value is sent later on."""
90
+ # according to DAQ_Move, this supersedes "check_position"
91
+ print("get_actuator_value")
92
+ # send the actuator position after this method has finished execution.
93
+ # this method sends the result to the controlling control module.
94
+ self.send_later(
95
+ receiver=self.remote_name,
96
+ method="set_position",
97
+ position=self._fake_position,
98
+ )
99
+
100
+ def stop_motion(self,) -> None:
101
+ # not implemented in DAQ_Move!
102
+ print("stop_motion")
103
+
104
+ # end of methods for being remote controlled
105
+
106
+ def send_later(self, receiver, method, **kwargs):
107
+ """Store information to send it later."""
108
+ self._stored.append((receiver, method, kwargs))
109
+
110
+ def send_stored(self):
111
+ """Send messages stored for later sending."""
112
+ while self._stored:
113
+ receiver, method, kwargs = self._stored.pop()
114
+ self.communicator.ask_rpc(receiver=receiver, method=method, **kwargs)
115
+
116
+
117
+ if __name__ == "__main__":
118
+ print("listening endlessly as 'qt_less'")
119
+ log = logging.getLogger()
120
+ log.addHandler(logging.StreamHandler())
121
+ # log.setLevel(logging.DEBUG)
122
+ m = QtLessModule("qt_less")
123
+ try:
124
+ while True:
125
+ sleep(0.1)
126
+ m.send_stored()
127
+ except KeyboardInterrupt:
128
+ m.stop_listen()
pymodaq/resources/VERSION CHANGED
@@ -1,2 +1,2 @@
1
- version = '4.4.5'
1
+ version = '4.4.7'
2
2
 
@@ -23,6 +23,7 @@ def setup(path: Path):
23
23
  name=PLUGIN_NAME,
24
24
  description=config['plugin-info']['description'],
25
25
  long_description=long_description,
26
+ long_description_content_type='text/x-rst',
26
27
  license=config['plugin-info']['license'],
27
28
  url=config['plugin-info']['package-url'],
28
29
  author=config['plugin-info']['author'],
pymodaq/utils/data.py CHANGED
@@ -98,6 +98,7 @@ class DataUnitError(Exception):
98
98
 
99
99
 
100
100
  class DwaType(BaseEnum):
101
+ """Different types of `DataWithAxes`."""
101
102
  DataWithAxes = 0
102
103
  DataRaw = 1
103
104
  DataActuator = 2
@@ -648,6 +649,13 @@ class DataBase(DataLowLevel):
648
649
  """ Change immediately the units to whatever else. Use this with care!"""
649
650
  self._units = units
650
651
 
652
+ def to_base_units(self):
653
+ dwa = self.deepcopy()
654
+ data_quantities = [quantity.to_base_units() for quantity in self.quantities]
655
+ dwa.data = [quantity.magnitude for quantity in data_quantities]
656
+ dwa.force_units(str(data_quantities[0].units))
657
+ return dwa
658
+
651
659
  def units_as(self, units: str, inplace=True) -> 'DataBase':
652
660
  """ Set the object units to the new one (if possible)
653
661
 
@@ -55,11 +55,14 @@ class DAQ_Move_LECODirector(LECODirector, DAQ_Move_base):
55
55
  params_state=params_state, **kwargs)
56
56
  self.register_rpc_methods((
57
57
  self.set_info,
58
+ ))
59
+ for method in (
58
60
  self.set_position,
59
61
  self.set_move_done,
60
62
  self.set_x_axis,
61
63
  self.set_y_axis,
62
- ))
64
+ ):
65
+ self.listener.register_binary_rpc_method(method, accept_binary_input=True)
63
66
 
64
67
  # copied, I think it is good:
65
68
  self.settings.child('bounds').hide()
@@ -143,22 +146,29 @@ class DAQ_Move_LECODirector(LECODirector, DAQ_Move_base):
143
146
  self.controller.stop_motion()
144
147
 
145
148
  # Methods accessible via remote calls
146
- def _set_position_value(self, position: Union[str, float]) -> DataActuator:
147
- if isinstance(position, str):
148
- deserializer = DeSerializer.from_b64_string(position)
149
- pos = deserializer.dwa_deserialization()
149
+ def _set_position_value(
150
+ self, position: Union[str, float, None], additional_payload=None
151
+ ) -> DataActuator:
152
+ if position:
153
+ if isinstance(position, str):
154
+ deserializer = DeSerializer.from_b64_string(position)
155
+ pos = deserializer.dwa_deserialization()
156
+ else:
157
+ pos = DataActuator(data=position)
158
+ elif additional_payload is not None:
159
+ pos = DeSerializer(additional_payload[0]).dwa_deserialization()
150
160
  else:
151
- pos = DataActuator(data=position)
161
+ raise ValueError("No position given")
152
162
  pos = self.get_position_with_scaling(pos) # type: ignore
153
163
  self._current_value = pos
154
164
  return pos
155
165
 
156
- def set_position(self, position: Union[str, float]) -> None:
157
- pos = self._set_position_value(position=position)
166
+ def set_position(self, position: Union[str, float, None], additional_payload=None) -> None:
167
+ pos = self._set_position_value(position=position, additional_payload=additional_payload)
158
168
  self.emit_status(ThreadCommand('get_actuator_value', [pos]))
159
169
 
160
- def set_move_done(self, position: Union[str, float]) -> None:
161
- pos = self._set_position_value(position=position)
170
+ def set_move_done(self, position: Union[str, float, None], additional_payload=None) -> None:
171
+ pos = self._set_position_value(position=position, additional_payload=additional_payload)
162
172
  self.emit_status(ThreadCommand('move_done', [pos]))
163
173
 
164
174
  def set_x_axis(self, data, label: str = "", units: str = "") -> None:
@@ -1,5 +1,5 @@
1
-
2
- from typing import Union
1
+ from __future__ import annotations
2
+ from typing import Optional, Union
3
3
 
4
4
  from easydict import EasyDict as edict
5
5
 
@@ -37,8 +37,11 @@ class DAQ_xDViewer_LECODirector(LECODirector, DAQ_Viewer_base):
37
37
  self.register_rpc_methods((
38
38
  self.set_x_axis,
39
39
  self.set_y_axis,
40
- self.set_data,
41
40
  ))
41
+ for method in (
42
+ self.set_data,
43
+ ):
44
+ self.listener.register_binary_rpc_method(method, accept_binary_input=True)
42
45
 
43
46
  self.client_type = "GRABBER"
44
47
  self.x_axis = None
@@ -150,7 +153,7 @@ class DAQ_xDViewer_LECODirector(LECODirector, DAQ_Viewer_base):
150
153
  self.y_axis = dict(data=data, label=label, units=units)
151
154
  self.emit_y_axis()
152
155
 
153
- def set_data(self, data: Union[list, str]) -> None:
156
+ def set_data(self, data: Union[list, str, None], additional_payload: Optional[list[bytes]]=None) -> None:
154
157
  """
155
158
  Set the grabbed data signal.
156
159
 
@@ -160,10 +163,12 @@ class DAQ_xDViewer_LECODirector(LECODirector, DAQ_Viewer_base):
160
163
  """
161
164
  if isinstance(data, str):
162
165
  deserializer = DeSerializer.from_b64_string(data)
163
- dte = deserializer.dte_deserialization()
164
- self.dte_signal.emit(dte)
166
+ elif additional_payload is not None:
167
+ deserializer = DeSerializer(additional_payload[0])
165
168
  else:
166
169
  raise NotImplementedError("Not implemented to set a list of values.")
170
+ dte = deserializer.dte_deserialization()
171
+ self.dte_signal.emit(dte)
167
172
 
168
173
 
169
174
  if __name__ == '__main__':
@@ -17,7 +17,7 @@ from qtpy.QtCore import QObject, Signal # type: ignore
17
17
  from pymodaq.utils.daq_utils import ThreadCommand
18
18
  from pymodaq.utils.parameter import ioxml
19
19
  from pymodaq.utils.tcp_ip.serializer import DataWithAxes, SERIALIZABLE, DeSerializer
20
- from pymodaq.utils.leco.utils import serialize_object
20
+ from pymodaq.utils.leco.utils import binary_serialization_to_kwargs
21
21
 
22
22
 
23
23
  class LECOClientCommands(StrEnum):
@@ -220,7 +220,7 @@ class ActorListener(PymodaqListener):
220
220
  self.communicator.ask_rpc(
221
221
  receiver=self.remote_name,
222
222
  method="set_data",
223
- data=serialize_object(value),
223
+ **binary_serialization_to_kwargs(value),
224
224
  )
225
225
 
226
226
  elif command.command == 'send_info':
@@ -236,14 +236,14 @@ class ActorListener(PymodaqListener):
236
236
  value = command.attribute[0] # type: ignore
237
237
  self.communicator.ask_rpc(receiver=self.remote_name,
238
238
  method="set_position",
239
- position=serialize_object(value),
239
+ **binary_serialization_to_kwargs(value, data_key="position"),
240
240
  )
241
241
 
242
242
  elif command.command == LECOMoveCommands.MOVE_DONE:
243
243
  value = command.attribute[0] # type: ignore
244
244
  self.communicator.ask_rpc(receiver=self.remote_name,
245
245
  method="set_move_done",
246
- position=serialize_object(value),
246
+ **binary_serialization_to_kwargs(value, data_key="position"),
247
247
  )
248
248
 
249
249
  elif command.command == 'x_axis':
@@ -251,7 +251,7 @@ class ActorListener(PymodaqListener):
251
251
  if isinstance(value, SERIALIZABLE):
252
252
  self.communicator.ask_rpc(receiver=self.remote_name,
253
253
  method="set_x_axis",
254
- data=serialize_object(value),
254
+ **binary_serialization_to_kwargs(value),
255
255
  )
256
256
  elif isinstance(value, dict):
257
257
  self.communicator.ask_rpc(receiver=self.remote_name, method="set_x_axis", **value)
@@ -263,7 +263,7 @@ class ActorListener(PymodaqListener):
263
263
  if isinstance(value, SERIALIZABLE):
264
264
  self.communicator.ask_rpc(receiver=self.remote_name,
265
265
  method="set_y_axis",
266
- data=serialize_object(value),
266
+ **binary_serialization_to_kwargs(value),
267
267
  )
268
268
  elif isinstance(value, dict):
269
269
  self.communicator.ask_rpc(receiver=self.remote_name, method="set_y_axis", **value)
@@ -1,6 +1,7 @@
1
+ from __future__ import annotations
1
2
  import subprocess
2
3
  import sys
3
- from typing import Any, Union, get_args
4
+ from typing import Any, Optional, Union, get_args
4
5
 
5
6
  # import also the DeSerializer for easier imports in dependents
6
7
  from pymodaq.utils.tcp_ip.serializer import SERIALIZABLE, Serializer, DeSerializer # type: ignore # noqa
@@ -23,6 +24,29 @@ def serialize_object(pymodaq_object: Union[SERIALIZABLE, Any]) -> Union[str, Any
23
24
  "JSON serializable, nor via PyMoDAQ.")
24
25
 
25
26
 
27
+ def binary_serialization(
28
+ pymodaq_object: Union[SERIALIZABLE, Any],
29
+ ) -> tuple[Optional[Any], Optional[list[bytes]]]:
30
+ """Serialize (binary) a pymodaq object, if it is not JSON compatible."""
31
+ if isinstance(pymodaq_object, get_args(JSON_TYPES)):
32
+ return pymodaq_object, None
33
+ elif isinstance(pymodaq_object, get_args(SERIALIZABLE)):
34
+ return None, [Serializer(pymodaq_object).to_bytes()]
35
+ else:
36
+ raise ValueError(
37
+ f"{pymodaq_object} of type '{type(pymodaq_object).__name__}' is neither "
38
+ "JSON serializable, nor via PyMoDAQ."
39
+ )
40
+
41
+
42
+ def binary_serialization_to_kwargs(
43
+ pymodaq_object: Union[SERIALIZABLE, Any], data_key: str = "data"
44
+ ) -> dict[str, Any]:
45
+ """Create a dictionary of data parameters and of additional payload to send."""
46
+ d, b = binary_serialization(pymodaq_object=pymodaq_object)
47
+ return {data_key: d, "additional_payload": b}
48
+
49
+
26
50
  def run_coordinator():
27
51
  command = [sys.executable, '-m', 'pyleco.coordinators.coordinator']
28
52
  subprocess.Popen(command)
@@ -17,7 +17,7 @@ logger = set_logger(get_module_name(__file__))
17
17
  remote_path = pymodaq.utils.config.get_set_remote_path()
18
18
  remote_types = ['ShortCut', 'Joystick']
19
19
 
20
- actuator_actions = ['move_Rel', 'move_Rel_p', 'move_Rel_m']
20
+ actuator_actions = ['move_rel', 'move_rel_p', 'move_rel_m']
21
21
  detector_actions = ['snap', 'grab', 'stop']
22
22
  remote_types = ['Keyboard', 'Joystick']
23
23
  try:
@@ -5,8 +5,9 @@ Created the 20/10/2023
5
5
  @author: Sebastien Weber
6
6
  """
7
7
  from base64 import b64encode, b64decode
8
+ from enum import Enum
8
9
  import numbers
9
- from typing import Tuple, List, Union, TYPE_CHECKING, Iterable
10
+ from typing import Optional, Tuple, List, Union, TYPE_CHECKING
10
11
 
11
12
 
12
13
  import numpy as np
@@ -20,17 +21,38 @@ if TYPE_CHECKING:
20
21
 
21
22
 
22
23
  SERIALIZABLE = Union[
23
- int,
24
+ # native
25
+ bool,
26
+ bytes,
24
27
  str,
25
- numbers.Number,
28
+ complex, # float and int are subtypes for type hinting
29
+ float,
30
+ int,
26
31
  list,
32
+ # numpy
27
33
  np.ndarray,
34
+ # pymodaq
28
35
  Axis,
29
36
  DataWithAxes,
30
37
  DataToExport,
38
+ putils.ParameterWithPath,
31
39
  ]
32
40
 
33
41
 
42
+ class SerializableTypes(Enum):
43
+ """Type names of serializable types"""
44
+ BOOL = "bool"
45
+ BYTES = "bytes"
46
+ STRING = "string"
47
+ SCALAR = "scalar"
48
+ LIST = "list"
49
+ ARRAY = "array"
50
+ AXIS = "axis"
51
+ DATA_WITH_AXES = "dwa"
52
+ DATA_TO_EXPORT = "dte"
53
+ PARAMETER = "parameter"
54
+
55
+
34
56
  class SocketString:
35
57
  """Mimic the Socket object but actually using a bytes string not a socket connection
36
58
 
@@ -86,7 +108,7 @@ class Serializer:
86
108
  """Used to Serialize to bytes python objects, numpy arrays and PyMoDAQ DataWithAxes and
87
109
  DataToExport objects"""
88
110
 
89
- def __init__(self, obj: SERIALIZABLE = None):
111
+ def __init__(self, obj: Optional[SERIALIZABLE] = None) -> None:
90
112
  self._bytes_string = b''
91
113
  self._obj = obj
92
114
 
@@ -154,7 +176,7 @@ class Serializer:
154
176
  return message.encode()
155
177
 
156
178
  @classmethod
157
- def str_len_to_bytes(cls, message: Union[str, bytes]) -> (bytes, bytes):
179
+ def str_len_to_bytes(cls, message: Union[str, bytes]) -> Tuple[bytes, bytes]:
158
180
  """ Convert a string and its length to two bytes
159
181
  Parameters
160
182
  ----------
@@ -174,7 +196,7 @@ class Serializer:
174
196
  return message, cls.int_to_bytes(len(message))
175
197
 
176
198
  def _int_serialization(self, int_obj: int) -> bytes:
177
- """serialize an unsigned integer used for getting the length of messages internaly, for outside integer
199
+ """Serialize an unsigned integer used for getting the length of messages internally, for outside integer
178
200
  serialization or deserialization use scalar_serialization"""
179
201
  int_bytes = self.int_to_bytes(int_obj)
180
202
  bytes_string = int_bytes
@@ -205,18 +227,19 @@ class Serializer:
205
227
  self._bytes_string += bytes_string
206
228
  return bytes_string
207
229
 
208
- def scalar_serialization(self, scalar: numbers.Number) -> bytes:
230
+ def scalar_serialization(self, scalar: complex) -> bytes:
209
231
  """ Convert a scalar into a bytes message together with the info to convert it back
210
232
 
211
233
  Parameters
212
234
  ----------
213
- scalar: str
235
+ scalar: A python number (complex or subtypes like float and int)
214
236
 
215
237
  Returns
216
238
  -------
217
239
  bytes: the total bytes message to serialize the scalar
218
240
  """
219
241
  if not isinstance(scalar, numbers.Number):
242
+ # type hint is complex, instance comparison Number
220
243
  raise TypeError(f'{scalar} should be an integer or a float, not a {type(scalar)}')
221
244
  scalar_array = np.array([scalar])
222
245
  data_type = scalar_array.dtype.descr[0][1]
@@ -350,49 +373,56 @@ class Serializer:
350
373
  self._bytes_string += bytes_string
351
374
  return bytes_string
352
375
 
353
- def type_and_object_serialization(self, obj):
376
+ def type_and_object_serialization(self, obj: Optional[SERIALIZABLE] = None) -> bytes:
377
+ """Serialize an object with its type, such that it can be retrieved by
378
+ `DeSerializer.type_and_object_deserialization`.
379
+ """
380
+
381
+ if obj is None and self._obj is not None:
382
+ obj = self._obj
383
+
354
384
  bytes_string = b''
355
385
  if isinstance(obj, DataWithAxes):
356
- bytes_string += self.string_serialization('dwa')
386
+ bytes_string += self.string_serialization(SerializableTypes.DATA_WITH_AXES.value)
357
387
  bytes_string += self.dwa_serialization(obj)
358
388
 
359
389
  elif isinstance(obj, Axis):
360
- bytes_string += self.string_serialization('axis')
390
+ bytes_string += self.string_serialization(SerializableTypes.AXIS.value)
361
391
  bytes_string += self.axis_serialization(obj)
362
392
 
363
393
  elif isinstance(obj, np.ndarray):
364
- bytes_string += self.string_serialization('array')
394
+ bytes_string += self.string_serialization(SerializableTypes.ARRAY.value)
365
395
  bytes_string += self.ndarray_serialization(obj)
366
396
 
367
397
  elif isinstance(obj, bytes):
368
- bytes_string += self.string_serialization('bytes')
398
+ bytes_string += self.string_serialization(SerializableTypes.BYTES.value)
369
399
  bytes_string += self.bytes_serialization(obj)
370
400
 
371
401
  elif isinstance(obj, str):
372
- bytes_string += self.string_serialization('string')
402
+ bytes_string += self.string_serialization(SerializableTypes.STRING.value)
373
403
  bytes_string += self.string_serialization(obj)
374
404
 
375
- elif isinstance(obj, numbers.Number):
376
- bytes_string += self.string_serialization('scalar')
405
+ elif isinstance(obj, bool):
406
+ bytes_string += self.string_serialization(SerializableTypes.BOOL.value)
377
407
  bytes_string += self.scalar_serialization(obj)
378
408
 
379
- elif isinstance(obj, bool):
380
- bytes_string += self.string_serialization('bool')
381
- bytes_string += self.scalar_serialization(int(obj))
409
+ elif isinstance(obj, numbers.Number):
410
+ bytes_string += self.string_serialization(SerializableTypes.SCALAR.value)
411
+ bytes_string += self.scalar_serialization(obj)
382
412
 
383
413
  elif isinstance(obj, list):
384
- bytes_string += self.string_serialization('list')
414
+ bytes_string += self.string_serialization(SerializableTypes.LIST.value)
385
415
  bytes_string += self.list_serialization(obj)
386
416
 
387
417
  elif isinstance(obj, putils.ParameterWithPath):
388
418
  path = obj.path
389
419
  param_as_xml = ioxml.parameter_to_xml_string(obj.parameter)
390
- bytes_string += self.string_serialization('parameter')
420
+ bytes_string += self.string_serialization(SerializableTypes.PARAMETER.value)
391
421
  bytes_string += self.list_serialization(path)
392
- bytes_string += self.string_serialization(param_as_xml)
422
+ bytes_string += self.bytes_serialization(param_as_xml)
393
423
 
394
424
  elif isinstance(obj, DataToExport):
395
- bytes_string += self.string_serialization('dte')
425
+ bytes_string += self.string_serialization(SerializableTypes.DATA_TO_EXPORT.value)
396
426
  bytes_string += self.dte_serialization(obj)
397
427
 
398
428
  else:
@@ -509,7 +539,7 @@ class DeSerializer:
509
539
  :py:class:`~pymodaq.utils.tcp_ip.mysocket.Socket`
510
540
  """
511
541
 
512
- def __init__(self, bytes_string: Union[bytes, 'Socket'] = None):
542
+ def __init__(self, bytes_string: Union[bytes, 'Socket'] = None) -> None:
513
543
  if isinstance(bytes_string, bytes):
514
544
  bytes_string = SocketString(bytes_string)
515
545
  self._bytes_string = bytes_string
@@ -531,7 +561,7 @@ class DeSerializer:
531
561
  return int.from_bytes(bytes_string, 'big')
532
562
 
533
563
  @staticmethod
534
- def bytes_to_scalar(data: bytes, dtype: np.dtype) -> numbers.Number:
564
+ def bytes_to_scalar(data: bytes, dtype: np.dtype) -> complex:
535
565
  """Convert bytes to a scalar given a certain numpy dtype
536
566
 
537
567
  Parameters
@@ -588,8 +618,8 @@ class DeSerializer:
588
618
  str_obj = self._bytes_string.get_first_nbytes(string_len).decode()
589
619
  return str_obj
590
620
 
591
- def scalar_deserialization(self) -> numbers.Number:
592
- """Convert bytes into a numbers.Number object
621
+ def scalar_deserialization(self) -> complex:
622
+ """Convert bytes into a python number object
593
623
 
594
624
  Get first the data type from a string deserialization, then the data length and finally convert this
595
625
  length of bytes into a number (float, int)
@@ -602,9 +632,11 @@ class DeSerializer:
602
632
  data_len = self._int_deserialization()
603
633
  number = np.frombuffer(self._bytes_string.get_first_nbytes(data_len), dtype=data_type)[0]
604
634
  if 'f' in data_type:
605
- number = float(number) # because one get numpy float type
635
+ number = float(number) # because one get numpy float type
606
636
  elif 'i' in data_type:
607
637
  number = int(number) # because one get numpy int type
638
+ elif 'c' in data_type:
639
+ number = complex(number) # because one get numpy complex type
608
640
  return number
609
641
 
610
642
  def boolean_deserialization(self) -> bool:
@@ -642,7 +674,7 @@ class DeSerializer:
642
674
  ndarray = np.atleast_1d(ndarray) # remove singleton dimensions
643
675
  return ndarray
644
676
 
645
- def type_and_object_deserialization(self):
677
+ def type_and_object_deserialization(self) -> SERIALIZABLE:
646
678
  """ Deserialize specific objects from their binary representation (inverse of `Serializer.type_and_object_serialization`).
647
679
 
648
680
  See Also
@@ -652,28 +684,29 @@ class DeSerializer:
652
684
  """
653
685
  obj_type = self.string_deserialization()
654
686
  elt = None
655
- if obj_type == 'scalar':
687
+ if obj_type == SerializableTypes.SCALAR.value:
656
688
  elt = self.scalar_deserialization()
657
- elif obj_type == 'string':
689
+ elif obj_type == SerializableTypes.STRING.value:
658
690
  elt = self.string_deserialization()
659
- elif obj_type == 'bytes':
691
+ elif obj_type == SerializableTypes.BYTES.value:
660
692
  elt = self.bytes_deserialization()
661
- elif obj_type == 'array':
693
+ elif obj_type == SerializableTypes.ARRAY.value:
662
694
  elt = self.ndarray_deserialization()
663
- elif obj_type == 'dwa':
695
+ elif obj_type == SerializableTypes.DATA_WITH_AXES.value:
664
696
  elt = self.dwa_deserialization()
665
- elif obj_type == 'dte':
697
+ elif obj_type == SerializableTypes.DATA_TO_EXPORT.value:
666
698
  elt = self.dte_deserialization()
667
- elif obj_type == 'axis':
699
+ elif obj_type == SerializableTypes.AXIS.value:
668
700
  elt = self.axis_deserialization()
669
- elif obj_type == 'bool':
701
+ elif obj_type == SerializableTypes.BOOL.value:
670
702
  elt = self.boolean_deserialization()
671
- elif obj_type == 'list':
703
+ elif obj_type == SerializableTypes.LIST.value:
672
704
  elt = self.list_deserialization()
673
- elif obj_type == 'parameter':
705
+ elif obj_type == SerializableTypes.PARAMETER.value:
674
706
  elt = self.parameter_deserialization()
675
707
  else:
676
708
  print(f'invalid object type {obj_type}')
709
+ elt = None # desired behavior?
677
710
  return elt
678
711
 
679
712
  def list_deserialization(self) -> list:
@@ -774,4 +807,4 @@ class DeSerializer:
774
807
  data=self.list_deserialization(),
775
808
  )
776
809
  dte.timestamp = timestamp
777
- return dte
810
+ return dte
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pymodaq
3
- Version: 4.4.5
3
+ Version: 4.4.7
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
@@ -27,7 +27,6 @@ License: The MIT License (MIT)
27
27
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
28
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29
29
  THE SOFTWARE.
30
- License-File: LICENSE
31
30
  Classifier: Development Status :: 5 - Production/Stable
32
31
  Classifier: Environment :: Other Environment
33
32
  Classifier: Intended Audience :: Science/Research
@@ -51,7 +50,7 @@ Requires-Dist: multipledispatch
51
50
  Requires-Dist: numpy<2.0.0
52
51
  Requires-Dist: packaging
53
52
  Requires-Dist: pint
54
- Requires-Dist: pyleco; python_version >= '3.8'
53
+ Requires-Dist: pyleco>0.3; python_version >= '3.8'
55
54
  Requires-Dist: pymodaq-plugin-manager>=0.0.17
56
55
  Requires-Dist: pymodaq-plugins-mock<5.0.0
57
56
  Requires-Dist: pyqtgraph>=0.12
@@ -3,12 +3,12 @@ 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=dUtqqmusaTSmnCcR-6tGHRwXM7zwV30viJFyQymZpAc,37346
6
+ pymodaq/control_modules/daq_move.py,sha256=0zit9shAo6HAVxkaYdhhuSN-yu8j8T_TLSF6YOaM85Y,37842
7
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=rtCTwYxb1gc7RSCNqT-MKm3LnAwOkN0Z3H7jTS1LRo8,42921
11
+ pymodaq/control_modules/move_utility_classes.py,sha256=PDiPVAfOEAhtTc6MS0oVCy3AL-6KLSxOgYg3c5jctoA,43417
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
@@ -18,6 +18,7 @@ pymodaq/examples/function_plotter.py,sha256=T-VT0Rd3jHP9GcR2h6Nao6lwZE06P8zWUbOl
18
18
  pymodaq/examples/nonlinearscanner.py,sha256=x0R2_FP0YnuOCCAmYRiAiZ1jfUdRxu5RqIYLyGQMZ0U,3790
19
19
  pymodaq/examples/parameter_ex.py,sha256=NmFUvUByNtm3j4leN_MkufQsKlNU8Rx5lmpsVG58IIM,8556
20
20
  pymodaq/examples/preset_MockCamera.xml,sha256=quQlMsX6YSoqqc9_9Y-9zu3TDM6Xvnuc2JSWwg9f948,15774
21
+ pymodaq/examples/qt_less_standalone_module.py,sha256=9UD4niVfNXscDVQiOJuuL6lN9PC7Jfp9FwVJ0qz_6Do,4857
21
22
  pymodaq/examples/tcp_client.py,sha256=FSdPlb3R_rxxNIqPqHVU8PxJzNZeFk_93l4TqsB5SnA,2584
22
23
  pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Client.aliases,sha256=t0eKH9Uq_AMk4wQ-6Pm5mKUjGcCvfT9GtvMsvDhkCUk,47
23
24
  pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Client.lvlps,sha256=VZHH96rKSnRYKeCAXUmKI3vQOX8Wz53FYCF2yXuqnaU,84
@@ -58,11 +59,11 @@ pymodaq/post_treatment/daq_measurement/daq_measurement_GUI.py,sha256=1u7hWDaiwsZ
58
59
  pymodaq/post_treatment/daq_measurement/daq_measurement_GUI.ui,sha256=PyzbCWPMkh5oIYYteZczXyWMeHKW9EJmM1QlzXhnyTk,7037
59
60
  pymodaq/post_treatment/daq_measurement/daq_measurement_main.py,sha256=CAKwcWMOD86aXB8mbdxOK7e8nZRos5d59FzDtqK1QoY,17093
60
61
  pymodaq/post_treatment/daq_measurement/process_from_QtDesigner_DAQ_Measurement_GUI.bat,sha256=e1tu2A67MS9fk3jhriF6saQgRxWIucIvNW92iWXFP6E,164
61
- pymodaq/resources/VERSION,sha256=6GGzX89SPwPeU5-Ey0MEM9ZwzJfhrT2qOR8OxMQdnVA,19
62
+ pymodaq/resources/VERSION,sha256=ib4rQ9G3YTfdijgNEWBcOoAIZQyUy-mfF0jvGog9fno,19
62
63
  pymodaq/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
64
  pymodaq/resources/config_template.toml,sha256=d3pofgIK5FxaRMELAI1qEsRcMD3GlYd87zZjDj9G9m0,3210
64
65
  pymodaq/resources/preset_default.xml,sha256=Dt8iWLwPPOPtcG00JCVP-mh-G7KC6B0YN8hd8RQdnNI,27256
65
- pymodaq/resources/setup_plugin.py,sha256=yab26FHPB-zsUZ6ZiKQ7Rm9H1ZhoZW8RQoW4wPK-T94,3130
66
+ pymodaq/resources/setup_plugin.py,sha256=jvMuSp4UxGaPUe9uPUvHg9DrdwyFakG6_sFy_zXb1f8,3182
66
67
  pymodaq/resources/triangulation_data.npy,sha256=Dzq6eE8f_i7Woloy1iUn6N1OfVdBZ4WnK4J4SCoqXso,9320
67
68
  pymodaq/resources/QtDesigner_Ressources/QtDesigner_ressources.bat,sha256=gqBmrc6Cfzn7wIZQtzgcglKRQ8zLXLW9Xt8LWxkJdw0,205
68
69
  pymodaq/resources/QtDesigner_Ressources/QtDesigner_ressources.qrc,sha256=S99847o7hvKYOWa9lR3qErMHwcjhjYPEduoq2ylCn4I,10123
@@ -311,7 +312,7 @@ pymodaq/utils/chrono_timer.py,sha256=rwX8apS8B-IKhA0Cp2H9tLz0BRN7G3Pg5ptozvd3MKM
311
312
  pymodaq/utils/config.py,sha256=0QqoBJC4ECuIeh1UsvUQqhxkKl7Vfgi4iERp-6qNWAc,16202
312
313
  pymodaq/utils/conftests.py,sha256=3Ak8WEpa3EhAp73Yb1LLq8YFONhPqiL7gG9eSDIoTNc,58
313
314
  pymodaq/utils/daq_utils.py,sha256=0jTrbT0aaZr3KaTgeDicmK9FbVnu3iaWBmNHnNJpr3A,28050
314
- pymodaq/utils/data.py,sha256=V4F_H-sUPmCwrcNDuF7JxzcJtnOlQX7fChSoyXdd6ng,111572
315
+ pymodaq/utils/data.py,sha256=RdzEDz3ziRmwbaqy875fLr06jrmaG1wzU-0e_5QQKXM,111908
315
316
  pymodaq/utils/enums.py,sha256=wpRipioUJkKcEfoaY2NrDQ2WhGxZTZiZoJty5f2Ljpc,2236
316
317
  pymodaq/utils/exceptions.py,sha256=wLO6VlofzfwWkOOWMN2B-3NEWMfpgygyeEdakIx_rAs,668
317
318
  pymodaq/utils/factory.py,sha256=QLqAPFnTZ93eUpmAAIr7kESDk2enD57RNSuFUsjxE4E,2311
@@ -358,13 +359,13 @@ pymodaq/utils/h5modules/exporters/base.py,sha256=hGX2teIMO03QB0qBGTP4rzeXbZrQcVL
358
359
  pymodaq/utils/h5modules/exporters/flimj.py,sha256=z44C30KlAbaPmjnS5inYophnA18LwwqZOa1UMLEi4Tw,2361
359
360
  pymodaq/utils/h5modules/exporters/hyperspy.py,sha256=rheeVJQO0BAF606D_0S_j8huzOLzZfkUAp0OdJEnUz4,6517
360
361
  pymodaq/utils/leco/__init__.py,sha256=wxgCC-0eragO_REWDMp-2zg9kK5l_W9oO68dtRYdoKA,878
361
- pymodaq/utils/leco/daq_move_LECODirector.py,sha256=roAGsTCK4fZ--G3T2enRwD6i6wOuq1b7iwViuUi0noY,6382
362
- pymodaq/utils/leco/daq_xDviewer_LECODirector.py,sha256=DsB-rLdmmA7B_Iv3cN7wetjL_ZH6FA5ZXgB_Y3y_CY0,5990
362
+ pymodaq/utils/leco/daq_move_LECODirector.py,sha256=neYq17utjIm_5KzJkglABd_iL0Ltolcri9oMcBDs-I8,6915
363
+ pymodaq/utils/leco/daq_xDviewer_LECODirector.py,sha256=q6r-oJFMWqc0AOlRQShxX1w64LgnVVSyFzh3sSYUFV4,6310
363
364
  pymodaq/utils/leco/desktop.ini,sha256=2zopClaSQqdFfIsC8CGo2Oc-14x9h1gV0-fUrDtLFmA,82
364
365
  pymodaq/utils/leco/director_utils.py,sha256=f_rzS6p7VxfWvgm36JRgBHphqYMZrw3gqsadgF9ZtIg,2109
365
366
  pymodaq/utils/leco/leco_director.py,sha256=WPsZB6oJIkDJIqtagbeuErfpGB-ENF-s0rtoj_EGVdM,3054
366
- pymodaq/utils/leco/pymodaq_listener.py,sha256=Nfk4iLrKaCsiEIR75h84XXMWJwp4E8g8CWgdbFV4YqQ,10699
367
- pymodaq/utils/leco/utils.py,sha256=yzLqAaZhfALmsrU4wlNGSnRYBiivYCFm9MZvzmZodog,1394
367
+ pymodaq/utils/leco/pymodaq_listener.py,sha256=OmYC6c3OYjqcT7M881b-g8lXIua2Pq-YYv4X_TuxaPM,10802
368
+ pymodaq/utils/leco/utils.py,sha256=jCHhco89uG9UA-t9LpaEuyZQMQW5XQP_t0URcvqLDNU,2362
368
369
  pymodaq/utils/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
369
370
  pymodaq/utils/managers/action_manager.py,sha256=v99NuRq7uT_PNTAsqbUyoWyDGUzhlP6dYtdAO30_cbc,17756
370
371
  pymodaq/utils/managers/batchscan_manager.py,sha256=jcL08YvFafX5kiy03BV1_6obt2Xogiby5pvTKeN8_ho,13905
@@ -373,7 +374,7 @@ pymodaq/utils/managers/overshoot_manager.py,sha256=fe_CR1Bkw85BER34MoVFlm-xtKl9H
373
374
  pymodaq/utils/managers/parameter_manager.py,sha256=hO3RXcpkYOtuqjAQaD5H3r71rVEHntYg64VHZwVCQBg,11473
374
375
  pymodaq/utils/managers/preset_manager.py,sha256=m8r_TcfFbUatddBX9SJj7XI_GZ3FhoiwzhFgocw9jZ8,9481
375
376
  pymodaq/utils/managers/preset_manager_utils.py,sha256=d148YBjeNOP9FTkFoTsfdRDxMIXOR8JJHqbOmoL2aVA,8155
376
- pymodaq/utils/managers/remote_manager.py,sha256=H6k9aiBkuJRJbZ4rpd5jfawQ-pMRFAXOE_miakvciP8,22251
377
+ pymodaq/utils/managers/remote_manager.py,sha256=SwzefJuGpgzvvjTbJSo3T3Rc2rncvHHyPYPBd8gCQX8,22251
377
378
  pymodaq/utils/managers/roi_manager.py,sha256=RRICNSLMxiUoq8tEBKihpdydIVAu78ogkYOFWWphaHE,29892
378
379
  pymodaq/utils/parameter/__init__.py,sha256=fMljZeQ9EVvh2bmss550C5BpxFeKOxT8_AVJdPxQ0kQ,433
379
380
  pymodaq/utils/parameter/ioxml.py,sha256=jduHhMrpc0lkSJlznnI-LRHUJ0Ofc4PoFfNlxLbLmw8,17091
@@ -436,10 +437,10 @@ pymodaq/utils/svg/svg_view.py,sha256=bmXpDqnw9S-Bp3F8Hi_oeYB5Y9gebiCNsQWVJzCq-PA
436
437
  pymodaq/utils/svg/svg_viewer2D.py,sha256=LTJ3Ulb5zWXdRPr7vqcWumbpq7ZctzrYUMtD5QV3x60,1523
437
438
  pymodaq/utils/tcp_ip/__init__.py,sha256=1e_EK0AgvdoLAD_CSGGEaITZdy6OWCO7ih9IAIp7HT4,81
438
439
  pymodaq/utils/tcp_ip/mysocket.py,sha256=StAWj8dzHeMnbLj68Sel81uWFy-YkKVNRnVf7gXrESI,3452
439
- pymodaq/utils/tcp_ip/serializer.py,sha256=htVQCE4saRBMeIcseEyxTt5G58A341m6OGkaJUA34Wk,27766
440
+ pymodaq/utils/tcp_ip/serializer.py,sha256=kXmvFLR5ZedjY2rVGUs83lsJk9Z9l_iAWR6srDxzaYQ,29262
440
441
  pymodaq/utils/tcp_ip/tcp_server_client.py,sha256=xIMTNgVW_rKK0yTi4FDNFLf85-Akb27Jz2LdrvOrP68,30660
441
- pymodaq-4.4.5.dist-info/METADATA,sha256=fXPlSRBE34NGAV3RqrDaPq36CdNaU35PW6Yke889ZCI,7629
442
- pymodaq-4.4.5.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
443
- pymodaq-4.4.5.dist-info/entry_points.txt,sha256=RAzdYNjvUT28I2eiCKki_g2NzXq0woWxhev6lwzwRv8,348
444
- pymodaq-4.4.5.dist-info/licenses/LICENSE,sha256=VKOejxexXAe3XwfhAhcFGqeXQ12irxVHdeAojZwFEI8,1108
445
- pymodaq-4.4.5.dist-info/RECORD,,
442
+ pymodaq-4.4.7.dist-info/METADATA,sha256=jkmtri16XiPHC-2It45CeeZp--C6giXVJ0FLp3g13u8,7611
443
+ pymodaq-4.4.7.dist-info/WHEEL,sha256=3U_NnUcV_1B1kPkYaPzN-irRckL5VW_lytn0ytO_kRY,87
444
+ pymodaq-4.4.7.dist-info/entry_points.txt,sha256=RAzdYNjvUT28I2eiCKki_g2NzXq0woWxhev6lwzwRv8,348
445
+ pymodaq-4.4.7.dist-info/licenses/LICENSE,sha256=VKOejxexXAe3XwfhAhcFGqeXQ12irxVHdeAojZwFEI8,1108
446
+ pymodaq-4.4.7.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.25.0
2
+ Generator: hatchling 1.26.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any