tomwer 1.2.0a1__py3-none-any.whl → 1.2.0a3__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.
- orangecontrib/tomwer/tutorials/append_raw_darks_and_flats_frames_to_NXtomos.ows +44 -0
- orangecontrib/tomwer/tutorials/copy_reduced_darks_and_flats_meth1.ows +55 -0
- orangecontrib/tomwer/tutorials/copy_reduced_darks_and_flats_meth2.ows +48 -0
- orangecontrib/tomwer/tutorials/default_cor_search.ows +40 -0
- orangecontrib/tomwer/tutorials/hello_world_python_script.ows +50 -0
- orangecontrib/tomwer/tutorials/simple_slice_reconstruction_on_slurm.ows +50 -0
- orangecontrib/tomwer/tutorials/simple_volume_to_slurm_reconstruction.ows +8 -8
- orangecontrib/tomwer/widgets/__init__.py +1 -1
- orangecontrib/tomwer/widgets/cluster/FutureSupervisorOW.py +0 -1
- orangecontrib/tomwer/widgets/cluster/SlurmClusterOW.py +8 -6
- orangecontrib/tomwer/widgets/control/AdvancementOW.py +0 -1
- orangecontrib/tomwer/widgets/control/DataDiscoveryOW.py +1 -6
- orangecontrib/tomwer/widgets/control/DataListOW.py +0 -1
- orangecontrib/tomwer/widgets/control/DataListenerOW.py +4 -4
- orangecontrib/tomwer/widgets/control/DataSelectorOW.py +0 -1
- orangecontrib/tomwer/widgets/control/DataTransfertOW.py +7 -7
- orangecontrib/tomwer/widgets/control/DataValidatorOW.py +0 -1
- orangecontrib/tomwer/widgets/control/DataWatcherOW.py +0 -3
- orangecontrib/tomwer/widgets/control/EDF2NXTomomillOW.py +3 -2
- orangecontrib/tomwer/widgets/control/EmailOW.py +82 -0
- orangecontrib/tomwer/widgets/control/FilterOW.py +3 -3
- orangecontrib/tomwer/widgets/control/NXTomomillOW.py +1 -1
- orangecontrib/tomwer/widgets/control/NotifierOW.py +0 -1
- orangecontrib/tomwer/widgets/control/ReduceDarkFlatSelectorOW.py +93 -0
- orangecontrib/tomwer/widgets/control/SingleTomoObjOW.py +29 -5
- orangecontrib/tomwer/widgets/control/TimerOW.py +1 -2
- orangecontrib/tomwer/widgets/control/TomoObjSerieOW.py +0 -1
- orangecontrib/tomwer/widgets/control/VolumeSelector.py +0 -1
- orangecontrib/tomwer/widgets/control/VolumeSymLinkOW.py +4 -10
- orangecontrib/tomwer/widgets/control/icons/email.png +0 -0
- orangecontrib/tomwer/widgets/control/icons/email.svg +58 -0
- orangecontrib/tomwer/widgets/control/icons/reduced_darkflat_selector.png +0 -0
- orangecontrib/tomwer/widgets/control/icons/reduced_darkflat_selector.svg +199 -0
- orangecontrib/tomwer/widgets/debugtools/DatasetGeneratorOW.py +0 -1
- orangecontrib/tomwer/widgets/debugtools/ObjectInspectorOW.py +0 -1
- orangecontrib/tomwer/widgets/edit/DarkFlatPatchOW.py +1 -2
- orangecontrib/tomwer/widgets/edit/ImageKeyEditorOW.py +1 -2
- orangecontrib/tomwer/widgets/edit/ImageKeyUpgraderOW.py +0 -1
- orangecontrib/tomwer/widgets/edit/NXtomoEditorOW.py +0 -1
- orangecontrib/tomwer/widgets/other/PythonScriptOW.py +29 -1
- orangecontrib/tomwer/widgets/other/TomoObjsHub.py +28 -0
- orangecontrib/tomwer/widgets/other/icons/hub.png +0 -0
- orangecontrib/tomwer/widgets/other/icons/hub.svg +113 -0
- orangecontrib/tomwer/widgets/reconstruction/AxisOW.py +18 -12
- orangecontrib/tomwer/widgets/reconstruction/CastNabuVolumeOW.py +0 -2
- orangecontrib/tomwer/widgets/reconstruction/DarkRefAndCopyOW.py +21 -6
- orangecontrib/tomwer/widgets/reconstruction/NabuOW.py +29 -7
- orangecontrib/tomwer/widgets/reconstruction/NabuVolumeOW.py +18 -5
- orangecontrib/tomwer/widgets/reconstruction/SAAxisOW.py +40 -13
- orangecontrib/tomwer/widgets/reconstruction/SADeltaBetaOW.py +37 -10
- orangecontrib/tomwer/widgets/reconstruction/SinoNormOW.py +2 -3
- orangecontrib/tomwer/widgets/reconstruction/TofuOW.py +5 -4
- orangecontrib/tomwer/widgets/stitching/StitcherOW.py +0 -1
- orangecontrib/tomwer/widgets/stitching/ZStitchingConfigOW.py +0 -1
- orangecontrib/tomwer/widgets/visualization/DataViewerOW.py +10 -4
- orangecontrib/tomwer/widgets/visualization/DiffViewerOW.py +1 -1
- orangecontrib/tomwer/widgets/visualization/LivesliceOW.py +0 -1
- orangecontrib/tomwer/widgets/visualization/NXtomoMetadataViewerOW.py +0 -1
- orangecontrib/tomwer/widgets/visualization/RadioStackOW.py +7 -5
- orangecontrib/tomwer/widgets/visualization/SampleMovedOW.py +1 -1
- orangecontrib/tomwer/widgets/visualization/SinogramViewerOW.py +0 -3
- orangecontrib/tomwer/widgets/visualization/SliceStackOW.py +7 -5
- orangecontrib/tomwer/widgets/visualization/VolumeViewerOW.py +4 -4
- tomwer/__main__.py +139 -5
- tomwer/app/axis.py +16 -5
- tomwer/app/canvas_launcher/config.py +1 -1
- tomwer/app/canvas_launcher/mainwindow.py +164 -6
- tomwer/app/darkref.py +10 -181
- tomwer/app/darkrefpatch.py +10 -131
- tomwer/app/diffframe.py +11 -0
- tomwer/app/imagekeyeditor.py +12 -19
- tomwer/app/intensitynormalization.py +1 -0
- tomwer/app/lamino.py +7 -2
- tomwer/app/patchrawdarkflat.py +131 -0
- tomwer/app/radiostack.py +10 -0
- tomwer/app/reducedarkflat.py +205 -0
- tomwer/app/saaxis.py +27 -8
- tomwer/app/sadeltabeta.py +29 -8
- tomwer/app/samplemoved.py +11 -0
- tomwer/app/scanviewer.py +12 -0
- tomwer/app/sinogramviewer.py +11 -0
- tomwer/app/slicestack.py +11 -0
- tomwer/app/zstitching.py +12 -0
- tomwer/core/futureobject.py +4 -2
- tomwer/core/process/conditions/filters.py +26 -4
- tomwer/core/process/control/datadiscovery.py +4 -0
- tomwer/core/process/control/datawatcher/datawatcher.py +5 -1
- tomwer/core/process/control/email.py +148 -0
- tomwer/core/process/control/nxtomoconcatenate.py +9 -2
- tomwer/core/process/control/nxtomomill.py +58 -16
- tomwer/core/process/control/scanselector.py +4 -0
- tomwer/core/process/control/scantransfer.py +52 -23
- tomwer/core/process/control/test/test_concatenate_nxtomos.py +1 -0
- tomwer/core/process/control/test/test_email.py +52 -0
- tomwer/core/process/control/test/test_h52nx_process.py +106 -0
- tomwer/core/process/control/test/test_volume_link.py +5 -4
- tomwer/core/process/control/timer.py +27 -6
- tomwer/core/process/control/tomoobjserie.py +4 -0
- tomwer/core/process/control/volumeselector.py +4 -0
- tomwer/core/process/control/volumesymlink.py +47 -8
- tomwer/core/process/edit/darkflatpatch.py +49 -8
- tomwer/core/process/edit/imagekeyeditor.py +63 -13
- tomwer/core/process/reconstruction/axis/__init__.py +1 -1
- tomwer/core/process/reconstruction/axis/axis.py +61 -41
- tomwer/core/process/reconstruction/axis/params.py +4 -6
- tomwer/core/process/reconstruction/darkref/darkrefs.py +53 -16
- tomwer/core/process/reconstruction/darkref/darkrefscopy.py +12 -2
- tomwer/core/process/reconstruction/lamino/__init__.py +1 -1
- tomwer/core/process/reconstruction/lamino/tofu.py +22 -2
- tomwer/core/process/reconstruction/nabu/nabucommon.py +93 -14
- tomwer/core/process/reconstruction/nabu/nabuscores.py +70 -33
- tomwer/core/process/reconstruction/nabu/nabuslices.py +219 -41
- tomwer/core/process/reconstruction/nabu/nabuvolume.py +240 -108
- tomwer/core/process/reconstruction/nabu/utils.py +10 -36
- tomwer/core/process/reconstruction/normalization/normalization.py +10 -3
- tomwer/core/process/reconstruction/saaxis/__init__.py +1 -0
- tomwer/core/process/reconstruction/saaxis/saaxis.py +564 -376
- tomwer/core/process/reconstruction/sadeltabeta/__init__.py +1 -0
- tomwer/core/process/reconstruction/sadeltabeta/sadeltabeta.py +481 -268
- tomwer/core/process/reconstruction/scores/params.py +21 -8
- tomwer/core/process/reconstruction/test/test_darkref_copy.py +2 -0
- tomwer/core/process/reconstruction/test/test_saaxis.py +21 -8
- tomwer/core/process/reconstruction/test/test_sadeltabeta.py +8 -5
- tomwer/core/process/script/python.py +7 -2
- tomwer/core/process/stitching/nabustitcher.py +10 -3
- tomwer/core/process/task.py +2 -9
- tomwer/core/process/test/test_axis.py +25 -15
- tomwer/core/process/test/test_conditions.py +6 -6
- tomwer/core/process/test/test_dark_and_flat.py +20 -15
- tomwer/core/process/test/test_data_transfer.py +8 -8
- tomwer/core/process/test/test_data_watcher.py +1 -1
- tomwer/core/process/test/test_lamino.py +6 -6
- tomwer/core/process/test/test_nabu.py +13 -8
- tomwer/core/process/test/test_normalization.py +1 -0
- tomwer/core/process/test/test_timer.py +6 -6
- tomwer/core/process/visualization/dataviewer.py +4 -0
- tomwer/core/process/visualization/diffviewer.py +4 -0
- tomwer/core/process/visualization/imagestackviewer.py +4 -0
- tomwer/core/process/visualization/radiostack.py +4 -0
- tomwer/core/process/visualization/samplemoved.py +4 -0
- tomwer/core/process/visualization/sinogramviewer.py +4 -0
- tomwer/core/process/visualization/slicestack.py +4 -0
- tomwer/core/process/visualization/volumeviewer.py +4 -0
- tomwer/core/scan/hdf5scan.py +4 -4
- tomwer/core/scan/scanbase.py +5 -1
- tomwer/core/scan/test/test_process_registration.py +9 -9
- tomwer/core/settings.py +59 -1
- tomwer/core/test/test_lamino.py +2 -1
- tomwer/core/utils/__init__.py +16 -0
- tomwer/core/utils/locker.py +0 -1
- tomwer/core/utils/resource.py +6 -11
- tomwer/core/utils/scanutils.py +2 -0
- tomwer/gui/cluster/slurm.py +91 -7
- tomwer/gui/cluster/supervisor.py +16 -11
- tomwer/gui/cluster/test/test_cluster.py +16 -1
- tomwer/gui/conditions/filter.py +3 -3
- tomwer/gui/control/datalist.py +24 -11
- tomwer/gui/control/email.py +183 -0
- tomwer/gui/control/reducedarkflatselector.py +545 -0
- tomwer/gui/control/singletomoobj.py +23 -1
- tomwer/gui/control/test/test_email.py +35 -0
- tomwer/gui/control/test/test_reducedarkflat_selector.py +280 -0
- tomwer/gui/reconstruction/axis/CompareImages.py +1 -1
- tomwer/gui/reconstruction/axis/axis.py +10 -6
- tomwer/gui/reconstruction/axis/radioaxis.py +14 -6
- tomwer/gui/reconstruction/darkref/darkrefcopywidget.py +2 -0
- tomwer/gui/reconstruction/darkref/darkrefwidget.py +4 -4
- tomwer/gui/reconstruction/nabu/nabuconfig/phase.py +3 -1
- tomwer/gui/reconstruction/nabu/nabuconfig/preprocessing.py +34 -33
- tomwer/gui/reconstruction/nabu/nabuconfig/reconstruction.py +1 -1
- tomwer/gui/reconstruction/normalization/intensity.py +5 -5
- tomwer/gui/reconstruction/saaxis/corrangeselector.py +1 -0
- tomwer/gui/reconstruction/saaxis/saaxis.py +6 -6
- tomwer/gui/reconstruction/sadeltabeta/saadeltabeta.py +6 -6
- tomwer/gui/reconstruction/scores/scoreplot.py +6 -4
- tomwer/gui/samplemoved/__init__.py +2 -2
- tomwer/gui/stackplot.py +18 -7
- tomwer/gui/stacks.py +2 -2
- tomwer/gui/stitching/stitchandbackground.py +2 -2
- tomwer/gui/stitching/stitching.py +1 -1
- tomwer/gui/stitching/stitching_raw.py +1 -1
- tomwer/gui/utils/__init__.py +1 -85
- tomwer/gui/utils/illustrations.py +1 -1
- tomwer/gui/utils/inputwidget.py +41 -36
- tomwer/gui/utils/slider.py +2 -2
- tomwer/gui/utils/utils.py +93 -0
- tomwer/gui/visualization/dataviewer.py +8 -5
- tomwer/gui/visualization/diffviewer/diffviewer.py +4 -4
- tomwer/gui/visualization/reconstructionparameters.py +26 -6
- tomwer/gui/visualization/sinogramviewer.py +7 -1
- tomwer/gui/visualization/test/test_reconstruction_parameters.py +2 -4
- tomwer/gui/visualization/volumeviewer.py +2 -0
- tomwer/resources/__init__.py +55 -43
- tomwer/resources/gui/icons/compose.png +0 -0
- tomwer/resources/gui/icons/compose.svg +75 -0
- tomwer/synctools/datatransfert.py +3 -1
- tomwer/synctools/stacks/edit/darkflatpatch.py +39 -34
- tomwer/synctools/stacks/edit/imagekeyeditor.py +8 -27
- tomwer/synctools/stacks/processingstack.py +45 -9
- tomwer/synctools/stacks/reconstruction/axis.py +6 -5
- tomwer/synctools/stacks/reconstruction/dkrefcopy.py +1 -0
- tomwer/synctools/stacks/reconstruction/lamino.py +3 -3
- tomwer/synctools/stacks/reconstruction/nabu.py +49 -140
- tomwer/synctools/stacks/reconstruction/normalization.py +1 -0
- tomwer/synctools/stacks/reconstruction/saaxis.py +19 -33
- tomwer/synctools/stacks/reconstruction/sadeltabeta.py +16 -32
- tomwer/synctools/test/test_darkRefs.py +19 -10
- tomwer/synctools/test/test_foldertransfer.py +7 -7
- tomwer/third_party/nabu/preproc/phase.py +6 -8
- tomwer/third_party/nabu/utils.py +2 -3
- tomwer/version.py +1 -1
- {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/METADATA +15 -54
- {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/RECORD +219 -192
- {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/WHEEL +1 -1
- {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/entry_points.txt +3 -3
- /tomwer-1.2.0a1-py3.9-nspkg.pth → /tomwer-1.2.0a3-py3.11-nspkg.pth +0 -0
- {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/LICENSE +0 -0
- {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/namespace_packages.txt +0 -0
- {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,545 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
from copy import copy
|
3
|
+
import os
|
4
|
+
import numpy
|
5
|
+
import logging
|
6
|
+
|
7
|
+
from silx.gui import qt
|
8
|
+
from silx.gui.plot import Plot2D
|
9
|
+
from silx.io.dictdump import h5todict
|
10
|
+
from silx.io.url import DataUrl
|
11
|
+
from silx.gui.dialog.DataFileDialog import DataFileDialog
|
12
|
+
|
13
|
+
from tomoscan.esrf.scan.utils import cwd_context
|
14
|
+
from tomoscan.framereducerbase import REDUCER_TARGET
|
15
|
+
from tomoscan.io import HDF5File
|
16
|
+
|
17
|
+
from tomwer.io.utils import get_default_directory
|
18
|
+
|
19
|
+
_logger = logging.getLogger(__name__)
|
20
|
+
|
21
|
+
|
22
|
+
class ReduceDarkFlatSelectorTableWidget(qt.QWidget):
|
23
|
+
"""
|
24
|
+
Table widget used to refgister and select a list of reduces frames
|
25
|
+
"""
|
26
|
+
|
27
|
+
sigActiveChanged = qt.Signal(object)
|
28
|
+
"""Signal emit when the active frame changed (the one to be displayed)"""
|
29
|
+
|
30
|
+
sigUpdated = qt.Signal()
|
31
|
+
"""Signal emmit when the table is updated"""
|
32
|
+
|
33
|
+
def __init__(self, parent: Optional[qt.QWidget] = None) -> None:
|
34
|
+
super().__init__(parent)
|
35
|
+
self._iDict = 0
|
36
|
+
self._dicts = {}
|
37
|
+
# key are 'dict name', value are dict of the reduced frame (acquisition index as key, 2D numpy array as value).
|
38
|
+
self.setLayout(qt.QVBoxLayout())
|
39
|
+
self._tree = qt.QTreeWidget(self)
|
40
|
+
self._tree.setHeaderLabels(("reduce frames", "acq index", "select"))
|
41
|
+
self._tree.header().setStretchLastSection(False)
|
42
|
+
self._tree.header().setSectionResizeMode(0, qt.QHeaderView.Stretch)
|
43
|
+
self._tree.setColumnWidth(1, 80)
|
44
|
+
self._tree.setColumnWidth(2, 50)
|
45
|
+
self.layout().addWidget(self._tree)
|
46
|
+
|
47
|
+
self.setAcceptDrops(True)
|
48
|
+
|
49
|
+
# connect signal / slot
|
50
|
+
self._tree.currentItemChanged.connect(self._updateActiveChanged)
|
51
|
+
|
52
|
+
def _updateActiveChanged(self, item, column: int):
|
53
|
+
frame = item.data(0, qt.Qt.UserRole)
|
54
|
+
self.sigActiveChanged.emit(frame)
|
55
|
+
|
56
|
+
def popDictIndex(self) -> int:
|
57
|
+
self._iDict = self._iDict + 1
|
58
|
+
return self._iDict - 1
|
59
|
+
|
60
|
+
def addReduceFrames(self, reduced_frames: dict, selected: tuple = ()):
|
61
|
+
"""
|
62
|
+
Add the reduced frames in the interface.
|
63
|
+
:param dict reduced_frames: frame index as key and numpy.ndarray as value
|
64
|
+
:param tuple selected: if the frame index is in selected tuple then this item will be automatically selected
|
65
|
+
"""
|
66
|
+
reduced_frames = copy(reduced_frames)
|
67
|
+
dict_key = (
|
68
|
+
reduced_frames.pop("reduce_frames_name", None)
|
69
|
+
or f"reduced frames #{self.popDictIndex()}"
|
70
|
+
)
|
71
|
+
# if the key already exists: remove it (avoid )
|
72
|
+
if dict_key in self._dicts:
|
73
|
+
self._removeReduceFramesByLabel(dict_key)
|
74
|
+
self._dicts[dict_key] = reduced_frames
|
75
|
+
|
76
|
+
topLevelItem = qt.QTreeWidgetItem(self._tree)
|
77
|
+
topLevelItem.setText(0, dict_key)
|
78
|
+
|
79
|
+
for frame_index, frame in reduced_frames.items():
|
80
|
+
if not isinstance(frame, numpy.ndarray):
|
81
|
+
_logger.error(
|
82
|
+
f"frame are expected to be numpy.ndarray. Get {type(frame)} instead - will not add it"
|
83
|
+
)
|
84
|
+
continue
|
85
|
+
if frame.ndim != 2:
|
86
|
+
_logger.error(f"frame are expected to be 2D. Get {frame.shape} instead")
|
87
|
+
continue
|
88
|
+
|
89
|
+
# frame info
|
90
|
+
frameInfo = qt.QTreeWidgetItem(topLevelItem)
|
91
|
+
frameInfo.setText(0, f"shape: {frame.shape}, dtype {frame.dtype}")
|
92
|
+
frameInfo.setData(0, qt.Qt.UserRole, frame)
|
93
|
+
# frame index
|
94
|
+
indexItem = qt.QLineEdit(self._tree)
|
95
|
+
# index can be provided as absolute indexes (integers)
|
96
|
+
validator = qt.QRegExpValidator(
|
97
|
+
qt.QRegExp(r"([+-]?([0-9]*[.])?[0-9]+)*[r]"), self
|
98
|
+
)
|
99
|
+
indexItem.setValidator(validator)
|
100
|
+
indexItem.setText(str(frame_index))
|
101
|
+
self._tree.setItemWidget(frameInfo, 1, indexItem)
|
102
|
+
indexItem.setToolTip(
|
103
|
+
"Index of the reduced frames in the acquisition. Value can be provided as an absolute value - integer or as a relative value. In this case we expect users to provide a value between [0 and 1.0[ and postfix by 'r'"
|
104
|
+
)
|
105
|
+
# item selection
|
106
|
+
selectCB = qt.QCheckBox(self._tree)
|
107
|
+
selectCB.setChecked(frame_index in selected)
|
108
|
+
self._tree.setItemWidget(frameInfo, 2, selectCB)
|
109
|
+
self.sigUpdated.emit()
|
110
|
+
|
111
|
+
def clear(self):
|
112
|
+
self._dicts.clear()
|
113
|
+
self._tree.clear()
|
114
|
+
self.sigUpdated.emit()
|
115
|
+
|
116
|
+
def getConfiguration(self) -> tuple:
|
117
|
+
"""
|
118
|
+
return configuration as a tuple of 'reduced frames'. Each reduced_frames are provided as:
|
119
|
+
{
|
120
|
+
"reduce_frames_name": str: name of the reduced frame
|
121
|
+
"reduce_frames": tuple of dict for each reduce frame.
|
122
|
+
this dict contains:
|
123
|
+
* index: index of this frame in the acquisition
|
124
|
+
* data: the reduce frame as a 2D array
|
125
|
+
* selected: is the frame selected by the user
|
126
|
+
}
|
127
|
+
"""
|
128
|
+
config = []
|
129
|
+
for i_child in range(self._tree.topLevelItemCount()):
|
130
|
+
child = self._tree.topLevelItem(i_child)
|
131
|
+
reduce_frames_infos = []
|
132
|
+
for j_child in range(child.childCount()):
|
133
|
+
sub_child = child.child(j_child)
|
134
|
+
reduce_frames_infos.append(
|
135
|
+
{
|
136
|
+
"data": sub_child.data(0, qt.Qt.UserRole),
|
137
|
+
"index": self._get_reduce_frame_index(
|
138
|
+
self._tree.itemWidget(sub_child, 1).text()
|
139
|
+
),
|
140
|
+
"selected": self._tree.itemWidget(sub_child, 2).isChecked(),
|
141
|
+
}
|
142
|
+
)
|
143
|
+
config.append(
|
144
|
+
{
|
145
|
+
"reduce_frames_name": child.text(0),
|
146
|
+
"reduce_frames": reduce_frames_infos,
|
147
|
+
}
|
148
|
+
)
|
149
|
+
return tuple(config)
|
150
|
+
|
151
|
+
def setConfiguration(self, config: Optional[tuple]):
|
152
|
+
if config is None:
|
153
|
+
return
|
154
|
+
self.clear()
|
155
|
+
for reduce_frames in config:
|
156
|
+
frames_dict = {
|
157
|
+
"reduce_frames_name": reduce_frames.get("reduce_frames_name", None)
|
158
|
+
}
|
159
|
+
selected = []
|
160
|
+
for reduce_frame in reduce_frames["reduce_frames"]:
|
161
|
+
assert isinstance(
|
162
|
+
reduce_frame, dict
|
163
|
+
), f"reduce_frames is expected to contain a list of dict. Get {type(reduce_frame)} instead"
|
164
|
+
frame_index = reduce_frame["index"]
|
165
|
+
assert isinstance(frame_index, int) or (
|
166
|
+
isinstance(frame_index, str) and frame_index.endswith("r")
|
167
|
+
), f"frame index should be an int or a str ending by 'r', get {type(frame_index)}"
|
168
|
+
frames_dict[frame_index] = reduce_frame["data"]
|
169
|
+
if reduce_frame["selected"]:
|
170
|
+
selected.append(reduce_frame["index"])
|
171
|
+
self.addReduceFrames(frames_dict, selected)
|
172
|
+
self.sigUpdated.emit()
|
173
|
+
|
174
|
+
@staticmethod
|
175
|
+
def _get_reduce_frame_index(index_as_str):
|
176
|
+
"""
|
177
|
+
simple util to return expected frame index depending if the index is provided as relative or as absolute.
|
178
|
+
"""
|
179
|
+
if index_as_str.endswith("r"):
|
180
|
+
return index_as_str
|
181
|
+
else:
|
182
|
+
return int(index_as_str)
|
183
|
+
|
184
|
+
def getSelectedReduceFrames(self) -> dict:
|
185
|
+
"""
|
186
|
+
Return the selected reduced frame as dict
|
187
|
+
"""
|
188
|
+
reduce_frames = {}
|
189
|
+
for i_child in range(self._tree.topLevelItemCount()):
|
190
|
+
child = self._tree.topLevelItem(i_child)
|
191
|
+
for j_child in range(child.childCount()):
|
192
|
+
sub_child = child.child(j_child)
|
193
|
+
is_selected = self._tree.itemWidget(sub_child, 2).isChecked()
|
194
|
+
if not is_selected:
|
195
|
+
continue
|
196
|
+
|
197
|
+
frame_index = self._get_reduce_frame_index(
|
198
|
+
self._tree.itemWidget(sub_child, 1).text()
|
199
|
+
)
|
200
|
+
data = sub_child.data(0, qt.Qt.UserRole)
|
201
|
+
assert isinstance(
|
202
|
+
data, numpy.ndarray
|
203
|
+
), f"frame are expected to be numpy.ndarray. Get {type(data)} instead"
|
204
|
+
assert (
|
205
|
+
data.ndim == 2
|
206
|
+
), f"frame are expected to be 2D. Get {data.shape} instead"
|
207
|
+
if frame_index in reduce_frames:
|
208
|
+
_logger.error(
|
209
|
+
f"frame index {frame_index} defined twice. Ignore last one"
|
210
|
+
)
|
211
|
+
reduce_frames[frame_index] = data
|
212
|
+
return reduce_frames
|
213
|
+
|
214
|
+
def clearSelection(self):
|
215
|
+
"""
|
216
|
+
Uncheck any check Combobox found
|
217
|
+
"""
|
218
|
+
for i_child in range(self._tree.topLevelItemCount()):
|
219
|
+
child = self._tree.topLevelItem(i_child)
|
220
|
+
for j_child in range(child.childCount()):
|
221
|
+
sub_child = child.child(j_child)
|
222
|
+
if self._tree.itemWidget(sub_child, 2).isChecked():
|
223
|
+
self._tree.itemWidget(sub_child, 2).setChecked(False)
|
224
|
+
|
225
|
+
def _removeSelected(self):
|
226
|
+
# note: simplest way to remove item it to reset the configuration
|
227
|
+
configuration = self.getConfiguration()
|
228
|
+
configuration = filter_unselected_reduced_frames(configuration=configuration)
|
229
|
+
self.setConfiguration(configuration)
|
230
|
+
self.sigUpdated.emit()
|
231
|
+
|
232
|
+
def _removeReduceFramesByLabel(self, label: str):
|
233
|
+
"""
|
234
|
+
remove the label named 'label' if exists.
|
235
|
+
The simplest way to remove reduced frames are by reseting the configuration for now
|
236
|
+
"""
|
237
|
+
configuration = list(self.getConfiguration())
|
238
|
+
new_configuration = tuple(
|
239
|
+
filter(
|
240
|
+
lambda my_dict: my_dict.get("reduce_frames_name", None) != label,
|
241
|
+
configuration,
|
242
|
+
),
|
243
|
+
)
|
244
|
+
|
245
|
+
if len(configuration) != len(new_configuration):
|
246
|
+
self.setConfiguration(new_configuration)
|
247
|
+
|
248
|
+
def _guessReduceFramesFromFile(self, file_path: str) -> tuple:
|
249
|
+
if not os.path.exists(file_path):
|
250
|
+
_logger.error(f"file doesn't exists ({file_path})")
|
251
|
+
|
252
|
+
with HDF5File(file_path, mode="r") as h5f:
|
253
|
+
entries = tuple(h5f.keys())
|
254
|
+
|
255
|
+
res = []
|
256
|
+
for entry in entries:
|
257
|
+
res.extend(
|
258
|
+
self.get_reduce_frames(
|
259
|
+
DataUrl(
|
260
|
+
file_path=file_path,
|
261
|
+
data_path=entry,
|
262
|
+
)
|
263
|
+
)
|
264
|
+
)
|
265
|
+
return tuple(res)
|
266
|
+
|
267
|
+
@staticmethod
|
268
|
+
def get_reduce_frames(url: DataUrl) -> tuple:
|
269
|
+
"""
|
270
|
+
try to guess location of darks / flats according to provided url.
|
271
|
+
The idea is to be more robust if the user provide a file or an higher level data path
|
272
|
+
|
273
|
+
:warning: Return a tuple and not a dict. Because in the case the user provide an entry which contains
|
274
|
+
a 'darks' and a flats' groups we want to return both reduced_frames.
|
275
|
+
And we cannot return them as a dict as they can have the same index - used as dict key
|
276
|
+
"""
|
277
|
+
if not isinstance(url, DataUrl):
|
278
|
+
raise TypeError(f"url is expected to be a DataUrl. Get {type(url)} instead")
|
279
|
+
if not os.path.exists(url.file_path()):
|
280
|
+
_logger.error(f"file doesn't exists ({url.file_path()})")
|
281
|
+
return tuple()
|
282
|
+
|
283
|
+
result = []
|
284
|
+
with cwd_context(url.file_path()):
|
285
|
+
reduced_info_dict = h5todict(
|
286
|
+
h5file=url.file_path(),
|
287
|
+
path=url.data_path(),
|
288
|
+
)
|
289
|
+
for target in REDUCER_TARGET.values():
|
290
|
+
if target in reduced_info_dict:
|
291
|
+
reduced_frames = reduced_info_dict[target]
|
292
|
+
reduced_frames[
|
293
|
+
"reduce_frames_name"
|
294
|
+
] = f"{target} from {url.data_path()}@{os.path.basename(url.file_path())}"
|
295
|
+
result.append(reduced_frames)
|
296
|
+
|
297
|
+
if len(reduced_frames) == 0:
|
298
|
+
# else we consider the data_path is the valid one
|
299
|
+
reduced_frames[
|
300
|
+
"reduce_frames_name"
|
301
|
+
] = f"{url.data_path()}@{os.path.basename(url.file_path())}"
|
302
|
+
result.append(reduced_frames)
|
303
|
+
|
304
|
+
return tuple(result)
|
305
|
+
|
306
|
+
# drag / drop handling
|
307
|
+
|
308
|
+
def dropEvent(self, event):
|
309
|
+
if event.mimeData().hasFormat("text/uri-list"):
|
310
|
+
for url in event.mimeData().urls():
|
311
|
+
reduced_frames_list = self._guessReduceFramesFromFile(
|
312
|
+
file_path=url.path()
|
313
|
+
)
|
314
|
+
for reduced_frames in reduced_frames_list:
|
315
|
+
self.addReduceFrames(reduced_frames)
|
316
|
+
|
317
|
+
def supportedDropActions(self):
|
318
|
+
"""Inherited method to redefine supported drop actions."""
|
319
|
+
return qt.Qt.CopyAction | qt.Qt.MoveAction
|
320
|
+
|
321
|
+
def dragEnterEvent(self, event):
|
322
|
+
if hasattr(event, "mimeData") and event.mimeData().hasFormat("text/uri-list"):
|
323
|
+
event.accept()
|
324
|
+
event.setDropAction(qt.Qt.CopyAction)
|
325
|
+
else:
|
326
|
+
try:
|
327
|
+
qt.QListWidget.dragEnterEvent(self, event)
|
328
|
+
except TypeError:
|
329
|
+
pass
|
330
|
+
|
331
|
+
def dragMoveEvent(self, event):
|
332
|
+
if hasattr(event, "mimeDatamyitems") and event.mimeDatamyitems().hasFormat(
|
333
|
+
"text/uri-list"
|
334
|
+
):
|
335
|
+
event.setDropAction(qt.Qt.CopyAction)
|
336
|
+
event.accept()
|
337
|
+
else:
|
338
|
+
try:
|
339
|
+
qt.QListWidget.dragMoveEvent(self, event)
|
340
|
+
except TypeError:
|
341
|
+
pass
|
342
|
+
|
343
|
+
|
344
|
+
class ReduceDarkFlatSelectorWidget(qt.QSplitter):
|
345
|
+
"""
|
346
|
+
Widget combining ReduceDarkFlatSelectorTableWidget and plot of the active item
|
347
|
+
"""
|
348
|
+
|
349
|
+
sigUpdated = qt.Signal()
|
350
|
+
"""Signal emmit when the table is updated"""
|
351
|
+
|
352
|
+
def __init__(self, parent: Optional[qt.QWidget] = None) -> None:
|
353
|
+
super().__init__(parent)
|
354
|
+
self._first_display = True
|
355
|
+
self._plot = Plot2D(self)
|
356
|
+
self.addWidget(self._plot)
|
357
|
+
self._table = ReduceDarkFlatSelectorTableWidget(parent=self)
|
358
|
+
self.addWidget(self._table)
|
359
|
+
|
360
|
+
# connect signal / slot
|
361
|
+
self._table.sigActiveChanged.connect(self._updatePlot)
|
362
|
+
self._table.sigUpdated.connect(self._tableUpdated)
|
363
|
+
|
364
|
+
def _tableUpdated(self, *args, **kwargs):
|
365
|
+
self.sigUpdated.emit()
|
366
|
+
|
367
|
+
def _updatePlot(self, obj: Optional[numpy.ndarray]):
|
368
|
+
if obj is None:
|
369
|
+
self._plot.clear()
|
370
|
+
else:
|
371
|
+
self._plot.addImage(
|
372
|
+
data=obj,
|
373
|
+
replace=True,
|
374
|
+
resetzoom=self._first_display,
|
375
|
+
)
|
376
|
+
self._first_display = False
|
377
|
+
|
378
|
+
# expose API
|
379
|
+
def addReduceFrames(self, *args, **kwargs):
|
380
|
+
self._table.addReduceFrames(*args, **kwargs)
|
381
|
+
|
382
|
+
def getConfiguration(self) -> tuple:
|
383
|
+
return self._table.getConfiguration()
|
384
|
+
|
385
|
+
def setConfiguration(self, *args, **kwargs):
|
386
|
+
self._table.setConfiguration(*args, **kwargs)
|
387
|
+
|
388
|
+
def getSelectedReduceFrames(self) -> dict:
|
389
|
+
return self._table.getSelectedReduceFrames()
|
390
|
+
|
391
|
+
def clearSelection(self) -> None:
|
392
|
+
self._table.clearSelection()
|
393
|
+
|
394
|
+
def _removeSelected(self) -> None:
|
395
|
+
self._table._removeSelected()
|
396
|
+
|
397
|
+
def clear(self) -> None:
|
398
|
+
self._table.clear()
|
399
|
+
|
400
|
+
|
401
|
+
class ReduceDarkFlatSelectorDialog(qt.QDialog):
|
402
|
+
"""
|
403
|
+
'Final' dialog to select reduce frames
|
404
|
+
"""
|
405
|
+
|
406
|
+
sigClearSelection = qt.Signal()
|
407
|
+
sigSelectActiveAsDarks = qt.Signal(dict)
|
408
|
+
sigSelectActiveAsFlats = qt.Signal(dict)
|
409
|
+
|
410
|
+
sigUpdated = qt.Signal()
|
411
|
+
"""Signal emmit when the table is updated"""
|
412
|
+
|
413
|
+
def __init__(self, parent=None) -> None:
|
414
|
+
super().__init__(parent)
|
415
|
+
|
416
|
+
self.setLayout(qt.QVBoxLayout())
|
417
|
+
# add list
|
418
|
+
self._widget = ReduceDarkFlatSelectorWidget()
|
419
|
+
self.layout().addWidget(self._widget)
|
420
|
+
# add buttons
|
421
|
+
self._buttons = qt.QDialogButtonBox(parent=self)
|
422
|
+
# select reduce flats
|
423
|
+
self._selectAsFlatButton = qt.QPushButton(
|
424
|
+
"Select as reduce flat(s)", parent=self
|
425
|
+
)
|
426
|
+
self._buttons.addButton(
|
427
|
+
self._selectAsFlatButton, qt.QDialogButtonBox.AcceptRole
|
428
|
+
)
|
429
|
+
# select reduce darks
|
430
|
+
self._selectAsDarkButton = qt.QPushButton(
|
431
|
+
"Select as reduce dark(s)", parent=self
|
432
|
+
)
|
433
|
+
self._buttons.addButton(
|
434
|
+
self._selectAsDarkButton, qt.QDialogButtonBox.AcceptRole
|
435
|
+
)
|
436
|
+
# clear
|
437
|
+
self._clearSelectionButton = qt.QPushButton("clear selection", parent=self)
|
438
|
+
self._buttons.addButton(
|
439
|
+
self._clearSelectionButton, qt.QDialogButtonBox.ResetRole
|
440
|
+
)
|
441
|
+
# remove selected
|
442
|
+
self._removeSelectionButton = qt.QPushButton("remove selection", parent=self)
|
443
|
+
self._buttons.addButton(
|
444
|
+
self._removeSelectionButton, qt.QDialogButtonBox.ActionRole
|
445
|
+
)
|
446
|
+
# remove selected
|
447
|
+
self._addButton = qt.QPushButton("add", parent=self)
|
448
|
+
self._buttons.addButton(self._addButton, qt.QDialogButtonBox.ActionRole)
|
449
|
+
self.layout().addWidget(self._buttons)
|
450
|
+
|
451
|
+
# connect signal / slot
|
452
|
+
self._selectAsFlatButton.released.connect(self._flatSelected)
|
453
|
+
self._selectAsDarkButton.released.connect(self._darkSelected)
|
454
|
+
self._clearSelectionButton.released.connect(self._clearSelection)
|
455
|
+
self._removeSelectionButton.released.connect(self._removeSelected)
|
456
|
+
self._addButton.released.connect(self._addFromFileSelection)
|
457
|
+
self._widget.sigUpdated.connect(self._tableUpdated)
|
458
|
+
|
459
|
+
def _tableUpdated(self, *args, **kwargs):
|
460
|
+
self.sigUpdated.emit()
|
461
|
+
|
462
|
+
def _flatSelected(self):
|
463
|
+
self.sigSelectActiveAsFlats.emit(
|
464
|
+
self._widget.getSelectedReduceFrames(),
|
465
|
+
)
|
466
|
+
|
467
|
+
def _darkSelected(self):
|
468
|
+
self.sigSelectActiveAsDarks.emit(
|
469
|
+
self._widget.getSelectedReduceFrames(),
|
470
|
+
)
|
471
|
+
|
472
|
+
def _clearSelection(self):
|
473
|
+
self._widget.clearSelection()
|
474
|
+
|
475
|
+
def _removeSelected(self):
|
476
|
+
self._widget._removeSelected()
|
477
|
+
|
478
|
+
def _addFromFileSelection(self):
|
479
|
+
dialog = DataFileDialog()
|
480
|
+
dialog.setDirectory(get_default_directory())
|
481
|
+
if dialog.exec_():
|
482
|
+
url = dialog.selectedUrl()
|
483
|
+
reduced_frames_tuple = ReduceDarkFlatSelectorTableWidget.get_reduce_frames(
|
484
|
+
DataUrl(path=url)
|
485
|
+
)
|
486
|
+
for reduced_frames in reduced_frames_tuple:
|
487
|
+
try:
|
488
|
+
self.addReduceFrames(reduced_frames)
|
489
|
+
except Exception as e:
|
490
|
+
_logger.error(e)
|
491
|
+
|
492
|
+
# expose API
|
493
|
+
def addReduceFrames(self, *args, **kwargs):
|
494
|
+
self._widget.addReduceFrames(*args, **kwargs)
|
495
|
+
|
496
|
+
def getConfiguration(self) -> tuple:
|
497
|
+
self._widget.getConfiguration()
|
498
|
+
|
499
|
+
def setConfiguration(self, configuration: tuple) -> None:
|
500
|
+
self._widget.setConfiguration(configuration)
|
501
|
+
|
502
|
+
|
503
|
+
def filter_selected_reduced_frames(configuration: tuple):
|
504
|
+
"""
|
505
|
+
remove all the frames not 'selected' from the configuration.
|
506
|
+
If a reduce frame set becomes empty it will also be removed
|
507
|
+
|
508
|
+
:param dict configuration: configuration of the reduced frames
|
509
|
+
"""
|
510
|
+
return _filter_reduced_frames(configuration=configuration, filter_selected=True)
|
511
|
+
|
512
|
+
|
513
|
+
def filter_unselected_reduced_frames(configuration: tuple):
|
514
|
+
"""
|
515
|
+
remove all the frames 'selected' from the configuration.
|
516
|
+
If a reduce frame set becomes empty it will also be removed
|
517
|
+
|
518
|
+
:param dict configuration: configuration of the reduced frames
|
519
|
+
"""
|
520
|
+
return _filter_reduced_frames(configuration=configuration, filter_selected=False)
|
521
|
+
|
522
|
+
|
523
|
+
def _filter_reduced_frames(configuration: tuple, filter_selected: bool):
|
524
|
+
assert isinstance(
|
525
|
+
configuration, (tuple, list, set)
|
526
|
+
), f"configuration is expected to be a tuple. Get {type(configuration)}"
|
527
|
+
result = []
|
528
|
+
for reduce_group in configuration:
|
529
|
+
reduce_frames = reduce_group.get("reduce_frames", tuple())
|
530
|
+
reduce_group_name = reduce_group.get("reduce_frames_name", None)
|
531
|
+
res_set = tuple(
|
532
|
+
filter(
|
533
|
+
lambda my_dict: my_dict["selected"] == filter_selected,
|
534
|
+
reduce_frames,
|
535
|
+
)
|
536
|
+
)
|
537
|
+
if len(res_set) > 0:
|
538
|
+
result_group = {}
|
539
|
+
if reduce_group_name is not None:
|
540
|
+
result_group["reduce_frames_name"] = reduce_group_name
|
541
|
+
result_group["reduce_frames"] = res_set
|
542
|
+
|
543
|
+
result.append(result_group)
|
544
|
+
|
545
|
+
return tuple(result)
|
@@ -107,7 +107,9 @@ class SingleTomoObj(qt.QWidget):
|
|
107
107
|
path = os.path.abspath(path)
|
108
108
|
|
109
109
|
try:
|
110
|
-
|
110
|
+
scans = ScanFactory.create_scan_objects(path)
|
111
|
+
if scans is None or len(scans) == 0:
|
112
|
+
raise ValueError
|
111
113
|
except: # noqa E722
|
112
114
|
try:
|
113
115
|
volumes = guess_volumes(
|
@@ -136,11 +138,17 @@ class SingleTomoObj(qt.QWidget):
|
|
136
138
|
|
137
139
|
volumes = tuple(filter(is_not_histogram, volumes))
|
138
140
|
return volumes
|
141
|
+
else:
|
142
|
+
return scans
|
139
143
|
|
140
144
|
|
141
145
|
class _TomoObjQLE(QLFileSystem):
|
142
146
|
"""QLineEdit that try to get a Tomo object identifier once dropped"""
|
143
147
|
|
148
|
+
def supportedDropActions(self):
|
149
|
+
"""Inherited method to redefine supported drop actions."""
|
150
|
+
return qt.Qt.CopyAction | qt.Qt.MoveAction
|
151
|
+
|
144
152
|
def dropEvent(self, a0) -> None:
|
145
153
|
if hasattr(a0, "mimeData") and a0.mimeData().hasFormat("text/uri-list"):
|
146
154
|
for url in a0.mimeData().urls():
|
@@ -155,3 +163,17 @@ class _TomoObjQLE(QLFileSystem):
|
|
155
163
|
self.editingFinished.emit()
|
156
164
|
else:
|
157
165
|
return super().dropEvent(a0)
|
166
|
+
|
167
|
+
def dragEnterEvent(self, event):
|
168
|
+
if event.mimeData().hasFormat("text/uri-list"):
|
169
|
+
event.accept()
|
170
|
+
event.setDropAction(qt.Qt.CopyAction)
|
171
|
+
else:
|
172
|
+
qt.QListWidget.dragEnterEvent(self, event)
|
173
|
+
|
174
|
+
def dragMoveEvent(self, event):
|
175
|
+
if event.mimeData().hasFormat("text/uri-list"):
|
176
|
+
event.setDropAction(qt.Qt.CopyAction)
|
177
|
+
event.accept()
|
178
|
+
else:
|
179
|
+
qt.QListWidget.dragMoveEvent(self, event)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
from tomwer.tests.conftest import qtapp # noqa F401
|
2
|
+
from tomwer.gui.control.email import Emailwidget
|
3
|
+
|
4
|
+
|
5
|
+
def test_Emailwidget(qtapp): # noqa F811
|
6
|
+
"""test of Emailwidget"""
|
7
|
+
widget = Emailwidget()
|
8
|
+
widget.show()
|
9
|
+
|
10
|
+
assert widget.getConfiguration() == {
|
11
|
+
"from_addr": "",
|
12
|
+
"to_addrs": ("",),
|
13
|
+
"subject": "tomwer: {tomo_obj_short_id} received",
|
14
|
+
"text": "{tomo_obj_id} received at {timestamp} \n\nprocesses:\n{dataset_processing_states}\n\nfile listening:\n{ls_tomo_obj}\n\nInfo: {footnote}",
|
15
|
+
"port": 0,
|
16
|
+
"host": "smtps.esrf.fr",
|
17
|
+
}
|
18
|
+
|
19
|
+
new_configuration = {
|
20
|
+
"from_addr": "toto.esrf.fr",
|
21
|
+
"to_addrs": "toto.esrf.fr;tata.esrf.fr",
|
22
|
+
"subject": "this is a scan",
|
23
|
+
"text": "",
|
24
|
+
"port": 445,
|
25
|
+
"host": "smtps.esrf.en",
|
26
|
+
}
|
27
|
+
widget.setConfiguration(new_configuration)
|
28
|
+
|
29
|
+
configuration = widget.getConfiguration()
|
30
|
+
new_configuration.pop("to_addrs")
|
31
|
+
assert configuration.pop("to_addrs") in (
|
32
|
+
("toto.esrf.fr", "tata.esrf.fr"),
|
33
|
+
("tata.esrf.fr", "toto.esrf.fr"),
|
34
|
+
)
|
35
|
+
assert configuration == new_configuration
|