pymodaq 5.1.6__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.
Files changed (154) hide show
  1. pymodaq/__init__.py +98 -0
  2. pymodaq/control_modules/__init__.py +1 -0
  3. pymodaq/control_modules/daq_move.py +1238 -0
  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/ui_base.py +359 -0
  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 +1517 -0
  13. pymodaq/control_modules/daq_viewer_ui.py +407 -0
  14. pymodaq/control_modules/mocks.py +57 -0
  15. pymodaq/control_modules/move_utility_classes.py +1141 -0
  16. pymodaq/control_modules/thread_commands.py +137 -0
  17. pymodaq/control_modules/ui_utils.py +72 -0
  18. pymodaq/control_modules/utils.py +591 -0
  19. pymodaq/control_modules/viewer_utility_classes.py +670 -0
  20. pymodaq/daq_utils/__init__.py +0 -0
  21. pymodaq/daq_utils/daq_utils.py +6 -0
  22. pymodaq/dashboard.py +2396 -0
  23. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Client.aliases +3 -0
  24. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Client.lvlps +3 -0
  25. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Client.lvproj +32 -0
  26. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Client.vi +0 -0
  27. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Server_1Dgaussian.vi +0 -0
  28. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Server_2Dgaussian.vi +0 -0
  29. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_read_cmd.vi +0 -0
  30. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_read_float.vi +0 -0
  31. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_read_int.vi +0 -0
  32. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_send_data.vi +0 -0
  33. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_send_int.vi +0 -0
  34. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_send_scalar.vi +0 -0
  35. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_send_string.vi +0 -0
  36. pymodaq/examples/Labview_TCP_Client/client_state.ctl +0 -0
  37. pymodaq/examples/Labview_TCP_Client/cmd_types.ctl +0 -0
  38. pymodaq/examples/__init__.py +0 -0
  39. pymodaq/examples/function_plotter.py +160 -0
  40. pymodaq/examples/nonlinearscanner.py +126 -0
  41. pymodaq/examples/qt_less_standalone_module.py +165 -0
  42. pymodaq/examples/tcp_client.py +97 -0
  43. pymodaq/extensions/__init__.py +25 -0
  44. pymodaq/extensions/adaptive/__init__.py +2 -0
  45. pymodaq/extensions/adaptive/adaptive_optimization.py +179 -0
  46. pymodaq/extensions/adaptive/loss_function/_1d_loss_functions.py +73 -0
  47. pymodaq/extensions/adaptive/loss_function/_2d_loss_functions.py +73 -0
  48. pymodaq/extensions/adaptive/loss_function/__init__.py +3 -0
  49. pymodaq/extensions/adaptive/loss_function/loss_factory.py +110 -0
  50. pymodaq/extensions/adaptive/utils.py +123 -0
  51. pymodaq/extensions/bayesian/__init__.py +2 -0
  52. pymodaq/extensions/bayesian/acquisition/__init__.py +2 -0
  53. pymodaq/extensions/bayesian/acquisition/acquisition_function_factory.py +80 -0
  54. pymodaq/extensions/bayesian/acquisition/base_acquisition_function.py +105 -0
  55. pymodaq/extensions/bayesian/bayesian_optimization.py +143 -0
  56. pymodaq/extensions/bayesian/utils.py +180 -0
  57. pymodaq/extensions/console.py +73 -0
  58. pymodaq/extensions/daq_logger/__init__.py +1 -0
  59. pymodaq/extensions/daq_logger/abstract.py +52 -0
  60. pymodaq/extensions/daq_logger/daq_logger.py +519 -0
  61. pymodaq/extensions/daq_logger/db/__init__.py +0 -0
  62. pymodaq/extensions/daq_logger/db/db_logger.py +300 -0
  63. pymodaq/extensions/daq_logger/db/db_logger_models.py +100 -0
  64. pymodaq/extensions/daq_logger/h5logging.py +84 -0
  65. pymodaq/extensions/daq_scan.py +1218 -0
  66. pymodaq/extensions/daq_scan_ui.py +241 -0
  67. pymodaq/extensions/data_mixer/__init__.py +0 -0
  68. pymodaq/extensions/data_mixer/daq_0Dviewer_DataMixer.py +97 -0
  69. pymodaq/extensions/data_mixer/data_mixer.py +262 -0
  70. pymodaq/extensions/data_mixer/model.py +108 -0
  71. pymodaq/extensions/data_mixer/models/__init__.py +0 -0
  72. pymodaq/extensions/data_mixer/models/equation_model.py +91 -0
  73. pymodaq/extensions/data_mixer/models/gaussian_fit_model.py +65 -0
  74. pymodaq/extensions/data_mixer/parser.py +53 -0
  75. pymodaq/extensions/data_mixer/utils.py +23 -0
  76. pymodaq/extensions/h5browser.py +9 -0
  77. pymodaq/extensions/optimizers_base/__init__.py +0 -0
  78. pymodaq/extensions/optimizers_base/optimizer.py +1016 -0
  79. pymodaq/extensions/optimizers_base/thread_commands.py +22 -0
  80. pymodaq/extensions/optimizers_base/utils.py +427 -0
  81. pymodaq/extensions/pid/__init__.py +16 -0
  82. pymodaq/extensions/pid/actuator_controller.py +14 -0
  83. pymodaq/extensions/pid/daq_move_PID.py +154 -0
  84. pymodaq/extensions/pid/pid_controller.py +1016 -0
  85. pymodaq/extensions/pid/utils.py +189 -0
  86. pymodaq/extensions/utils.py +111 -0
  87. pymodaq/icon.ico +0 -0
  88. pymodaq/post_treatment/__init__.py +6 -0
  89. pymodaq/post_treatment/load_and_plot.py +352 -0
  90. pymodaq/resources/__init__.py +0 -0
  91. pymodaq/resources/config_template.toml +57 -0
  92. pymodaq/resources/preset_default.xml +1 -0
  93. pymodaq/resources/setup_plugin.py +73 -0
  94. pymodaq/splash.png +0 -0
  95. pymodaq/utils/__init__.py +0 -0
  96. pymodaq/utils/array_manipulation.py +6 -0
  97. pymodaq/utils/calibration_camera.py +180 -0
  98. pymodaq/utils/chrono_timer.py +203 -0
  99. pymodaq/utils/config.py +53 -0
  100. pymodaq/utils/conftests.py +5 -0
  101. pymodaq/utils/daq_utils.py +158 -0
  102. pymodaq/utils/data.py +128 -0
  103. pymodaq/utils/enums.py +6 -0
  104. pymodaq/utils/exceptions.py +38 -0
  105. pymodaq/utils/gui_utils/__init__.py +10 -0
  106. pymodaq/utils/gui_utils/loader_utils.py +75 -0
  107. pymodaq/utils/gui_utils/utils.py +18 -0
  108. pymodaq/utils/gui_utils/widgets/lcd.py +8 -0
  109. pymodaq/utils/h5modules/__init__.py +2 -0
  110. pymodaq/utils/h5modules/module_saving.py +526 -0
  111. pymodaq/utils/leco/__init__.py +25 -0
  112. pymodaq/utils/leco/daq_move_LECODirector.py +217 -0
  113. pymodaq/utils/leco/daq_xDviewer_LECODirector.py +163 -0
  114. pymodaq/utils/leco/director_utils.py +74 -0
  115. pymodaq/utils/leco/leco_director.py +166 -0
  116. pymodaq/utils/leco/pymodaq_listener.py +364 -0
  117. pymodaq/utils/leco/rpc_method_definitions.py +43 -0
  118. pymodaq/utils/leco/utils.py +74 -0
  119. pymodaq/utils/logger.py +6 -0
  120. pymodaq/utils/managers/__init__.py +0 -0
  121. pymodaq/utils/managers/batchscan_manager.py +346 -0
  122. pymodaq/utils/managers/modules_manager.py +589 -0
  123. pymodaq/utils/managers/overshoot_manager.py +242 -0
  124. pymodaq/utils/managers/preset_manager.py +229 -0
  125. pymodaq/utils/managers/preset_manager_utils.py +262 -0
  126. pymodaq/utils/managers/remote_manager.py +484 -0
  127. pymodaq/utils/math_utils.py +6 -0
  128. pymodaq/utils/messenger.py +6 -0
  129. pymodaq/utils/parameter/__init__.py +10 -0
  130. pymodaq/utils/parameter/utils.py +6 -0
  131. pymodaq/utils/scanner/__init__.py +5 -0
  132. pymodaq/utils/scanner/scan_config.py +16 -0
  133. pymodaq/utils/scanner/scan_factory.py +259 -0
  134. pymodaq/utils/scanner/scan_selector.py +477 -0
  135. pymodaq/utils/scanner/scanner.py +324 -0
  136. pymodaq/utils/scanner/scanners/_1d_scanners.py +174 -0
  137. pymodaq/utils/scanner/scanners/_2d_scanners.py +299 -0
  138. pymodaq/utils/scanner/scanners/__init__.py +1 -0
  139. pymodaq/utils/scanner/scanners/sequential.py +224 -0
  140. pymodaq/utils/scanner/scanners/tabular.py +319 -0
  141. pymodaq/utils/scanner/utils.py +110 -0
  142. pymodaq/utils/svg/__init__.py +6 -0
  143. pymodaq/utils/svg/svg_renderer.py +20 -0
  144. pymodaq/utils/svg/svg_view.py +35 -0
  145. pymodaq/utils/svg/svg_viewer2D.py +50 -0
  146. pymodaq/utils/tcp_ip/__init__.py +6 -0
  147. pymodaq/utils/tcp_ip/mysocket.py +12 -0
  148. pymodaq/utils/tcp_ip/serializer.py +13 -0
  149. pymodaq/utils/tcp_ip/tcp_server_client.py +772 -0
  150. pymodaq-5.1.6.dist-info/METADATA +238 -0
  151. pymodaq-5.1.6.dist-info/RECORD +154 -0
  152. pymodaq-5.1.6.dist-info/WHEEL +4 -0
  153. pymodaq-5.1.6.dist-info/entry_points.txt +7 -0
  154. pymodaq-5.1.6.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,1218 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """Automated scanning module functionalities for PyMoDAQ
5
+
6
+ Contains all objects related to the DAQScan module, to do automated scans, saving data...
7
+ """
8
+ from __future__ import annotations
9
+ from collections import OrderedDict
10
+ import logging
11
+ import os
12
+ from pathlib import Path
13
+ import sys
14
+ import tempfile
15
+ from typing import List, Tuple, TYPE_CHECKING
16
+
17
+ import numpy as np
18
+ from qtpy import QtWidgets, QtCore, QtGui
19
+ from qtpy.QtWidgets import QDialogButtonBox
20
+ from qtpy.QtCore import QObject, Slot, QThread, Signal, QDateTime, QDate, QTime
21
+
22
+ from pymodaq_utils.logger import set_logger, get_module_name
23
+ from pymodaq_utils.config import Config
24
+ from pymodaq_utils import utils
25
+
26
+ from pymodaq_data import data as data_mod
27
+ from pymodaq_data.h5modules import data_saving
28
+
29
+ from pymodaq_gui.parameter import ioxml
30
+ from pymodaq_gui.plotting.data_viewers import ViewersEnum
31
+ from pymodaq_gui.managers.parameter_manager import ParameterManager, Parameter, ParameterTree
32
+ from pymodaq_gui.plotting.navigator import Navigator
33
+ from pymodaq_gui.messenger import messagebox
34
+ from pymodaq_gui import utils as gutils
35
+ from pymodaq_gui.h5modules.saving import H5Saver
36
+
37
+ from pymodaq.utils.scanner.scanner import Scanner
38
+ from pymodaq.utils.managers.batchscan_manager import BatchScanner
39
+ from pymodaq.utils.managers.modules_manager import ModulesManager
40
+ from pymodaq.post_treatment.load_and_plot import LoaderPlotter
41
+ from pymodaq.extensions.daq_scan_ui import DAQScanUI
42
+ from pymodaq.utils.h5modules import module_saving
43
+ from pymodaq.utils.scanner.scan_selector import ScanSelector, SelectorItem
44
+ from pymodaq.utils.data import DataActuator
45
+ from pymodaq.utils.config import Config as ControlModulesConfig
46
+
47
+
48
+ if TYPE_CHECKING:
49
+ from pymodaq.dashboard import DashBoard
50
+
51
+ config_utils = Config()
52
+ config = ControlModulesConfig()
53
+
54
+
55
+ logger = set_logger(get_module_name(__file__))
56
+
57
+ SHOW_POPUPS = config('scan', 'show_popups')
58
+
59
+
60
+ class DAQ_ScanException(Exception):
61
+ """Raised when an error occur within the DAQScan"""
62
+ pass
63
+
64
+
65
+ class ScanDataTemp:
66
+ """Convenience class to hold temporary data to be plotted in the live plots"""
67
+ def __init__(self, scan_index: int, indexes: Tuple[int], data: data_mod.DataToExport):
68
+ self.scan_index = scan_index
69
+ self.indexes = indexes
70
+ self.data = data
71
+
72
+
73
+ class DAQScan(QObject, ParameterManager):
74
+ """
75
+ Main class initializing a DAQScan module with its dashboard and scanning control panel
76
+ """
77
+ settings_name = 'daq_scan_settings'
78
+ command_daq_signal = Signal(utils.ThreadCommand)
79
+ status_signal = Signal(str)
80
+ live_data_1D_signal = Signal(list)
81
+
82
+ params = [
83
+ {'title': 'Time Flow:', 'name': 'time_flow', 'type': 'group', 'expanded': False,
84
+ 'children': [
85
+ {'title': 'Wait time step (ms)', 'name': 'wait_time', 'type': 'int', 'value': 0,
86
+ 'tip': 'Wait time in ms after each step of acquisition (move and grab)'},
87
+ {'title': 'Wait time between (ms)', 'name': 'wait_time_between', 'type': 'int',
88
+ 'value': 0,
89
+ 'tip': 'Wait time in ms between move and grab processes'},
90
+ {'title': 'Timeout (ms)', 'name': 'timeout', 'type': 'int', 'value': 10000},
91
+ ]},
92
+ {'title': 'Scan options', 'name': 'scan_options', 'type': 'group', 'children': [
93
+ {'title': 'Naverage:', 'name': 'scan_average', 'type': 'int',
94
+ 'value': config('scan', 'Naverage'), 'min': 1},
95
+ {'title': 'Plot on top:', 'name': 'average_on_top', 'type': 'bool',
96
+ 'value': config('scan', 'average_on_top'),
97
+ 'tip': 'At the second iteration will plot the averaged scan on top (True) of the current one'
98
+ 'or in a second panel (False)'},
99
+ ]},
100
+
101
+ {'title': 'Plotting options', 'name': 'plot_options', 'type': 'group', 'children': [
102
+ {'title': 'Get data', 'name': 'plot_probe', 'type': 'bool_push'},
103
+ {'title': 'Group 0D data:', 'name': 'group0D', 'type': 'bool', 'value': True},
104
+ {'title': 'Plot 0Ds:', 'name': 'plot_0d', 'type': 'itemselect', 'checkbox': True},
105
+ {'title': 'Plot 1Ds:', 'name': 'plot_1d', 'type': 'itemselect', 'checkbox': True},
106
+ {'title': 'Prepare Viewers', 'name': 'prepare_viewers', 'type': 'bool_push'},
107
+ {'title': 'Plot at each step?', 'name': 'plot_at_each_step', 'type': 'bool',
108
+ 'value': True},
109
+ {'title': 'Refresh Plots (ms)', 'name': 'refresh_live', 'type': 'int',
110
+ 'value': 1000, 'visible': False},
111
+ ]},
112
+ ]
113
+
114
+ def __init__(self, dockarea: gutils.DockArea = None, dashboard: DashBoard = None):
115
+ """
116
+
117
+ Parameters
118
+ ----------
119
+ dockarea: DockArea
120
+ instance of the modified pyqtgraph Dockarea
121
+ dashboard: DashBoard
122
+ instance of the pymodaq dashboard
123
+
124
+ """
125
+
126
+ logger.info('Initializing DAQScan')
127
+ QObject.__init__(self)
128
+ ParameterManager.__init__(self)
129
+
130
+ self.title = __class__.__name__
131
+
132
+ self.dockarea: gutils.DockArea = dockarea
133
+ self.dashboard: DashBoard = dashboard
134
+ if dashboard is None:
135
+ raise Exception('No valid dashboard initialized')
136
+
137
+ self.mainwindow = self.dockarea.parent()
138
+ self.ui: DAQScanUI = DAQScanUI(self.dockarea)
139
+
140
+ self.wait_time = 1000
141
+
142
+ self.navigator: Navigator = None
143
+ self.scan_selector: ScanSelector = None
144
+
145
+ self.ind_scan = 0
146
+ self.ind_average = 0
147
+
148
+ self._metada_dataset_set = False
149
+
150
+ self.curvilinear_values = []
151
+ self.plot_colors = utils.plot_colors
152
+
153
+ self.runner_thread: QThread = None
154
+
155
+ self.modules_manager = ModulesManager(self.dashboard.detector_modules, self.dashboard.actuators_modules)
156
+ self.modules_manager.settings.child('data_dimensions').setOpts(expanded=False)
157
+ self.modules_manager.settings.child('actuators_positions').setOpts(expanded=False)
158
+ self.modules_manager.detectors_changed.connect(self.clear_plot_from)
159
+
160
+
161
+ self._h5saver = H5Saver(backend=config_utils('general', 'hdf5_backend'))
162
+ self._h5saver.settings.child('do_save').hide()
163
+ self._h5saver.settings.child('custom_name').hide()
164
+ self._h5saver.new_file_sig.connect(self.create_new_file)
165
+
166
+ self._module_and_data_saver: module_saving.ScanSaver = module_saving.ScanSaver(self)
167
+
168
+ self.extended_saver: data_saving.DataToExportExtendedSaver = None
169
+ self.h5temp: H5Saver = None
170
+ self.temp_path: tempfile.TemporaryDirectory = None
171
+
172
+ self.scanner = Scanner(actuators=self.modules_manager.actuators) # , adaptive_losses=adaptive_losses)
173
+ self.scan_parameters = None
174
+
175
+ self.batcher: BatchScanner = None
176
+ self.batch_started = False
177
+ self.ind_batch = 0
178
+
179
+ self.modules_manager.actuators_changed[list].connect(self.update_actuators)
180
+
181
+ self.setup_ui()
182
+ self.ui.command_sig.connect(self.process_ui_cmds)
183
+
184
+ self.create_dataset_settings()
185
+
186
+ self.set_config()
187
+
188
+ self.live_plotter = LoaderPlotter(self.dockarea)
189
+ self.live_timer = QtCore.QTimer()
190
+ self.live_timer.timeout.connect(self.update_live_plots)
191
+
192
+ self.ui.enable_start_stop(True)
193
+ logger.info('DAQScan Initialized')
194
+
195
+ def plot_from(self):
196
+ self.modules_manager.get_det_data_list()
197
+ data0D = self.modules_manager.settings['data_dimensions', 'det_data_list0D']
198
+ data1D = self.modules_manager.settings['data_dimensions', 'det_data_list1D']
199
+ data0D['selected'] = data0D['all_items']
200
+ data1D['selected'] = data1D['all_items']
201
+ self.settings.child('plot_options', 'plot_0d').setValue(data0D)
202
+ self.settings.child('plot_options', 'plot_1d').setValue(data1D)
203
+
204
+ def setup_ui(self):
205
+ self.ui.populate_toolbox_widget([self.settings_tree, self._h5saver.settings_tree],
206
+ ['General Settings', 'Save Settings'])
207
+
208
+ self.ui.set_scanner_settings(self.scanner.parent_widget)
209
+ self.ui.set_modules_settings(self.modules_manager.settings_tree)
210
+
211
+ self.plotting_settings_tree = ParameterTree()
212
+ self.plotting_settings_tree.setParameters(self.settings.child('plot_options'))
213
+ self.ui.set_plotting_settings(self.plotting_settings_tree)
214
+
215
+ ################
216
+ # CONFIG/SETUP UI / EXIT
217
+
218
+ def set_config(self):
219
+ self.settings.child('time_flow', 'wait_time').setValue(config['scan']['timeflow']['wait_time'])
220
+ self.settings.child('time_flow', 'wait_time_between').setValue(config['scan']['timeflow']['wait_time'])
221
+ self.settings.child('time_flow', 'timeout').setValue(config['scan']['timeflow']['timeout'])
222
+
223
+ self.settings.child('scan_options', 'scan_average').setValue(config['scan']['Naverage'])
224
+
225
+ def process_ui_cmds(self, cmd: utils.ThreadCommand):
226
+ """Process commands sent by actions done in the ui
227
+
228
+ Parameters
229
+ ----------
230
+ cmd: ThreadCommand
231
+ Possible values are:
232
+ * quit
233
+ * ini_positions
234
+ * start
235
+ * start_batch
236
+ * stop
237
+ * move_at
238
+ * show_log
239
+ * load
240
+ * save
241
+ * show_file
242
+ * navigator
243
+ * batch
244
+ * viewers_changed
245
+ """
246
+ if cmd.command == 'quit':
247
+ self.quit_fun()
248
+ elif cmd.command == 'ini_positions':
249
+ self.set_ini_positions()
250
+ elif cmd.command == 'start':
251
+ self.start_scan()
252
+ elif cmd.command == 'start_batch':
253
+ self.start_scan_batch()
254
+ elif cmd.command == 'stop':
255
+ self.stop_scan()
256
+ elif cmd.command == 'move_at':
257
+ self.move_to_crosshair()
258
+ elif cmd.command == 'show_log':
259
+ self.show_log()
260
+ elif cmd.command == 'load':
261
+ self.load_file()
262
+ elif cmd.command == 'save':
263
+ self.save_file()
264
+ elif cmd.command == 'show_file':
265
+ self.show_file_content()
266
+ elif cmd.command == 'navigator':
267
+ self.show_navigator()
268
+ elif cmd.command == 'batch':
269
+ self.show_batcher(self.ui.menubar)
270
+ elif cmd.command == 'viewers_changed':
271
+ ...
272
+
273
+ def show_log(self):
274
+ """Open the log file in the default text editor"""
275
+ import webbrowser
276
+ webbrowser.open(logger.parent.handlers[0].baseFilename)
277
+
278
+ def quit_fun(self):
279
+ """
280
+ Quit the current instance of DAQ_scan
281
+
282
+ See Also
283
+ --------
284
+ quit_fun
285
+ """
286
+ try:
287
+ if self.temp_path is not None:
288
+ try:
289
+ self.h5temp.close()
290
+ self.temp_path.cleanup()
291
+ except Exception as e:
292
+ logger.exception(str(e))
293
+
294
+ self.close_file()
295
+ self.mainwindow.close()
296
+
297
+ except Exception as e:
298
+ logger.exception(str(e))
299
+
300
+ def create_dataset_settings(self):
301
+ # params about dataset attributes and scan attibutes
302
+ date = QDateTime(QDate.currentDate(), QTime.currentTime())
303
+ params_dataset = [{'title': 'Dataset information', 'name': 'dataset_info', 'type': 'group', 'children': [
304
+ {'title': 'Author:', 'name': 'author', 'type': 'str', 'value': config_utils['user']['name']},
305
+ {'title': 'Date/time:', 'name': 'date_time', 'type': 'date_time', 'value': date},
306
+ {'title': 'Sample:', 'name': 'sample', 'type': 'str', 'value': ''},
307
+ {'title': 'Experiment type:', 'name': 'experiment_type', 'type': 'str', 'value': ''},
308
+ {'title': 'Description:', 'name': 'description', 'type': 'text', 'value': ''}]}]
309
+
310
+ params_scan = [{'title': 'Scan information', 'name': 'scan_info', 'type': 'group', 'children': [
311
+ {'title': 'Author:', 'name': 'author', 'type': 'str', 'value': config_utils['user']['name']},
312
+ {'title': 'Date/time:', 'name': 'date_time', 'type': 'date_time', 'value': date},
313
+ {'title': 'Scan type:', 'name': 'scan_type', 'type': 'str', 'value': ''},
314
+ {'title': 'Scan subtype:', 'name': 'scan_sub_type', 'type': 'str', 'value': ''},
315
+ {'title': 'Scan name:', 'name': 'scan_name', 'type': 'str', 'value': '', 'readonly': True},
316
+ {'title': 'Description:', 'name': 'description', 'type': 'text', 'value': ''},
317
+ ]}]
318
+
319
+ self.dataset_attributes = Parameter.create(name='Attributes', type='group', children=params_dataset)
320
+ self.scan_attributes = Parameter.create(name='Attributes', type='group', children=params_scan)
321
+ ###################
322
+ # external modules
323
+
324
+ def show_batcher(self, menubar):
325
+ self.batcher = BatchScanner(self.dockarea, self.modules_manager.actuators_all,
326
+ self.modules_manager.detectors_all)
327
+ self.batcher.create_menu(menubar)
328
+ self.batcher.setupUI()
329
+ self.ui.set_action_visible('start_batch', True)
330
+
331
+ def start_scan_batch(self):
332
+ self.batch_started = True
333
+ self.ind_batch = 0
334
+ self.loop_scan_batch()
335
+
336
+ def loop_scan_batch(self):
337
+ if self.ind_batch >= len(self.batcher.scans_names):
338
+ self.stop_scan()
339
+ return
340
+ self.scanner = self.batcher.get_scan(self.batcher.scans_names[self.ind_batch])
341
+ actuators, detectors = self.batcher.get_act_dets()
342
+ self.set_scan_batch(actuators[self.batcher.scans_names[self.ind_batch]],
343
+ detectors[self.batcher.scans_names[self.ind_batch]])
344
+ self.start_scan()
345
+
346
+ def set_scan_batch(self, actuators, detectors):
347
+ self.modules_manager.selected_detectors_name = detectors
348
+ self.modules_manager.selected_actuators_name = actuators
349
+ QtWidgets.QApplication.processEvents()
350
+
351
+ def show_file_attributes(self, type_info='dataset'):
352
+ """
353
+ Switch the type_info value.
354
+
355
+ In case of :
356
+ * *scan* : Set parameters showing top false
357
+ * *dataset* : Set parameters showing top false
358
+ * *managers* : Set parameters showing top false. Add the save/cancel buttons to the accept/reject dialog (to save managers parameters in a xml file).
359
+
360
+ Finally, in case of accepted managers type info, save the managers parameters in a xml file.
361
+
362
+ =============== =========== ====================================
363
+ **Parameters** **Type** **Description**
364
+ *type_info* string The file type information between
365
+ * scan
366
+ * dataset
367
+ * managers
368
+ =============== =========== ====================================
369
+
370
+ See Also
371
+ --------
372
+ custom_tree.parameter_to_xml_file, create_menu
373
+ """
374
+ if SHOW_POPUPS:
375
+ dialog = QtWidgets.QDialog()
376
+ vlayout = QtWidgets.QVBoxLayout()
377
+ tree = ParameterTree()
378
+ tree.setMinimumWidth(400)
379
+ tree.setMinimumHeight(500)
380
+ if type_info == 'scan':
381
+ tree.setParameters(self.scan_attributes, showTop=False)
382
+ elif type_info == 'dataset':
383
+ tree.setParameters(self.dataset_attributes, showTop=False)
384
+
385
+ vlayout.addWidget(tree)
386
+ dialog.setLayout(vlayout)
387
+ buttonBox = QDialogButtonBox(parent=dialog)
388
+ buttonBox.addButton("Cancel", QDialogButtonBox.ButtonRole.RejectRole)
389
+ buttonBox.addButton("Apply", QDialogButtonBox.ButtonRole.AcceptRole)
390
+ buttonBox.rejected.connect(dialog.reject)
391
+ buttonBox.accepted.connect(dialog.accept)
392
+
393
+ vlayout.addWidget(buttonBox)
394
+ dialog.setWindowTitle('Fill in information about this {}'.format(type_info))
395
+ res = dialog.exec()
396
+ else:
397
+ res = True
398
+ return res
399
+
400
+ def show_file_content(self):
401
+ try:
402
+ self.h5saver.show_file_content()
403
+ except Exception as e:
404
+ logger.exception(str(e))
405
+
406
+ def show_navigator(self):
407
+
408
+ if self.navigator is None:
409
+ # loading navigator
410
+ self.navigator_dock = gutils.Dock('Navigator')
411
+ widgnav = QtWidgets.QWidget()
412
+ self.navigator_dock.addWidget(widgnav)
413
+ self.dockarea.addDock(self.navigator_dock)
414
+ self.navigator_dock.float()
415
+
416
+ self.navigator = Navigator(widgnav)
417
+
418
+ self.navigator.log_signal[str].connect(self.dashboard.add_status)
419
+ self.navigator.settings.child('settings', 'Load h5').hide()
420
+ self.navigator.set_action_visible('load_scan', False)
421
+
422
+ self.navigator.sig_double_clicked.connect(self.move_at)
423
+ self.navigator.h5saver = self.h5saver
424
+ self.navigator.list_2D_scans()
425
+
426
+ self.show_scan_selector()
427
+
428
+ def show_scan_selector(self):
429
+ viewer_items = []
430
+ if self.navigator is not None:
431
+ viewer_items.append(SelectorItem(self.navigator.viewer, name='Navigator'))
432
+ #
433
+ # for viewer in self.live_plotter.viewers:
434
+ # viewer_items.update({viewer.title: dict(viewers=[viewer], names=[viewer.title])})
435
+ self.scan_selector = ScanSelector(viewer_items)
436
+
437
+ self.ui.add_scanner_settings(self.scan_selector.settings_tree)
438
+
439
+ self.scan_selector.scan_select_signal.connect(self.scanner.update_from_scan_selector)
440
+
441
+ ################
442
+ # LOADING SAVING
443
+
444
+ def load_file(self):
445
+ self.h5saver.load_file(self.h5saver.h5_file_path)
446
+
447
+ def save_file(self):
448
+ if not os.path.isdir(self.h5saver.settings['base_path']):
449
+ os.mkdir(self.h5saver.settings['base_path'])
450
+ filename = gutils.file_io.select_file(self.h5saver.settings['base_path'], save=True, ext='h5')
451
+ self.h5saver.h5_file.copy_file(str(filename), overwrite=True)
452
+
453
+ def save_metadata(self, node, type_info='dataset_info'):
454
+ """
455
+ Switch the type_info value with :
456
+ * *'dataset_info'* : Give the params attributes the dataset_attributes values
457
+ * *'dataset'* : Give the params attributes the scan_attributes values
458
+
459
+ |
460
+ | Once done, course the params and add string casted date/time metadata as an element of attributes array.
461
+ | Save the contents of given parameter object into a xml string unde the attributes settings.
462
+
463
+ =============== =================== =========================================
464
+ **Parameters** **Type** **Description**
465
+ *node* pytables h5 node Root node to be treated
466
+ *type_info* string File type info between :
467
+ * 'dataset_info'
468
+ * 'scan_info'
469
+ =============== =================== =========================================
470
+
471
+ See Also
472
+ --------
473
+ custom_tree.parameter_to_xml_string
474
+ """
475
+
476
+ attr = node.attrs
477
+ if type_info == 'dataset_info':
478
+ attr['type'] = 'dataset'
479
+ params = self.dataset_attributes
480
+ else:
481
+ attr['type'] = 'scan'
482
+ params = self.scan_attributes
483
+ for child in params.child(type_info).children():
484
+ if type(child.value()) is QDateTime:
485
+ attr[child.name()] = child.value().toString('dd/mm/yyyy HH:MM:ss')
486
+ else:
487
+ attr[child.name()] = child.value()
488
+ if type_info == 'dataset_info':
489
+ # save contents of given parameter object into an xml string under the attribute settings
490
+ settings_str = b'<All_settings title="All Settings" type="group">' + \
491
+ ioxml.parameter_to_xml_string(params) + \
492
+ ioxml.parameter_to_xml_string(self.settings)
493
+ # ioxml.parameter_to_xml_string(
494
+ # self.dashboard.preset_manager.preset_params) +\
495
+ settings_str += b'</All_settings>'
496
+ attr['settings'] = settings_str
497
+
498
+ elif type_info == 'scan_info':
499
+ settings_all = [ioxml.parameter_to_xml_string(params),
500
+ ioxml.parameter_to_xml_string(self.settings),
501
+ ioxml.parameter_to_xml_string(self.h5saver.settings),
502
+ ioxml.parameter_to_xml_string(self.scanner.settings)]
503
+
504
+ settings_str = b'<All_settings title="All Settings" type="group">'
505
+ for set in settings_all:
506
+ if len(settings_str + set) < 60000:
507
+ # size limit for any object header (including all the other attributes) is 64kb
508
+ settings_str += set
509
+ else:
510
+ break
511
+ settings_str += b'</All_settings>'
512
+ attr['settings'] = settings_str
513
+
514
+ def create_new_file(self, new_file):
515
+ if new_file:
516
+ self._metada_dataset_set = False
517
+ self.close_file()
518
+
519
+ self.module_and_data_saver.h5saver = self.h5saver # force it for detectors to update their h5saver
520
+ res = self.update_file_settings()
521
+ if new_file:
522
+ self.ui.enable_start_stop()
523
+ return res
524
+
525
+ @property
526
+ def h5saver(self):
527
+ if self._h5saver is None:
528
+ self._h5saver = H5Saver(backend=config_utils('general', 'hdf5_backend'))
529
+ self._h5saver.settings.child('do_save').hide()
530
+ self._h5saver.settings.child('custom_name').hide()
531
+ self._h5saver.new_file_sig.connect(self.create_new_file)
532
+ if self._h5saver.h5_file is None:
533
+ self._h5saver.init_file(update_h5=True)
534
+ if not self._h5saver.isopen():
535
+ self._h5saver.init_file(addhoc_file_path=self._h5saver.settings['current_h5_file'])
536
+ return self._h5saver
537
+
538
+ @h5saver.setter
539
+ def h5saver(self, h5saver_temp: H5Saver):
540
+ self._h5saver = h5saver_temp
541
+
542
+ def close_file(self):
543
+ self.h5saver.close_file()
544
+
545
+ @property
546
+ def module_and_data_saver(self):
547
+ if not self._module_and_data_saver.h5saver.isopen():
548
+ self._module_and_data_saver.h5saver = self.h5saver
549
+ return self._module_and_data_saver
550
+
551
+ @module_and_data_saver.setter
552
+ def module_and_data_saver(self, mod: module_saving.ScanSaver):
553
+ self._module_and_data_saver = mod
554
+ self._module_and_data_saver.h5saver = self.h5saver
555
+
556
+ def update_file_settings(self):
557
+ try:
558
+ res = True
559
+ if not self._metada_dataset_set:
560
+ res = self.set_metadata_about_dataset()
561
+ self.save_metadata(self.h5saver.raw_group, 'dataset_info')
562
+
563
+ if self.navigator is not None:
564
+ self.navigator.update_h5file(self.h5saver.h5_file)
565
+ self.navigator.settings.child('settings', 'filepath').setValue(self.h5saver.h5_file.filename)
566
+
567
+ return res
568
+
569
+ except Exception as e:
570
+ logger.exception(str(e))
571
+
572
+ def update_scan_info(self):
573
+ # set attributes to the current group, such as scan_type....
574
+ self.scan_attributes.child('scan_info', 'scan_type').setValue(
575
+ self.scanner.settings.child('scan_type').value())
576
+ self.scan_attributes.child('scan_info', 'scan_sub_type').setValue(
577
+ self.scanner.settings.child('scan_sub_type').value())
578
+ scan_node = self.module_and_data_saver.get_set_node(new=False)
579
+ if scan_node.attrs['scan_done']:
580
+ scan_name = self.module_and_data_saver.get_next_node_name()
581
+ else:
582
+ scan_name = scan_node.name
583
+ self.scan_attributes.child('scan_info', 'scan_name').setValue(scan_name)
584
+ self.scan_attributes.child('scan_info', 'description').setValue('')
585
+ self.h5saver.settings.child('current_scan_name').setValue(scan_name)
586
+
587
+ res = self.set_metadata_about_current_scan()
588
+ return res
589
+
590
+ # PROCESS MODIFICATIONS
591
+ def update_actuators(self, actuators: List[str]):
592
+ self.scanner.actuators = self.modules_manager.actuators
593
+
594
+ def move_to_crosshair(self, *args, **kwargs):
595
+ if self.ui.is_action_checked('move_at'):
596
+ self.modules_manager.connect_actuators()
597
+ self.live_plotter.connect_double_clicked(self.move_at)
598
+ else:
599
+ self.live_plotter.disconnect(self.move_at)
600
+ self.modules_manager.connect_actuators(False)
601
+
602
+ def move_at(self, posx: float, posy: float = None):
603
+ if logging.getLevelName(logger.level) == 'DEBUG':
604
+ print(f'clicked at: {posx}, {posy}')
605
+ positions = [posx, posy]
606
+ positions = positions[:self.scanner.n_axes]
607
+ actuators = self.modules_manager.actuators
608
+ dte = data_mod.DataToExport(name="move_at")
609
+ for ind, pos in enumerate(positions):
610
+ dte.append(DataActuator(actuators[ind].title, data=float(pos), units=actuators[ind].units))
611
+
612
+ self.modules_manager.move_actuators(dte, polling=False)
613
+
614
+ def value_changed(self, param):
615
+ """
616
+
617
+ """
618
+ if param.name() == 'scan_average':
619
+ self.ui.show_average_step(param.value() > 1)
620
+ elif param.name() == 'prepare_viewers':
621
+ self.prepare_viewers()
622
+ elif param.name() == 'plot_probe':
623
+ self.plot_from()
624
+ elif param.name() == 'plot_at_each_step':
625
+ self.settings.child('plot_options', 'refresh_live').show(not param.value())
626
+
627
+ def clear_plot_from(self):
628
+ self.settings.child('plot_options', 'plot_0d').setValue(dict(all_items=[], selected=[]))
629
+ self.settings.child('plot_options', 'plot_1d').setValue(dict(all_items=[], selected=[]))
630
+
631
+ def check_number_type_viewers(self) -> Tuple[
632
+ List[ViewersEnum],
633
+ List[str],
634
+ bool]:
635
+ """ Assert from selected options the number and type of needed viewers for live plotting
636
+
637
+ Return
638
+ ------
639
+ List[ViewersEnum]: the list of needed viewers
640
+ List[str]: the list of data names to be plotted in the corresponding viewer
641
+ """
642
+ viewer2D_overload = False
643
+ viewers_enum = [ViewersEnum.Viewer0D.increase_dim(self.scanner.n_axes)
644
+ for _ in range(len(self.settings['plot_options', 'plot_0d']['selected']))]
645
+ data_names = self.settings['plot_options', 'plot_0d']['selected'][:]
646
+
647
+ if self.settings['plot_options', 'group0D'] and len(viewers_enum) > 0 and ViewersEnum.Viewer1D in viewers_enum:
648
+ viewers_enum = [ViewersEnum.Viewer1D]
649
+ data_names = [self.live_plotter.grouped_data0D_fullname]
650
+ elif (self.settings['plot_options', 'group0D'] and len(viewers_enum) > 0 and
651
+ ViewersEnum.Viewer2D in viewers_enum):
652
+ viewers_enum = [ViewersEnum.Viewer2D]
653
+ n2Dplots = len(data_names)
654
+ if (self.settings['scan_options', 'scan_average'] > 1 and
655
+ self.settings['scan_options', 'average_on_top']):
656
+ n2Dplots *= 2
657
+ if n2Dplots > 3:
658
+ viewer2D_overload = True
659
+ data_names = [self.live_plotter.grouped_data0D_fullname]
660
+
661
+ if self.scanner.n_axes <= 1:
662
+ viewers_enum.extend([ViewersEnum.Viewer1D.increase_dim(self.scanner.n_axes)
663
+ for _ in range(len(self.settings['plot_options', 'plot_1d']['selected']))])
664
+ data_names.extend(self.settings['plot_options', 'plot_1d']['selected'][:])
665
+ if (self.settings['scan_options', 'scan_average'] > 1 and
666
+ not self.settings['scan_options', 'average_on_top']):
667
+
668
+ viewers_enum = viewers_enum + viewers_enum
669
+ data_names = data_names + [f'{data_name}_averaged' for data_name in data_names]
670
+
671
+ return viewers_enum, data_names, viewer2D_overload
672
+
673
+ def prepare_viewers(self):
674
+ """ Assert from selected options the number and type of needed viewers for live plotting
675
+ and prepare them on the live plot panel
676
+ """
677
+ viewers_enum, data_names, _ = self.check_number_type_viewers()
678
+ self.live_plotter.prepare_viewers(viewers_enum, viewers_name=data_names)
679
+
680
+ def update_status(self, txt: str, wait_time=0):
681
+ """ Show the txt message in the status bar with a delay of wait_time ms.
682
+
683
+ add an info log in the logger
684
+
685
+ Parameters
686
+ ----------
687
+ txt: str
688
+ the message to log
689
+ wait_time: int
690
+ leave the message apparent in the status bar for this duration in ms
691
+ """
692
+ self.ui.display_status(txt, wait_time)
693
+ self.status_signal.emit(txt)
694
+ logger.info(txt)
695
+
696
+ def thread_status(self, status: utils.ThreadCommand):
697
+ """ General function to get datas/infos from child thread back to the main.
698
+
699
+ Possible commands are:
700
+
701
+ * "Update_Status"
702
+ * "Update_scan_index"
703
+ * "Scan_done"
704
+ * "Timeout"
705
+ """
706
+ if status.command == "Update_Status":
707
+ self.update_status(status.attribute, wait_time=self.wait_time)
708
+
709
+ elif status.command == "Update_scan_index":
710
+ # status[1] = [ind_scan,ind_average]
711
+ self.ind_scan = status.attribute[0]
712
+ self.ui.set_scan_step(status.attribute[0] + 1)
713
+ self.ind_average = status.attribute[1]
714
+ self.ui.set_scan_step_average(status.attribute[1] + 1)
715
+
716
+ elif status.command == "Scan_done":
717
+
718
+ self.modules_manager.reset_signals()
719
+ self.live_timer.stop()
720
+ self.ui.set_scan_done()
721
+ scan_node = self.module_and_data_saver.get_last_node()
722
+ scan_node.attrs['scan_done'] = True
723
+ self.module_and_data_saver.flush()
724
+ self.close_file()
725
+
726
+ if not self.batch_started:
727
+ if not self.dashboard.overshoot:
728
+ self.set_ini_positions()
729
+ self.ui.set_action_enabled('ini_positions', True)
730
+ self.ui.set_action_enabled('start', True)
731
+
732
+ # reactivate module controls using remote_control
733
+ if hasattr(self.dashboard, 'remote_manager'):
734
+ remote_manager = getattr(self.dashboard, 'remote_manager')
735
+ remote_manager.activate_all(True)
736
+ if self.navigator is not None:
737
+ self.navigator.list_2D_scans()
738
+ else:
739
+ self.ind_batch += 1
740
+ self.loop_scan_batch()
741
+
742
+ elif status.command == "Timeout":
743
+ self.ui.set_permanent_status('Timeout occurred')
744
+
745
+ elif status.command == 'add_data':
746
+ self.module_and_data_saver.add_data(**status.attribute)
747
+
748
+ elif status.command == 'add_nav_axes':
749
+ self.module_and_data_saver.add_nav_axes(status.attribute)
750
+
751
+ ############
752
+ # PLOTTING
753
+
754
+ def save_temp_live_data(self, scan_data: ScanDataTemp):
755
+ if scan_data.scan_index == 0:
756
+ nav_axes = self.scanner.get_nav_axes()
757
+ Naverage = self.settings['scan_options', 'scan_average']
758
+ if Naverage > 1:
759
+ for nav_axis in nav_axes:
760
+ nav_axis.index += 1
761
+ nav_axes.append(data_mod.Axis('Average',
762
+ data=np.linspace(0, Naverage - 1, Naverage),
763
+ index=0))
764
+
765
+ self.extended_saver.add_nav_axes(self.h5temp.raw_group, nav_axes)
766
+
767
+ self.extended_saver.add_data(self.h5temp.raw_group, scan_data.data, scan_data.indexes,
768
+ distribution=self.scanner.distribution)
769
+ if self.settings['plot_options', 'plot_at_each_step']:
770
+ self.update_live_plots()
771
+
772
+ def update_live_plots(self):
773
+
774
+ if self.settings['scan_options', 'scan_average'] > 1:
775
+ average_axis = 0
776
+ else:
777
+ average_axis = None
778
+ try:
779
+ self.live_plotter.load_plot_data(group_0D=self.settings['plot_options', 'group0D'],
780
+ average_axis=average_axis,
781
+ average_index=self.ind_average,
782
+ separate_average= not self.settings['scan_options', 'average_on_top'],
783
+ target_at=self.scanner.positions[self.ind_scan],
784
+ last_step=(self.ind_scan ==
785
+ self.scanner.positions.size - 1 and
786
+ self.ind_average ==
787
+ self.settings[
788
+ 'scan_options', 'scan_average'] - 1))
789
+ except Exception as e:
790
+ logger.exception(str(e))
791
+ #################
792
+ # SCAN FLOW
793
+
794
+ def set_scan(self, scan=None) -> bool:
795
+ """
796
+ Sets the current scan given the selected settings. Makes some checks,
797
+ increments the h5 file scans.
798
+ In case the dialog is cancelled, return False and aborts the scan
799
+ """
800
+ try:
801
+
802
+ res = self.update_scan_info()
803
+ if not res:
804
+ return False
805
+
806
+ is_oversteps = self.scanner.set_scan()
807
+ if is_oversteps:
808
+ messagebox(
809
+ text=f"An error occurred when establishing the scan steps. Actual settings "
810
+ f"gives approximately {int(self.scanner.n_steps)} steps."
811
+ f" Please check the steps number "
812
+ f"limit in the config file ({config['scan']['steps_limit']}) or modify"
813
+ f" your scan settings.")
814
+
815
+ _, _, viewer2D_overload = self.check_number_type_viewers()
816
+ if viewer2D_overload:
817
+ messagebox(text=
818
+ 'The number of live data chosen and the selected options '
819
+ 'will not be able to render fully on the 2D live viewers. Consider changing '
820
+ 'the options, such as "plot on top" for the averaging or "Group 0D data" '
821
+ 'or the number of selected data')
822
+ return False
823
+
824
+ if self.modules_manager.Nactuators != self.scanner.n_axes:
825
+ messagebox(
826
+ text="There are not enough or too much selected move modules for this scan")
827
+ return False
828
+
829
+ self.ui.n_scan_steps = self.scanner.n_steps
830
+
831
+ # check if the modules are initialized
832
+ for module in self.modules_manager.actuators:
833
+ if not module.initialized_state:
834
+ raise DAQ_ScanException('module ' + module.title + " is not initialized")
835
+
836
+ for module in self.modules_manager.detectors:
837
+ if not module.initialized_state:
838
+ raise DAQ_ScanException('module ' + module.title + " is not initialized")
839
+
840
+ self.ui.enable_start_stop(True)
841
+ return True
842
+
843
+ except Exception as e:
844
+ logger.exception(str(e))
845
+ self.ui.enable_start_stop(False)
846
+
847
+ def set_metadata_about_current_scan(self):
848
+ """
849
+ Set the date/time and author values of the scan_info child of the scan_attributes tree.
850
+ Show the 'scan' file attributes.
851
+
852
+ See Also
853
+ --------
854
+ show_file_attributes
855
+ """
856
+ date = QDateTime(QDate.currentDate(), QTime.currentTime())
857
+ self.scan_attributes.child('scan_info', 'date_time').setValue(date)
858
+ self.scan_attributes.child('scan_info', 'author').setValue(
859
+ self.dataset_attributes.child('dataset_info', 'author').value())
860
+ if not self.batch_started:
861
+ res = self.show_file_attributes('scan')
862
+ else:
863
+ res = True
864
+ return res
865
+
866
+ def set_metadata_about_dataset(self):
867
+ """
868
+ Set the date value of the data_set_info-date_time child of the data_set_attributes tree.
869
+ Show the 'dataset' file attributes.
870
+
871
+ See Also
872
+ --------
873
+ show_file_attributes
874
+ """
875
+ date = QDateTime(QDate.currentDate(), QTime.currentTime())
876
+ self.dataset_attributes.child('dataset_info', 'date_time').setValue(date)
877
+ res = self.show_file_attributes('dataset')
878
+ self._metada_dataset_set = True
879
+ return res
880
+
881
+ def exit_runner_thread(self, duration : int = 5000):
882
+ self.runner_thread.quit()
883
+ terminated = self.runner_thread.wait(duration)
884
+ if not terminated:
885
+ self.runner_thread.terminate()
886
+ self.runner_thread.wait()
887
+
888
+ def start_scan(self):
889
+ """
890
+ Start an acquisition calling the set_scan function.
891
+ Emit the command_DAQ signal "start_acquisition".
892
+
893
+ See Also
894
+ --------
895
+ set_scan
896
+ """
897
+ self.ui.display_status('Starting acquisition')
898
+ self.dashboard.overshoot = False
899
+ #deactivate double_clicked
900
+ if self.ui.is_action_checked('move_at'):
901
+ self.ui.get_action('move_at').trigger()
902
+
903
+ self._module_and_data_saver.h5saver = self.h5saver
904
+ res = self.set_scan()
905
+ if res:
906
+ # deactivate module controls using remote_control
907
+ if hasattr(self.dashboard, 'remote_manager'):
908
+ remote_manager = getattr(self.dashboard, 'remote_manager')
909
+ remote_manager.activate_all(False)
910
+
911
+ new_scan = self.module_and_data_saver.get_last_node().attrs['scan_done'] # get_last_node
912
+ scan_node = self.module_and_data_saver.get_set_node(new=new_scan)
913
+ self.save_metadata(scan_node, 'scan_info')
914
+
915
+ self._init_live()
916
+ Naverage = self.settings['scan_options', 'scan_average']
917
+ if Naverage > 1:
918
+ scan_shape = [Naverage]
919
+ scan_shape.extend(self.scanner.get_scan_shape())
920
+ else:
921
+ scan_shape = self.scanner.get_scan_shape()
922
+ for det in self.modules_manager.detectors:
923
+ det._module_and_data_saver = (
924
+ module_saving.DetectorExtendedSaver(det, scan_shape))
925
+ self._module_and_data_saver.h5saver = self.h5saver # force the update as the h5saver will also be set on each detectors
926
+
927
+ # mandatory to deal with multithreads
928
+ if self.runner_thread is not None:
929
+ self.command_daq_signal.disconnect()
930
+ self.exit_runner_thread()
931
+ self.runner_thread = None
932
+
933
+ self.runner_thread = QThread()
934
+
935
+ scan_acquisition = DAQScanAcquisition(self.settings, self.scanner, self.modules_manager,
936
+ )
937
+
938
+ if config['scan']['scan_in_thread']:
939
+ scan_acquisition.moveToThread(self.runner_thread)
940
+ self.command_daq_signal[utils.ThreadCommand].connect(scan_acquisition.queue_command)
941
+ scan_acquisition.scan_data_tmp[ScanDataTemp].connect(self.save_temp_live_data)
942
+ scan_acquisition.status_sig[utils.ThreadCommand].connect(self.thread_status)
943
+
944
+ self.runner_thread.scan_acquisition = scan_acquisition
945
+ self.runner_thread.start()
946
+
947
+ self.ui.set_action_enabled('ini_positions', False)
948
+ self.ui.set_action_enabled('start', False)
949
+ self.ui.set_scan_done(False)
950
+ if not self.settings['plot_options', 'plot_at_each_step']:
951
+ self.live_timer.start(self.settings['plot_options', 'refresh_live'])
952
+ self.command_daq_signal.emit(utils.ThreadCommand('start_acquisition'))
953
+ self.ui.set_permanent_status('Running acquisition')
954
+ logger.info('Running acquisition')
955
+
956
+ def _init_live(self):
957
+ Naverage = self.settings['scan_options', 'scan_average']
958
+ if Naverage > 1:
959
+ scan_shape = [Naverage]
960
+ scan_shape.extend(self.scanner.get_scan_shape())
961
+ else:
962
+ scan_shape = self.scanner.get_scan_shape()
963
+ if self.temp_path is not None:
964
+ try:
965
+ self.h5temp.close()
966
+ self.temp_path.cleanup()
967
+ except Exception as e:
968
+ logger.exception(str(e))
969
+
970
+ self.h5temp = H5Saver()
971
+ self.temp_path = tempfile.TemporaryDirectory(prefix='pymo')
972
+ addhoc_file_path = Path(self.temp_path.name).joinpath('temp_data.h5')
973
+ self.h5temp.init_file(custom_naming=True, addhoc_file_path=addhoc_file_path)
974
+ self.extended_saver: data_saving.DataToExportExtendedSaver =\
975
+ data_saving.DataToExportExtendedSaver(self.h5temp, extended_shape=scan_shape)
976
+ self.live_plotter.h5saver = self.h5temp
977
+
978
+ self.prepare_viewers()
979
+ QtWidgets.QApplication.processEvents()
980
+
981
+ def set_ini_positions(self):
982
+ """
983
+ Send the command_DAQ signal with "set_ini_positions" list item as an attribute.
984
+ """
985
+ self.command_daq_signal.emit(utils.ThreadCommand("set_ini_positions"))
986
+
987
+ def stop_scan(self):
988
+ """
989
+ Emit the command_DAQ signal "stop_acquisition".
990
+
991
+ See Also
992
+ --------
993
+ set_ini_positions
994
+ """
995
+ self.ui.set_permanent_status('Stoping acquisition')
996
+ self.command_daq_signal.emit(utils.ThreadCommand("stop_acquisition"))
997
+ scan_node = self.module_and_data_saver.get_last_node()
998
+ if scan_node is not None:
999
+ scan_node.attrs['scan_done'] = True
1000
+
1001
+ if not self.dashboard.overshoot:
1002
+ self.set_ini_positions() # do not set ini position again in case overshoot fired
1003
+ status = 'Data Acquisition has been stopped by user'
1004
+ else:
1005
+ status = 'Data Acquisition has been stopped due to overshoot'
1006
+
1007
+ self.update_status(status)
1008
+ self.ui.set_permanent_status('')
1009
+
1010
+ self.ui.set_action_enabled('ini_positions', True)
1011
+ self.ui.set_action_enabled('start', True)
1012
+
1013
+ def do_scan(self, start_scan=True):
1014
+ """Public method to start the scan programmatically"""
1015
+ if start_scan:
1016
+ if not self.ui.is_action_enabled('start'):
1017
+ self.ui.get_action('set_scan').trigger()
1018
+ QtWidgets.QApplication.processEvents()
1019
+ self.ui.get_action('start').trigger()
1020
+ else:
1021
+ self.ui.get_action('stop').trigger()
1022
+
1023
+
1024
+ class DAQScanAcquisition(QObject):
1025
+ """
1026
+ =========================== ========================================
1027
+
1028
+ =========================== ========================================
1029
+
1030
+ """
1031
+ scan_data_tmp = Signal(ScanDataTemp)
1032
+ status_sig = Signal(utils.ThreadCommand)
1033
+
1034
+ def __init__(self, scan_settings: Parameter = None, scanner: Scanner = None,
1035
+ modules_manager: ModulesManager = None,):
1036
+
1037
+ """
1038
+ DAQScanAcquisition deal with the acquisition part of daq_scan, that is transferring commands to modules,
1039
+ getting back data, saviong and letting know th UI about the scan status
1040
+
1041
+ """
1042
+
1043
+ super().__init__()
1044
+
1045
+ self.scan_settings = scan_settings
1046
+ self.modules_manager = modules_manager
1047
+ self.scanner = scanner
1048
+
1049
+ self.stop_scan_flag = False
1050
+ self.Naverage = self.scan_settings['scan_options', 'scan_average']
1051
+ self.ind_average = 0
1052
+ self.ind_scan = 0
1053
+
1054
+ self.isadaptive = self.scanner.scan_sub_type == 'Adaptive'
1055
+
1056
+ self.modules_manager.timeout_signal.connect(self.timeout)
1057
+ self.timeout_scan_flag = False
1058
+
1059
+
1060
+ self.move_done_flag = False
1061
+ self.det_done_flag = False
1062
+
1063
+ self.det_done_datas = data_mod.DataToExport('ScanData')
1064
+
1065
+ scan_shape = self.scanner.get_scan_shape()
1066
+ if self.Naverage > 1:
1067
+ self.scan_shape = [self.Naverage]
1068
+ self.scan_shape.extend(scan_shape)
1069
+ else:
1070
+ self.scan_shape = scan_shape
1071
+
1072
+ def queue_command(self, command: utils.ThreadCommand):
1073
+ """Process the commands sent by the main ui
1074
+
1075
+ Parameters
1076
+ ----------
1077
+ command: utils.ThreadCommand
1078
+ """
1079
+ if command.command == "start_acquisition":
1080
+ self.start_acquisition()
1081
+
1082
+ elif command.command == "stop_acquisition":
1083
+ self.stop_scan_flag = True
1084
+
1085
+ elif command.command == "set_ini_positions":
1086
+ self.set_ini_positions()
1087
+
1088
+ elif command.command == "move_stages":
1089
+ self.modules_manager.move_actuators(command.attribute, polling=False)
1090
+
1091
+ def set_ini_positions(self):
1092
+ """ Set the actuators's positions totheir initial value as defined in the scanner """
1093
+ try:
1094
+ if self.scanner.scan_sub_type != 'Adaptive':
1095
+ self.modules_manager.move_actuators(self.scanner.positions_at(0), polling=False)
1096
+
1097
+ except Exception as e:
1098
+ logger.exception(str(e))
1099
+
1100
+ def start_acquisition(self):
1101
+ try:
1102
+
1103
+ self.modules_manager.connect_actuators()
1104
+ self.modules_manager.connect_detectors()
1105
+
1106
+ self.stop_scan_flag = False
1107
+
1108
+ Naxes = self.scanner.n_axes
1109
+ scan_type = self.scanner.scan_type
1110
+ self.navigation_axes = self.scanner.get_nav_axes()
1111
+ self.status_sig.emit(utils.ThreadCommand("Update_Status",
1112
+ attribute="Acquisition has started"))
1113
+
1114
+ self.timeout_scan_flag = False
1115
+ for ind_average in range(self.Naverage):
1116
+ self.ind_average = ind_average
1117
+ self.ind_scan = -1
1118
+ while True:
1119
+ self.ind_scan += 1
1120
+ if not self.isadaptive:
1121
+ if self.ind_scan >= len(self.scanner.positions):
1122
+ break
1123
+ positions = self.scanner.positions_at(self.ind_scan) # get positions
1124
+ else:
1125
+ pass
1126
+
1127
+ self.status_sig.emit(
1128
+ utils.ThreadCommand("Update_scan_index",
1129
+ attribute=[self.ind_scan, ind_average]))
1130
+
1131
+ if self.stop_scan_flag or self.timeout_scan_flag:
1132
+ break
1133
+
1134
+ #move motors of modules and wait for move completion
1135
+ positions = self.modules_manager.order_positions(self.modules_manager.move_actuators(positions))
1136
+
1137
+ QThread.msleep(self.scan_settings['time_flow', 'wait_time_between'])
1138
+
1139
+ #grab datas and wait for grab completion
1140
+ self.det_done(self.modules_manager.grab_data(positions=positions), positions)
1141
+
1142
+ # daq_scan wait time
1143
+ QThread.msleep(self.scan_settings.child('time_flow', 'wait_time').value())
1144
+
1145
+ self.modules_manager.timeout_signal.disconnect()
1146
+ self.modules_manager.connect_actuators(False)
1147
+ self.modules_manager.connect_detectors(False)
1148
+
1149
+ self.status_sig.emit(utils.ThreadCommand("Update_Status",
1150
+ attribute="Acquisition has finished"))
1151
+ self.status_sig.emit(utils.ThreadCommand("Scan_done"))
1152
+
1153
+
1154
+ except Exception as e:
1155
+ logger.exception(str(e))
1156
+
1157
+ def det_done(self, det_done_datas: data_mod.DataToExport, positions):
1158
+ """
1159
+
1160
+ """
1161
+ try:
1162
+ indexes = self.scanner.get_indexes_from_scan_index(self.ind_scan)
1163
+ if self.Naverage > 1:
1164
+ indexes = [self.ind_average] + list(indexes)
1165
+ indexes = tuple(indexes)
1166
+ if self.ind_scan == 0:
1167
+ nav_axes = self.scanner.get_nav_axes()
1168
+ if self.Naverage > 1:
1169
+ for nav_axis in nav_axes:
1170
+ nav_axis.index += 1
1171
+ nav_axes.append(data_mod.Axis('Average', data=np.linspace(0, self.Naverage - 1, self.Naverage),
1172
+ index=0))
1173
+ self.status_sig.emit(utils.ThreadCommand("add_nav_axes", nav_axes))
1174
+
1175
+ self.status_sig.emit(
1176
+ utils.ThreadCommand("add_data",
1177
+ dict(indexes=indexes, distribution=self.scanner.distribution)))
1178
+
1179
+ self.det_done_flag = True
1180
+
1181
+ full_names: list = self.scan_settings['plot_options', 'plot_0d']['selected'][:]
1182
+ full_names.extend(self.scan_settings['plot_options', 'plot_1d']['selected'][:])
1183
+ data_temp = det_done_datas.get_data_from_full_names(full_names, deepcopy=False)
1184
+ n_nav_axis_selection = 2-len(indexes) + 1 if self.Naverage > 1 else 2-len(indexes)
1185
+ data_temp = data_temp.get_data_with_naxes_lower_than(n_nav_axis_selection) # maximum Data2D included nav indexes
1186
+
1187
+ self.scan_data_tmp.emit(ScanDataTemp(self.ind_scan, indexes, data_temp))
1188
+
1189
+ except Exception as e:
1190
+ logger.exception(str(e))
1191
+
1192
+ def timeout(self):
1193
+ """
1194
+ Send the status signal *'Time out during acquisition'*.
1195
+ """
1196
+ self.timeout_scan_flag = True
1197
+ self.status_sig.emit(utils.ThreadCommand("Update_Status",
1198
+ attribute="Timeout during acquisition"))
1199
+ self.status_sig.emit(utils.ThreadCommand("Timeout"))
1200
+
1201
+
1202
+ def main():
1203
+ from pymodaq_gui.utils.utils import mkQApp
1204
+ from pymodaq.utils.gui_utils.loader_utils import load_dashboard_with_preset
1205
+
1206
+ app = mkQApp('DAQScan')
1207
+ preset_file_name = config('presets', f'default_preset_for_scan')
1208
+
1209
+ dashboard, extension, win = load_dashboard_with_preset(preset_file_name, 'DAQScan')
1210
+
1211
+ app.exec()
1212
+
1213
+ return dashboard, extension, win
1214
+
1215
+
1216
+ if __name__ == '__main__':
1217
+ main()
1218
+