tomwer 1.0.3__py3-none-any.whl → 1.1.0__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/EBS_tomo_listener.ows +39 -0
- orangecontrib/tomwer/tutorials/cast_volume.ows +34 -0
- orangecontrib/tomwer/tutorials/simple_slice_reconstruction.ows +39 -0
- orangecontrib/tomwer/tutorials/simple_volume_local_reconstruction.ows +49 -0
- orangecontrib/tomwer/tutorials/simple_volume_to_slurm_reconstruction.ows +59 -0
- orangecontrib/tomwer/tutorials/using_saaxis_to_find_cor.ows +44 -0
- orangecontrib/tomwer/widgets/cluster/FutureSupervisorOW.py +1 -1
- orangecontrib/tomwer/widgets/cluster/SlurmClusterOW.py +14 -4
- orangecontrib/tomwer/widgets/cluster/__init__.py +1 -1
- orangecontrib/tomwer/widgets/control/DataListOW.py +12 -5
- orangecontrib/tomwer/widgets/control/DataListenerOW.py +18 -9
- orangecontrib/tomwer/widgets/control/DataSelectorOW.py +13 -6
- orangecontrib/tomwer/widgets/control/DataTransfertOW.py +3 -5
- orangecontrib/tomwer/widgets/control/DataValidatorOW.py +8 -4
- orangecontrib/tomwer/widgets/control/DataWatcherOW.py +4 -6
- orangecontrib/tomwer/widgets/control/EDF2NXTomomillOW.py +49 -62
- orangecontrib/tomwer/widgets/control/FilterOW.py +2 -4
- orangecontrib/tomwer/widgets/control/NXTomomillMixIn.py +93 -0
- orangecontrib/tomwer/widgets/control/NXTomomillOW.py +135 -129
- orangecontrib/tomwer/widgets/control/NotifierOW.py +34 -9
- orangecontrib/tomwer/widgets/control/SingleTomoObjOW.py +3 -5
- orangecontrib/tomwer/widgets/control/TomoObjSerieOW.py +19 -13
- orangecontrib/tomwer/widgets/control/VolumeSelector.py +12 -4
- orangecontrib/tomwer/widgets/control/VolumeSymLinkOW.py +11 -7
- orangecontrib/tomwer/widgets/control/icons/notification.svg +4 -4
- orangecontrib/tomwer/widgets/control/icons/nxtomomill.png +0 -0
- orangecontrib/tomwer/widgets/control/icons/nxtomomill.svg +8 -5
- orangecontrib/tomwer/widgets/control/icons/tomoobjserie.png +0 -0
- orangecontrib/tomwer/widgets/control/icons/tomoobjserie.svg +73 -78
- orangecontrib/tomwer/widgets/edit/DarkFlatPatchOW.py +16 -4
- orangecontrib/tomwer/widgets/edit/NXtomoEditorOW.py +100 -0
- orangecontrib/tomwer/widgets/edit/icons/image_key_editor.png +0 -0
- orangecontrib/tomwer/widgets/edit/icons/image_key_upgrader.png +0 -0
- orangecontrib/tomwer/widgets/edit/icons/nx_tomo_editor.png +0 -0
- orangecontrib/tomwer/widgets/edit/icons/nx_tomo_editor.svg +123 -0
- orangecontrib/tomwer/widgets/edit/test/test_dark_flat_patch.py +21 -1
- orangecontrib/tomwer/widgets/edit/test/test_image_key_editor.py +1 -1
- orangecontrib/tomwer/widgets/edit/test/test_image_key_upgrader.py +1 -1
- orangecontrib/tomwer/widgets/edit/test/test_nxtomo_editor.py +25 -0
- orangecontrib/tomwer/widgets/other/PythonScriptOW.py +19 -11
- orangecontrib/tomwer/widgets/reconstruction/AxisOW.py +20 -14
- orangecontrib/tomwer/widgets/reconstruction/CastNabuVolumeOW.py +24 -10
- orangecontrib/tomwer/widgets/reconstruction/DarkRefAndCopyOW.py +26 -21
- orangecontrib/tomwer/widgets/reconstruction/NabuOW.py +29 -12
- orangecontrib/tomwer/widgets/reconstruction/NabuVolumeOW.py +44 -17
- orangecontrib/tomwer/widgets/reconstruction/SAAxisOW.py +28 -20
- orangecontrib/tomwer/widgets/reconstruction/SADeltaBetaOW.py +24 -18
- orangecontrib/tomwer/widgets/reconstruction/SinoNormOW.py +6 -6
- orangecontrib/tomwer/widgets/reconstruction/TofuOW.py +4 -2
- orangecontrib/tomwer/widgets/reconstruction/icons/nabu_2d.png +0 -0
- orangecontrib/tomwer/widgets/reconstruction/icons/nabu_2d.svg +11 -8
- orangecontrib/tomwer/widgets/visualization/DataViewerOW.py +10 -4
- orangecontrib/tomwer/widgets/visualization/DiffViewerOW.py +1 -1
- orangecontrib/tomwer/widgets/visualization/NXtomoMetadataViewerOW.py +69 -0
- orangecontrib/tomwer/widgets/visualization/SampleMovedOW.py +2 -4
- orangecontrib/tomwer/widgets/visualization/icons/nx_tomo_metadata_viewer.png +0 -0
- orangecontrib/tomwer/widgets/visualization/icons/nx_tomo_metadata_viewer.svg +105 -0
- tomwer/__main__.py +10 -5
- tomwer/app/canvas_launcher/config.py +10 -10
- tomwer/app/canvas_launcher/mainwindow.py +68 -6
- tomwer/app/canvas_launcher/widgetsscheme.py +1 -3
- tomwer/app/darkref.py +16 -12
- tomwer/app/imagekeyeditor.py +2 -2
- tomwer/app/imagekeyupgrader.py +104 -0
- tomwer/app/intensitynormalization.py +0 -1
- tomwer/app/nxtomoeditor.py +103 -0
- tomwer/app/rsync.py +1 -1
- tomwer/core/cluster/cluster.py +1 -1
- tomwer/core/futureobject.py +1 -0
- tomwer/core/process/control/datalistener/datalistener.py +7 -1
- tomwer/core/process/control/datalistener/rpcserver.py +3 -4
- tomwer/core/process/control/datawatcher/datawatcher.py +18 -18
- tomwer/core/process/control/datawatcher/datawatcherobserver.py +5 -8
- tomwer/core/process/control/datawatcher/datawatcherprocess.py +2 -3
- tomwer/core/process/control/datawatcher/edfdwprocess.py +2 -2
- tomwer/core/process/control/nxtomomill.py +33 -58
- tomwer/core/process/control/scanlist.py +2 -1
- tomwer/core/process/control/scanselector.py +7 -0
- tomwer/core/process/control/scantransfer.py +2 -2
- tomwer/core/process/control/scanvalidator.py +6 -5
- tomwer/core/process/control/singletomoobj.py +2 -1
- tomwer/core/process/control/timer.py +2 -1
- tomwer/core/process/control/tomoobjserie.py +8 -2
- tomwer/core/process/control/volumeselector.py +2 -1
- tomwer/core/process/control/volumesymlink.py +2 -1
- tomwer/core/process/edit/darkflatpatch.py +2 -1
- tomwer/core/process/edit/imagekeyeditor.py +4 -3
- tomwer/core/process/reconstruction/axis/axis.py +29 -32
- tomwer/core/process/reconstruction/axis/mode.py +3 -2
- tomwer/core/process/reconstruction/axis/params.py +35 -16
- tomwer/core/process/reconstruction/darkref/darkrefs.py +90 -707
- tomwer/core/process/reconstruction/darkref/darkrefscopy.py +44 -16
- tomwer/core/process/reconstruction/darkref/params.py +62 -67
- tomwer/core/process/reconstruction/lamino/tofu.py +1 -2
- tomwer/core/process/reconstruction/nabu/castvolume.py +21 -26
- tomwer/core/process/reconstruction/nabu/nabucommon.py +36 -38
- tomwer/core/process/reconstruction/nabu/nabuscores.py +28 -13
- tomwer/core/process/reconstruction/nabu/nabuslices.py +41 -14
- tomwer/core/process/reconstruction/nabu/nabuvolume.py +21 -12
- tomwer/core/process/reconstruction/nabu/utils.py +32 -3
- tomwer/core/process/reconstruction/normalization/normalization.py +9 -8
- tomwer/core/process/reconstruction/saaxis/saaxis.py +46 -20
- tomwer/core/process/reconstruction/sadeltabeta/sadeltabeta.py +38 -12
- tomwer/core/process/reconstruction/test/__init__.py +0 -39
- tomwer/core/process/reconstruction/test/test_axis_params.py +25 -3
- tomwer/core/process/reconstruction/test/test_darkref_copy.py +117 -1
- tomwer/core/process/script/python.py +16 -12
- tomwer/core/process/task.py +3 -7
- tomwer/core/process/test/test_axis.py +1 -1
- tomwer/core/process/test/test_dark_and_flat.py +41 -111
- tomwer/core/process/test/test_data_listener.py +0 -29
- tomwer/core/process/test/test_data_transfer.py +10 -14
- tomwer/core/process/test/test_nabu.py +1 -1
- tomwer/core/process/test/test_normalization.py +1 -1
- tomwer/core/process/visualization/liveslice.py +6 -0
- tomwer/core/scan/blissscan.py +37 -2
- tomwer/core/scan/edfscan.py +19 -8
- tomwer/core/scan/hdf5scan.py +10 -4
- tomwer/core/scan/scanbase.py +35 -29
- tomwer/core/scan/scanfactory.py +3 -17
- tomwer/core/scan/test/test_h5.py +1 -1
- tomwer/core/scan/test/test_process_registration.py +0 -11
- tomwer/core/scan/test/test_scan.py +32 -30
- tomwer/core/settings.py +2 -2
- tomwer/core/test/test_utils.py +1 -1
- tomwer/core/tomwer_object.py +19 -0
- tomwer/core/utils/__init__.py +0 -45
- tomwer/core/utils/char.py +2 -0
- tomwer/core/utils/gpu.py +5 -5
- tomwer/core/utils/nxtomoutils.py +2 -2
- tomwer/core/utils/scanutils.py +50 -0
- tomwer/core/utils/volumeutils.py +13 -0
- tomwer/core/volume/edfvolume.py +4 -0
- tomwer/core/volume/hdf5volume.py +4 -0
- tomwer/core/volume/jp2kvolume.py +4 -0
- tomwer/core/volume/rawvolume.py +22 -5
- tomwer/core/volume/tiffvolume.py +4 -0
- tomwer/core/volume/volumebase.py +19 -12
- tomwer/core/volume/volumefactory.py +20 -1
- tomwer/gui/cluster/slurm.py +1 -1
- tomwer/gui/cluster/supervisor.py +0 -2
- tomwer/gui/cluster/test/test_cluster.py +2 -2
- tomwer/gui/control/datalist.py +109 -36
- tomwer/gui/control/datatransfert.py +1 -1
- tomwer/gui/control/datawatcher/configuration.py +0 -2
- tomwer/gui/control/datawatcher/datawatcher.py +23 -13
- tomwer/gui/control/datawatcher/datawatcherobserver.py +1 -1
- tomwer/gui/control/observations.py +0 -3
- tomwer/gui/control/selectorwidgetbase.py +42 -12
- tomwer/gui/control/serie/seriecreator.py +967 -0
- tomwer/{web/__init__.py → gui/control/serie/seriewaiter.py} +5 -7
- tomwer/gui/control/singletomoobj.py +15 -4
- tomwer/gui/control/test/test_datalist.py +1 -1
- tomwer/gui/control/test/test_datalistener.py +1 -1
- tomwer/gui/control/test/test_inputwidget.py +1 -1
- tomwer/gui/control/test/test_process_manager.py +1 -13
- tomwer/gui/control/test/test_scanselector.py +1 -1
- tomwer/gui/control/test/test_scanvalidator.py +1 -1
- tomwer/gui/control/test/test_single_tomo_obj.py +1 -1
- tomwer/gui/control/test/test_volume_dialog.py +19 -7
- tomwer/gui/control/test/test_volumeselector.py +4 -4
- tomwer/gui/debugtools/datasetgenerator.py +1 -9
- tomwer/gui/edit/dkrfpatch.py +2 -3
- tomwer/gui/edit/imagekeyeditor.py +12 -11
- tomwer/gui/edit/nxtomoeditor.py +475 -0
- tomwer/gui/edit/test/test_dkrf_patch.py +2 -14
- tomwer/gui/edit/test/test_image_key_editor.py +2 -2
- tomwer/gui/edit/test/test_nx_editor.py +155 -0
- tomwer/gui/icons.py +0 -1
- tomwer/gui/qfolderdialog.py +11 -0
- tomwer/gui/reconstruction/axis/CompareImages.py +27 -29
- tomwer/gui/reconstruction/axis/axis.py +2 -0
- tomwer/gui/reconstruction/axis/radioaxis.py +70 -14
- tomwer/gui/reconstruction/darkref/darkrefcopywidget.py +7 -9
- tomwer/gui/reconstruction/darkref/darkrefwidget.py +22 -24
- tomwer/gui/reconstruction/lamino/tofu/projections.py +1 -1
- tomwer/gui/reconstruction/lamino/tofu/tofu.py +3 -3
- tomwer/gui/reconstruction/lamino/tofu/tofuexpert.py +4 -4
- tomwer/gui/reconstruction/lamino/tofu/tofuoutput.py +10 -5
- tomwer/gui/reconstruction/nabu/castvolume.py +103 -24
- tomwer/gui/reconstruction/nabu/check.py +1 -1
- tomwer/gui/reconstruction/nabu/nabuconfig/ctf.py +352 -0
- tomwer/gui/reconstruction/nabu/nabuconfig/nabuconfig.py +0 -9
- tomwer/gui/reconstruction/nabu/nabuconfig/output.py +1 -1
- tomwer/gui/reconstruction/nabu/nabuconfig/phase.py +18 -19
- tomwer/gui/reconstruction/nabu/nabuconfig/preprocessing.py +30 -7
- tomwer/gui/reconstruction/nabu/nabuconfig/reconstruction.py +26 -15
- tomwer/gui/reconstruction/nabu/slices.py +10 -4
- tomwer/gui/reconstruction/nabu/slurm.py +1 -1
- tomwer/gui/reconstruction/nabu/volume.py +13 -7
- tomwer/gui/reconstruction/normalization/intensity.py +1 -5
- tomwer/gui/reconstruction/saaxis/corrangeselector.py +10 -37
- tomwer/gui/reconstruction/saaxis/saaxis.py +11 -7
- tomwer/gui/reconstruction/saaxis/sliceselector.py +11 -26
- tomwer/gui/reconstruction/sadeltabeta/saadeltabeta.py +13 -8
- tomwer/gui/reconstruction/scores/scoreplot.py +67 -62
- tomwer/gui/reconstruction/test/test_axis.py +2 -2
- tomwer/gui/reconstruction/test/test_lamino.py +2 -2
- tomwer/gui/reconstruction/test/test_nabu.py +14 -1
- tomwer/gui/reconstruction/test/test_saaxis.py +8 -17
- tomwer/gui/reconstruction/test/test_sadeltabeta.py +7 -13
- tomwer/gui/stackplot.py +11 -28
- tomwer/gui/test/test_axis_gui.py +4 -4
- tomwer/gui/test/test_qfolder_dialog.py +12 -0
- tomwer/gui/utils/inputwidget.py +42 -22
- tomwer/gui/utils/lineselector/lineselector.py +13 -21
- tomwer/gui/utils/scandescription.py +2 -4
- tomwer/gui/utils/slider.py +1 -102
- tomwer/gui/utils/unitsystem.py +48 -11
- tomwer/gui/visualization/dataviewer.py +24 -17
- tomwer/gui/visualization/diffviewer/diffviewer.py +2 -11
- tomwer/gui/visualization/nxtomometadata.py +21 -0
- tomwer/gui/visualization/scanoverview.py +0 -1
- tomwer/gui/visualization/test/test_nx_tomo_metadata_viewer.py +72 -0
- tomwer/gui/visualization/test/test_stacks.py +1 -1
- tomwer/gui/visualization/tomoobjoverview.py +49 -0
- tomwer/gui/visualization/volumeoverview.py +64 -0
- tomwer/gui/visualization/volumeviewer.py +1 -1
- tomwer/io/utils/utils.py +2 -2
- tomwer/resources/gui/icons/multi-document-save.png +0 -0
- tomwer/resources/gui/icons/multi-document-save.svg +101 -0
- tomwer/resources/gui/illustrations/ctf_z1.png +0 -0
- tomwer/resources/gui/illustrations/ctf_z1.svg +471 -0
- tomwer/synctools/axis.py +0 -1
- tomwer/synctools/darkref.py +0 -1
- tomwer/synctools/datalistener.py +5 -1
- tomwer/synctools/imageloaderthread.py +2 -2
- tomwer/synctools/saaxis.py +0 -1
- tomwer/synctools/sadeltabeta.py +0 -1
- tomwer/synctools/stacks/edit/imagekeyeditor.py +1 -1
- tomwer/synctools/stacks/processingstack.py +2 -2
- tomwer/synctools/stacks/reconstruction/castvolume.py +1 -0
- tomwer/synctools/stacks/reconstruction/dkrefcopy.py +1 -1
- tomwer/synctools/stacks/reconstruction/lamino.py +1 -3
- tomwer/synctools/stacks/reconstruction/sadeltabeta.py +0 -2
- tomwer/synctools/test/test_darkRefs.py +32 -149
- tomwer/synctools/test/test_foldertransfer.py +1 -1
- tomwer/synctools/test/test_scanstages.py +2 -2
- tomwer/tests/conftest.py +51 -0
- tomwer/{test → tests}/test_scripts.py +1 -1
- tomwer/tests/test_utils.py +10 -0
- tomwer/{test → tests}/utils/utilstest.py +0 -11
- tomwer/version.py +3 -3
- {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/METADATA +14 -16
- {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/RECORD +255 -235
- {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/WHEEL +1 -1
- {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/entry_points.txt +6 -0
- orangecontrib/tomwer/setup.py +0 -45
- orangecontrib/tomwer/widgets/setup.py +0 -49
- tomwer/app/process.py +0 -153
- tomwer/core/process/reconstruction/nabu/slurm.py +0 -36
- tomwer/core/process/reconstruction/utils/nabu_slice_exec.py +0 -10
- tomwer/core/utils/laminoutils.py +0 -80
- tomwer/gui/utils/lineselector/lineselection.py +0 -76
- tomwer/setup.py +0 -52
- tomwer/slurm/executor.py +0 -36
- tomwer/slurm/job.py +0 -349
- tomwer/slurm/utils.py +0 -44
- tomwer/web/client.py +0 -43
- tomwer/web/config.py +0 -36
- tomwer/web/test/test_graylog_connection.py +0 -59
- {tomwer/slurm → orangecontrib/tomwer/tutorials}/__init__.py +0 -0
- /tomwer/{test → gui/control/serie}/__init__.py +0 -0
- /tomwer/{web/test → tests}/__init__.py +0 -0
- /tomwer/{test → tests}/utils/__init__.py +0 -0
- /tomwer-1.0.3-py3.8-nspkg.pth → /tomwer-1.1.0-py3.9-nspkg.pth +0 -0
- {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/LICENSE +0 -0
- {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/namespace_packages.txt +0 -0
- {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,967 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# /*##########################################################################
|
3
|
+
#
|
4
|
+
# Copyright (c) 2016-2017 European Synchrotron Radiation Facility
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
11
|
+
# furnished to do so, subject to the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
14
|
+
# all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
# THE SOFTWARE.
|
23
|
+
#
|
24
|
+
# ###########################################################################*/
|
25
|
+
|
26
|
+
__authors__ = ["H. Payno"]
|
27
|
+
__license__ = "MIT"
|
28
|
+
__date__ = "12/01/2022"
|
29
|
+
|
30
|
+
|
31
|
+
from contextlib import AbstractContextManager
|
32
|
+
from typing import Optional, Iterable
|
33
|
+
from tomwer.core.scan.scanfactory import ScanFactory
|
34
|
+
from tomwer.core.scan.scanbase import TomwerScanBase
|
35
|
+
from tomwer.core.volume.volumefactory import VolumeFactory
|
36
|
+
from tomwer.gui.control.datalist import TomoObjList
|
37
|
+
from tomwer.gui.visualization.tomoobjoverview import TomoObjOverview
|
38
|
+
from tomwer.core.tomwer_object import TomwerObject
|
39
|
+
from silx.gui import qt
|
40
|
+
from silx.gui.utils import blockSignals
|
41
|
+
from tomoscan.serie import Serie
|
42
|
+
from silx.utils.enum import Enum as _Enum
|
43
|
+
import logging
|
44
|
+
|
45
|
+
from tomwer.gui.qfolderdialog import QDataDialog
|
46
|
+
|
47
|
+
_logger = logging.getLogger(__name__)
|
48
|
+
|
49
|
+
|
50
|
+
class SerieWidgetDialog(qt.QDialog):
|
51
|
+
sigSerieSelected = qt.Signal(Serie)
|
52
|
+
"""
|
53
|
+
emit when a serie is selected / triggered by the user
|
54
|
+
"""
|
55
|
+
|
56
|
+
def __init__(self, *args, **kwargs) -> None:
|
57
|
+
super().__init__(*args, **kwargs)
|
58
|
+
|
59
|
+
self.setLayout(qt.QVBoxLayout())
|
60
|
+
# add list
|
61
|
+
self._widget = SerieWidget()
|
62
|
+
self.layout().addWidget(self._widget)
|
63
|
+
# add buttons
|
64
|
+
self._buttons = qt.QDialogButtonBox(parent=self)
|
65
|
+
self._selectButton = qt.QPushButton("Select (active) serie", parent=self)
|
66
|
+
self._buttons.addButton(self._selectButton, qt.QDialogButtonBox.ActionRole)
|
67
|
+
self.layout().addWidget(self._buttons)
|
68
|
+
|
69
|
+
# connect signal / slot
|
70
|
+
self._selectButton.released.connect(self._serieSelected)
|
71
|
+
|
72
|
+
def getSelectedSerie(self) -> Optional[Serie]:
|
73
|
+
return self._widget.getSelectedSerie()
|
74
|
+
|
75
|
+
def _serieSelected(self, *args, **kwargs):
|
76
|
+
serie = self.getSelectedSerie()
|
77
|
+
if serie is not None:
|
78
|
+
self.sigSerieSelected.emit(serie)
|
79
|
+
|
80
|
+
# expose API
|
81
|
+
def add(self, tomo_obj):
|
82
|
+
self._widget.add(tomo_obj=tomo_obj)
|
83
|
+
|
84
|
+
|
85
|
+
class SerieWidget(qt.QTabWidget):
|
86
|
+
sigCurrentSerieChanged = qt.Signal()
|
87
|
+
"""signal emit when the current serie changes"""
|
88
|
+
|
89
|
+
sigHistoryChanged = qt.Signal()
|
90
|
+
"""signal emit when the history changed (a serie has been added or removed"""
|
91
|
+
|
92
|
+
sigSerieSend = qt.Signal(Serie)
|
93
|
+
"""Signal emited when a serie has been send"""
|
94
|
+
|
95
|
+
_HISTORY_MODE = "history"
|
96
|
+
_DEFINITION_MODE = "serie definition"
|
97
|
+
|
98
|
+
def __init__(self, parent=None) -> None:
|
99
|
+
super().__init__(parent)
|
100
|
+
self.setWindowTitle("serie of scans")
|
101
|
+
self._serieDefinitionWidget = SerieDefinition(parent=self)
|
102
|
+
self.addTab(self._serieDefinitionWidget, self._DEFINITION_MODE)
|
103
|
+
self._historyWidget = SerieHistoryDialog(parent=self)
|
104
|
+
self._historyWidget.setWindowFlags(qt.Qt.Widget)
|
105
|
+
self.addTab(self._historyWidget, self._HISTORY_MODE)
|
106
|
+
|
107
|
+
# conenct signal / slot
|
108
|
+
self._historyWidget.sigEditSerie.connect(self._serieEditionRequested)
|
109
|
+
self._historyWidget.sigSerieSend.connect(self._repeatSerieSend)
|
110
|
+
self._historyWidget.sigHistoryUpdated.connect(self._repeatHistoryUpdated)
|
111
|
+
self._serieDefinitionWidget.sigSerieChanged.connect(self._repeatSerieChanged)
|
112
|
+
self._serieDefinitionWidget.sigSerieSend.connect(self._repeatSerieSend)
|
113
|
+
self._serieDefinitionWidget.sigSerieSend.connect(self._historyWidget.addSerie)
|
114
|
+
|
115
|
+
def getHistoryWidget(self):
|
116
|
+
return self._historyWidget
|
117
|
+
|
118
|
+
def getDefinitionWidget(self):
|
119
|
+
return self._serieDefinitionWidget
|
120
|
+
|
121
|
+
def getSelectedSerie(self) -> Optional[Serie]:
|
122
|
+
return self._serieDefinitionWidget.getSelectedSerie()
|
123
|
+
|
124
|
+
def setMode(self, mode: str, definition_mode: Optional[str] = None):
|
125
|
+
valid_modes = (self._HISTORY_MODE, self._DEFINITION_MODE)
|
126
|
+
if mode == self._HISTORY_MODE:
|
127
|
+
self.setCurrentWidget(self._historyWidget)
|
128
|
+
elif mode == self._DEFINITION_MODE:
|
129
|
+
self.setCurrentWidget(self._serieDefinitionWidget)
|
130
|
+
self._serieDefinitionWidget.setMode(definition_mode)
|
131
|
+
else:
|
132
|
+
raise ValueError(
|
133
|
+
f"mode {mode} is no recognized. Valid modes are {valid_modes}"
|
134
|
+
)
|
135
|
+
|
136
|
+
def _serieEditionRequested(self, serie: Serie):
|
137
|
+
if not isinstance(serie, Serie):
|
138
|
+
raise TypeError(f"serie is expected to be a serie not {type(serie)}")
|
139
|
+
self.setMode("serie definition", "manual")
|
140
|
+
self.getDefinitionWidget().getManualDefinitionWidget().setSerie(serie)
|
141
|
+
|
142
|
+
def _repeatSerieChanged(self, *args, **kwargs):
|
143
|
+
self.sigCurrentSerieChanged.emit()
|
144
|
+
|
145
|
+
def _repeatSerieSend(self, serie: Serie):
|
146
|
+
self.sigSerieSend.emit(serie)
|
147
|
+
|
148
|
+
def _repeatHistoryUpdated(self, *args, **kwargs):
|
149
|
+
self.sigHistoryChanged.emit()
|
150
|
+
|
151
|
+
def add(self, tomo_obj):
|
152
|
+
return self._serieDefinitionWidget.addTomoObj(tomo_obj)
|
153
|
+
|
154
|
+
|
155
|
+
class _SerieDefinitionMode(_Enum):
|
156
|
+
MANUAL = "manual"
|
157
|
+
AUTO = "auto"
|
158
|
+
|
159
|
+
|
160
|
+
class SerieDefinition(qt.QWidget):
|
161
|
+
sigSerieChanged = qt.Signal()
|
162
|
+
### signal emit when a the serie defined manually changed
|
163
|
+
|
164
|
+
sigSerieSend = qt.Signal(Serie)
|
165
|
+
### signal emit when a serie is send
|
166
|
+
|
167
|
+
def __init__(self, parent=None) -> None:
|
168
|
+
super().__init__(parent)
|
169
|
+
self.setLayout(qt.QGridLayout())
|
170
|
+
|
171
|
+
self._modeLabel = qt.QLabel("Mode", self)
|
172
|
+
self.layout().addWidget(self._modeLabel, 0, 0, 1, 1)
|
173
|
+
|
174
|
+
self._modeCB = qt.QComboBox(self)
|
175
|
+
for mode in _SerieDefinitionMode.values():
|
176
|
+
self._modeCB.addItem(mode)
|
177
|
+
self.layout().addWidget(self._modeCB, 0, 1, 1, 1)
|
178
|
+
|
179
|
+
self._manualDefWidget = SerieManualFromTomoObj(parent=self)
|
180
|
+
self.layout().addWidget(self._manualDefWidget, 1, 0, 1, 2)
|
181
|
+
self._manualDefWidget.setWindowFlags(qt.Qt.Widget)
|
182
|
+
|
183
|
+
self._automaticDefWidget = SerieAutomaticDefinitionWidget(parent=self)
|
184
|
+
self.layout().addWidget(self._automaticDefWidget, 2, 0, 1, 2)
|
185
|
+
|
186
|
+
# connect signal / slot
|
187
|
+
self._modeCB.currentIndexChanged.connect(self._updateVisibility)
|
188
|
+
self._manualDefWidget._newSerieWidget.sigUpdated.connect(
|
189
|
+
self._repeatSerieChanged
|
190
|
+
)
|
191
|
+
|
192
|
+
# set up
|
193
|
+
self._updateVisibility()
|
194
|
+
|
195
|
+
def getSelectedSerie(self) -> Optional[Serie]:
|
196
|
+
if self.getMode() == _SerieDefinitionMode.MANUAL:
|
197
|
+
return self._manualDefWidget.getSerie()
|
198
|
+
else:
|
199
|
+
raise ValueError(f"mode {self.getMode()} is not handled yet")
|
200
|
+
|
201
|
+
def getMode(self) -> str:
|
202
|
+
return _SerieDefinitionMode.from_value(self._modeCB.currentText())
|
203
|
+
|
204
|
+
def setMode(self, mode: str):
|
205
|
+
mode = _SerieDefinitionMode.from_value(mode)
|
206
|
+
idx = self._modeCB.findText(mode.value)
|
207
|
+
self._modeCB.setCurrentIndex(idx)
|
208
|
+
|
209
|
+
def _updateVisibility(self):
|
210
|
+
self._manualDefWidget.setVisible(self.getMode() == _SerieDefinitionMode.MANUAL)
|
211
|
+
self._automaticDefWidget.setVisible(self.getMode() == _SerieDefinitionMode.AUTO)
|
212
|
+
|
213
|
+
def getManualDefinitionWidget(self):
|
214
|
+
return self._manualDefWidget
|
215
|
+
|
216
|
+
def getAutoDefinitionWidget(self):
|
217
|
+
return self._automaticDefWidget
|
218
|
+
|
219
|
+
def _repeatSerieChanged(self, *args, **kwargs):
|
220
|
+
self.sigSerieChanged.emit()
|
221
|
+
|
222
|
+
def _repeatSerieSend(self, serie: Serie):
|
223
|
+
self.sigSerieSend.emit(serie)
|
224
|
+
|
225
|
+
def createManualSerie(self):
|
226
|
+
self._manualDefWidget.createSerie()
|
227
|
+
|
228
|
+
def addTomoObj(self, tomo_obj: TomwerObject):
|
229
|
+
self._manualDefWidget.addTomoObj(tomo_obj=tomo_obj)
|
230
|
+
|
231
|
+
def setSerieName(self, name: str):
|
232
|
+
self._manualDefWidget.setSerieName(name=name)
|
233
|
+
|
234
|
+
|
235
|
+
class _SerieDefinitionTree(qt.QWidget):
|
236
|
+
"""
|
237
|
+
Tree used to define manually serie of scan.
|
238
|
+
Drag and drop of files is handled
|
239
|
+
"""
|
240
|
+
|
241
|
+
sigUpdated = qt.Signal()
|
242
|
+
"""Signal emit when the serie is updated"""
|
243
|
+
|
244
|
+
class SignalBlocker(AbstractContextManager):
|
245
|
+
"""Simple context manager to hide / show button dialogs"""
|
246
|
+
|
247
|
+
def __init__(self, serie_definition_widget) -> None:
|
248
|
+
super().__init__()
|
249
|
+
self.serie_definition_widget = serie_definition_widget
|
250
|
+
|
251
|
+
def __enter__(self):
|
252
|
+
self.old_widget = self.serie_definition_widget.blockSignals(True)
|
253
|
+
self.old_tree = self.serie_definition_widget._tree.blockSignals(True)
|
254
|
+
|
255
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
256
|
+
self.serie_definition_widget.blockSignals(self.old_widget)
|
257
|
+
self.serie_definition_widget._tree.blockSignals(self.old_tree)
|
258
|
+
|
259
|
+
def __init__(self, parent=None, serie_name="my_serie") -> None:
|
260
|
+
self._tomo_objs = {}
|
261
|
+
# associated serie name (key) to tuple (serie, QTreeWidgetItem)
|
262
|
+
super().__init__(parent)
|
263
|
+
self.setLayout(qt.QVBoxLayout())
|
264
|
+
self._tree = qt.QTreeWidget(self)
|
265
|
+
self._tree.setSelectionMode(qt.QAbstractItemView.MultiSelection)
|
266
|
+
self._tree.setColumnCount(2)
|
267
|
+
self._tree.setHeaderLabels(("serie", "scan ids"))
|
268
|
+
self._tree.setItemsExpandable(False)
|
269
|
+
self.layout().addWidget(self._tree)
|
270
|
+
|
271
|
+
# set up the tree with the serie name that will stay during the entire
|
272
|
+
# life time of the tree
|
273
|
+
self._serieItem = qt.QTreeWidgetItem(self._tree)
|
274
|
+
self._serieItem.setFlags(self._serieItem.flags() | qt.Qt.ItemIsEditable)
|
275
|
+
self._serieItem.setExpanded(True)
|
276
|
+
|
277
|
+
self.setAcceptDrops(True)
|
278
|
+
|
279
|
+
# connect signal / slot
|
280
|
+
self._tree.itemChanged.connect(self._updated)
|
281
|
+
|
282
|
+
# set up
|
283
|
+
self.setSerieName(name=serie_name)
|
284
|
+
|
285
|
+
# expose API
|
286
|
+
self.itemChanged = self._tree.itemChanged
|
287
|
+
|
288
|
+
@property
|
289
|
+
def rootItem(self):
|
290
|
+
return self._serieItem
|
291
|
+
|
292
|
+
def setSerieName(self, name: str):
|
293
|
+
with self.SignalBlocker(self):
|
294
|
+
self._serieItem.setText(0, name)
|
295
|
+
self.sigUpdated.emit()
|
296
|
+
|
297
|
+
def getSerieName(self):
|
298
|
+
return self._serieItem.text(0)
|
299
|
+
|
300
|
+
def addTomoObj(self, tomo_obj: TomwerObject):
|
301
|
+
if not isinstance(tomo_obj, TomwerObject):
|
302
|
+
raise TypeError(
|
303
|
+
f"{tomo_obj} is expected to be an instance of {TomwerObject} not {type(tomo_obj)}"
|
304
|
+
)
|
305
|
+
identifier = tomo_obj.get_identifier().to_str()
|
306
|
+
if identifier in self._tomo_objs:
|
307
|
+
_logger.warning(f"scan {identifier} already part of the serie")
|
308
|
+
return
|
309
|
+
|
310
|
+
with self.SignalBlocker(self):
|
311
|
+
tomo_obj_item = qt.QTreeWidgetItem(self.rootItem)
|
312
|
+
tomo_obj_item.setText(1, identifier)
|
313
|
+
tomo_obj_item.setFlags(tomo_obj_item.flags() | qt.Qt.ItemIsUserCheckable)
|
314
|
+
self._tomo_objs[identifier] = (tomo_obj, tomo_obj_item)
|
315
|
+
self.sigUpdated.emit()
|
316
|
+
|
317
|
+
def removeTomoObj(self, tomo_obj: TomwerObject):
|
318
|
+
if not isinstance(tomo_obj, TomwerObject):
|
319
|
+
raise TypeError(
|
320
|
+
f"{tomo_obj} is expected to be an instance of {TomwerObject} not {type(tomo_obj)}"
|
321
|
+
)
|
322
|
+
|
323
|
+
with self.SignalBlocker(self):
|
324
|
+
identifier = tomo_obj.get_identifier().to_str()
|
325
|
+
if identifier not in self._tomo_objs:
|
326
|
+
_logger.warning(f"{identifier} is not in the serie")
|
327
|
+
else:
|
328
|
+
_, tomo_obj_item = self._tomo_objs.pop(identifier)
|
329
|
+
root = self._tree.invisibleRootItem()
|
330
|
+
root.removeChild(tomo_obj_item)
|
331
|
+
self.sigUpdated.emit()
|
332
|
+
|
333
|
+
@property
|
334
|
+
def n_tomo_objs(self):
|
335
|
+
return len(self._tomo_objs)
|
336
|
+
|
337
|
+
def setSerie(self, serie: Serie) -> None:
|
338
|
+
if not isinstance(serie, Serie):
|
339
|
+
raise TypeError(
|
340
|
+
f"serie is expected to be an instance of {Serie} not {type(serie)}"
|
341
|
+
)
|
342
|
+
|
343
|
+
with self.SignalBlocker(self):
|
344
|
+
self.clearTomoObjs()
|
345
|
+
self.setSerieName(serie.name)
|
346
|
+
for tomo_obj in serie:
|
347
|
+
if isinstance(tomo_obj, str):
|
348
|
+
try:
|
349
|
+
tomo_obj = ScanFactory.create_tomo_object_from_identifier(
|
350
|
+
identifier=tomo_obj
|
351
|
+
)
|
352
|
+
except Exception:
|
353
|
+
try:
|
354
|
+
tomo_obj = VolumeFactory.create_tomo_object_from_identifier(
|
355
|
+
identifier=tomo_obj
|
356
|
+
)
|
357
|
+
except Exception:
|
358
|
+
_logger.warning(f"Fail to recreate scan from {tomo_obj}.")
|
359
|
+
return
|
360
|
+
elif not isinstance(tomo_obj, TomwerObject):
|
361
|
+
raise TypeError(
|
362
|
+
f"tomo_obj is expected to be an instance of {TomwerObject}. Not {type(tomo_obj)}"
|
363
|
+
)
|
364
|
+
self.addTomoObj(tomo_obj)
|
365
|
+
self.sigUpdated.emit()
|
366
|
+
|
367
|
+
def getSerie(self, use_identifiers=False) -> Serie:
|
368
|
+
scans = [scan for scan, _ in self._tomo_objs.values()]
|
369
|
+
return Serie(
|
370
|
+
name=self.getSerieName(),
|
371
|
+
iterable=scans,
|
372
|
+
use_identifiers=use_identifiers,
|
373
|
+
)
|
374
|
+
|
375
|
+
def clearTomoObjs(self):
|
376
|
+
with self.SignalBlocker(self):
|
377
|
+
keys = list(self._tomo_objs.keys())
|
378
|
+
for key in keys:
|
379
|
+
_, scan_item = self._tomo_objs.pop(key)
|
380
|
+
root = self._tree.invisibleRootItem()
|
381
|
+
root.removeChild(scan_item)
|
382
|
+
self.sigUpdated.emit()
|
383
|
+
|
384
|
+
def setSelectedTomoObjs(self, objs):
|
385
|
+
self.clearSelection()
|
386
|
+
for scan in objs:
|
387
|
+
scan_item = self._getTomoObjItem(scan)
|
388
|
+
if scan_item is not None:
|
389
|
+
scan_item.setSelected(True)
|
390
|
+
|
391
|
+
def _getTomoObjItem(self, tomo_obj: TomwerObject) -> Optional[qt.QTreeWidgetItem]:
|
392
|
+
if not isinstance(tomo_obj, TomwerObject):
|
393
|
+
raise TypeError(
|
394
|
+
f"scan is expected to be an instance of {TomwerObject} not {type(tomo_obj)}"
|
395
|
+
)
|
396
|
+
return self._tomo_objs.get(tomo_obj.get_identifier().to_str(), (None, None))[1]
|
397
|
+
|
398
|
+
def getSelectedTomoObjs(self) -> tuple():
|
399
|
+
"""return selected scans"""
|
400
|
+
selected = []
|
401
|
+
for _, (scan, item) in self._tomo_objs.items():
|
402
|
+
if item.isSelected():
|
403
|
+
selected.append(scan)
|
404
|
+
return tuple(selected)
|
405
|
+
|
406
|
+
def removeSelectedTomoObjs(self) -> None:
|
407
|
+
with self.SignalBlocker(self):
|
408
|
+
for tomo_obj in self.getSelectedTomoObjs():
|
409
|
+
self.removeTomoObj(tomo_obj)
|
410
|
+
|
411
|
+
def _updated(self, *args, **kwargs):
|
412
|
+
self.sigUpdated.emit()
|
413
|
+
|
414
|
+
def clearSelection(self) -> None:
|
415
|
+
self._tree.selectionModel().clearSelection()
|
416
|
+
|
417
|
+
def addScanFromNxFile(self, file_: str, entry: Optional[str] = None):
|
418
|
+
try:
|
419
|
+
if entry is None:
|
420
|
+
scans = ScanFactory.create_scan_objects(scan_path=file_)
|
421
|
+
else:
|
422
|
+
scans = [ScanFactory.create_scan_object(scan_path=file_, entry=entry)]
|
423
|
+
except Exception as e:
|
424
|
+
_logger.error(
|
425
|
+
"cannot create scan instances from {}. Error is {}".format(file_, e)
|
426
|
+
)
|
427
|
+
else:
|
428
|
+
changed = False
|
429
|
+
with self.SignalBlocker(self):
|
430
|
+
for scan in scans:
|
431
|
+
if scan is not None:
|
432
|
+
try:
|
433
|
+
self.addTomoObj(tomo_obj=scan)
|
434
|
+
except TypeError:
|
435
|
+
_logger.error(
|
436
|
+
f"fail to add scan {scan}. Invalid type encountered ({type(scan)})"
|
437
|
+
)
|
438
|
+
else:
|
439
|
+
changed = True
|
440
|
+
if changed:
|
441
|
+
self.sigUpdated.emit()
|
442
|
+
|
443
|
+
def dropEvent(self, event):
|
444
|
+
if event.mimeData().hasFormat("text/uri-list"):
|
445
|
+
for url in event.mimeData().urls():
|
446
|
+
self.addScanFromNxFile(file_=str(url.path()), entry=None)
|
447
|
+
|
448
|
+
def supportedDropActions(self):
|
449
|
+
"""Inherited method to redefine supported drop actions."""
|
450
|
+
return qt.Qt.CopyAction | qt.Qt.MoveAction
|
451
|
+
|
452
|
+
def dragEnterEvent(self, event):
|
453
|
+
if event.mimeData().hasFormat("text/uri-list"):
|
454
|
+
event.accept()
|
455
|
+
event.setDropAction(qt.Qt.CopyAction)
|
456
|
+
else:
|
457
|
+
qt.QListWidget.dragEnterEvent(self, event)
|
458
|
+
|
459
|
+
def dragMoveEvent(self, event):
|
460
|
+
if event.mimeData().hasFormat("text/uri-list"):
|
461
|
+
event.setDropAction(qt.Qt.CopyAction)
|
462
|
+
event.accept()
|
463
|
+
else:
|
464
|
+
qt.QListWidget.dragMoveEvent(self, event)
|
465
|
+
|
466
|
+
|
467
|
+
class SerieManualControlDialog(qt.QDialog):
|
468
|
+
"""
|
469
|
+
Same as the :class:`SerieManualDefinitionDialog` but with control of the serie.
|
470
|
+
This include a `create serie` and a `create serie and clear button`
|
471
|
+
"""
|
472
|
+
|
473
|
+
sigSerieSend = qt.Signal(Serie)
|
474
|
+
|
475
|
+
def __init__(self, parent=None) -> None:
|
476
|
+
super().__init__(parent)
|
477
|
+
self.setLayout(qt.QVBoxLayout())
|
478
|
+
self._mainWidget = SerieManualDefinitionDialog(parent=self)
|
479
|
+
self._mainWidget.setWindowFlags(qt.Qt.Widget)
|
480
|
+
self.layout().addWidget(self._mainWidget)
|
481
|
+
|
482
|
+
self._buttons = qt.QDialogButtonBox(parent=self)
|
483
|
+
self._createButton = qt.QPushButton("create serie", parent=self)
|
484
|
+
self._buttons.addButton(self._createButton, qt.QDialogButtonBox.ActionRole)
|
485
|
+
|
486
|
+
# connect signal / slot
|
487
|
+
self._createButton.clicked.connect(self._sendSerie)
|
488
|
+
self.layout().addWidget(self._buttons)
|
489
|
+
|
490
|
+
# expose API
|
491
|
+
self.sigUpdated = self._mainWidget.sigUpdated
|
492
|
+
|
493
|
+
def _sendSerie(self):
|
494
|
+
self.sigSerieSend.emit(self._mainWidget.getSerie())
|
495
|
+
|
496
|
+
@property
|
497
|
+
def n_tomo_objs(self):
|
498
|
+
return self._mainWidget.n_tomo_objs
|
499
|
+
|
500
|
+
def setSerieName(self, name: str):
|
501
|
+
self._mainWidget.setSerieName(name=name)
|
502
|
+
|
503
|
+
def getSerieName(self) -> str:
|
504
|
+
return self._mainWidget.getSerieName()
|
505
|
+
|
506
|
+
def setSerie(self, serie: Serie) -> None:
|
507
|
+
self._mainWidget.setSerie(serie)
|
508
|
+
|
509
|
+
def getSerie(self, *args, **kwargs) -> Serie:
|
510
|
+
return self._mainWidget.getSerie(*args, **kwargs)
|
511
|
+
|
512
|
+
def addScanFromNxFile(self, file_: str, entry: Optional[str] = None):
|
513
|
+
return self._mainWidget.addScanFromNxFile(file_=file_, entry=entry)
|
514
|
+
|
515
|
+
def removeSelectedScans(self) -> None:
|
516
|
+
return self._mainWidget.removeSelectedTomoObjs()
|
517
|
+
|
518
|
+
def getSelectedScans(self) -> tuple:
|
519
|
+
return self._mainWidget.getSelectedTomoObjs()
|
520
|
+
|
521
|
+
def setSelectedScans(self, scans: Iterable) -> None:
|
522
|
+
self._mainWidget.setSelectedTomoObjs(scans=scans)
|
523
|
+
|
524
|
+
def addScan(self, scan: TomwerScanBase) -> None:
|
525
|
+
self._mainWidget.addTomoObj(scan=scan)
|
526
|
+
|
527
|
+
def removeScan(self, scan: TomwerScanBase) -> None:
|
528
|
+
self._mainWidget.removeTomoObj(scan=scan)
|
529
|
+
|
530
|
+
def clearSerie(self) -> None:
|
531
|
+
self._mainWidget.clearSerie()
|
532
|
+
|
533
|
+
def createSerie(self):
|
534
|
+
self.sigSerieSend.emit(self.getSerie())
|
535
|
+
|
536
|
+
|
537
|
+
class SerieManualFromTomoObj(qt.QWidget):
|
538
|
+
def __init__(self, parent=None) -> None:
|
539
|
+
super().__init__(parent)
|
540
|
+
style = qt.QApplication.style()
|
541
|
+
self.setLayout(qt.QGridLayout())
|
542
|
+
|
543
|
+
self._tomoObjList = TomoObjList(self)
|
544
|
+
self.layout().addWidget(self._tomoObjList, 0, 0, 4, 2)
|
545
|
+
|
546
|
+
# right arrow
|
547
|
+
self._rightArrowButton = qt.QPushButton(self)
|
548
|
+
rightArrowIcon = style.standardIcon(qt.QStyle.SP_ArrowRight)
|
549
|
+
self._rightArrowButton.setIcon(rightArrowIcon)
|
550
|
+
self._rightArrowButton.setFixedWidth(30)
|
551
|
+
self.layout().addWidget(self._rightArrowButton, 1, 2, 1, 1)
|
552
|
+
|
553
|
+
# left arrow
|
554
|
+
self._leftArrowButton = qt.QPushButton(self)
|
555
|
+
leftArrowIcon = style.standardIcon(qt.QStyle.SP_ArrowLeft)
|
556
|
+
self._leftArrowButton.setIcon(leftArrowIcon)
|
557
|
+
self._leftArrowButton.setFixedWidth(30)
|
558
|
+
self.layout().addWidget(self._leftArrowButton, 2, 2, 1, 1)
|
559
|
+
|
560
|
+
# new serie
|
561
|
+
self._newSerieWidget = NewSerieWidget(self)
|
562
|
+
self.layout().addWidget(self._newSerieWidget, 0, 3, 4, 2)
|
563
|
+
|
564
|
+
# tomo obj details
|
565
|
+
self._tomoObjInfos = TomoObjOverview(self)
|
566
|
+
self._tomoObjInfos.setContentsMargins(0, 0, 0, 0)
|
567
|
+
self.layout().addWidget(self._tomoObjInfos, 4, 0, 2, 2)
|
568
|
+
|
569
|
+
# connect signals / slot
|
570
|
+
self._leftArrowButton.released.connect(self._removeSelectedObjs)
|
571
|
+
self._rightArrowButton.released.connect(self._addSelectedObjs)
|
572
|
+
self._tomoObjList.selectionModel().selectionChanged.connect(
|
573
|
+
self._updateTomoObjInfos
|
574
|
+
)
|
575
|
+
|
576
|
+
def selectedTomoObjects(self) -> tuple:
|
577
|
+
"""
|
578
|
+
:return: tuple of tomo object selected on the list
|
579
|
+
:rtype: tuple
|
580
|
+
"""
|
581
|
+
items = self._tomoObjList.selectedItems()
|
582
|
+
return [item.data(qt.Qt.UserRole) for item in items]
|
583
|
+
|
584
|
+
def _removeSelectedObjs(self, *args, **kwargs):
|
585
|
+
for tomo_obj in self.selectedTomoObjects():
|
586
|
+
self._newSerieWidget.removeTomoObjToCurrentSerie(tomo_obj)
|
587
|
+
|
588
|
+
def _addSelectedObjs(self, *args, **kwargs):
|
589
|
+
for tomo_obj in self.selectedTomoObjects():
|
590
|
+
self._newSerieWidget.addTomoObjToCurrentSerie(tomo_obj)
|
591
|
+
|
592
|
+
def _updateTomoObjInfos(self, *args, **kwargs):
|
593
|
+
# should
|
594
|
+
select_objs = self._tomoObjList.selectedItems()
|
595
|
+
if select_objs and len(select_objs) > 0:
|
596
|
+
tomo_obj = select_objs[0].data(qt.Qt.UserRole)
|
597
|
+
self._tomoObjInfos.setTomoObj(tomo_obj)
|
598
|
+
else:
|
599
|
+
self._tomoObjInfos.setTomoObj(None)
|
600
|
+
|
601
|
+
# expose API
|
602
|
+
def setSerie(self, serie: Serie):
|
603
|
+
self._newSerieWidget.setSerie(serie=serie)
|
604
|
+
|
605
|
+
def getSerie(self, *args, **kwargs) -> Serie:
|
606
|
+
return self._newSerieWidget.getSerie(*args, **kwargs)
|
607
|
+
|
608
|
+
def addTomoObj(self, tomo_obj):
|
609
|
+
self._tomoObjList.add(tomo_obj)
|
610
|
+
|
611
|
+
def addToCurrentSerie(self, tomo_obj):
|
612
|
+
self._newSerieWidget.addTomoObjToCurrentSerie(tomo_obj)
|
613
|
+
|
614
|
+
def setSerieName(self, name: str):
|
615
|
+
self._newSerieWidget.setSerieName(name=name)
|
616
|
+
|
617
|
+
|
618
|
+
class NewSerieWidget(qt.QWidget):
|
619
|
+
sigNameChanged = qt.Signal()
|
620
|
+
"""Emit when serie name changed"""
|
621
|
+
|
622
|
+
sigUpdated = qt.Signal()
|
623
|
+
"""
|
624
|
+
Emit when the serie has been updated by the tree
|
625
|
+
"""
|
626
|
+
|
627
|
+
DEFAULT_SERIE_NAME = "my_serie"
|
628
|
+
|
629
|
+
def __init__(self, parent=None) -> None:
|
630
|
+
super().__init__(parent)
|
631
|
+
self.setLayout(qt.QVBoxLayout())
|
632
|
+
|
633
|
+
self._nameWidget = qt.QWidget(self)
|
634
|
+
self._nameWidget.setLayout(qt.QHBoxLayout())
|
635
|
+
self._nameWidget.layout().addWidget(qt.QLabel("serie name", self))
|
636
|
+
self._nameQLE = qt.QLineEdit(self.DEFAULT_SERIE_NAME, self)
|
637
|
+
self._nameWidget.layout().addWidget(self._nameQLE)
|
638
|
+
self.layout().addWidget(self._nameWidget)
|
639
|
+
|
640
|
+
self._serieTree = _SerieDefinitionTree(self, serie_name=self.DEFAULT_SERIE_NAME)
|
641
|
+
self.layout().addWidget(self._serieTree)
|
642
|
+
|
643
|
+
# Signal / slot connection
|
644
|
+
self._serieTree.itemChanged.connect(self._handleItemUpdate)
|
645
|
+
self._serieTree.sigUpdated.connect(self._repeatUpdateSignal)
|
646
|
+
self._nameQLE.textChanged.connect(self._nameChangedOnQLE)
|
647
|
+
|
648
|
+
def setSerie(self, serie: Serie) -> None:
|
649
|
+
with blockSignals(self._nameQLE):
|
650
|
+
self._nameQLE.setText(serie.name)
|
651
|
+
self._serieTree.setSerie(serie=serie)
|
652
|
+
|
653
|
+
def getSerie(self, *args, **kwargs) -> Serie:
|
654
|
+
return self._serieTree.getSerie(*args, **kwargs)
|
655
|
+
|
656
|
+
def _nameChangedOnQLE(self, name):
|
657
|
+
with blockSignals(self._serieTree):
|
658
|
+
self._serieTree.setSerieName(name)
|
659
|
+
self.sigNameChanged.emit()
|
660
|
+
|
661
|
+
def _handleItemUpdate(self, item, column):
|
662
|
+
if item == self._serieTree.rootItem:
|
663
|
+
old = self.blockSignals(True)
|
664
|
+
self._nameQLE.setText(self._serieTree.rootItem.text(0))
|
665
|
+
self.blockSignals(old)
|
666
|
+
self.sigUpdated.emit()
|
667
|
+
|
668
|
+
def _repeatUpdateSignal(self):
|
669
|
+
self.sigUpdated.emit()
|
670
|
+
|
671
|
+
def addTomoObjToCurrentSerie(self, tomo_obj: TomwerObject):
|
672
|
+
assert isinstance(
|
673
|
+
tomo_obj, TomwerObject
|
674
|
+
), f"invalid type {type(tomo_obj)}. {TomwerObject} expected"
|
675
|
+
self._serieTree.addTomoObj(tomo_obj)
|
676
|
+
|
677
|
+
def removeTomoObjToCurrentSerie(self, tomo_obj: TomwerObject):
|
678
|
+
assert isinstance(
|
679
|
+
tomo_obj, TomwerObject
|
680
|
+
), f"invalid type {type(tomo_obj)}. {TomwerObject} expected"
|
681
|
+
self._serieTree.removeTomoObj(tomo_obj)
|
682
|
+
|
683
|
+
def getSerieName(self) -> str:
|
684
|
+
return self._serieTree.getSerieName()
|
685
|
+
|
686
|
+
def setSerieName(self, name: str):
|
687
|
+
self._serieTree.setSerieName(name=name)
|
688
|
+
|
689
|
+
|
690
|
+
class SerieManualDefinitionDialog(qt.QDialog):
|
691
|
+
"""Dialog to define a serie manually"""
|
692
|
+
|
693
|
+
sigUpdated = qt.Signal()
|
694
|
+
|
695
|
+
def __init__(self, parent=None) -> None:
|
696
|
+
super().__init__(parent)
|
697
|
+
self.setLayout(qt.QVBoxLayout())
|
698
|
+
|
699
|
+
self._newSerieWidget = NewSerieWidget(self)
|
700
|
+
|
701
|
+
self._buttons = qt.QDialogButtonBox(parent=self)
|
702
|
+
|
703
|
+
self._addScanButton = qt.QPushButton("Add scan to the serie", parent=self)
|
704
|
+
self._buttons.addButton(self._addScanButton, qt.QDialogButtonBox.ActionRole)
|
705
|
+
|
706
|
+
self._removeSelectedButton = qt.QPushButton(
|
707
|
+
"Remove selected scans", parent=self
|
708
|
+
)
|
709
|
+
self._buttons.addButton(
|
710
|
+
self._removeSelectedButton, qt.QDialogButtonBox.ActionRole
|
711
|
+
)
|
712
|
+
|
713
|
+
self._clearButton = qt.QPushButton("Clear", parent=self)
|
714
|
+
self._buttons.addButton(self._clearButton, qt.QDialogButtonBox.ActionRole)
|
715
|
+
|
716
|
+
self.layout().addWidget(self._buttons)
|
717
|
+
|
718
|
+
# connect signal / slot
|
719
|
+
self._newSerieWidget.sigNameChanged.connect(self._nameChanged)
|
720
|
+
self._newSerieWidget.sigUpdated.connect(self._repeatUpdateSignal)
|
721
|
+
self._addScanButton.clicked.connect(self.addScanFromFileDialog)
|
722
|
+
self._removeSelectedButton.clicked.connect(self.removeSelectedTomoObjs)
|
723
|
+
self._clearButton.clicked.connect(self.clearSerie)
|
724
|
+
|
725
|
+
@property
|
726
|
+
def n_tomo_objs(self):
|
727
|
+
serieTree = self._newSerieWidget._serieTree
|
728
|
+
return serieTree.n_tomo_objs
|
729
|
+
|
730
|
+
def _nameChanged(self, new_name):
|
731
|
+
serieTree = self._newSerieWidget._serieTree
|
732
|
+
with blockSignals(self._serieTree):
|
733
|
+
serieTree.setSerieName(name=new_name)
|
734
|
+
self.sigUpdated.emit()
|
735
|
+
|
736
|
+
def _repeatUpdateSignal(self):
|
737
|
+
self.sigUpdated.emit()
|
738
|
+
|
739
|
+
def setSerieName(self, name: str):
|
740
|
+
self._newSerieWidget.setSerieName(name=name)
|
741
|
+
|
742
|
+
def getSerieName(self) -> str:
|
743
|
+
return self._newSerieWidget.getSerieName()
|
744
|
+
|
745
|
+
def setSerie(self, serie: Serie) -> None:
|
746
|
+
self._newSerieWidget.setSerie(serie)
|
747
|
+
|
748
|
+
def getSerie(self, *args, **kwargs) -> Serie:
|
749
|
+
return self._newSerieWidget.getSerie(*args, **kwargs)
|
750
|
+
|
751
|
+
def addScanFromFileDialog(self) -> None:
|
752
|
+
dialog = QDataDialog(self, multiSelection=True)
|
753
|
+
|
754
|
+
if not dialog.exec_():
|
755
|
+
dialog.close()
|
756
|
+
return
|
757
|
+
|
758
|
+
foldersSelected = dialog.files_selected()
|
759
|
+
for folder in foldersSelected:
|
760
|
+
self.addScanFromNxFile(file_=folder, entry=None)
|
761
|
+
|
762
|
+
def addScanFromNxFile(self, file_: str, entry: Optional[str] = None):
|
763
|
+
serieTree = self._newSerieWidget._serieTree
|
764
|
+
return serieTree.addScanFromNxFile(file_=file_, entry=entry)
|
765
|
+
|
766
|
+
def removeSelectedTomoObjs(self) -> None:
|
767
|
+
serieTree = self._newSerieWidget._serieTree
|
768
|
+
return serieTree.removeSelectedTomoObjs()
|
769
|
+
|
770
|
+
def getSelectedTomoObjs(self) -> tuple:
|
771
|
+
serieTree = self._newSerieWidget._serieTree
|
772
|
+
return serieTree.getSelectedTomoObjs()
|
773
|
+
|
774
|
+
def setSelectedTomoObjs(self, scans: Iterable) -> None:
|
775
|
+
serieTree = self._newSerieWidget._serieTree
|
776
|
+
serieTree.setSelectedTomoObjs(objs=scans)
|
777
|
+
|
778
|
+
def addTomoObj(self, scan: TomwerScanBase) -> None:
|
779
|
+
serieTree = self._newSerieWidget._serieTree
|
780
|
+
return serieTree.addTomoObj(tomo_obj=scan)
|
781
|
+
|
782
|
+
def removeTomoObj(self, scan: TomwerScanBase) -> None:
|
783
|
+
serieTree = self._newSerieWidget._serieTree
|
784
|
+
serieTree.removeTomoObj(tomo_obj=scan)
|
785
|
+
|
786
|
+
def clearSerie(self) -> None:
|
787
|
+
serieTree = self._newSerieWidget._serieTree
|
788
|
+
serieTree.clearTomoObjs()
|
789
|
+
|
790
|
+
|
791
|
+
class SerieAutomaticDefinitionWidget(qt.QWidget):
|
792
|
+
pass
|
793
|
+
|
794
|
+
|
795
|
+
class SerieTree(qt.QWidget):
|
796
|
+
"""
|
797
|
+
Widget used to define a scan serie from a list of scans.
|
798
|
+
"""
|
799
|
+
|
800
|
+
def __init__(self, parent=None, scans=tuple()) -> None:
|
801
|
+
self._series = {}
|
802
|
+
# associated serie name (key) to tuple (serie, QTreeWidgetItem)
|
803
|
+
super().__init__(parent)
|
804
|
+
self.setLayout(qt.QVBoxLayout())
|
805
|
+
self._tree = qt.QTreeWidget(self)
|
806
|
+
self._tree.setSelectionMode(qt.QAbstractItemView.MultiSelection)
|
807
|
+
self._tree.setColumnCount(2)
|
808
|
+
self._tree.setHeaderLabels(("serie", "scan ids"))
|
809
|
+
self.layout().addWidget(self._tree)
|
810
|
+
|
811
|
+
# set up
|
812
|
+
[self.addSerie(scan) for scan in scans]
|
813
|
+
|
814
|
+
def addSerie(self, serie: Serie):
|
815
|
+
if not isinstance(serie, Serie):
|
816
|
+
raise TypeError(
|
817
|
+
f"{serie} is expected to be an instance of {Serie} not {type(serie)}"
|
818
|
+
)
|
819
|
+
if serie.name in self._series:
|
820
|
+
self.removeSerie(self._series[serie.name][0])
|
821
|
+
|
822
|
+
root_item = qt.QTreeWidgetItem(self._tree)
|
823
|
+
root_item.setText(0, serie.name)
|
824
|
+
self._series[serie.name] = (serie, root_item)
|
825
|
+
for obj in serie:
|
826
|
+
scan_item = qt.QTreeWidgetItem(root_item)
|
827
|
+
if isinstance(obj, TomwerObject):
|
828
|
+
text = obj.get_identifier().to_str()
|
829
|
+
else:
|
830
|
+
text = obj
|
831
|
+
scan_item.setText(1, text)
|
832
|
+
scan_item.setFlags(qt.Qt.NoItemFlags)
|
833
|
+
|
834
|
+
def removeSerie(self, serie: Serie):
|
835
|
+
if not isinstance(serie, Serie):
|
836
|
+
raise TypeError(
|
837
|
+
f"{serie} is expected to be an instance of {Serie} not {type(serie)}"
|
838
|
+
)
|
839
|
+
if serie.name in self._series:
|
840
|
+
_, serie_item = self._series.pop(serie.name)
|
841
|
+
root = self._tree.invisibleRootItem()
|
842
|
+
root.removeChild(serie_item)
|
843
|
+
|
844
|
+
@property
|
845
|
+
def n_series(self):
|
846
|
+
return len(self._series)
|
847
|
+
|
848
|
+
def series(self) -> tuple:
|
849
|
+
series = []
|
850
|
+
[series.append(serie) for serie, _ in self._series.values()]
|
851
|
+
return tuple(series)
|
852
|
+
|
853
|
+
def clearSelection(self):
|
854
|
+
self._tree.selectionModel().clearSelection()
|
855
|
+
|
856
|
+
def setSelectedSeries(self, series):
|
857
|
+
self.clearSelection()
|
858
|
+
for serie in series:
|
859
|
+
serie_item = self._getSerieItem(serie)
|
860
|
+
if serie_item is not None:
|
861
|
+
serie_item.setSelected(True)
|
862
|
+
|
863
|
+
def _getSerieItem(self, serie: Serie) -> Optional[qt.QTreeWidgetItem]:
|
864
|
+
if not isinstance(serie, Serie):
|
865
|
+
raise TypeError(
|
866
|
+
f"serie is expected to be an instance of {Serie} not {type(serie)}"
|
867
|
+
)
|
868
|
+
return self._series.get(serie.name, (None, None))[1]
|
869
|
+
|
870
|
+
def getSelectedSeries(self) -> tuple():
|
871
|
+
"""return selected series"""
|
872
|
+
selected = []
|
873
|
+
for _, (serie, item) in self._series.items():
|
874
|
+
if item.isSelected():
|
875
|
+
selected.append(serie)
|
876
|
+
return tuple(selected)
|
877
|
+
|
878
|
+
def removeSelected(self) -> None:
|
879
|
+
for serie in self.getSelectedSeries():
|
880
|
+
self.removeSerie(serie)
|
881
|
+
|
882
|
+
|
883
|
+
class SerieHistoryDialog(qt.QDialog):
|
884
|
+
sigSerieSend = qt.Signal(Serie)
|
885
|
+
"""signal emit when a serie has been selected by the user"""
|
886
|
+
|
887
|
+
sigEditSerie = qt.Signal(Serie)
|
888
|
+
"""Signal emit when user request to edit a serie"""
|
889
|
+
|
890
|
+
sigHistoryUpdated = qt.Signal()
|
891
|
+
"""Signal emit when the history has been modified"""
|
892
|
+
|
893
|
+
def __init__(self, parent=None) -> None:
|
894
|
+
super().__init__(parent)
|
895
|
+
self.setLayout(qt.QVBoxLayout())
|
896
|
+
|
897
|
+
self._serieList = SerieTree(self)
|
898
|
+
self.layout().addWidget(self._serieList)
|
899
|
+
|
900
|
+
self._buttons = qt.QDialogButtonBox(parent=self)
|
901
|
+
|
902
|
+
self._editButton = qt.QPushButton("Edit", parent=self)
|
903
|
+
self._buttons.addButton(self._editButton, qt.QDialogButtonBox.ActionRole)
|
904
|
+
|
905
|
+
self._sendButton = qt.QPushButton("Resend", parent=self)
|
906
|
+
self._buttons.addButton(self._sendButton, qt.QDialogButtonBox.ActionRole)
|
907
|
+
|
908
|
+
self._removeButton = qt.QPushButton("Remove", parent=self)
|
909
|
+
self._buttons.addButton(self._removeButton, qt.QDialogButtonBox.ActionRole)
|
910
|
+
|
911
|
+
self._clearButton = qt.QPushButton("Clear", parent=self)
|
912
|
+
self._buttons.addButton(self._clearButton, qt.QDialogButtonBox.ActionRole)
|
913
|
+
|
914
|
+
self.layout().addWidget(self._buttons)
|
915
|
+
|
916
|
+
# connect signal / slot
|
917
|
+
self._sendButton.clicked.connect(self.sendSelected)
|
918
|
+
self._removeButton.clicked.connect(self.removeSelected)
|
919
|
+
self._clearButton.clicked.connect(self.clearSelection)
|
920
|
+
self._editButton.clicked.connect(self.editSelected)
|
921
|
+
|
922
|
+
def addSerie(self, serie: Serie):
|
923
|
+
old = self.blockSignals(True)
|
924
|
+
self._serieList.addSerie(serie)
|
925
|
+
self.blockSignals(old)
|
926
|
+
self.sigHistoryUpdated.emit()
|
927
|
+
|
928
|
+
def removeSerie(self, serie: Serie):
|
929
|
+
old = self.blockSignals(True)
|
930
|
+
self._serieList.removeSerie(serie)
|
931
|
+
self.blockSignals(old)
|
932
|
+
self.sigHistoryUpdated.emit()
|
933
|
+
|
934
|
+
def getSelectedSeries(self):
|
935
|
+
return self._serieList.getSelectedSeries()
|
936
|
+
|
937
|
+
def setSelectedSeries(self, series):
|
938
|
+
old = self.blockSignals(True)
|
939
|
+
self._serieList.setSelectedSeries(series)
|
940
|
+
self.blockSignals(old)
|
941
|
+
self.sigHistoryUpdated.emit()
|
942
|
+
|
943
|
+
def sendSelected(self):
|
944
|
+
for serie in self.getSelectedSeries():
|
945
|
+
self.sigSerieSend.emit(serie)
|
946
|
+
|
947
|
+
def editSelected(self):
|
948
|
+
selected = self.getSelectedSeries()
|
949
|
+
if len(selected) == 0:
|
950
|
+
return
|
951
|
+
if len(selected) > 1:
|
952
|
+
_logger.warning(
|
953
|
+
"more than one serie selected for edition. Will only edit the first one"
|
954
|
+
)
|
955
|
+
self.sigEditSerie.emit(selected[0])
|
956
|
+
|
957
|
+
def removeSelected(self):
|
958
|
+
old = self.blockSignals(True)
|
959
|
+
self._serieList.removeSelected()
|
960
|
+
self.blockSignals(old)
|
961
|
+
self.sigHistoryUpdated.emit()
|
962
|
+
|
963
|
+
def clearSelection(self):
|
964
|
+
self._serieList.clearSelection()
|
965
|
+
|
966
|
+
def series(self) -> tuple:
|
967
|
+
return self._serieList.series()
|