pymodaq 3.6.13__py3-none-any.whl → 4.0.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 (233) hide show
  1. pymodaq/__init__.py +13 -6
  2. pymodaq/control_modules/__init__.py +0 -7
  3. pymodaq/control_modules/daq_move.py +965 -2
  4. pymodaq/control_modules/daq_move_ui.py +319 -0
  5. pymodaq/control_modules/daq_viewer.py +1573 -3
  6. pymodaq/control_modules/daq_viewer_ui.py +393 -0
  7. pymodaq/control_modules/mocks.py +51 -0
  8. pymodaq/control_modules/move_utility_classes.py +709 -8
  9. pymodaq/control_modules/utils.py +256 -0
  10. pymodaq/control_modules/viewer_utility_classes.py +663 -6
  11. pymodaq/daq_utils.py +89 -0
  12. pymodaq/dashboard.py +91 -72
  13. pymodaq/examples/custom_app.py +12 -11
  14. pymodaq/examples/custom_viewer.py +10 -10
  15. pymodaq/examples/function_plotter.py +16 -13
  16. pymodaq/examples/nonlinearscanner.py +8 -6
  17. pymodaq/examples/parameter_ex.py +7 -7
  18. pymodaq/examples/preset_MockCamera.xml +1 -0
  19. pymodaq/extensions/__init__.py +16 -0
  20. pymodaq/extensions/console.py +76 -0
  21. pymodaq/{daq_logger.py → extensions/daq_logger.py} +115 -65
  22. pymodaq/extensions/daq_scan.py +1339 -0
  23. pymodaq/extensions/daq_scan_ui.py +240 -0
  24. pymodaq/extensions/h5browser.py +23 -0
  25. pymodaq/{pid → extensions/pid}/__init__.py +4 -2
  26. pymodaq/{pid → extensions/pid}/daq_move_PID.py +2 -2
  27. pymodaq/{pid → extensions/pid}/pid_controller.py +48 -36
  28. pymodaq/{pid → extensions/pid}/utils.py +52 -6
  29. pymodaq/extensions/utils.py +40 -0
  30. pymodaq/post_treatment/__init__.py +6 -0
  31. pymodaq/{daq_analysis → post_treatment/daq_analysis}/daq_analysis_main.py +17 -17
  32. pymodaq/{daq_measurement → post_treatment/daq_measurement}/daq_measurement_main.py +8 -14
  33. pymodaq/post_treatment/load_and_plot.py +219 -0
  34. pymodaq/post_treatment/process_to_scalar.py +263 -0
  35. pymodaq/resources/QtDesigner_Ressources/Icon_Library/run_all.png +0 -0
  36. pymodaq/resources/QtDesigner_Ressources/Icon_Library/stop_all.png +0 -0
  37. pymodaq/resources/QtDesigner_Ressources/QtDesigner_ressources.bat +1 -1
  38. pymodaq/resources/QtDesigner_Ressources/QtDesigner_ressources.qrc +1 -0
  39. pymodaq/resources/QtDesigner_Ressources/QtDesigner_ressources_rc.py +109784 -109173
  40. pymodaq/resources/QtDesigner_Ressources/icons.svg +142 -0
  41. pymodaq/resources/VERSION +1 -1
  42. pymodaq/resources/config_template.toml +32 -13
  43. pymodaq/resources/preset_default.xml +1 -1
  44. pymodaq/{daq_utils → utils}/Tuto innosetup/script_full_setup.iss +1 -1
  45. pymodaq/utils/__init__.py +0 -29
  46. pymodaq/utils/abstract/__init__.py +48 -0
  47. pymodaq/{daq_utils → utils}/abstract/logger.py +7 -3
  48. pymodaq/utils/array_manipulation.py +379 -8
  49. pymodaq/{daq_utils → utils}/calibration_camera.py +6 -6
  50. pymodaq/{daq_utils → utils}/chrono_timer.py +1 -1
  51. pymodaq/utils/config.py +448 -0
  52. pymodaq/utils/conftests.py +5 -0
  53. pymodaq/utils/daq_utils.py +828 -8
  54. pymodaq/utils/data.py +1873 -7
  55. pymodaq/{daq_utils → utils}/db/db_logger/db_logger.py +86 -47
  56. pymodaq/{daq_utils → utils}/db/db_logger/db_logger_models.py +31 -10
  57. pymodaq/{daq_utils → utils}/enums.py +12 -7
  58. pymodaq/utils/exceptions.py +37 -0
  59. pymodaq/utils/factory.py +82 -0
  60. pymodaq/{daq_utils → utils}/gui_utils/__init__.py +1 -1
  61. pymodaq/utils/gui_utils/custom_app.py +129 -0
  62. pymodaq/utils/gui_utils/file_io.py +66 -0
  63. pymodaq/{daq_utils → utils}/gui_utils/layout.py +2 -2
  64. pymodaq/{daq_utils → utils}/gui_utils/utils.py +13 -3
  65. pymodaq/{daq_utils → utils}/gui_utils/widgets/__init__.py +2 -2
  66. pymodaq/utils/gui_utils/widgets/label.py +24 -0
  67. pymodaq/{daq_utils → utils}/gui_utils/widgets/lcd.py +12 -7
  68. pymodaq/{daq_utils → utils}/gui_utils/widgets/push.py +66 -2
  69. pymodaq/{daq_utils → utils}/gui_utils/widgets/qled.py +6 -4
  70. pymodaq/utils/gui_utils/widgets/spinbox.py +24 -0
  71. pymodaq/{daq_utils → utils}/gui_utils/widgets/table.py +2 -2
  72. pymodaq/utils/h5modules/__init__.py +1 -0
  73. pymodaq/{daq_utils/h5backend.py → utils/h5modules/backends.py} +200 -112
  74. pymodaq/utils/h5modules/browsing.py +683 -0
  75. pymodaq/utils/h5modules/data_saving.py +839 -0
  76. pymodaq/utils/h5modules/h5logging.py +110 -0
  77. pymodaq/utils/h5modules/module_saving.py +350 -0
  78. pymodaq/utils/h5modules/saving.py +914 -0
  79. pymodaq/utils/h5modules/utils.py +85 -0
  80. pymodaq/utils/logger.py +64 -6
  81. pymodaq/utils/managers/action_manager.py +460 -0
  82. pymodaq/{daq_utils → utils}/managers/batchscan_manager.py +144 -112
  83. pymodaq/{daq_utils → utils}/managers/modules_manager.py +188 -114
  84. pymodaq/{daq_utils → utils}/managers/overshoot_manager.py +3 -3
  85. pymodaq/utils/managers/parameter_manager.py +110 -0
  86. pymodaq/{daq_utils → utils}/managers/preset_manager.py +17 -13
  87. pymodaq/{daq_utils → utils}/managers/preset_manager_utils.py +8 -7
  88. pymodaq/{daq_utils → utils}/managers/remote_manager.py +7 -6
  89. pymodaq/{daq_utils → utils}/managers/roi_manager.py +148 -57
  90. pymodaq/utils/math_utils.py +546 -10
  91. pymodaq/{daq_utils → utils}/messenger.py +5 -1
  92. pymodaq/utils/parameter/__init__.py +2 -15
  93. pymodaq/{daq_utils → utils}/parameter/ioxml.py +12 -6
  94. pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/__init__.py +1 -3
  95. pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/filedir.py +1 -1
  96. pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/itemselect.py +3 -0
  97. pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/led.py +1 -1
  98. pymodaq/utils/parameter/pymodaq_ptypes/pixmap.py +161 -0
  99. pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/slide.py +1 -1
  100. pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/table.py +1 -1
  101. pymodaq/utils/parameter/utils.py +206 -11
  102. pymodaq/utils/plotting/data_viewers/__init__.py +6 -0
  103. pymodaq/utils/plotting/data_viewers/viewer.py +393 -0
  104. pymodaq/utils/plotting/data_viewers/viewer0D.py +251 -0
  105. pymodaq/utils/plotting/data_viewers/viewer1D.py +574 -0
  106. pymodaq/{daq_utils → utils}/plotting/data_viewers/viewer1Dbasic.py +8 -3
  107. pymodaq/{daq_utils → utils}/plotting/data_viewers/viewer2D.py +292 -357
  108. pymodaq/{daq_utils → utils}/plotting/data_viewers/viewer2D_basic.py +58 -75
  109. pymodaq/utils/plotting/data_viewers/viewerND.py +738 -0
  110. pymodaq/{daq_utils → utils}/plotting/gant_chart.py +2 -2
  111. pymodaq/{daq_utils → utils}/plotting/items/axis_scaled.py +4 -2
  112. pymodaq/{daq_utils → utils}/plotting/items/image.py +8 -6
  113. pymodaq/utils/plotting/navigator.py +355 -0
  114. pymodaq/utils/plotting/scan_selector.py +480 -0
  115. pymodaq/utils/plotting/utils/axes_viewer.py +88 -0
  116. pymodaq/utils/plotting/utils/filter.py +538 -0
  117. pymodaq/utils/plotting/utils/lineout.py +224 -0
  118. pymodaq/{daq_utils → utils}/plotting/utils/plot_utils.py +196 -84
  119. pymodaq/{daq_utils → utils}/plotting/utils/signalND.py +21 -13
  120. pymodaq/utils/plotting/widgets.py +76 -0
  121. pymodaq/utils/scanner/__init__.py +10 -0
  122. pymodaq/utils/scanner/scan_factory.py +204 -0
  123. pymodaq/utils/scanner/scanner.py +271 -0
  124. pymodaq/utils/scanner/scanners/_1d_scanners.py +117 -0
  125. pymodaq/utils/scanner/scanners/_2d_scanners.py +293 -0
  126. pymodaq/utils/scanner/scanners/sequential.py +192 -0
  127. pymodaq/utils/scanner/scanners/tabular.py +294 -0
  128. pymodaq/utils/scanner/utils.py +83 -0
  129. pymodaq/utils/slicing.py +47 -0
  130. pymodaq/utils/svg/__init__.py +6 -0
  131. pymodaq/utils/svg/svg_renderer.py +20 -0
  132. pymodaq/utils/svg/svg_view.py +35 -0
  133. pymodaq/utils/svg/svg_viewer2D.py +51 -0
  134. pymodaq/{daq_utils → utils}/tcp_server_client.py +36 -37
  135. pymodaq/{daq_utils → utils}/tree_layout/tree_layout_main.py +50 -35
  136. pymodaq/utils/units.py +216 -0
  137. pymodaq-4.0.1.dist-info/METADATA +159 -0
  138. {pymodaq-3.6.13.dist-info → pymodaq-4.0.1.dist-info}/RECORD +167 -170
  139. {pymodaq-3.6.13.dist-info → pymodaq-4.0.1.dist-info}/WHEEL +1 -2
  140. pymodaq-4.0.1.dist-info/entry_points.txt +8 -0
  141. pymodaq/daq_move/daq_move_gui.py +0 -279
  142. pymodaq/daq_move/daq_move_gui.ui +0 -534
  143. pymodaq/daq_move/daq_move_main.py +0 -1042
  144. pymodaq/daq_move/process_from_QtDesigner_DAQ_Move_GUI.bat +0 -2
  145. pymodaq/daq_move/utility_classes.py +0 -686
  146. pymodaq/daq_scan.py +0 -2160
  147. pymodaq/daq_utils/array_manipulation.py +0 -386
  148. pymodaq/daq_utils/config.py +0 -273
  149. pymodaq/daq_utils/conftests.py +0 -7
  150. pymodaq/daq_utils/custom_parameter_tree.py +0 -9
  151. pymodaq/daq_utils/daq_enums.py +0 -133
  152. pymodaq/daq_utils/daq_utils.py +0 -1402
  153. pymodaq/daq_utils/exceptions.py +0 -71
  154. pymodaq/daq_utils/gui_utils/custom_app.py +0 -103
  155. pymodaq/daq_utils/gui_utils/file_io.py +0 -75
  156. pymodaq/daq_utils/gui_utils/widgets/spinbox.py +0 -9
  157. pymodaq/daq_utils/h5exporter_hyperspy.py +0 -115
  158. pymodaq/daq_utils/h5exporters.py +0 -242
  159. pymodaq/daq_utils/h5modules.py +0 -1559
  160. pymodaq/daq_utils/h5utils.py +0 -241
  161. pymodaq/daq_utils/managers/action_manager.py +0 -236
  162. pymodaq/daq_utils/managers/parameter_manager.py +0 -57
  163. pymodaq/daq_utils/math_utils.py +0 -705
  164. pymodaq/daq_utils/parameter/__init__.py +0 -1
  165. pymodaq/daq_utils/parameter/oldpymodaq_ptypes.py +0 -1626
  166. pymodaq/daq_utils/parameter/pymodaq_ptypes/pixmap.py +0 -85
  167. pymodaq/daq_utils/parameter/utils.py +0 -136
  168. pymodaq/daq_utils/plotting/data_viewers/__init__.py +0 -0
  169. pymodaq/daq_utils/plotting/data_viewers/process_from_QtDesigner_0DViewer_GUI.bat +0 -2
  170. pymodaq/daq_utils/plotting/data_viewers/viewer0D.py +0 -204
  171. pymodaq/daq_utils/plotting/data_viewers/viewer0D_GUI.py +0 -89
  172. pymodaq/daq_utils/plotting/data_viewers/viewer0D_GUI.ui +0 -131
  173. pymodaq/daq_utils/plotting/data_viewers/viewer1D.py +0 -781
  174. pymodaq/daq_utils/plotting/data_viewers/viewerND.py +0 -894
  175. pymodaq/daq_utils/plotting/data_viewers/viewerbase.py +0 -64
  176. pymodaq/daq_utils/plotting/items/__init__.py +0 -0
  177. pymodaq/daq_utils/plotting/navigator.py +0 -500
  178. pymodaq/daq_utils/plotting/scan_selector.py +0 -289
  179. pymodaq/daq_utils/plotting/utils/__init__.py +0 -0
  180. pymodaq/daq_utils/plotting/utils/filter.py +0 -236
  181. pymodaq/daq_utils/plotting/viewer0D/__init__.py +0 -0
  182. pymodaq/daq_utils/plotting/viewer0D/viewer0D_main.py +0 -4
  183. pymodaq/daq_utils/plotting/viewer1D/__init__.py +0 -0
  184. pymodaq/daq_utils/plotting/viewer1D/viewer1D_main.py +0 -4
  185. pymodaq/daq_utils/plotting/viewer1D/viewer1Dbasic.py +0 -4
  186. pymodaq/daq_utils/plotting/viewer2D/viewer_2D_basic.py +0 -4
  187. pymodaq/daq_utils/plotting/viewer2D/viewer_2D_main.py +0 -4
  188. pymodaq/daq_utils/plotting/viewerND/__init__.py +0 -0
  189. pymodaq/daq_utils/plotting/viewerND/viewerND_main.py +0 -4
  190. pymodaq/daq_utils/scanner.py +0 -1289
  191. pymodaq/daq_utils/tree_layout/__init__.py +0 -0
  192. pymodaq/daq_viewer/__init__.py +0 -0
  193. pymodaq/daq_viewer/daq_gui_settings.py +0 -237
  194. pymodaq/daq_viewer/daq_gui_settings.ui +0 -441
  195. pymodaq/daq_viewer/daq_viewer_main.py +0 -2225
  196. pymodaq/daq_viewer/process_from_QtDesigner_DAQ_GUI_settings.bat +0 -2
  197. pymodaq/daq_viewer/utility_classes.py +0 -673
  198. pymodaq/examples/logger_image/__init__.py +0 -0
  199. pymodaq/examples/logger_image/logger_displayer.py +0 -121
  200. pymodaq/examples/logger_image/setup.svg +0 -3119
  201. pymodaq/examples/logger_image/setup_svg.py +0 -114
  202. pymodaq/h5browser.py +0 -39
  203. pymodaq/utils/scanner.py +0 -15
  204. pymodaq-3.6.13.dist-info/METADATA +0 -39
  205. pymodaq-3.6.13.dist-info/entry_points.txt +0 -8
  206. pymodaq-3.6.13.dist-info/top_level.txt +0 -1
  207. /pymodaq/{daq_analysis → post_treatment/daq_analysis}/__init__.py +0 -0
  208. /pymodaq/{daq_measurement → post_treatment/daq_measurement}/__init__.py +0 -0
  209. /pymodaq/{daq_measurement → post_treatment/daq_measurement}/daq_measurement_GUI.py +0 -0
  210. /pymodaq/{daq_measurement → post_treatment/daq_measurement}/daq_measurement_GUI.ui +0 -0
  211. /pymodaq/{daq_measurement → post_treatment/daq_measurement}/process_from_QtDesigner_DAQ_Measurement_GUI.bat +0 -0
  212. /pymodaq/{daq_utils → utils}/Tuto innosetup/Tuto innosetup.odt +0 -0
  213. /pymodaq/{daq_utils → utils}/Tuto innosetup/Tuto innosetup.pdf +0 -0
  214. /pymodaq/{daq_move → utils/db}/__init__.py +0 -0
  215. /pymodaq/{daq_utils → utils/db/db_logger}/__init__.py +0 -0
  216. /pymodaq/{daq_utils → utils}/gui_utils/dock.py +0 -0
  217. /pymodaq/{daq_utils → utils}/gui_utils/list_picker.py +0 -0
  218. /pymodaq/{daq_utils/abstract → utils/managers}/__init__.py +0 -0
  219. /pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/bool.py +0 -0
  220. /pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/date.py +0 -0
  221. /pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/list.py +0 -0
  222. /pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/numeric.py +0 -0
  223. /pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/tableview.py +0 -0
  224. /pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/text.py +0 -0
  225. /pymodaq/{daq_utils/db → utils/plotting}/__init__.py +0 -0
  226. /pymodaq/{daq_utils → utils}/plotting/image_viewer.py +0 -0
  227. /pymodaq/{daq_utils/db/db_logger → utils/plotting/items}/__init__.py +0 -0
  228. /pymodaq/{daq_utils → utils}/plotting/items/crosshair.py +0 -0
  229. /pymodaq/{daq_utils/managers → utils/plotting/utils}/__init__.py +0 -0
  230. /pymodaq/{daq_utils → utils}/qvariant.py +0 -0
  231. /pymodaq/{daq_utils/plotting/viewer2D → utils/scanner/scanners}/__init__.py +0 -0
  232. /pymodaq/{daq_utils/plotting → utils/tree_layout}/__init__.py +0 -0
  233. {pymodaq-3.6.13.dist-info → pymodaq-4.0.1.dist-info/licenses}/LICENSE +0 -0
@@ -1,1559 +0,0 @@
1
- # Standard imports
2
- import os
3
- import sys
4
- from collections import OrderedDict
5
- import warnings
6
- import logging
7
- import copy
8
- from pathlib import Path
9
- import importlib
10
-
11
- # 3rd party imports
12
- from qtpy import QtGui, QtCore, QtWidgets
13
- from qtpy.QtCore import Qt, QObject, Signal, QByteArray
14
- import numpy as np
15
- import datetime
16
-
17
- # Project imports
18
- import pymodaq.daq_utils.parameter.ioxml
19
- from pyqtgraph.parametertree import Parameter, ParameterTree
20
- from pymodaq.daq_utils.config import Config
21
- from pymodaq.daq_utils.parameter import utils as putils
22
- from pymodaq.daq_utils.tree_layout.tree_layout_main import Tree_layout
23
- from pymodaq.daq_utils.gui_utils.utils import h5tree_to_QTree, pngbinary2Qlabel
24
- from pymodaq.daq_utils.gui_utils.file_io import select_file
25
- from pymodaq.daq_utils.gui_utils.dock import DockArea
26
- from pymodaq.daq_utils.gui_utils.utils import dashboard_submodules_params
27
- from pymodaq.daq_utils.plotting.data_viewers.viewerND import ViewerND
28
- from pymodaq.daq_utils.abstract.logger import AbstractLogger
29
- from pymodaq.daq_utils.h5backend import H5Backend, backends_available, Node
30
- from pymodaq.daq_utils.h5exporters import ExporterFactory
31
- from pymodaq.daq_utils.h5utils import get_h5_data_from_node
32
- from pymodaq.daq_utils.exceptions import InvalidSave, InvalidGroupDataType, InvalidDataDimension, \
33
- InvalidDataType, InvalidScanType, InvalidGroupType
34
-
35
- from pymodaq.daq_utils import daq_utils as utils
36
- from pymodaq.daq_utils.scanner import SCAN_TYPES as stypes
37
-
38
- from dateutil import parser
39
- from packaging import version as version_mod
40
-
41
- config = Config()
42
-
43
- logger = utils.set_logger(utils.get_module_name(__file__))
44
-
45
- version = '0.0.1'
46
- save_types = ['scan', 'detector', 'logger', 'custom']
47
- group_types = ['raw_datas', 'scan', 'detector', 'move', 'data', 'ch', '', 'external_h5']
48
- group_data_types = ['data0D', 'data1D', 'data2D', 'dataND']
49
- data_types = ['data', 'axis', 'live_scan', 'navigation_axis', 'external_h5', 'strings', 'bkg']
50
- data_dimensions = ['0D', '1D', '2D', 'ND']
51
- scan_types = ['']
52
- scan_types.extend(stypes)
53
-
54
-
55
- class H5LogHandler(logging.StreamHandler):
56
- def __init__(self, h5saver):
57
- super().__init__()
58
- self.h5saver = h5saver
59
- formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
60
- self.setFormatter(formatter)
61
-
62
- def emit(self, record):
63
- msg = self.format(record)
64
- self.h5saver.add_log(msg)
65
-
66
-
67
- class H5SaverBase(H5Backend):
68
- """Object containing all methods in order to save datas in a *hdf5 file* with a hierachy compatible with the
69
- H5Browser. The saving parameters are contained within a **Parameter** object: self.settings that can be displayed
70
- on a UI using the widget self.settings_tree. At the creation of a new file, a node group named **Raw_datas**
71
- and represented by the attribute ``raw_group`` is created and set with a metadata attribute:
72
-
73
- * 'type' given by the **save_type** class parameter
74
-
75
- The root group of the file is then set with a few metadata:
76
-
77
- * 'pymodaq_version' the current pymodaq version, e.g. 1.6.2
78
- * 'file' the file name
79
- * 'date' the current date
80
- * 'time' the current time
81
-
82
- All datas will then be saved under this node in various groups
83
-
84
- See Also
85
- --------
86
- H5Browser
87
-
88
- Parameters
89
- ----------
90
- h5_file: pytables hdf5 file
91
- object used to save all datas and metadas
92
- h5_file_path: str or Path
93
- Signal signal represented by a float. Is emitted each time the hardware reached the target
94
- position within the epsilon precision (see comon_parameters variable)
95
- save_type: str
96
- an element of the list module attribute save_types = ['scan', 'detector', 'custom']
97
- * 'scan' is used for DAQ_Scan module and should be used for similar application
98
- * 'detector' is used for DAQ_Viewer module and should be used for similar application
99
- * 'custom' should be used for customized applications
100
-
101
- Attributes
102
- ----------
103
-
104
- settings: Parameter
105
- Parameter instance (pyqtgraph) containing all settings (can be represented with the settings_tree widget)
106
-
107
- settings_tree: ParameterTree
108
- Widget representing a Tree structure, all settings are defined in the class variable ``params``
109
-
110
- """
111
-
112
- params = [
113
- {'title': 'Save type:', 'name': 'save_type', 'type': 'list', 'limits': save_types, 'readonly': True},
114
- ] + dashboard_submodules_params + \
115
- [{'title': 'Backend:', 'name': 'backend', 'type': 'group', 'children': [
116
- {'title': 'Backend type:', 'name': 'backend_type', 'type': 'list', 'limits': backends_available,
117
- 'readonly': True},
118
- {'title': 'HSDS Server:', 'name': 'hsds_options', 'type': 'group', 'visible': False, 'children': [
119
- {'title': 'Endpoint:', 'name': 'endpoint', 'type': 'str',
120
- 'value': config('data_saving', 'hsds', 'root_url'), 'readonly': False},
121
- {'title': 'User:', 'name': 'user', 'type': 'str',
122
- 'value': config('data_saving', 'hsds', 'username'), 'readonly': False},
123
- {'title': 'password:', 'name': 'password', 'type': 'str',
124
- 'value': config('data_saving', 'hsds', 'pwd'), 'readonly': False},
125
- ]},
126
- ]},
127
-
128
- {'title': 'custom_name?:', 'name': 'custom_name', 'type': 'bool', 'default': False, 'value': False},
129
- {'title': 'show file content?', 'name': 'show_file', 'type': 'bool_push', 'default': False,
130
- 'value': False},
131
- {'title': 'Base path:', 'name': 'base_path', 'type': 'browsepath',
132
- 'value': config('data_saving', 'h5file', 'save_path'), 'filetype': False, 'readonly': True, },
133
- {'title': 'Base name:', 'name': 'base_name', 'type': 'str', 'value': 'Scan', 'readonly': True},
134
- {'title': 'Current scan:', 'name': 'current_scan_name', 'type': 'str', 'value': '', 'readonly': True},
135
- {'title': 'Current path:', 'name': 'current_scan_path', 'type': 'text',
136
- 'value': config('data_saving', 'h5file', 'save_path'), 'readonly': True, 'visible': False},
137
- {'title': 'h5file:', 'name': 'current_h5_file', 'type': 'text', 'value': '', 'readonly': True},
138
- {'title': 'New file', 'name': 'new_file', 'type': 'action'},
139
- {'title': 'Saving dynamic', 'name': 'dynamic', 'type': 'list',
140
- 'limits': config('data_saving', 'data_type', 'dynamics'),
141
- 'value': config('data_saving', 'data_type', 'dynamic')},
142
- {'title': 'Compression options:', 'name': 'compression_options', 'type': 'group', 'children': [
143
- {'title': 'Compression library:', 'name': 'h5comp_library', 'type': 'list', 'value': 'zlib',
144
- 'limits': ['zlib', 'gzip']},
145
- {'title': 'Compression level:', 'name': 'h5comp_level', 'type': 'int',
146
- 'value': config('data_saving', 'h5file', 'compression_level'), 'min': 0, 'max': 9},
147
- ]},
148
- ]
149
-
150
- def __init__(self, save_type='scan', backend='tables'):
151
- """
152
-
153
- Parameters
154
- ----------
155
- save_type (str): one of ['scan', 'detector', 'logger', 'custom']
156
- backend (str): either 'tables' for pytables backend, 'h5py' for h5py backends or 'h5pyd' for HSDS backend
157
-
158
- See Also
159
- --------
160
- https://github.com/HDFGroup/hsds
161
- """
162
- H5Backend.__init__(self, backend)
163
- if save_type not in save_types:
164
- raise InvalidSave('Invalid saving type')
165
-
166
- self.h5_file_path = None
167
- self.h5_file_name = None
168
- self.logger_array = None
169
- self.file_loaded = False
170
-
171
- self.current_group = None
172
- self.current_scan_group = None
173
- self.current_scan_name = None
174
- self.raw_group = None
175
-
176
- self.settings = Parameter.create(title='Saving settings', name='save_settings', type='group',
177
- children=self.params)
178
- self.settings.child(('save_type')).setValue(save_type)
179
-
180
- # self.settings.child('saving_options', 'save_independent').show(save_type == 'scan')
181
- # self.settings.child('saving_options', 'do_save').show(not save_type == 'scan')
182
- # self.settings.child('saving_options', 'current_scan_name').show(save_type == 'scan')
183
-
184
- self.settings.sigTreeStateChanged.connect(
185
- self.parameter_tree_changed) # any changes on the settings will update accordingly the detector
186
-
187
- @property
188
- def h5_file(self):
189
- return self._h5file
190
-
191
- def init_file(self, update_h5=False, custom_naming=False, addhoc_file_path=None, metadata=dict([]),
192
- raw_group_name='Raw_datas'):
193
- """Initializes a new h5 file.
194
- Could set the h5_file attributes as:
195
-
196
- * a file with a name following a template if ``custom_naming`` is ``False`` and ``addhoc_file_path`` is ``None``
197
- * a file within a name set using a file dialog popup if ``custom_naming`` is ``True``
198
- * a file with a custom name if ``addhoc_file_path`` is a ``Path`` object or a path string
199
-
200
- Parameters
201
- ----------
202
- update_h5: bool
203
- create a new h5 file with name specified by other parameters
204
- if false try to open an existing file and will append new data to it
205
- custom_naming: bool
206
- if True, a selection file dialog opens to set a new file name
207
- addhoc_file_path: Path or str
208
- supplied name by the user for the new file
209
- metadata: dict
210
- dictionnary with pair of key, value that should be saved as attributes of the root group
211
- Returns
212
- -------
213
- update_h5: bool
214
- True if new file has been created, False otherwise
215
- """
216
- datetime_now = datetime.datetime.now()
217
-
218
- if addhoc_file_path is None:
219
- if not os.path.isdir(self.settings.child('base_path').value()):
220
- os.mkdir(self.settings.child('base_path').value())
221
-
222
- # set the filename and path
223
- base_name = self.settings.child('base_name').value()
224
-
225
- if not custom_naming:
226
- custom_naming = self.settings.child('custom_name').value()
227
-
228
- if not custom_naming:
229
- scan_type = self.settings.child('save_type').value() == 'scan'
230
- scan_path, current_scan_name, save_path = self.update_file_paths(update_h5)
231
- self.current_scan_name = current_scan_name
232
- self.settings.child('current_scan_name').setValue(current_scan_name)
233
- self.settings.child('current_scan_path').setValue(str(scan_path))
234
-
235
- if not scan_type:
236
- self.h5_file_path = save_path.parent # will remove the dataset part used for DAQ_scan datas
237
- self.h5_file_name = base_name + datetime_now.strftime('_%Y%m%d_%H_%M_%S.h5')
238
- else:
239
- self.h5_file_name = save_path.name + ".h5"
240
- self.h5_file_path = save_path.parent
241
-
242
- else:
243
- self.h5_file_name = select_file(start_path=base_name, save=True, ext='h5')
244
- self.h5_file_path = self.h5_file_name.parent
245
-
246
- else:
247
- if isinstance(addhoc_file_path, str):
248
- addhoc_file_path = Path(addhoc_file_path)
249
- self.h5_file_path = addhoc_file_path.parent
250
- self.h5_file_name = addhoc_file_path.name
251
-
252
- fullpathname = str(self.h5_file_path.joinpath(self.h5_file_name))
253
- self.settings.child('current_h5_file').setValue(fullpathname)
254
-
255
- if update_h5:
256
- self.current_scan_group = None
257
-
258
- scan_group = None
259
- if self.current_scan_group is not None:
260
- scan_group = self.get_node_name(self.current_scan_group)
261
-
262
- if update_h5:
263
- self.close_file()
264
- self.open_file(fullpathname, 'w', title='PyMoDAQ file')
265
-
266
- else:
267
- self.close_file()
268
- self.open_file(fullpathname, 'a', title='PyMoDAQ file')
269
-
270
- self.raw_group = self.get_set_group(self.root(), raw_group_name, title='Data from PyMoDAQ modules')
271
- self.get_set_logger(self.raw_group)
272
-
273
- if scan_group is not None:
274
- self.current_scan_group = self.get_set_group(self.raw_group, scan_group)
275
- else:
276
- self.current_scan_group = self.get_last_scan()
277
-
278
- self.raw_group.attrs['type'] = self.settings.child(
279
- ('save_type')).value() # first possibility to set a node attribute
280
- self.root().set_attr('file', self.h5_file_name) # second possibility
281
- if update_h5:
282
- self.set_attr(self.root(), 'date', datetime_now.date().isoformat())
283
- self.set_attr(self.root(), 'time', datetime_now.time().isoformat())
284
- for metadat in metadata:
285
- self.raw_group.attrs[metadat] = metadata[metadat]
286
- return update_h5
287
-
288
- def add_scan_group(self, title='', settings_as_xml='', metadata=dict([])):
289
- """
290
- Add a new group of type scan
291
- See Also
292
- -------
293
- add_incremental_group
294
- """
295
- if self.current_scan_group is not None:
296
- if len(self.get_children(self.current_scan_group)) == 0:
297
- new_scan = False
298
- else:
299
- new_scan = True
300
- else:
301
- new_scan = True
302
- if new_scan:
303
- self.current_scan_group = self.add_incremental_group('scan', self.raw_group, title, settings_as_xml,
304
- metadata)
305
- self.set_attr(self.current_scan_group, 'description', '')
306
- self.settings.child(('current_scan_name')).setValue(self.get_node_name(self.current_scan_group))
307
-
308
- return self.current_scan_group
309
-
310
- def update_file_paths(self, update_h5=False):
311
- """
312
-
313
- Parameters
314
- ----------
315
- update_h5: bool
316
- if True, will increment the file name and eventually the current scan index
317
- if False, get the current scan index in the h5 file
318
-
319
- Returns
320
- -------
321
- scan_path: Path
322
- current_filename: str
323
- dataset_path: Path
324
-
325
- """
326
-
327
- try:
328
- # set the filename and path
329
- base_path = self.settings.child(('base_path')).value()
330
- base_name = self.settings.child(('base_name')).value()
331
- current_scan = self.settings.child(('current_scan_name')).value()
332
- scan_type = self.settings.child(('save_type')).value() == 'scan'
333
- ind_dataset = None
334
- if current_scan == '' or update_h5:
335
- next_scan_index = 0
336
- update_h5 = True # just started the main program so one should create a new h5
337
- self.file_loaded = False
338
- else:
339
- next_scan_index = self.get_scan_index()
340
- if self.file_loaded:
341
- ind_dataset = int(os.path.splitext(self.h5_file_name)[0][-3:])
342
- try:
343
- curr_date = datetime.date.fromisoformat(self.get_attr(self.root(), 'date'))
344
- except ValueError:
345
- curr_date = parser.parse(self.get_attr(self.root(), 'date')).date()
346
- else:
347
- curr_date = datetime.date.today()
348
-
349
- scan_path, current_filename, dataset_path = self.set_current_scan_path(base_path, base_name, update_h5,
350
- next_scan_index,
351
- create_dataset_folder=False,
352
- curr_date=curr_date,
353
- ind_dataset=ind_dataset)
354
- self.settings.child(('current_scan_path')).setValue(str(dataset_path))
355
-
356
- return scan_path, current_filename, dataset_path
357
-
358
- except Exception as e:
359
- logger.exception(str(e))
360
-
361
- @classmethod
362
- def find_part_in_path_and_subpath(cls, base_dir, part='', create=False, increment=True):
363
- """
364
- Find path from part time.
365
-
366
- =============== ============ =============================================
367
- **Parameters** **Type** **Description**
368
- *base_dir* Path object The directory to browse
369
- *part* string The date of the directory to find/create
370
- *create* boolean Indicate the creation flag of the directory
371
- =============== ============ =============================================
372
-
373
- Returns
374
- -------
375
- Path object
376
- found path from part
377
- """
378
- found_path = None
379
- if part in base_dir.parts: # check if current year is in the given base path
380
- if base_dir.name == part:
381
- found_path = base_dir
382
- else:
383
- for ind in range(len(base_dir.parts)):
384
- tmp_path = base_dir.parents[ind]
385
- if tmp_path.name == part:
386
- found_path = base_dir.parents[ind]
387
- break
388
- else: # if not check if year is in the subfolders
389
- subfolders_year_name = [x.name for x in base_dir.iterdir() if x.is_dir()]
390
- subfolders_found_path = [x for x in base_dir.iterdir() if x.is_dir()]
391
- if part not in subfolders_year_name:
392
- if increment:
393
- found_path = base_dir.joinpath(part)
394
- else:
395
- found_path = base_dir
396
- if create:
397
- found_path.mkdir()
398
- else:
399
- ind_path = subfolders_year_name.index(part)
400
- found_path = subfolders_found_path[ind_path]
401
- return found_path
402
-
403
- @classmethod
404
- def set_current_scan_path(cls, base_dir, base_name='Scan', update_h5=False, next_scan_index=0,
405
- create_scan_folder=False,
406
- create_dataset_folder=True, curr_date=None, ind_dataset=None):
407
- """
408
-
409
- Parameters
410
- ----------
411
- base_dir
412
- base_name
413
- update_h5
414
- next_scan_index
415
- create_scan_folder
416
- create_dataset_folder
417
-
418
- Returns
419
- -------
420
-
421
- """
422
- base_dir = Path(base_dir)
423
- if curr_date is None:
424
- curr_date = datetime.date.today()
425
-
426
- year_path = cls.find_part_in_path_and_subpath(base_dir, part=str(curr_date.year),
427
- create=True) # create directory of the year if it doen't exist and return it
428
- day_path = cls.find_part_in_path_and_subpath(year_path, part=curr_date.strftime('%Y%m%d'),
429
- create=True) # create directory of the day if it doen't exist and return it
430
- dataset_base_name = curr_date.strftime('Dataset_%Y%m%d')
431
- dataset_paths = sorted([path for path in day_path.glob(dataset_base_name + "*" + ".h5") if path.is_file()])
432
-
433
- if ind_dataset is None:
434
- if dataset_paths == []:
435
-
436
- ind_dataset = 0
437
- else:
438
- if update_h5:
439
- ind_dataset = int(dataset_paths[-1].stem.partition(dataset_base_name + "_")[2]) + 1
440
- else:
441
- ind_dataset = int(dataset_paths[-1].stem.partition(dataset_base_name + "_")[2])
442
-
443
- dataset_path = cls.find_part_in_path_and_subpath(day_path,
444
- part=dataset_base_name + "_{:03d}".format(ind_dataset),
445
- create=False, increment=True)
446
- scan_paths = sorted([path for path in dataset_path.glob(base_name + '*') if path.is_dir()])
447
- # if scan_paths==[]:
448
- # ind_scan=0
449
- # else:
450
- # if list(scan_paths[-1].iterdir())==[]:
451
- # ind_scan=int(scan_paths[-1].name.partition(base_name)[2])
452
- # else:
453
- # ind_scan=int(scan_paths[-1].name.partition(base_name)[2])+1
454
- ind_scan = next_scan_index
455
- #
456
- # scan_path = cls.find_part_in_path_and_subpath(dataset_path, part=base_name + '{:03d}'.format(ind_scan),
457
- # create=create_scan_folder)
458
- scan_path = ''
459
- return dataset_path, base_name + '{:03d}'.format(ind_scan), dataset_path
460
-
461
- def get_last_scan(self):
462
- """Gets the last scan node within the h5_file and under the the **raw_group**
463
-
464
- Returns
465
- -------
466
- scan_group: pytables group or None
467
-
468
-
469
- """
470
- groups = [group for group in list(self.get_children(self.raw_group)) if 'Scan' in group]
471
- groups.sort()
472
- if len(groups) != 0:
473
- scan_group = self.get_node(self.raw_group, groups[-1])
474
- else:
475
- scan_group = None
476
- return scan_group
477
-
478
- def get_scan_index(self):
479
- """ return the scan group index in the "scan templating": Scan000, Scan001 as an integer
480
- """
481
- try:
482
- if self.current_scan_group is None:
483
- return 0
484
- else:
485
-
486
- groups = [group for group in self.get_children(self.raw_group) if 'Scan' in group]
487
- groups.sort()
488
- flag = False
489
- if len(groups) != 0:
490
- if 'scan_done' in self.get_attr(self.get_node(self.raw_group, groups[-1])):
491
- if self.get_attr(self.get_node(self.raw_group, groups[-1]), 'scan_done'):
492
- return len(groups)
493
- return len(groups) - 1
494
- return len(groups) - 1
495
- return 0
496
-
497
- except Exception as e:
498
- logger.exception(str(e))
499
-
500
- def load_file(self, base_path=None, file_path=None):
501
- """Opens a file dialog to select a h5file saved on disk to be used
502
-
503
- Parameters
504
- ----------
505
- base_path
506
- file_path
507
-
508
- See Also
509
- --------
510
- :py:meth:`init_file`
511
-
512
- """
513
- if base_path is None:
514
- base_path = self.settings.child('base_path').value()
515
- if not os.path.isdir(base_path):
516
- base_path = None
517
-
518
- if file_path is None:
519
- file_path = select_file(base_path, save=False, ext='h5')
520
-
521
- if not (file_path is None or file_path == ''):
522
- if not isinstance(file_path, Path):
523
- file_path = Path(file_path)
524
-
525
- if 'h5' not in file_path.suffix:
526
- raise IOError('Invalid file type, should be a h5 file')
527
-
528
- self.init_file(addhoc_file_path=file_path)
529
- self.file_loaded = True
530
-
531
- def save_file(self, filename=None):
532
- if filename is None:
533
- filename = select_file(None, save=True, ext='h5')
534
- if filename != '':
535
- super().save_file_as(filename)
536
-
537
- def get_set_logger(self, where):
538
- """ Retrieve or create (if absent) a logger enlargeable array to store logs
539
- Get attributed to the class attribute ``logger_array``
540
- Parameters
541
- ----------
542
- where: node
543
- location within the tree where to save or retrieve the array
544
-
545
- Returns
546
- -------
547
- logger_array: vlarray
548
- enlargeable array accepting strings as elements
549
- """
550
- if isinstance(where, Node):
551
- where = where.node
552
- logger = 'Logger'
553
- if logger not in list(self.get_children(where)):
554
- # check if logger node exist
555
- self.logger_array = self.add_string_array(where, logger)
556
- self.logger_array.attrs['type'] = 'log'
557
- else:
558
- self.logger_array = self.get_node(where, name=logger)
559
- return self.logger_array
560
-
561
- def add_log(self, msg):
562
- self.logger_array.append(msg)
563
-
564
- def add_data_group(self, where, group_data_type, title='', settings_as_xml='', metadata=dict([])):
565
- """Creates a group node at given location in the tree
566
-
567
- Parameters
568
- ----------
569
- where: group node
570
- where to create data group
571
- group_data_type: list of str
572
- either ['data0D', 'data1D', 'data2D', 'dataND']
573
- title: str, optional
574
- a title for this node, will be saved as metadata
575
- settings_as_xml: str, optional
576
- XML string created from a Parameter object to be saved as metadata
577
- metadata: dict, optional
578
- will be saved as a new metadata attribute with name: key and value: dict value
579
-
580
- Returns
581
- -------
582
- group: group node
583
-
584
- See Also
585
- --------
586
- :py:meth:`àdd_group`
587
- """
588
- if group_data_type not in group_data_types:
589
- raise InvalidGroupDataType('Invalid data group type')
590
- metadata.update(settings=settings_as_xml)
591
- group = self.add_group(group_data_type, '', where, title, metadata)
592
- return group
593
-
594
- def add_navigation_axis(self, data, parent_group, axis='x_axis', enlargeable=False, title='', metadata=dict([])):
595
- """
596
- Create carray or earray for navigation axis within a scan
597
- Parameters
598
- ----------
599
- data: (ndarray) of dimension 1
600
- parent_group: (str or node) parent node where to save new data
601
- axis: (str) either x_axis, y_axis, z_axis or time_axis. 'x_axis', 'y_axis', 'z_axis', 'time_axis' are axes containing scalar values (floats or ints). 'time_axis' can be interpreted as the posix timestamp corresponding to a datetime object, see datetime.timestamp()
602
- enlargeable: (bool) if True the created array is a earray type if False the created array is a carray type
603
- """
604
-
605
- if axis not in ['x_axis', 'y_axis', 'z_axis', 'time_axis']:
606
- if 'axis' not in axis: # this take care of the case of sequential scans where axes are labelled with indexes
607
- raise NameError('Invalid navigation axis name')
608
-
609
- array = self.add_array(parent_group, f"{self.settings.child(('save_type')).value()}_{axis}", 'navigation_axis',
610
- data_shape=data.shape,
611
- data_dimension='1D', array_to_save=data, enlargeable=enlargeable, title=title,
612
- metadata=metadata)
613
- return array
614
-
615
- def add_data_live_scan(self, channel_group, data_dict, scan_type='scan1D', title='', scan_subtype=''):
616
- isadaptive = scan_subtype == 'Adaptive'
617
- if not isadaptive:
618
- shape, dimension, size = utils.get_data_dimension(data_dict['data'], scan_type=scan_type,
619
- remove_scan_dimension=True)
620
- else:
621
- shape, dimension, size = data_dict['data'].shape, '0D', 1
622
- data_array = self.add_array(channel_group, 'Data', 'data', array_type=np.float,
623
- title=title,
624
- data_shape=shape,
625
- data_dimension=dimension, scan_type=scan_type,
626
- scan_subtype=scan_subtype,
627
- array_to_save=data_dict['data'])
628
- if 'x_axis' in data_dict:
629
- if not isinstance(data_dict['x_axis'], dict):
630
- array_to_save = data_dict['x_axis']
631
- tmp_dict = dict(label='', units='')
632
- else:
633
- tmp_dict = copy.deepcopy(data_dict['x_axis'])
634
- array_to_save = tmp_dict.pop('data')
635
- self.add_array(channel_group, 'x_axis', 'axis',
636
- array_type=np.float, array_to_save=array_to_save,
637
- enlargeable=False, data_dimension='1D', metadata=tmp_dict)
638
- if 'y_axis' in data_dict:
639
- if not isinstance(data_dict['y_axis'], dict):
640
- array_to_save = data_dict['y_axis']
641
- tmp_dict = dict(label='', units='')
642
- else:
643
- tmp_dict = copy.deepcopy(data_dict['y_axis'])
644
- array_to_save = tmp_dict.pop('data')
645
- self.add_array(channel_group, 'y_axis', 'axis',
646
- array_type=np.float, array_to_save=array_to_save,
647
- enlargeable=False, data_dimension='1D', metadata=tmp_dict)
648
- return data_array
649
-
650
- def add_data(self, channel_group, data_dict, scan_type='scan1D', scan_subtype='',
651
- scan_shape=[], title='', enlargeable=False,
652
- init=False, add_scan_dim=False, metadata=dict([])):
653
- """save data within the hdf5 file together with axes data (if any) and metadata, node name will be 'Data'
654
-
655
- Parameters
656
- ----------
657
- channel_group: (hdf5 node) node where to save the array, in general within a channel type group
658
- data_dict: (dict) dictionnary containing the data to save and all the axis and metadata mandatory key: 'data':
659
- (ndarray) data to save other keys: 'xxx_axis' (for instance x_axis, y_axis, 'nav_x_axis'....) or background
660
- scan_type: (str) either '', 'scan1D' or 'scan2D' or Tabular or sequential
661
- scan_subtype: (str) see scanner module
662
- scan_shape: (iterable) the shape of the scan dimensions
663
- title: (str) the title attribute of the array node
664
- enlargeable: (bool) if False, data are save as a CARRAY, otherwise as a EARRAY (for ragged data, see add_sting_array)
665
- init: (bool) if True, the array saved in the h5 file is initialized with the correct type but all element equal
666
- to zero. Else, the 'data' key of data_dict is saved as is
667
- add_scan_dim: (bool) if True, the scan axes dimension (scan_shape iterable) is prepended to the array shape on the hdf5
668
- In that case, the array is usually initialized as zero and further populated
669
- metadata: (dict) dictionnary whose keys will be saved as the array attributes
670
-
671
-
672
- Returns
673
- -------
674
- array (CARRAY or EARRAY)
675
-
676
- See Also
677
- --------
678
- add_array, add_string_array
679
- """
680
-
681
- shape, dimension, size = utils.get_data_dimension(data_dict['data'])
682
- tmp_data_dict = copy.deepcopy(data_dict)
683
- array_type = getattr(np, self.settings.child('dynamic').value())
684
- # save axis
685
- # this loop covers all type of axis : x_axis, y_axis... nav_x_axis, ...
686
- axis_keys = [k for k in tmp_data_dict.keys() if 'axis' in k]
687
- for key in axis_keys:
688
- if not isinstance(tmp_data_dict[key], dict):
689
- array_to_save = tmp_data_dict[key]
690
- tmp_dict = dict(label='', units='')
691
- else:
692
- tmp_dict = copy.deepcopy(tmp_data_dict[key])
693
- array_to_save = tmp_dict.pop('data')
694
- tmp_data_dict.pop(key)
695
-
696
- self.add_array(channel_group, key, 'axis', array_type=None, array_to_save=array_to_save,
697
- enlargeable=False, data_dimension='1D', metadata=tmp_dict)
698
-
699
- array_to_save = tmp_data_dict.pop('data')
700
- if 'type' in tmp_data_dict:
701
- tmp_data_dict.pop('type') # otherwise this metadata would overide mandatory attribute 'type' for a h5 node
702
-
703
- if 'bkg' in tmp_data_dict:
704
- bkg = tmp_data_dict.pop('bkg')
705
- self.add_array(channel_group, 'Bkg', 'bkg', array_type=array_type, array_to_save=bkg,
706
- data_dimension=dimension)
707
- tmp_data_dict.update(metadata)
708
- array_to_save = array_to_save.astype(array_type)
709
- data_array = self.add_array(channel_group, 'Data', 'data', array_type=array_type,
710
- title=title, data_shape=shape, enlargeable=enlargeable, data_dimension=dimension,
711
- scan_type=scan_type, scan_subtype=scan_subtype, scan_shape=scan_shape,
712
- array_to_save=array_to_save,
713
- init=init, add_scan_dim=add_scan_dim, metadata=tmp_data_dict)
714
-
715
- self.flush()
716
- return data_array
717
-
718
- def add_array(self, where, name, data_type, data_shape=None, data_dimension=None, scan_type='', scan_subtype='',
719
- scan_shape=[],
720
- title='', array_to_save=None, array_type=None, enlargeable=False, metadata=dict([]),
721
- init=False, add_scan_dim=False):
722
- """save data arrays on the hdf5 file together with metadata
723
- Parameters
724
- ----------
725
- where: (hdf5 node) node where to save the array
726
- name: (str) name of the array in the hdf5 file
727
- data_type: (str) one of ['data', 'axis', 'live_scan', 'navigation_axis', 'external_h5', 'strings', 'bkg'], mandatory
728
- so that the h5Browsr interpret correctly the array (see add_data)
729
- data_shape: (iterable) the shape of the array to save, mandatory if array_to_save is None
730
- data_dimension: (str) one of ['0D', '1D', '2D', 'ND']
731
- scan_type: (str) either '', 'scan1D' or 'scan2D'
732
- scan_shape: (iterable): the shape of the scan dimensions
733
- title: (str) the title attribute of the array node
734
- array_to_save: (ndarray or None) data to be saved in the array. If None, array_type and data_shape
735
- should be specified
736
- array_type: (np.dtype or numpy types), eg np.float, np.int32 ...
737
- enlargeable: (bool) if False, data are save as a CARRAY, otherwise as a EARRAY (for ragged data, see add_sting_array)
738
- metadata: (dict) dictionnary whose keys will be saved as the array attributes
739
- init: (bool) if True, the array saved in the h5 file is initialized with the correct type but all element equal
740
- to zero. Else, the 'data' key of data_dict is saved as is
741
- add_scan_dim: if True, the scan axes dimension (scan_shape iterable) is prepended to the array shape on the hdf5
742
- In that case, the array is usually initialized as zero and further populated
743
-
744
- Returns
745
- -------
746
- array (CARRAY or EARRAY)
747
-
748
- See Also
749
- --------
750
- add_data, add_string_array
751
- """
752
- if array_type is None:
753
- if array_to_save is None:
754
- array_type = getattr(np, self.settings.child('dynamic').value())
755
- else:
756
- array_type = array_to_save.dtype
757
-
758
- if data_dimension not in data_dimensions:
759
- raise InvalidDataDimension('Invalid data dimension')
760
- if data_type not in data_types:
761
- raise InvalidDataType('Invalid data type')
762
- if scan_type != '':
763
- scan_type = utils.uncapitalize(scan_type)
764
- if scan_type.lower() not in [s.lower() for s in scan_types]:
765
- raise InvalidScanType('Invalid scan type')
766
- if enlargeable:
767
- if data_shape == (1,):
768
- data_shape = None
769
- array = self.create_earray(where, utils.capitalize(name), dtype=np.dtype(array_type),
770
- data_shape=data_shape, title=title)
771
- else:
772
- if add_scan_dim: # means it is an array initialization to zero
773
- shape = list(scan_shape[:])
774
- shape.extend(data_shape)
775
- if init or array_to_save is None:
776
- array_to_save = np.zeros(shape, dtype=np.dtype(array_type))
777
-
778
- array = self.create_carray(where, utils.capitalize(name), obj=array_to_save, title=title)
779
- self.set_attr(array, 'type', data_type)
780
- self.set_attr(array, 'data_dimension', data_dimension)
781
- self.set_attr(array, 'scan_type', scan_type)
782
- self.set_attr(array, 'scan_subtype', scan_subtype)
783
-
784
- for metadat in metadata:
785
- self.set_attr(array, metadat, metadata[metadat])
786
- return array
787
-
788
- def add_string_array(self, where, name, title='', metadata=dict([])):
789
- array = self.create_vlarray(where, name, dtype='string', title=title)
790
- array.attrs['shape'] = (0,)
791
- array.attrs['data_dimension'] = '0D'
792
- array.attrs['scan_type'] = 'scan1D'
793
-
794
- for metadat in metadata:
795
- array.attrs[metadat] = metadata[metadat]
796
- return array
797
-
798
- def get_set_group(self, where, name, title=''):
799
- self.current_group = super().get_set_group(where, name, title)
800
- return self.current_group
801
-
802
- def add_incremental_group(self, group_type, where, title='', settings_as_xml='', metadata=dict([])):
803
- """
804
- Add a node in the h5 file tree of the group type with an increment in the given name
805
- Parameters
806
- ----------
807
- group_type: (str) one of the possible values of **group_types**
808
- where: (str or node) parent node where to create the new group
809
- title: (str) node title
810
- settings_as_xml: (str) XML string containing Parameters representation (see custom_Tree)
811
- metadata: (dict) extra metadata to be saved with this new group node
812
-
813
- Returns
814
- -------
815
- (node): newly created group node
816
- """
817
- if group_type not in group_types:
818
- raise InvalidGroupType('Invalid group type')
819
- nodes = [name for name in self.get_children(self.get_node(where))]
820
- nodes_tmp = []
821
- for node in nodes:
822
- if utils.capitalize(group_type) in node:
823
- nodes_tmp.append(node)
824
- nodes_tmp.sort()
825
- if len(nodes_tmp) == 0:
826
- ind_group = -1
827
- else:
828
- ind_group = int(nodes_tmp[-1][-3:])
829
- group = self.get_set_group(where, utils.capitalize(group_type) + '{:03d}'.format(ind_group + 1), title)
830
- self.set_attr(group, 'settings', settings_as_xml)
831
- if group_type.lower() != 'ch':
832
- self.set_attr(group, 'type', group_type.lower())
833
- else:
834
- self.set_attr(group, 'type', '')
835
- for metadat in metadata:
836
- self.set_attr(group, metadat, metadata[metadat])
837
- return group
838
-
839
- def add_det_group(self, where, title='', settings_as_xml='', metadata=dict([])):
840
- """
841
- Add a new group of type detector
842
- See Also
843
- -------
844
- add_incremental_group
845
- """
846
- group = self.add_incremental_group('detector', where, title, settings_as_xml, metadata)
847
- return group
848
-
849
- def add_CH_group(self, where, title='', settings_as_xml='', metadata=dict([])):
850
- """
851
- Add a new group of type channel
852
- See Also
853
- -------
854
- add_incremental_group
855
- """
856
- group = self.add_incremental_group('ch', where, title, settings_as_xml, metadata)
857
- return group
858
-
859
- def add_live_scan_group(self, where, dimensionality, title='', settings_as_xml='', metadata=dict([])):
860
- """
861
- Add a new group of type live scan
862
- See Also
863
- -------
864
- add_incremental_group
865
- """
866
- metadata.update(settings=settings_as_xml)
867
- group = self.add_group(utils.capitalize('Live_scan_{:s}'.format(dimensionality)), '', where, title=title,
868
- metadata=metadata)
869
- return group
870
-
871
- def add_move_group(self, where, title='', settings_as_xml='', metadata=dict([])):
872
- """
873
- Add a new group of type move
874
- See Also
875
- -------
876
- add_incremental_group
877
- """
878
- group = self.add_incremental_group('move', where, title, settings_as_xml, metadata)
879
- return group
880
-
881
- def parameter_tree_changed(self, param, changes):
882
- for param, change, data in changes:
883
- path = self.settings.childPath(param)
884
-
885
- if change == 'childAdded':
886
- pass
887
-
888
- elif change == 'value':
889
- if param.name() == 'show_file':
890
- param.setValue(False)
891
- self.show_file_content()
892
-
893
- elif param.name() == 'base_path':
894
- try:
895
- if not os.path.isdir(param.value()):
896
- os.mkdir(param.value())
897
- except Exception as e:
898
- logger.warning(f"The base path couldn't be set, please check your options: {str(e)}")
899
- self.update_status("The base path couldn't be set, please check your options")
900
-
901
- elif param.name() in putils.iter_children(self.settings.child('compression_options'), []):
902
- compression = self.settings.child('compression_options', 'h5comp_library').value()
903
- compression_opts = self.settings.child('compression_options', 'h5comp_level').value()
904
- self.define_compression(compression, compression_opts)
905
-
906
- elif change == 'parent':
907
- pass
908
-
909
- def update_status(self, status):
910
- # self.status_sig.emit(utils.ThreadCommand("Update_Status", [status, 'log']))
911
- # logger.info(status)
912
- pass
913
-
914
- def show_file_content(self):
915
- form = QtWidgets.QWidget()
916
- if not self.isopen():
917
- if self.h5_file_path is not None:
918
- if self.h5_file_path.exists():
919
- self.analysis_prog = H5Browser(form, h5file_path=self.h5_file_path)
920
- else:
921
- logger.warning('The h5 file path has not been defined yet')
922
- else:
923
- logger.warning('The h5 file path has not been defined yet')
924
- else:
925
- self.flush()
926
- self.analysis_prog = H5Browser(form, h5file=self.h5file)
927
- form.show()
928
-
929
-
930
- class H5Saver(H5SaverBase, QObject):
931
- """
932
- status_sig: Signal
933
- emits a signal of type Threadcommand in order to senf log information to a main UI
934
- new_file_sig: Signal
935
- emits a boolean signal to let the program know when the user pressed the new file button on the UI
936
- """
937
-
938
- status_sig = Signal(utils.ThreadCommand)
939
- new_file_sig = Signal(bool)
940
-
941
- def __init__(self, *args, **kwargs):
942
- QObject.__init__(self)
943
- H5SaverBase.__init__(self, *args, **kwargs)
944
-
945
- self.settings_tree = ParameterTree()
946
- self.settings_tree.setMinimumHeight(310)
947
- self.settings_tree.setParameters(self.settings, showTop=False)
948
- self.settings.child(('new_file')).sigActivated.connect(lambda: self.emit_new_file(True))
949
-
950
- def close(self):
951
- # to display the correct interface to other parts of the library
952
- # TODO create the related interface/abstract class
953
-
954
- self.close_file()
955
-
956
- def emit_new_file(self, status):
957
- """Emits the new_file_sig
958
-
959
- Parameters
960
- ----------
961
- status: bool
962
- emits True if a new file has been asked by the user pressing the new file button on the UI
963
- """
964
- self.new_file_sig.emit(status)
965
-
966
-
967
- class H5Logger(AbstractLogger):
968
- def __init__(self, *args, **kwargs):
969
- self.h5saver = H5Saver(*args, save_type='logger', **kwargs)
970
-
971
- def close(self):
972
- self.h5saver.close_file()
973
-
974
- @property
975
- def settings_tree(self):
976
- return self.h5saver.settings_tree
977
-
978
- @property
979
- def settings(self):
980
- return self.h5saver.settings
981
-
982
- def init_logger(self, settings):
983
- self.h5saver.init_file(update_h5=True, metadata=dict(settings=settings))
984
- self.h5saver.flush()
985
- return True
986
-
987
- def get_handler(self):
988
- return H5LogHandler(self.h5saver)
989
-
990
- def add_detector(self, det_name, settings):
991
- if det_name not in self.h5saver.raw_group.children_name():
992
- det_group = self.h5saver.add_det_group(self.h5saver.raw_group, det_name, settings)
993
- self.h5saver.add_navigation_axis(np.array([0.0, ]),
994
- det_group, 'time_axis', enlargeable=True,
995
- title='Time axis',
996
- metadata=dict(label='Time axis', units='s', nav_index=0))
997
-
998
- def add_datas(self, datas):
999
- det_name = datas['name']
1000
- det_group = self.h5saver.get_group_by_title(self.h5saver.raw_group, det_name)
1001
- time_array = self.h5saver.get_node(det_group, 'Logger_time_axis')
1002
- time_array.append(np.array([datas['acq_time_s']]))
1003
-
1004
- data_types = ['data0D', 'data1D']
1005
- if self.settings.child(('save_2D')).value():
1006
- data_types.extend(['data2D', 'dataND'])
1007
-
1008
- for data_type in data_types:
1009
- if data_type in datas.keys() and len(datas[data_type]) != 0:
1010
- if not self.h5saver.is_node_in_group(det_group, data_type):
1011
- data_group = self.h5saver.add_data_group(det_group, data_type, metadata=dict(type='scan'))
1012
- else:
1013
- data_group = self.h5saver.get_node(det_group, utils.capitalize(data_type))
1014
- for ind_channel, channel in enumerate(datas[data_type]):
1015
- channel_group = self.h5saver.get_group_by_title(data_group, channel)
1016
- if channel_group is None:
1017
- channel_group = self.h5saver.add_CH_group(data_group, title=channel)
1018
- data_array = self.h5saver.add_data(channel_group, datas[data_type][channel],
1019
- scan_type='scan1D', enlargeable=True)
1020
- else:
1021
- data_array = self.h5saver.get_node(channel_group, 'Data')
1022
- if data_type == 'data0D':
1023
- data_array.append(np.array([datas[data_type][channel]['data']]))
1024
- else:
1025
- data_array.append(datas[data_type][channel]['data'])
1026
- self.h5saver.flush()
1027
- self.settings.child(('N_saved')).setValue(
1028
- self.settings.child(('N_saved')).value() + 1)
1029
-
1030
- def stop_logger(self):
1031
- self.h5saver.flush()
1032
-
1033
-
1034
- def find_scan_node(scan_node):
1035
- """
1036
- utility function to find the parent node of "scan" type, meaning some of its children (DAQ_scan case)
1037
- or co-nodes (daq_logger case) are navigation axes
1038
- Parameters
1039
- ----------
1040
- scan_node: (pytables node)
1041
- data node from where this function look for its navigation axes if any
1042
- Returns
1043
- -------
1044
- node: the parent node of 'scan' type
1045
- list: the data nodes of type 'navigation_axis' corresponding to the initial data node
1046
-
1047
-
1048
- """
1049
- try:
1050
- while True:
1051
- if scan_node.attrs['type'] == 'scan':
1052
- break
1053
- else:
1054
- scan_node = scan_node.parent_node
1055
- children = list(scan_node.children().values()) # for data saved using daq_scan
1056
- children.extend([scan_node.parent_node.children()[child] for child in
1057
- scan_node.parent_node.children_name()]) # for data saved using the daq_logger
1058
- nav_children = []
1059
- for child in children:
1060
- if 'type' in child.attrs.attrs_name:
1061
- if child.attrs['type'] == 'navigation_axis':
1062
- nav_children.append(child)
1063
- return scan_node, nav_children
1064
- except Exception:
1065
- return None, []
1066
-
1067
-
1068
- class H5BrowserUtil(H5Backend):
1069
- """Class to handle manipulation and export of h5 nodes"""
1070
-
1071
- def __init__(self, backend='tables'):
1072
- super().__init__(backend=backend)
1073
-
1074
- def export_data(self, node_path='/', filesavename: str = 'datafile.h5'):
1075
- """Initialize the correct exporter and export the node"""
1076
-
1077
- # Format the node and file type
1078
- filepath = Path(filesavename)
1079
- node = self.get_node(node_path)
1080
- # Separate dot from extension
1081
- extension = filepath.suffix[1:]
1082
- # Obtain the suitable exporter object
1083
- exporter = ExporterFactory.create_exporter(extension)
1084
- # Export the data
1085
- exporter._export_data(node, filepath)
1086
-
1087
- def get_h5file_scans(self, where='/'):
1088
- # TODO add a test for this method
1089
- scan_list = []
1090
- where = self.get_node(where)
1091
- for node in self.walk_nodes(where):
1092
- if 'pixmap2D' in node.attrs.attrs_name:
1093
- scan_list.append(
1094
- dict(scan_name='{:s}_{:s}'.format(node.parent_node.name, node.name), path=node.path,
1095
- data=node.attrs['pixmap2D']))
1096
-
1097
- return scan_list
1098
-
1099
- def get_h5_attributes(self, node_path):
1100
- """
1101
-
1102
- """
1103
-
1104
- node = self.get_node(node_path)
1105
- attrs_names = node.attrs.attrs_name
1106
- attr_dict = OrderedDict([])
1107
- for attr in attrs_names:
1108
- # if attr!='settings':
1109
- attr_dict[attr] = node.attrs[attr]
1110
-
1111
- settings = None
1112
- scan_settings = None
1113
- if 'settings' in attrs_names:
1114
- if node.attrs['settings'] != '':
1115
- settings = node.attrs['settings']
1116
-
1117
- if 'scan_settings' in attrs_names:
1118
- if node.attrs['scan_settings'] != '':
1119
- scan_settings = node.attrs['scan_settings']
1120
- pixmaps = []
1121
- for attr in attrs_names:
1122
- if 'pixmap' in attr:
1123
- pixmaps.append(node.attrs[attr])
1124
-
1125
- return attr_dict, settings, scan_settings, pixmaps
1126
-
1127
-
1128
- class H5Browser(QObject):
1129
- """UI used to explore h5 files, plot and export subdatas"""
1130
- data_node_signal = Signal(
1131
- str) # the path of a node where data should be monitored, displayed...whatever use from the caller
1132
- status_signal = Signal(str)
1133
-
1134
- def __init__(self, parent, h5file=None, h5file_path=None, backend='tables'):
1135
- """
1136
-
1137
- Parameters
1138
- ----------
1139
- parent: QtWidgets container, either a QWidget or a QMainWindow
1140
- h5file: h5file instance (exact type depends on the backend)
1141
- h5file_path: (str or Path) if specified load the corresponding file, otherwise open a select file dialog
1142
- backend: (str) eitre 'tables, 'h5py' or 'h5pyd'
1143
-
1144
- See Also
1145
- --------
1146
- H5Backend, H5Backend
1147
- """
1148
-
1149
- super(H5Browser, self).__init__()
1150
- if not (isinstance(parent, QtWidgets.QWidget) or isinstance(parent, QtWidgets.QMainWindow)):
1151
- raise Exception('no valid parent container, expected a QWidget or a QMainWindow')
1152
-
1153
- if isinstance(parent, QtWidgets.QMainWindow):
1154
- self.main_window = parent
1155
- self.parent = QtWidgets.QWidget()
1156
- self.main_window.setCentralWidget(self.parent)
1157
- else:
1158
- self.main_window = None
1159
- self.parent = parent
1160
-
1161
- self.backend = backend
1162
- self.current_node_path = None
1163
-
1164
- # construct the UI interface
1165
- self.ui = QObject() # the user interface
1166
- self.set_GUI()
1167
-
1168
- # construct the h5 interface and load the file (or open a select file message)
1169
- self.h5utils = H5BrowserUtil(backend=self.backend)
1170
- if h5file is None:
1171
- if h5file_path is None:
1172
- h5file_path = select_file(save=False, ext=['h5', 'hdf5'])
1173
- if h5file_path != '':
1174
- self.h5utils.open_file(h5file_path, 'r+')
1175
- else:
1176
- return
1177
- else:
1178
- self.h5utils.h5file = h5file
1179
-
1180
- self.check_version()
1181
- self.populate_tree()
1182
- self.ui.h5file_tree.ui.Open_Tree.click()
1183
-
1184
- def check_version(self):
1185
- if 'pymodaq_version' in self.h5utils.root().attrs.attrs_name:
1186
- if version_mod.parse(self.h5utils.root().attrs['pymodaq_version']) < version_mod.parse('2.0'):
1187
- msgBox = QtWidgets.QMessageBox(parent=None)
1188
- msgBox.setWindowTitle("Invalid version")
1189
- msgBox.setText(f"Your file has been saved using PyMoDAQ "
1190
- f"version {self.h5utils.root().attrs['pymodaq_version']} "
1191
- f"while you're using version: {utils.get_version()}\n"
1192
- f"Please create and use an adapted environment to use this version (up to 1.6.4):\n"
1193
- f"pip install pymodaq==1.6.4")
1194
- ret = msgBox.exec()
1195
- self.quit_fun()
1196
- if self.main_window is not None:
1197
- self.main_window.close()
1198
- else:
1199
- self.parent.close()
1200
-
1201
- def add_comments(self, status, comment=''):
1202
- try:
1203
- self.current_node_path = self.get_tree_node_path()
1204
- node = self.h5utils.get_node(self.current_node_path)
1205
- if 'comments' in node.attrs.attrs_name:
1206
- tmp = node.attrs['comments']
1207
- else:
1208
- tmp = ''
1209
- if comment == '':
1210
- text, res = QtWidgets.QInputDialog.getMultiLineText(None, 'Enter comments', 'Enter comments here:', tmp)
1211
- if res and text != '':
1212
- comment = text
1213
- node.attrs['comments'] = comment
1214
- else:
1215
- node.attrs['comments'] = tmp + comment
1216
-
1217
- self.h5utils.flush()
1218
-
1219
- except Exception as e:
1220
- logger.exception(str(e))
1221
-
1222
- def get_tree_node_path(self):
1223
- return self.ui.h5file_tree.ui.Tree.currentItem().text(2)
1224
-
1225
- def export_data(self):
1226
- try:
1227
- # get file filters automatically
1228
- file_filter = ExporterFactory.get_file_filters()
1229
- file = select_file(save=True, filter=file_filter)
1230
- self.current_node_path = self.get_tree_node_path()
1231
- # Here select and initialize exporter from extension
1232
- if file != '':
1233
- self.h5utils.export_data(self.current_node_path, str(file))
1234
-
1235
- except Exception as e:
1236
- logger.exception(str(e))
1237
-
1238
- def save_file(self, filename=None):
1239
- if filename is None:
1240
- filename = select_file(save=True, ext='txt')
1241
- if filename != '':
1242
- self.h5utils.save_file(filename)
1243
-
1244
- def quit_fun(self):
1245
- """
1246
- """
1247
- try:
1248
- self.h5utils.close_file()
1249
- if self.main_window is None:
1250
- self.parent.close()
1251
- else:
1252
- self.main_window.close()
1253
- except Exception as e:
1254
- logger.exception(str(e))
1255
-
1256
- def create_menu(self):
1257
- """
1258
-
1259
- """
1260
- self.menubar = self.main_window.menuBar()
1261
-
1262
- # %% create Settings menu
1263
- self.file_menu = self.menubar.addMenu('File')
1264
- load_action = self.file_menu.addAction('Load file')
1265
- load_action.triggered.connect(lambda: self.load_file(None))
1266
- save_action = self.file_menu.addAction('Save file')
1267
- save_action.triggered.connect(self.save_file)
1268
- self.file_menu.addSeparator()
1269
- quit_action = self.file_menu.addAction('Quit')
1270
- quit_action.triggered.connect(self.quit_fun)
1271
-
1272
- # help menu
1273
- help_menu = self.menubar.addMenu('?')
1274
- action_about = help_menu.addAction('About')
1275
- action_about.triggered.connect(self.show_about)
1276
- action_help = help_menu.addAction('Help')
1277
- action_help.triggered.connect(self.show_help)
1278
- action_help.setShortcut(QtCore.Qt.Key_F1)
1279
- log_action = help_menu.addAction('Show log')
1280
- log_action.triggered.connect(self.show_log)
1281
-
1282
- def show_about(self):
1283
- splash_path = os.path.join(os.path.split(os.path.split(__file__)[0])[0], 'splash.png')
1284
- splash = QtGui.QPixmap(splash_path)
1285
- self.splash_sc = QtWidgets.QSplashScreen(splash, QtCore.Qt.WindowStaysOnTopHint)
1286
- self.splash_sc.setVisible(True)
1287
- self.splash_sc.showMessage(f"PyMoDAQ version {utils.get_version()}\n"
1288
- f"Modular Acquisition with Python\nWritten by Sébastien Weber",
1289
- QtCore.Qt.AlignRight, QtCore.Qt.white)
1290
-
1291
- def show_log(self):
1292
- import webbrowser
1293
- webbrowser.open(logger.handlers[0].baseFilename)
1294
-
1295
- def show_help(self):
1296
- QtGui.QDesktopServices.openUrl(QtCore.QUrl("http://pymodaq.cnrs.fr"))
1297
-
1298
- def set_GUI(self):
1299
-
1300
- if self.main_window is not None:
1301
- self.create_menu()
1302
-
1303
- layout = QtWidgets.QGridLayout()
1304
-
1305
- V_splitter = QtWidgets.QSplitter(Qt.Vertical)
1306
- V_splitter2 = QtWidgets.QSplitter(Qt.Vertical)
1307
- H_splitter = QtWidgets.QSplitter(Qt.Horizontal)
1308
-
1309
- Form = QtWidgets.QWidget()
1310
- # self.ui.h5file_tree = Tree_layout(Form,col_counts=2,labels=["Node",'Pixmap'])
1311
- self.ui.h5file_tree = Tree_layout(Form, col_counts=1, labels=["Node"])
1312
- self.ui.h5file_tree.ui.Tree.setMinimumWidth(300)
1313
- self.ui.h5file_tree.ui.Tree.itemClicked.connect(self.show_h5_attributes)
1314
- self.ui.h5file_tree.ui.Tree.itemDoubleClicked.connect(self.show_h5_data)
1315
-
1316
- self.export_action = QtWidgets.QAction("Export data", None)
1317
- self.export_action.triggered.connect(self.export_data)
1318
-
1319
- self.add_comments_action = QtWidgets.QAction("Add comments to this node", None)
1320
- self.add_comments_action.triggered.connect(self.add_comments)
1321
- self.ui.h5file_tree.ui.Tree.addAction(self.export_action)
1322
- self.ui.h5file_tree.ui.Tree.addAction(self.add_comments_action)
1323
-
1324
- V_splitter.addWidget(Form)
1325
- self.ui.attributes_tree = ParameterTree()
1326
- self.ui.attributes_tree.setMinimumWidth(300)
1327
- V_splitter.addWidget(self.ui.attributes_tree)
1328
-
1329
- self.settings_raw = Parameter.create(name='Param_raw', type='group')
1330
- self.ui.attributes_tree.setParameters(self.settings_raw, showTop=False)
1331
-
1332
- H_splitter.addWidget(V_splitter)
1333
- self.pixmap_widget = QtWidgets.QWidget()
1334
- self.pixmap_widget.setMaximumHeight(100)
1335
- V_splitter2.addWidget(self.pixmap_widget)
1336
- self.ui.settings_tree = ParameterTree()
1337
- self.ui.settings_tree.setMinimumWidth(300)
1338
- V_splitter2.addWidget(self.ui.settings_tree)
1339
- self.ui.text_list = QtWidgets.QListWidget()
1340
-
1341
- V_splitter2.addWidget(self.ui.text_list)
1342
-
1343
- H_splitter.addWidget(V_splitter2)
1344
-
1345
- form_viewer = QtWidgets.QWidget()
1346
- self.viewer_area = DockArea()
1347
- self.hyperviewer = ViewerND(self.viewer_area)
1348
- H_splitter.addWidget(self.viewer_area)
1349
-
1350
- layout.addWidget(H_splitter)
1351
- self.parent.setLayout(layout)
1352
-
1353
- self.settings = Parameter.create(name='Param', type='group')
1354
- self.ui.settings_tree.setParameters(self.settings, showTop=False)
1355
-
1356
- self.status_signal.connect(self.add_log)
1357
-
1358
- def add_log(self, txt):
1359
- logger.info(txt)
1360
-
1361
- def show_h5_attributes(self, item):
1362
- """
1363
-
1364
- """
1365
- try:
1366
- self.current_node_path = self.get_tree_node_path()
1367
-
1368
- attr_dict, settings, scan_settings, pixmaps = self.h5utils.get_h5_attributes(self.current_node_path)
1369
-
1370
- for child in self.settings_raw.children():
1371
- child.remove()
1372
- params = []
1373
- for attr in attr_dict:
1374
- params.append({'title': attr, 'name': attr, 'type': 'str', 'value': attr_dict[attr], 'readonly': True})
1375
- self.settings_raw.addChildren(params)
1376
-
1377
- if settings is not None:
1378
- for child in self.settings.children():
1379
- child.remove()
1380
- QtWidgets.QApplication.processEvents() # so that the tree associated with settings updates
1381
- params = pymodaq.daq_utils.parameter.ioxml.XML_string_to_parameter(settings)
1382
- self.settings.addChildren(params)
1383
-
1384
- if scan_settings is not None:
1385
- params = pymodaq.daq_utils.parameter.ioxml.XML_string_to_parameter(scan_settings)
1386
- self.settings.addChildren(params)
1387
-
1388
- if pixmaps == []:
1389
- self.pixmap_widget.setVisible(False)
1390
- else:
1391
- self.pixmap_widget.setVisible(True)
1392
- self.show_pixmaps(pixmaps)
1393
-
1394
- except Exception as e:
1395
- logger.exception(str(e))
1396
-
1397
- def show_pixmaps(self, pixmaps=[]):
1398
- if self.pixmap_widget.layout() is None:
1399
- layout = QtWidgets.QHBoxLayout()
1400
- self.pixmap_widget.setLayout(layout)
1401
- while 1:
1402
- child = self.pixmap_widget.layout().takeAt(0)
1403
- if not child:
1404
- break
1405
- child.widget().deleteLater()
1406
- QtWidgets.QApplication.processEvents()
1407
- labs = []
1408
- for pix in pixmaps:
1409
- labs.append(pngbinary2Qlabel(pix))
1410
- self.pixmap_widget.layout().addWidget(labs[-1])
1411
-
1412
- def show_h5_data(self, item):
1413
- """
1414
- """
1415
- try:
1416
- self.current_node_path = item.text(2)
1417
- self.show_h5_attributes(item)
1418
- node = self.h5utils.get_node(self.current_node_path)
1419
- self.data_node_signal.emit(self.current_node_path)
1420
- if 'ARRAY' in node.attrs['CLASS']:
1421
- data, axes, nav_axes, is_spread = get_h5_data_from_node(node)
1422
- # data, axes, nav_axes, is_spread = self.h5utils.get_h5_data(self.current_node_path)
1423
- if isinstance(data, np.ndarray):
1424
- if 'scan_type' in node.attrs.attrs_name:
1425
- scan_type = node.attrs['scan_type']
1426
- else:
1427
- scan_type = ''
1428
- self.hyperviewer.show_data(copy.deepcopy(data), nav_axes=nav_axes, is_spread=is_spread,
1429
- scan_type=scan_type, **copy.deepcopy(axes))
1430
- self.hyperviewer.init_ROI()
1431
- elif isinstance(data, list):
1432
- if not (not data):
1433
- if isinstance(data[0], str):
1434
- self.ui.text_list.clear()
1435
- for txt in data:
1436
- self.ui.text_list.addItem(txt)
1437
- except Exception as e:
1438
- logger.exception(str(e))
1439
-
1440
- def populate_tree(self):
1441
- """
1442
- | Init the ui-tree and store data into calling the h5_tree_to_Qtree convertor method
1443
-
1444
- See Also
1445
- --------
1446
- h5tree_to_QTree, update_status
1447
- """
1448
- try:
1449
- if self.h5utils.h5file is not None:
1450
- self.ui.h5file_tree.ui.Tree.clear()
1451
- base_node = self.h5utils.root()
1452
- base_tree_item, pixmap_items = h5tree_to_QTree(base_node)
1453
- self.ui.h5file_tree.ui.Tree.addTopLevelItem(base_tree_item)
1454
- self.add_widget_totree(pixmap_items)
1455
-
1456
- except Exception as e:
1457
- logger.exception(str(e))
1458
-
1459
- def add_widget_totree(self, pixmap_items):
1460
-
1461
- for item in pixmap_items:
1462
- widget = QtWidgets.QWidget()
1463
-
1464
- vLayout = QtWidgets.QVBoxLayout()
1465
- label1D = QtWidgets.QLabel()
1466
- bytes = QByteArray(item['node'].attrs['pixmap1D'])
1467
- im1 = QtGui.QImage.fromData(bytes)
1468
- a = QtGui.QPixmap.fromImage(im1)
1469
- label1D.setPixmap(a)
1470
-
1471
- label2D = QtWidgets.QLabel()
1472
- bytes = QByteArray(item['node'].attrs['pixmap2D'])
1473
- im2 = QtGui.QImage.fromData(bytes)
1474
- b = QtGui.QPixmap.fromImage(im2)
1475
- label2D.setPixmap(b)
1476
-
1477
- vLayout.addWidget(label1D)
1478
- vLayout.addwidget(label2D)
1479
- widget.setLayout(vLayout)
1480
- self.ui.h5file_tree.ui.Tree.setItemWidget(item['item'], 1, widget)
1481
-
1482
-
1483
- def browse_data(fname=None, ret_all=False, message=None):
1484
- """
1485
- | Browse data present in any h5 file, when user has selected the one,
1486
- """
1487
- if fname is None:
1488
- fname = str(select_file(start_path=config('data_saving', 'h5file', 'save_path'), save=False, ext='h5'))
1489
-
1490
- if type(fname) != str:
1491
- try:
1492
- fname = str(fname)
1493
- except Exception:
1494
- raise Exception('filename in browse data is not valid')
1495
- if fname != '':
1496
- (root, ext) = os.path.splitext(fname)
1497
- if not ('h5' in ext or 'hdf5' in ext):
1498
- warnings.warn('This is not a PyMODAQ h5 file, there could be issues', Warning)
1499
-
1500
- form = QtWidgets.QWidget()
1501
- browser = H5Browser(form, h5file_path=fname)
1502
-
1503
- dialog = QtWidgets.QDialog()
1504
- vlayout = QtWidgets.QVBoxLayout()
1505
-
1506
- vlayout.addWidget(form)
1507
- dialog.setLayout(vlayout)
1508
- buttonBox = QtWidgets.QDialogButtonBox(parent=dialog)
1509
-
1510
- buttonBox.addButton('OK', buttonBox.AcceptRole)
1511
- buttonBox.accepted.connect(dialog.accept)
1512
- buttonBox.addButton('Cancel', buttonBox.RejectRole)
1513
- buttonBox.rejected.connect(dialog.reject)
1514
- vlayout.addWidget(buttonBox)
1515
-
1516
- dialog.setWindowTitle('Select a data node in the tree')
1517
- if message is None or not isinstance(message, str):
1518
- dialog.setWindowTitle('Select a data node in the tree')
1519
- else:
1520
- dialog.setWindowTitle(message)
1521
- res = dialog.exec()
1522
-
1523
- if res == dialog.Accepted:
1524
- node_path = browser.current_node_path
1525
- data = browser.h5utils.get_node(node_path).read()
1526
- else:
1527
- data = None
1528
- node_path = None
1529
-
1530
- browser.h5utils.close_file()
1531
-
1532
- if ret_all:
1533
- return data, fname, node_path
1534
- else:
1535
- return data
1536
- return None, '', ''
1537
-
1538
-
1539
- ### Additional imports. This is needed to register additional formats in the Exporterfactory
1540
-
1541
- # This Exporter depends on the availability of the hyperspy package.
1542
- # In the future, adding an optional dependency
1543
- found = importlib.util.find_spec("hyperspy")
1544
- if not found:
1545
- logger.warning('Hyperspy module not found. To save data in the .hspy format, install hyperspy 1.7 or more recent.')
1546
- else:
1547
- import pymodaq.daq_utils.h5exporter_hyperspy
1548
-
1549
- if __name__ == '__main__':
1550
- app = QtWidgets.QApplication(sys.argv)
1551
- win = QtWidgets.QMainWindow()
1552
- area = DockArea()
1553
- win.setCentralWidget(area)
1554
- win.resize(1000, 500)
1555
- win.setWindowTitle('PyMoDAQ H5Browser')
1556
- prog = H5Browser(win)
1557
- win.show()
1558
- QtWidgets.QApplication.processEvents()
1559
- sys.exit(app.exec_())