tomwer 1.2.1__py3-none-any.whl → 1.3.12__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 (334) hide show
  1. orangecontrib/tomwer/tutorials/icat_publication.ows +58 -0
  2. orangecontrib/tomwer/widgets/__init__.py +11 -11
  3. orangecontrib/tomwer/widgets/control/DataDiscoveryOW.py +2 -2
  4. orangecontrib/tomwer/widgets/control/DataListOW.py +9 -7
  5. orangecontrib/tomwer/widgets/control/DataListenerOW.py +6 -6
  6. orangecontrib/tomwer/widgets/control/DataSelectorOW.py +21 -10
  7. orangecontrib/tomwer/widgets/control/DataValidatorOW.py +6 -6
  8. orangecontrib/tomwer/widgets/control/EDF2NXTomomillOW.py +24 -7
  9. orangecontrib/tomwer/widgets/control/EmailOW.py +4 -4
  10. orangecontrib/tomwer/widgets/control/NXTomomillMixIn.py +3 -3
  11. orangecontrib/tomwer/widgets/control/NXTomomillOW.py +64 -23
  12. orangecontrib/tomwer/widgets/control/NXtomoConcatenate.py +20 -8
  13. orangecontrib/tomwer/widgets/control/NotifierOW.py +1 -0
  14. orangecontrib/tomwer/widgets/control/SingleTomoObjOW.py +6 -6
  15. orangecontrib/tomwer/widgets/control/VolumeSelector.py +7 -4
  16. orangecontrib/tomwer/widgets/control/VolumeSymLinkOW.py +182 -182
  17. orangecontrib/tomwer/widgets/debugtools/DatasetGeneratorOW.py +5 -5
  18. orangecontrib/tomwer/widgets/edit/DarkFlatPatchOW.py +4 -4
  19. orangecontrib/tomwer/widgets/edit/ImageKeyEditorOW.py +3 -3
  20. orangecontrib/tomwer/widgets/edit/ImageKeyUpgraderOW.py +8 -1
  21. orangecontrib/tomwer/widgets/edit/NXtomoEditorOW.py +3 -3
  22. orangecontrib/tomwer/widgets/edit/test/test_nxtomo_editor.py +3 -3
  23. orangecontrib/tomwer/widgets/icat/PublishProcessedDataOW.py +115 -0
  24. orangecontrib/tomwer/widgets/icat/RawDataScreenshotCreatorOW.py +98 -0
  25. orangecontrib/tomwer/widgets/icat/SaveToGalleryAndPublishOW.py +129 -0
  26. orangecontrib/tomwer/widgets/icat/__init__.py +13 -0
  27. orangecontrib/tomwer/widgets/icat/icons/add_gallery.png +0 -0
  28. orangecontrib/tomwer/widgets/icat/icons/add_gallery.svg +82 -0
  29. orangecontrib/tomwer/widgets/icat/icons/publish_processed_data.png +0 -0
  30. orangecontrib/tomwer/widgets/icat/icons/publish_processed_data.svg +95 -0
  31. orangecontrib/tomwer/widgets/icat/icons/raw_screenshots.png +0 -0
  32. orangecontrib/tomwer/widgets/icat/icons/raw_screenshots.svg +143 -0
  33. orangecontrib/tomwer/widgets/icons/tomwer_data_portal.png +0 -0
  34. orangecontrib/tomwer/widgets/icons/tomwer_data_portal.svg +76 -0
  35. orangecontrib/tomwer/widgets/reconstruction/AxisOW.py +22 -20
  36. orangecontrib/tomwer/widgets/reconstruction/CastNabuVolumeOW.py +19 -3
  37. orangecontrib/tomwer/widgets/reconstruction/NabuHelicalPrepareWeightsDoubleOW.py +184 -169
  38. orangecontrib/tomwer/widgets/reconstruction/NabuOW.py +23 -0
  39. orangecontrib/tomwer/widgets/reconstruction/NabuVolumeOW.py +39 -5
  40. orangecontrib/tomwer/widgets/reconstruction/SAAxisOW.py +18 -22
  41. orangecontrib/tomwer/widgets/reconstruction/SADeltaBetaOW.py +18 -26
  42. orangecontrib/tomwer/widgets/reconstruction/SinoNormOW.py +15 -19
  43. orangecontrib/tomwer/widgets/visualization/DataViewerOW.py +9 -9
  44. orangecontrib/tomwer/widgets/visualization/DiffViewerOW.py +1 -1
  45. orangecontrib/tomwer/widgets/visualization/LivesliceOW.py +1 -1
  46. orangecontrib/tomwer/widgets/visualization/NXtomoMetadataViewerOW.py +3 -3
  47. orangecontrib/tomwer/widgets/visualization/SinogramViewerOW.py +0 -1
  48. orangecontrib/tomwer/widgets/visualization/VolumeViewerOW.py +3 -29
  49. tomwer/__main__.py +7 -64
  50. tomwer/app/axis.py +3 -3
  51. tomwer/app/canvas.py +8 -0
  52. tomwer/app/canvas_launcher/config.py +16 -14
  53. tomwer/app/canvas_launcher/environ.py +1 -0
  54. tomwer/app/canvas_launcher/mainwindow.py +4 -1
  55. tomwer/app/darkref.py +1 -1
  56. tomwer/app/darkrefpatch.py +1 -1
  57. tomwer/app/diffframe.py +3 -3
  58. tomwer/app/imagekeyeditor.py +5 -5
  59. tomwer/app/imagekeyupgrader.py +5 -5
  60. tomwer/app/intensitynormalization.py +14 -13
  61. tomwer/app/{saaxis.py → multicor.py} +3 -3
  62. tomwer/app/{sadeltabeta.py → multipag.py} +3 -3
  63. tomwer/app/nabuapp.py +0 -11
  64. tomwer/app/radiostack.py +6 -4
  65. tomwer/app/samplemoved.py +3 -2
  66. tomwer/app/scanviewer.py +4 -2
  67. tomwer/app/sinogramviewer.py +3 -2
  68. tomwer/app/slicestack.py +3 -2
  69. tomwer/app/zstitching.py +88 -6
  70. tomwer/core/cluster/cluster.py +26 -0
  71. tomwer/core/log/logger.py +7 -5
  72. tomwer/core/process/conditions/filters.py +1 -1
  73. tomwer/core/process/control/datalistener/datalistener.py +19 -14
  74. tomwer/core/process/control/datawatcher/edfdwprocess.py +0 -9
  75. tomwer/core/process/control/nxtomoconcatenate.py +13 -13
  76. tomwer/core/process/control/nxtomomill.py +92 -34
  77. tomwer/core/process/control/scantransfer.py +20 -43
  78. tomwer/core/process/control/scanvalidator.py +3 -2
  79. tomwer/core/process/control/test/test_concatenate_nxtomos.py +9 -9
  80. tomwer/core/process/control/test/test_email.py +4 -4
  81. tomwer/core/process/control/test/test_h52nx_process.py +59 -7
  82. tomwer/core/process/control/test/test_volume_link.py +64 -64
  83. tomwer/core/process/control/timer.py +1 -1
  84. tomwer/core/process/control/volumesymlink.py +200 -200
  85. tomwer/core/process/edit/darkflatpatch.py +14 -15
  86. tomwer/core/process/edit/imagekeyeditor.py +41 -39
  87. tomwer/core/process/icat/__init__.py +0 -0
  88. tomwer/core/process/icat/createscreenshots.py +100 -0
  89. tomwer/core/process/icat/gallery.py +377 -0
  90. tomwer/core/process/icat/icatbase.py +36 -0
  91. tomwer/core/process/icat/publish.py +228 -0
  92. tomwer/core/process/icat/screenshots.py +27 -0
  93. tomwer/core/process/output.py +52 -0
  94. tomwer/core/process/reconstruction/axis/axis.py +280 -69
  95. tomwer/core/process/reconstruction/axis/mode.py +163 -48
  96. tomwer/core/process/reconstruction/axis/params.py +29 -21
  97. tomwer/core/process/reconstruction/darkref/darkrefs.py +41 -127
  98. tomwer/core/process/reconstruction/darkref/darkrefscopy.py +4 -3
  99. tomwer/core/process/reconstruction/darkref/params.py +1 -1
  100. tomwer/core/process/reconstruction/nabu/castvolume.py +4 -4
  101. tomwer/core/process/reconstruction/nabu/helical.py +9 -5
  102. tomwer/core/process/reconstruction/nabu/nabucommon.py +71 -78
  103. tomwer/core/process/reconstruction/nabu/nabuscores.py +425 -53
  104. tomwer/core/process/reconstruction/nabu/nabuslices.py +114 -93
  105. tomwer/core/process/reconstruction/nabu/nabuvolume.py +54 -27
  106. tomwer/core/process/reconstruction/nabu/plane.py +9 -0
  107. tomwer/core/process/reconstruction/nabu/settings.py +2 -2
  108. tomwer/core/process/reconstruction/nabu/utils.py +164 -26
  109. tomwer/core/process/reconstruction/output.py +108 -0
  110. tomwer/core/process/reconstruction/saaxis/saaxis.py +238 -264
  111. tomwer/core/process/reconstruction/sadeltabeta/sadeltabeta.py +151 -87
  112. tomwer/core/process/reconstruction/scores/params.py +7 -4
  113. tomwer/core/process/reconstruction/scores/scores.py +13 -0
  114. tomwer/core/process/reconstruction/test/test_axis_params.py +2 -2
  115. tomwer/core/process/reconstruction/test/test_darkref.py +3 -3
  116. tomwer/core/process/reconstruction/test/test_darkref_copy.py +7 -7
  117. tomwer/core/process/reconstruction/test/test_saaxis.py +3 -4
  118. tomwer/core/process/reconstruction/test/test_sadeltabeta.py +2 -2
  119. tomwer/core/process/stitching/nabustitcher.py +13 -12
  120. tomwer/core/process/task.py +34 -26
  121. tomwer/core/process/test/test_axis.py +13 -12
  122. tomwer/core/process/test/test_dark_and_flat.py +10 -7
  123. tomwer/core/process/test/test_data_transfer.py +10 -8
  124. tomwer/core/process/test/test_nabu.py +14 -6
  125. tomwer/core/process/test/test_normalization.py +4 -4
  126. tomwer/core/scan/blissscan.py +3 -3
  127. tomwer/core/scan/edfscan.py +13 -10
  128. tomwer/core/scan/hdf5scan.py +19 -530
  129. tomwer/core/scan/nxtomoscan.py +534 -0
  130. tomwer/core/scan/scanbase.py +72 -44
  131. tomwer/core/scan/scanfactory.py +13 -13
  132. tomwer/core/scan/test/test_edf.py +2 -2
  133. tomwer/core/scan/test/test_future_scan.py +3 -3
  134. tomwer/core/scan/test/test_h5.py +18 -16
  135. tomwer/core/scan/test/test_process_registration.py +4 -40
  136. tomwer/core/scan/test/test_scan.py +5 -78
  137. tomwer/core/settings.py +22 -2
  138. tomwer/core/test/test_scanutils.py +8 -7
  139. tomwer/core/test/test_utils.py +35 -28
  140. tomwer/core/tomwer_object.py +1 -1
  141. tomwer/core/utils/__init__.py +0 -466
  142. tomwer/core/utils/deprecation.py +1 -1
  143. tomwer/core/utils/dictutils.py +14 -0
  144. tomwer/core/utils/lbsram.py +35 -0
  145. tomwer/core/utils/nxtomoutils.py +1 -1
  146. tomwer/core/utils/scanutils.py +6 -6
  147. tomwer/core/utils/spec.py +263 -0
  148. tomwer/core/volume/edfvolume.py +6 -6
  149. tomwer/core/volume/hdf5volume.py +6 -6
  150. tomwer/core/volume/jp2kvolume.py +6 -6
  151. tomwer/core/volume/rawvolume.py +6 -6
  152. tomwer/core/volume/tiffvolume.py +12 -12
  153. tomwer/core/volume/volumefactory.py +2 -2
  154. tomwer/gui/cluster/slurm.py +274 -65
  155. tomwer/gui/cluster/supervisor.py +12 -0
  156. tomwer/gui/cluster/test/test_cluster.py +14 -2
  157. tomwer/gui/cluster/test/test_supervisor.py +3 -3
  158. tomwer/gui/configuration/__init__.py +0 -0
  159. tomwer/gui/{reconstruction/nabu → configuration}/action.py +1 -32
  160. tomwer/gui/configuration/level.py +22 -0
  161. tomwer/gui/control/actions.py +54 -0
  162. tomwer/gui/control/datalist.py +83 -16
  163. tomwer/gui/control/datalistener.py +4 -16
  164. tomwer/gui/control/datawatcher/controlwidget.py +2 -4
  165. tomwer/gui/control/datawatcher/datawatcher.py +1 -24
  166. tomwer/gui/control/{email.py → emailnotifier.py} +9 -18
  167. tomwer/gui/control/history.py +2 -2
  168. tomwer/gui/control/observations.py +2 -2
  169. tomwer/gui/control/reducedarkflatselector.py +9 -9
  170. tomwer/gui/control/selectorwidgetbase.py +36 -9
  171. tomwer/gui/control/serie/seriecreator.py +5 -22
  172. tomwer/gui/control/test/test_email.py +1 -1
  173. tomwer/gui/control/test/test_scanvalidator.py +6 -5
  174. tomwer/gui/control/test/test_single_tomo_obj.py +3 -3
  175. tomwer/gui/control/tomoobjdisplaymode.py +8 -0
  176. tomwer/gui/debugtools/datasetgenerator.py +3 -3
  177. tomwer/gui/edit/dkrfpatch.py +20 -26
  178. tomwer/gui/edit/imagekeyeditor.py +11 -12
  179. tomwer/gui/edit/nxtomoeditor.py +111 -44
  180. tomwer/gui/edit/nxtomowarmer.py +7 -6
  181. tomwer/gui/edit/test/test_dkrf_patch.py +13 -13
  182. tomwer/gui/edit/test/test_image_key_editor.py +3 -3
  183. tomwer/gui/edit/test/test_nx_editor.py +40 -16
  184. tomwer/gui/icat/__init__.py +0 -0
  185. tomwer/gui/icat/createscreenshots.py +80 -0
  186. tomwer/gui/icat/gallery.py +214 -0
  187. tomwer/gui/icat/publish.py +187 -0
  188. tomwer/gui/imagefromfile.py +2 -2
  189. tomwer/gui/qfolderdialog.py +24 -1
  190. tomwer/gui/reconstruction/axis/CompareImages.py +88 -168
  191. tomwer/gui/reconstruction/axis/axis.py +171 -57
  192. tomwer/gui/reconstruction/axis/radioaxis.py +122 -257
  193. tomwer/gui/reconstruction/darkref/darkrefcopywidget.py +3 -2
  194. tomwer/gui/reconstruction/darkref/darkrefwidget.py +2 -1
  195. tomwer/gui/reconstruction/nabu/castvolume.py +14 -3
  196. tomwer/gui/reconstruction/nabu/check.py +9 -9
  197. tomwer/gui/reconstruction/nabu/helical.py +29 -12
  198. tomwer/gui/reconstruction/nabu/nabuconfig/base.py +2 -4
  199. tomwer/gui/reconstruction/nabu/nabuconfig/nabuconfig.py +2 -1
  200. tomwer/gui/reconstruction/nabu/nabuconfig/output.py +126 -35
  201. tomwer/gui/reconstruction/nabu/nabuconfig/phase.py +39 -32
  202. tomwer/gui/reconstruction/nabu/nabuconfig/preprocessing.py +222 -31
  203. tomwer/gui/reconstruction/nabu/nabuconfig/reconstruction.py +57 -27
  204. tomwer/gui/reconstruction/nabu/nabuflow.py +12 -20
  205. tomwer/gui/reconstruction/nabu/slices.py +10 -11
  206. tomwer/gui/reconstruction/nabu/volume.py +22 -10
  207. tomwer/gui/reconstruction/normalization/intensity.py +18 -48
  208. tomwer/gui/reconstruction/saaxis/corrangeselector.py +8 -24
  209. tomwer/gui/reconstruction/saaxis/dimensionwidget.py +1 -1
  210. tomwer/gui/reconstruction/saaxis/saaxis.py +9 -21
  211. tomwer/gui/reconstruction/sadeltabeta/saadeltabeta.py +45 -12
  212. tomwer/gui/reconstruction/scores/control.py +2 -9
  213. tomwer/gui/reconstruction/scores/scoreplot.py +13 -11
  214. tomwer/gui/reconstruction/test/test_axis.py +41 -16
  215. tomwer/gui/reconstruction/test/test_nabu.py +31 -9
  216. tomwer/gui/reconstruction/test/test_saaxis.py +3 -3
  217. tomwer/gui/reconstruction/test/test_sadeltabeta.py +12 -2
  218. tomwer/gui/settings.py +5 -28
  219. tomwer/gui/stackplot.py +2 -5
  220. tomwer/gui/stitching/action.py +49 -0
  221. tomwer/gui/stitching/config/axisparams.py +7 -24
  222. tomwer/gui/stitching/config/output.py +10 -8
  223. tomwer/gui/stitching/config/positionoveraxis.py +22 -23
  224. tomwer/gui/stitching/normalization.py +117 -0
  225. tomwer/gui/stitching/stitchandbackground.py +4 -6
  226. tomwer/gui/stitching/stitching.py +267 -45
  227. tomwer/gui/stitching/stitching_preview.py +62 -55
  228. tomwer/gui/stitching/stitching_raw.py +13 -12
  229. tomwer/gui/stitching/z_stitching/fineestimation.py +0 -60
  230. tomwer/gui/utils/buttons.py +112 -29
  231. tomwer/gui/utils/inputwidget.py +43 -25
  232. tomwer/gui/utils/lineselector/lineselector.py +1 -1
  233. tomwer/gui/utils/scandescription.py +4 -0
  234. tomwer/gui/utils/step.py +144 -0
  235. tomwer/gui/utils/unitsystem.py +2 -5
  236. tomwer/gui/utils/vignettes.py +176 -15
  237. tomwer/gui/visualization/dataviewer.py +48 -35
  238. tomwer/gui/visualization/diffviewer/diffviewer.py +7 -16
  239. tomwer/gui/visualization/diffviewer/shiftwidget.py +2 -5
  240. tomwer/gui/visualization/scanoverview.py +1 -1
  241. tomwer/gui/visualization/sinogramviewer.py +20 -36
  242. tomwer/gui/visualization/test/test_diffviewer.py +3 -3
  243. tomwer/gui/visualization/test/test_nx_tomo_metadata_viewer.py +4 -4
  244. tomwer/gui/visualization/test/test_sinogramviewer.py +2 -2
  245. tomwer/gui/visualization/test/test_stacks.py +3 -3
  246. tomwer/gui/visualization/test/test_volumeviewer.py +65 -67
  247. tomwer/gui/visualization/volumeviewer.py +114 -113
  248. tomwer/io/utils/h5pyutils.py +3 -3
  249. tomwer/io/utils/raw_and_processed_data.py +84 -0
  250. tomwer/io/utils/tomoobj.py +4 -6
  251. tomwer/io/utils/utils.py +7 -7
  252. tomwer/resources/gui/icons/parameters.svg +1 -1
  253. tomwer/resources/gui/icons/ruler.png +0 -0
  254. tomwer/resources/gui/icons/ruler.svg +273 -0
  255. tomwer/resources/gui/icons/short_description.png +0 -0
  256. tomwer/resources/gui/icons/short_description.svg +58 -0
  257. tomwer/resources/gui/icons/url.png +0 -0
  258. tomwer/resources/gui/icons/url.svg +58 -0
  259. tomwer/resources/gui/illustrations/no_rot.svg +1 -1
  260. tomwer/synctools/stacks/edit/darkflatpatch.py +19 -14
  261. tomwer/synctools/stacks/edit/imagekeyeditor.py +2 -2
  262. tomwer/synctools/stacks/reconstruction/axis.py +4 -4
  263. tomwer/synctools/stacks/reconstruction/castvolume.py +22 -7
  264. tomwer/synctools/stacks/reconstruction/dkrefcopy.py +25 -20
  265. tomwer/synctools/stacks/reconstruction/nabu.py +2 -2
  266. tomwer/synctools/stacks/reconstruction/normalization.py +2 -2
  267. tomwer/synctools/stacks/reconstruction/saaxis.py +2 -2
  268. tomwer/synctools/stacks/reconstruction/sadeltabeta.py +2 -2
  269. tomwer/synctools/test/test_darkRefs.py +7 -58
  270. tomwer/synctools/test/test_foldertransfer.py +6 -6
  271. tomwer/synctools/utils/scanstages.py +6 -6
  272. tomwer/tests/conftest.py +34 -0
  273. tomwer/tests/datasets.py +13 -0
  274. tomwer/tests/test_scripts.py +91 -41
  275. tomwer/tests/utils.py +5 -0
  276. tomwer/third_part/WaitingOverlay.py +110 -0
  277. tomwer/third_part/__init__.py +0 -0
  278. tomwer/version.py +2 -2
  279. tomwer-1.3.12-py3.11-nspkg.pth +1 -0
  280. {tomwer-1.2.1.dist-info → tomwer-1.3.12.dist-info}/METADATA +73 -58
  281. {tomwer-1.2.1.dist-info → tomwer-1.3.12.dist-info}/RECORD +287 -286
  282. {tomwer-1.2.1.dist-info → tomwer-1.3.12.dist-info}/WHEEL +1 -1
  283. orangecontrib/tomwer/widgets/reconstruction/TofuOW.py +0 -197
  284. orangecontrib/tomwer/widgets/reconstruction/icons/XY_lamino.svg +0 -168
  285. orangecontrib/tomwer/widgets/reconstruction/icons/XZ_lamino.svg +0 -275
  286. orangecontrib/tomwer/widgets/reconstruction/icons/YZ_lamino.svg +0 -182
  287. tomwer/app/lamino.py +0 -143
  288. tomwer/core/process/reconstruction/lamino/__init__.py +0 -1
  289. tomwer/core/process/reconstruction/lamino/tofu.py +0 -1000
  290. tomwer/core/process/test/test_lamino.py +0 -76
  291. tomwer/core/test/test_lamino.py +0 -92
  292. tomwer/gui/reconstruction/lamino/__init__.py +0 -31
  293. tomwer/gui/reconstruction/lamino/tofu/TofuOptionLoader.py +0 -107
  294. tomwer/gui/reconstruction/lamino/tofu/__init__.py +0 -1
  295. tomwer/gui/reconstruction/lamino/tofu/misc.py +0 -148
  296. tomwer/gui/reconstruction/lamino/tofu/projections.py +0 -896
  297. tomwer/gui/reconstruction/lamino/tofu/settings.py +0 -75
  298. tomwer/gui/reconstruction/lamino/tofu/tofu.py +0 -432
  299. tomwer/gui/reconstruction/lamino/tofu/tofuexpert.py +0 -567
  300. tomwer/gui/reconstruction/lamino/tofu/tofuoutput.py +0 -760
  301. tomwer/gui/reconstruction/test/test_lamino.py +0 -189
  302. tomwer/resources/gui/icons/esrf_1.svg +0 -307
  303. tomwer/resources/gui/icons/lamino_parameters.svg +0 -70
  304. tomwer/resources/gui/icons/triangle.svg +0 -80
  305. tomwer/resources/gui/illustrations/lamino_angle.png +0 -0
  306. tomwer/resources/gui/illustrations/lamino_angle.svg +0 -509
  307. tomwer/resources/gui/illustrations/lamino_beta_angle.png +0 -0
  308. tomwer/resources/gui/illustrations/lamino_beta_angle.svg +0 -97
  309. tomwer/resources/gui/illustrations/lamino_theta_angle.png +0 -0
  310. tomwer/resources/gui/illustrations/lamino_theta_angle.svg +0 -368
  311. tomwer/resources/gui/illustrations/manual_slice.png +0 -0
  312. tomwer/resources/gui/illustrations/manual_slice.svg +0 -221
  313. tomwer/resources/gui/illustrations/psi_angle.png +0 -0
  314. tomwer/resources/gui/illustrations/psi_angle.svg +0 -479
  315. tomwer/resources/gui/illustrations/rotation_center.png +0 -0
  316. tomwer/resources/gui/illustrations/rotation_center.svg +0 -276
  317. tomwer/resources/gui/illustrations/slice_stack.png +0 -0
  318. tomwer/resources/gui/illustrations/slice_stack.svg +0 -266
  319. tomwer/resources/gui/illustrations/xy_slice.png +0 -0
  320. tomwer/resources/gui/illustrations/xy_slice.svg +0 -269
  321. tomwer/resources/gui/illustrations/xz_slice.png +0 -0
  322. tomwer/resources/gui/illustrations/xz_slice.svg +0 -270
  323. tomwer/resources/gui/illustrations/yz_slice.png +0 -0
  324. tomwer/resources/gui/illustrations/yz_slice.svg +0 -270
  325. tomwer/synctools/stacks/reconstruction/lamino.py +0 -233
  326. tomwer/synctools/test/test_scanstages.py +0 -162
  327. tomwer/tests/utils/__init__.py +0 -247
  328. tomwer/tests/utils/utilstest.py +0 -220
  329. tomwer-1.2.1-py3.11-nspkg.pth +0 -1
  330. /tomwer/core/process/control/{email.py → emailnotifier.py} +0 -0
  331. {tomwer-1.2.1.dist-info → tomwer-1.3.12.dist-info}/LICENSE +0 -0
  332. {tomwer-1.2.1.dist-info → tomwer-1.3.12.dist-info}/entry_points.txt +0 -0
  333. {tomwer-1.2.1.dist-info → tomwer-1.3.12.dist-info}/namespace_packages.txt +0 -0
  334. {tomwer-1.2.1.dist-info → tomwer-1.3.12.dist-info}/top_level.txt +0 -0
@@ -1,35 +1,4 @@
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__ = "05/08/2020"
29
-
30
-
31
1
  import logging
32
- import os
33
2
  import weakref
34
3
  import h5py
35
4
  from typing import Union
@@ -46,11 +15,10 @@ from tomoscan.esrf.volume.hdf5volume import HDF5Volume
46
15
  from tomoscan.factory import Factory
47
16
  from tomoscan.identifier import VolumeIdentifier
48
17
  from tomoscan.volumebase import VolumeBase
49
- from tomoscan.io import HDF5File
50
18
 
51
19
  from tomwer.core.scan.scanbase import TomwerScanBase
52
- from tomwer.core.utils.ftseriesutils import get_vol_file_shape
53
20
  from tomwer.gui.visualization.reconstructionparameters import ReconstructionParameters
21
+ from silx.gui.widgets.WaitingOverlay import WaitingOverlay
54
22
 
55
23
  _logger = logging.getLogger(__name__)
56
24
 
@@ -247,6 +215,9 @@ class _TomoApplicationContext(DataViewHooks):
247
215
  class VolumeViewer(qt.QMainWindow):
248
216
  def __init__(self, parent):
249
217
  qt.QMainWindow.__init__(self, parent)
218
+
219
+ self._volume_loaded_in_background = (None, None)
220
+ # store the volume loaded in the background and the thread used for it as (volume_identifier, thread)
250
221
  self._centralWidget = DataViewerFrame(parent=self)
251
222
  self.__context = _TomoApplicationContext(self)
252
223
  self._centralWidget.setGlobalHooks(self.__context)
@@ -254,6 +225,12 @@ class VolumeViewer(qt.QMainWindow):
254
225
  qt.QSizePolicy.Expanding, qt.QSizePolicy.Expanding
255
226
  )
256
227
  self.setCentralWidget(self._centralWidget)
228
+ # waiter overlay to notify user loading is on-going
229
+ self._waitingOverlay = WaitingOverlay(self._centralWidget)
230
+ self._waitingOverlay.setIconSize(qt.QSize(30, 30))
231
+ self._waitingOverlay.hide()
232
+
233
+ # display scan information when possible
257
234
  self._infoWidget = _ScanInfo(parent=self)
258
235
 
259
236
  # top level dock widget to display information regarding the scan
@@ -278,7 +255,6 @@ class VolumeViewer(qt.QMainWindow):
278
255
  """pointer to the hdf5 file since we want to set the HDF5Dataset for
279
256
  loading data on the fly and avoid loading everything into memory for
280
257
  hdf5."""
281
- self.__first_load = True
282
258
  self.__last_mode = None
283
259
 
284
260
  def _close_h5_file(self):
@@ -308,9 +284,12 @@ class VolumeViewer(qt.QMainWindow):
308
284
  if volume is None:
309
285
  return
310
286
  self._set_volumes(volumes=(volume,))
311
- # TODO in the future: do the equivalent of setScan from the volume metadata
312
287
 
313
288
  def _get_data_volume(self, volume: VolumeBase):
289
+ """
290
+ load the data of the requested volume.
291
+ :return: (data: Optional[str], state: str) state can be "loaded", "loading" or "failed"
292
+ """
314
293
  if not isinstance(volume, VolumeBase):
315
294
  raise TypeError(
316
295
  f"volume is expected to be an instance of {VolumeBase}. Not {type(volume)}"
@@ -318,18 +297,73 @@ class VolumeViewer(qt.QMainWindow):
318
297
 
319
298
  self._close_h5_file()
320
299
  if isinstance(volume, HDF5Volume):
321
- self._h5_file = HDF5File(filename=volume.data_url.file_path(), mode="r")
300
+ try:
301
+ self._h5_file = h5py.File(volume.data_url.file_path(), mode="r")
302
+ except OSError:
303
+ self._h5_file = h5py.File(
304
+ volume.data_url.file_path(),
305
+ mode="r",
306
+ libver="latest",
307
+ swmr=True,
308
+ )
309
+
322
310
  if volume.data_url.data_path() in self._h5_file:
323
311
  data = self._h5_file[volume.data_url.data_path()]
312
+ state = "loaded"
324
313
  else:
325
314
  data = None
315
+ state = "failed"
316
+ elif volume.data is not None:
317
+ data = volume.data
318
+ state = "loaded"
326
319
  else:
327
- _logger.warning(
328
- "Attempt to set a non HDF5 volume to the viewer. This requires to load all the data in memory. This can take a while"
329
- )
330
- data = volume.data if volume.data is not None else volume.load_data()
320
+ volume_id, _ = self._volume_loaded_in_background
321
+ if volume_id == volume.get_identifier().to_str():
322
+ # special case if the user send several time the volume which is currently loading
323
+ # in this case we just want to ignore the request to avoid reloading the volume
324
+ # can happen in the case of a large volume that take some time to be loaded.
325
+ pass
326
+ else:
327
+ _logger.warning(
328
+ "Attempt to set a non HDF5 volume to the viewer. This requires to load all the data in memory. This can take a while"
329
+ )
330
+ self._stopExistingLoaderThread()
331
+ self._loadAndDisplay(volume)
332
+ state = "loading"
333
+ data = None
331
334
 
332
- return data
335
+ return data, state
336
+
337
+ def _loaderThreadFinished(self):
338
+ """Callback activated when a VolumeLoader thread is finished"""
339
+ sender = self.sender()
340
+ if not isinstance(sender, VolumeLoader):
341
+ raise TypeError("sender is expected to be a VolumeLoader")
342
+
343
+ self._stopExistingLoaderThread()
344
+ if sender.volume.data is None:
345
+ _logger.error(f"Failed to load volume {sender.volume.get_identifier()}")
346
+ else:
347
+ self.setVolume(sender.volume)
348
+
349
+ def _loadAndDisplay(self, volume):
350
+ """Load a thread and add a callback when loading is done"""
351
+ loader_thread = VolumeLoader(volume=volume)
352
+ self._volume_loaded_in_background = (
353
+ volume.get_identifier().to_str(),
354
+ loader_thread,
355
+ )
356
+ loader_thread.finished.connect(self._loaderThreadFinished)
357
+ loader_thread.start()
358
+
359
+ def _stopExistingLoaderThread(self):
360
+ """Will stop any existing loader thread. Make sure we load one volume at most at the time"""
361
+ _, loader_thread = self._volume_loaded_in_background
362
+ if loader_thread is not None:
363
+ loader_thread.finished.disconnect(self._loaderThreadFinished)
364
+ if loader_thread.isRunning():
365
+ loader_thread.quit()
366
+ self._volume_loaded_in_background = (None, None)
333
367
 
334
368
  def _set_volumes(self, volumes: tuple):
335
369
  self.clear()
@@ -351,11 +385,7 @@ class VolumeViewer(qt.QMainWindow):
351
385
  f"Volume should be an instance of a Volume, a VolumeIdentifier or a string refering to a VolumeIdentifier. {type(volume)} provided"
352
386
  )
353
387
 
354
- data = self._get_data_volume(volume)
355
- # set volume dataset
356
- if data is not None:
357
- self._set_volume(data)
358
- # set reconstruction parameters
388
+ # warning: load metadata before data because can get some conflict with the HDF5 reader flag if done after
359
389
  try:
360
390
  # warning: limitation expected for .vol as it gets two configuration file. The default one is vol.info and does not contains
361
391
  # any of the metadata 'distance', 'pixel size'... but it is here for backward compatiblity
@@ -367,81 +397,52 @@ class VolumeViewer(qt.QMainWindow):
367
397
  f"Unable to set reconstruction parameters from {volume.data_url}. Not handled for pyhst reconstructions. Error is {e}"
368
398
  )
369
399
 
400
+ data, state = self._get_data_volume(volume)
401
+ # set volume dataset
402
+ if state == "loading":
403
+ self._waitingOverlay.show()
404
+ elif state == "loaded":
405
+ self._waitingOverlay.hide()
406
+ if data is not None:
407
+ self._set_volume(data)
408
+
409
+ elif state == "failed":
410
+ _logger.warning(
411
+ f"Failed to load data from {volume.get_identifier().to_str()}"
412
+ )
413
+ return
414
+
370
415
  def _set_volume(self, volume: Union[numpy.ndarray, h5py.Dataset]):
371
416
  self._centralWidget.setData(volume)
372
- if self.__first_load:
373
- self._centralWidget.setDisplayMode(IMAGE_MODE)
374
- self.__first_load = False
375
- elif self.__last_mode is not None:
376
- self._centralWidget.setDisplayMode(self.__last_mode)
417
+ self._centralWidget.setDisplayMode(self.__last_mode or IMAGE_MODE)
377
418
 
378
419
  def clear(self):
379
- self.__last_mode = self._centralWidget.displayMode()
420
+ if self._centralWidget.displayMode():
421
+ self.__last_mode = self._centralWidget.displayMode()
380
422
  self._close_h5_file()
381
- # self._centralWidget.setData(None)
382
423
  self._infoWidget.clear()
424
+ self._centralWidget.setData(None)
425
+ # if clear stop loading any volume
426
+ self._stopExistingLoaderThread()
383
427
 
384
428
  def sizeHint(self):
385
429
  return qt.QSize(600, 600)
386
430
 
387
- # TODO: should be merged with dataviewer._load_vol function ?
388
- def _load_vol(self, url):
389
- """
390
- load a .vol file
391
- """
392
- if url.file_path().lower().endswith(".vol.info"):
393
- info_file = url.file_path()
394
- raw_file = url.file_path().replace(".vol.info", ".vol")
395
- else:
396
- assert url.file_path().lower().endswith(".vol")
397
- raw_file = url.file_path()
398
- info_file = url.file_path().replace(".vol", ".vol.info")
399
431
 
400
- if not os.path.exists(raw_file):
401
- data = None
402
- mess = f"Can't find raw data file {raw_file} associated with {info_file}"
403
- _logger.warning(mess)
404
- elif not os.path.exists(info_file):
405
- mess = f"Can't find info file {info_file} associated with {raw_file}"
406
- _logger.warning(mess)
407
- data = None
408
- else:
409
- shape = get_vol_file_shape(info_file)
410
- if None in shape:
411
- _logger.warning(f"Fail to retrieve data shape for {info_file}.")
412
- data = None
413
- else:
414
- try:
415
- numpy.zeros(shape)
416
- except MemoryError:
417
- data = None
418
- _logger.warning(f"Raw file {raw_file} is too large for being read")
419
- else:
420
- data = numpy.fromfile(
421
- raw_file, dtype=numpy.float32, count=-1, sep=""
422
- )
423
- try:
424
- data = data.reshape(shape)
425
- except ValueError:
426
- _logger.warning(
427
- f"unable to fix shape for raw file {raw_file}. "
428
- "Look for information in {info_file}"
429
- )
430
- try:
431
- sqr = int(numpy.sqrt(len(data)))
432
- shape = (1, sqr, sqr)
433
- data = data.reshape(shape)
434
- except ValueError:
435
- _logger.info(
436
- f"deduction of shape size for {raw_file} " "failed"
437
- )
438
- data = None
439
- else:
440
- _logger.warning(
441
- f"try deducing shape size for {raw_file} "
442
- "might be an incorrect interpretation"
443
- )
444
- if url.data_slice() is None:
445
- return data
446
- else:
447
- return data[url.data_slice()]
432
+ class VolumeLoader(qt.QThread):
433
+ """
434
+ simple thread that load a volume in memory
435
+ """
436
+
437
+ def __init__(self, volume: VolumeBase) -> None:
438
+ super().__init__()
439
+ if not isinstance(volume, VolumeBase):
440
+ raise TypeError()
441
+ self.__volume = volume
442
+
443
+ def run(self):
444
+ self.__volume.load_data(store=True)
445
+
446
+ @property
447
+ def volume(self):
448
+ return self.__volume
@@ -35,7 +35,7 @@ import contextlib
35
35
 
36
36
  import h5py
37
37
  from silx.io.url import DataUrl
38
- from tomoscan.io import HDF5File
38
+ from silx.io.utils import open as open_hdf5
39
39
 
40
40
 
41
41
  class _BaseReader(contextlib.AbstractContextManager):
@@ -60,7 +60,7 @@ class EntryReader(_BaseReader):
60
60
  """Context manager used to read a bliss node"""
61
61
 
62
62
  def __enter__(self):
63
- self._file_handler = HDF5File(filename=self._url.file_path(), mode="r")
63
+ self._file_handler = open_hdf5(filename=self._url.file_path())
64
64
  entry = self._file_handler[self._url.data_path()]
65
65
  if not isinstance(entry, h5py.Group):
66
66
  raise ValueError("Data path should point to a bliss node (h5py.Group)")
@@ -71,7 +71,7 @@ class DatasetReader(_BaseReader):
71
71
  """Context manager used to read a bliss node"""
72
72
 
73
73
  def __enter__(self):
74
- self._file_handler = HDF5File(filename=self._url.file_path(), mode="r")
74
+ self._file_handler = open_hdf5(filename=self._url.file_path())
75
75
  entry = self._file_handler[self._url.data_path()]
76
76
  if not isinstance(entry, h5py.Dataset):
77
77
  raise ValueError("Data path should point to a dtaset (h5py.Dataset)")
@@ -0,0 +1,84 @@
1
+ """some utils to move a path to process_data or to raw_data"""
2
+
3
+ import os
4
+ from nxtomomill.converter.hdf5.utils import (
5
+ RAW_DATA_DIR_NAME,
6
+ PROCESSED_DATA_DIR_NAME,
7
+ )
8
+
9
+
10
+ def to_raw_data_path(file_path: str):
11
+ """convert a path to 'RAW_DATA' when possible"""
12
+ split_path = file_path.split(os.sep)
13
+ # reverse it to find the lower level value of 'PROCESSED_DATA_DIR_NAME' if by any 'chance' has several in the path
14
+ # in this case this is most likely what we want
15
+ split_path = split_path[::-1]
16
+ # check if already contain in a "RAW_DATA_DIR_NAME" directory
17
+ try:
18
+ index_raw_data = split_path.index(RAW_DATA_DIR_NAME)
19
+ except ValueError:
20
+ index_raw_data = None
21
+
22
+ try:
23
+ index_processed_data = split_path.index(PROCESSED_DATA_DIR_NAME)
24
+ except ValueError:
25
+ # if the value is not in the list
26
+ pass
27
+ else:
28
+ if index_raw_data is None or index_raw_data > index_processed_data:
29
+ # make sure we are not already in a 'PROCESSED_DATA' directory. Not sure it will never happen but safer
30
+ split_path[index_processed_data] = RAW_DATA_DIR_NAME
31
+
32
+ # reorder path to original
33
+ split_path = split_path[::-1]
34
+ return os.sep.join(split_path)
35
+
36
+
37
+ def to_processed_data_path(file_path: str):
38
+ """convert a path to 'PROCESSED_DATA' when possible"""
39
+ split_path = file_path.split(os.sep)
40
+ # reverse it to find the lower level value of '_RAW_DATA_DIR_NAME' if by any 'chance' has several in the path
41
+ # in this case this is most likely what we want
42
+ split_path = split_path[::-1]
43
+ # check if already contain in a "PROCESSED_DATA" directory
44
+ try:
45
+ index_processed_data = split_path.index(PROCESSED_DATA_DIR_NAME)
46
+ except ValueError:
47
+ index_processed_data = None
48
+
49
+ try:
50
+ index_raw_data = split_path.index(RAW_DATA_DIR_NAME)
51
+ except ValueError:
52
+ # if the value is not in the list
53
+ pass
54
+ else:
55
+ if index_processed_data is None or index_raw_data < index_processed_data:
56
+ # make sure we are not already in a 'PROCESSED_DATA' directory. Not sure it will never happen but safer
57
+ split_path[index_raw_data] = PROCESSED_DATA_DIR_NAME
58
+
59
+ # reorder path to original
60
+ split_path = split_path[::-1]
61
+ return os.sep.join(split_path)
62
+
63
+
64
+ def file_is_on_processed_data(file_path: str):
65
+ """Util that raises an error if path is not in PROCESSED_DATA folder"""
66
+ split_path = file_path.split(os.sep)
67
+ # reverse it to find the lower level value of 'PROCESSED_DATA_DIR_NAME' if by any 'chance' has several in the path
68
+ # in this case this is most likely what we want
69
+ split_path = split_path[::-1]
70
+ # check if already contain in a "RAW_DATA_DIR_NAME" directory
71
+ try:
72
+ index_raw_data = split_path.index(RAW_DATA_DIR_NAME)
73
+ except ValueError:
74
+ index_raw_data = None
75
+
76
+ try:
77
+ index_processed_data = split_path.index(PROCESSED_DATA_DIR_NAME)
78
+ except ValueError:
79
+ # if the value is not in the list
80
+ index_processed_data = None
81
+ else:
82
+ if index_raw_data is not None and index_raw_data < index_processed_data:
83
+ return False
84
+ return index_processed_data is not None
@@ -36,7 +36,7 @@ def get_tomo_objs_instances(tomo_objs: tuple) -> tuple:
36
36
  """
37
37
  instances = []
38
38
  has_scans = False
39
- has_vols = True
39
+ has_vols = False
40
40
  for tomo_obj in tomo_objs:
41
41
 
42
42
  def get_scans():
@@ -45,9 +45,7 @@ def get_tomo_objs_instances(tomo_objs: tuple) -> tuple:
45
45
  except Exception:
46
46
  try:
47
47
  return ScanFactory.create_scan_objects(tomo_obj)
48
- except Exception as e:
49
- raise e
50
- print("error is", e)
48
+ except Exception:
51
49
  return tuple()
52
50
 
53
51
  def get_volumes():
@@ -68,12 +66,12 @@ def get_tomo_objs_instances(tomo_objs: tuple) -> tuple:
68
66
  # we start by scan because they have a 'definition' as NXtomo available.
69
67
  # otherwise we might get some entries defined as scan AND volume...
70
68
  # for stitching we expect users to ask of for one or the other.
71
- volumes = get_volumes()
69
+ volumes = get_volumes() or ()
72
70
  if len(volumes) > 0:
73
71
  has_vols = True
74
72
  instances.extend(volumes)
75
73
  else:
76
- has_scans = True
74
+ has_scans = len(scans_found) > 0
77
75
  instances.extend(scans_found)
78
76
 
79
77
  return tuple(instances), (has_scans, has_vols)
tomwer/io/utils/utils.py CHANGED
@@ -37,9 +37,9 @@ import os
37
37
  import h5py
38
38
  import numpy.lib.npyio
39
39
  from PIL import Image
40
+ from silx.io.utils import open as open_hdf5
40
41
  from tomoscan.esrf import has_glymur
41
42
  from tomoscan.esrf.scan.utils import get_data as tomoscan_get_data
42
- from tomoscan.io import HDF5File
43
43
  from typing import Union
44
44
 
45
45
  from tomwer.core.utils import ftseriesutils
@@ -190,7 +190,7 @@ def get_linked_files_with_entry(hdf5_file: str, entry: str) -> set:
190
190
  final_datasets = set() # file_path, dataset_path
191
191
  treated_items = set() # abs_file_path, data_path
192
192
 
193
- abs_hdf5_file = os.path.realpath(hdf5_file)
193
+ abs_hdf5_file = os.path.abspath(hdf5_file)
194
194
  items_to_treat.add((abs_hdf5_file, hdf5_file, entry))
195
195
 
196
196
  while len(items_to_treat) > 0:
@@ -201,14 +201,14 @@ def get_linked_files_with_entry(hdf5_file: str, entry: str) -> set:
201
201
  if item in treated_items:
202
202
  continue
203
203
  dirname = os.path.dirname(abs_file_path)
204
- with HDF5File(abs_file_path, mode="r") as h5f:
204
+ with open_hdf5(abs_file_path) as h5f:
205
205
  node = h5f.get(data_path, getlink=True)
206
206
  if isinstance(node, h5py.ExternalLink):
207
207
  ext_file_path = node.filename
208
208
  if not os.path.isabs(ext_file_path):
209
209
  ext_file_path = os.path.join(dirname, ext_file_path)
210
210
  items_to_treat.add(
211
- (os.path.realpath(ext_file_path), node.filename, node.path)
211
+ (os.path.abspath(ext_file_path), node.filename, node.path)
212
212
  )
213
213
  node = h5f.get(data_path, getlink=False)
214
214
  if isinstance(node, h5py.Dataset) and node.is_virtual:
@@ -241,7 +241,7 @@ def get_linked_files_with_vds(hdf5_file: str, dataset_path: str) -> set:
241
241
  final_datasets = set() # file_path, dataset_path
242
242
  treated_items = set() # abs_file_path, dataset_path
243
243
 
244
- abs_hdf5_file = os.path.realpath(hdf5_file)
244
+ abs_hdf5_file = os.path.abspath(hdf5_file)
245
245
  items_to_treat.add((abs_hdf5_file, hdf5_file, dataset_path))
246
246
 
247
247
  while len(items_to_treat) > 0:
@@ -252,7 +252,7 @@ def get_linked_files_with_vds(hdf5_file: str, dataset_path: str) -> set:
252
252
  if item in treated_items:
253
253
  continue
254
254
  dirname = os.path.dirname(abs_file_path)
255
- with HDF5File(abs_file_path, mode="r") as h5f:
255
+ with open_hdf5(abs_file_path) as h5f:
256
256
  dataset = h5f[dataset_path]
257
257
  if dataset.is_virtual:
258
258
  for vs_info in dataset.virtual_sources():
@@ -261,7 +261,7 @@ def get_linked_files_with_vds(hdf5_file: str, dataset_path: str) -> set:
261
261
  vs_file_path = os.path.join(dirname, vs_file_path)
262
262
  items_to_treat.add(
263
263
  (
264
- os.path.realpath(vs_file_path),
264
+ os.path.abspath(vs_file_path),
265
265
  vs_info.file_name,
266
266
  vs_info.dset_name,
267
267
  )
@@ -14,7 +14,7 @@
14
14
  id="svg2985"
15
15
  version="1.1"
16
16
  inkscape:version="0.48.5 r10040"
17
- sodipodi:docname="lamino_parameters.svg"
17
+ sodipodi:docname="parameters.svg"
18
18
  inkscape:export-filename="/nobackup/linazimov/payno/dev/esrf/ID06/id06workflow/resources/gui/icons/input.png"
19
19
  inkscape:export-xdpi="200"
20
20
  inkscape:export-ydpi="200">
Binary file