tomwer 1.2.9__py3-none-any.whl → 1.3.0a0__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 (253) hide show
  1. orangecontrib/tomwer/tutorials/icat_publication.ows +58 -0
  2. orangecontrib/tomwer/widgets/__init__.py +1 -0
  3. orangecontrib/tomwer/widgets/control/DataDiscoveryOW.py +2 -2
  4. orangecontrib/tomwer/widgets/control/DataListOW.py +9 -7
  5. orangecontrib/tomwer/widgets/control/DataSelectorOW.py +21 -10
  6. orangecontrib/tomwer/widgets/control/EDF2NXTomomillOW.py +11 -5
  7. orangecontrib/tomwer/widgets/control/EmailOW.py +4 -4
  8. orangecontrib/tomwer/widgets/control/NXTomomillOW.py +31 -18
  9. orangecontrib/tomwer/widgets/control/NXtomoConcatenate.py +14 -7
  10. orangecontrib/tomwer/widgets/control/NotifierOW.py +1 -0
  11. orangecontrib/tomwer/widgets/control/VolumeSelector.py +7 -4
  12. orangecontrib/tomwer/widgets/control/VolumeSymLinkOW.py +182 -182
  13. orangecontrib/tomwer/widgets/debugtools/DatasetGeneratorOW.py +4 -4
  14. orangecontrib/tomwer/widgets/edit/DarkFlatPatchOW.py +4 -4
  15. orangecontrib/tomwer/widgets/edit/ImageKeyEditorOW.py +3 -3
  16. orangecontrib/tomwer/widgets/edit/ImageKeyUpgraderOW.py +2 -0
  17. orangecontrib/tomwer/widgets/edit/NXtomoEditorOW.py +3 -3
  18. orangecontrib/tomwer/widgets/edit/test/test_nxtomo_editor.py +3 -3
  19. orangecontrib/tomwer/widgets/icat/PublishProcessedDataOW.py +115 -0
  20. orangecontrib/tomwer/widgets/icat/RawDataScreenshotCreatorOW.py +98 -0
  21. orangecontrib/tomwer/widgets/icat/SaveToGalleryAndPublishOW.py +129 -0
  22. orangecontrib/tomwer/widgets/icat/__init__.py +13 -0
  23. orangecontrib/tomwer/widgets/icat/icons/add_gallery.png +0 -0
  24. orangecontrib/tomwer/widgets/icat/icons/add_gallery.svg +82 -0
  25. orangecontrib/tomwer/widgets/icat/icons/publish_processed_data.png +0 -0
  26. orangecontrib/tomwer/widgets/icat/icons/publish_processed_data.svg +95 -0
  27. orangecontrib/tomwer/widgets/icat/icons/raw_screenshots.png +0 -0
  28. orangecontrib/tomwer/widgets/icat/icons/raw_screenshots.svg +143 -0
  29. orangecontrib/tomwer/widgets/icons/tomwer_data_portal.png +0 -0
  30. orangecontrib/tomwer/widgets/icons/tomwer_data_portal.svg +76 -0
  31. orangecontrib/tomwer/widgets/reconstruction/AxisOW.py +9 -8
  32. orangecontrib/tomwer/widgets/reconstruction/CastNabuVolumeOW.py +3 -3
  33. orangecontrib/tomwer/widgets/reconstruction/NabuHelicalPrepareWeightsDoubleOW.py +179 -169
  34. orangecontrib/tomwer/widgets/reconstruction/NabuOW.py +23 -0
  35. orangecontrib/tomwer/widgets/reconstruction/NabuVolumeOW.py +39 -5
  36. orangecontrib/tomwer/widgets/reconstruction/SAAxisOW.py +7 -13
  37. orangecontrib/tomwer/widgets/reconstruction/SADeltaBetaOW.py +7 -17
  38. orangecontrib/tomwer/widgets/reconstruction/SinoNormOW.py +3 -4
  39. orangecontrib/tomwer/widgets/visualization/LivesliceOW.py +1 -1
  40. orangecontrib/tomwer/widgets/visualization/NXtomoMetadataViewerOW.py +3 -3
  41. orangecontrib/tomwer/widgets/visualization/VolumeViewerOW.py +3 -29
  42. tomwer/__main__.py +11 -58
  43. tomwer/app/canvas.py +8 -0
  44. tomwer/app/canvas_launcher/config.py +13 -11
  45. tomwer/app/darkref.py +1 -1
  46. tomwer/app/darkrefpatch.py +1 -1
  47. tomwer/app/imagekeyeditor.py +5 -5
  48. tomwer/app/imagekeyupgrader.py +5 -5
  49. tomwer/app/intensitynormalization.py +2 -2
  50. tomwer/app/radiostack.py +2 -2
  51. tomwer/app/zstitching.py +74 -3
  52. tomwer/core/cluster/cluster.py +26 -0
  53. tomwer/core/log/logger.py +7 -5
  54. tomwer/core/process/conditions/filters.py +1 -1
  55. tomwer/core/process/control/datalistener/datalistener.py +3 -3
  56. tomwer/core/process/control/nxtomoconcatenate.py +13 -13
  57. tomwer/core/process/control/nxtomomill.py +83 -25
  58. tomwer/core/process/control/scantransfer.py +11 -10
  59. tomwer/core/process/control/scanvalidator.py +3 -2
  60. tomwer/core/process/control/test/test_concatenate_nxtomos.py +9 -9
  61. tomwer/core/process/control/test/test_email.py +4 -4
  62. tomwer/core/process/control/test/test_h52nx_process.py +59 -7
  63. tomwer/core/process/control/test/test_volume_link.py +64 -64
  64. tomwer/core/process/control/timer.py +1 -1
  65. tomwer/core/process/control/volumesymlink.py +200 -200
  66. tomwer/core/process/edit/darkflatpatch.py +6 -6
  67. tomwer/core/process/edit/imagekeyeditor.py +17 -18
  68. tomwer/core/process/icat/__init__.py +0 -0
  69. tomwer/core/process/icat/createscreenshots.py +100 -0
  70. tomwer/core/process/icat/gallery.py +377 -0
  71. tomwer/core/process/icat/icatbase.py +36 -0
  72. tomwer/core/process/icat/publish.py +228 -0
  73. tomwer/core/process/icat/screenshots.py +26 -0
  74. tomwer/core/process/output.py +52 -0
  75. tomwer/core/process/reconstruction/axis/axis.py +17 -10
  76. tomwer/core/process/reconstruction/axis/mode.py +4 -0
  77. tomwer/core/process/reconstruction/axis/params.py +9 -4
  78. tomwer/core/process/reconstruction/darkref/darkrefs.py +8 -6
  79. tomwer/core/process/reconstruction/darkref/darkrefscopy.py +1 -1
  80. tomwer/core/process/reconstruction/darkref/params.py +1 -1
  81. tomwer/core/process/reconstruction/lamino/tofu.py +4 -4
  82. tomwer/core/process/reconstruction/nabu/castvolume.py +1 -1
  83. tomwer/core/process/reconstruction/nabu/helical.py +9 -5
  84. tomwer/core/process/reconstruction/nabu/nabucommon.py +32 -62
  85. tomwer/core/process/reconstruction/nabu/nabuscores.py +387 -61
  86. tomwer/core/process/reconstruction/nabu/nabuslices.py +33 -21
  87. tomwer/core/process/reconstruction/nabu/nabuvolume.py +37 -14
  88. tomwer/core/process/reconstruction/nabu/settings.py +2 -2
  89. tomwer/core/process/reconstruction/nabu/utils.py +129 -24
  90. tomwer/core/process/reconstruction/output.py +108 -0
  91. tomwer/core/process/reconstruction/saaxis/saaxis.py +233 -263
  92. tomwer/core/process/reconstruction/sadeltabeta/sadeltabeta.py +140 -86
  93. tomwer/core/process/reconstruction/scores/params.py +4 -1
  94. tomwer/core/process/reconstruction/scores/scores.py +13 -0
  95. tomwer/core/process/reconstruction/test/test_axis_params.py +2 -2
  96. tomwer/core/process/reconstruction/test/test_darkref.py +3 -3
  97. tomwer/core/process/reconstruction/test/test_darkref_copy.py +3 -3
  98. tomwer/core/process/reconstruction/test/test_saaxis.py +3 -4
  99. tomwer/core/process/reconstruction/test/test_sadeltabeta.py +2 -2
  100. tomwer/core/process/stitching/nabustitcher.py +2 -2
  101. tomwer/core/process/test/test_axis.py +6 -6
  102. tomwer/core/process/test/test_dark_and_flat.py +10 -7
  103. tomwer/core/process/test/test_data_transfer.py +7 -6
  104. tomwer/core/process/test/test_nabu.py +4 -4
  105. tomwer/core/process/test/test_normalization.py +2 -2
  106. tomwer/core/scan/edfscan.py +4 -1
  107. tomwer/core/scan/hdf5scan.py +19 -500
  108. tomwer/core/scan/nxtomoscan.py +532 -0
  109. tomwer/core/scan/scanbase.py +42 -20
  110. tomwer/core/scan/scanfactory.py +13 -13
  111. tomwer/core/scan/test/test_future_scan.py +2 -2
  112. tomwer/core/scan/test/test_h5.py +12 -10
  113. tomwer/core/scan/test/test_process_registration.py +2 -2
  114. tomwer/core/scan/test/test_scan.py +4 -3
  115. tomwer/core/settings.py +20 -0
  116. tomwer/core/test/test_scanutils.py +8 -7
  117. tomwer/core/test/test_utils.py +33 -26
  118. tomwer/core/utils/__init__.py +0 -466
  119. tomwer/core/utils/deprecation.py +1 -1
  120. tomwer/core/utils/dictutils.py +14 -0
  121. tomwer/core/utils/lbsram.py +35 -0
  122. tomwer/core/utils/nxtomoutils.py +1 -1
  123. tomwer/core/utils/scanutils.py +6 -6
  124. tomwer/core/utils/spec.py +263 -0
  125. tomwer/core/volume/volumefactory.py +2 -2
  126. tomwer/gui/cluster/slurm.py +260 -60
  127. tomwer/gui/cluster/test/test_cluster.py +13 -0
  128. tomwer/gui/cluster/test/test_supervisor.py +2 -2
  129. tomwer/gui/configuration/__init__.py +0 -0
  130. tomwer/gui/{reconstruction/nabu → configuration}/action.py +1 -32
  131. tomwer/gui/configuration/level.py +22 -0
  132. tomwer/gui/control/actions.py +54 -0
  133. tomwer/gui/control/datalist.py +78 -16
  134. tomwer/gui/control/datalistener.py +4 -16
  135. tomwer/gui/control/{email.py → emailnotifier.py} +9 -18
  136. tomwer/gui/control/history.py +2 -2
  137. tomwer/gui/control/observations.py +2 -2
  138. tomwer/gui/control/reducedarkflatselector.py +1 -1
  139. tomwer/gui/control/selectorwidgetbase.py +36 -9
  140. tomwer/gui/control/serie/seriecreator.py +5 -22
  141. tomwer/gui/control/test/test_email.py +1 -1
  142. tomwer/gui/control/test/test_scanvalidator.py +6 -5
  143. tomwer/gui/control/test/test_single_tomo_obj.py +2 -2
  144. tomwer/gui/control/tomoobjdisplaymode.py +8 -0
  145. tomwer/gui/debugtools/datasetgenerator.py +3 -3
  146. tomwer/gui/edit/dkrfpatch.py +16 -22
  147. tomwer/gui/edit/imagekeyeditor.py +8 -11
  148. tomwer/gui/edit/nxtomoeditor.py +111 -44
  149. tomwer/gui/edit/nxtomowarmer.py +4 -4
  150. tomwer/gui/edit/test/test_dkrf_patch.py +7 -7
  151. tomwer/gui/edit/test/test_image_key_editor.py +3 -3
  152. tomwer/gui/edit/test/test_nx_editor.py +40 -16
  153. tomwer/gui/icat/__init__.py +0 -0
  154. tomwer/gui/icat/createscreenshots.py +80 -0
  155. tomwer/gui/icat/gallery.py +214 -0
  156. tomwer/gui/icat/publish.py +187 -0
  157. tomwer/gui/reconstruction/axis/axis.py +171 -57
  158. tomwer/gui/reconstruction/axis/radioaxis.py +80 -95
  159. tomwer/gui/reconstruction/darkref/darkrefcopywidget.py +3 -2
  160. tomwer/gui/reconstruction/lamino/tofu/projections.py +1 -1
  161. tomwer/gui/reconstruction/lamino/tofu/tofuoutput.py +3 -6
  162. tomwer/gui/reconstruction/nabu/castvolume.py +1 -1
  163. tomwer/gui/reconstruction/nabu/check.py +9 -9
  164. tomwer/gui/reconstruction/nabu/helical.py +29 -12
  165. tomwer/gui/reconstruction/nabu/nabuconfig/base.py +2 -4
  166. tomwer/gui/reconstruction/nabu/nabuconfig/output.py +110 -33
  167. tomwer/gui/reconstruction/nabu/nabuconfig/phase.py +9 -12
  168. tomwer/gui/reconstruction/nabu/nabuconfig/preprocessing.py +219 -29
  169. tomwer/gui/reconstruction/nabu/nabuconfig/reconstruction.py +3 -6
  170. tomwer/gui/reconstruction/nabu/nabuflow.py +12 -20
  171. tomwer/gui/reconstruction/nabu/slices.py +6 -7
  172. tomwer/gui/reconstruction/nabu/volume.py +22 -10
  173. tomwer/gui/reconstruction/normalization/intensity.py +15 -23
  174. tomwer/gui/reconstruction/saaxis/corrangeselector.py +7 -23
  175. tomwer/gui/reconstruction/saaxis/dimensionwidget.py +1 -1
  176. tomwer/gui/reconstruction/saaxis/saaxis.py +7 -9
  177. tomwer/gui/reconstruction/sadeltabeta/saadeltabeta.py +2 -1
  178. tomwer/gui/reconstruction/scores/control.py +2 -9
  179. tomwer/gui/reconstruction/scores/scoreplot.py +11 -5
  180. tomwer/gui/reconstruction/test/test_axis.py +23 -12
  181. tomwer/gui/reconstruction/test/test_lamino.py +8 -3
  182. tomwer/gui/reconstruction/test/test_nabu.py +28 -9
  183. tomwer/gui/reconstruction/test/test_saaxis.py +3 -3
  184. tomwer/gui/reconstruction/test/test_sadeltabeta.py +2 -2
  185. tomwer/gui/settings.py +5 -28
  186. tomwer/gui/stackplot.py +2 -5
  187. tomwer/gui/stitching/action.py +49 -0
  188. tomwer/gui/stitching/config/axisparams.py +7 -24
  189. tomwer/gui/stitching/config/output.py +10 -8
  190. tomwer/gui/stitching/config/positionoveraxis.py +22 -23
  191. tomwer/gui/stitching/normalization.py +117 -0
  192. tomwer/gui/stitching/stitchandbackground.py +4 -6
  193. tomwer/gui/stitching/stitching.py +265 -43
  194. tomwer/gui/stitching/stitching_preview.py +62 -5
  195. tomwer/gui/stitching/stitching_raw.py +2 -5
  196. tomwer/gui/stitching/z_stitching/fineestimation.py +0 -60
  197. tomwer/gui/utils/buttons.py +112 -29
  198. tomwer/gui/utils/inputwidget.py +33 -25
  199. tomwer/gui/utils/scandescription.py +4 -0
  200. tomwer/gui/utils/step.py +144 -0
  201. tomwer/gui/utils/unitsystem.py +2 -5
  202. tomwer/gui/utils/vignettes.py +176 -15
  203. tomwer/gui/visualization/dataviewer.py +1 -4
  204. tomwer/gui/visualization/diffviewer/diffviewer.py +7 -16
  205. tomwer/gui/visualization/diffviewer/shiftwidget.py +2 -5
  206. tomwer/gui/visualization/scanoverview.py +1 -1
  207. tomwer/gui/visualization/sinogramviewer.py +1 -10
  208. tomwer/gui/visualization/test/test_diffviewer.py +3 -3
  209. tomwer/gui/visualization/test/test_nx_tomo_metadata_viewer.py +4 -4
  210. tomwer/gui/visualization/test/test_sinogramviewer.py +2 -2
  211. tomwer/gui/visualization/test/test_stacks.py +3 -3
  212. tomwer/gui/visualization/test/test_volumeviewer.py +2 -2
  213. tomwer/io/utils/raw_and_processed_data.py +84 -0
  214. tomwer/io/utils/tomoobj.py +4 -6
  215. tomwer/resources/gui/icons/ruler.png +0 -0
  216. tomwer/resources/gui/icons/ruler.svg +273 -0
  217. tomwer/resources/gui/icons/short_description.png +0 -0
  218. tomwer/resources/gui/icons/short_description.svg +58 -0
  219. tomwer/resources/gui/icons/url.png +0 -0
  220. tomwer/resources/gui/icons/url.svg +58 -0
  221. tomwer/synctools/stacks/edit/darkflatpatch.py +2 -2
  222. tomwer/synctools/stacks/edit/imagekeyeditor.py +2 -2
  223. tomwer/synctools/stacks/reconstruction/axis.py +4 -4
  224. tomwer/synctools/stacks/reconstruction/castvolume.py +2 -2
  225. tomwer/synctools/stacks/reconstruction/dkrefcopy.py +4 -10
  226. tomwer/synctools/stacks/reconstruction/nabu.py +2 -2
  227. tomwer/synctools/stacks/reconstruction/normalization.py +2 -2
  228. tomwer/synctools/stacks/reconstruction/saaxis.py +2 -2
  229. tomwer/synctools/stacks/reconstruction/sadeltabeta.py +2 -2
  230. tomwer/synctools/test/test_darkRefs.py +7 -58
  231. tomwer/synctools/test/test_foldertransfer.py +6 -6
  232. tomwer/synctools/utils/scanstages.py +6 -6
  233. tomwer/tests/conftest.py +34 -0
  234. tomwer/tests/datasets.py +13 -0
  235. tomwer/tests/test_scripts.py +92 -39
  236. tomwer/tests/utils.py +5 -0
  237. tomwer/version.py +3 -3
  238. {tomwer-1.2.9.dist-info → tomwer-1.3.0a0.dist-info}/METADATA +39 -39
  239. {tomwer-1.2.9.dist-info → tomwer-1.3.0a0.dist-info}/RECORD +248 -209
  240. tomwer/resources/gui/icons/esrf_1.svg +0 -307
  241. tomwer/resources/gui/icons/triangle.svg +0 -80
  242. tomwer/synctools/test/test_scanstages.py +0 -162
  243. tomwer/tests/utils/__init__.py +0 -247
  244. tomwer/tests/utils/utilstest.py +0 -220
  245. /tomwer/app/{saaxis.py → multicor.py} +0 -0
  246. /tomwer/app/{sadeltabeta.py → multipag.py} +0 -0
  247. /tomwer/core/process/control/{email.py → emailnotifier.py} +0 -0
  248. /tomwer-1.2.9-py3.11-nspkg.pth → /tomwer-1.3.0a0-py3.11-nspkg.pth +0 -0
  249. {tomwer-1.2.9.dist-info → tomwer-1.3.0a0.dist-info}/LICENSE +0 -0
  250. {tomwer-1.2.9.dist-info → tomwer-1.3.0a0.dist-info}/WHEEL +0 -0
  251. {tomwer-1.2.9.dist-info → tomwer-1.3.0a0.dist-info}/entry_points.txt +0 -0
  252. {tomwer-1.2.9.dist-info → tomwer-1.3.0a0.dist-info}/namespace_packages.txt +0 -0
  253. {tomwer-1.2.9.dist-info → tomwer-1.3.0a0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,532 @@
1
+ # coding: utf-8
2
+ # /*##########################################################################
3
+ # Copyright (C) 2016 European Synchrotron Radiation Facility
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+ #
23
+ #############################################################################*/
24
+
25
+
26
+ __authors__ = ["H.Payno"]
27
+ __license__ = "MIT"
28
+ __date__ = "09/08/2018"
29
+
30
+
31
+ import functools
32
+ import io
33
+ import json
34
+ import logging
35
+ import os
36
+ import pathlib
37
+ from datetime import datetime
38
+ from typing import Optional
39
+ from urllib.parse import urlparse
40
+
41
+ import h5py
42
+ from processview.core.dataset import DatasetIdentifier
43
+ from tomoscan.esrf.identifier.hdf5Identifier import (
44
+ NXtomoScanIdentifier as _NXtomoScanIdentifier,
45
+ )
46
+ from tomoscan.esrf.identifier.url_utils import UrlSettings, split_path, split_query
47
+ from tomoscan.esrf.scan.nxtomoscan import NXtomoScan as _tsNXtomoScan
48
+ from tomoscan.io import HDF5File
49
+ from nxtomo.nxobject.nxdetector import ImageKey
50
+
51
+ from tomwer.utils import docstring
52
+ from tomwer.core.scan.helicalmetadata import HelicalMetadata
53
+
54
+ from .scanbase import TomwerScanBase
55
+
56
+ _logger = logging.getLogger(__name__)
57
+
58
+
59
+ class NXtomoScanIdentifier(_NXtomoScanIdentifier, DatasetIdentifier):
60
+ def __init__(self, metadata=None, *args, **kwargs):
61
+ super().__init__(*args, **kwargs)
62
+ DatasetIdentifier.__init__(
63
+ self, data_builder=NXtomoScan.from_identifier, metadata=metadata
64
+ )
65
+
66
+ @staticmethod
67
+ def from_str(identifier):
68
+ info = urlparse(identifier)
69
+ paths = split_path(info.path)
70
+ if len(paths) == 1:
71
+ hdf5_file = paths[0]
72
+ tomo_type = None
73
+ elif len(paths) == 2:
74
+ tomo_type, hdf5_file = paths
75
+ else:
76
+ raise ValueError("Failed to parse path string:", info.path)
77
+ if tomo_type is not None and tomo_type != NXtomoScanIdentifier.TOMO_TYPE:
78
+ raise TypeError(
79
+ f"provided identifier fits {tomo_type} and not {NXtomoScanIdentifier.TOMO_TYPE}"
80
+ )
81
+
82
+ queries = split_query(info.query)
83
+ entry = queries.get(UrlSettings.DATA_PATH_KEY, None)
84
+ if entry is None:
85
+ raise ValueError(f"expects to get {UrlSettings.DATA_PATH_KEY} query")
86
+
87
+ return NXtomoScanIdentifier(object=NXtomoScan, hdf5_file=hdf5_file, entry=entry)
88
+
89
+ def long_description(self) -> str:
90
+ """used for processview header tooltip for now"""
91
+ return self.to_str()
92
+
93
+ def short_description(self) -> str:
94
+ return f"scan: {self.data_path.lstrip('/')}@{os.path.basename(self._file_path)}"
95
+
96
+
97
+ class NXtomoScan(_tsNXtomoScan, TomwerScanBase):
98
+ """
99
+ This is the implementation of a TomoBase class for an acquisition stored
100
+ in a HDF5 file.
101
+
102
+ For now several property of the acquisition is accessible thought a getter
103
+ (like get_scan_range) and a property (scan_range).
104
+
105
+ This is done to be compliant with TomoBase instanciation. But his will be
106
+ replace progressively by properties at the 'TomoBase' level
107
+
108
+ :param scan: scan directory or scan masterfile.h5
109
+ :type: Union[str,None]
110
+ """
111
+
112
+ _TYPE = "hdf5"
113
+
114
+ def __init__(self, scan, entry, index=None, overwrite_proc_file=False):
115
+ TomwerScanBase.__init__(self)
116
+ _tsNXtomoScan.__init__(self, scan=scan, entry=entry, index=index)
117
+ # register at least the 'default' working directory as a possible reconstruction path
118
+ self.add_reconstruction_path(self.path)
119
+ self._helical = HelicalMetadata()
120
+
121
+ self._reconstruction_urls = None
122
+ self._projections_with_angles = None
123
+ self._process_file = self.get_process_file_name(self)
124
+ self._init_index_process_file(overwrite_proc_file=overwrite_proc_file)
125
+ try:
126
+ reduced_darks, metadata = self.load_reduced_darks(return_info=True)
127
+ except (KeyError, OSError, ValueError):
128
+ # file or key does not exists
129
+ pass
130
+ else:
131
+ self.set_reduced_darks(reduced_darks, darks_infos=metadata)
132
+
133
+ try:
134
+ reduced_flats, metadata = self.load_reduced_flats(return_info=True)
135
+ except (KeyError, OSError, ValueError):
136
+ pass
137
+ else:
138
+ self.set_reduced_flats(reduced_flats, flats_infos=metadata)
139
+
140
+ @property
141
+ def working_directory(self):
142
+ if self.master_file is None:
143
+ return None
144
+ else:
145
+ return os.path.realpath(os.path.dirname(self.master_file))
146
+
147
+ @property
148
+ def helical(self):
149
+ return self._helical
150
+
151
+ @staticmethod
152
+ def from_identifier(identifier):
153
+ """Return the Dataset from a identifier"""
154
+ if not isinstance(identifier, NXtomoScanIdentifier):
155
+ raise TypeError(
156
+ f"identifier should be an instance of {NXtomoScanIdentifier}. Not {type(identifier)}"
157
+ )
158
+ return NXtomoScan(scan=identifier.file_path, entry=identifier.data_path)
159
+
160
+ def get_identifier(self):
161
+ try:
162
+ stat = pathlib.Path(self.master_file).stat()
163
+ except Exception:
164
+ stat = None
165
+
166
+ return NXtomoScanIdentifier(
167
+ object=self,
168
+ hdf5_file=self.master_file,
169
+ entry=self.entry,
170
+ metadata={
171
+ "name": self.master_file,
172
+ "creation_time": datetime.fromtimestamp(stat.st_ctime)
173
+ if stat
174
+ else None,
175
+ "modification_time": datetime.fromtimestamp(stat.st_ctime)
176
+ if stat
177
+ else None,
178
+ },
179
+ )
180
+
181
+ @staticmethod
182
+ def get_process_file_name(scan):
183
+ if scan.path is not None:
184
+ basename, _ = os.path.splitext(scan.master_file)
185
+ basename = os.path.basename(basename)
186
+ basename = "_".join((basename, "tomwer_processes.h5"))
187
+ return os.path.join(scan.path, basename)
188
+ else:
189
+ return None
190
+
191
+ @staticmethod
192
+ def directory_contains_scan(directory, src_pattern=None, dest_pattern=None):
193
+ """
194
+
195
+ Check if the given directory is holding an acquisition
196
+
197
+ :param str directory: directory we want to check
198
+ :param str src_pattern: buffer name pattern ('lbsram')
199
+ :param dest_pattern: output pattern (''). Needed because some
200
+ acquisition can split the file produce between
201
+ two directories. This is the case for edf,
202
+ where .info file are generated in /data/dir
203
+ instead of /lbsram/data/dir
204
+ :type: str
205
+ :return: does the given directory contains any acquisition
206
+ :rtype: bool
207
+ """
208
+ master_file = os.path.join(directory, os.path.basename(directory))
209
+ if os.path.exists("master_file.hdf5"):
210
+ return True
211
+ else:
212
+ return os.path.exists(master_file + ".h5")
213
+
214
+ def clear_caches(self):
215
+ _tsNXtomoScan.clear_caches(self)
216
+ TomwerScanBase.clear_caches(self)
217
+
218
+ def is_abort(self, src_pattern, dest_pattern):
219
+ """
220
+ Check if the acquisition have been aborted. In this case the directory
221
+ should contain a [scan].abo file
222
+
223
+ :param str src_pattern: buffer name pattern ('lbsram')
224
+ :param dest_pattern: output pattern (''). Needed because some
225
+ acquisition can split the file produce between
226
+ two directories. This is the case for edf,
227
+ where .info file are generated in /data/dir
228
+ instead of /lbsram/data/dir
229
+ :return: True if the acquisition have been abort and the directory
230
+ should be abort
231
+ """
232
+ # for now there is no abort definition in .hdf5
233
+ return False
234
+
235
+ @staticmethod
236
+ def from_dict(_dict):
237
+ path = _dict[NXtomoScan.DICT_PATH_KEY]
238
+ entry = _dict[NXtomoScan._DICT_ENTRY_KEY]
239
+
240
+ scan = NXtomoScan(scan=path, entry=entry)
241
+ scan.load_from_dict(_dict=_dict)
242
+ return scan
243
+
244
+ @docstring(TomwerScanBase)
245
+ def load_from_dict(self, _dict):
246
+ """
247
+
248
+ :param _dict:
249
+ :return:
250
+ """
251
+ if isinstance(_dict, io.TextIOWrapper):
252
+ data = json.load(_dict)
253
+ else:
254
+ data = _dict
255
+ if not (self.DICT_TYPE_KEY in data and data[self.DICT_TYPE_KEY] == self._TYPE):
256
+ raise ValueError("Description is not an EDFScan json description")
257
+
258
+ _tsNXtomoScan.load_from_dict(self, _dict)
259
+ TomwerScanBase.load_from_dict(self, _dict)
260
+ return self
261
+
262
+ @docstring(TomwerScanBase)
263
+ def to_dict(self) -> dict:
264
+ ddict = _tsNXtomoScan.to_dict(self)
265
+ ddict.update(TomwerScanBase.to_dict(self))
266
+ return ddict
267
+
268
+ def update(self):
269
+ """update list of radio and reconstruction by parsing the scan folder"""
270
+ if self.master_file is None:
271
+ return
272
+ if not os.path.exists(self.master_file):
273
+ return
274
+ _tsNXtomoScan.update(self)
275
+ self.reconstructions = self.get_reconstructions_urls()
276
+
277
+ def _get_scheme(self):
278
+ """
279
+
280
+ :return: scheme to read url
281
+ :rtype: str
282
+ """
283
+ return "silx"
284
+
285
+ def is_finish(self):
286
+ return len(self.projections) >= self.tomo_n
287
+
288
+ @docstring(TomwerScanBase.get_sinogram)
289
+ @functools.lru_cache(maxsize=16, typed=True)
290
+ def get_sinogram(self, line, subsampling=1, norm_method=None, **kwargs):
291
+ """
292
+
293
+ extract the sinogram from projections
294
+
295
+ :param line: which sinogram we want
296
+ :type: int
297
+ :param subsampling: subsampling to apply if any. Allows to skip some io
298
+ :type: int
299
+ :return: sinogram from the radio lines
300
+ :rtype: numpy.array
301
+ """
302
+ return _tsNXtomoScan.get_sinogram(
303
+ self,
304
+ line=line,
305
+ subsampling=subsampling,
306
+ norm_method=norm_method,
307
+ **kwargs,
308
+ )
309
+
310
+ @docstring(TomwerScanBase.get_proj_angle_url)
311
+ def get_proj_angle_url(self, use_cache: bool = True, with_alignment=True):
312
+ if not use_cache:
313
+ self._cache_proj_urls = None
314
+
315
+ if self._cache_proj_urls is None:
316
+ frames = self.frames
317
+ if frames is None:
318
+ return {}
319
+
320
+ self._cache_proj_urls = {}
321
+ for frame in frames:
322
+ if frame.image_key is ImageKey.PROJECTION:
323
+ if frame.is_control and with_alignment:
324
+ self._cache_proj_urls[f"{frame.rotation_angle} (1)"] = frame.url
325
+ elif frame.is_control:
326
+ continue
327
+ else:
328
+ self._cache_proj_urls[str(frame.rotation_angle)] = frame.url
329
+ return self._cache_proj_urls
330
+
331
+ @docstring(TomwerScanBase._deduce_transfert_scan)
332
+ def _deduce_transfert_scan(self, output_dir):
333
+ new_master_file_path = os.path.join(
334
+ output_dir, os.path.basename(self.master_file)
335
+ )
336
+ return NXtomoScan(scan=new_master_file_path, entry=self.entry)
337
+
338
+ def data_flat_field_correction(self, data, index=None):
339
+ flats = self.reduced_flats
340
+ flat1 = flat2 = None
341
+ index_flat1 = index_flat2 = None
342
+ if flats is not None:
343
+ flat_indexes = sorted(list(flats.keys()))
344
+ if len(flats) > 0:
345
+ index_flat1 = flat_indexes[0]
346
+ flat1 = flats[index_flat1]
347
+ if len(flats) > 1:
348
+ index_flat2 = flat_indexes[-1]
349
+ flat2 = flats[index_flat2]
350
+ darks = self.reduced_darks
351
+ dark = None
352
+ if darks is not None and len(darks) > 0:
353
+ # take only one dark into account for now
354
+ dark = list(darks.values())[0]
355
+ return self._flat_field_correction(
356
+ data=data,
357
+ dark=dark,
358
+ flat1=flat1,
359
+ flat2=flat2,
360
+ index_flat1=index_flat1,
361
+ index_flat2=index_flat2,
362
+ index_proj=index,
363
+ )
364
+
365
+ @docstring(_tsNXtomoScan.ff_interval)
366
+ @property
367
+ def ff_interval(self):
368
+ """
369
+ Make some assumption to compute the flat field interval:
370
+ """
371
+
372
+ def get_first_two_ff_indexes():
373
+ if self.flats is None:
374
+ return None, None
375
+ else:
376
+ self._last_flat_index = None
377
+ self._first_serie_flat_index = None
378
+ for flat_index in self.flats:
379
+ if self._last_flat_index is None:
380
+ self._last_flat_index = flat_index
381
+ elif flat_index == self._last_flat_index + 1:
382
+ self._last_flat_index = flat_index
383
+ continue
384
+ else:
385
+ return self._last_flat_index, flat_index
386
+ return None, None
387
+
388
+ first_serie_index, second_serie_index = get_first_two_ff_indexes()
389
+ if first_serie_index is None:
390
+ return 0
391
+ elif second_serie_index is not None:
392
+ return second_serie_index - first_serie_index - 1
393
+ else:
394
+ return 0
395
+
396
+ def projections_with_angle(self):
397
+ """projections / radio, does not include the return projections"""
398
+ if self._projections_with_angles is None:
399
+ if self.frames:
400
+ proj_frames = tuple(
401
+ filter(
402
+ lambda x: x.image_key == ImageKey.PROJECTION
403
+ and x.is_control is False,
404
+ self.frames,
405
+ )
406
+ )
407
+ self._projections_with_angles = {}
408
+ for proj_frame in proj_frames:
409
+ self._projections_with_angles[
410
+ proj_frame.rotation_angle
411
+ ] = proj_frame.url
412
+ return self._projections_with_angles
413
+
414
+ @staticmethod
415
+ def is_nexus_nxtomo_file(file_path: str) -> bool:
416
+ if h5py.is_hdf5(file_path):
417
+ return len(NXtomoScan.get_nxtomo_entries(file_path)) > 0
418
+
419
+ @staticmethod
420
+ def get_nxtomo_entries(file_path: str) -> tuple:
421
+ if not h5py.is_hdf5(file_path):
422
+ return tuple()
423
+ else:
424
+ res = []
425
+ with HDF5File(file_path, mode="r", swmr=True, libver="latest") as h5s:
426
+ for entry_name, node in h5s.items():
427
+ if isinstance(node, h5py.Group):
428
+ if NXtomoScan.entry_is_nx_tomo(node):
429
+ res.append(entry_name)
430
+ return tuple(res)
431
+
432
+ @staticmethod
433
+ def entry_is_nx_tomo(entry: h5py.Group):
434
+ return ("beam" in entry and "instrument" in entry and "sample" in entry) or (
435
+ hasattr(entry, "attrs")
436
+ and "definition" in entry.attrs
437
+ and entry.attrs["definition"] == "NXtomo"
438
+ )
439
+
440
+ @staticmethod
441
+ def is_nxdetector(grp: h5py.Group):
442
+ """
443
+ Check if the grp is an nx detector
444
+
445
+ :param h5py.Group grp:
446
+ :return: True if this is the definition of a group
447
+ :rtype: bool
448
+ """
449
+ if hasattr(grp, "attrs"):
450
+ if "NX_class" in grp.attrs and grp.attrs["NX_class"] == "NXdetector":
451
+ return True
452
+ return False
453
+
454
+ # Dataset implementation
455
+
456
+ @docstring(TomwerScanBase)
457
+ def get_nabu_dataset_info(self, binning=1, binning_z=1, proj_subsampling=1):
458
+ if not isinstance(binning, int):
459
+ raise TypeError(f"binning should be an int. Not {type(binning)}")
460
+ if not isinstance(binning_z, int):
461
+ raise TypeError(f"binning_z should be an int. Not {type(binning_z)}")
462
+ if not isinstance(proj_subsampling, int):
463
+ raise TypeError(
464
+ f"proj_subsampling should be an int. Not {type(proj_subsampling)}"
465
+ )
466
+ return {
467
+ "hdf5_entry": self.entry,
468
+ "location": os.path.basename(self.master_file),
469
+ "binning": binning,
470
+ "binning_z": binning_z,
471
+ "projections_subsampling": proj_subsampling,
472
+ }
473
+
474
+ @docstring(TomwerScanBase)
475
+ def to_nabu_dataset_analyser(self):
476
+ from nabu.resources.dataset_analyzer import HDF5DatasetAnalyzer
477
+
478
+ return HDF5DatasetAnalyzer(
479
+ location=self.master_file, extra_options={"hdf5_entry": self.entry}
480
+ )
481
+
482
+ @docstring(TomwerScanBase)
483
+ def scan_dir_name(self) -> Optional[str]:
484
+ """for 'this/is/my/file.h5' returns 'my'"""
485
+ if self.master_file is not None:
486
+ return os.path.dirname(self.master_file).split(os.sep)[-1]
487
+ else:
488
+ return None
489
+
490
+ @docstring(TomwerScanBase)
491
+ def scan_basename(self):
492
+ if self.master_file is not None:
493
+ try:
494
+ return os.path.dirname(self.master_file)
495
+ except Exception:
496
+ return None
497
+ else:
498
+ return None
499
+
500
+ @docstring(TomwerScanBase)
501
+ def scan_parent_dir_basename(self):
502
+ if self.master_file is not None:
503
+ try:
504
+ return os.path.dirname(os.path.dirname(self.master_file))
505
+ except Exception:
506
+ return None
507
+ else:
508
+ return None
509
+
510
+ def get_proposal_name(self) -> Optional[str]:
511
+ if self._proposal_name is None and self.master_file is not None:
512
+ bliss_raw_data_files = self.get_bliss_orginal_files() or ()
513
+ bliss_raw_data_file = (
514
+ bliss_raw_data_files[0] if len(bliss_raw_data_files) > 0 else None
515
+ )
516
+ if bliss_raw_data_file is not None:
517
+ strips = bliss_raw_data_file.lstrip("/").split("/")
518
+ if len(strips) > 2 and bliss_raw_data_file.startswith(
519
+ ("/data/visitor", "/data/visitor")
520
+ ):
521
+ self._proposal_name = strips[2]
522
+ elif (
523
+ bliss_raw_data_file.startswith(("/data", "data"))
524
+ and len(strips) > 3
525
+ and strips[2] == "inhouse"
526
+ ):
527
+ self._proposal_name = strips[3]
528
+ else:
529
+ _logger.warning(
530
+ "this doesn't looks like a bliss file aquired at the ESRF. Unable to find proposal name"
531
+ )
532
+ return self._proposal_name
@@ -44,6 +44,7 @@ from tomoscan.identifier import VolumeIdentifier
44
44
  from tomoscan.io import HDF5File
45
45
  from tomoscan.normalization import IntensityNormalization
46
46
  from tomoscan.volumebase import VolumeBase
47
+ from tomoscan.identifier import BaseIdentifier
47
48
 
48
49
  from tomwer.core.tomwer_object import TomwerObject
49
50
  from tomwer.core.utils.ftseriesutils import orderFileByLastLastModification
@@ -113,6 +114,7 @@ class TomwerScanBase(TomwerObject):
113
114
  self._latest_vol_reconstructions = []
114
115
  """list of url related to latest volume reconstruction from nabu"""
115
116
  self._reconstruction_paths = set()
117
+ self._proposal_name = None
116
118
 
117
119
  def _clear_heavy_cache(self):
118
120
  """For scan for now we don't want to remove any information from the cache.
@@ -166,7 +168,7 @@ class TomwerScanBase(TomwerObject):
166
168
 
167
169
  if dark is None:
168
170
  if self._notify_ffc_rsc_missing:
169
- logger.error("cannot make flat field correction, dark not found")
171
+ logger.warning("cannot make flat field correction, dark not found")
170
172
  can_process = False
171
173
 
172
174
  if dark is not None and dark.ndim != 2:
@@ -177,7 +179,7 @@ class TomwerScanBase(TomwerObject):
177
179
 
178
180
  if flat1 is None:
179
181
  if self._notify_ffc_rsc_missing:
180
- logger.error("cannot make flat field correction, flat not found")
182
+ logger.warning("cannot make flat field correction, flat not found")
181
183
  can_process = False
182
184
  else:
183
185
  if flat1.ndim != 2:
@@ -681,29 +683,43 @@ class TomwerScanBase(TomwerObject):
681
683
  f"url should be a {VolumeIdentifier} or a string reprenseting a {VolumeIdentifier}. {type(url)} provided instead"
682
684
  )
683
685
 
684
- def set_latest_vol_reconstructions(self, urls: typing.Iterable):
685
- if urls is None:
686
+ def set_latest_vol_reconstructions(self, volume_identifiers: typing.Iterable):
687
+ if volume_identifiers is None:
686
688
  self._latest_vol_reconstructions = None
687
689
  else:
688
690
  self._latest_vol_reconstructions = list(
689
- [self._process_volume_url(url) for url in urls]
691
+ [self._process_volume_url(url) for url in volume_identifiers]
690
692
  )
691
693
 
692
- def add_latest_vol_reconstructions(self, urls):
693
- self._latest_vol_reconstructions.extend(urls)
694
-
695
- def _update_latest_recons_urls(self, old_path, new_path):
696
- new_urls = []
697
- for recons_url in self._latest_reconstructions:
698
- new_urls.append(
699
- DataUrl(
700
- file_path=recons_url.path().replace(old_path, new_path, 1),
701
- data_path=recons_url.data_path(),
702
- data_slice=recons_url.data_slice(),
703
- scheme=recons_url.scheme(),
704
- )
705
- )
706
- self._latest_reconstructions = new_urls
694
+ def add_latest_vol_reconstructions(self, volume_identifiers: tuple):
695
+ assert isinstance(
696
+ volume_identifiers, tuple
697
+ ), "volume_identifiers is expected to be a tuple"
698
+ self._latest_vol_reconstructions.extend(
699
+ self._process_volume_url(volume_identifier)
700
+ for volume_identifier in volume_identifiers
701
+ )
702
+
703
+ def _update_latest_recons_identifiers(self, old_path, new_path):
704
+ def update_identifier(identifier):
705
+ assert isinstance(
706
+ identifier, BaseIdentifier
707
+ ), f"identifier is expected to be an instance of {BaseIdentifier}"
708
+ # small hack as this is not much used: simply try to replace a path by another. this is only used by the data transfer and EDF / SPEC
709
+ # this time is almost over
710
+ # FIXME: correct way to do this would be to recreate the volume, modify file or folder path and
711
+ # recreate the new identifier
712
+ identifier.replace(old_path, new_path, 1)
713
+
714
+ self._latest_reconstructions = [
715
+ update_identifier(identifier=identifier)
716
+ for identifier in self._latest_reconstructions
717
+ ]
718
+
719
+ self._latest_vol_reconstructions = [
720
+ update_identifier(identifier=identifier)
721
+ for identifier in self._latest_vol_reconstructions
722
+ ]
707
723
 
708
724
  def get_url_proj_index(self, url):
709
725
  """Return the index in the acquisition from the url"""
@@ -764,6 +780,12 @@ class TomwerScanBase(TomwerObject):
764
780
  """Return the equivalent DatasetAnalyzer for nabu"""
765
781
  raise NotImplementedError("Base class")
766
782
 
783
+ def get_proposal_name(self) -> Optional[str]:
784
+ return self._proposal_name
785
+
786
+ def set_proposal_name(self, proposal_name: str) -> None:
787
+ self._proposal_name = proposal_name
788
+
767
789
 
768
790
  class _TomwerBaseDock(object):
769
791
  """