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,172 @@
1
+ """
2
+ LECO Director instrument plugin are to be used to communicate (and control) remotely real
3
+ instrument plugin through TCP/IP using the LECO Protocol
4
+
5
+ For this to work a coordinator must be instantiated can be done within the dashboard or directly
6
+ running: `python -m pyleco.coordinators.coordinator`
7
+
8
+ """
9
+
10
+ from typing import Union
11
+
12
+ from pymodaq.control_modules.move_utility_classes import (DAQ_Move_base, comon_parameters_fun, main,
13
+ DataActuatorType, DataActuator)
14
+
15
+ from pymodaq.utils.daq_utils import ThreadCommand
16
+ from pymodaq.utils.parameter import Parameter
17
+
18
+ from pymodaq.utils.leco.leco_director import LECODirector, leco_parameters
19
+ from pymodaq.utils.leco.director_utils import ActuatorDirector
20
+ from pymodaq.utils.tcp_ip.serializer import DeSerializer
21
+
22
+
23
+ class DAQ_Move_LECODirector(LECODirector, DAQ_Move_base):
24
+ """A control module, which in the dashboard, allows to control a remote Move module.
25
+
26
+ ================= ==============================
27
+ **Attributes** **Type**
28
+ *command_server* instance of Signal
29
+ *x_axis* 1D numpy array
30
+ *y_axis* 1D numpy array
31
+ *data* double precision float array
32
+ ================= ==============================
33
+
34
+ See Also
35
+ --------
36
+ utility_classes.DAQ_TCP_server
37
+ """
38
+ settings: Parameter
39
+ controller: ActuatorDirector
40
+
41
+ is_multiaxes = False
42
+ axes_names = []
43
+ params_client = [] # parameters of a client grabber
44
+ data_actuator_type = DataActuatorType['float'] # DataActuatorType['DataActuator']
45
+
46
+ message_list = LECODirector.message_list + ["move_abs", 'move_home', 'move_rel',
47
+ 'get_actuator_value', 'stop_motion', 'position_is',
48
+ 'move_done']
49
+ socket_types = ["ACTUATOR"]
50
+ params = [
51
+ ] + comon_parameters_fun(is_multiaxes=is_multiaxes, axes_names=axes_names) + leco_parameters
52
+
53
+ def __init__(self, parent=None, params_state=None, **kwargs) -> None:
54
+ super().__init__(parent=parent,
55
+ params_state=params_state, **kwargs)
56
+ self.register_rpc_methods((
57
+ self.set_info,
58
+ self.set_position,
59
+ self.set_move_done,
60
+ self.set_x_axis,
61
+ self.set_y_axis,
62
+ ))
63
+
64
+ # copied, I think it is good:
65
+ self.settings.child('bounds').hide()
66
+ self.settings.child('scaling').hide()
67
+ self.settings.child('epsilon').setValue(1)
68
+
69
+ def commit_settings(self, param) -> None:
70
+ self.commit_leco_settings(param=param)
71
+
72
+ def ini_stage(self, controller=None):
73
+ """Actuator communication initialization
74
+
75
+ Parameters
76
+ ----------
77
+ controller: (object)
78
+ custom object of a PyMoDAQ plugin (Slave case). None if only one actuator by controller
79
+ (Master case)
80
+
81
+ Returns
82
+ -------
83
+ info: str
84
+ initialized: bool
85
+ False if initialization failed otherwise True
86
+ """
87
+ actor_name = self.settings.child("actor_name").value()
88
+ self.controller = self.ini_stage_init( # type: ignore
89
+ old_controller=controller,
90
+ new_controller=ActuatorDirector(actor=actor_name, communicator=self.communicator),
91
+ )
92
+ try:
93
+ self.controller.set_remote_name(self.communicator.full_name) # type: ignore
94
+ except TimeoutError:
95
+ print("Timeout setting remote name.") # TODO change to real logging
96
+ # self.settings.child('infos').addChildren(self.params_client)
97
+
98
+ self.settings.child('units').hide()
99
+ self.settings.child('epsilon').hide()
100
+
101
+ self.status.info = "LECODirector"
102
+ self.status.controller = self.controller
103
+ self.status.initialized = True
104
+ return self.status
105
+
106
+ def move_abs(self, position: DataActuator) -> None:
107
+ position = self.check_bound(position)
108
+ position = self.set_position_with_scaling(position)
109
+
110
+ self.controller.move_abs(position=position)
111
+
112
+ self.target_value = position
113
+
114
+ def move_rel(self, position: DataActuator) -> None:
115
+ position = self.check_bound(self.current_value + position) - self.current_value # type: ignore # noqa
116
+ self.target_value = position + self.current_value
117
+
118
+ position = self.set_position_relative_with_scaling(position)
119
+ self.controller.move_rel(position=position)
120
+
121
+ def move_home(self):
122
+ self.controller.move_home()
123
+
124
+ def get_actuator_value(self) -> DataActuator:
125
+ """
126
+ Get the current hardware position with scaling conversion given by
127
+ `get_position_with_scaling`.
128
+
129
+ See Also
130
+ --------
131
+ daq_move_base.get_position_with_scaling, daq_utils.ThreadCommand
132
+ """
133
+ self.controller.set_remote_name(self.communicator.full_name) # to ensure communication
134
+ self.controller.get_actuator_value()
135
+ return self._current_value
136
+
137
+ def stop_motion(self) -> None:
138
+ """
139
+ See Also
140
+ --------
141
+ daq_move_base.move_done
142
+ """
143
+ self.controller.stop_motion()
144
+
145
+ # 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()
150
+ else:
151
+ pos = DataActuator(data=position)
152
+ pos = self.get_position_with_scaling(pos) # type: ignore
153
+ self._current_value = pos
154
+ return pos
155
+
156
+ def set_position(self, position: Union[str, float]) -> None:
157
+ pos = self._set_position_value(position=position)
158
+ self.emit_status(ThreadCommand('get_actuator_value', [pos]))
159
+
160
+ def set_move_done(self, position: Union[str, float]) -> None:
161
+ pos = self._set_position_value(position=position)
162
+ self.emit_status(ThreadCommand('move_done', [pos]))
163
+
164
+ def set_x_axis(self, data, label: str = "", units: str = "") -> None:
165
+ raise NotImplementedError("where is it handled?")
166
+
167
+ def set_y_axis(self, data, label: str = "", units: str = "") -> None:
168
+ raise NotImplementedError("where is it handled?")
169
+
170
+
171
+ if __name__ == '__main__':
172
+ main(__file__)
@@ -0,0 +1,170 @@
1
+
2
+ from typing import Union
3
+
4
+ from easydict import EasyDict as edict
5
+
6
+ from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main
7
+
8
+ from pymodaq.utils.daq_utils import ThreadCommand, getLineInfo
9
+ from pymodaq.utils.parameter import Parameter
10
+ from pymodaq.utils.tcp_ip.serializer import DeSerializer
11
+
12
+ from pymodaq.utils.leco.leco_director import LECODirector, leco_parameters
13
+ from pymodaq.utils.leco.director_utils import DetectorDirector
14
+
15
+
16
+ class DAQ_xDViewer_LECODirector(LECODirector, DAQ_Viewer_base):
17
+ """A control module, which in the dashboard, allows to control a remote Viewer module.
18
+
19
+ This is the base class for the viewer LECO director modules.
20
+ """
21
+
22
+ settings: Parameter
23
+ controller: DetectorDirector
24
+
25
+ params_GRABBER = []
26
+
27
+ message_list = LECODirector.message_list + ["Quit", "Send Data 0D", "Send Data 1D",
28
+ "Send Data 2D", "Send Data ND",
29
+ "Status", "Done", "Server Closed",
30
+ "Info", "Infos", "Info_xml", 'x_axis', 'y_axis']
31
+ socket_types = ["GRABBER"]
32
+ params = [
33
+ ] + comon_parameters + leco_parameters
34
+
35
+ def __init__(self, parent=None, params_state=None, grabber_type: str = "0D", **kwargs) -> None:
36
+ super().__init__(parent=parent, params_state=params_state, **kwargs)
37
+ self.register_rpc_methods((
38
+ self.set_x_axis,
39
+ self.set_y_axis,
40
+ self.set_data,
41
+ ))
42
+
43
+ self.client_type = "GRABBER"
44
+ self.x_axis = None
45
+ self.y_axis = None
46
+ self.data = None
47
+ self.grabber_type = grabber_type
48
+ self.ind_data = 0
49
+ self.data_mock = None
50
+
51
+ def ini_detector(self, controller=None):
52
+ """
53
+ | Initialisation procedure of the detector updating the status dictionary.
54
+ |
55
+ | Init axes from image , here returns only None values (to tricky to di it with the
56
+ server and not really necessary for images anyway)
57
+
58
+ See Also
59
+ --------
60
+ utility_classes.DAQ_TCP_server.init_server, get_xaxis, get_yaxis
61
+ """
62
+ self.status.update(edict(initialized=False, info="", x_axis=None, y_axis=None,
63
+ controller=None))
64
+ actor_name = self.settings.child("actor_name").value()
65
+ self.controller = self.ini_detector_init( # type: ignore
66
+ old_controller=controller,
67
+ new_controller=DetectorDirector(actor=actor_name, communicator=self.communicator),
68
+ )
69
+ self.controller.set_remote_name(self.communicator.full_name) # type: ignore
70
+ try:
71
+ # self.settings.child(('infos')).addChildren(self.params_GRABBER)
72
+
73
+ # init axes from image , here returns only None values (to tricky to di it with the
74
+ # server and not really necessary for images anyway)
75
+ self.x_axis = self.get_xaxis()
76
+ self.y_axis = self.get_yaxis()
77
+ self.status.x_axis = self.x_axis
78
+ self.status.y_axis = self.y_axis
79
+ self.status.initialized = True
80
+ return self.status
81
+
82
+ except Exception as e:
83
+ self.status.info = getLineInfo() + str(e)
84
+ self.status.initialized = False
85
+ return self.status
86
+
87
+ def get_xaxis(self):
88
+ """
89
+ Obtain the horizontal axis of the image.
90
+
91
+ Returns
92
+ -------
93
+ 1D numpy array
94
+ Contains a vector of integer corresponding to the horizontal camera pixels.
95
+ """
96
+ pass
97
+ return self.x_axis
98
+
99
+ def get_yaxis(self):
100
+ """
101
+ Obtain the vertical axis of the image.
102
+
103
+ Returns
104
+ -------
105
+ 1D numpy array
106
+ Contains a vector of integer corresponding to the vertical camera pixels.
107
+ """
108
+ pass
109
+ return self.y_axis
110
+
111
+ def grab_data(self, Naverage=1, **kwargs):
112
+ """
113
+ Start new acquisition.
114
+ Grabbed indice is used to keep track of the current image in the average.
115
+
116
+ ============== ========== ==============================
117
+ **Parameters** **Type** **Description**
118
+
119
+ *Naverage* int Number of images to average
120
+ ============== ========== ==============================
121
+
122
+ See Also
123
+ --------
124
+ utility_classes.DAQ_TCP_server.process_cmds
125
+ """
126
+ try:
127
+ self.ind_grabbed = 0 # to keep track of the current image in the average
128
+ self.Naverage = Naverage
129
+ self.controller.set_remote_name(self.communicator.full_name)
130
+ self.controller.send_data(grabber_type=self.grabber_type)
131
+
132
+ except Exception as e:
133
+ self.emit_status(ThreadCommand('Update_Status', [getLineInfo() + str(e), "log"]))
134
+
135
+ def stop(self):
136
+ """
137
+ not implemented.
138
+ """
139
+ pass
140
+ return ""
141
+
142
+ # Methods for RPC calls
143
+ def set_x_axis(self, data, label: str = "", units: str = ""):
144
+ # TODO make to work
145
+ self.x_axis = dict(data=data, label=label, units=units)
146
+ self.emit_x_axis()
147
+
148
+ def set_y_axis(self, data, label: str = "", units: str = ""):
149
+ # TODO make to work
150
+ self.y_axis = dict(data=data, label=label, units=units)
151
+ self.emit_y_axis()
152
+
153
+ def set_data(self, data: Union[list, str]) -> None:
154
+ """
155
+ Set the grabbed data signal.
156
+
157
+ corresponds to the "data_ready" signal
158
+
159
+ :param data: If None, look for the additional object
160
+ """
161
+ if isinstance(data, str):
162
+ deserializer = DeSerializer.from_b64_string(data)
163
+ dte = deserializer.dte_deserialization()
164
+ self.dte_signal.emit(dte)
165
+ else:
166
+ raise NotImplementedError("Not implemented to set a list of values.")
167
+
168
+
169
+ if __name__ == '__main__':
170
+ main(__file__)
@@ -0,0 +1,2 @@
1
+ [LocalizedFileNames]
2
+ Command Prompt.lnk=@%SystemRoot%\system32\shell32.dll,-22022
@@ -0,0 +1,58 @@
1
+ """
2
+ Utils for the Director Modules
3
+
4
+ These directors correspond to the PymodaqListener
5
+ """
6
+
7
+ from typing import Optional, Union, List
8
+
9
+ from pyleco.directors.director import Director
10
+
11
+ import pymodaq.utils.parameter.utils as putils
12
+ from pymodaq.utils.parameter import Parameter, ioxml
13
+ from pymodaq.control_modules.move_utility_classes import DataActuator
14
+ from pymodaq.utils.leco.utils import serialize_object
15
+
16
+
17
+ class GenericDirector(Director):
18
+ """Director helper to control some Module remotely."""
19
+
20
+ def set_remote_name(self, name: Optional[str] = None):
21
+ """Set the remote name of the Module (i.e. where it should send responses to)."""
22
+ self.ask_rpc(method="set_remote_name", name=name or self.communicator.name)
23
+
24
+ def set_info(self, param: Parameter):
25
+ # It removes the first two parts (main_settings and detector_settings?)
26
+ self.set_info_str(path=putils.get_param_path(param)[2:],
27
+ param_dict_str=ioxml.parameter_to_xml_string(param).decode())
28
+
29
+ def set_info_str(self, path: List[str], param_dict_str: str) -> None:
30
+ self.ask_rpc(method="sef_info", path=path, param_dict_str=param_dict_str)
31
+
32
+
33
+ class DetectorDirector(GenericDirector):
34
+ def send_data(self, grabber_type: str = "") -> None:
35
+ self.ask_rpc("send_data", grabber_type=grabber_type)
36
+
37
+
38
+ class ActuatorDirector(GenericDirector):
39
+ def move_abs(self, position: Union[float, DataActuator]) -> None:
40
+ self.ask_rpc("move_abs", position=serialize_object(position))
41
+
42
+ def move_rel(self, position: Union[float, DataActuator]) -> None:
43
+ self.ask_rpc("move_rel", position=serialize_object(position))
44
+
45
+ def move_home(self) -> None:
46
+ self.ask_rpc("move_home")
47
+
48
+ def get_actuator_value(self) -> None:
49
+ """Request that the actuator value is sent later on.
50
+
51
+ Later the `set_data` method will be called.
52
+ """
53
+ # according to DAQ_Move, this supersedes "check_position"
54
+ self.ask_rpc("get_actuator_value")
55
+
56
+ def stop_motion(self,) -> None:
57
+ # not implemented in DAQ_Move!
58
+ self.ask_rpc("stop_motion")
@@ -0,0 +1,88 @@
1
+
2
+ import random
3
+
4
+ from typing import Callable, Sequence, List
5
+
6
+ import pymodaq.utils.parameter.utils as putils
7
+ # object used to send info back to the main thread:
8
+ from pymodaq.utils.daq_utils import ThreadCommand
9
+ from pymodaq.utils.parameter import Parameter
10
+
11
+ from pymodaq.utils.leco.director_utils import GenericDirector
12
+ from pymodaq.utils.leco.pymodaq_listener import PymodaqListener
13
+
14
+
15
+ leco_parameters = [
16
+ {'title': 'Actor name:', 'name': 'actor_name', 'type': 'str', 'value': "actor_name",
17
+ 'text': 'Name of the actor plugin to communicate with.'},
18
+ ]
19
+
20
+
21
+ class LECODirector:
22
+ """
23
+ This is a mixin for a Control module to direct another, remote module (analogous to TCP Server).
24
+
25
+ ================= ==============================
26
+ **Attributes** **Type**
27
+ *command_server* instance of Signal
28
+ *x_axis* 1D numpy array
29
+ *y_axis* 1D numpy array
30
+ *data* double precision float array
31
+ ================= ==============================
32
+
33
+ See Also
34
+ --------
35
+ utility_classes.DAQ_TCP_server
36
+ """
37
+ message_list = ["Quit", "Status", "Done", "Server Closed", "Info", "Infos", "Info_xml",
38
+ "move_abs", 'move_home', 'move_rel', 'get_actuator_value', 'stop_motion',
39
+ 'position_is', 'move_done',
40
+ ]
41
+ socket_types: List[str]
42
+
43
+ controller: GenericDirector
44
+ settings: Parameter
45
+
46
+ def __init__(self, **kwargs) -> None:
47
+ super().__init__(**kwargs)
48
+
49
+ name = f'{self._title}_{random.randrange(0, 10000)}_director'
50
+ # TODO use the same Listener instance as the LECOActorModule
51
+ self.listener = PymodaqListener(name=name)
52
+ self.listener.start_listen()
53
+ self.communicator = self.listener.get_communicator()
54
+ self.register_rpc_methods((
55
+ self.set_info,
56
+ ))
57
+
58
+ def register_rpc_methods(self, methods: Sequence[Callable]) -> None:
59
+ for method in methods:
60
+ self.communicator.register_rpc_method(method=method)
61
+
62
+ def commit_settings(self, param: Parameter) -> None:
63
+ raise NotImplementedError
64
+
65
+ def commit_leco_settings(self, param: Parameter) -> None:
66
+ if param.name() == "actor_name":
67
+ self.controller.actor = param.value()
68
+ elif param.name() in putils.iter_children(self.settings.child('settings_client'), []):
69
+ self.controller.set_info(param=param)
70
+
71
+ def close(self) -> None:
72
+ self.listener.stop_listen()
73
+
74
+ def stop(self):
75
+ """
76
+ not implemented.
77
+ """
78
+ pass
79
+ return ""
80
+
81
+ def emit_status(self, status: ThreadCommand) -> None:
82
+ """ Emit the status_sig signal with the given status ThreadCommand back to the main GUI.
83
+ """
84
+ super().emit_status(status=status) # type: ignore
85
+
86
+ # Methods accessible via remote calls
87
+ def set_info(self, path: List[str], param_dict_str: str) -> None:
88
+ self.emit_status(ThreadCommand("set_info", attribute=[path, param_dict_str]))