ewoksfluo 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. ewoksfluo-0.1.0/LICENSE.md +20 -0
  2. ewoksfluo-0.1.0/PKG-INFO +29 -0
  3. ewoksfluo-0.1.0/README.md +7 -0
  4. ewoksfluo-0.1.0/pyproject.toml +6 -0
  5. ewoksfluo-0.1.0/setup.cfg +95 -0
  6. ewoksfluo-0.1.0/setup.py +4 -0
  7. ewoksfluo-0.1.0/src/ewoksfluo/__init__.py +1 -0
  8. ewoksfluo-0.1.0/src/ewoksfluo/gui/__init__.py +0 -0
  9. ewoksfluo-0.1.0/src/ewoksfluo/gui/data_viewer.py +516 -0
  10. ewoksfluo-0.1.0/src/ewoksfluo/io/__init__.py +0 -0
  11. ewoksfluo-0.1.0/src/ewoksfluo/io/convert.py +52 -0
  12. ewoksfluo-0.1.0/src/ewoksfluo/io/nexus.py +80 -0
  13. ewoksfluo-0.1.0/src/ewoksfluo/io/spec.py +263 -0
  14. ewoksfluo-0.1.0/src/ewoksfluo/io/types.py +20 -0
  15. ewoksfluo-0.1.0/src/ewoksfluo/io/zap_utils.py +272 -0
  16. ewoksfluo-0.1.0/src/ewoksfluo/tasks/__init__.py +0 -0
  17. ewoksfluo-0.1.0/src/ewoksfluo/tasks/example_data/__init__.py +1 -0
  18. ewoksfluo-0.1.0/src/ewoksfluo/tasks/example_data/example_xrf_scan.py +252 -0
  19. ewoksfluo-0.1.0/src/ewoksfluo/tasks/multi_detector_fit/__init__.py +120 -0
  20. ewoksfluo-0.1.0/src/ewoksfluo/tasks/multi_scan_fit/__init__.py +132 -0
  21. ewoksfluo-0.1.0/src/ewoksfluo/tasks/nexus.py +51 -0
  22. ewoksfluo-0.1.0/src/ewoksfluo/tasks/normalization/__init__.py +79 -0
  23. ewoksfluo-0.1.0/src/ewoksfluo/tasks/pick_scans/__init__.py +30 -0
  24. ewoksfluo-0.1.0/src/ewoksfluo/tasks/regrid_data/__init__.py +2 -0
  25. ewoksfluo-0.1.0/src/ewoksfluo/tasks/regrid_data/regrid_map.py +89 -0
  26. ewoksfluo-0.1.0/src/ewoksfluo/tasks/regrid_data/regrid_stack.py +104 -0
  27. ewoksfluo-0.1.0/src/ewoksfluo/tasks/regrid_data/utils.py +80 -0
  28. ewoksfluo-0.1.0/src/ewoksfluo/tasks/single_detector_fit/__init__.py +61 -0
  29. ewoksfluo-0.1.0/src/ewoksfluo/tasks/spec_to_bliss/__init__.py +19 -0
  30. ewoksfluo-0.1.0/src/ewoksfluo/tasks/sum_detectors/__init__.py +77 -0
  31. ewoksfluo-0.1.0/src/ewoksfluo/tasks/sum_fit_results/__init__.py +78 -0
  32. ewoksfluo-0.1.0/src/ewoksfluo/tasks/sum_fit_results/utils.py +129 -0
  33. ewoksfluo-0.1.0/src/ewoksfluo/tasks/utils.py +149 -0
  34. ewoksfluo-0.1.0/src/ewoksfluo/tests/__init__.py +0 -0
  35. ewoksfluo-0.1.0/src/ewoksfluo/tests/conftest.py +9 -0
  36. ewoksfluo-0.1.0/src/ewoksfluo/tests/data/__init__.py +0 -0
  37. ewoksfluo-0.1.0/src/ewoksfluo/tests/data/deadtime.py +69 -0
  38. ewoksfluo-0.1.0/src/ewoksfluo/tests/data/xrf_spectra.py +408 -0
  39. ewoksfluo-0.1.0/src/ewoksfluo/tests/test_convert.py +330 -0
  40. ewoksfluo-0.1.0/src/ewoksfluo/tests/test_deadtime.py +28 -0
  41. ewoksfluo-0.1.0/src/ewoksfluo/tests/test_examples.py +39 -0
  42. ewoksfluo-0.1.0/src/ewoksfluo/tests/test_fit.py +251 -0
  43. ewoksfluo-0.1.0/src/ewoksfluo/tests/test_pymca_config.py +120 -0
  44. ewoksfluo-0.1.0/src/ewoksfluo/tests/tutorials/__init__.py +0 -0
  45. ewoksfluo-0.1.0/src/ewoksfluo/tests/tutorials/test_multi_scan_multi_detectors.py +126 -0
  46. ewoksfluo-0.1.0/src/ewoksfluo/tests/tutorials/test_single_scan_multi_detectors_fit_then_sum.py +132 -0
  47. ewoksfluo-0.1.0/src/ewoksfluo/tests/tutorials/test_single_scan_single_detector.py +103 -0
  48. ewoksfluo-0.1.0/src/ewoksfluo/tests/tutorials/test_single_scan_sum_multi_detectors.py +122 -0
  49. ewoksfluo-0.1.0/src/ewoksfluo/xrffit/__init__.py +3 -0
  50. ewoksfluo-0.1.0/src/ewoksfluo/xrffit/buffers/__init__.py +3 -0
  51. ewoksfluo-0.1.0/src/ewoksfluo/xrffit/buffers/external.py +512 -0
  52. ewoksfluo-0.1.0/src/ewoksfluo/xrffit/buffers/pymca.py +66 -0
  53. ewoksfluo-0.1.0/src/ewoksfluo/xrffit/config.py +260 -0
  54. ewoksfluo-0.1.0/src/ewoksfluo/xrffit/fit.py +105 -0
  55. ewoksfluo-0.1.0/src/ewoksfluo/xrffit/handlers/__init__.py +6 -0
  56. ewoksfluo-0.1.0/src/ewoksfluo/xrffit/handlers/abstract.py +27 -0
  57. ewoksfluo-0.1.0/src/ewoksfluo/xrffit/handlers/nexus.py +140 -0
  58. ewoksfluo-0.1.0/src/ewoksfluo/xrffit/handlers/queue.py +150 -0
  59. ewoksfluo-0.1.0/src/ewoksfluo/xrffit/queue_messages.py +56 -0
  60. ewoksfluo-0.1.0/src/ewoksfluo/xrffit/utils.py +54 -0
  61. ewoksfluo-0.1.0/src/ewoksfluo.egg-info/PKG-INFO +29 -0
  62. ewoksfluo-0.1.0/src/ewoksfluo.egg-info/SOURCES.txt +88 -0
  63. ewoksfluo-0.1.0/src/ewoksfluo.egg-info/dependency_links.txt +1 -0
  64. ewoksfluo-0.1.0/src/ewoksfluo.egg-info/entry_points.txt +17 -0
  65. ewoksfluo-0.1.0/src/ewoksfluo.egg-info/namespace_packages.txt +1 -0
  66. ewoksfluo-0.1.0/src/ewoksfluo.egg-info/requires.txt +19 -0
  67. ewoksfluo-0.1.0/src/ewoksfluo.egg-info/top_level.txt +2 -0
  68. ewoksfluo-0.1.0/src/orangecontrib/__init__.py +3 -0
  69. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/__init__.py +20 -0
  70. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/example_xrf_scan.py +136 -0
  71. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/icons/__init__.py +0 -0
  72. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/icons/category.png +0 -0
  73. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/icons/widget.png +0 -0
  74. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/multi_detector_fit.py +136 -0
  75. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/multi_scan_fit.py +142 -0
  76. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/normalization.py +107 -0
  77. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/pick_scans_in_files.py +175 -0
  78. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/pick_scans_widget.py +55 -0
  79. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/regrid.py +106 -0
  80. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/regrid_stack.py +113 -0
  81. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/single_detector_fit.py +121 -0
  82. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/spec_to_bliss.py +109 -0
  83. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/sum_detectors.py +112 -0
  84. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/sum_fit_results.py +113 -0
  85. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/tutorials/__init__.py +0 -0
  86. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/tutorials/multi_scan_multi_detectors.ows +23 -0
  87. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/tutorials/single_scan_multi_detectors_fit_then_sum.ows +35 -0
  88. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/tutorials/single_scan_single_detector.ows +28 -0
  89. ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/tutorials/single_scan_sum_multi_detectors.ows +35 -0
@@ -0,0 +1,20 @@
1
+ # MIT License
2
+
3
+ **Copyright (c) 2021 European Synchrotron Radiation Facility**
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ Metadata-Version: 2.1
2
+ Name: ewoksfluo
3
+ Version: 0.1.0
4
+ Summary: Data processing workflows for X-ray Fluorescence
5
+ Home-page: https://gitlab.esrf.fr/workflow/ewoksfluo/
6
+ Author: ESRF
7
+ Author-email: wout.de_nolf@esrf.fr
8
+ License: MIT
9
+ Project-URL: Source, https://gitlab.esrf.fr/workflow/ewoksfluo/
10
+ Project-URL: Documentation, https://ewoksfluo.readthedocs.io/
11
+ Project-URL: Tracker, https://gitlab.esrf.fr/workflow/ewoksfluo/issues/
12
+ Keywords: orange3 add-on,ewoks
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Requires-Python: >=3.7
17
+ Description-Content-Type: text/markdown
18
+ Provides-Extra: test
19
+ Provides-Extra: dev
20
+ Provides-Extra: doc
21
+ License-File: LICENSE.md
22
+
23
+ # ewoksfluo
24
+
25
+ Data processing workflows for X-ray Fluorescence
26
+
27
+ ## Documentation
28
+
29
+ https://ewoksfluo.readthedocs.io/
@@ -0,0 +1,7 @@
1
+ # ewoksfluo
2
+
3
+ Data processing workflows for X-ray Fluorescence
4
+
5
+ ## Documentation
6
+
7
+ https://ewoksfluo.readthedocs.io/
@@ -0,0 +1,6 @@
1
+ [build-system]
2
+ requires = [
3
+ "setuptools>=46.4<63",
4
+ "wheel",
5
+ ]
6
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,95 @@
1
+ [metadata]
2
+ name = ewoksfluo
3
+ version = attr: ewoksfluo.__version__
4
+ author = ESRF
5
+ author_email = wout.de_nolf@esrf.fr
6
+ description = Data processing workflows for X-ray Fluorescence
7
+ long_description = file: README.md
8
+ long_description_content_type = text/markdown
9
+ license = MIT
10
+ url = https://gitlab.esrf.fr/workflow/ewoksfluo/
11
+ project_urls =
12
+ Source = https://gitlab.esrf.fr/workflow/ewoksfluo/
13
+ Documentation = https://ewoksfluo.readthedocs.io/
14
+ Tracker = https://gitlab.esrf.fr/workflow/ewoksfluo/issues/
15
+ classifiers =
16
+ Intended Audience :: Science/Research
17
+ License :: OSI Approved :: MIT License
18
+ Programming Language :: Python :: 3
19
+ keywords =
20
+ orange3 add-on,ewoks
21
+
22
+ [options]
23
+ package_dir =
24
+ =src
25
+ packages = find:
26
+ python_requires = >=3.7
27
+ install_requires =
28
+ ewoks[orange]
29
+ h5py
30
+ numpy
31
+ pymca >=5.8.1
32
+ fabio
33
+ ewoksdata >=0.2.7
34
+ namespace_packages =
35
+ orangecontrib
36
+
37
+ [options.packages.find]
38
+ where = src
39
+
40
+ [options.extras_require]
41
+ test =
42
+ pytest >=7
43
+ dev =
44
+ %(test)s
45
+ black >=22
46
+ flake8 >=4
47
+ doc =
48
+ %(test)s
49
+ sphinx >=4.5
50
+ sphinx-autodoc-typehints >=1.16
51
+
52
+ [options.entry_points]
53
+ orange3.addon =
54
+ ewoksfluo=orangecontrib.ewoksfluo
55
+ orange.widgets =
56
+ FLUO=orangecontrib.ewoksfluo
57
+ orangecanvas.examples =
58
+ FLUO=orangecontrib.ewoksfluo.tutorials
59
+ orange.widgets.tutorials =
60
+ FLUO=orangecontrib.ewoksfluo.tutorials
61
+ orange.canvas.help =
62
+ html-index=orangecontrib.ewoksfluo:WIDGET_HELP_PATH
63
+ ewoks.tasks.class =
64
+ ewoksfluo.tasks.*=ewoksfluo
65
+
66
+ [options.package_data]
67
+ * = *.ows, *.png, *.svg
68
+
69
+ [flake8]
70
+ ignore = E501, E203, W503
71
+ max-line-length = 88
72
+ exclude =
73
+ .eggs
74
+
75
+ [coverage:run]
76
+ omit =
77
+ setup.py
78
+ */tests/*
79
+
80
+ [mypy]
81
+ python_version = 3.7
82
+
83
+ [mypy-h5py.*]
84
+ ignore_missing_imports = True
85
+
86
+ [mypy-PyMca5.*]
87
+ ignore_missing_imports = True
88
+
89
+ [mypy-setuptools.*]
90
+ ignore_missing_imports = True
91
+
92
+ [egg_info]
93
+ tag_build =
94
+ tag_date = 0
95
+
@@ -0,0 +1,4 @@
1
+ import setuptools
2
+
3
+ if __name__ == "__main__":
4
+ setuptools.setup()
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
File without changes
@@ -0,0 +1,516 @@
1
+ import os
2
+ import logging
3
+ from typing import Optional, Iterator, Tuple, Sequence
4
+ from contextlib import contextmanager
5
+
6
+ import h5py
7
+ import silx.io
8
+ from silx.gui import qt
9
+ from silx.gui import icons
10
+ from silx.gui.hdf5 import Hdf5TreeView
11
+ from silx.gui.hdf5 import Hdf5TreeModel
12
+ from silx.gui.hdf5 import NexusSortFilterProxyModel
13
+ from silx.gui.hdf5 import Hdf5ContextMenuEvent
14
+ from silx.gui.data.DataViewerFrame import DataViewerFrame
15
+
16
+
17
+ _logger = logging.getLogger(__name__)
18
+
19
+
20
+ class DataViewer(qt.QWidget):
21
+ """Browse data from files supported by silx.
22
+
23
+ To create the widget
24
+
25
+ .. code: python
26
+
27
+ viewer = DataViewer(parent)
28
+ viewer.setVisible(True)
29
+ parent.layout().addWidget(viewer)
30
+
31
+ To close and refresh files
32
+
33
+ .. code: python
34
+
35
+ viewer.updateFile("/path/to/file1.h5")
36
+ viewer.updateFile("/path/to/file2.h5")
37
+ viewer.closeFile("/path/to/file1.h5")
38
+
39
+ To close all files
40
+
41
+ .. code: python
42
+
43
+ viewer.closeAll()
44
+ """
45
+
46
+ def __init__(self, parent):
47
+ super().__init__(parent)
48
+
49
+ self._h5files = list()
50
+
51
+ # Do we need buttons for these?
52
+ # silx.config.DEFAULT_PLOT_IMAGE_Y_AXIS_ORIENTATION = "downward"
53
+ # silx.config.DEFAULT_PLOT_IMAGE_Y_AXIS_ORIENTATION = "upward"
54
+ # silx.config.DEFAULT_PLOT_BACKEND = "matplotlib"
55
+ # silx.config.DEFAULT_PLOT_BACKEND = "opengl"
56
+
57
+ self.__treePanel = qt.QSplitter(self)
58
+ self.__treePanel.setOrientation(qt.Qt.Vertical)
59
+
60
+ self.__dataPanel = DataViewerFrame(self)
61
+
62
+ self.__treeview = Hdf5TreeView(self)
63
+ self.__treeview.setExpandsOnDoubleClick(False)
64
+
65
+ self.__treeWindow = self.__createTreeWindow(self.__treeview)
66
+ self.__treeModelSorted = self.__createTreeModel(self.__treeview)
67
+
68
+ self.__treePanel.addWidget(self.__treeWindow)
69
+ self.__treePanel.setStretchFactor(1, 1)
70
+ self.__treePanel.setCollapsible(0, False)
71
+
72
+ self.__mainWidget = self.__createMainWidget(self.__treePanel, self.__dataPanel)
73
+
74
+ self.__setLayout(self.__mainWidget)
75
+
76
+ self.__treeview.activated.connect(self.displaySelectedData)
77
+ self.__treeview.addContextMenuCallback(self.treeContextMenu)
78
+ self.__customizeTreeModelColumns()
79
+
80
+ def __setLayout(self, mainWidget):
81
+ layout = qt.QVBoxLayout()
82
+ layout.addWidget(mainWidget)
83
+ layout.setStretchFactor(mainWidget, 1)
84
+ self.setLayout(layout)
85
+
86
+ def __createMainWidget(self, *widgets) -> qt.QWidget:
87
+ mainWidget = qt.QSplitter(self)
88
+ for widget in widgets:
89
+ mainWidget.addWidget(widget)
90
+ mainWidget.setStretchFactor(1, 1)
91
+ for i in range(len(widgets)):
92
+ mainWidget.setCollapsible(i, False)
93
+ return mainWidget
94
+
95
+ def __createTreeModel(self, treeview: Hdf5TreeView) -> NexusSortFilterProxyModel:
96
+ treeModel = Hdf5TreeModel(treeview, ownFiles=False)
97
+ treeModel.sigH5pyObjectLoaded.connect(self.__h5FileLoaded)
98
+ treeModel.sigH5pyObjectRemoved.connect(self.__h5FileRemoved)
99
+ treeModel.sigH5pyObjectSynchronized.connect(self.__h5FileSynchronized)
100
+ treeModel.setDatasetDragEnabled(True)
101
+
102
+ treeModelSorted = NexusSortFilterProxyModel(treeview)
103
+ treeModelSorted.setSourceModel(treeModel)
104
+ treeModelSorted.sort(0, qt.Qt.AscendingOrder)
105
+ treeModelSorted.setSortCaseSensitivity(qt.Qt.CaseInsensitive)
106
+ treeview.setModel(treeModelSorted)
107
+ return treeModelSorted
108
+
109
+ def __customizeTreeModelColumns(self):
110
+ treeModel = self.__treeview.findHdf5TreeModel()
111
+ columns = list(treeModel.COLUMN_IDS)
112
+ columns.remove(treeModel.VALUE_COLUMN)
113
+ columns.remove(treeModel.NODE_COLUMN)
114
+ columns.remove(treeModel.DESCRIPTION_COLUMN)
115
+ columns.insert(1, treeModel.DESCRIPTION_COLUMN)
116
+ self.__treeview.header().setSections(columns)
117
+
118
+ def __createTreeWindow(self, treeView: Hdf5TreeView) -> qt.QWidget:
119
+ toolbar = qt.QToolBar(self)
120
+ toolbar.setIconSize(qt.QSize(16, 16))
121
+ toolbar.setStyleSheet("QToolBar { border: 0px }")
122
+
123
+ action = qt.QAction(toolbar)
124
+ action.setIcon(icons.getQIcon("view-refresh"))
125
+ action.setText("Refresh")
126
+ action.setToolTip("Refresh all selected items")
127
+ action.triggered.connect(self.__refreshSelected)
128
+ action.setShortcut(qt.QKeySequence(qt.Qt.Key_F5))
129
+ toolbar.addAction(action)
130
+ treeView.addAction(action)
131
+ self.__refreshAction = action
132
+
133
+ # Another shortcut for refresh
134
+ action = qt.QAction(toolbar)
135
+ action.setShortcut(qt.QKeySequence(qt.Qt.CTRL | qt.Qt.Key_R))
136
+ treeView.addAction(action)
137
+ action.triggered.connect(self.__refreshSelected)
138
+
139
+ action = qt.QAction(toolbar)
140
+ # action.setIcon(icons.getQIcon("view-refresh"))
141
+ action.setText("Close")
142
+ action.setToolTip("Close selected item")
143
+ action.triggered.connect(self.__removeSelected)
144
+ action.setShortcut(qt.QKeySequence(qt.Qt.Key_Delete))
145
+ treeView.addAction(action)
146
+ self.__closeAction = action
147
+
148
+ toolbar.addSeparator()
149
+
150
+ action = qt.QAction(toolbar)
151
+ action.setIcon(icons.getQIcon("tree-expand-all"))
152
+ action.setText("Expand all")
153
+ action.setToolTip("Expand all selected items")
154
+ action.triggered.connect(self.__expandAllSelected)
155
+ action.setShortcut(qt.QKeySequence(qt.Qt.CTRL | qt.Qt.Key_Plus))
156
+ toolbar.addAction(action)
157
+ treeView.addAction(action)
158
+ self.__expandAllAction = action
159
+
160
+ action = qt.QAction(toolbar)
161
+ action.setIcon(icons.getQIcon("tree-collapse-all"))
162
+ action.setText("Collapse all")
163
+ action.setToolTip("Collapse all selected items")
164
+ action.triggered.connect(self.__collapseAllSelected)
165
+ action.setShortcut(qt.QKeySequence(qt.Qt.CTRL | qt.Qt.Key_Minus))
166
+ toolbar.addAction(action)
167
+ treeView.addAction(action)
168
+ self.__collapseAllAction = action
169
+
170
+ action = qt.QAction("&Sort file content", toolbar)
171
+ action.setIcon(icons.getQIcon("tree-sort"))
172
+ action.setToolTip("Toggle sorting of file content")
173
+ action.setCheckable(True)
174
+ action.setChecked(True)
175
+ action.triggered.connect(self.setContentSorted)
176
+ toolbar.addAction(action)
177
+ treeView.addAction(action)
178
+ self._sortContentAction = action
179
+
180
+ widget = qt.QWidget(self)
181
+ layout = qt.QVBoxLayout(widget)
182
+ layout.setContentsMargins(0, 0, 0, 0)
183
+ layout.setSpacing(0)
184
+ layout.addWidget(toolbar)
185
+ layout.addWidget(treeView)
186
+ return widget
187
+
188
+ def __iterModelIndices(
189
+ self, max_depth: Optional[int] = None, indexes: Optional[Sequence] = None
190
+ ) -> Iterator[Tuple[Tuple[qt.QModelIndex, int], qt.QModelIndex, int]]:
191
+ selection = self.__treeview.selectionModel()
192
+ if indexes is None:
193
+ indexes = selection.selectedIndexes()
194
+ while len(indexes) > 0:
195
+ index = indexes.pop(0)
196
+ if isinstance(index, tuple):
197
+ index, depth = index
198
+ else:
199
+ depth = 0
200
+ if index.column() != 0:
201
+ continue
202
+ if max_depth is not None and depth > max_depth:
203
+ break
204
+ yield indexes, index, depth
205
+
206
+ @staticmethod
207
+ def __getRootIndex(index: qt.QModelIndex):
208
+ rootIndex = index
209
+ while rootIndex.parent().isValid():
210
+ rootIndex = rootIndex.parent()
211
+ return rootIndex
212
+
213
+ def __removeSelected(self):
214
+ """Close selected items"""
215
+ model = self.__treeview.model()
216
+ h5files = set()
217
+ selectedItems = []
218
+ with self.__waitCursor():
219
+ for _, index, _ in self.__iterModelIndices():
220
+ rootIndex = self.__getRootIndex(index)
221
+ relativePath = self.__getRelativePath(model, rootIndex, index)
222
+ selectedItems.append((rootIndex.row(), relativePath))
223
+
224
+ h5 = model.data(index, role=Hdf5TreeModel.H5PY_OBJECT_ROLE)
225
+ h5files.add(h5.file)
226
+
227
+ if not h5files:
228
+ return
229
+
230
+ model = self.__treeview.findHdf5TreeModel()
231
+ for h5 in h5files:
232
+ model.removeH5pyObject(h5)
233
+
234
+ def __refreshSelected(self):
235
+ """Refresh all selected items"""
236
+ model = self.__treeview.model()
237
+ selection = self.__treeview.selectionModel()
238
+ selectedItems = []
239
+ h5files = []
240
+ with self.__waitCursor():
241
+ for _, index, _ in self.__iterModelIndices():
242
+ rootIndex = self.__getRootIndex(index)
243
+ relativePath = self.__getRelativePath(model, rootIndex, index)
244
+ selectedItems.append((rootIndex.row(), relativePath))
245
+
246
+ h5 = model.data(rootIndex, role=Hdf5TreeModel.H5PY_OBJECT_ROLE)
247
+ item = model.data(rootIndex, role=Hdf5TreeModel.H5PY_ITEM_ROLE)
248
+ h5files.append((h5, item._openedPath))
249
+
250
+ if not h5files:
251
+ return
252
+
253
+ for h5, filename in h5files:
254
+ self.__synchronizeH5pyObject(h5, filename)
255
+
256
+ itemSelection = qt.QItemSelection()
257
+ for rootRow, relativePath in selectedItems:
258
+ rootIndex = model.index(rootRow, 0, qt.QModelIndex())
259
+ index = self.__indexFromPath(model, rootIndex, relativePath)
260
+ if index is None:
261
+ continue
262
+ indexEnd = model.index(
263
+ index.row(), model.columnCount() - 1, index.parent()
264
+ )
265
+ itemSelection.select(index, indexEnd)
266
+ selection.select(itemSelection, qt.QItemSelectionModel.ClearAndSelect)
267
+
268
+ def __synchronizeH5pyObject(self, h5, filename: Optional[str] = None):
269
+ model = self.__treeview.findHdf5TreeModel()
270
+ # This is buggy right now while h5py do not allow to close a file
271
+ # while references are still used.
272
+ # FIXME: The architecture have to be reworked to support this feature.
273
+ # model.synchronizeH5pyObject(h5)
274
+
275
+ if filename is None:
276
+ filename = f"{h5.file.filename}::{h5.name}"
277
+ row = model.h5pyObjectRow(h5)
278
+ index = self.__treeview.model().index(row, 0, qt.QModelIndex())
279
+ paths = self.__getPathFromExpandedNodes(self.__treeview, index)
280
+ model.removeH5pyObject(h5)
281
+ model.insertFile(filename, row)
282
+ index = self.__treeview.model().index(row, 0, qt.QModelIndex())
283
+ self.__expandNodesFromPaths(self.__treeview, index, paths)
284
+
285
+ def __getRelativePath(self, model, rootIndex, index):
286
+ """Returns a relative path from an index to his rootIndex.
287
+
288
+ If the path is empty the index is also the rootIndex.
289
+ """
290
+ path = ""
291
+ while index.isValid():
292
+ if index == rootIndex:
293
+ return path
294
+ name = model.data(index)
295
+ if path == "":
296
+ path = name
297
+ else:
298
+ path = name + "/" + path
299
+ index = index.parent()
300
+
301
+ # index is not a children of rootIndex
302
+ raise ValueError("index is not a children of the rootIndex")
303
+
304
+ def __getPathFromExpandedNodes(self, view, rootIndex):
305
+ """Return relative path from the root index of the extended nodes"""
306
+ model = view.model()
307
+ rootPath = None
308
+ paths = []
309
+
310
+ for indexes, index, depth in self.__iterModelIndices(indexes=[rootIndex]):
311
+ if not view.isExpanded(index):
312
+ continue
313
+
314
+ node = model.data(index, role=Hdf5TreeModel.H5PY_ITEM_ROLE)
315
+ path = node._getCanonicalName()
316
+ if rootPath is None:
317
+ rootPath = path
318
+ path = path[len(rootPath) :]
319
+ paths.append(path)
320
+
321
+ for child in range(model.rowCount(index)):
322
+ childIndex = model.index(child, 0, index)
323
+ indexes.append((childIndex, depth + 1))
324
+ return paths
325
+
326
+ def __indexFromPath(self, model, rootIndex, path):
327
+ elements = path.split("/")
328
+ if elements[0] == "":
329
+ elements.pop(0)
330
+ index = rootIndex
331
+ while len(elements) != 0:
332
+ element = elements.pop(0)
333
+ found = False
334
+ for child in range(model.rowCount(index)):
335
+ childIndex = model.index(child, 0, index)
336
+ name = model.data(childIndex)
337
+ if element == name:
338
+ index = childIndex
339
+ found = True
340
+ break
341
+ if not found:
342
+ return None
343
+ return index
344
+
345
+ def __expandNodesFromPaths(self, view, rootIndex, paths):
346
+ model = view.model()
347
+ for path in paths:
348
+ index = self.__indexFromPath(model, rootIndex, path)
349
+ if index is not None:
350
+ view.setExpanded(index, True)
351
+
352
+ @contextmanager
353
+ def __waitCursor(self):
354
+ qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor)
355
+ try:
356
+ yield
357
+ finally:
358
+ qt.QApplication.restoreOverrideCursor()
359
+
360
+ def __expandAllSelected(self):
361
+ """Expand all selected items of the tree."""
362
+ with self.__waitCursor():
363
+ self.__setExpanded(True)
364
+
365
+ def __collapseAllSelected(self):
366
+ """Collapse all selected items of the tree."""
367
+ self.__setExpanded(False)
368
+
369
+ def __setExpanded(self, expanded: bool):
370
+ model = self.__treeview.model()
371
+ for indexes, index, depth in self.__iterModelIndices(max_depth=2):
372
+ if not model.hasChildren(index):
373
+ continue
374
+ self.__treeview.setExpanded(index, expanded)
375
+ for row in range(model.rowCount(index)):
376
+ childIndex = model.index(row, 0, index)
377
+ indexes.append((childIndex, depth + 1))
378
+
379
+ def __h5FileLoaded(self, loadedH5):
380
+ self._h5files.append(loadedH5)
381
+ self.displayData(loadedH5)
382
+
383
+ def __h5FileRemoved(self, removedH5):
384
+ data = self.__dataPanel.data()
385
+ if data is not None:
386
+ if data.file is not None:
387
+ if data.file.filename == removedH5.file.filename:
388
+ self.__dataPanel.setData(None)
389
+ removedH5.close()
390
+ self._h5files.remove(removedH5)
391
+
392
+ def __h5FileSynchronized(self, removedH5, loadedH5):
393
+ data = self.__dataPanel.data()
394
+ if data is not None:
395
+ if data.file is not None:
396
+ if data.file.filename == removedH5.file.filename:
397
+ try:
398
+ newData = loadedH5[data.name]
399
+ self.__dataPanel.setData(newData)
400
+ except Exception:
401
+ _logger.debug("Cannot synchronize", exc_info=True)
402
+ removedH5.close()
403
+ self._h5files.remove(removedH5)
404
+
405
+ def closeEvent(self, event):
406
+ self.displayData(None)
407
+ self.closeAll()
408
+
409
+ def closeAll(self):
410
+ """Close all currently opened files"""
411
+ self.__treeview.findHdf5TreeModel().clear()
412
+
413
+ def __getFileObject(self, filename):
414
+ for h5file in self._h5files:
415
+ if h5file.filename == filename:
416
+ return h5file
417
+
418
+ def closeFile(self, filename):
419
+ h5file = self.__getFileObject(filename)
420
+ model = self.__treeview.findHdf5TreeModel()
421
+ model.removeH5pyObject(h5file)
422
+
423
+ def updateFile(self, filename):
424
+ if not os.path.exists(filename):
425
+ return
426
+ h5file = self.__getFileObject(filename)
427
+ if h5file is not None:
428
+ self.__refreshAction.trigger()
429
+ return
430
+ self.closeFile(filename)
431
+ model = self.__treeview.findHdf5TreeModel()
432
+ h5file = h5py.File(filename, mode="a")
433
+ model.sigH5pyObjectLoaded.emit(h5file)
434
+ model.insertH5pyObject(h5file, filename=filename)
435
+
436
+ def setContentSorted(self, sort):
437
+ """Set whether file content should be sorted or not.
438
+
439
+ :param bool sort:
440
+ """
441
+ sort = bool(sort)
442
+ if sort != self.isContentSorted():
443
+ # save expanded nodes
444
+ pathss = []
445
+ root = qt.QModelIndex()
446
+ model = self.__treeview.model()
447
+ for i in range(model.rowCount(root)):
448
+ index = model.index(i, 0, root)
449
+ paths = self.__getPathFromExpandedNodes(self.__treeview, index)
450
+ pathss.append(paths)
451
+
452
+ self.__treeview.setModel(
453
+ self.__treeModelSorted if sort else self.__treeModelSorted.sourceModel()
454
+ )
455
+ self._sortContentAction.setChecked(self.isContentSorted())
456
+
457
+ # restore expanded nodes
458
+ model = self.__treeview.model()
459
+ for i in range(model.rowCount(root)):
460
+ index = model.index(i, 0, root)
461
+ paths = pathss.pop(0)
462
+ self.__expandNodesFromPaths(self.__treeview, index, paths)
463
+
464
+ def isContentSorted(self):
465
+ """Returns whether the file content is sorted or not.
466
+
467
+ :rtype: bool
468
+ """
469
+ return self.__treeview.model() is self.__treeModelSorted
470
+
471
+ def displaySelectedData(self):
472
+ """Called to update the dataviewer with the selected data."""
473
+ selected = list(self.__treeview.selectedH5Nodes(ignoreBrokenLinks=False))
474
+ if len(selected) == 1:
475
+ # Update the viewer for a single selection
476
+ data = selected[0]
477
+ self.__dataPanel.setData(data)
478
+ else:
479
+ _logger.debug("Too many data selected")
480
+
481
+ def displayData(self, data):
482
+ """Called to update the dataviewer with specific data."""
483
+ self.__dataPanel.setData(data)
484
+
485
+ def treeContextMenu(self, event: Hdf5ContextMenuEvent):
486
+ """Called to populate the context menu"""
487
+ selectedObjects = event.source().selectedH5Nodes(ignoreBrokenLinks=False)
488
+ menu = event.menu()
489
+
490
+ if not menu.isEmpty():
491
+ menu.addSeparator()
492
+
493
+ for obj in selectedObjects:
494
+ h5 = obj.h5py_object
495
+
496
+ name = obj.name
497
+ if name.startswith("/"):
498
+ name = name[1:]
499
+ if name == "":
500
+ name = "the root"
501
+
502
+ action = qt.QAction("Show %s" % name, event.source())
503
+ action.triggered.connect(lambda: self.displayData(h5))
504
+ menu.addAction(action)
505
+
506
+ if silx.io.is_file(h5):
507
+ action = qt.QAction("Close %s" % obj.local_filename, event.source())
508
+ action.triggered.connect(
509
+ lambda: self.__treeview.findHdf5TreeModel().removeH5pyObject(h5)
510
+ )
511
+ menu.addAction(action)
512
+ action = qt.QAction(
513
+ "Synchronize %s" % obj.local_filename, event.source()
514
+ )
515
+ action.triggered.connect(lambda: self.__synchronizeH5pyObject(h5))
516
+ menu.addAction(action)
File without changes