pymodaq 5.0.17__py3-none-any.whl → 5.1.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.

Files changed (92) hide show
  1. pymodaq/__init__.py +23 -11
  2. pymodaq/control_modules/__init__.py +1 -0
  3. pymodaq/control_modules/daq_move.py +458 -246
  4. pymodaq/control_modules/daq_move_ui/__init__.py +0 -0
  5. pymodaq/control_modules/daq_move_ui/factory.py +48 -0
  6. pymodaq/control_modules/{daq_move_ui.py → daq_move_ui/ui_base.py} +168 -210
  7. pymodaq/control_modules/daq_move_ui/uis/__init__.py +0 -0
  8. pymodaq/control_modules/daq_move_ui/uis/binary.py +139 -0
  9. pymodaq/control_modules/daq_move_ui/uis/original.py +120 -0
  10. pymodaq/control_modules/daq_move_ui/uis/relative.py +124 -0
  11. pymodaq/control_modules/daq_move_ui/uis/simple.py +126 -0
  12. pymodaq/control_modules/daq_viewer.py +113 -101
  13. pymodaq/control_modules/daq_viewer_ui.py +41 -31
  14. pymodaq/control_modules/mocks.py +2 -2
  15. pymodaq/control_modules/move_utility_classes.py +113 -41
  16. pymodaq/control_modules/thread_commands.py +137 -0
  17. pymodaq/control_modules/ui_utils.py +72 -0
  18. pymodaq/control_modules/utils.py +107 -63
  19. pymodaq/control_modules/viewer_utility_classes.py +13 -17
  20. pymodaq/dashboard.py +1294 -625
  21. pymodaq/examples/qt_less_standalone_module.py +48 -11
  22. pymodaq/extensions/__init__.py +8 -3
  23. pymodaq/extensions/adaptive/__init__.py +2 -0
  24. pymodaq/extensions/adaptive/adaptive_optimization.py +179 -0
  25. pymodaq/extensions/adaptive/loss_function/_1d_loss_functions.py +73 -0
  26. pymodaq/extensions/adaptive/loss_function/_2d_loss_functions.py +73 -0
  27. pymodaq/extensions/adaptive/loss_function/__init__.py +3 -0
  28. pymodaq/extensions/adaptive/loss_function/loss_factory.py +110 -0
  29. pymodaq/extensions/adaptive/utils.py +123 -0
  30. pymodaq/extensions/bayesian/__init__.py +1 -1
  31. pymodaq/extensions/bayesian/acquisition/__init__.py +2 -0
  32. pymodaq/extensions/bayesian/acquisition/acquisition_function_factory.py +80 -0
  33. pymodaq/extensions/bayesian/acquisition/base_acquisition_function.py +105 -0
  34. pymodaq/extensions/bayesian/bayesian_optimization.py +143 -0
  35. pymodaq/extensions/bayesian/utils.py +71 -297
  36. pymodaq/extensions/daq_logger/daq_logger.py +7 -12
  37. pymodaq/extensions/daq_logger/h5logging.py +1 -1
  38. pymodaq/extensions/daq_scan.py +30 -55
  39. pymodaq/extensions/data_mixer/__init__.py +0 -0
  40. pymodaq/extensions/data_mixer/daq_0Dviewer_DataMixer.py +97 -0
  41. pymodaq/extensions/data_mixer/data_mixer.py +262 -0
  42. pymodaq/extensions/data_mixer/model.py +108 -0
  43. pymodaq/extensions/data_mixer/models/__init__.py +0 -0
  44. pymodaq/extensions/data_mixer/models/equation_model.py +91 -0
  45. pymodaq/extensions/data_mixer/models/gaussian_fit_model.py +65 -0
  46. pymodaq/extensions/data_mixer/parser.py +53 -0
  47. pymodaq/extensions/data_mixer/utils.py +23 -0
  48. pymodaq/extensions/h5browser.py +3 -34
  49. pymodaq/extensions/optimizers_base/__init__.py +0 -0
  50. pymodaq/extensions/optimizers_base/optimizer.py +1016 -0
  51. pymodaq/extensions/optimizers_base/thread_commands.py +22 -0
  52. pymodaq/extensions/optimizers_base/utils.py +427 -0
  53. pymodaq/extensions/pid/actuator_controller.py +3 -2
  54. pymodaq/extensions/pid/daq_move_PID.py +107 -30
  55. pymodaq/extensions/pid/pid_controller.py +613 -287
  56. pymodaq/extensions/pid/utils.py +8 -5
  57. pymodaq/extensions/utils.py +17 -2
  58. pymodaq/resources/config_template.toml +57 -0
  59. pymodaq/resources/preset_default.xml +1 -1
  60. pymodaq/utils/config.py +13 -4
  61. pymodaq/utils/daq_utils.py +14 -0
  62. pymodaq/utils/data.py +1 -0
  63. pymodaq/utils/gui_utils/loader_utils.py +25 -15
  64. pymodaq/utils/h5modules/module_saving.py +134 -22
  65. pymodaq/utils/leco/daq_move_LECODirector.py +123 -84
  66. pymodaq/utils/leco/daq_xDviewer_LECODirector.py +84 -97
  67. pymodaq/utils/leco/director_utils.py +32 -16
  68. pymodaq/utils/leco/leco_director.py +104 -27
  69. pymodaq/utils/leco/pymodaq_listener.py +186 -97
  70. pymodaq/utils/leco/rpc_method_definitions.py +43 -0
  71. pymodaq/utils/leco/utils.py +25 -25
  72. pymodaq/utils/managers/batchscan_manager.py +12 -11
  73. pymodaq/utils/managers/modules_manager.py +74 -33
  74. pymodaq/utils/managers/overshoot_manager.py +11 -10
  75. pymodaq/utils/managers/preset_manager.py +100 -64
  76. pymodaq/utils/managers/preset_manager_utils.py +163 -107
  77. pymodaq/utils/managers/remote_manager.py +21 -16
  78. pymodaq/utils/scanner/scan_factory.py +18 -4
  79. pymodaq/utils/scanner/scan_selector.py +1 -3
  80. pymodaq/utils/scanner/scanner.py +35 -6
  81. pymodaq/utils/scanner/scanners/_1d_scanners.py +15 -46
  82. pymodaq/utils/scanner/scanners/_2d_scanners.py +21 -68
  83. pymodaq/utils/scanner/scanners/sequential.py +50 -31
  84. pymodaq/utils/scanner/scanners/tabular.py +45 -28
  85. {pymodaq-5.0.17.dist-info → pymodaq-5.1.0.dist-info}/METADATA +7 -6
  86. pymodaq-5.1.0.dist-info/RECORD +154 -0
  87. {pymodaq-5.0.17.dist-info → pymodaq-5.1.0.dist-info}/entry_points.txt +0 -2
  88. pymodaq/extensions/bayesian/bayesian_optimisation.py +0 -685
  89. pymodaq/utils/leco/desktop.ini +0 -2
  90. pymodaq-5.0.17.dist-info/RECORD +0 -121
  91. {pymodaq-5.0.17.dist-info → pymodaq-5.1.0.dist-info}/WHEEL +0 -0
  92. {pymodaq-5.0.17.dist-info → pymodaq-5.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -16,6 +16,7 @@ from typing import List, Tuple, TYPE_CHECKING
16
16
 
17
17
  import numpy as np
18
18
  from qtpy import QtWidgets, QtCore, QtGui
19
+ from qtpy.QtWidgets import QDialogButtonBox
19
20
  from qtpy.QtCore import QObject, Slot, QThread, Signal, QDateTime, QDate, QTime
20
21
 
21
22
  from pymodaq_utils.logger import set_logger, get_module_name
@@ -41,12 +42,15 @@ from pymodaq.extensions.daq_scan_ui import DAQScanUI
41
42
  from pymodaq.utils.h5modules import module_saving
42
43
  from pymodaq.utils.scanner.scan_selector import ScanSelector, SelectorItem
43
44
  from pymodaq.utils.data import DataActuator
45
+ from pymodaq.utils.config import Config as ControlModulesConfig
44
46
 
45
47
 
46
48
  if TYPE_CHECKING:
47
49
  from pymodaq.dashboard import DashBoard
48
50
 
49
- config = Config()
51
+ config_utils = Config()
52
+ config = ControlModulesConfig()
53
+
50
54
 
51
55
  logger = set_logger(get_module_name(__file__))
52
56
 
@@ -146,7 +150,7 @@ class DAQScan(QObject, ParameterManager):
146
150
  self.curvilinear_values = []
147
151
  self.plot_colors = utils.plot_colors
148
152
 
149
- self.scan_thread: QThread = None
153
+ self.runner_thread: QThread = None
150
154
  self._h5saver: H5Saver = None
151
155
  self._module_and_data_saver: module_saving.ScanSaver = None
152
156
 
@@ -297,14 +301,14 @@ class DAQScan(QObject, ParameterManager):
297
301
  # params about dataset attributes and scan attibutes
298
302
  date = QDateTime(QDate.currentDate(), QTime.currentTime())
299
303
  params_dataset = [{'title': 'Dataset information', 'name': 'dataset_info', 'type': 'group', 'children': [
300
- {'title': 'Author:', 'name': 'author', 'type': 'str', 'value': config['user']['name']},
304
+ {'title': 'Author:', 'name': 'author', 'type': 'str', 'value': config_utils['user']['name']},
301
305
  {'title': 'Date/time:', 'name': 'date_time', 'type': 'date_time', 'value': date},
302
306
  {'title': 'Sample:', 'name': 'sample', 'type': 'str', 'value': ''},
303
307
  {'title': 'Experiment type:', 'name': 'experiment_type', 'type': 'str', 'value': ''},
304
308
  {'title': 'Description:', 'name': 'description', 'type': 'text', 'value': ''}]}]
305
309
 
306
310
  params_scan = [{'title': 'Scan information', 'name': 'scan_info', 'type': 'group', 'children': [
307
- {'title': 'Author:', 'name': 'author', 'type': 'str', 'value': config['user']['name']},
311
+ {'title': 'Author:', 'name': 'author', 'type': 'str', 'value': config_utils['user']['name']},
308
312
  {'title': 'Date/time:', 'name': 'date_time', 'type': 'date_time', 'value': date},
309
313
  {'title': 'Scan type:', 'name': 'scan_type', 'type': 'str', 'value': ''},
310
314
  {'title': 'Scan subtype:', 'name': 'scan_sub_type', 'type': 'str', 'value': ''},
@@ -380,9 +384,9 @@ class DAQScan(QObject, ParameterManager):
380
384
 
381
385
  vlayout.addWidget(tree)
382
386
  dialog.setLayout(vlayout)
383
- buttonBox = QtWidgets.QDialogButtonBox(parent=dialog)
384
- buttonBox.addButton('Cancel', buttonBox.RejectRole)
385
- buttonBox.addButton('Apply', buttonBox.AcceptRole)
387
+ buttonBox = QDialogButtonBox(parent=dialog)
388
+ buttonBox.addButton("Cancel", QDialogButtonBox.ButtonRole.RejectRole)
389
+ buttonBox.addButton("Apply", QDialogButtonBox.ButtonRole.AcceptRole)
386
390
  buttonBox.rejected.connect(dialog.reject)
387
391
  buttonBox.accepted.connect(dialog.accept)
388
392
 
@@ -521,7 +525,7 @@ class DAQScan(QObject, ParameterManager):
521
525
  @property
522
526
  def h5saver(self):
523
527
  if self._h5saver is None:
524
- self._h5saver = H5Saver(backend=config('general', 'hdf5_backend'))
528
+ self._h5saver = H5Saver(backend=config_utils('general', 'hdf5_backend'))
525
529
  if self._h5saver.h5_file is None:
526
530
  self._h5saver.init_file(update_h5=True)
527
531
  if not self._h5saver.isopen():
@@ -600,7 +604,7 @@ class DAQScan(QObject, ParameterManager):
600
604
  actuators = self.modules_manager.actuators
601
605
  dte = data_mod.DataToExport(name="move_at")
602
606
  for ind, pos in enumerate(positions):
603
- dte.append(DataActuator(actuators[ind].title, data=float(pos)))
607
+ dte.append(DataActuator(actuators[ind].title, data=float(pos), units=actuators[ind].units))
604
608
 
605
609
  self.modules_manager.move_actuators(dte, polling=False)
606
610
 
@@ -880,6 +884,13 @@ class DAQScan(QObject, ParameterManager):
880
884
  self._metada_dataset_set = True
881
885
  return res
882
886
 
887
+ def exit_runner_thread(self, duration : int = 5000):
888
+ self.runner_thread.quit()
889
+ terminated = self.runner_thread.wait(duration)
890
+ if not terminated:
891
+ self.runner_thread.terminate()
892
+ self.runner_thread.wait()
893
+
883
894
  def start_scan(self):
884
895
  """
885
896
  Start an acquisition calling the set_scan function.
@@ -919,27 +930,24 @@ class DAQScan(QObject, ParameterManager):
919
930
  self.module_and_data_saver.h5saver = self.h5saver # force the update as the h5saver ill also be set on each detectors
920
931
 
921
932
  # mandatory to deal with multithreads
922
- if self.scan_thread is not None:
933
+ if self.runner_thread is not None:
923
934
  self.command_daq_signal.disconnect()
924
- if self.scan_thread.isRunning():
925
- self.scan_thread.terminate()
926
- while not self.scan_thread.isFinished():
927
- QThread.msleep(100)
928
- self.scan_thread = None
935
+ self.exit_runner_thread()
936
+ self.runner_thread = None
929
937
 
930
- self.scan_thread = QThread()
938
+ self.runner_thread = QThread()
931
939
 
932
940
  scan_acquisition = DAQScanAcquisition(self.settings, self.scanner, self.modules_manager,
933
941
  )
934
942
 
935
943
  if config['scan']['scan_in_thread']:
936
- scan_acquisition.moveToThread(self.scan_thread)
944
+ scan_acquisition.moveToThread(self.runner_thread)
937
945
  self.command_daq_signal[utils.ThreadCommand].connect(scan_acquisition.queue_command)
938
946
  scan_acquisition.scan_data_tmp[ScanDataTemp].connect(self.save_temp_live_data)
939
947
  scan_acquisition.status_sig[utils.ThreadCommand].connect(self.thread_status)
940
948
 
941
- self.scan_thread.scan_acquisition = scan_acquisition
942
- self.scan_thread.start()
949
+ self.runner_thread.scan_acquisition = scan_acquisition
950
+ self.runner_thread.start()
943
951
 
944
952
  self.ui.set_action_enabled('ini_positions', False)
945
953
  self.ui.set_action_enabled('start', False)
@@ -992,7 +1000,8 @@ class DAQScan(QObject, ParameterManager):
992
1000
  self.ui.set_permanent_status('Stoping acquisition')
993
1001
  self.command_daq_signal.emit(utils.ThreadCommand("stop_acquisition"))
994
1002
  scan_node = self.module_and_data_saver.get_last_node()
995
- scan_node.attrs['scan_done'] = True
1003
+ if scan_node is not None:
1004
+ scan_node.attrs['scan_done'] = True
996
1005
 
997
1006
  if not self.dashboard.overshoot:
998
1007
  self.set_ini_positions() # do not set ini position again in case overshoot fired
@@ -1095,7 +1104,6 @@ class DAQScanAcquisition(QObject):
1095
1104
 
1096
1105
  def start_acquisition(self):
1097
1106
  try:
1098
- #todo hoaw to apply newlayout to adaptive mode? => cannot has to be a new extension
1099
1107
 
1100
1108
  self.modules_manager.connect_actuators()
1101
1109
  self.modules_manager.connect_detectors()
@@ -1120,20 +1128,6 @@ class DAQScanAcquisition(QObject):
1120
1128
  positions = self.scanner.positions_at(self.ind_scan) # get positions
1121
1129
  else:
1122
1130
  pass
1123
- #todo update for v4
1124
- # positions = learner.ask(1)[0][-1] # next point to probe
1125
- # if self.scanner.scan_type == 'Tabular': # translate normalized curvilinear position to real coordinates
1126
- # self.curvilinear = positions
1127
- # length = 0.
1128
- # for v in self.scanner.vectors:
1129
- # length += v.norm()
1130
- # if length >= self.curvilinear:
1131
- # vec = v
1132
- # frac_curvilinear = (self.curvilinear - (length - v.norm())) / v.norm()
1133
- # break
1134
- #
1135
- # position = (vec.vectorize() * frac_curvilinear).translate_to(vec.p1()).p2()
1136
- # positions = [position.x(), position.y()]
1137
1131
 
1138
1132
  self.status_sig.emit(
1139
1133
  utils.ThreadCommand("Update_scan_index",
@@ -1148,21 +1142,7 @@ class DAQScanAcquisition(QObject):
1148
1142
  QThread.msleep(self.scan_settings['time_flow', 'wait_time_between'])
1149
1143
 
1150
1144
  #grab datas and wait for grab completion
1151
- self.det_done(self.modules_manager.grab_datas(positions=positions), positions)
1152
-
1153
- if self.isadaptive:
1154
- #todo update for v4
1155
- # det_channel = self.modules_manager.get_selected_probed_data()
1156
- # det, channel = det_channel[0].split('/')
1157
- # if self.scanner.scan_type == 'Tabular':
1158
- # self.curvilinear_array.append(np.array([self.curvilinear]))
1159
- # new_positions = self.curvilinear
1160
- # elif self.scanner.scan_type == 'Scan1D':
1161
- # new_positions = positions[0]
1162
- # else:
1163
- # new_positions = positions[:]
1164
- # learner.tell(new_positions, self.modules_manager.det_done_datas[det]['data0D'][channel]['data'])
1165
- pass
1145
+ self.det_done(self.modules_manager.grab_data(positions=positions), positions)
1166
1146
 
1167
1147
  # daq_scan wait time
1168
1148
  QThread.msleep(self.scan_settings.child('time_flow', 'wait_time').value())
@@ -1201,11 +1181,6 @@ class DAQScanAcquisition(QObject):
1201
1181
  utils.ThreadCommand("add_data",
1202
1182
  dict(indexes=indexes, distribution=self.scanner.distribution)))
1203
1183
 
1204
- #todo related to adaptive (solution lies along the Enlargeable data saver)
1205
- if self.isadaptive:
1206
- for ind_ax, nav_axis in enumerate(self.navigation_axes):
1207
- nav_axis.append(np.array(positions[ind_ax]))
1208
-
1209
1184
  self.det_done_flag = True
1210
1185
 
1211
1186
  full_names: list = self.scan_settings['plot_options', 'plot_0d']['selected'][:]
File without changes
@@ -0,0 +1,97 @@
1
+ from typing import TYPE_CHECKING
2
+ import numpy as np
3
+
4
+ from pymodaq_utils.utils import ThreadCommand
5
+ from pymodaq_data.data import DataToExport
6
+ from pymodaq_gui.parameter import Parameter
7
+
8
+ from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main
9
+ from pymodaq.utils.data import DataFromPlugins
10
+
11
+ if TYPE_CHECKING:
12
+ from pymodaq.extensions.data_mixer import DataMixer
13
+
14
+
15
+ class DAQ_0DViewer_DataMixer(DAQ_Viewer_base):
16
+ """ Instrument plugin class for a OD viewer.
17
+
18
+ This object inherits all functionalities to communicate with PyMoDAQ’s DAQ_Viewer module through inheritance via
19
+ DAQ_Viewer_base. It makes a bridge between the DAQ_Viewer module and the Python wrapper of a particular instrument.
20
+
21
+ Attributes:
22
+ -----------
23
+ controller: object
24
+ The particular object that allow the communication with the hardware, in general a python wrapper around the
25
+ hardware library.
26
+
27
+ """
28
+ params = (comon_parameters+
29
+ [
30
+ {'title': 'Related Detectors', 'name': 'overridden_detectors', 'type': 'list',
31
+ 'readonly': True} # mandatory to know what detectors are related to the DataMixer
32
+ ])
33
+
34
+ def ini_attributes(self):
35
+ self.controller: DataMixer = None
36
+
37
+ def commit_settings(self, param: Parameter):
38
+ """Apply the consequences of a change of value in the detector settings
39
+
40
+ Parameters
41
+ ----------
42
+ param: Parameter
43
+ A given parameter (within detector_settings) whose value has been changed by the user
44
+ """
45
+ pass
46
+
47
+ def ini_detector(self, controller=None):
48
+ """Detector communication initialization
49
+
50
+ Parameters
51
+ ----------
52
+ controller: (object)
53
+ custom object of a PyMoDAQ plugin (Slave case). None if only one actuator/detector by controller
54
+ (Master case)
55
+
56
+ Returns
57
+ -------
58
+ info: str
59
+ initialized: bool
60
+ False if initialization failed otherwise True
61
+ """
62
+
63
+ self.controller: DataMixer = controller
64
+ if self.controller is not None:
65
+ self.controller.dte_computed_signal.connect(self.grab_done)
66
+
67
+ info = "DataMixer Detector Initialized"
68
+ initialized = True
69
+ return info, initialized
70
+
71
+ def close(self):
72
+ """Terminate the communication protocol"""
73
+ pass
74
+
75
+ def grab_done(self, dte: DataToExport):
76
+ self.dte_signal.emit(dte)
77
+
78
+ def grab_data(self, Naverage=1, **kwargs):
79
+ """Start a grab from the detector
80
+
81
+ Parameters
82
+ ----------
83
+ Naverage: int
84
+ Number of hardware averaging (if hardware averaging is possible, self.hardware_averaging should be set to
85
+ True in class preamble and you should code this implementation)
86
+ kwargs: dict
87
+ others optionals arguments
88
+ """
89
+ self.controller.snap()
90
+
91
+ def stop(self):
92
+ """Stop the current grab hardware wise if necessary"""
93
+ return ''
94
+
95
+
96
+ if __name__ == '__main__':
97
+ main(__file__)
@@ -0,0 +1,262 @@
1
+ from qtpy import QtWidgets, QtCore
2
+ import numpy as np
3
+ from pathlib import Path
4
+
5
+ from typing import Optional
6
+
7
+ from pymodaq_gui import utils as gutils
8
+ from pymodaq_utils.config import Config, ConfigError
9
+ from pymodaq_utils.logger import set_logger, get_module_name
10
+ from pymodaq_utils.utils import find_dict_in_list_from_key_val
11
+ from pymodaq_data.data import DataToExport, DataWithAxes
12
+
13
+ from pymodaq.utils.config import Config as PyMoConfig
14
+ from pymodaq.extensions.utils import CustomExt
15
+
16
+
17
+ from pymodaq_gui.plotting.data_viewers.viewer import ViewerDispatcher
18
+ from pymodaq_gui.utils.widgets.qled import QLED
19
+ from pymodaq_gui.parameter import utils as putils
20
+
21
+
22
+ from pymodaq.extensions.data_mixer.model import get_models, DataMixerModel
23
+ from pymodaq.extensions.data_mixer.utils import DataMixerConfig, find_key_in_nested_dict
24
+
25
+ logger = set_logger(get_module_name(__file__))
26
+
27
+ config_utils = Config()
28
+ config_pymodaq = PyMoConfig()
29
+
30
+ EXTENSION_NAME = 'Data Mixer' # the name that will be displayed in the extension list in the
31
+ # dashboard
32
+ CLASS_NAME = 'DataMixer' # this should be the name of your class defined below
33
+
34
+
35
+ class DataMixer(CustomExt):
36
+ settings_name = 'DataMixerSettings'
37
+ models = get_models()
38
+ params = [
39
+ {'title': 'Models', 'name': 'models', 'type': 'group', 'expanded': True, 'visible': True,
40
+ 'children': [
41
+ {'title': 'Models class:', 'name': 'model_class', 'type': 'list',
42
+ 'limits': [d['name'] for d in models]},
43
+ {'title': 'Ini Model', 'name': 'ini_model', 'type': 'action', },
44
+ {'title': 'Model params:', 'name': 'model_params', 'type': 'group', 'children': []},
45
+
46
+ ]}]
47
+
48
+ dte_computed_signal = QtCore.Signal(DataToExport)
49
+
50
+ def __init__(self, parent: gutils.DockArea, dashboard):
51
+ super().__init__(parent, dashboard)
52
+
53
+ self.model_class: Optional[DataMixerModel] = None
54
+ self.datamixer_config = DataMixerConfig()
55
+ self.setup_ui()
56
+
57
+ self.settings.child('models', 'ini_model').sigActivated.connect(
58
+ self.get_action('ini_model').trigger)
59
+
60
+ def get_set_model_params(self, model_name):
61
+ self.settings.child('models', 'model_params').clearChildren()
62
+ if len(self.models) > 0:
63
+ model_class = find_dict_in_list_from_key_val(self.models, 'name', model_name)['class']
64
+ params = getattr(model_class, 'params')
65
+ self.settings.child('models', 'model_params').addChildren(params)
66
+
67
+
68
+ def setup_docks(self):
69
+ """Mandatory method to be subclassed to setup the docks layout
70
+
71
+ """
72
+ self.docks['settings'] = gutils.Dock('Settings')
73
+ self.dockarea.addDock(self.docks['settings'])
74
+ splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical)
75
+ self.docks['settings'].addWidget(splitter)
76
+ splitter.addWidget(self.modules_manager.settings_tree)
77
+ self.modules_manager.tree.header().setVisible(False)
78
+ self.modules_manager.settings.child('modules', 'actuators').hide()
79
+ self.modules_manager.settings.child('move_done').hide()
80
+ self.modules_manager.settings.child('det_done').hide()
81
+ self.modules_manager.settings.child('data_dimensions',
82
+ 'det_data_list0D').setOpts(height=150)
83
+ self.modules_manager.settings.child('data_dimensions').hide()
84
+ self.modules_manager.settings.child('actuators_positions').hide()
85
+
86
+ splitter.addWidget(self.settings_tree)
87
+
88
+ self.docks['computed'] = gutils.Dock('Computed data')
89
+ self.dockarea.addDock(self.docks['computed'], 'right')
90
+
91
+ self.area_computed = gutils.DockArea()
92
+ self.docks['computed'].addWidget(self.area_computed)
93
+
94
+ self.dte_computed_viewer = ViewerDispatcher(self.area_computed)
95
+
96
+ if len(self.models) != 0:
97
+ self.get_set_model_params(self.models[0]['name'])
98
+
99
+ @property
100
+ def config_path(self) -> Path:
101
+ return self.datamixer_config.config_path
102
+
103
+ def validate_config(self) -> bool:
104
+ """ Read eventually saved settings from self.datamixer_config
105
+
106
+ Example
107
+ -------
108
+ utility = find_key_in_nested_dict(self.datamixer_config.to_dict(), 'prediction')
109
+
110
+ """
111
+ return True
112
+
113
+ def setup_actions(self):
114
+ """Method where to create actions to be subclassed. Mandatory
115
+
116
+ """
117
+ self.add_action('quit', 'Quit', 'close2', "Quit program")
118
+ combo_model = QtWidgets.QComboBox()
119
+ combo_model.addItems([model['name'] for model in self.models])
120
+ self.add_widget('models', combo_model, tip='List of available models')
121
+ self.add_action('ini_model', 'Init Model', 'ini')
122
+ self.add_widget('model_led', QLED, toolbar=self.toolbar)
123
+ self.add_action('snap', 'Snap Detectors', 'snap',
124
+ 'Snap all selected detectors')
125
+ self.add_action('create_computed_detectors', 'Create Computed Detectors', 'Add_Step',
126
+ tip='Create a DAQ_Viewer Control Module')
127
+
128
+ def connect_things(self):
129
+ """Connect actions and/or other widgets signal to methods"""
130
+ self.connect_action('quit', self.quit_fun)
131
+ self.connect_action('models', self.update_model_settings_from_action, signal_name='currentTextChanged')
132
+ self.connect_action('ini_model', self.ini_model)
133
+ self.modules_manager.det_done_signal.connect(self.process_data)
134
+ self.dte_computed_signal.connect(self.plot_computed_results)
135
+ self.connect_action('snap', self.snap)
136
+ self.modules_manager.detectors_changed.connect(self.update_connect_detectors)
137
+ self.connect_action('create_computed_detectors', self.create_computed_detectors)
138
+
139
+ def update_model_settings_from_action(self, model: str):
140
+ self.settings.child('models', 'model_class').setValue(model)
141
+
142
+ def process_data(self, dte: DataToExport):
143
+ if self.model_class is not None:
144
+ dte_computed = self.model_class.process_dte(dte)
145
+ self.dte_computed_signal.emit(dte_computed)
146
+
147
+ def snap(self):
148
+ self.modules_manager.grab_data(check_do_override=False)
149
+
150
+ def create_computed_detectors(self):
151
+ try:
152
+ self.dashboard.add_det_from_extension('DataMixer', 'DAQ0D', 'DataMixer', self)
153
+ self.dashboard.modules_manager.get_mod_from_name(
154
+ 'DataMixer', 'det').settings.child('detector_settings', 'overridden_detectors').setOpts(
155
+ limits=self.modules_manager.selected_detectors_name)
156
+ self.set_action_enabled('create_computed_detectors', False)
157
+ #self.dashboard.override_det_from_extension(self.modules_manager.selected_detectors_name)
158
+ except Exception as e:
159
+ logger.exception(str(e))
160
+ pass
161
+
162
+ def update_connect_detectors(self):
163
+ try:
164
+ self.connect_detectors(False)
165
+ except :
166
+ pass
167
+ self.connect_detectors()
168
+
169
+ def connect_detectors(self, connect=True):
170
+ """Connect detectors to DAQ_Logging do_save_continuous method
171
+
172
+ Parameters
173
+ ----------
174
+ connect: bool
175
+ If True make the connection else disconnect
176
+ """
177
+ self.modules_manager.connect_detectors(connect=connect)
178
+
179
+ def plot_computed_results(self, dte):
180
+ self.dte_computed_viewer.show_data(dte)
181
+
182
+ def ini_model(self):
183
+ if self.model_class is None:
184
+ self.set_model()
185
+
186
+ self.get_action('model_led').set_as_true()
187
+ self.set_action_enabled('ini_model', False)
188
+ self.settings.child('models', 'ini_model').setValue(True)
189
+ self.set_action_enabled('models', False)
190
+ self.settings.child('models', 'model_class').setOpts(enabled=False)
191
+ self.modules_manager.settings_tree.setEnabled(False)
192
+
193
+ self.update_connect_detectors()
194
+
195
+ def set_model(self):
196
+ model_name = self.settings['models', 'model_class']
197
+ self.model_class = find_dict_in_list_from_key_val(
198
+ self.models, 'name', model_name)['class'](self)
199
+ self.model_class.ini_model_base()
200
+
201
+ def setup_menu(self, menubar: QtWidgets.QMenuBar = None):
202
+ """Non mandatory method to be subclassed in order to create a menubar
203
+
204
+ create menu for actions contained into the self._actions, for instance:
205
+
206
+ Examples
207
+ --------
208
+ >>>file_menu = self.mainwindow.menuBar().addMenu('File')
209
+ >>>self.affect_to('load', file_menu)
210
+ >>>self.affect_to('save', file_menu)
211
+
212
+ >>>file_menu.addSeparator()
213
+ >>>self.affect_to('quit', file_menu)
214
+
215
+ See Also
216
+ --------
217
+ pymodaq.utils.managers.action_manager.ActionManager
218
+ """
219
+ # todo create and populate menu using actions defined above in self.setup_actions
220
+ pass
221
+
222
+ def value_changed(self, param):
223
+ """ Actions to perform when one of the param's value in self.settings is changed from the
224
+ user interface
225
+
226
+ For instance:
227
+ if param.name() == 'do_something':
228
+ if param.value():
229
+ print('Do something')
230
+ self.settings.child('main_settings', 'something_done').setValue(False)
231
+
232
+ Parameters
233
+ ----------
234
+ param: (Parameter) the parameter whose value just changed
235
+ """
236
+ if param.name() == 'model_class':
237
+ self.get_set_model_params(param.value())
238
+ self.get_action('models').setCurrentText(param.value())
239
+ elif param.name() in putils.iter_children(self.settings.child('models', 'model_params'), []):
240
+ if self.model_class is not None:
241
+ self.model_class.update_settings(param)
242
+
243
+ def quit_fun(self):
244
+ self.mainwindow.close()
245
+ self.dashboard.remove_modules(['DataMixer'])
246
+
247
+
248
+ def main():
249
+ from pymodaq_gui.utils.utils import mkQApp
250
+ from pymodaq.utils.gui_utils.loader_utils import load_dashboard_with_preset
251
+
252
+ app = mkQApp('DataMixer')
253
+
254
+ preset_file_name = config_pymodaq('presets', 'default_preset_for_datamixer')
255
+ dashboard, extension, win = load_dashboard_with_preset(preset_file_name, EXTENSION_NAME)
256
+ app.exec()
257
+
258
+ return dashboard, extension, win
259
+
260
+
261
+ if __name__ == '__main__':
262
+ main()
@@ -0,0 +1,108 @@
1
+ import dataclasses
2
+ from typing import List, TYPE_CHECKING
3
+ import importlib
4
+ import inspect
5
+ import pkgutil
6
+ import warnings
7
+ from pathlib import Path
8
+ from typing import Union, List
9
+
10
+ import numpy as np # to be imported within models
11
+
12
+ from pymodaq_utils.utils import find_dict_in_list_from_key_val, get_entrypoints
13
+ from pymodaq_utils.logger import set_logger, get_module_name
14
+
15
+ from pymodaq_data.data import DataToExport
16
+
17
+ from pymodaq_gui.managers.parameter_manager import ParameterManager, Parameter
18
+ from pymodaq_gui.config_saver_loader import ConfigSaverLoader
19
+
20
+
21
+ logger = set_logger(get_module_name(__file__))
22
+
23
+ if TYPE_CHECKING:
24
+ from pymodaq.extensions.data_mixer import DataMixer
25
+ from pymodaq.utils.managers.modules_manager import ModulesManager
26
+
27
+
28
+ class DataMixerModel:
29
+
30
+ detectors_name: List[str] = []
31
+ params = []
32
+
33
+ def __init__(self, data_mixer: 'DataMixer'):
34
+ self.data_mixer = data_mixer
35
+ self.modules_manager: ModulesManager = data_mixer.modules_manager
36
+ self.settings: Parameter = self.data_mixer.settings.child('models', 'model_params')
37
+
38
+
39
+ def ini_model_base(self):
40
+ """ Method to add things that should be executed before instantiating the model"""
41
+ self.ini_model()
42
+
43
+ def ini_model(self):
44
+ pass
45
+
46
+ def update_settings(self, param: Parameter):
47
+ pass
48
+
49
+ def process_dte(self, measurements: DataToExport) -> DataToExport:
50
+ """
51
+ Convert the measurements in the units to be fed to the PID (same dimensionality as the setpoint)
52
+ Parameters
53
+ ----------
54
+ measurements: DataToExport
55
+ DataToExport object from which the model extract a value of the same units as the setpoint
56
+
57
+ Returns
58
+ -------
59
+ DataToExport: the converted input as 0D DataCalculated stored in a DataToExport
60
+ """
61
+ raise NotImplementedError
62
+
63
+
64
+ @dataclasses.dataclass
65
+ class PkgMock:
66
+ value: str
67
+
68
+
69
+ def get_models(model_name=None) -> list[dict[(str, str), (str, type)]]:
70
+ """
71
+ Get DataMixer Models
72
+
73
+ Returns
74
+ -------
75
+ list: list of dict containing the name and python module of the found models
76
+
77
+ Example
78
+ -------
79
+ model = [{'name': 'MyModel', 'class': DataModel}]
80
+ """
81
+ models_import = []
82
+ discovered_models = list(get_entrypoints(group='pymodaq.models'))
83
+ discovered_models.append(PkgMock('pymodaq.extensions.data_mixer'))
84
+ if len(discovered_models) > 0:
85
+ for pkg in discovered_models:
86
+ try:
87
+ module = importlib.import_module(pkg.value)
88
+ module_name = pkg.value
89
+
90
+ for mod in pkgutil.iter_modules([str(Path(module.__file__).parent.joinpath('models'))]):
91
+ try:
92
+ model_module = importlib.import_module(f'{module_name}.models.{mod.name}', module)
93
+ classes = inspect.getmembers(model_module, inspect.isclass)
94
+ for name, klass in classes:
95
+ if klass.__base__ is DataMixerModel:
96
+ models_import.append({'name': mod.name, 'module': model_module, 'class': klass})
97
+ break
98
+
99
+ except Exception as e: # pragma: no cover
100
+ logger.warning(str(e))
101
+
102
+ except Exception as e: # pragma: no cover
103
+ logger.warning(f'Impossible to import the {pkg.value} extension: {str(e)}')
104
+
105
+ if model_name is None:
106
+ return models_import
107
+ else:
108
+ return find_dict_in_list_from_key_val(models_import, 'name', model_name)
File without changes