pymodaq 4.1.5__py3-none-any.whl → 4.2.1__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 (80) hide show
  1. pymodaq/__init__.py +23 -4
  2. pymodaq/control_modules/daq_move.py +32 -73
  3. pymodaq/control_modules/daq_viewer.py +73 -98
  4. pymodaq/control_modules/daq_viewer_ui.py +2 -1
  5. pymodaq/control_modules/move_utility_classes.py +17 -7
  6. pymodaq/control_modules/utils.py +153 -5
  7. pymodaq/control_modules/viewer_utility_classes.py +31 -20
  8. pymodaq/dashboard.py +23 -5
  9. pymodaq/examples/tcp_client.py +97 -0
  10. pymodaq/extensions/__init__.py +4 -0
  11. pymodaq/extensions/bayesian/__init__.py +2 -0
  12. pymodaq/extensions/bayesian/bayesian_optimisation.py +673 -0
  13. pymodaq/extensions/bayesian/utils.py +403 -0
  14. pymodaq/extensions/daq_scan.py +4 -4
  15. pymodaq/extensions/daq_scan_ui.py +2 -1
  16. pymodaq/extensions/pid/pid_controller.py +12 -7
  17. pymodaq/extensions/pid/utils.py +9 -26
  18. pymodaq/extensions/utils.py +3 -0
  19. pymodaq/post_treatment/load_and_plot.py +42 -19
  20. pymodaq/resources/VERSION +1 -1
  21. pymodaq/resources/config_template.toml +9 -24
  22. pymodaq/resources/setup_plugin.py +1 -1
  23. pymodaq/utils/config.py +103 -5
  24. pymodaq/utils/daq_utils.py +35 -134
  25. pymodaq/utils/data.py +614 -95
  26. pymodaq/utils/enums.py +17 -1
  27. pymodaq/utils/factory.py +2 -2
  28. pymodaq/utils/gui_utils/custom_app.py +5 -2
  29. pymodaq/utils/gui_utils/dock.py +33 -4
  30. pymodaq/utils/gui_utils/utils.py +14 -1
  31. pymodaq/utils/h5modules/backends.py +9 -1
  32. pymodaq/utils/h5modules/data_saving.py +254 -57
  33. pymodaq/utils/h5modules/saving.py +1 -0
  34. pymodaq/utils/leco/__init__.py +25 -0
  35. pymodaq/utils/leco/daq_move_LECODirector.py +172 -0
  36. pymodaq/utils/leco/daq_xDviewer_LECODirector.py +170 -0
  37. pymodaq/utils/leco/desktop.ini +2 -0
  38. pymodaq/utils/leco/director_utils.py +58 -0
  39. pymodaq/utils/leco/leco_director.py +88 -0
  40. pymodaq/utils/leco/pymodaq_listener.py +279 -0
  41. pymodaq/utils/leco/utils.py +41 -0
  42. pymodaq/utils/managers/action_manager.py +20 -6
  43. pymodaq/utils/managers/parameter_manager.py +6 -4
  44. pymodaq/utils/managers/roi_manager.py +63 -54
  45. pymodaq/utils/math_utils.py +1 -1
  46. pymodaq/utils/plotting/data_viewers/__init__.py +3 -1
  47. pymodaq/utils/plotting/data_viewers/base.py +286 -0
  48. pymodaq/utils/plotting/data_viewers/viewer.py +29 -202
  49. pymodaq/utils/plotting/data_viewers/viewer0D.py +94 -47
  50. pymodaq/utils/plotting/data_viewers/viewer1D.py +341 -174
  51. pymodaq/utils/plotting/data_viewers/viewer1Dbasic.py +1 -1
  52. pymodaq/utils/plotting/data_viewers/viewer2D.py +271 -181
  53. pymodaq/utils/plotting/data_viewers/viewerND.py +26 -22
  54. pymodaq/utils/plotting/items/crosshair.py +3 -3
  55. pymodaq/utils/plotting/items/image.py +2 -1
  56. pymodaq/utils/plotting/plotter/plotter.py +94 -0
  57. pymodaq/utils/plotting/plotter/plotters/__init__.py +0 -0
  58. pymodaq/utils/plotting/plotter/plotters/matplotlib_plotters.py +134 -0
  59. pymodaq/utils/plotting/plotter/plotters/qt_plotters.py +78 -0
  60. pymodaq/utils/plotting/utils/axes_viewer.py +1 -1
  61. pymodaq/utils/plotting/utils/filter.py +194 -147
  62. pymodaq/utils/plotting/utils/lineout.py +13 -11
  63. pymodaq/utils/plotting/utils/plot_utils.py +89 -12
  64. pymodaq/utils/scanner/__init__.py +0 -3
  65. pymodaq/utils/scanner/scan_config.py +1 -9
  66. pymodaq/utils/scanner/scan_factory.py +10 -36
  67. pymodaq/utils/scanner/scanner.py +3 -2
  68. pymodaq/utils/scanner/scanners/_1d_scanners.py +7 -5
  69. pymodaq/utils/scanner/scanners/_2d_scanners.py +36 -49
  70. pymodaq/utils/scanner/scanners/sequential.py +10 -4
  71. pymodaq/utils/scanner/scanners/tabular.py +10 -5
  72. pymodaq/utils/slicing.py +1 -1
  73. pymodaq/utils/tcp_ip/serializer.py +38 -5
  74. pymodaq/utils/tcp_ip/tcp_server_client.py +25 -17
  75. {pymodaq-4.1.5.dist-info → pymodaq-4.2.1.dist-info}/METADATA +4 -2
  76. {pymodaq-4.1.5.dist-info → pymodaq-4.2.1.dist-info}/RECORD +79 -63
  77. pymodaq/resources/config_scan_template.toml +0 -42
  78. {pymodaq-4.1.5.dist-info → pymodaq-4.2.1.dist-info}/WHEEL +0 -0
  79. {pymodaq-4.1.5.dist-info → pymodaq-4.2.1.dist-info}/entry_points.txt +0 -0
  80. {pymodaq-4.1.5.dist-info → pymodaq-4.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,279 @@
1
+
2
+ try:
3
+ from enum import StrEnum # type: ignore
4
+ except ImportError:
5
+ from enum import Enum
6
+
7
+ class StrEnum(str, Enum):
8
+ pass
9
+ import logging
10
+ from threading import Event
11
+ from typing import Optional, Union, List, Type
12
+
13
+ from pyleco.core import COORDINATOR_PORT
14
+ from pyleco.utils.listener import Listener, PipeHandler
15
+ from qtpy.QtCore import QObject, Signal # type: ignore
16
+
17
+ from pymodaq.utils.daq_utils import ThreadCommand
18
+ from pymodaq.utils.parameter import ioxml
19
+ from pymodaq.utils.tcp_ip.serializer import DataWithAxes, SERIALIZABLE, DeSerializer
20
+ from pymodaq.utils.leco.utils import serialize_object
21
+
22
+
23
+ class LECOClientCommands(StrEnum):
24
+ LECO_CONNECTED = "leco_connected"
25
+ LECO_DISCONNECTED = "leco_disconnected"
26
+
27
+
28
+ class LECOCommands(StrEnum):
29
+ CONNECT = "ini_connection"
30
+ QUIT = "quit"
31
+
32
+
33
+ class LECOMoveCommands(StrEnum):
34
+ POSITION = 'position_is'
35
+ MOVE_DONE = 'move_done'
36
+
37
+
38
+ class LECOViewerCommands(StrEnum):
39
+ DATA_READY = 'data_ready'
40
+
41
+
42
+ class ListenerSignals(QObject):
43
+ cmd_signal = Signal(ThreadCommand)
44
+ """
45
+ Possible messages sendable via `cmd_signal`
46
+ For all modules: Info, Infos, Info_xml, set_info
47
+
48
+ For a detector: Send Data 0D, Send Data 1D, Send Data 2D
49
+
50
+ For an actuator: move_abs, move_home, move_rel, check_position, stop_motion
51
+ """
52
+ # message = Signal(Message)
53
+
54
+
55
+ class PymodaqPipeHandler(PipeHandler):
56
+
57
+ def __init__(self, name: str, signals: ListenerSignals, **kwargs) -> None:
58
+ super().__init__(name, **kwargs)
59
+ self.signals = signals
60
+
61
+
62
+ class ActorHandler(PymodaqPipeHandler):
63
+
64
+ def register_rpc_methods(self) -> None:
65
+ super().register_rpc_methods()
66
+ self.register_rpc_method(self.set_info)
67
+ self.register_rpc_method(self.send_data)
68
+ self.register_rpc_method(self.move_abs)
69
+ self.register_rpc_method(self.move_rel)
70
+ self.register_rpc_method(self.move_home)
71
+ self.register_rpc_method(self.get_actuator_value)
72
+ self.register_rpc_method(self.stop_motion)
73
+
74
+ @staticmethod
75
+ def extract_dwa_object(data_string: str) -> DataWithAxes:
76
+ """Extract a DataWithAxes object from the received message."""
77
+ desererializer = DeSerializer.from_b64_string(data_string)
78
+ return desererializer.dwa_deserialization()
79
+
80
+ # generic commands
81
+ def set_info(self, path: List[str], param_dict_str: str) -> None:
82
+ self.signals.cmd_signal.emit(ThreadCommand("set_info", attribute=[path, param_dict_str]))
83
+
84
+ # detector commands
85
+ def send_data(self, grabber_type: str = "") -> None:
86
+ self.signals.cmd_signal.emit(ThreadCommand(f"Send Data {grabber_type}"))
87
+
88
+ # actuator commands
89
+ def move_abs(self, position: Union[float, str]) -> None:
90
+ pos = self.extract_dwa_object(position) if isinstance(position, str) else position
91
+ self.signals.cmd_signal.emit(ThreadCommand("move_abs", attribute=[pos]))
92
+
93
+ def move_rel(self, position: Union[float, str]) -> None:
94
+ pos = self.extract_dwa_object(position) if isinstance(position, str) else position
95
+ self.signals.cmd_signal.emit(ThreadCommand("move_rel", attribute=[pos]))
96
+
97
+ def move_home(self) -> None:
98
+ self.signals.cmd_signal.emit(ThreadCommand("move_home"))
99
+
100
+ def get_actuator_value(self) -> None:
101
+ """Request that the actuator value is sent later on."""
102
+ # according to DAQ_Move, this supersedes "check_position"
103
+ self.signals.cmd_signal.emit(ThreadCommand("get_actuator_value"))
104
+
105
+ def stop_motion(self,) -> None:
106
+ # not implemented in DAQ_Move!
107
+ self.signals.cmd_signal.emit(ThreadCommand("stop_motion"))
108
+
109
+
110
+ # to be able to separate them later on
111
+ MoveActorHandler = ActorHandler
112
+ ViewerActorHandler = ActorHandler
113
+
114
+
115
+ class PymodaqListener(Listener):
116
+ """A Listener prepared for PyMoDAQ.
117
+
118
+ :param name: Name of this module.
119
+ :param host: Host name of the communication server.
120
+ :param port: Port number of the communication server.
121
+ """
122
+ remote_name: str = ""
123
+
124
+ local_methods = ["pong", "set_log_level"]
125
+
126
+ def __init__(self,
127
+ name: str,
128
+ handler_class: Type[PymodaqPipeHandler] = PymodaqPipeHandler,
129
+ host: str = "localhost",
130
+ port: int = COORDINATOR_PORT,
131
+ logger: Optional[logging.Logger] = None,
132
+ timeout: float = 1,
133
+ **kwargs) -> None:
134
+ super().__init__(name, host, port, logger=logger, timeout=timeout,
135
+ **kwargs)
136
+ self.signals = ListenerSignals()
137
+ # self.signals.message.connect(self.handle_message)
138
+ self.cmd_signal = self.signals.cmd_signal
139
+ self._handler_class = handler_class
140
+
141
+ def _listen(self, name: str, stop_event: Event, coordinator_host: str, coordinator_port: int,
142
+ data_host: str, data_port: int) -> None:
143
+ self.message_handler = self._handler_class(name,
144
+ host=coordinator_host, port=coordinator_port,
145
+ data_host=data_host, data_port=data_port,
146
+ signals=self.signals,
147
+ )
148
+ self.message_handler.register_on_name_change_method(self.indicate_sign_in_out)
149
+ self.message_handler.listen(stop_event=stop_event)
150
+
151
+ def stop_listen(self) -> None:
152
+ super().stop_listen()
153
+ try:
154
+ del self.communicator
155
+ except AttributeError:
156
+ pass
157
+ self.signals.cmd_signal.emit(ThreadCommand(LECOClientCommands.LECO_DISCONNECTED))
158
+
159
+ def indicate_sign_in_out(self, full_name: str):
160
+ if "." in full_name:
161
+ self.signals.cmd_signal.emit(ThreadCommand(LECOClientCommands.LECO_CONNECTED))
162
+ else:
163
+ self.signals.cmd_signal.emit(ThreadCommand(LECOClientCommands.LECO_DISCONNECTED))
164
+
165
+
166
+ class ActorListener(PymodaqListener):
167
+ """Listener for modules being an Actor (being remote controlled)."""
168
+
169
+ def __init__(self,
170
+ name: str,
171
+ handler_class: Type[ActorHandler] = ActorHandler,
172
+ host: str = "localhost",
173
+ port: int = COORDINATOR_PORT,
174
+ logger: Optional[logging.Logger] = None,
175
+ timeout: float = 1,
176
+ **kwargs) -> None:
177
+ super().__init__(name, handler_class=handler_class, host=host, port=port,
178
+ logger=logger, timeout=timeout,
179
+ **kwargs)
180
+
181
+ def start_listen(self) -> None:
182
+ super().start_listen()
183
+ self.message_handler.register_rpc_method(self.set_remote_name)
184
+
185
+ def set_remote_name(self, name: str) -> None:
186
+ """Define what the name of the remote for answers is."""
187
+ self.remote_name = name
188
+
189
+ # @Slot(ThreadCommand)
190
+ def queue_command(self, command: ThreadCommand) -> None:
191
+ """Queue a command to send it via LECO to the server."""
192
+
193
+ # generic commands
194
+ if command.command == LECOCommands.CONNECT:
195
+ try:
196
+ if self.thread.is_alive():
197
+ return # already started
198
+ except AttributeError:
199
+ pass # start later on, as there is no thread.
200
+ self.start_listen()
201
+
202
+ elif command.command == LECOCommands.QUIT:
203
+ try:
204
+ self.stop_listen()
205
+ except Exception:
206
+ pass
207
+ finally:
208
+ self.cmd_signal.emit(ThreadCommand('disconnected'))
209
+
210
+ elif command.command == 'update_connection':
211
+ # self.ipaddress = command.attribute['ipaddress']
212
+ # self.port = command.attribute['port']
213
+ pass # TODO change name?
214
+
215
+ elif command.command == LECOViewerCommands.DATA_READY:
216
+ # code from the original:
217
+ # self.data_ready(data=command.attribute)
218
+ # def data_ready(data): self.send_data(datas[0]['data'])
219
+ value = command.attribute # type: ignore
220
+ self.communicator.ask_rpc(
221
+ receiver=self.remote_name,
222
+ method="set_data",
223
+ data=serialize_object(value),
224
+ )
225
+
226
+ elif command.command == 'send_info':
227
+ path = command.attribute['path'] # type: ignore
228
+ param = command.attribute['param'] # type: ignore
229
+ self.communicator.ask_rpc(
230
+ receiver=self.remote_name,
231
+ method="set_info",
232
+ path=path,
233
+ param_dict_str=ioxml.parameter_to_xml_string(param).decode())
234
+
235
+ elif command.command == LECOMoveCommands.POSITION:
236
+ value = command.attribute[0] # type: ignore
237
+ self.communicator.ask_rpc(receiver=self.remote_name,
238
+ method="set_position",
239
+ position=serialize_object(value),
240
+ )
241
+
242
+ elif command.command == LECOMoveCommands.MOVE_DONE:
243
+ value = command.attribute[0] # type: ignore
244
+ self.communicator.ask_rpc(receiver=self.remote_name,
245
+ method="set_move_done",
246
+ position=serialize_object(value),
247
+ )
248
+
249
+ elif command.command == 'x_axis':
250
+ value = command.attribute[0] # type: ignore
251
+ if isinstance(value, SERIALIZABLE):
252
+ self.communicator.ask_rpc(receiver=self.remote_name,
253
+ method="set_x_axis",
254
+ data=serialize_object(value),
255
+ )
256
+ elif isinstance(value, dict):
257
+ self.communicator.ask_rpc(receiver=self.remote_name, method="set_x_axis", **value)
258
+ else:
259
+ raise ValueError("Nothing to send!")
260
+
261
+ elif command.command == 'y_axis':
262
+ value = command.attribute[0] # type: ignore
263
+ if isinstance(value, SERIALIZABLE):
264
+ self.communicator.ask_rpc(receiver=self.remote_name,
265
+ method="set_y_axis",
266
+ data=serialize_object(value),
267
+ )
268
+ elif isinstance(value, dict):
269
+ self.communicator.ask_rpc(receiver=self.remote_name, method="set_y_axis", **value)
270
+ else:
271
+ raise ValueError("Nothing to send!")
272
+
273
+ else:
274
+ raise IOError('Unknown TCP client command')
275
+
276
+
277
+ # to be able to separate them later on
278
+ MoveActorListener = ActorListener
279
+ ViewerActorListener = ActorListener
@@ -0,0 +1,41 @@
1
+ import subprocess
2
+ import sys
3
+ from typing import Any, Union, get_args
4
+
5
+ # import also the DeSerializer for easier imports in dependents
6
+ from pymodaq.utils.tcp_ip.serializer import SERIALIZABLE, Serializer, DeSerializer # type: ignore # noqa
7
+ from pymodaq.utils.logger import set_logger
8
+
9
+
10
+ logger = set_logger('leco_utils')
11
+
12
+ JSON_TYPES = Union[str, int, float]
13
+
14
+
15
+ def serialize_object(pymodaq_object: Union[SERIALIZABLE, Any]) -> Union[str, Any]:
16
+ """Serialize a pymodaq object, if it is not JSON compatible."""
17
+ if isinstance(pymodaq_object, get_args(JSON_TYPES)):
18
+ return pymodaq_object
19
+ elif isinstance(pymodaq_object, get_args(SERIALIZABLE)):
20
+ return Serializer(pymodaq_object).to_b64_string()
21
+ else:
22
+ raise ValueError(f"{pymodaq_object} of type '{type(pymodaq_object).__name__}' is neither "
23
+ "JSON serializable, nor via PyMoDAQ.")
24
+
25
+
26
+ def run_coordinator():
27
+ command = [sys.executable, '-m', 'pyleco.coordinators.coordinator']
28
+ subprocess.Popen(command)
29
+
30
+
31
+ def start_coordinator():
32
+ from pyleco.directors.director import Director
33
+ try:
34
+ with Director(actor="COORDINATOR") as director:
35
+ if director.communicator.namespace is None:
36
+ run_coordinator()
37
+ else:
38
+ logger.info('Coordinator already running')
39
+ except ConnectionRefusedError as e:
40
+ run_coordinator()
41
+
@@ -50,7 +50,8 @@ class QAction(QAction):
50
50
 
51
51
  def addaction(name: str = '', icon_name: str = '', tip='', checkable=False, checked=False,
52
52
  slot: Callable = None, toolbar: QtWidgets.QToolBar = None,
53
- menu: QtWidgets.QMenu = None, visible=True, shortcut=None):
53
+ menu: QtWidgets.QMenu = None, visible=True, shortcut=None,
54
+ enabled=True):
54
55
  """Create a new action and add it eventually to a toolbar and a menu
55
56
 
56
57
  Parameters
@@ -73,6 +74,10 @@ def addaction(name: str = '', icon_name: str = '', tip='', checkable=False, chec
73
74
  a menu where action should be added.
74
75
  visible: bool
75
76
  display or not the action in the toolbar/menu
77
+ shortcut: str
78
+ a string defining a shortcut for this action
79
+ enabled: bool
80
+ set the enabled state
76
81
  """
77
82
  if icon_name != '':
78
83
  action = QAction(create_icon(icon_name), name, None)
@@ -92,6 +97,7 @@ def addaction(name: str = '', icon_name: str = '', tip='', checkable=False, chec
92
97
  if shortcut is not None:
93
98
  action.setShortcut(shortcut)
94
99
  action.setVisible(visible)
100
+ action.setEnabled(enabled)
95
101
  return action
96
102
 
97
103
 
@@ -182,9 +188,11 @@ class ActionManager:
182
188
  raise NotImplementedError(f'You have to define actions here in the following form:'
183
189
  f'{self.setup_actions.__doc__}')
184
190
 
185
- def add_action(self, short_name: str = '', name: str = '', icon_name: str = '', tip='', checkable=False,
191
+ def add_action(self, short_name: str = '', name: str = '', icon_name: str = '', tip='',
192
+ checkable=False,
186
193
  checked=False, toolbar=None, menu=None,
187
- visible=True, shortcut=None, auto_toolbar=True, auto_menu=True):
194
+ visible=True, shortcut=None, auto_toolbar=True, auto_menu=True,
195
+ enabled=True):
188
196
  """Create a new action and add it to toolbar and menu
189
197
 
190
198
  Parameters
@@ -207,7 +215,12 @@ class ActionManager:
207
215
  a menu where action should be added. Actions can also be added later see *affect_to*
208
216
  visible: bool
209
217
  display or not the action in the toolbar/menu
210
-
218
+ auto_toolbar: bool
219
+ if True add this action to the defined toolbar
220
+ auto_menu: bool
221
+ if True add this action to the defined menu
222
+ enabled: bool
223
+ set the enabled state of this action
211
224
  See Also
212
225
  --------
213
226
  affect_to, pymodaq.resources.QtDesigner_Ressources.Icon_Library,
@@ -219,8 +232,9 @@ class ActionManager:
219
232
  if auto_menu:
220
233
  if menu is None:
221
234
  menu = self._menu
222
- self._actions[short_name] = addaction(name, icon_name, tip, checkable=checkable, checked=checked, toolbar=toolbar, menu=menu,
223
- visible=visible, shortcut=shortcut)
235
+ self._actions[short_name] = addaction(name, icon_name, tip, checkable=checkable,
236
+ checked=checked, toolbar=toolbar, menu=menu,
237
+ visible=visible, shortcut=shortcut, enabled=enabled)
224
238
 
225
239
  def add_widget(self, short_name, klass: Union[str, QtWidgets.QWidget], *args, tip='',
226
240
  toolbar: QtWidgets.QToolBar = None, visible=True, signal_str=None, slot: Callable=None, **kwargs):
@@ -1,5 +1,5 @@
1
1
  from pathlib import Path
2
- from typing import List, Union, Dict
2
+ from typing import List, Union, Dict, Optional
3
3
 
4
4
  from qtpy import QtWidgets, QtCore
5
5
  from pymodaq.utils.managers.action_manager import ActionManager
@@ -84,7 +84,9 @@ class ParameterManager:
84
84
  settings_name = 'custom_settings'
85
85
  params = []
86
86
 
87
- def __init__(self, settings_name: str = None, action_list: tuple = ('save', 'update', 'load')):
87
+ def __init__(self, settings_name: Optional[str] = None,
88
+ action_list: tuple = ('save', 'update', 'load'),
89
+ ):
88
90
  if settings_name is None:
89
91
  settings_name = self.settings_name
90
92
  # create a settings tree to be shown eventually in a dock
@@ -96,7 +98,7 @@ class ParameterManager:
96
98
  self._settings_tree.get_action(f'update_settings').connect_to(self.update_settings_slot)
97
99
  self._settings_tree.get_action(f'load_settings').connect_to(self.load_settings_slot)
98
100
 
99
- self.settings: Parameter = Parameter.create(name=settings_name, type='group', children=self.params) # create a Parameter
101
+ self.settings = Parameter.create(name=settings_name, type='group', children=self.params) # create a Parameter
100
102
  # object containing the settings defined in the preamble
101
103
 
102
104
  @property
@@ -108,7 +110,7 @@ class ParameterManager:
108
110
  return self._settings_tree.tree
109
111
 
110
112
  @property
111
- def settings(self):
113
+ def settings(self) -> Parameter:
112
114
  return self._settings
113
115
 
114
116
  @settings.setter