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.
Files changed (269) hide show
  1. orangecontrib/tomwer/tutorials/EBS_tomo_listener.ows +39 -0
  2. orangecontrib/tomwer/tutorials/cast_volume.ows +34 -0
  3. orangecontrib/tomwer/tutorials/simple_slice_reconstruction.ows +39 -0
  4. orangecontrib/tomwer/tutorials/simple_volume_local_reconstruction.ows +49 -0
  5. orangecontrib/tomwer/tutorials/simple_volume_to_slurm_reconstruction.ows +59 -0
  6. orangecontrib/tomwer/tutorials/using_saaxis_to_find_cor.ows +44 -0
  7. orangecontrib/tomwer/widgets/cluster/FutureSupervisorOW.py +1 -1
  8. orangecontrib/tomwer/widgets/cluster/SlurmClusterOW.py +14 -4
  9. orangecontrib/tomwer/widgets/cluster/__init__.py +1 -1
  10. orangecontrib/tomwer/widgets/control/DataListOW.py +12 -5
  11. orangecontrib/tomwer/widgets/control/DataListenerOW.py +18 -9
  12. orangecontrib/tomwer/widgets/control/DataSelectorOW.py +13 -6
  13. orangecontrib/tomwer/widgets/control/DataTransfertOW.py +3 -5
  14. orangecontrib/tomwer/widgets/control/DataValidatorOW.py +8 -4
  15. orangecontrib/tomwer/widgets/control/DataWatcherOW.py +4 -6
  16. orangecontrib/tomwer/widgets/control/EDF2NXTomomillOW.py +49 -62
  17. orangecontrib/tomwer/widgets/control/FilterOW.py +2 -4
  18. orangecontrib/tomwer/widgets/control/NXTomomillMixIn.py +93 -0
  19. orangecontrib/tomwer/widgets/control/NXTomomillOW.py +135 -129
  20. orangecontrib/tomwer/widgets/control/NotifierOW.py +34 -9
  21. orangecontrib/tomwer/widgets/control/SingleTomoObjOW.py +3 -5
  22. orangecontrib/tomwer/widgets/control/TomoObjSerieOW.py +19 -13
  23. orangecontrib/tomwer/widgets/control/VolumeSelector.py +12 -4
  24. orangecontrib/tomwer/widgets/control/VolumeSymLinkOW.py +11 -7
  25. orangecontrib/tomwer/widgets/control/icons/notification.svg +4 -4
  26. orangecontrib/tomwer/widgets/control/icons/nxtomomill.png +0 -0
  27. orangecontrib/tomwer/widgets/control/icons/nxtomomill.svg +8 -5
  28. orangecontrib/tomwer/widgets/control/icons/tomoobjserie.png +0 -0
  29. orangecontrib/tomwer/widgets/control/icons/tomoobjserie.svg +73 -78
  30. orangecontrib/tomwer/widgets/edit/DarkFlatPatchOW.py +16 -4
  31. orangecontrib/tomwer/widgets/edit/NXtomoEditorOW.py +100 -0
  32. orangecontrib/tomwer/widgets/edit/icons/image_key_editor.png +0 -0
  33. orangecontrib/tomwer/widgets/edit/icons/image_key_upgrader.png +0 -0
  34. orangecontrib/tomwer/widgets/edit/icons/nx_tomo_editor.png +0 -0
  35. orangecontrib/tomwer/widgets/edit/icons/nx_tomo_editor.svg +123 -0
  36. orangecontrib/tomwer/widgets/edit/test/test_dark_flat_patch.py +21 -1
  37. orangecontrib/tomwer/widgets/edit/test/test_image_key_editor.py +1 -1
  38. orangecontrib/tomwer/widgets/edit/test/test_image_key_upgrader.py +1 -1
  39. orangecontrib/tomwer/widgets/edit/test/test_nxtomo_editor.py +25 -0
  40. orangecontrib/tomwer/widgets/other/PythonScriptOW.py +19 -11
  41. orangecontrib/tomwer/widgets/reconstruction/AxisOW.py +20 -14
  42. orangecontrib/tomwer/widgets/reconstruction/CastNabuVolumeOW.py +24 -10
  43. orangecontrib/tomwer/widgets/reconstruction/DarkRefAndCopyOW.py +26 -21
  44. orangecontrib/tomwer/widgets/reconstruction/NabuOW.py +29 -12
  45. orangecontrib/tomwer/widgets/reconstruction/NabuVolumeOW.py +44 -17
  46. orangecontrib/tomwer/widgets/reconstruction/SAAxisOW.py +28 -20
  47. orangecontrib/tomwer/widgets/reconstruction/SADeltaBetaOW.py +24 -18
  48. orangecontrib/tomwer/widgets/reconstruction/SinoNormOW.py +6 -6
  49. orangecontrib/tomwer/widgets/reconstruction/TofuOW.py +4 -2
  50. orangecontrib/tomwer/widgets/reconstruction/icons/nabu_2d.png +0 -0
  51. orangecontrib/tomwer/widgets/reconstruction/icons/nabu_2d.svg +11 -8
  52. orangecontrib/tomwer/widgets/visualization/DataViewerOW.py +10 -4
  53. orangecontrib/tomwer/widgets/visualization/DiffViewerOW.py +1 -1
  54. orangecontrib/tomwer/widgets/visualization/NXtomoMetadataViewerOW.py +69 -0
  55. orangecontrib/tomwer/widgets/visualization/SampleMovedOW.py +2 -4
  56. orangecontrib/tomwer/widgets/visualization/icons/nx_tomo_metadata_viewer.png +0 -0
  57. orangecontrib/tomwer/widgets/visualization/icons/nx_tomo_metadata_viewer.svg +105 -0
  58. tomwer/__main__.py +10 -5
  59. tomwer/app/canvas_launcher/config.py +10 -10
  60. tomwer/app/canvas_launcher/mainwindow.py +68 -6
  61. tomwer/app/canvas_launcher/widgetsscheme.py +1 -3
  62. tomwer/app/darkref.py +16 -12
  63. tomwer/app/imagekeyeditor.py +2 -2
  64. tomwer/app/imagekeyupgrader.py +104 -0
  65. tomwer/app/intensitynormalization.py +0 -1
  66. tomwer/app/nxtomoeditor.py +103 -0
  67. tomwer/app/rsync.py +1 -1
  68. tomwer/core/cluster/cluster.py +1 -1
  69. tomwer/core/futureobject.py +1 -0
  70. tomwer/core/process/control/datalistener/datalistener.py +7 -1
  71. tomwer/core/process/control/datalistener/rpcserver.py +3 -4
  72. tomwer/core/process/control/datawatcher/datawatcher.py +18 -18
  73. tomwer/core/process/control/datawatcher/datawatcherobserver.py +5 -8
  74. tomwer/core/process/control/datawatcher/datawatcherprocess.py +2 -3
  75. tomwer/core/process/control/datawatcher/edfdwprocess.py +2 -2
  76. tomwer/core/process/control/nxtomomill.py +33 -58
  77. tomwer/core/process/control/scanlist.py +2 -1
  78. tomwer/core/process/control/scanselector.py +7 -0
  79. tomwer/core/process/control/scantransfer.py +2 -2
  80. tomwer/core/process/control/scanvalidator.py +6 -5
  81. tomwer/core/process/control/singletomoobj.py +2 -1
  82. tomwer/core/process/control/timer.py +2 -1
  83. tomwer/core/process/control/tomoobjserie.py +8 -2
  84. tomwer/core/process/control/volumeselector.py +2 -1
  85. tomwer/core/process/control/volumesymlink.py +2 -1
  86. tomwer/core/process/edit/darkflatpatch.py +2 -1
  87. tomwer/core/process/edit/imagekeyeditor.py +4 -3
  88. tomwer/core/process/reconstruction/axis/axis.py +29 -32
  89. tomwer/core/process/reconstruction/axis/mode.py +3 -2
  90. tomwer/core/process/reconstruction/axis/params.py +35 -16
  91. tomwer/core/process/reconstruction/darkref/darkrefs.py +90 -707
  92. tomwer/core/process/reconstruction/darkref/darkrefscopy.py +44 -16
  93. tomwer/core/process/reconstruction/darkref/params.py +62 -67
  94. tomwer/core/process/reconstruction/lamino/tofu.py +1 -2
  95. tomwer/core/process/reconstruction/nabu/castvolume.py +21 -26
  96. tomwer/core/process/reconstruction/nabu/nabucommon.py +36 -38
  97. tomwer/core/process/reconstruction/nabu/nabuscores.py +28 -13
  98. tomwer/core/process/reconstruction/nabu/nabuslices.py +41 -14
  99. tomwer/core/process/reconstruction/nabu/nabuvolume.py +21 -12
  100. tomwer/core/process/reconstruction/nabu/utils.py +32 -3
  101. tomwer/core/process/reconstruction/normalization/normalization.py +9 -8
  102. tomwer/core/process/reconstruction/saaxis/saaxis.py +46 -20
  103. tomwer/core/process/reconstruction/sadeltabeta/sadeltabeta.py +38 -12
  104. tomwer/core/process/reconstruction/test/__init__.py +0 -39
  105. tomwer/core/process/reconstruction/test/test_axis_params.py +25 -3
  106. tomwer/core/process/reconstruction/test/test_darkref_copy.py +117 -1
  107. tomwer/core/process/script/python.py +16 -12
  108. tomwer/core/process/task.py +3 -7
  109. tomwer/core/process/test/test_axis.py +1 -1
  110. tomwer/core/process/test/test_dark_and_flat.py +41 -111
  111. tomwer/core/process/test/test_data_listener.py +0 -29
  112. tomwer/core/process/test/test_data_transfer.py +10 -14
  113. tomwer/core/process/test/test_nabu.py +1 -1
  114. tomwer/core/process/test/test_normalization.py +1 -1
  115. tomwer/core/process/visualization/liveslice.py +6 -0
  116. tomwer/core/scan/blissscan.py +37 -2
  117. tomwer/core/scan/edfscan.py +19 -8
  118. tomwer/core/scan/hdf5scan.py +10 -4
  119. tomwer/core/scan/scanbase.py +35 -29
  120. tomwer/core/scan/scanfactory.py +3 -17
  121. tomwer/core/scan/test/test_h5.py +1 -1
  122. tomwer/core/scan/test/test_process_registration.py +0 -11
  123. tomwer/core/scan/test/test_scan.py +32 -30
  124. tomwer/core/settings.py +2 -2
  125. tomwer/core/test/test_utils.py +1 -1
  126. tomwer/core/tomwer_object.py +19 -0
  127. tomwer/core/utils/__init__.py +0 -45
  128. tomwer/core/utils/char.py +2 -0
  129. tomwer/core/utils/gpu.py +5 -5
  130. tomwer/core/utils/nxtomoutils.py +2 -2
  131. tomwer/core/utils/scanutils.py +50 -0
  132. tomwer/core/utils/volumeutils.py +13 -0
  133. tomwer/core/volume/edfvolume.py +4 -0
  134. tomwer/core/volume/hdf5volume.py +4 -0
  135. tomwer/core/volume/jp2kvolume.py +4 -0
  136. tomwer/core/volume/rawvolume.py +22 -5
  137. tomwer/core/volume/tiffvolume.py +4 -0
  138. tomwer/core/volume/volumebase.py +19 -12
  139. tomwer/core/volume/volumefactory.py +20 -1
  140. tomwer/gui/cluster/slurm.py +1 -1
  141. tomwer/gui/cluster/supervisor.py +0 -2
  142. tomwer/gui/cluster/test/test_cluster.py +2 -2
  143. tomwer/gui/control/datalist.py +109 -36
  144. tomwer/gui/control/datatransfert.py +1 -1
  145. tomwer/gui/control/datawatcher/configuration.py +0 -2
  146. tomwer/gui/control/datawatcher/datawatcher.py +23 -13
  147. tomwer/gui/control/datawatcher/datawatcherobserver.py +1 -1
  148. tomwer/gui/control/observations.py +0 -3
  149. tomwer/gui/control/selectorwidgetbase.py +42 -12
  150. tomwer/gui/control/serie/seriecreator.py +967 -0
  151. tomwer/{web/__init__.py → gui/control/serie/seriewaiter.py} +5 -7
  152. tomwer/gui/control/singletomoobj.py +15 -4
  153. tomwer/gui/control/test/test_datalist.py +1 -1
  154. tomwer/gui/control/test/test_datalistener.py +1 -1
  155. tomwer/gui/control/test/test_inputwidget.py +1 -1
  156. tomwer/gui/control/test/test_process_manager.py +1 -13
  157. tomwer/gui/control/test/test_scanselector.py +1 -1
  158. tomwer/gui/control/test/test_scanvalidator.py +1 -1
  159. tomwer/gui/control/test/test_single_tomo_obj.py +1 -1
  160. tomwer/gui/control/test/test_volume_dialog.py +19 -7
  161. tomwer/gui/control/test/test_volumeselector.py +4 -4
  162. tomwer/gui/debugtools/datasetgenerator.py +1 -9
  163. tomwer/gui/edit/dkrfpatch.py +2 -3
  164. tomwer/gui/edit/imagekeyeditor.py +12 -11
  165. tomwer/gui/edit/nxtomoeditor.py +475 -0
  166. tomwer/gui/edit/test/test_dkrf_patch.py +2 -14
  167. tomwer/gui/edit/test/test_image_key_editor.py +2 -2
  168. tomwer/gui/edit/test/test_nx_editor.py +155 -0
  169. tomwer/gui/icons.py +0 -1
  170. tomwer/gui/qfolderdialog.py +11 -0
  171. tomwer/gui/reconstruction/axis/CompareImages.py +27 -29
  172. tomwer/gui/reconstruction/axis/axis.py +2 -0
  173. tomwer/gui/reconstruction/axis/radioaxis.py +70 -14
  174. tomwer/gui/reconstruction/darkref/darkrefcopywidget.py +7 -9
  175. tomwer/gui/reconstruction/darkref/darkrefwidget.py +22 -24
  176. tomwer/gui/reconstruction/lamino/tofu/projections.py +1 -1
  177. tomwer/gui/reconstruction/lamino/tofu/tofu.py +3 -3
  178. tomwer/gui/reconstruction/lamino/tofu/tofuexpert.py +4 -4
  179. tomwer/gui/reconstruction/lamino/tofu/tofuoutput.py +10 -5
  180. tomwer/gui/reconstruction/nabu/castvolume.py +103 -24
  181. tomwer/gui/reconstruction/nabu/check.py +1 -1
  182. tomwer/gui/reconstruction/nabu/nabuconfig/ctf.py +352 -0
  183. tomwer/gui/reconstruction/nabu/nabuconfig/nabuconfig.py +0 -9
  184. tomwer/gui/reconstruction/nabu/nabuconfig/output.py +1 -1
  185. tomwer/gui/reconstruction/nabu/nabuconfig/phase.py +18 -19
  186. tomwer/gui/reconstruction/nabu/nabuconfig/preprocessing.py +30 -7
  187. tomwer/gui/reconstruction/nabu/nabuconfig/reconstruction.py +26 -15
  188. tomwer/gui/reconstruction/nabu/slices.py +10 -4
  189. tomwer/gui/reconstruction/nabu/slurm.py +1 -1
  190. tomwer/gui/reconstruction/nabu/volume.py +13 -7
  191. tomwer/gui/reconstruction/normalization/intensity.py +1 -5
  192. tomwer/gui/reconstruction/saaxis/corrangeselector.py +10 -37
  193. tomwer/gui/reconstruction/saaxis/saaxis.py +11 -7
  194. tomwer/gui/reconstruction/saaxis/sliceselector.py +11 -26
  195. tomwer/gui/reconstruction/sadeltabeta/saadeltabeta.py +13 -8
  196. tomwer/gui/reconstruction/scores/scoreplot.py +67 -62
  197. tomwer/gui/reconstruction/test/test_axis.py +2 -2
  198. tomwer/gui/reconstruction/test/test_lamino.py +2 -2
  199. tomwer/gui/reconstruction/test/test_nabu.py +14 -1
  200. tomwer/gui/reconstruction/test/test_saaxis.py +8 -17
  201. tomwer/gui/reconstruction/test/test_sadeltabeta.py +7 -13
  202. tomwer/gui/stackplot.py +11 -28
  203. tomwer/gui/test/test_axis_gui.py +4 -4
  204. tomwer/gui/test/test_qfolder_dialog.py +12 -0
  205. tomwer/gui/utils/inputwidget.py +42 -22
  206. tomwer/gui/utils/lineselector/lineselector.py +13 -21
  207. tomwer/gui/utils/scandescription.py +2 -4
  208. tomwer/gui/utils/slider.py +1 -102
  209. tomwer/gui/utils/unitsystem.py +48 -11
  210. tomwer/gui/visualization/dataviewer.py +24 -17
  211. tomwer/gui/visualization/diffviewer/diffviewer.py +2 -11
  212. tomwer/gui/visualization/nxtomometadata.py +21 -0
  213. tomwer/gui/visualization/scanoverview.py +0 -1
  214. tomwer/gui/visualization/test/test_nx_tomo_metadata_viewer.py +72 -0
  215. tomwer/gui/visualization/test/test_stacks.py +1 -1
  216. tomwer/gui/visualization/tomoobjoverview.py +49 -0
  217. tomwer/gui/visualization/volumeoverview.py +64 -0
  218. tomwer/gui/visualization/volumeviewer.py +1 -1
  219. tomwer/io/utils/utils.py +2 -2
  220. tomwer/resources/gui/icons/multi-document-save.png +0 -0
  221. tomwer/resources/gui/icons/multi-document-save.svg +101 -0
  222. tomwer/resources/gui/illustrations/ctf_z1.png +0 -0
  223. tomwer/resources/gui/illustrations/ctf_z1.svg +471 -0
  224. tomwer/synctools/axis.py +0 -1
  225. tomwer/synctools/darkref.py +0 -1
  226. tomwer/synctools/datalistener.py +5 -1
  227. tomwer/synctools/imageloaderthread.py +2 -2
  228. tomwer/synctools/saaxis.py +0 -1
  229. tomwer/synctools/sadeltabeta.py +0 -1
  230. tomwer/synctools/stacks/edit/imagekeyeditor.py +1 -1
  231. tomwer/synctools/stacks/processingstack.py +2 -2
  232. tomwer/synctools/stacks/reconstruction/castvolume.py +1 -0
  233. tomwer/synctools/stacks/reconstruction/dkrefcopy.py +1 -1
  234. tomwer/synctools/stacks/reconstruction/lamino.py +1 -3
  235. tomwer/synctools/stacks/reconstruction/sadeltabeta.py +0 -2
  236. tomwer/synctools/test/test_darkRefs.py +32 -149
  237. tomwer/synctools/test/test_foldertransfer.py +1 -1
  238. tomwer/synctools/test/test_scanstages.py +2 -2
  239. tomwer/tests/conftest.py +51 -0
  240. tomwer/{test → tests}/test_scripts.py +1 -1
  241. tomwer/tests/test_utils.py +10 -0
  242. tomwer/{test → tests}/utils/utilstest.py +0 -11
  243. tomwer/version.py +3 -3
  244. {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/METADATA +14 -16
  245. {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/RECORD +255 -235
  246. {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/WHEEL +1 -1
  247. {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/entry_points.txt +6 -0
  248. orangecontrib/tomwer/setup.py +0 -45
  249. orangecontrib/tomwer/widgets/setup.py +0 -49
  250. tomwer/app/process.py +0 -153
  251. tomwer/core/process/reconstruction/nabu/slurm.py +0 -36
  252. tomwer/core/process/reconstruction/utils/nabu_slice_exec.py +0 -10
  253. tomwer/core/utils/laminoutils.py +0 -80
  254. tomwer/gui/utils/lineselector/lineselection.py +0 -76
  255. tomwer/setup.py +0 -52
  256. tomwer/slurm/executor.py +0 -36
  257. tomwer/slurm/job.py +0 -349
  258. tomwer/slurm/utils.py +0 -44
  259. tomwer/web/client.py +0 -43
  260. tomwer/web/config.py +0 -36
  261. tomwer/web/test/test_graylog_connection.py +0 -59
  262. {tomwer/slurm → orangecontrib/tomwer/tutorials}/__init__.py +0 -0
  263. /tomwer/{test → gui/control/serie}/__init__.py +0 -0
  264. /tomwer/{web/test → tests}/__init__.py +0 -0
  265. /tomwer/{test → tests}/utils/__init__.py +0 -0
  266. /tomwer-1.0.3-py3.8-nspkg.pth → /tomwer-1.1.0-py3.9-nspkg.pth +0 -0
  267. {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/LICENSE +0 -0
  268. {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/namespace_packages.txt +0 -0
  269. {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()