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.
- ewoksfluo-0.1.0/LICENSE.md +20 -0
- ewoksfluo-0.1.0/PKG-INFO +29 -0
- ewoksfluo-0.1.0/README.md +7 -0
- ewoksfluo-0.1.0/pyproject.toml +6 -0
- ewoksfluo-0.1.0/setup.cfg +95 -0
- ewoksfluo-0.1.0/setup.py +4 -0
- ewoksfluo-0.1.0/src/ewoksfluo/__init__.py +1 -0
- ewoksfluo-0.1.0/src/ewoksfluo/gui/__init__.py +0 -0
- ewoksfluo-0.1.0/src/ewoksfluo/gui/data_viewer.py +516 -0
- ewoksfluo-0.1.0/src/ewoksfluo/io/__init__.py +0 -0
- ewoksfluo-0.1.0/src/ewoksfluo/io/convert.py +52 -0
- ewoksfluo-0.1.0/src/ewoksfluo/io/nexus.py +80 -0
- ewoksfluo-0.1.0/src/ewoksfluo/io/spec.py +263 -0
- ewoksfluo-0.1.0/src/ewoksfluo/io/types.py +20 -0
- ewoksfluo-0.1.0/src/ewoksfluo/io/zap_utils.py +272 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tasks/__init__.py +0 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tasks/example_data/__init__.py +1 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tasks/example_data/example_xrf_scan.py +252 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tasks/multi_detector_fit/__init__.py +120 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tasks/multi_scan_fit/__init__.py +132 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tasks/nexus.py +51 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tasks/normalization/__init__.py +79 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tasks/pick_scans/__init__.py +30 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tasks/regrid_data/__init__.py +2 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tasks/regrid_data/regrid_map.py +89 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tasks/regrid_data/regrid_stack.py +104 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tasks/regrid_data/utils.py +80 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tasks/single_detector_fit/__init__.py +61 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tasks/spec_to_bliss/__init__.py +19 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tasks/sum_detectors/__init__.py +77 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tasks/sum_fit_results/__init__.py +78 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tasks/sum_fit_results/utils.py +129 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tasks/utils.py +149 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tests/__init__.py +0 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tests/conftest.py +9 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tests/data/__init__.py +0 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tests/data/deadtime.py +69 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tests/data/xrf_spectra.py +408 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tests/test_convert.py +330 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tests/test_deadtime.py +28 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tests/test_examples.py +39 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tests/test_fit.py +251 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tests/test_pymca_config.py +120 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tests/tutorials/__init__.py +0 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tests/tutorials/test_multi_scan_multi_detectors.py +126 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tests/tutorials/test_single_scan_multi_detectors_fit_then_sum.py +132 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tests/tutorials/test_single_scan_single_detector.py +103 -0
- ewoksfluo-0.1.0/src/ewoksfluo/tests/tutorials/test_single_scan_sum_multi_detectors.py +122 -0
- ewoksfluo-0.1.0/src/ewoksfluo/xrffit/__init__.py +3 -0
- ewoksfluo-0.1.0/src/ewoksfluo/xrffit/buffers/__init__.py +3 -0
- ewoksfluo-0.1.0/src/ewoksfluo/xrffit/buffers/external.py +512 -0
- ewoksfluo-0.1.0/src/ewoksfluo/xrffit/buffers/pymca.py +66 -0
- ewoksfluo-0.1.0/src/ewoksfluo/xrffit/config.py +260 -0
- ewoksfluo-0.1.0/src/ewoksfluo/xrffit/fit.py +105 -0
- ewoksfluo-0.1.0/src/ewoksfluo/xrffit/handlers/__init__.py +6 -0
- ewoksfluo-0.1.0/src/ewoksfluo/xrffit/handlers/abstract.py +27 -0
- ewoksfluo-0.1.0/src/ewoksfluo/xrffit/handlers/nexus.py +140 -0
- ewoksfluo-0.1.0/src/ewoksfluo/xrffit/handlers/queue.py +150 -0
- ewoksfluo-0.1.0/src/ewoksfluo/xrffit/queue_messages.py +56 -0
- ewoksfluo-0.1.0/src/ewoksfluo/xrffit/utils.py +54 -0
- ewoksfluo-0.1.0/src/ewoksfluo.egg-info/PKG-INFO +29 -0
- ewoksfluo-0.1.0/src/ewoksfluo.egg-info/SOURCES.txt +88 -0
- ewoksfluo-0.1.0/src/ewoksfluo.egg-info/dependency_links.txt +1 -0
- ewoksfluo-0.1.0/src/ewoksfluo.egg-info/entry_points.txt +17 -0
- ewoksfluo-0.1.0/src/ewoksfluo.egg-info/namespace_packages.txt +1 -0
- ewoksfluo-0.1.0/src/ewoksfluo.egg-info/requires.txt +19 -0
- ewoksfluo-0.1.0/src/ewoksfluo.egg-info/top_level.txt +2 -0
- ewoksfluo-0.1.0/src/orangecontrib/__init__.py +3 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/__init__.py +20 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/example_xrf_scan.py +136 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/icons/__init__.py +0 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/icons/category.png +0 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/icons/widget.png +0 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/multi_detector_fit.py +136 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/multi_scan_fit.py +142 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/normalization.py +107 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/pick_scans_in_files.py +175 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/pick_scans_widget.py +55 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/regrid.py +106 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/regrid_stack.py +113 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/single_detector_fit.py +121 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/spec_to_bliss.py +109 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/sum_detectors.py +112 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/sum_fit_results.py +113 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/tutorials/__init__.py +0 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/tutorials/multi_scan_multi_detectors.ows +23 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/tutorials/single_scan_multi_detectors_fit_then_sum.ows +35 -0
- ewoksfluo-0.1.0/src/orangecontrib/ewoksfluo/tutorials/single_scan_single_detector.ows +28 -0
- 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.
|
ewoksfluo-0.1.0/PKG-INFO
ADDED
|
@@ -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,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
|
+
|
ewoksfluo-0.1.0/setup.py
ADDED
|
@@ -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
|