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
@@ -0,0 +1,683 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created the 15/11/2022
4
+
5
+ @author: Sebastien Weber
6
+ """
7
+ from typing import Tuple
8
+ import os
9
+ from collections import OrderedDict
10
+ from typing import List
11
+ import warnings
12
+ import logging
13
+ import webbrowser
14
+ import numpy as np
15
+ from pathlib import Path
16
+ from packaging import version as version_mod
17
+
18
+ from pymodaq.utils.logger import set_logger, get_module_name
19
+ from pymodaq.utils.config import Config
20
+ from qtpy import QtGui, QtCore
21
+ from qtpy.QtCore import Qt, QObject, Signal, QByteArray
22
+
23
+ import pymodaq.utils.parameter.ioxml
24
+
25
+ from pymodaq.utils.tree_layout.tree_layout_main import TreeLayout
26
+ from pymodaq.utils.daq_utils import capitalize
27
+ from pymodaq.utils.data import Axis
28
+ from pymodaq.utils.gui_utils.utils import h5tree_to_QTree, pngbinary2Qlabel
29
+ from pymodaq.utils.gui_utils.file_io import select_file
30
+ from pymodaq.utils.plotting.data_viewers.viewerND import ViewerND
31
+ from qtpy import QtWidgets
32
+ from pymodaq.utils import daq_utils as utils
33
+ from pymodaq.utils.managers.action_manager import ActionManager
34
+ from pymodaq.utils.managers.parameter_manager import ParameterManager
35
+ from pymodaq.utils.messenger import messagebox
36
+ from .backends import H5Backend
37
+ from .saving import H5Saver
38
+ from . import data_saving
39
+ from .utils import find_scan_node
40
+
41
+ config = Config()
42
+ logger = set_logger(get_module_name(__file__))
43
+
44
+
45
+ class H5BrowserUtil(H5Backend):
46
+ """Utility object to interact and get info and data from a hdf5 file
47
+
48
+ Inherits H5Backend and all its functionalities
49
+
50
+ Parameters
51
+ ----------
52
+ backend: str
53
+ The used hdf5 backend: either tables, h5py or h5pyd
54
+ """
55
+ def __init__(self, backend='tables'):
56
+ super().__init__(backend=backend)
57
+
58
+ def export_data(self, node_path='/', filesavename='datafile.h5'):
59
+ """Export data in nodes in another file format
60
+
61
+ Parameters
62
+ ----------
63
+ node_path: str
64
+ the path in the file
65
+ filesavename:
66
+ the exported file name with a particular extension
67
+ Accepted extensions are:
68
+ * txt: to save node content in a tab delimited text file
69
+ * ascii: to save node content in a tab delimited ascii file
70
+ * h5
71
+ """
72
+ if filesavename != '':
73
+ file = Path(filesavename)
74
+ node = self.get_node(node_path)
75
+ if file.suffix == '.txt' or file.suffix == '.ascii':
76
+ if 'ARRAY' in node.attrs['CLASS']:
77
+ data = node.read()
78
+ if not isinstance(data, np.ndarray):
79
+ # in case one has a list of same objects (array of strings for instance, logger or other)
80
+ data = np.array(data)
81
+ np.savetxt(filesavename,
82
+ data if file.suffix == '.txt' else data.T if len(data.shape) > 1 else [data],
83
+ '%s', '\t')
84
+ else:
85
+ np.savetxt(filesavename,
86
+ data if file.suffix == '.txt' else data.T if len(data.shape) > 1 else [data],
87
+ '%.6e', '\t')
88
+
89
+ elif 'GROUP' in node.attrs['CLASS']:
90
+ data_tot = []
91
+ header = []
92
+ dtypes = []
93
+ fmts = []
94
+ for subnode_name, subnode in node.children().items():
95
+ if 'ARRAY' in subnode.attrs['CLASS']:
96
+ if len(subnode.attrs['shape']) == 1:
97
+ data = subnode.read()
98
+ if not isinstance(data, np.ndarray):
99
+ # in case one has a list of same objects (array of strings for instance, logger or other)
100
+ data = np.array(data)
101
+ data_tot.append(data)
102
+ dtypes.append((subnode_name, data.dtype))
103
+ header.append(subnode_name)
104
+ if data.dtype.char == 'U':
105
+ fmt = '%s' # for strings
106
+ elif data.dtype.char == 'l':
107
+ fmt = '%d' # for integers
108
+ else:
109
+ fmt = '%.6f' # for decimal numbers
110
+ fmts.append(fmt)
111
+
112
+ data_trans = np.array(list(zip(*data_tot)), dtype=dtypes)
113
+ np.savetxt(filesavename, data_trans, fmts, '\t', header='#' + '\t'.join(header))
114
+ elif file.suffix == '.h5':
115
+ self.save_file_as(str(file))
116
+ copied_file = H5Backend()
117
+ copied_file.open_file(str(file), 'a')
118
+
119
+ copied_file.h5file.move_node(self.get_node_path(node), newparent=copied_file.h5file.get_node('/'))
120
+ copied_file.h5file.remove_node('/Raw_datas', recursive=True)
121
+ copied_file.close_file()
122
+
123
+ def get_h5file_scans(self, where='/'):
124
+ """Get the list of the scan nodes in the file
125
+
126
+ Parameters
127
+ ----------
128
+ where: str
129
+ the path in the file
130
+
131
+ Returns
132
+ -------
133
+ list of dict
134
+ dict with keys: scan_name, path (within the file) and data (the live scan png image)
135
+ """
136
+ # TODO add a test for this method
137
+ scan_list = []
138
+ where = self.get_node(where)
139
+ for node in self.walk_nodes(where):
140
+ if 'pixmap2D' in node.attrs:
141
+ scan_list.append(
142
+ dict(scan_name='{:s}_{:s}'.format(node.parent_node.name, node.name), path=node.path,
143
+ data=node.attrs['pixmap2D']))
144
+
145
+ return scan_list
146
+
147
+ def get_h5_attributes(self, node_path):
148
+ """Get the list of attributes (metadata) of a given node
149
+
150
+ Parameters
151
+ ----------
152
+ node_path: str
153
+ the path in the file
154
+
155
+ Returns
156
+ -------
157
+ attr_dict: OrderedDict
158
+ attributes as a dict
159
+ settings: str
160
+ settings attribute
161
+ scan_settings: str
162
+ scan settings attribute
163
+ pixmaps: list of pixmap
164
+ """
165
+ node = self.get_node(node_path)
166
+ attrs_names = node.attrs.attrs_name
167
+ attr_dict = OrderedDict([])
168
+ for attr in attrs_names:
169
+ # if attr!='settings':
170
+ attr_dict[attr] = node.attrs[attr]
171
+
172
+ settings = None
173
+ scan_settings = None
174
+ if 'settings' in attrs_names:
175
+ if node.attrs['settings'] != '':
176
+ settings = node.attrs['settings']
177
+
178
+ if 'scan_settings' in attrs_names:
179
+ if node.attrs['scan_settings'] != '':
180
+ scan_settings = node.attrs['scan_settings']
181
+ pixmaps = []
182
+ for attr in attrs_names:
183
+ if 'pixmap' in attr:
184
+ pixmaps.append(node.attrs[attr])
185
+
186
+ return attr_dict, settings, scan_settings, pixmaps
187
+
188
+
189
+ class View(QObject):
190
+ item_clicked_sig = Signal(object)
191
+ item_double_clicked_sig = Signal(object)
192
+
193
+ def __init__(self, widget: QtWidgets.QWidget, settings_tree, settings_attributes_tree):
194
+ super().__init__()
195
+ self.parent_widget = widget
196
+ self.h5file_tree: TreeLayout = None
197
+
198
+ self._viewer_widget: QtWidgets.QWidget = None
199
+ self._text_list: QtWidgets.QListWidget = None
200
+ self._pixmap_widget: QtWidgets.QWidget = None
201
+
202
+ self.setup_ui(settings_tree, settings_attributes_tree)
203
+
204
+ def setup_ui(self, settings_tree, settings_attributes_tree):
205
+ layout = QtWidgets.QGridLayout()
206
+
207
+ v_splitter = QtWidgets.QSplitter(Qt.Vertical)
208
+ v_splitter2 = QtWidgets.QSplitter(Qt.Vertical)
209
+ h_splitter = QtWidgets.QSplitter(Qt.Horizontal)
210
+
211
+ widget = QtWidgets.QWidget()
212
+ # self.ui.h5file_tree = TreeLayout(Form,col_counts=2,labels=["Node",'Pixmap'])
213
+ self.h5file_tree = TreeLayout(widget, col_counts=1, labels=["Node"])
214
+ self.h5file_tree.tree.setMinimumWidth(300)
215
+
216
+ self.h5file_tree.item_clicked_sig.connect(self.item_clicked_sig.emit)
217
+ self.h5file_tree.item_double_clicked_sig.connect(self.item_double_clicked_sig.emit)
218
+
219
+ v_splitter.addWidget(widget)
220
+ v_splitter.addWidget(settings_attributes_tree)
221
+
222
+ h_splitter.addWidget(v_splitter)
223
+ self._pixmap_widget = QtWidgets.QWidget()
224
+ self._pixmap_widget.setMaximumHeight(100)
225
+ v_splitter2.addWidget(self._pixmap_widget)
226
+
227
+ v_splitter2.addWidget(settings_tree)
228
+ self._text_list = QtWidgets.QListWidget()
229
+
230
+ v_splitter2.addWidget(self._text_list)
231
+ h_splitter.addWidget(v_splitter2)
232
+ self._viewer_widget = QtWidgets.QWidget()
233
+ h_splitter.addWidget(self._viewer_widget)
234
+ layout.addWidget(h_splitter)
235
+ self.parent_widget.setLayout(layout)
236
+
237
+ def current_node_path(self):
238
+ return self.h5file_tree.current_node_path()
239
+
240
+ def add_actions(self, actions: List[QtWidgets.QAction]):
241
+ for action in actions:
242
+ self.h5file_tree.tree.addAction(action)
243
+
244
+ @property
245
+ def viewer_widget(self):
246
+ return self._viewer_widget
247
+
248
+ @property
249
+ def text_list(self):
250
+ return self._text_list
251
+
252
+ @property
253
+ def pixmap_widget(self):
254
+ return self._pixmap_widget
255
+
256
+ def clear(self):
257
+ self.h5file_tree.tree.clear()
258
+
259
+ def add_base_item(self, base_tree_item):
260
+ self.h5file_tree.tree.addTopLevelItem(base_tree_item)
261
+
262
+ def add_widget_to_tree(self, pixmap_items):
263
+ for item in pixmap_items:
264
+ widget = QtWidgets.QWidget()
265
+
266
+ vLayout = QtWidgets.QVBoxLayout()
267
+ label1D = QtWidgets.QLabel()
268
+ bytes = QByteArray(item['node'].attrs['pixmap1D'])
269
+ im1 = QtGui.QImage.fromData(bytes)
270
+ a = QtGui.QPixmap.fromImage(im1)
271
+ label1D.setPixmap(a)
272
+
273
+ label2D = QtWidgets.QLabel()
274
+ bytes = QByteArray(item['node'].attrs['pixmap2D'])
275
+ im2 = QtGui.QImage.fromData(bytes)
276
+ b = QtGui.QPixmap.fromImage(im2)
277
+ label2D.setPixmap(b)
278
+
279
+ vLayout.addWidget(label1D)
280
+ vLayout.addwidget(label2D)
281
+ widget.setLayout(vLayout)
282
+ self.h5file_tree.tree.setItemWidget(item['item'], 1, widget)
283
+
284
+
285
+ class H5Browser(QObject, ActionManager):
286
+ """UI used to explore h5 files, plot and export subdatas
287
+
288
+ Parameters
289
+ ----------
290
+ parent: QtWidgets container
291
+ either a QWidget or a QMainWindow
292
+ h5file: h5file instance
293
+ exact type depends on the backend
294
+ h5file_path: str or Path
295
+ if specified load the corresponding file, otherwise open a select file dialog
296
+ backend: str
297
+ either 'tables, 'h5py' or 'h5pyd'
298
+
299
+ See Also
300
+ --------
301
+ H5Backend, H5Backend
302
+ """
303
+ data_node_signal = Signal(str) # the path of a node where data should be monitored, displayed...
304
+ # whatever use from the caller
305
+ status_signal = Signal(str)
306
+
307
+ def __init__(self, parent: QtWidgets.QMainWindow, h5file=None, h5file_path=None, backend='tables'):
308
+ QObject.__init__(self)
309
+ # toolbar = QtWidgets.QToolBar()
310
+ ActionManager.__init__(self) # , toolbar=toolbar)
311
+
312
+ if not isinstance(parent, QtWidgets.QMainWindow):
313
+ raise Exception('no valid parent container, expected a QMainWindow')
314
+
315
+ self.main_window = parent
316
+ self.parent_widget = QtWidgets.QWidget()
317
+ self.main_window.setCentralWidget(self.parent_widget)
318
+ #self.main_window.addToolBar(self.toolbar)
319
+
320
+ self.current_node_path = None
321
+
322
+ self.settings_attributes = ParameterManager()
323
+ self.settings = ParameterManager()
324
+
325
+ # construct the UI interface
326
+ self.view = View(self.parent_widget, settings_tree=self.settings.settings_tree,
327
+ settings_attributes_tree=self.settings_attributes.settings_tree)
328
+ self.view.item_clicked_sig.connect(self.show_h5_attributes)
329
+ self.view.item_double_clicked_sig.connect(self.show_h5_data)
330
+ self.hyper_viewer = ViewerND(self.view.viewer_widget)
331
+
332
+ self.setup_actions()
333
+ self.setup_menu()
334
+ self.connect_things()
335
+
336
+ # construct the h5 interface and load the file (or open a select file message)
337
+ self.h5utils = H5BrowserUtil(backend=backend)
338
+ if h5file is None:
339
+ if h5file_path is None:
340
+ h5file_path = select_file(save=False, ext=['h5', 'hdf5'])
341
+ if h5file_path != '':
342
+ self.h5utils.open_file(h5file_path, 'r+')
343
+ else:
344
+ return
345
+ else:
346
+ self.h5utils.h5file = h5file
347
+
348
+ self.data_loader = data_saving.DataLoader(self.h5utils)
349
+
350
+ self.check_version()
351
+ self.populate_tree()
352
+ self.view.h5file_tree.expand_all()
353
+
354
+ def connect_things(self):
355
+ self.connect_action('export', self.export_data)
356
+ self.connect_action('comment', self.add_comments)
357
+ self.connect_action('load', self.load_file)
358
+ self.connect_action('save', self.save_file)
359
+ self.connect_action('quit', self.quit_fun)
360
+ self.connect_action('about', self.show_about)
361
+ self.connect_action('help', self.show_help)
362
+ self.connect_action('log', self.show_log)
363
+
364
+ self.connect_action('plot_node', lambda: self.get_node_and_plot(False))
365
+ self.connect_action('plot_nodes', lambda: self.get_node_and_plot(False, True))
366
+ self.connect_action('plot_node_with_bkg', lambda: self.get_node_and_plot(True))
367
+ self.connect_action('plot_nodes_with_bkg', lambda: self.get_node_and_plot(True, True))
368
+
369
+ self.status_signal.connect(self.add_log)
370
+
371
+ def get_node_and_plot(self, with_bkg, plot_all=False):
372
+ self.show_h5_data(item=None, with_bkg=with_bkg, plot_all=plot_all)
373
+
374
+ def load_file(self):
375
+ #todo
376
+ pass
377
+
378
+ def setup_menu(self):
379
+ menubar = self.main_window.menuBar()
380
+ file_menu = menubar.addMenu('File')
381
+ self.affect_to('load', file_menu)
382
+ self.affect_to('save', file_menu)
383
+ file_menu.addSeparator()
384
+ self.affect_to('quit', file_menu)
385
+
386
+ help_menu = menubar.addMenu('?')
387
+ self.affect_to('about', help_menu)
388
+ self.affect_to('help', help_menu)
389
+ self.affect_to('log', help_menu)
390
+
391
+ def setup_actions(self):
392
+ self.add_action('export', 'Export as', 'SaveAs', tip='Export node content (and children) as ',
393
+ toolbar=self.toolbar)
394
+ self.add_action('comment', 'Add Comment', 'properties', tip='Add comments to the node',
395
+ toolbar=self.toolbar)
396
+ self.add_action('plot_node', 'Plot Node', 'color', tip='Plot the current node',
397
+ toolbar=self.toolbar)
398
+ self.add_action('plot_nodes', 'Plot Nodes', 'color', tip='Plot all nodes hanging from the same parent',
399
+ toolbar=self.toolbar)
400
+ self.add_action('plot_node_with_bkg', 'Plot Node With Bkg', 'color', tip='Plot the current node with background'
401
+ ' substraction if possible',
402
+ toolbar=self.toolbar)
403
+ self.add_action('plot_nodes_with_bkg', 'Plot Nodes With Bkg', 'color', tip='Plot all nodes hanging from'
404
+ ' the same parent with background'
405
+ ' substraction if possible',
406
+ toolbar=self.toolbar)
407
+ self.view.add_actions([self.get_action('export'), self.get_action('comment'),
408
+ self.get_action('plot_node'), self.get_action('plot_nodes'),
409
+ self.get_action('plot_node_with_bkg'),
410
+ self.get_action('plot_nodes_with_bkg')])
411
+
412
+ self.add_action('load', 'Load File', 'Open', tip='Open a new file')
413
+ self.add_action('save', 'Save File as', 'SaveAs', tip='Save as another file')
414
+ self.add_action('quit', 'Quit the application', 'Exit', tip='Quit the application')
415
+ self.add_action('about', 'About', tip='About')
416
+ self.add_action('help', 'Help', 'Help', tip='Show documentation', shortcut=QtCore.Qt.Key_F1)
417
+ self.add_action('log', 'Show Log', 'information2', tip='Open Log')
418
+
419
+ def check_version(self):
420
+ """Check version of PyMoDAQ to assert if file is compatible or not with the current version of the Browser"""
421
+ if 'pymodaq_version' in self.h5utils.root().attrs.attrs_name:
422
+ if version_mod.parse(self.h5utils.root().attrs['pymodaq_version']) < version_mod.parse('4.0.0a0'):
423
+ msg_box = messagebox(severity='warning', title='Invalid version',
424
+ text=f"Your file has been saved using PyMoDAQ "
425
+ f"version {self.h5utils.root().attrs['pymodaq_version']} "
426
+ f"while you're using version: {utils.get_version()}\n"
427
+ f"Please create and use an adapted environment to use this"
428
+ f" version (up to 3.x.y):\n"
429
+ f"pip install pymodaq==3.x.y")
430
+ self.quit_fun()
431
+
432
+ def add_comments(self, status: bool, comment=''):
433
+ """Add comments to a node
434
+
435
+ Parameters
436
+ ----------
437
+ status: bool
438
+ comment: str
439
+ The comment to be added in a comment attribute to the current node path
440
+
441
+ See Also
442
+ --------
443
+ current_node_path
444
+ """
445
+ try:
446
+ self.current_node_path = self.get_tree_node_path()
447
+ node = self.h5utils.get_node(self.current_node_path)
448
+ if 'comments' in node.attrs.attrs_name:
449
+ tmp = node.attrs['comments']
450
+ else:
451
+ tmp = ''
452
+ if comment == '':
453
+ text, res = QtWidgets.QInputDialog.getMultiLineText(None, 'Enter comments', 'Enter comments here:', tmp)
454
+ if res and text != '':
455
+ comment = text
456
+ node.attrs['comments'] = comment
457
+ else:
458
+ node.attrs['comments'] = tmp + comment
459
+
460
+ self.h5utils.flush()
461
+
462
+ except Exception as e:
463
+ logger.exception(str(e))
464
+
465
+ def get_tree_node_path(self):
466
+ """Get the node path of the currently selected node in the UI"""
467
+ return self.view.current_node_path()
468
+
469
+ def export_data(self):
470
+ """Opens a dialog to export data
471
+
472
+ See Also
473
+ --------
474
+ H5BrowserUtil.export_data
475
+ """
476
+ try:
477
+ file_filter = "Single node h5 file (*.h5);;Text files (*.txt);;Ascii file (*.ascii)"
478
+ file = select_file(save=True, filter=file_filter)
479
+ self.current_node_path = self.get_tree_node_path()
480
+ if file != '':
481
+ self.h5utils.export_data(self.current_node_path, str(file))
482
+
483
+ except Exception as e:
484
+ logger.exception(str(e))
485
+
486
+ def save_file(self, filename=None):
487
+
488
+ if filename is None:
489
+ filename = select_file(save=True, ext='txt')
490
+ if filename != '':
491
+ self.h5utils.save_file(filename)
492
+
493
+ def quit_fun(self):
494
+ """
495
+ """
496
+ try:
497
+ self.h5utils.close_file()
498
+ if self.main_window is None:
499
+ self.parent_widget.close()
500
+ else:
501
+ self.main_window.close()
502
+ except Exception as e:
503
+ logger.exception(str(e))
504
+
505
+ def show_about(self):
506
+ splash_path = os.path.join(os.path.split(os.path.split(__file__)[0])[0], 'splash.png')
507
+ splash = QtGui.QPixmap(splash_path)
508
+ self.splash_sc = QtWidgets.QSplashScreen(splash, QtCore.Qt.WindowStaysOnTopHint)
509
+ self.splash_sc.setVisible(True)
510
+ self.splash_sc.showMessage(f"PyMoDAQ version {utils.get_version()}\n"
511
+ f"Modular Acquisition with Python\nWritten by Sébastien Weber",
512
+ QtCore.Qt.AlignRight, QtCore.Qt.white)
513
+
514
+ @staticmethod
515
+ def show_log():
516
+ webbrowser.open(logging.getLogger('pymodaq').handlers[0].baseFilename)
517
+
518
+ @staticmethod
519
+ def show_help():
520
+ QtGui.QDesktopServices.openUrl(QtCore.QUrl("http://pymodaq.cnrs.fr"))
521
+
522
+ @staticmethod
523
+ def add_log(txt):
524
+ logger.info(txt)
525
+
526
+ def show_h5_attributes(self, item=None):
527
+ try:
528
+ self.current_node_path = self.get_tree_node_path()
529
+
530
+ attr_dict, settings, scan_settings, pixmaps = self.h5utils.get_h5_attributes(self.current_node_path)
531
+
532
+ for child in self.settings_attributes.settings.children():
533
+ child.remove()
534
+ params = []
535
+ for attr in attr_dict:
536
+ params.append({'title': attr, 'name': attr, 'type': 'str', 'value': attr_dict[attr], 'readonly': True})
537
+ self.settings_attributes.settings.addChildren(params)
538
+
539
+ if settings is not None:
540
+ for child in self.settings.settings.children():
541
+ child.remove()
542
+ QtWidgets.QApplication.processEvents() # so that the tree associated with settings updates
543
+ params = pymodaq.utils.parameter.ioxml.XML_string_to_parameter(settings)
544
+ self.settings.settings.addChildren(params)
545
+
546
+ if scan_settings is not None:
547
+ params = pymodaq.utils.parameter.ioxml.XML_string_to_parameter(scan_settings)
548
+ self.settings.settings.addChildren(params)
549
+
550
+ if pixmaps == []:
551
+ self.view.pixmap_widget.setVisible(False)
552
+ else:
553
+ self.view.pixmap_widget.setVisible(True)
554
+ self.show_pixmaps(pixmaps)
555
+
556
+ except Exception as e:
557
+ logger.exception(str(e))
558
+
559
+ def show_pixmaps(self, pixmaps=[]):
560
+ if self.view.pixmap_widget.layout() is None:
561
+ self.view.pixmap_widget.setLayout(QtWidgets.QHBoxLayout())
562
+ while 1:
563
+ child = self.view.pixmap_widget.layout().takeAt(0)
564
+ if not child:
565
+ break
566
+ child.widget().deleteLater()
567
+ QtWidgets.QApplication.processEvents()
568
+ labs = []
569
+ for pix in pixmaps:
570
+ labs.append(pngbinary2Qlabel(pix))
571
+ self.view.pixmap_widget.layout().addWidget(labs[-1])
572
+
573
+ def show_h5_data(self, item, with_bkg=False, plot_all=False):
574
+ """
575
+ """
576
+ try:
577
+ if item is None:
578
+ self.current_node_path = self.get_tree_node_path()
579
+ self.show_h5_attributes()
580
+ node = self.h5utils.get_node(self.current_node_path)
581
+ self.data_node_signal.emit(self.current_node_path)
582
+
583
+ if 'data_type' in node.attrs and node.attrs['data_type'] == 'strings':
584
+ self.view.text_list.clear()
585
+ for txt in node.read():
586
+ self.view.text_list.addItem(txt)
587
+ else:
588
+ data_with_axes = self.data_loader.load_data(node, with_bkg=with_bkg, load_all=plot_all)
589
+ self.hyper_viewer.show_data(data_with_axes, force_update=True)
590
+
591
+ except Exception as e:
592
+ logger.exception(str(e))
593
+
594
+ def populate_tree(self):
595
+ """
596
+ | Init the ui-tree and store data into calling the h5_tree_to_Qtree convertor method
597
+
598
+ See Also
599
+ --------
600
+ h5tree_to_QTree, update_status
601
+ """
602
+ try:
603
+ if self.h5utils.h5file is not None:
604
+ self.view.clear()
605
+ base_node = self.h5utils.root()
606
+ base_tree_item, pixmap_items = h5tree_to_QTree(base_node)
607
+ self.view.add_base_item(base_tree_item)
608
+ self.view.add_widget_to_tree(pixmap_items)
609
+
610
+ except Exception as e:
611
+ logger.exception(str(e))
612
+
613
+
614
+ def browse_data(fname=None, ret_all=False, message=None) -> Tuple[data_saving.DataWithAxes, str, str]:
615
+ """Browse data present in any h5 file using the H5Browser within a dialog window
616
+ when the user has selected a given node, return its content
617
+
618
+ Parameters
619
+ ----------
620
+ fname: str
621
+ ret_all: bool
622
+ message: str
623
+
624
+ Returns
625
+ -------
626
+ data: DataWithAxes
627
+ if argument ret_all is True, returns also:
628
+ fname: the file name
629
+ node_path: hte path of the selected node within the H5 file tree
630
+
631
+ """
632
+ if fname is None:
633
+ fname = str(select_file(start_path=config('data_saving', 'h5file', 'save_path'), save=False, ext='h5'))
634
+
635
+ if type(fname) != str:
636
+ try:
637
+ fname = str(fname)
638
+ except Exception:
639
+ raise Exception('filename in browse data is not valid')
640
+ if fname != '':
641
+ (root, ext) = os.path.splitext(fname)
642
+ if not ('h5' in ext or 'hdf5' in ext):
643
+ warnings.warn('This is not a PyMODAQ h5 file, there could be issues', Warning)
644
+
645
+ form = QtWidgets.QMainWindow()
646
+ browser = H5Browser(form, h5file_path=fname)
647
+ dataloader = data_saving.DataLoader(browser.h5utils)
648
+ dialog = QtWidgets.QDialog()
649
+ vlayout = QtWidgets.QVBoxLayout()
650
+
651
+ vlayout.addWidget(form)
652
+ dialog.setLayout(vlayout)
653
+ buttonBox = QtWidgets.QDialogButtonBox(parent=dialog)
654
+
655
+ buttonBox.addButton('OK', buttonBox.AcceptRole)
656
+ buttonBox.accepted.connect(dialog.accept)
657
+ buttonBox.addButton('Cancel', buttonBox.RejectRole)
658
+ buttonBox.rejected.connect(dialog.reject)
659
+ vlayout.addWidget(buttonBox)
660
+
661
+ dialog.setWindowTitle('Select a data node in the tree')
662
+ if message is None or not isinstance(message, str):
663
+ dialog.setWindowTitle('Select a data node in the tree')
664
+ else:
665
+ dialog.setWindowTitle(message)
666
+ res = dialog.exec()
667
+
668
+ if res == dialog.Accepted:
669
+ node_path = browser.current_node_path
670
+ data = dataloader.load_data(node_path, with_bkg=True)
671
+ else:
672
+ data = None
673
+ node_path = None
674
+
675
+ browser.h5utils.close_file()
676
+
677
+ if ret_all:
678
+ return data, fname, node_path
679
+ else:
680
+ return data
681
+ return None, '', ''
682
+
683
+