tomwer 1.3.26__py3-none-any.whl → 1.4.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 (638) hide show
  1. orangecontrib/tomwer/orange/managedprocess.py +1 -29
  2. orangecontrib/tomwer/orange/settings.py +2 -28
  3. orangecontrib/tomwer/tests/TestAcquisition.py +220 -0
  4. orangecontrib/tomwer/tutorials/append_raw_darks_and_flats_frames_to_NXtomos.ows +2 -2
  5. orangecontrib/tomwer/tutorials/copy_reduced_darks_and_flats_meth1.ows +1 -1
  6. orangecontrib/tomwer/tutorials/{icat_publication.ows → drac_publication.ows} +7 -21
  7. orangecontrib/tomwer/tutorials/id16b/ID16b_full_volume.ows +22 -0
  8. orangecontrib/tomwer/tutorials/id16b/ID16b_insitu.ows +302 -0
  9. orangecontrib/tomwer/tutorials/id16b/ID16b_normalization.ows +218 -0
  10. orangecontrib/tomwer/tutorials/simple_volume_to_slurm_reconstruction.ows +29 -24
  11. orangecontrib/tomwer/tutorials/test_cor.ows +19 -0
  12. orangecontrib/tomwer/tutorials/volume_casting_on_slurm.ows +54 -0
  13. orangecontrib/tomwer/widgets/__init__.py +3 -5
  14. orangecontrib/tomwer/widgets/cluster/FutureSupervisorOW.py +35 -54
  15. orangecontrib/tomwer/widgets/cluster/SlurmClusterOW.py +1 -31
  16. orangecontrib/tomwer/widgets/control/AdvancementOW.py +1 -29
  17. orangecontrib/tomwer/widgets/control/DataDiscoveryOW.py +5 -4
  18. orangecontrib/tomwer/widgets/control/DataListenerOW.py +1 -29
  19. orangecontrib/tomwer/widgets/control/DataSelectorOW.py +11 -30
  20. orangecontrib/tomwer/widgets/control/DataTransfertOW.py +2 -28
  21. orangecontrib/tomwer/widgets/control/DataValidatorOW.py +1 -28
  22. orangecontrib/tomwer/widgets/control/DataWatcherOW.py +1 -28
  23. orangecontrib/tomwer/widgets/control/EDF2NXTomomillOW.py +48 -51
  24. orangecontrib/tomwer/widgets/control/EmailOW.py +12 -2
  25. orangecontrib/tomwer/widgets/control/FilterOW.py +1 -28
  26. orangecontrib/tomwer/widgets/control/NXTomomillOW.py +37 -53
  27. orangecontrib/tomwer/widgets/control/NXtomoConcatenate.py +21 -20
  28. orangecontrib/tomwer/widgets/control/NotifierOW.py +9 -28
  29. orangecontrib/tomwer/widgets/control/ReduceDarkFlatSelectorOW.py +3 -2
  30. orangecontrib/tomwer/widgets/control/SingleTomoObjOW.py +1 -27
  31. orangecontrib/tomwer/widgets/control/TimerOW.py +9 -28
  32. orangecontrib/tomwer/widgets/control/TomoObjSeriesOW.py +58 -0
  33. orangecontrib/tomwer/widgets/control/VolumeSelector.py +8 -30
  34. orangecontrib/tomwer/widgets/control/icons/nxtomomill.svg +173 -119
  35. orangecontrib/tomwer/widgets/control/icons/reduced_darkflat_selector.png +0 -0
  36. orangecontrib/tomwer/widgets/control/icons/reduced_darkflat_selector.svg +55 -195
  37. orangecontrib/tomwer/widgets/control/icons/{tomoobjserie.svg → tomoobjseries.svg} +2 -2
  38. orangecontrib/tomwer/widgets/dataportal/PublishProcessedDataOW.py +172 -0
  39. orangecontrib/tomwer/widgets/{icat → dataportal}/__init__.py +2 -2
  40. orangecontrib/tomwer/widgets/debugtools/DatasetGeneratorOW.py +2 -29
  41. orangecontrib/tomwer/widgets/debugtools/ObjectInspectorOW.py +1 -29
  42. orangecontrib/tomwer/widgets/edit/DarkFlatPatchOW.py +48 -73
  43. orangecontrib/tomwer/widgets/edit/ImageKeyEditorOW.py +48 -75
  44. orangecontrib/tomwer/widgets/edit/ImageKeyUpgraderOW.py +5 -30
  45. orangecontrib/tomwer/widgets/edit/NXtomoEditorOW.py +51 -52
  46. orangecontrib/tomwer/widgets/other/PythonScriptOW.py +22 -10
  47. orangecontrib/tomwer/widgets/reconstruction/AxisOW.py +51 -60
  48. orangecontrib/tomwer/widgets/reconstruction/CastNabuVolumeOW.py +22 -68
  49. orangecontrib/tomwer/widgets/reconstruction/DarkRefAndCopyOW.py +3 -30
  50. orangecontrib/tomwer/widgets/reconstruction/NabuHelicalPrepareWeightsDoubleOW.py +21 -19
  51. orangecontrib/tomwer/widgets/reconstruction/NabuOW.py +7 -70
  52. orangecontrib/tomwer/widgets/reconstruction/NabuVolumeOW.py +41 -84
  53. orangecontrib/tomwer/widgets/reconstruction/SAAxisOW.py +15 -40
  54. orangecontrib/tomwer/widgets/reconstruction/SADeltaBetaOW.py +13 -35
  55. orangecontrib/tomwer/widgets/reconstruction/SinoNormOW.py +2 -30
  56. orangecontrib/tomwer/widgets/stitching/ZStitchingConfigOW.py +11 -11
  57. orangecontrib/tomwer/widgets/utils.py +2 -29
  58. orangecontrib/tomwer/widgets/visualization/DataViewerOW.py +1 -27
  59. orangecontrib/tomwer/widgets/visualization/DiffViewerOW.py +1 -27
  60. orangecontrib/tomwer/widgets/visualization/NXtomoMetadataViewerOW.py +1 -1
  61. orangecontrib/tomwer/widgets/visualization/RadioStackOW.py +2 -28
  62. orangecontrib/tomwer/widgets/visualization/SampleMovedOW.py +1 -27
  63. orangecontrib/tomwer/widgets/visualization/SinogramViewerOW.py +1 -27
  64. orangecontrib/tomwer/widgets/visualization/SliceStackOW.py +2 -28
  65. tomwer/__init__.py +3 -28
  66. tomwer/__main__.py +8 -4
  67. tomwer/app/__init__.py +2 -0
  68. tomwer/app/axis.py +8 -10
  69. tomwer/app/canvas.py +23 -29
  70. tomwer/app/canvas_launcher/config.py +14 -102
  71. tomwer/app/canvas_launcher/environ.py +5 -8
  72. tomwer/app/canvas_launcher/mainwindow.py +175 -69
  73. tomwer/app/canvas_launcher/splash.py +1 -3
  74. tomwer/app/canvas_launcher/utils.py +47 -0
  75. tomwer/app/canvas_launcher/widgetsscheme.py +3 -10
  76. tomwer/app/diffframe.py +2 -0
  77. tomwer/app/imagekeyeditor.py +2 -1
  78. tomwer/app/imagekeyupgrader.py +2 -0
  79. tomwer/app/multicor.py +5 -2
  80. tomwer/app/multipag.py +16 -5
  81. tomwer/app/nabuapp.py +2 -1
  82. tomwer/app/nxtomoeditor.py +17 -12
  83. tomwer/app/patchrawdarkflat.py +2 -0
  84. tomwer/app/radiostack.py +3 -2
  85. tomwer/app/reducedarkflat.py +3 -0
  86. tomwer/app/samplemoved.py +2 -0
  87. tomwer/app/scanviewer.py +2 -0
  88. tomwer/app/sinogramviewer.py +2 -0
  89. tomwer/app/slicestack.py +11 -13
  90. tomwer/app/stitching/common.py +431 -0
  91. tomwer/app/stopdatalistener.py +3 -0
  92. tomwer/app/ystitching.py +26 -0
  93. tomwer/app/zstitching.py +8 -363
  94. tomwer/core/__init__.py +2 -28
  95. tomwer/core/cluster/__init__.py +3 -0
  96. tomwer/core/cluster/cluster.py +10 -26
  97. tomwer/core/futureobject.py +17 -43
  98. tomwer/core/log/__init__.py +2 -0
  99. tomwer/core/log/processlog.py +0 -28
  100. tomwer/core/process/cluster/supervisor.py +52 -34
  101. tomwer/core/process/conditions/filters.py +3 -28
  102. tomwer/core/process/control/datalistener/datalistener.py +7 -75
  103. tomwer/core/process/control/datalistener/rpcserver.py +8 -38
  104. tomwer/core/process/control/datawatcher/datawatcher.py +11 -40
  105. tomwer/core/process/control/datawatcher/datawatcherobserver.py +30 -64
  106. tomwer/core/process/control/datawatcher/datawatcherprocess.py +11 -32
  107. tomwer/core/process/control/datawatcher/edfdwprocess.py +2 -27
  108. tomwer/core/process/control/datawatcher/hdf5dwprocess.py +1 -26
  109. tomwer/core/process/control/datawatcher/status.py +1 -26
  110. tomwer/core/process/control/emailnotifier.py +11 -23
  111. tomwer/core/process/control/nxtomoconcatenate.py +20 -18
  112. tomwer/core/process/control/nxtomomill.py +9 -44
  113. tomwer/core/process/control/scanlist.py +0 -27
  114. tomwer/core/process/control/scantransfer.py +15 -13
  115. tomwer/core/process/control/scanvalidator.py +4 -30
  116. tomwer/core/process/control/{test → tests}/test_concatenate_nxtomos.py +5 -5
  117. tomwer/core/process/control/timer.py +1 -27
  118. tomwer/core/process/control/tomoobjseries.py +12 -0
  119. tomwer/core/process/drac/binning.py +37 -0
  120. tomwer/core/process/drac/dracbase.py +170 -0
  121. tomwer/core/process/drac/gallery.py +109 -0
  122. tomwer/core/process/drac/output.py +12 -0
  123. tomwer/core/process/drac/processeddataset.py +147 -0
  124. tomwer/core/process/drac/publish.py +118 -0
  125. tomwer/core/process/drac/rawdataset.py +142 -0
  126. tomwer/core/process/drac/tests/test_gallery.py +71 -0
  127. tomwer/core/process/drac/tests/test_icat_processed_dataset.py +80 -0
  128. tomwer/core/process/drac/tests/test_icat_raw_dataset.py +90 -0
  129. tomwer/core/process/edit/darkflatpatch.py +1 -28
  130. tomwer/core/process/edit/imagekeyeditor.py +4 -32
  131. tomwer/core/process/edit/nxtomoeditor.py +307 -0
  132. tomwer/core/process/edit/tests/test_darkflatpatch.py +243 -0
  133. tomwer/core/process/edit/tests/test_imagekey_editor.py +99 -0
  134. tomwer/core/process/output.py +9 -2
  135. tomwer/core/process/reconstruction/__init__.py +0 -26
  136. tomwer/core/process/reconstruction/axis/anglemode.py +1 -29
  137. tomwer/core/process/reconstruction/axis/axis.py +47 -804
  138. tomwer/core/process/reconstruction/axis/mode.py +89 -25
  139. tomwer/core/process/reconstruction/axis/params.py +131 -283
  140. tomwer/core/process/reconstruction/axis/projectiontype.py +0 -28
  141. tomwer/core/process/reconstruction/axis/side.py +8 -0
  142. tomwer/core/process/reconstruction/darkref/darkrefs.py +14 -39
  143. tomwer/core/process/reconstruction/darkref/darkrefscopy.py +7 -39
  144. tomwer/core/process/reconstruction/darkref/params.py +1 -28
  145. tomwer/core/process/reconstruction/nabu/castvolume.py +19 -34
  146. tomwer/core/process/reconstruction/nabu/nabucommon.py +18 -43
  147. tomwer/core/process/reconstruction/nabu/nabuscores.py +64 -68
  148. tomwer/core/process/reconstruction/nabu/nabuslices.py +63 -105
  149. tomwer/core/process/reconstruction/nabu/nabuvolume.py +44 -70
  150. tomwer/core/process/reconstruction/nabu/plane.py +10 -0
  151. tomwer/core/process/reconstruction/nabu/settings.py +1 -28
  152. tomwer/core/process/reconstruction/nabu/target.py +0 -28
  153. tomwer/core/process/reconstruction/nabu/test/test_castvolume.py +116 -0
  154. tomwer/core/process/reconstruction/nabu/test/test_nabu_utils.py +277 -0
  155. tomwer/core/process/reconstruction/nabu/test/test_nabunormalization.py +199 -0
  156. tomwer/core/process/reconstruction/nabu/utils.py +11 -60
  157. tomwer/core/process/reconstruction/normalization/normalization.py +2 -32
  158. tomwer/core/process/reconstruction/normalization/params.py +3 -35
  159. tomwer/core/process/reconstruction/output.py +14 -19
  160. tomwer/core/process/reconstruction/paramsbase.py +4 -33
  161. tomwer/core/process/reconstruction/saaxis/params.py +5 -33
  162. tomwer/core/process/reconstruction/saaxis/saaxis.py +22 -51
  163. tomwer/core/process/reconstruction/sadeltabeta/params.py +2 -31
  164. tomwer/core/process/reconstruction/sadeltabeta/sadeltabeta.py +18 -46
  165. tomwer/core/process/reconstruction/scores/params.py +9 -39
  166. tomwer/core/process/reconstruction/scores/scores.py +10 -42
  167. tomwer/core/process/reconstruction/tests/__init__.py +0 -0
  168. tomwer/core/process/reconstruction/tests/test_axis.py +46 -0
  169. tomwer/core/process/reconstruction/tests/test_darkref.py +33 -0
  170. tomwer/core/process/reconstruction/{test → tests}/test_saaxis.py +1 -27
  171. tomwer/core/process/reconstruction/tests/test_sadeltabeta.py +48 -0
  172. tomwer/core/process/reconstruction/{test → tests}/test_utils.py +4 -4
  173. tomwer/core/process/reconstruction/utils/cor.py +8 -4
  174. tomwer/core/process/script/python.py +1 -27
  175. tomwer/core/process/script/tests/test_script.py +41 -0
  176. tomwer/core/process/stitching/metadataholder.py +5 -4
  177. tomwer/core/process/stitching/nabustitcher.py +35 -5
  178. tomwer/core/process/stitching/tests/test_metadataholder.py +17 -0
  179. tomwer/core/process/task.py +20 -63
  180. tomwer/core/process/tests/__init__.py +0 -0
  181. tomwer/core/process/{test → tests}/test_conditions.py +1 -28
  182. tomwer/core/process/{test → tests}/test_dark_and_flat.py +1 -27
  183. tomwer/core/process/{test → tests}/test_data_listener.py +1 -27
  184. tomwer/core/process/{test → tests}/test_data_transfer.py +2 -28
  185. tomwer/core/process/{test → tests}/test_data_watcher.py +1 -27
  186. tomwer/core/process/{test → tests}/test_nabu.py +2 -33
  187. tomwer/core/process/{test → tests}/test_normalization.py +1 -26
  188. tomwer/core/process/{test → tests}/test_timer.py +1 -27
  189. tomwer/core/process/utils.py +2 -31
  190. tomwer/core/process/visualization/dataviewer.py +1 -26
  191. tomwer/core/process/visualization/diffviewer.py +1 -26
  192. tomwer/core/process/visualization/imagestackviewer.py +0 -26
  193. tomwer/core/process/visualization/radiostack.py +0 -26
  194. tomwer/core/process/visualization/samplemoved.py +0 -28
  195. tomwer/core/process/visualization/sinogramviewer.py +0 -27
  196. tomwer/core/process/visualization/slicestack.py +0 -28
  197. tomwer/core/process/visualization/tests/test_data_viewer.py +12 -0
  198. tomwer/core/process/visualization/tests/test_diff_viewer.py +12 -0
  199. tomwer/core/process/visualization/tests/test_image_stack_viewer.py +14 -0
  200. tomwer/core/process/visualization/tests/test_radio_stack.py +12 -0
  201. tomwer/core/process/visualization/tests/test_sample_moved.py +14 -0
  202. tomwer/core/process/visualization/tests/test_sinogram_viewer.py +13 -0
  203. tomwer/core/process/visualization/tests/test_slice_stack.py +13 -0
  204. tomwer/core/process/visualization/tests/test_volume_viewer.py +12 -0
  205. tomwer/core/process/visualization/volumeviewer.py +0 -29
  206. tomwer/core/scan/__init__.py +3 -27
  207. tomwer/core/scan/blissscan.py +5 -34
  208. tomwer/core/scan/edfscan.py +19 -53
  209. tomwer/core/scan/hdf5scan.py +2 -2
  210. tomwer/core/scan/nxtomoscan.py +48 -54
  211. tomwer/core/scan/scanbase.py +107 -203
  212. tomwer/core/scan/scanfactory.py +11 -41
  213. tomwer/core/scan/tests/__init__.py +0 -0
  214. tomwer/core/scan/tests/test_edf.py +25 -0
  215. tomwer/core/scan/tests/test_future_scan.py +35 -0
  216. tomwer/core/scan/tests/test_nxtomoscan.py +143 -0
  217. tomwer/core/scan/tests/test_process_registration.py +64 -0
  218. tomwer/core/settings.py +18 -40
  219. tomwer/core/tests/__init__.py +0 -0
  220. tomwer/core/tests/test_scanutils.py +26 -0
  221. tomwer/core/{test → tests}/test_utils.py +1 -28
  222. tomwer/core/tomwer_object.py +4 -0
  223. tomwer/core/utils/__init__.py +2 -0
  224. tomwer/core/utils/char.py +0 -28
  225. tomwer/core/utils/deprecation.py +12 -38
  226. tomwer/core/utils/dictutils.py +1 -3
  227. tomwer/core/utils/ftseriesutils.py +1 -27
  228. tomwer/core/utils/gpu.py +0 -28
  229. tomwer/core/utils/image.py +8 -39
  230. tomwer/core/utils/locker.py +1 -28
  231. tomwer/core/utils/logconfig.py +0 -27
  232. tomwer/core/utils/normalization.py +4 -31
  233. tomwer/core/utils/nxtomoutils.py +8 -38
  234. tomwer/core/utils/resource.py +0 -26
  235. tomwer/core/utils/scanutils.py +23 -52
  236. tomwer/core/utils/slurm.py +7 -30
  237. tomwer/core/utils/spec.py +6 -6
  238. tomwer/core/utils/tests/__init__.py +0 -0
  239. tomwer/core/utils/tests/test_image.py +30 -0
  240. tomwer/core/utils/tests/test_nxtomo.py +38 -0
  241. tomwer/core/utils/tests/test_scan_utils.py +46 -0
  242. tomwer/core/utils/tests/test_time.py +6 -0
  243. tomwer/core/utils/threads.py +0 -26
  244. tomwer/core/utils/time.py +0 -27
  245. tomwer/core/volume/__init__.py +4 -0
  246. tomwer/core/volume/edfvolume.py +1 -28
  247. tomwer/core/volume/hdf5volume.py +1 -28
  248. tomwer/core/volume/jp2kvolume.py +1 -27
  249. tomwer/core/volume/rawvolume.py +1 -28
  250. tomwer/core/volume/tests/test_volumes.py +21 -0
  251. tomwer/core/volume/tiffvolume.py +1 -28
  252. tomwer/core/volume/volumebase.py +5 -0
  253. tomwer/core/volume/volumefactory.py +7 -33
  254. tomwer/gui/__init__.py +0 -28
  255. tomwer/gui/cluster/__init__.py +2 -0
  256. tomwer/gui/cluster/slurm.py +297 -95
  257. tomwer/gui/cluster/supervisor.py +1 -27
  258. tomwer/gui/cluster/tests/__init__.py +0 -0
  259. tomwer/gui/cluster/{test → tests}/test_cluster.py +21 -26
  260. tomwer/gui/cluster/{test → tests}/test_supervisor.py +1 -27
  261. tomwer/gui/conditions/__init__.py +2 -0
  262. tomwer/gui/conditions/filter.py +1 -26
  263. tomwer/gui/configuration/__init__.py +2 -0
  264. tomwer/gui/control/__init__.py +2 -0
  265. tomwer/gui/control/actions.py +2 -28
  266. tomwer/gui/control/datadiscovery.py +4 -3
  267. tomwer/gui/control/datalist.py +64 -69
  268. tomwer/gui/control/datalistener.py +1 -39
  269. tomwer/gui/control/datareacheractions.py +1 -28
  270. tomwer/gui/control/datatransfert.py +2 -29
  271. tomwer/gui/control/datavalidator.py +3 -37
  272. tomwer/gui/control/datawatcher/__init__.py +0 -28
  273. tomwer/gui/control/datawatcher/configuration.py +1 -28
  274. tomwer/gui/control/datawatcher/datawatcher.py +3 -32
  275. tomwer/gui/control/datawatcher/datawatcherobserver.py +2 -28
  276. tomwer/gui/control/history.py +5 -35
  277. tomwer/gui/control/nxtomomill.py +3 -30
  278. tomwer/gui/control/observations.py +61 -55
  279. tomwer/gui/control/reducedarkflatselector.py +24 -20
  280. tomwer/gui/control/scanselectorwidget.py +2 -28
  281. tomwer/gui/control/selectorwidgetbase.py +17 -17
  282. tomwer/gui/control/series/__init__.py +0 -0
  283. tomwer/gui/control/{serie/seriecreator.py → series/seriescreator.py} +214 -235
  284. tomwer/gui/control/series/serieswaiter.py +0 -0
  285. tomwer/gui/control/series/test/test_creator.py +424 -0
  286. tomwer/gui/control/series/test/test_nxtomo_concatenate.py +21 -0
  287. tomwer/gui/control/singletomoobj.py +1 -1
  288. tomwer/gui/control/tests/__init__.py +0 -0
  289. tomwer/gui/control/tests/test_datalist.py +47 -0
  290. tomwer/gui/control/{test → tests}/test_datalistener.py +1 -28
  291. tomwer/gui/control/tests/test_datavalidator.py +27 -0
  292. tomwer/gui/control/{test → tests}/test_inputwidget.py +1 -27
  293. tomwer/gui/control/tests/test_process_manager.py +38 -0
  294. tomwer/gui/control/tests/test_scan_observations.py +23 -0
  295. tomwer/gui/control/tests/test_scanselector.py +42 -0
  296. tomwer/gui/control/{test → tests}/test_scanvalidator.py +1 -27
  297. tomwer/gui/control/{test → tests}/test_volume_dialog.py +2 -30
  298. tomwer/gui/control/{test → tests}/test_volumeselector.py +1 -27
  299. tomwer/gui/control/volumeselectorwidget.py +2 -30
  300. tomwer/gui/dataportal/__init__.py +2 -0
  301. tomwer/gui/{icat → dataportal}/createscreenshots.py +3 -2
  302. tomwer/gui/dataportal/gallery.py +133 -0
  303. tomwer/gui/dataportal/outputformat.py +0 -0
  304. tomwer/gui/dataportal/publish.py +96 -0
  305. tomwer/gui/dataportal/tests/test_create_screenshots_gui.py +23 -0
  306. tomwer/gui/dataportal/tests/test_gallery_gui.py +21 -0
  307. tomwer/gui/debugtools/__init__.py +2 -0
  308. tomwer/gui/debugtools/datasetgenerator.py +1 -30
  309. tomwer/gui/debugtools/objectinspector.py +1 -29
  310. tomwer/gui/dialog/QDataDialog.py +89 -0
  311. tomwer/gui/{qfolderdialog.py → dialog/QVolumeDialog.py} +8 -107
  312. tomwer/gui/dialog/__init__.py +1 -0
  313. tomwer/gui/edit/__init__.py +2 -0
  314. tomwer/gui/edit/dkrfpatch.py +52 -87
  315. tomwer/gui/edit/imagekeyeditor.py +18 -54
  316. tomwer/gui/edit/nxtomoeditor.py +113 -300
  317. tomwer/gui/edit/nxtomowarmer.py +3 -2
  318. tomwer/gui/edit/tests/__init__.py +0 -0
  319. tomwer/gui/edit/{test → tests}/test_dkrf_patch.py +3 -29
  320. tomwer/gui/edit/{test → tests}/test_image_key_editor.py +1 -27
  321. tomwer/gui/edit/{test → tests}/test_nx_editor.py +45 -23
  322. tomwer/gui/fonts.py +5 -0
  323. tomwer/gui/icons.py +28 -66
  324. tomwer/gui/illustrations.py +4 -34
  325. tomwer/gui/imagefromfile.py +5 -30
  326. tomwer/gui/metadataloader.py +36 -0
  327. tomwer/gui/qconfigfile.py +3 -0
  328. tomwer/gui/qlefilesystem.py +3 -0
  329. tomwer/gui/reconstruction/__init__.py +2 -0
  330. tomwer/gui/reconstruction/axis/AxisMainWindow.py +275 -0
  331. tomwer/gui/reconstruction/axis/AxisOptionsWidget.py +313 -0
  332. tomwer/gui/reconstruction/axis/AxisSettingsWidget.py +797 -0
  333. tomwer/gui/reconstruction/axis/AxisWidget.py +534 -0
  334. tomwer/gui/reconstruction/axis/CalculationWidget.py +218 -0
  335. tomwer/gui/reconstruction/axis/CompareImages.py +11 -44
  336. tomwer/gui/reconstruction/axis/ControlWidget.py +285 -0
  337. tomwer/gui/reconstruction/axis/EstimatedCORWidget.py +394 -0
  338. tomwer/gui/reconstruction/axis/EstimatedCorComboBox.py +118 -0
  339. tomwer/gui/reconstruction/axis/InputWidget.py +347 -0
  340. tomwer/gui/reconstruction/axis/ManualFramesSelection.py +168 -0
  341. tomwer/gui/reconstruction/axis/__init__.py +2 -2
  342. tomwer/gui/reconstruction/darkref/__init__.py +0 -27
  343. tomwer/gui/reconstruction/darkref/darkrefcopywidget.py +5 -34
  344. tomwer/gui/reconstruction/darkref/darkrefwidget.py +1 -27
  345. tomwer/gui/reconstruction/nabu/castvolume.py +40 -59
  346. tomwer/gui/reconstruction/nabu/check.py +7 -33
  347. tomwer/gui/reconstruction/nabu/nabuconfig/base.py +7 -34
  348. tomwer/gui/reconstruction/nabu/nabuconfig/ctf.py +6 -5
  349. tomwer/gui/reconstruction/nabu/nabuconfig/nabuconfig.py +10 -69
  350. tomwer/gui/reconstruction/nabu/nabuconfig/output.py +3 -47
  351. tomwer/gui/reconstruction/nabu/nabuconfig/phase.py +54 -36
  352. tomwer/gui/reconstruction/nabu/nabuconfig/preprocessing.py +103 -54
  353. tomwer/gui/reconstruction/nabu/nabuconfig/reconstruction.py +116 -65
  354. tomwer/gui/reconstruction/nabu/nabuflow.py +31 -61
  355. tomwer/gui/reconstruction/nabu/platform.py +94 -0
  356. tomwer/gui/reconstruction/nabu/slices.py +81 -45
  357. tomwer/gui/reconstruction/nabu/slurm.py +1 -27
  358. tomwer/gui/reconstruction/nabu/test/test_cast_volume.py +82 -0
  359. tomwer/gui/reconstruction/nabu/test/test_check.py +66 -0
  360. tomwer/gui/reconstruction/nabu/test/test_ctf.py +46 -0
  361. tomwer/gui/reconstruction/nabu/test/test_helical.py +21 -0
  362. tomwer/gui/reconstruction/nabu/test/test_nabu_preprocessing.py +81 -0
  363. tomwer/gui/reconstruction/nabu/volume.py +62 -90
  364. tomwer/gui/reconstruction/normalization/intensity.py +5 -32
  365. tomwer/gui/reconstruction/normalization/test/test_intensity.py +89 -0
  366. tomwer/gui/reconstruction/saaxis/corrangeselector.py +32 -56
  367. tomwer/gui/reconstruction/saaxis/dimensionwidget.py +56 -96
  368. tomwer/gui/reconstruction/saaxis/saaxis.py +17 -38
  369. tomwer/gui/reconstruction/saaxis/sliceselector.py +8 -39
  370. tomwer/gui/reconstruction/sadeltabeta/saadeltabeta.py +19 -37
  371. tomwer/gui/reconstruction/scores/control.py +2 -30
  372. tomwer/gui/reconstruction/scores/scoreplot.py +23 -49
  373. tomwer/gui/reconstruction/tests/__init__.py +0 -0
  374. tomwer/gui/reconstruction/{test → tests}/test_axis.py +23 -49
  375. tomwer/gui/reconstruction/{test → tests}/test_nabu.py +16 -31
  376. tomwer/gui/reconstruction/{test → tests}/test_saaxis.py +10 -37
  377. tomwer/gui/reconstruction/{test → tests}/test_sadeltabeta.py +1 -26
  378. tomwer/gui/samplemoved/__init__.py +2 -30
  379. tomwer/gui/samplemoved/selectiontable.py +3 -33
  380. tomwer/gui/settings.py +7 -0
  381. tomwer/gui/stackplot.py +33 -661
  382. tomwer/gui/stacks.py +261 -135
  383. tomwer/gui/stitching/SingleAxisStitchingWidget.py +326 -0
  384. tomwer/gui/stitching/StitchingOptionsWidget.py +438 -0
  385. tomwer/gui/stitching/StitchingWindow.py +586 -0
  386. tomwer/gui/stitching/__init__.py +2 -0
  387. tomwer/gui/stitching/alignment.py +90 -0
  388. tomwer/gui/stitching/axisorderedlist.py +44 -34
  389. tomwer/gui/stitching/config/axisparams.py +25 -11
  390. tomwer/gui/stitching/config/output.py +6 -5
  391. tomwer/gui/stitching/config/positionoveraxis.py +313 -51
  392. tomwer/gui/stitching/config/stitchingstrategies.py +22 -16
  393. tomwer/gui/stitching/config/tests/test_axisparams.py +25 -0
  394. tomwer/gui/stitching/config/tomoobjdetails.py +3 -5
  395. tomwer/gui/stitching/normalization.py +1 -0
  396. tomwer/gui/stitching/preview.py +59 -0
  397. tomwer/gui/stitching/singleaxis.py +57 -0
  398. tomwer/gui/stitching/stitchandbackground.py +3 -2
  399. tomwer/gui/stitching/stitching_preview.py +44 -36
  400. tomwer/gui/stitching/stitching_raw.py +5 -3
  401. tomwer/gui/stitching/tests/test_ZStitchingWindow.py +88 -0
  402. tomwer/gui/stitching/tests/test_axis_ordered_list.py +21 -0
  403. tomwer/gui/stitching/tests/test_normalization.py +27 -0
  404. tomwer/gui/stitching/tests/test_preview.py +68 -0
  405. tomwer/gui/stitching/tests/test_stitching_raw.py +110 -0
  406. tomwer/gui/stitching/tests/utils.py +92 -0
  407. tomwer/gui/stitching/utils.py +14 -0
  408. tomwer/gui/stitching/z_stitching/fineestimation.py +5 -33
  409. tomwer/gui/stitching/z_stitching/tests/test_fine_estimation.py +35 -0
  410. tomwer/gui/stitching/z_stitching/tests/test_raw_estimation.py +215 -0
  411. tomwer/gui/stitching/z_stitching/tests/test_stitching_window.py +51 -0
  412. tomwer/gui/tests/__init__.py +0 -0
  413. tomwer/gui/tests/test_axis_gui.py +43 -0
  414. tomwer/gui/{test → tests}/test_qfolder_dialog.py +1 -1
  415. tomwer/gui/utils/RangeWidget.py +44 -0
  416. tomwer/gui/utils/buttons.py +4 -3
  417. tomwer/gui/utils/completer.py +2 -33
  418. tomwer/gui/utils/flow.py +11 -40
  419. tomwer/gui/utils/gpu.py +60 -0
  420. tomwer/gui/utils/illustrations.py +1 -26
  421. tomwer/gui/utils/inputwidget.py +35 -73
  422. tomwer/gui/utils/lineselector/lineselector.py +9 -46
  423. tomwer/gui/utils/loadingmode.py +81 -0
  424. tomwer/gui/utils/qt_utils.py +9 -0
  425. tomwer/gui/utils/sandboxes.py +1 -26
  426. tomwer/gui/utils/scandescription.py +2 -31
  427. tomwer/gui/utils/scrollarea.py +6 -55
  428. tomwer/gui/utils/slider.py +4 -28
  429. tomwer/gui/utils/splashscreen.py +0 -28
  430. tomwer/gui/utils/step.py +14 -13
  431. tomwer/gui/utils/tests/test_completer.py +41 -0
  432. tomwer/gui/utils/tests/test_line_selector.py +21 -0
  433. tomwer/gui/utils/tests/test_splashscreen.py +8 -0
  434. tomwer/gui/utils/tests/test_vignettes.py +68 -0
  435. tomwer/gui/utils/unitsystem.py +15 -69
  436. tomwer/gui/utils/utils.py +4 -5
  437. tomwer/gui/utils/vignettes.py +10 -41
  438. tomwer/gui/utils/waiterthread.py +0 -26
  439. tomwer/gui/visualization/__init__.py +2 -0
  440. tomwer/gui/visualization/dataviewer.py +68 -421
  441. tomwer/gui/visualization/diffviewer/diffviewer.py +2 -30
  442. tomwer/gui/visualization/diffviewer/shiftwidget.py +4 -29
  443. tomwer/gui/visualization/fullscreenplot.py +5 -5
  444. tomwer/gui/visualization/imagestack.py +403 -0
  445. tomwer/gui/visualization/nxtomometadata.py +0 -4
  446. tomwer/gui/visualization/reconstructionparameters.py +14 -32
  447. tomwer/gui/visualization/scanoverview.py +33 -66
  448. tomwer/gui/visualization/sinogramviewer.py +2 -28
  449. tomwer/gui/visualization/test/__init__.py +0 -28
  450. tomwer/gui/visualization/test/test_dataviewer.py +1 -28
  451. tomwer/gui/visualization/test/test_diffviewer.py +1 -28
  452. tomwer/gui/visualization/test/test_nx_tomo_metadata_viewer.py +0 -5
  453. tomwer/gui/visualization/test/test_reconstruction_parameters.py +1 -27
  454. tomwer/gui/visualization/test/test_sinogramviewer.py +1 -28
  455. tomwer/gui/visualization/test/test_stacks.py +184 -115
  456. tomwer/gui/visualization/test/test_volumeviewer.py +3 -2
  457. tomwer/gui/visualization/tomoobjoverview.py +2 -2
  458. tomwer/gui/visualization/volumeoverview.py +3 -2
  459. tomwer/gui/visualization/volumeviewer.py +47 -43
  460. tomwer/io/__init__.py +2 -0
  461. tomwer/io/utils/h5pyutils.py +1 -27
  462. tomwer/io/utils/test/test_raw_and_processed_data.py +10 -0
  463. tomwer/io/utils/test/test_utils.py +67 -0
  464. tomwer/io/utils/utils.py +2 -31
  465. tomwer/resources/__init__.py +13 -33
  466. tomwer/resources/gui/icons/edit_downstream.svg +114 -0
  467. tomwer/resources/gui/icons/edit_upstream.svg +112 -0
  468. tomwer/resources/gui/icons/free_edition.svg +23 -0
  469. tomwer/resources/gui/icons/icat_gallery_opts.png +0 -0
  470. tomwer/resources/gui/icons/icat_gallery_opts.svg +80 -0
  471. tomwer/resources/gui/icons/search.png +0 -0
  472. tomwer/resources/gui/icons/search.svg +23 -0
  473. tomwer/resources/gui/icons/warning.png +0 -0
  474. tomwer/synctools/__init__.py +2 -0
  475. tomwer/synctools/axis.py +1 -27
  476. tomwer/synctools/darkref.py +1 -26
  477. tomwer/synctools/datalistener.py +1 -27
  478. tomwer/synctools/datatransfert.py +2 -27
  479. tomwer/synctools/imageloaderthread.py +1 -28
  480. tomwer/synctools/rsyncmanager.py +1 -25
  481. tomwer/synctools/saaxis.py +1 -26
  482. tomwer/synctools/sadeltabeta.py +1 -26
  483. tomwer/synctools/stacks/control/datalistener.py +1 -26
  484. tomwer/synctools/stacks/processingstack.py +4 -33
  485. tomwer/synctools/stacks/reconstruction/axis.py +6 -53
  486. tomwer/synctools/stacks/reconstruction/castvolume.py +12 -43
  487. tomwer/synctools/stacks/reconstruction/dkrefcopy.py +4 -27
  488. tomwer/synctools/stacks/reconstruction/nabu.py +3 -28
  489. tomwer/synctools/stacks/reconstruction/normalization.py +2 -27
  490. tomwer/synctools/stacks/reconstruction/saaxis.py +2 -27
  491. tomwer/synctools/stacks/reconstruction/sadeltabeta.py +2 -27
  492. tomwer/synctools/tests/__init__.py +0 -0
  493. tomwer/synctools/{test → tests}/test_darkRefs.py +16 -40
  494. tomwer/synctools/{test → tests}/test_foldertransfer.py +2 -33
  495. tomwer/synctools/utils/scanstages.py +2 -31
  496. tomwer/tests/__init__.py +1 -0
  497. tomwer/tests/app/test_stitching.py +95 -0
  498. tomwer/tests/datasets.py +1 -5
  499. tomwer/tests/orangecontrib/tomwer/widgets/cluster/tests/test_future_supervisorow.py +48 -0
  500. tomwer/tests/orangecontrib/tomwer/widgets/cluster/tests/test_slurm_clusterow.py +40 -0
  501. tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_advancement.py +8 -0
  502. tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_data_validator.py +55 -0
  503. tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_datadiscovery.py +129 -0
  504. tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_datalistener.py +111 -0
  505. tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_dataselector.py +69 -0
  506. tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_datawatcher.py +411 -0
  507. tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_emailow.py +29 -0
  508. tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_notifier.py +24 -0
  509. tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_nxtomo_concatenate_ow.py +64 -0
  510. tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_nxtomomill.py +133 -0
  511. tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_reduce_dark_flat_selector.py +40 -0
  512. tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_singletomoobj.py +40 -0
  513. tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_timerow.py +25 -0
  514. tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_tomoobj_series.py +96 -0
  515. tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_volume_selector.py +69 -0
  516. orangecontrib/tomwer/widgets/edit/test/test_image_key_editor.py → tomwer/tests/orangecontrib/tomwer/widgets/debugtools/tests/test_dataset_generator.py +17 -16
  517. tomwer/tests/orangecontrib/tomwer/widgets/debugtools/tests/test_object_inspector.py +36 -0
  518. {orangecontrib/tomwer/widgets/edit/test → tomwer/tests/orangecontrib/tomwer/widgets/edit/tests}/test_dark_flat_patch.py +1 -27
  519. tomwer/tests/orangecontrib/tomwer/widgets/edit/tests/test_image_key_editor.py +30 -0
  520. tomwer/tests/orangecontrib/tomwer/widgets/edit/tests/test_nxtomo_editor.py +138 -0
  521. tomwer/tests/orangecontrib/tomwer/widgets/other/tests/test_pythonscript.py +31 -0
  522. tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_axis.py +199 -0
  523. tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_cast_volumeow.py +58 -0
  524. tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_dark_refs_widget.py +136 -0
  525. tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_delta_beta_selector.py +15 -0
  526. tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_i_norm.py +200 -0
  527. tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_nabu_helical_prepare_weights_double.py +20 -0
  528. tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_nabu_volume.py +74 -0
  529. tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_nabu_widget.py +107 -0
  530. tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_sa_delta_beta.py +194 -0
  531. tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_saaxis.py +194 -0
  532. tomwer/tests/orangecontrib/tomwer/widgets/stitching/tests/test_zstitching.py +313 -0
  533. tomwer/tests/orangecontrib/tomwer/widgets/tests/test_conditions.py +85 -0
  534. tomwer/tests/orangecontrib/tomwer/widgets/tests/test_darkref.py +225 -0
  535. tomwer/tests/orangecontrib/tomwer/widgets/tests/test_foldertransfert.py +105 -0
  536. tomwer/tests/orangecontrib/tomwer/widgets/visualization/tests/test_dataviewerow.py +57 -0
  537. tomwer/tests/orangecontrib/tomwer/widgets/visualization/tests/test_diffviewerow.py +39 -0
  538. tomwer/tests/orangecontrib/tomwer/widgets/visualization/tests/test_nxtomo_metadata_viewer.py +29 -0
  539. tomwer/tests/orangecontrib/tomwer/widgets/visualization/tests/test_radio_stackow.py +31 -0
  540. tomwer/tests/orangecontrib/tomwer/widgets/visualization/tests/test_sample_movedow.py +46 -0
  541. tomwer/tests/orangecontrib/tomwer/widgets/visualization/tests/test_sinogram_viewerow.py +31 -0
  542. tomwer/tests/orangecontrib/tomwer/widgets/visualization/tests/test_slice_stackow.py +31 -0
  543. tomwer/tests/orangecontrib/tomwer/widgets/visualization/tests/test_volume_viewerow.py +32 -0
  544. tomwer/tests/test_ewoks/test_conversion.py +104 -0
  545. tomwer/tests/test_ewoks/test_single_node_execution.py +87 -0
  546. tomwer/tests/test_ewoks/test_workflows.py +138 -0
  547. tomwer/utils.py +11 -39
  548. tomwer/version.py +2 -2
  549. {tomwer-1.3.26.dist-info → tomwer-1.4.0.dist-info}/LICENSE +3 -3
  550. tomwer-1.4.0.dist-info/METADATA +337 -0
  551. tomwer-1.4.0.dist-info/RECORD +912 -0
  552. {tomwer-1.3.26.dist-info → tomwer-1.4.0.dist-info}/WHEEL +1 -1
  553. {tomwer-1.3.26.dist-info → tomwer-1.4.0.dist-info}/entry_points.txt +1 -0
  554. orangecontrib/tomwer/widgets/control/DataListOW.py +0 -129
  555. orangecontrib/tomwer/widgets/control/TomoObjSerieOW.py +0 -86
  556. orangecontrib/tomwer/widgets/control/VolumeSymLinkOW.py +0 -182
  557. orangecontrib/tomwer/widgets/edit/test/test_nxtomo_editor.py +0 -141
  558. orangecontrib/tomwer/widgets/icat/PublishProcessedDataOW.py +0 -115
  559. orangecontrib/tomwer/widgets/icat/RawDataScreenshotCreatorOW.py +0 -98
  560. orangecontrib/tomwer/widgets/icat/SaveToGalleryAndPublishOW.py +0 -129
  561. orangecontrib/tomwer/widgets/icat/icons/add_gallery.png +0 -0
  562. orangecontrib/tomwer/widgets/icat/icons/add_gallery.svg +0 -82
  563. orangecontrib/tomwer/widgets/icat/icons/raw_screenshots.png +0 -0
  564. orangecontrib/tomwer/widgets/icat/icons/raw_screenshots.svg +0 -143
  565. orangecontrib/tomwer/widgets/visualization/LivesliceOW.py +0 -87
  566. orangecontrib/tomwer/widgets/visualization/icons/liveslice.png +0 -0
  567. orangecontrib/tomwer/widgets/visualization/icons/liveslice.svg +0 -110
  568. tomwer/core/log/logger.py +0 -130
  569. tomwer/core/process/control/test/test_volume_link.py +0 -74
  570. tomwer/core/process/control/tomoobjserie.py +0 -12
  571. tomwer/core/process/control/volumesymlink.py +0 -200
  572. tomwer/core/process/icat/createscreenshots.py +0 -100
  573. tomwer/core/process/icat/gallery.py +0 -377
  574. tomwer/core/process/icat/icatbase.py +0 -36
  575. tomwer/core/process/icat/publish.py +0 -228
  576. tomwer/core/process/icat/screenshots.py +0 -27
  577. tomwer/core/process/reconstruction/test/test_darkref.py +0 -60
  578. tomwer/core/process/reconstruction/test/test_sadeltabeta.py +0 -74
  579. tomwer/core/process/test/test_axis.py +0 -309
  580. tomwer/core/process/visualization/liveslice.py +0 -6
  581. tomwer/core/progress.py +0 -100
  582. tomwer/core/scan/test/test_edf.py +0 -53
  583. tomwer/core/scan/test/test_future_scan.py +0 -61
  584. tomwer/core/scan/test/test_h5.py +0 -96
  585. tomwer/core/scan/test/test_process_registration.py +0 -109
  586. tomwer/core/test/test_scanutils.py +0 -53
  587. tomwer/gui/control/emailnotifier.py +0 -174
  588. tomwer/gui/control/serie/seriewaiter.py +0 -28
  589. tomwer/gui/control/test/__init__.py +0 -28
  590. tomwer/gui/control/test/test_datalist.py +0 -96
  591. tomwer/gui/control/test/test_datavalidator.py +0 -54
  592. tomwer/gui/control/test/test_email.py +0 -35
  593. tomwer/gui/control/test/test_process_manager.py +0 -65
  594. tomwer/gui/control/test/test_scanselector.py +0 -67
  595. tomwer/gui/edit/test/__init__.py +0 -28
  596. tomwer/gui/icat/gallery.py +0 -214
  597. tomwer/gui/icat/publish.py +0 -187
  598. tomwer/gui/reconstruction/axis/axis.py +0 -733
  599. tomwer/gui/reconstruction/axis/radioaxis.py +0 -2467
  600. tomwer/gui/stitching/stitching.py +0 -1392
  601. tomwer/gui/test/__init__.py +0 -28
  602. tomwer/gui/test/test_axis_gui.py +0 -34
  603. tomwer/synctools/stacks/edit/darkflatpatch.py +0 -169
  604. tomwer/synctools/stacks/edit/imagekeyeditor.py +0 -135
  605. tomwer/third_part/WaitingOverlay.py +0 -110
  606. tomwer-1.3.26-py3.11-nspkg.pth +0 -1
  607. tomwer-1.3.26.dist-info/METADATA +0 -292
  608. tomwer-1.3.26.dist-info/RECORD +0 -785
  609. tomwer-1.3.26.dist-info/namespace_packages.txt +0 -1
  610. /orangecontrib/tomwer/{widgets/edit/test → tests}/__init__.py +0 -0
  611. {tomwer/core/process/control/test → orangecontrib/tomwer/tutorials/id16b}/__init__.py +0 -0
  612. {tomwer/core/process/icat → orangecontrib/tomwer/widgets/cluster/tests}/__init__.py +0 -0
  613. /orangecontrib/tomwer/widgets/control/icons/{tomoobjserie.png → tomoobjseries.png} +0 -0
  614. {tomwer/core/process/reconstruction/test → orangecontrib/tomwer/widgets/control/tests}/__init__.py +0 -0
  615. /orangecontrib/tomwer/widgets/{icat → dataportal}/icons/publish_processed_data.png +0 -0
  616. /orangecontrib/tomwer/widgets/{icat → dataportal}/icons/publish_processed_data.svg +0 -0
  617. {tomwer/core/process/test → orangecontrib/tomwer/widgets/debugtools/tests}/__init__.py +0 -0
  618. {tomwer/core/scan/test → orangecontrib/tomwer/widgets/edit/tests}/__init__.py +0 -0
  619. {tomwer/core/test → orangecontrib/tomwer/widgets/other/tests}/__init__.py +0 -0
  620. {tomwer/gui/cluster/test → orangecontrib/tomwer/widgets/reconstruction/tests}/__init__.py +0 -0
  621. {tomwer/gui/control/serie → orangecontrib/tomwer/widgets/stitching/tests}/__init__.py +0 -0
  622. {tomwer/gui/icat → orangecontrib/tomwer/widgets/tests}/__init__.py +0 -0
  623. {tomwer/gui/reconstruction/test → orangecontrib/tomwer/widgets/visualization/tests}/__init__.py +0 -0
  624. /tomwer/{synctools/stacks/edit → app/stitching}/__init__.py +0 -0
  625. /tomwer/{synctools/test → core/process/control/tests}/__init__.py +0 -0
  626. /tomwer/core/process/control/{test → tests}/test_email.py +0 -0
  627. /tomwer/core/process/control/{test → tests}/test_h52nx_process.py +0 -0
  628. /tomwer/{third_part → core/process/drac}/__init__.py +0 -0
  629. /tomwer/core/process/reconstruction/{test → tests}/test_axis_params.py +0 -0
  630. /tomwer/core/process/reconstruction/{test → tests}/test_darkref_copy.py +0 -0
  631. /tomwer/core/process/reconstruction/{test → tests}/test_paramsbase.py +0 -0
  632. /tomwer/core/scan/{test → tests}/test_scan.py +0 -0
  633. /tomwer/gui/control/{serie → series}/nxtomoconcatenate.py +0 -0
  634. /tomwer/gui/control/{test → tests}/test_datadiscovery.py +0 -0
  635. /tomwer/gui/control/{test → tests}/test_reducedarkflat_selector.py +0 -0
  636. /tomwer/gui/control/{test → tests}/test_single_tomo_obj.py +0 -0
  637. {orangecontrib/tomwer/widgets/edit/test → tomwer/tests/orangecontrib/tomwer/widgets/edit/tests}/test_image_key_upgrader.py +0 -0
  638. {tomwer-1.3.26.dist-info → tomwer-1.4.0.dist-info}/top_level.txt +0 -0
@@ -1,2467 +0,0 @@
1
- import enum
2
- import logging
3
- import os
4
- from bisect import bisect_left
5
- from typing import Optional, Union
6
-
7
- import numpy
8
- import scipy.signal
9
- from silx.gui import qt
10
- from silx.io.url import DataUrl
11
- from silx.utils.deprecation import deprecated
12
-
13
- from tomwer.core.process.reconstruction.axis import mode as axis_mode
14
- from tomwer.core.process.reconstruction.axis.anglemode import CorAngleMode
15
- from tomwer.core.process.reconstruction.axis.params import (
16
- DEFAULT_CMP_N_SUBSAMPLING_Y,
17
- DEFAULT_CMP_OVERSAMPLING,
18
- DEFAULT_CMP_TAKE_LOG,
19
- DEFAULT_CMP_THETA,
20
- AxisCalculationInput,
21
- )
22
- from tomwer.core.scan.scanbase import TomwerScanBase
23
- from tomwer.core.scan.scanfactory import ScanFactory
24
- from tomwer.core.utils import image
25
- from tomwer.gui.utils.buttons import PadlockButton
26
- from tomwer.gui.utils.qt_utils import block_signals
27
- from tomwer.gui.settings import EDITING_BACKGROUND_COLOR
28
- from tomwer.gui.utils.step import StepSizeSelectorWidget
29
- from tomwer.synctools.axis import QAxisRP
30
-
31
- from .CompareImages import CompareImages
32
-
33
- _logger = logging.getLogger(__name__)
34
-
35
-
36
- class RadioAxisWindow(qt.QMainWindow):
37
- """
38
- QMainWindow for defining the rotation axis
39
-
40
- :raises ValueError: given axis is not an instance of _QAxisRP
41
- """
42
-
43
- sigAxisEditionLocked = qt.Signal(bool)
44
- """Signal emitted when the status of the reconstruction parameters edition
45
- change"""
46
-
47
- sigLockModeChanged = qt.Signal(bool)
48
- """signal emitted when the lock on the mode change"""
49
-
50
- sigPositionChanged = qt.Signal(tuple)
51
- """signal emitted when the center of rotation center change"""
52
-
53
- def __init__(self, axis, parent=None, backend=None):
54
- super().__init__(parent)
55
- if isinstance(axis, QAxisRP):
56
- self.__recons_params = axis
57
- else:
58
- raise TypeError("axis should be an instance of _QAxisRP")
59
-
60
- self._imgA = None
61
- self._imgB = None
62
- self._shiftedImgA = None
63
- self._flipB = True
64
- """Option if we want to flip the image B"""
65
- self._scan = None
66
- self._axis_params = None
67
- self._lastManualFlip = None
68
- """Cache for the last user entry for manual flip"""
69
- self._lastXShift = None
70
- # cache to know if the x shift has changed since
71
- self._lastYShift = None
72
- # cache to know if the y shift has changed
73
- self._lastXOrigin = None
74
- # cache to know if the x origin has changed since
75
- self._lastYOrigin = None
76
- # cache to know if the y origin has changed since
77
-
78
- self.setWindowFlags(qt.Qt.Widget)
79
- self._plot = CompareImages(parent=self, backend=backend)
80
- self._plot.setAutoResetZoom(False)
81
- _mode = CompareImages.VisualizationMode.COMPOSITE_A_MINUS_B
82
- self._plot.setVisualizationMode(_mode)
83
- self._plot.setAlignmentMode(CompareImages.AlignmentMode.STRETCH)
84
- self.setCentralWidget(self._plot)
85
-
86
- self._dockWidgetCtrl = qt.QDockWidget(parent=self)
87
- self._dockWidgetCtrl.layout().setContentsMargins(0, 0, 0, 0)
88
- self._dockWidgetCtrl.setFeatures(qt.QDockWidget.DockWidgetMovable)
89
- self._controlWidget = _AxisManual(
90
- parent=self, reconsParams=self.__recons_params
91
- )
92
- self._controlWidgetScrollArea = qt.QScrollArea(self)
93
- self._controlWidgetScrollArea.setWidgetResizable(True)
94
- self._controlWidgetScrollArea.setWidget(self._controlWidget)
95
- self._dockWidgetCtrl.setWidget(self._controlWidgetScrollArea)
96
- self.addDockWidget(qt.Qt.RightDockWidgetArea, self._dockWidgetCtrl)
97
-
98
- # expose API
99
- self.getXShift = self._controlWidget.getXShift
100
- self.getYShift = self._controlWidget.getYShift
101
- self._setShift = self._controlWidget.setShift
102
- self.setXShift = self._controlWidget.setXShift
103
- self.setYShift = self._controlWidget.setYShift
104
- self.getShiftStep = self._controlWidget.getShiftStep
105
- self.setShiftStep = self._controlWidget.setShiftStep
106
- self.getAxis = self._controlWidget.getAxis
107
- self.getMode = self._controlWidget.getMode
108
- self.setModeLock = self._controlWidget.setModeLock
109
-
110
- # signal / slot connection
111
- self._controlWidget.sigShiftChanged.connect(self._updateShift)
112
- self._controlWidget.sigShiftChanged.connect(self._corChanged)
113
- self._controlWidget.sigRoiChanged.connect(self._updateShift)
114
- self._controlWidget.sigAuto.connect(self._updateAuto)
115
- self._controlWidget.sigModeChanged.connect(self.setMode)
116
- self._controlWidget.sigModeLockChanged.connect(self._modeLockChanged)
117
- self._controlWidget.sigResetZoomRequested.connect(self._resetZoomPlot)
118
- self._controlWidget.sigSubsamplingChanged.connect(self._updateSubSampling)
119
- self._controlWidget.sigUrlChanged.connect(self._urlChanged)
120
- self._plot.sigCropImagesChanged.connect(self._updateShift)
121
-
122
- # adapt gui to the axis value
123
- self.setReconsParams(axis=self.__recons_params)
124
- self.getPlot().getPlot().setAxesDisplayed(True)
125
-
126
- def manual_uses_full_image(self, value):
127
- self._controlWidget.manual_uses_full_image(value)
128
-
129
- def _modeLockChanged(self, lock):
130
- self.sigLockModeChanged.emit(lock)
131
-
132
- def _corChanged(self):
133
- self.sigPositionChanged.emit((self.getXShift(), self.getYShift()))
134
-
135
- def getPlot(self):
136
- return self._plot
137
-
138
- def _resetZoomPlot(self):
139
- self._plot.getPlot().resetZoom()
140
-
141
- def getSettingsWidget(self):
142
- return self._controlWidgetScrollArea
143
-
144
- def getSettingsWidgetDocker(self):
145
- return self._dockWidgetCtrl
146
-
147
- def setMode(self, mode):
148
- """
149
- Define the mode to use for radio axis
150
-
151
- :param mode:
152
- :return:
153
- """
154
- mode = axis_mode.AxisMode.from_value(mode)
155
- with block_signals(self._controlWidget):
156
- with block_signals(self._axis_params):
157
- self._controlWidget.setMode(mode)
158
- if mode is axis_mode.AxisMode.manual:
159
- self._setModeLockFrmSettings(False)
160
-
161
- def updateAutomaticallyEstimatedCor(self):
162
- return self._controlWidget.updateAutomaticallyEstimatedCor()
163
-
164
- def setUpdateAutomaticallyEstimatedCor(self, value):
165
- self._controlWidget.setUpdateAutomaticallyEstimatedCor(value)
166
-
167
- def setEstimatedCor(self, value):
168
- self._controlWidget.setEstimatedCor(value=value)
169
-
170
- def getEstimatedCor(self):
171
- return self._controlWidget.getEstimatedCor()
172
-
173
- def _setModeLockFrmSettings(self, lock: bool):
174
- # only lock the push button
175
- with block_signals(self):
176
- self._controlWidget._mainWidget._calculationWidget._lockMethodPB.setLock(
177
- lock
178
- )
179
-
180
- def getROIDims(self):
181
- if self.getMode() == axis_mode.AxisMode.manual:
182
- return self._controlWidget.getROIDims()
183
- else:
184
- return None
185
-
186
- def getROIOrigin(self):
187
- if self.getMode() == axis_mode.AxisMode.manual:
188
- return self._controlWidget.getROIOrigin()
189
- else:
190
- return None
191
-
192
- def getImgSubsampling(self):
193
- return self._controlWidget.getImgSubsampling()
194
-
195
- def _computationRequested(self):
196
- self.sigComputationRequested.emit()
197
-
198
- def setLocked(self, locked):
199
- with block_signals(self):
200
- if self._axis_params.mode not in (
201
- axis_mode.AxisMode.read,
202
- axis_mode.AxisMode.manual,
203
- ):
204
- self._axis_params.mode = axis_mode.AxisMode.manual
205
- self._controlWidget.setLocked(locked)
206
-
207
- self.sigAxisEditionLocked.emit(locked)
208
-
209
- def isModeLock(self):
210
- return self._controlWidget.isModeLock()
211
-
212
- def _validated(self):
213
- """callback when the validate button is activated"""
214
- self.sigApply.emit()
215
-
216
- def _setRadio2Flip(self, checked):
217
- self._plot.setRadio2Flip(checked)
218
-
219
- def _flipChanged(self, checked):
220
- if self.getMode() == axis_mode.AxisMode.manual:
221
- self._lastManualFlip = self._plot.isRadio2Flip()
222
-
223
- if checked == self._flipB:
224
- return
225
- else:
226
- self._flipB = checked
227
- self._updatePlot()
228
-
229
- def setReconsParams(self, axis):
230
- """
231
-
232
- :param AxisRP axis: axis to edit
233
- :return:
234
- """
235
- assert isinstance(axis, QAxisRP)
236
- self._axis_params = axis
237
- with block_signals(self):
238
- self.resetShift()
239
- self._controlWidget.setAxis(axis)
240
-
241
- def setScan(self, scan):
242
- """
243
- Update the interface concerning the given scan. Try to display the
244
- radios for angle 0 and 180.
245
-
246
- :param scan: scan for which we want the axis updated.
247
- :type scan: Union[str, tomwer.core.scan.TomoBase]
248
- """
249
- self.clear()
250
- _scan = scan
251
- if type(scan) is str:
252
- try:
253
- _scan = ScanFactory.create_scan_object(scan)
254
- except ValueError:
255
- raise ValueError("Fail to discover a valid scan in %s" % scan)
256
- elif not isinstance(_scan, TomwerScanBase):
257
- raise ValueError(
258
- f"type of {scan} ({type(scan)}) is invalid, scan should be a file/dir path or an instance of ScanBase"
259
- )
260
- assert isinstance(_scan, TomwerScanBase)
261
-
262
- if _scan.axis_params is None:
263
- _scan.axis_params = QAxisRP()
264
-
265
- if self._scan is not None:
266
- self._scan.axis_params.sigAxisUrlChanged.disconnect(self._updatePlot)
267
- if (
268
- scan.estimated_cor_frm_motor is not None
269
- and self.updateAutomaticallyEstimatedCor()
270
- ):
271
- self.setEstimatedCor(scan.estimated_cor_frm_motor)
272
-
273
- # update visualization
274
- self._scan = _scan
275
- self._scan.axis_params.sigAxisUrlChanged.connect(self._updatePlot)
276
- self._controlWidget.setScan(scan=self._scan)
277
- self._updatePlot()
278
- self.getPlot().getPlot().resetZoom()
279
-
280
- def _updatePlot(self):
281
- if self._scan is None:
282
- return
283
- self._urlChanged()
284
-
285
- def _urlChanged(self):
286
- with block_signals(self):
287
- coreAngleMode = CorAngleMode.from_value(self.__recons_params.angle_mode)
288
- if self._scan is None:
289
- return
290
- axis_rp = self._scan.axis_params
291
- if coreAngleMode is CorAngleMode.manual_selection:
292
- manual_sel_widget = (
293
- self._controlWidget._mainWidget._inputWidget._angleModeWidget._manualFrameSelection
294
- )
295
- urls = manual_sel_widget.getFramesUrl(as_txt=False)
296
- axis_rp.axis_url_1, axis_rp.axis_url_2 = urls
297
- axis_rp.flip_lr = manual_sel_widget.isFrame2LRFLip()
298
- else:
299
- axis_rp.flip_lr = True
300
- res = self._scan.get_opposite_projections(mode=coreAngleMode)
301
- axis_rp.axis_url_1 = res[0]
302
- axis_rp.axis_url_2 = res[1]
303
-
304
- if axis_rp.n_url() < 2:
305
- _logger.error("Fail to detect radio for axis calculation")
306
- elif axis_rp.axis_url_1.url:
307
- # if necessary normalize data
308
- axis_rp.axis_url_1.normalize_data(self._scan, log_=False)
309
- axis_rp.axis_url_2.normalize_data(self._scan, log_=False)
310
-
311
- paganin = self.__recons_params.paganin_preproc
312
- # check if normed
313
- if paganin:
314
- imgA = axis_rp.axis_url_1.normalized_data_paganin
315
- imgB = axis_rp.axis_url_2.normalized_data_paganin
316
- else:
317
- imgA = axis_rp.axis_url_1.normalized_data
318
- imgB = axis_rp.axis_url_2.normalized_data
319
- assert imgA is not None
320
- assert imgB is not None
321
- self.setImages(imgA=imgA, imgB=imgB, flipB=axis_rp.flip_lr)
322
- else:
323
- _logger.error(
324
- "fail to find radios for angle 0 and 180. Unable to update axis gui"
325
- )
326
-
327
- def clear(self):
328
- if self._scan is not None:
329
- self._scan.axis_params.sigAxisUrlChanged.disconnect(self._updatePlot)
330
- self._scan = None
331
-
332
- def setImages(self, imgA, imgB, flipB):
333
- """
334
-
335
- :warning: does not reset the shift when change images
336
-
337
- :param numpy.array imgA: first image to compare. Will be the one shifted
338
- :param numpy.array imgB: second image to compare
339
- :param bool flipB: True if the image B has to be flipped
340
- :param bool paganin: True to apply paganin phase retrieval
341
- """
342
- assert imgA is not None
343
- assert imgB is not None
344
- _imgA = imgA
345
- _imgB = imgB
346
-
347
- if _imgA.shape != _imgB.shape:
348
- _logger.error(
349
- "The two provided images have incoherent shapes "
350
- f"({_imgA.shape} vs {_imgB.shape})"
351
- )
352
- elif _imgA.ndim != 2:
353
- _logger.error("Image shape are not 2 dimensional")
354
- else:
355
- self._imgA = _imgA
356
- self._imgB = _imgB
357
- self._flipB = flipB
358
-
359
- self._controlWidget._roiControl.setLimits(
360
- width=self._imgA.shape[1], height=self._imgA.shape[0]
361
- )
362
- self._updateShift()
363
-
364
- def _updateSubSampling(self):
365
- self._updateShift()
366
- self.getPlot().getPlot().resetZoom()
367
-
368
- def _updateShift(self, xShift=None, yShift=None):
369
- if self._imgA is None or self._imgB is None:
370
- return
371
- xShift = xShift or self.getXShift()
372
- yShift = yShift or self.getYShift()
373
-
374
- # TODO: we might avoid flipping image at each new x_shift...
375
- _imgA, _imgB = self._getRawImages()
376
- # apply shift
377
- if xShift == 0.0 and yShift == 0.0:
378
- self._shiftedImgA = _imgA
379
- self._shiftedImgB = _imgB
380
- else:
381
- try:
382
- cval_imgA = _imgA.min()
383
- cval_imgB = _imgB.min()
384
- except ValueError:
385
- _logger.warning("enable to retrieve imgA.min() and / or" "imgB.min().")
386
- cval_imgA = 0
387
- cval_imgB = 0
388
- try:
389
- x_shift = self.getXShift() / self.getImgSubsampling()
390
- y_shift = self.getYShift() / self.getImgSubsampling()
391
- self._shiftedImgA = image.shift_img(
392
- data=_imgA,
393
- dx=-x_shift,
394
- dy=y_shift,
395
- cval=cval_imgA,
396
- )
397
- self._shiftedImgB = image.shift_img(
398
- data=_imgB,
399
- dx=x_shift,
400
- dy=y_shift,
401
- cval=cval_imgB,
402
- )
403
- crop = self.getPlot().cropComparedImages()
404
-
405
- if not crop:
406
- # handling of the crop:
407
- # 1. we will concatenate the shifted array with the unshifted to avoid crop
408
- # 2. in order to handled properly the shift and overlaps we need to add an empty array
409
- abs_x_shift = abs(int(x_shift))
410
- buffer_array_img_A = numpy.full(
411
- shape=(self._shiftedImgA.shape[0], abs_x_shift),
412
- fill_value=cval_imgA,
413
- )
414
- buffer_array_img_B = numpy.full(
415
- shape=(self._shiftedImgB.shape[0], abs_x_shift),
416
- fill_value=cval_imgB,
417
- )
418
- if x_shift == 0:
419
- pass
420
- elif x_shift > 0:
421
- self._shiftedImgA = numpy.concatenate(
422
- (
423
- _imgA[:, :abs_x_shift],
424
- self._shiftedImgA,
425
- buffer_array_img_A,
426
- ),
427
- axis=1,
428
- )
429
- self._shiftedImgB = numpy.concatenate(
430
- (
431
- buffer_array_img_B,
432
- self._shiftedImgB,
433
- _imgB[:, -abs_x_shift:],
434
- ),
435
- axis=1,
436
- )
437
- else:
438
- self._shiftedImgA = numpy.concatenate(
439
- (
440
- buffer_array_img_A,
441
- self._shiftedImgA,
442
- _imgA[:, :abs_x_shift],
443
- ),
444
- axis=1,
445
- )
446
- self._shiftedImgB = numpy.concatenate(
447
- (
448
- _imgB[:, :abs_x_shift],
449
- self._shiftedImgB,
450
- buffer_array_img_B,
451
- ),
452
- axis=1,
453
- )
454
- except ValueError as e:
455
- _logger.error(e)
456
- self._shiftedImgA = _imgA
457
- self._shiftedImgB = _imgB
458
-
459
- with block_signals(self):
460
- try:
461
- self._plot.setData(
462
- image1=self._shiftedImgA,
463
- image2=self._shiftedImgB,
464
- )
465
- except ValueError:
466
- _logger.warning(
467
- "Unable to set images. Maybe there is some "
468
- "incomplete dataset or an issue with "
469
- "normalization."
470
- )
471
- roi_origin = self.getROIOrigin()
472
- if roi_origin is not None:
473
- x_origin, y_origin = roi_origin
474
- else:
475
- x_origin = y_origin = None
476
- self._lastXShift = xShift
477
- self._lastYShift = yShift
478
- self._lastXOrigin = x_origin
479
- self._lastYOrigin = y_origin
480
-
481
- def _getRawImages(self):
482
- def selectROI(data, width, height, x_origin, y_origin, subsampling):
483
- assert subsampling > 0
484
- x_min = x_origin - width // 2
485
- x_max = x_origin + width // 2
486
- y_min = y_origin - height // 2
487
- y_max = y_origin + height // 2
488
- return data[y_min:y_max:subsampling, x_min:x_max:subsampling]
489
-
490
- # get images and apply ROI if any
491
- _roi_dims = self.getROIDims()
492
- _origin = self.getROIOrigin()
493
- subsampling = self.getImgSubsampling()
494
- _imgA = self._imgA
495
- _imgB = self._imgB
496
- # flip image B
497
- _imgB = numpy.fliplr(_imgB) if self._flipB else _imgB
498
- if _roi_dims is not None:
499
- assert type(_roi_dims) is tuple, f"invalide roi value {_roi_dims}"
500
- _imgA = selectROI(
501
- _imgA,
502
- width=_roi_dims[0],
503
- height=_roi_dims[1],
504
- x_origin=_origin[0],
505
- y_origin=_origin[1],
506
- subsampling=subsampling,
507
- )
508
- _imgB = selectROI(
509
- _imgB,
510
- width=_roi_dims[0],
511
- height=_roi_dims[1],
512
- x_origin=_origin[0],
513
- y_origin=_origin[1],
514
- subsampling=subsampling,
515
- )
516
- return _imgA, _imgB
517
-
518
- def _updateAuto(self):
519
- _imgA, _imgB = self._getRawImages()
520
- correlation = scipy.signal.correlate2d(in1=_imgA, in2=_imgB)
521
- y, x = numpy.unravel_index(numpy.argmax(correlation), correlation.shape)
522
- self._setShift(x=x, y=y)
523
-
524
- def resetShift(self):
525
- with block_signals(self._controlWidget):
526
- self._controlWidget.reset()
527
- if self._imgA is not None and self._imgB is not None:
528
- self.setImages(imgA=self._imgA, imgB=self._imgB, flipB=self._flipB)
529
-
530
-
531
- class _AxisRead(qt.QWidget):
532
- """Widget to select a position value from a file"""
533
-
534
- sigFileChanged = qt.Signal(str)
535
-
536
- def __init__(self, parent, axis=None):
537
- qt.QWidget.__init__(self, parent)
538
- self._axis = None
539
- if axis:
540
- self.setAxis(axis)
541
- self.setLayout(qt.QHBoxLayout())
542
-
543
- self.layout().addWidget(qt.QLabel("File", parent=self))
544
- self._filePathQLE = qt.QLineEdit("", parent=self)
545
- self.layout().addWidget(self._filePathQLE)
546
- self._fileSelPB = qt.QPushButton("select", parent=self)
547
- self.layout().addWidget(self._fileSelPB)
548
-
549
- # connect signal / slot
550
- self._fileSelPB.pressed.connect(self._selectFile)
551
- self._filePathQLE.textChanged.connect(self._fileChanged)
552
-
553
- def setAxis(self, axis):
554
- assert isinstance(axis, QAxisRP)
555
- self._axis = axis
556
-
557
- def _selectFile(self): # pragma: no cover
558
- dialog = qt.QFileDialog(self)
559
- dialog.setFileMode(qt.QFileDialog.ExistingFile)
560
-
561
- if not dialog.exec_():
562
- dialog.close()
563
- return
564
-
565
- _file_path = dialog.selectedFiles()[0]
566
- _logger.info("user select file %s for reading position value" % _file_path)
567
- self._filePathQLE.setText(dialog.selectedFiles()[0])
568
-
569
- def _fileChanged(self, file_path):
570
- """callback when the line edit (containing the file path) changed"""
571
- if self._axis and os.path.isfile(file_path):
572
- self._axis.set_position_frm_par_file(file_path, force=True)
573
-
574
-
575
- class _AxisManual(qt.QWidget):
576
- """
577
- Widget to define the shift to apply on an image
578
- """
579
-
580
- sigShiftChanged = qt.Signal(float, float)
581
- """Signal emitted when requested shift changed. Parameter is x, y"""
582
-
583
- sigModeLockChanged = qt.Signal(bool)
584
- """Signal emitted when the mode is lock or unlock"""
585
-
586
- sigResetZoomRequested = qt.Signal()
587
- """Signal emitted when request a zoom reset from the plot"""
588
-
589
- sigSubsamplingChanged = qt.Signal()
590
- """Signal emitted when subsampling change"""
591
-
592
- sigUrlChanged = qt.Signal()
593
- """Signal emit when frames urls changed"""
594
-
595
- def __init__(self, parent, reconsParams):
596
- assert isinstance(reconsParams, QAxisRP)
597
- qt.QWidget.__init__(self, parent)
598
- self._xShift = 0
599
- self._yShift = 0
600
- self._recons_params = reconsParams or QAxisRP()
601
- self._axis = None
602
-
603
- self.setLayout(qt.QVBoxLayout())
604
-
605
- self._manualSelectionWidget = _AxisManualSelection(
606
- parent=self, shift_mode=ShiftMode.x_only
607
- )
608
- self._manualSelectionWidget.layout().setContentsMargins(0, 0, 0, 0)
609
-
610
- self._readFileSelWidget = _AxisRead(parent=self)
611
- self._readFileSelWidget.layout().setContentsMargins(0, 0, 0, 0)
612
-
613
- self._displacementSelector = self._manualSelectionWidget._displacementSelector
614
- self._shiftControl = self._manualSelectionWidget._shiftControl
615
- self._roiControl = self._manualSelectionWidget._roiControl
616
- self._imgOpts = self._manualSelectionWidget._imgOpts
617
-
618
- self._mainWidget = AxisTabWidget(
619
- parent=self,
620
- mode_dependant_widget=self._manualSelectionWidget,
621
- read_file_sel_widget=self._readFileSelWidget,
622
- recons_params=self._recons_params,
623
- )
624
-
625
- self.layout().addWidget(self._mainWidget)
626
-
627
- # signal / slot connection
628
- self._shiftControl.sigShiftLeft.connect(self._incrementLeftShift)
629
- self._shiftControl.sigShiftRight.connect(self._incrementRightShift)
630
- self._shiftControl.sigShiftTop.connect(self._incrementTopShift)
631
- self._shiftControl.sigShiftBottom.connect(self._incrementBottomShift)
632
- self._shiftControl.sigReset.connect(self._resetShift)
633
- self._shiftControl.sigShiftChanged.connect(self._setShiftAndSignal)
634
- self._mainWidget.sigLockModeChanged.connect(self._modeLockChanged)
635
- self._manualSelectionWidget.sigResetZoomRequested.connect(
636
- self._requestZoomReset
637
- )
638
- self._imgOpts.sigSubsamplingChanged.connect(self.sigSubsamplingChanged)
639
- self._mainWidget.sigUrlChanged.connect(self.sigUrlChanged)
640
-
641
- # expose API
642
- self.getShiftStep = self._displacementSelector.getStepSize
643
- self.setShiftStep = self._displacementSelector.setStepSize
644
- self.sigRoiChanged = self._roiControl.sigRoiChanged
645
- self.sigAuto = self._shiftControl.sigAuto
646
- self.getROIOrigin = self._roiControl.getROIOrigin
647
- self.getImgSubsampling = self._imgOpts.getSubsampling
648
- self.getMode = self._mainWidget.getMode
649
- self.sigModeChanged = self._mainWidget.sigModeChanged
650
- self.isModeLock = self._mainWidget.isModeLock
651
- self.setModeLock = self._mainWidget.setModeLock
652
-
653
- # set up interface
654
- self.setAxis(self._recons_params)
655
-
656
- def setScan(self, scan):
657
- self._mainWidget.setScan(scan=scan)
658
- self._roiControl.setScan(scan=scan)
659
-
660
- def manual_uses_full_image(self, value):
661
- self._roiControl.manual_uses_full_image(value)
662
-
663
- def _incrementLeftShift(self):
664
- self._incrementShift("left")
665
-
666
- def _incrementRightShift(self):
667
- self._incrementShift("right")
668
-
669
- def _incrementTopShift(self):
670
- self._incrementShift("top")
671
-
672
- def _incrementBottomShift(self):
673
- self._incrementShift("bottom")
674
-
675
- def _setShiftAndSignal(self, x, y):
676
- if x == self._xShift and y == self._yShift:
677
- return
678
- self.setShift(x, y)
679
- self._shiftControl._updateShiftInfo(x=x, y=y)
680
- self.sigShiftChanged.emit(x, y)
681
-
682
- def setAxis(self, axis):
683
- if axis == self._axis:
684
- return
685
- assert isinstance(axis, QAxisRP)
686
- with block_signals(self):
687
- if self._axis:
688
- self._axis.sigChanged.disconnect(self._updateAxisView)
689
- self._axis = axis
690
- self.setXShift(self._axis.relative_cor_value)
691
- self._mainWidget.setAxisParams(self._axis)
692
- self._readFileSelWidget.setAxis(self._axis)
693
- self._updateAxisView()
694
- self._axis.sigChanged.connect(self._updateAxisView)
695
-
696
- def _modeLockChanged(self, lock):
697
- self.sigModeLockChanged.emit(lock)
698
-
699
- def setMode(self, mode):
700
- with block_signals(self._axis):
701
- self._axis.mode = mode
702
- self._mainWidget._calculationWidget.setMode(mode)
703
- self._updateAxisView()
704
- self._axis.sigChanged.emit()
705
-
706
- def updateAutomaticallyEstimatedCor(self):
707
- return self._mainWidget.updateAutomaticallyEstimatedCor()
708
-
709
- def setUpdateAutomaticallyEstimatedCor(self, value):
710
- self._mainWidget.setUpdateAutomaticallyEstimatedCor(value)
711
-
712
- def setEstimatedCor(self, value):
713
- self._mainWidget.setEstimatedCorValue(value=value)
714
-
715
- def getEstimatedCor(self):
716
- return self._mainWidget.getEstimatedCor()
717
-
718
- def _updateAxisView(self):
719
- with block_signals(self._axis):
720
- if self._axis.relative_cor_value not in (None, "..."):
721
- self.setXShift(self._axis.relative_cor_value)
722
-
723
- self._manualSelectionWidget.setVisible(
724
- self._axis.mode is axis_mode.AxisMode.manual
725
- )
726
- self._readFileSelWidget.setVisible(self._axis.mode is axis_mode.AxisMode.read)
727
-
728
- def getAxis(self):
729
- return self._axis
730
-
731
- def _incrementShift(self, direction):
732
- assert direction in ("left", "right", "top", "bottom")
733
- if direction == "left":
734
- self.setXShift(self._xShift - self.getShiftStep())
735
- elif direction == "right":
736
- self.setXShift(self._xShift + self.getShiftStep())
737
- elif direction == "top":
738
- self.setYShift(self._yShift + self.getShiftStep())
739
- else:
740
- self.setYShift(self._yShift - self.getShiftStep())
741
-
742
- self._shiftControl._updateShiftInfo(x=self._xShift, y=self._yShift)
743
-
744
- def _resetShift(self):
745
- with block_signals(self._axis):
746
- self.setXShift(0)
747
- self.setYShift(0)
748
- self._shiftControl._updateShiftInfo(x=self._xShift, y=self._yShift)
749
-
750
- self.sigShiftChanged.emit(self._xShift, self._yShift)
751
-
752
- def getXShift(self):
753
- if self._xShift == "...":
754
- return 0
755
- return self._xShift
756
-
757
- def getYShift(self):
758
- if self._yShift == "...":
759
- return 0
760
- return self._yShift
761
-
762
- def setXShift(self, x: float):
763
- self.setShift(x=x, y=self._yShift)
764
-
765
- def setYShift(self, y):
766
- self.setShift(x=self._xShift, y=y)
767
-
768
- def setShift(self, x, y):
769
- if x == self._xShift and y == self._yShift:
770
- return
771
- self._xShift = x if x is not None else 0.0
772
- self._yShift = y if y is not None else 0.0
773
- if self._axis:
774
- with block_signals(self._axis):
775
- self._axis.set_relative_value(x)
776
- self._shiftControl._updateShiftInfo(x=self._xShift, y=self._yShift)
777
- if not isinstance(self._xShift, str):
778
- # filter `...` and `?` values (used for issues or processing)
779
- self.sigShiftChanged.emit(self._xShift, self._yShift)
780
-
781
- def reset(self):
782
- with block_signals(self):
783
- self.setShift(0, 0)
784
- self.sigShiftChanged.emit(self._xShift, self._yShift)
785
-
786
- def setLocked(self, locked):
787
- self._mainWidget.setEnabled(not locked)
788
-
789
- def _requestZoomReset(self):
790
- self.sigResetZoomRequested.emit()
791
-
792
- def getROIDims(self):
793
- return self._roiControl.getROIDims()
794
-
795
-
796
- class _AxisManualSelection(qt.QWidget):
797
- sigResetZoomRequested = qt.Signal()
798
- """Signal emitted when a zoom request is necessary (when change to full
799
- image)"""
800
-
801
- def __init__(self, parent, shift_mode):
802
- qt.QWidget.__init__(self, parent)
803
- self.setLayout(qt.QVBoxLayout())
804
- self._displacementSelector = StepSizeSelectorWidget(
805
- parent=self,
806
- fine_value=0.1,
807
- medium_value=1.0,
808
- rough_value=None,
809
- dtype=float,
810
- )
811
- self.layout().addWidget(self._displacementSelector)
812
-
813
- self._shiftControl = _ShiftControl(parent=self, shift_mode=shift_mode)
814
- self.layout().addWidget(self._shiftControl)
815
-
816
- self._roiControl = _ROIControl(parent=self)
817
- self.layout().addWidget(self._roiControl)
818
-
819
- self._imgOpts = _ImgOpts(parent=self)
820
- self.layout().addWidget(self._imgOpts)
821
-
822
- # connect signal / slot
823
- self._roiControl.sigResetZoomRequested.connect(self.sigResetZoomRequested)
824
-
825
-
826
- class _ROIControl(qt.QGroupBox):
827
- """
828
- Widget used to define the ROI on images to compare
829
- """
830
-
831
- sigRoiChanged = qt.Signal(object)
832
- """Signal emitted when the ROI changed"""
833
- sigResetZoomRequested = qt.Signal()
834
- """Signal emitted when a zoom request is necessary (when change to full
835
- image)"""
836
-
837
- def __init__(self, parent):
838
- qt.QGroupBox.__init__(self, "ROI selection", parent)
839
- self.setLayout(qt.QVBoxLayout())
840
-
841
- self._buttonGrp = qt.QButtonGroup(parent=self)
842
- self._buttonGrp.setExclusive(True)
843
-
844
- self._roiWidget = qt.QWidget(parent=self)
845
- self._roiWidget.setLayout(qt.QHBoxLayout())
846
- self._roiWidget.layout().setContentsMargins(0, 0, 0, 0)
847
- self._fullImgButton = qt.QRadioButton("full image", parent=self)
848
- self._buttonGrp.addButton(self._fullImgButton)
849
- self.layout().addWidget(self._fullImgButton)
850
- self._roiButton = qt.QRadioButton("ROI", parent=self._roiWidget)
851
- self._roiWidget.layout().addWidget(self._roiButton)
852
- self._buttonGrp.addButton(self._roiButton)
853
- self._roiDefinition = _ROIDefinition(parent=self)
854
- self._roiWidget.layout().addWidget(self._roiDefinition)
855
- self.layout().addWidget(self._roiWidget)
856
-
857
- # connect signal / Slot
858
- self._roiButton.toggled.connect(self._roiDefinition.setEnabled)
859
- self._fullImgButton.toggled.connect(self.sigResetZoomRequested)
860
- self._roiButton.toggled.connect(self.sigResetZoomRequested)
861
-
862
- # expose API
863
- self.sigRoiChanged = self._roiDefinition.sigRoiChanged
864
- self.getROIOrigin = self._roiDefinition.getROIOrigin
865
- self.setLimits = self._roiDefinition.setLimits
866
- self.setScan = self._roiDefinition.setScan
867
-
868
- # setup for full image
869
- self._fullImgButton.setChecked(True)
870
-
871
- def getROIDims(self):
872
- if self._roiButton.isChecked():
873
- return self._roiDefinition.getROIDims()
874
- else:
875
- return None
876
-
877
- def manual_uses_full_image(self, activate):
878
- if activate:
879
- self._fullImgButton.setChecked(True)
880
- else:
881
- self._roiButton.setChecked(True)
882
-
883
-
884
- class _ROIDefinition(qt.QWidget):
885
- """
886
- Widget used to define ROI width and height.
887
-
888
- :note: emit ROI == None if setDisabled
889
- """
890
-
891
- sigRoiChanged = qt.Signal(object)
892
- """Signal emitted when the ROI changed"""
893
-
894
- def __init__(self, parent):
895
- qt.QWidget.__init__(self, parent)
896
- self.setLayout(qt.QGridLayout())
897
- self._already_set = False
898
-
899
- # width & height
900
- self.layout().addWidget(qt.QLabel("dims", self), 0, 0)
901
- self._widthSB = qt.QSpinBox(parent=self)
902
- self._widthSB.setSingleStep(2)
903
- self._widthSB.setMaximum(10000)
904
- self._widthSB.setSuffix(" px")
905
- self._widthSB.setPrefix("w: ")
906
- self._widthSB.setToolTip("ROI width")
907
- self.layout().addWidget(self._widthSB, 0, 1)
908
- self._heightSB = qt.QSpinBox(parent=self)
909
- self._heightSB.setSingleStep(2)
910
- self._heightSB.setSuffix(" px")
911
- self._heightSB.setPrefix("h: ")
912
- self._heightSB.setToolTip("ROI height")
913
- self._heightSB.setMaximum(10000)
914
- self.layout().addWidget(self._heightSB, 0, 2)
915
-
916
- # origin x and y position
917
- self.layout().addWidget(qt.QLabel("origin", self), 1, 0)
918
- self._xOriginSB = qt.QSpinBox(parent=self)
919
- self._xOriginSB.setSingleStep(10)
920
- self._xOriginSB.setMaximum(10000)
921
- self._xOriginSB.setPrefix("x: ")
922
- self.layout().addWidget(self._xOriginSB, 1, 1)
923
- self._yOriginSB = qt.QSpinBox(parent=self)
924
- self._yOriginSB.setSingleStep(10)
925
- self._yOriginSB.setPrefix("y: ")
926
- self._yOriginSB.setMaximum(10000)
927
- self.layout().addWidget(self._yOriginSB, 1, 2)
928
-
929
- # Signal / Slot connection
930
- self._widthSB.editingFinished.connect(self.__roiChanged)
931
- self._heightSB.editingFinished.connect(self.__roiChanged)
932
- self._xOriginSB.editingFinished.connect(self.__roiChanged)
933
- self._yOriginSB.editingFinished.connect(self.__roiChanged)
934
-
935
- def __roiChanged(self, *args, **kwargs):
936
- self.sigRoiChanged.emit((self.getROIDims(), self.getROIOrigin()))
937
-
938
- def setLimits(self, width, height):
939
- """
940
-
941
- :param int x: width maximum value
942
- :param int height: height maximum value
943
- """
944
- for spinButton in (self._widthSB, self._heightSB):
945
- spinButton.blockSignals(True)
946
- assert type(width) is int
947
- assert type(height) is int
948
- valueChanged = False
949
- if self._widthSB.value() > width:
950
- self._widthSB.setValue(width)
951
- valueChanged = True
952
- if self._heightSB.value() > height:
953
- self._heightSB.setValue(height)
954
- valueChanged = True
955
-
956
- # if this is the first limit definition, propose default width and
957
- # height
958
- if self._widthSB.value() == 0:
959
- self._widthSB.setValue(min(256, width))
960
- valueChanged = True
961
- if self._heightSB.value() == 0:
962
- self._heightSB.setValue(min(256, height))
963
- valueChanged = True
964
-
965
- # define minimum / maximum
966
- self._widthSB.setRange(1, width)
967
- self._heightSB.setRange(1, height)
968
- for spinButton in (self._widthSB, self._heightSB):
969
- spinButton.blockSignals(False)
970
- if valueChanged is True:
971
- self.__roiChanged()
972
-
973
- def getROIDims(self):
974
- """
975
-
976
- :return: (width, height) or None
977
- :rtype: Union[None, tuple]
978
- """
979
- if self.isEnabled():
980
- return (self._widthSB.value(), self._heightSB.value())
981
- else:
982
- return None
983
-
984
- def getROIOrigin(self):
985
- return (self._xOriginSB.value(), self._yOriginSB.value())
986
-
987
- def setEnabled(self, *arg, **kwargs):
988
- qt.QWidget.setEnabled(self, *arg, **kwargs)
989
- self.__roiChanged()
990
-
991
- def setScan(self, scan):
992
- if not self._already_set:
993
- self._already_set = True
994
- try:
995
- x_origin = scan.dim_1 // 2
996
- y_origin = scan.dim_2 // 2
997
- self._xOriginSB.setValue(x_origin)
998
- self._yOriginSB.setValue(y_origin)
999
- except Exception:
1000
- _logger.warning(f"unable to determine origin for {scan}")
1001
-
1002
-
1003
- @enum.unique
1004
- class ShiftMode(enum.Enum):
1005
- x_only = 0
1006
- y_only = 1
1007
- x_and_y = 2
1008
-
1009
-
1010
- class _ShiftControl(qt.QWidget):
1011
- """
1012
- Widget to control the shift step we want to apply
1013
- """
1014
-
1015
- sigShiftLeft = qt.Signal()
1016
- """Signal emitted when the left button is activated"""
1017
- sigShiftRight = qt.Signal()
1018
- """Signal emitted when the right button is activated"""
1019
- sigShiftTop = qt.Signal()
1020
- """Signal emitted when the top button is activated"""
1021
- sigShiftBottom = qt.Signal()
1022
- """Signal emitted when the bottom button is activated"""
1023
- sigReset = qt.Signal()
1024
- """Signal emitted when the reset button is activated"""
1025
- sigAuto = qt.Signal()
1026
- """Signal emitted when the auto button is activated"""
1027
- sigShiftChanged = qt.Signal(float, float)
1028
- """Signal emitted ony when xLE and yLE edition is finished"""
1029
-
1030
- def __init__(self, parent, shift_mode):
1031
- """
1032
-
1033
- :param parent: qt.QWidget
1034
- :param ShiftMode shift_mode: what are the shift we want to control
1035
- """
1036
- qt.QWidget.__init__(self, parent)
1037
- self.setLayout(qt.QGridLayout())
1038
- self.layout().setContentsMargins(0, 0, 0, 0)
1039
-
1040
- self._leftButton = qt.QPushButton("left", parent=self)
1041
- self.layout().addWidget(self._leftButton, 1, 0)
1042
-
1043
- self._rightButton = qt.QPushButton("right", parent=self)
1044
- self.layout().addWidget(self._rightButton, 1, 3)
1045
-
1046
- self._shiftInfo = _ShiftInformation(parent=self)
1047
- self.layout().addWidget(self._shiftInfo, 1, 1)
1048
- self._shiftInfo._updateShiftInfo(x=0.0, y=0.0)
1049
-
1050
- self._topButton = qt.QPushButton("top", parent=self)
1051
- self.layout().addWidget(self._topButton, 0, 1)
1052
-
1053
- self._bottomButton = qt.QPushButton("bottom", parent=self)
1054
- self.layout().addWidget(self._bottomButton, 2, 1)
1055
-
1056
- self._resetButton = qt.QPushButton("reset", parent=self)
1057
- self.layout().addWidget(self._resetButton, 3, 2, 3, 4)
1058
-
1059
- self._autoButton = qt.QPushButton("auto", parent=self)
1060
- self.layout().addWidget(self._autoButton, 3, 0, 3, 2)
1061
- self._autoButton.hide()
1062
-
1063
- # Signal / Slot connection
1064
- self._leftButton.pressed.connect(self.sigShiftLeft.emit)
1065
- self._rightButton.pressed.connect(self.sigShiftRight.emit)
1066
- self._topButton.pressed.connect(self.sigShiftTop.emit)
1067
- self._bottomButton.pressed.connect(self.sigShiftBottom.emit)
1068
- self._resetButton.pressed.connect(self.sigReset.emit)
1069
- self._autoButton.pressed.connect(self.sigAuto.emit)
1070
- self._shiftInfo.sigShiftChanged.connect(self.sigShiftChanged.emit)
1071
-
1072
- # expose API
1073
- self._updateShiftInfo = self._shiftInfo._updateShiftInfo
1074
-
1075
- self.setShiftMode(shift_mode)
1076
-
1077
- def setShiftMode(self, shift_mode):
1078
- show_x_shift = shift_mode in (ShiftMode.x_only, ShiftMode.x_and_y)
1079
- show_y_shift = shift_mode in (ShiftMode.y_only, ShiftMode.x_and_y)
1080
- self._leftButton.setVisible(show_x_shift)
1081
- self._rightButton.setVisible(show_x_shift)
1082
- self._topButton.setVisible(show_y_shift)
1083
- self._bottomButton.setVisible(show_y_shift)
1084
- self._shiftInfo._xLE.setVisible(show_x_shift)
1085
- self._shiftInfo._xLabel.setVisible(show_x_shift)
1086
- self._shiftInfo._yLE.setVisible(show_y_shift)
1087
- self._shiftInfo._yLabel.setVisible(show_y_shift)
1088
-
1089
-
1090
- class _ImgOpts(qt.QGroupBox):
1091
- sigSubsamplingChanged = qt.Signal()
1092
- """Signal emitted when the subsampling change"""
1093
-
1094
- def __init__(self, parent, title="Image Option"):
1095
- super().__init__(title, parent)
1096
- self.setLayout(qt.QFormLayout())
1097
- self._subsamplingQSpinBox = qt.QSpinBox(self)
1098
- self.layout().addRow("subsampling:", self._subsamplingQSpinBox)
1099
- self._subsamplingQSpinBox.setMinimum(1)
1100
-
1101
- # set up
1102
- self._subsamplingQSpinBox.setValue(1)
1103
-
1104
- # connect signal / slot
1105
- self._subsamplingQSpinBox.valueChanged.connect(self._subsamplingChanged)
1106
-
1107
- def _subsamplingChanged(self):
1108
- self.sigSubsamplingChanged.emit()
1109
-
1110
- def getSubsampling(self):
1111
- return self._subsamplingQSpinBox.value()
1112
-
1113
- def setSubsampling(self, value):
1114
- return self._subsamplingQSpinBox.setValue(int(value))
1115
-
1116
-
1117
- class _ShiftInformation(qt.QWidget):
1118
- """
1119
- Widget displaying information about the current x and y shift.
1120
- Both x shift and y shift are editable.
1121
- """
1122
-
1123
- class _ShiftLineEdit(qt.QLineEdit):
1124
- def __init__(self, *args, **kwargs):
1125
- qt.QLineEdit.__init__(self, *args, **kwargs)
1126
- self._defaultBackgroundColor = None
1127
- # validator
1128
- validator = qt.QDoubleValidator(parent=self, decimals=2)
1129
- self.setValidator(validator)
1130
- self._getDefaultBackgroundColor()
1131
- # connect signal / slot
1132
- self.textEdited.connect(self._userEditing)
1133
- self.editingFinished.connect(self._userEndEditing)
1134
-
1135
- def sizeHint(self):
1136
- return qt.QSize(40, 10)
1137
-
1138
- def _getDefaultBackgroundColor(self):
1139
- if self._defaultBackgroundColor is None:
1140
- self._defaultBackgroundColor = self.palette().color(
1141
- self.backgroundRole()
1142
- )
1143
- return self._defaultBackgroundColor
1144
-
1145
- def _userEditing(self, *args, **kwargs):
1146
- palette = self.palette()
1147
- palette.setColor(self.backgroundRole(), EDITING_BACKGROUND_COLOR)
1148
- self.setPalette(palette)
1149
-
1150
- def _userEndEditing(self, *args, **kwargs):
1151
- palette = self.palette()
1152
- palette.setColor(
1153
- self.backgroundRole(),
1154
- self._getDefaultBackgroundColor(),
1155
- )
1156
- self.setPalette(palette)
1157
-
1158
- sigShiftChanged = qt.Signal(float, float)
1159
- """Signal emitted ony when xLE and yLE edition is finished"""
1160
-
1161
- def __init__(self, parent):
1162
- qt.QWidget.__init__(self, parent)
1163
- self.setLayout(qt.QHBoxLayout())
1164
- self.layout().setContentsMargins(0, 0, 0, 0)
1165
- self.layout().setSpacing(0)
1166
-
1167
- self._xLabel = qt.QLabel("x=", parent=self)
1168
- self.layout().addWidget(self._xLabel)
1169
- self._xLE = _ShiftInformation._ShiftLineEdit("", parent=self)
1170
- self.layout().addWidget(self._xLE)
1171
-
1172
- self._yLabel = qt.QLabel("y=", parent=self)
1173
- self.layout().addWidget(self._yLabel)
1174
- self._yLE = _ShiftInformation._ShiftLineEdit("", parent=self)
1175
- self.layout().addWidget(self._yLE)
1176
-
1177
- # connect Signal / Slot
1178
- self._xLE.editingFinished.connect(self._shiftChanged)
1179
- self._yLE.editingFinished.connect(self._shiftChanged)
1180
-
1181
- def _updateShiftInfo(self, x, y):
1182
- with block_signals(self):
1183
- if x is None:
1184
- x = 0.0
1185
- if y is None:
1186
- y = 0.0
1187
- x_text = x
1188
- if x_text != "...":
1189
- x_text = "%.1f" % float(x)
1190
- y_text = y
1191
- if y_text != "...":
1192
- y_text = "%.1f" % float(y)
1193
- self._xLE.setText(x_text)
1194
- self._yLE.setText(y_text)
1195
-
1196
- def _shiftChanged(self, *args, **kwargs):
1197
- self.sigShiftChanged.emit(float(self._xLE.text()), float(self._yLE.text()))
1198
-
1199
-
1200
- class _AxisOptionsWidget(qt.QWidget):
1201
- """GUI to tune the axis algorithm"""
1202
-
1203
- def __init__(self, parent, axis):
1204
- qt.QWidget.__init__(self, parent=parent)
1205
- assert isinstance(axis, QAxisRP)
1206
- self._axis = axis
1207
- self.setLayout(qt.QVBoxLayout())
1208
-
1209
- # define common options
1210
- self._commonOpts = qt.QWidget(parent=self)
1211
- self._commonOpts.setLayout(qt.QFormLayout())
1212
-
1213
- self._qcbDataMode = qt.QComboBox(parent=self)
1214
- for data_mode in AxisCalculationInput:
1215
- # paganin is not managed for sinogram
1216
- self._qcbDataMode.addItem(data_mode.name(), data_mode)
1217
- # for now not handle
1218
- # self._commonOpts.layout().addRow('data mode', self._qcbDataMode)
1219
- self._qcbDataMode.hide()
1220
-
1221
- # add scale option
1222
- self._scaleOpt = qt.QCheckBox(parent=self)
1223
- self._commonOpts.layout().addRow("scale the two images", self._scaleOpt)
1224
- self.layout().addWidget(self._commonOpts)
1225
-
1226
- # add option for computing min-max
1227
- # TODO
1228
- pass
1229
-
1230
- # add near options
1231
- self._nearOpts = _AxisNearOptsWidget(parent=self, axis=self._axis)
1232
- self.layout().addWidget(self._nearOpts)
1233
-
1234
- # set up
1235
- self.setCalculationInputType(self._axis.calculation_input_type)
1236
-
1237
- # connect signal / slot
1238
- self._scaleOpt.toggled.connect(self._updateScaleOpt)
1239
- self._qcbDataMode.currentIndexChanged.connect(self._updateInputType)
1240
- self._axis.sigChanged.connect(self._updateMode)
1241
-
1242
- def setMode(self, mode):
1243
- pass
1244
-
1245
- def _updateMode(self):
1246
- with block_signals(self):
1247
- index = self._qcbDataMode.findText(self._axis.calculation_input_type.name())
1248
- if index >= 0:
1249
- self._qcbDataMode.setCurrentIndex(index)
1250
-
1251
- def _updateScaleOpt(self, *arg, **kwargs):
1252
- self._axis.scale_img2_to_img1 = self.isImageScaled()
1253
-
1254
- def isImageScaled(self):
1255
- return self._scaleOpt.isChecked()
1256
-
1257
- def _updateInputType(self, *arg, **kwargs):
1258
- self._axis.calculation_input_type = self.getCalulationInputType()
1259
-
1260
- def getCalulationInputType(self, *arg, **kwargs):
1261
- return AxisCalculationInput.from_value(self._qcbDataMode.currentText())
1262
-
1263
- def setCalculationInputType(self, calculation_type):
1264
- calculation_type = AxisCalculationInput.from_value(calculation_type)
1265
- index_dm = self._qcbDataMode.findText(calculation_type.name())
1266
- self._qcbDataMode.setCurrentIndex(index_dm)
1267
-
1268
- def setAxisParams(self, axis):
1269
- self._nearOpts.setAxisParams(axis=axis)
1270
- self._axis = axis
1271
- with block_signals(self):
1272
- self._scaleOpt.setChecked(axis.scale_img2_to_img1)
1273
- index = self._qcbDataMode.findText(axis.calculation_input_type.name())
1274
- self._qcbDataMode.setCurrentIndex(index)
1275
-
1276
-
1277
- class _AxisNearOptsWidget(qt.QWidget):
1278
- """GUI dedicated to the neat option"""
1279
-
1280
- def __init__(self, parent, axis):
1281
- qt.QWidget.__init__(self, parent=parent)
1282
- assert isinstance(axis, QAxisRP)
1283
- self._axis = axis
1284
-
1285
- self.setLayout(qt.QFormLayout())
1286
-
1287
- self._stdMaxOpt = qt.QCheckBox(parent=self)
1288
- self.layout().addRow("look at max standard deviation", self._stdMaxOpt)
1289
-
1290
- self._nearWX = qt.QSpinBox(parent=self)
1291
- self._nearWX.setMinimum(1)
1292
- self._nearWX.setValue(5)
1293
- self.layout().addRow("window size", self._nearWX)
1294
-
1295
- self._fineStepX = qt.QDoubleSpinBox(parent=self)
1296
- self._fineStepX.setMinimum(0.05)
1297
- self._fineStepX.setSingleStep(0.05)
1298
- self._fineStepX.setMaximum(1.0)
1299
- self.layout().addRow("fine step x", self._fineStepX)
1300
-
1301
- # connect signal / Slot
1302
- self._stdMaxOpt.toggled.connect(self._lookforStxMaxChanged)
1303
- self._nearWX.valueChanged.connect(self._windowSizeChanged)
1304
- self._fineStepX.valueChanged.connect(self._fineStepXChanged)
1305
-
1306
- def _lookforStxMaxChanged(self, *args, **kwargs):
1307
- self._axis.look_at_stdmax = self.isLookAtStdMax()
1308
-
1309
- def isLookAtStdMax(self):
1310
- """
1311
-
1312
- :return: is the option for looking at max standard deviation is
1313
- activated
1314
- :rtype: bool
1315
- """
1316
- return self._stdMaxOpt.isChecked()
1317
-
1318
- def _windowSizeChanged(self, *args, **kwargs):
1319
- self._axis.near_wx = self.getWindowSize()
1320
-
1321
- def getWindowSize(self):
1322
- """
1323
-
1324
- :return: window size for near search
1325
- :rtype: int
1326
- """
1327
- return self._nearWX.value()
1328
-
1329
- def _fineStepXChanged(self, *args, **kwargs):
1330
- self._axis.fine_step_x = self.getFineStepX()
1331
-
1332
- def getFineStepX(self):
1333
- """
1334
-
1335
- :return: fine step x for near calculation
1336
- :rtype: float
1337
- """
1338
- return self._fineStepX.value()
1339
-
1340
- def setAxisParams(self, axis):
1341
- """
1342
-
1343
- :param axis: axis to edit
1344
- :type: AxisRP
1345
- """
1346
- with block_signals(self):
1347
- self._axis = axis
1348
- self._stdMaxOpt.setChecked(axis.look_at_stdmax)
1349
- self._nearWX.setValue(axis.near_wx)
1350
- self._fineStepX.setValue(axis.fine_step_x)
1351
-
1352
-
1353
- class AxisTabWidget(qt.QTabWidget):
1354
- """
1355
- TabWidget containing all the information to edit the AXIS parameters
1356
- """
1357
-
1358
- sigLockModeChanged = qt.Signal(bool)
1359
- """signal emitted when the mode lock change"""
1360
-
1361
- sigUrlChanged = qt.Signal()
1362
- """Signal emit when frames urls changed"""
1363
-
1364
- def __init__(
1365
- self,
1366
- recons_params,
1367
- parent=None,
1368
- mode_dependant_widget=None,
1369
- read_file_sel_widget=None,
1370
- ):
1371
- """
1372
-
1373
- :param recons_params: reconstruction parameters edited by the widget
1374
- :type: QAxisRP
1375
- :param mode_dependant_widget: widget used for manual selection of the
1376
- axis
1377
- :type mode_dependant_widget: Union[None, `._AxisManualSelection`]
1378
- :param read_file_sel_widget: widget used to select a par file containing
1379
- the axis position
1380
- :type read_file_sel_widget: Union[None, `._AxisRead`]
1381
- """
1382
- qt.QTabWidget.__init__(self, parent)
1383
- assert recons_params is not None
1384
- # first tab 'calculation widget'
1385
- self._calculationWidget = _CalculationWidget(
1386
- parent=self, axis_params=recons_params
1387
- )
1388
-
1389
- # second tab: options
1390
- self._optionsWidget = _AxisOptionsWidget(parent=self, axis=recons_params)
1391
- self._inputWidget = _InputWidget(parent=self, axis_params=recons_params)
1392
-
1393
- if mode_dependant_widget:
1394
- self._calculationWidget.layout().addWidget(mode_dependant_widget)
1395
-
1396
- if read_file_sel_widget:
1397
- self._calculationWidget.layout().addWidget(read_file_sel_widget)
1398
-
1399
- for widget in self._calculationWidget, self._optionsWidget:
1400
- spacer = qt.QWidget(self)
1401
- spacer.setSizePolicy(qt.QSizePolicy.Minimum, qt.QSizePolicy.Expanding)
1402
- widget.layout().addWidget(spacer)
1403
-
1404
- self._optionsSA = qt.QScrollArea(parent=self)
1405
- self._optionsSA.setWidget(self._optionsWidget)
1406
- self.addTab(self._calculationWidget, "calculation")
1407
- self.addTab(self._optionsSA, "options")
1408
- # simplify set up. Hide options
1409
- self._optionsSA.hide()
1410
- self.addTab(self._inputWidget, "input")
1411
-
1412
- # set up
1413
- self.setAxisParams(recons_params)
1414
- self._optionsWidget.setMode(self.getMode())
1415
- self._updatePossibleInput()
1416
-
1417
- # expose API
1418
- self.sigModeChanged = self._calculationWidget.sigModeChanged
1419
- self.isModeLock = self._calculationWidget.isModeLock
1420
- self.setModeLock = self._calculationWidget.setModeLock
1421
- self.setEstimatedCorValue = self._calculationWidget.setEstimatedCorValue
1422
- self.getEstimatedCor = self._calculationWidget.getEstimatedCor
1423
-
1424
- # connect signal / slot
1425
- self._calculationWidget.sigLockModeChanged.connect(self.sigLockModeChanged)
1426
- self.sigModeChanged.connect(self._updatePossibleInput)
1427
- self._inputWidget._sigUrlChanged.connect(self._urlChanged)
1428
- # not very nice but very convenient to have the setting near at the same level
1429
- self._calculationWidget._qleNearPosQLE.textChanged.connect(
1430
- self._inputWidget._changed
1431
- )
1432
-
1433
- def getNearPos(self):
1434
- return self._calculationWidget.getNearPosition()
1435
-
1436
- def setNearPos(self, position):
1437
- return self._calculationWidget.setNearPosition(position=position)
1438
-
1439
- def updateAutomaticallyEstimatedCor(self):
1440
- return self._calculationWidget.updateAutomaticallyEstimatedCor()
1441
-
1442
- def setUpdateAutomaticallyEstimatedCor(self, value):
1443
- self._calculationWidget.setUpdateAutomaticallyEstimatedCor(value)
1444
-
1445
- def _urlChanged(self):
1446
- self.sigUrlChanged.emit()
1447
-
1448
- def getMode(self):
1449
- """Return algorithm to use for axis calculation"""
1450
- return self._calculationWidget.getMode()
1451
-
1452
- def setScan(self, scan):
1453
- if scan is not None:
1454
- self._inputWidget.setScan(scan)
1455
-
1456
- def setAxisParams(self, axis):
1457
- with block_signals(self):
1458
- self._calculationWidget.setAxisParams(axis)
1459
- self._optionsWidget.setAxisParams(axis)
1460
- self._inputWidget.setAxisParams(axis)
1461
-
1462
- def _updatePossibleInput(self):
1463
- """Update Input tab according to the current mode"""
1464
- current_mode = self.getMode()
1465
- valid_inputs = axis_mode.AXIS_MODE_METADATAS[current_mode].valid_inputs
1466
- if valid_inputs is None:
1467
- self._inputWidget.setEnabled(False)
1468
- else:
1469
- self._inputWidget.setEnabled(True)
1470
- self._inputWidget.setValidInputs(valid_inputs)
1471
-
1472
-
1473
- class _CalculationWidget(qt.QWidget):
1474
- """Main widget to select the algorithm to use for COR calculation"""
1475
-
1476
- sigModeChanged = qt.Signal(str)
1477
- """signal emitted when the algorithm for computing COR changed"""
1478
-
1479
- sigLockModeChanged = qt.Signal(bool)
1480
- """signal emitted when the mode has been lock or unlock"""
1481
-
1482
- def __init__(self, parent, axis_params):
1483
- assert isinstance(axis_params, QAxisRP)
1484
- qt.QWidget.__init__(self, parent)
1485
- self._axis_params = None
1486
- self.setLayout(qt.QVBoxLayout())
1487
-
1488
- self._modeWidget = qt.QWidget(parent=self)
1489
- self._modeWidget.setLayout(qt.QHBoxLayout())
1490
- self.layout().addWidget(self._modeWidget)
1491
-
1492
- self.__rotAxisSelLabel = qt.QLabel("algorithm to compute cor")
1493
- self._modeWidget.layout().addWidget(self.__rotAxisSelLabel)
1494
- self._qcbPosition = qt.QComboBox(self)
1495
-
1496
- algorithm_groups = (
1497
- # radio group
1498
- (
1499
- axis_mode.AxisMode.centered,
1500
- axis_mode.AxisMode.global_,
1501
- axis_mode.AxisMode.growing_window_radios,
1502
- axis_mode.AxisMode.sliding_window_radios,
1503
- axis_mode.AxisMode.octave_accurate_radios,
1504
- ),
1505
- # sino group
1506
- (
1507
- axis_mode.AxisMode.growing_window_sinogram,
1508
- axis_mode.AxisMode.sino_coarse_to_fine,
1509
- axis_mode.AxisMode.sliding_window_sinogram,
1510
- axis_mode.AxisMode.sino_fourier_angles,
1511
- ),
1512
- # composite corase to fine
1513
- (
1514
- axis_mode.AxisMode.composite_coarse_to_fine,
1515
- axis_mode.AxisMode.near,
1516
- ),
1517
- # manual
1518
- (axis_mode.AxisMode.manual,),
1519
- # read
1520
- (axis_mode.AxisMode.read,),
1521
- )
1522
- current_pos = 0
1523
- for i_grp, algorithm_group in enumerate(algorithm_groups):
1524
- if i_grp != 0:
1525
- self._qcbPosition.insertSeparator(current_pos)
1526
- current_pos += 1
1527
- for cor_algorithm in algorithm_group:
1528
- self._qcbPosition.addItem(cor_algorithm.value)
1529
- idx = self._qcbPosition.findText(cor_algorithm.value)
1530
- self._qcbPosition.setItemData(
1531
- idx,
1532
- axis_mode.AXIS_MODE_METADATAS[cor_algorithm].tooltip,
1533
- qt.Qt.ToolTipRole,
1534
- )
1535
- current_pos += 1
1536
-
1537
- self._modeWidget.layout().addWidget(self._qcbPosition)
1538
-
1539
- # method lock button
1540
- self._lockMethodPB = PadlockButton(parent=self._modeWidget)
1541
- self._lockMethodPB.setToolTip(
1542
- "Lock the method to compute the cor. \n"
1543
- "This will automatically call the "
1544
- "defined algorithm each time a scan is received."
1545
- )
1546
- self._modeWidget.layout().addWidget(self._lockMethodPB)
1547
-
1548
- self._optsWidget = qt.QWidget(self)
1549
- self._optsWidget.setLayout(qt.QVBoxLayout())
1550
- self.layout().addWidget(self._optsWidget)
1551
-
1552
- # padding option
1553
- self._padding_widget = qt.QGroupBox("padding mode")
1554
- self._padding_widget.setCheckable(True)
1555
- self._optsWidget.layout().addWidget(self._padding_widget)
1556
- self._padding_widget.setLayout(qt.QHBoxLayout())
1557
-
1558
- self._qbPaddingMode = qt.QComboBox(self._padding_widget)
1559
- for _mode in (
1560
- "constant",
1561
- "edge",
1562
- "linear_ramp",
1563
- "maximum",
1564
- "mean",
1565
- "median",
1566
- "minimum",
1567
- "reflect",
1568
- "symmetric",
1569
- "wrap",
1570
- ):
1571
- self._qbPaddingMode.addItem(_mode)
1572
- def_index = self._qbPaddingMode.findText("edge")
1573
- self._qbPaddingMode.setCurrentIndex(def_index)
1574
- self._padding_widget.layout().addWidget(self._qbPaddingMode)
1575
-
1576
- # side option
1577
- self._sideWidget = qt.QWidget(self)
1578
- self._sideWidget.setLayout(qt.QHBoxLayout())
1579
- self._optsWidget.layout().addWidget(self._sideWidget)
1580
- self._sideWidget.layout().addWidget(qt.QLabel("side:", self))
1581
- self._sideCB = qt.QComboBox(self._optsWidget)
1582
- self._sideWidget.layout().addWidget(self._sideCB)
1583
- self._sideCB.setToolTip(
1584
- "Define a side for the sliding and growing" "window algorithms"
1585
- )
1586
-
1587
- # near mode options
1588
- self._nearOptsWidget = qt.QWidget(parent=self)
1589
- self._nearOptsWidget.setLayout(qt.QVBoxLayout())
1590
- self._optsWidget.layout().addWidget(self._nearOptsWidget)
1591
-
1592
- # near value lock button
1593
- self._nearValueCB = qt.QCheckBox(
1594
- "Update automatically if `estimated_cor_from_motor` found"
1595
- )
1596
- self._nearValueCB.setToolTip(
1597
- "If the acquisition contains an "
1598
- "estimation of the COR value then "
1599
- "will set it automatically as estimated "
1600
- "value."
1601
- )
1602
- self._nearOptsWidget.layout().addWidget(self._nearValueCB)
1603
-
1604
- # LineEdit position value
1605
- self._qleValueW = qt.QWidget(self._nearOptsWidget)
1606
- self._qleValueW.setLayout(qt.QFormLayout())
1607
- self._nearOptsWidget.layout().addWidget(self._qleValueW)
1608
-
1609
- self._qleNearPosQLE = qt.QLineEdit("0", self._nearOptsWidget)
1610
- validator = qt.QDoubleValidator(self._qleNearPosQLE)
1611
- self._qleNearPosQLE.setValidator(validator)
1612
- self._qleValueW.layout().addRow(
1613
- "estimated value (in relative):", self._qleNearPosQLE
1614
- )
1615
-
1616
- # cor_options
1617
- self._corOptsWidget = qt.QWidget(self)
1618
- self._corOptsWidget.setLayout(qt.QHBoxLayout())
1619
- self._corOpts = qt.QLineEdit(self)
1620
- self._corOpts.setToolTip(
1621
- "Options for methods finding automatically the rotation axis position. 'side', 'near_pos' and 'near_width' are already provided by dedicated interface. The parameters are separated by commas and passed as 'name=value'. Mind the semicolon separator (;)."
1622
- )
1623
- self._corOpts.setPlaceholderText("low_pass=1; high_pass=20")
1624
- self._corOptsWidget.layout().addWidget(qt.QLabel("cor advanced options"))
1625
- self._corOptsWidget.layout().addWidget(self._corOpts)
1626
- self._optsWidget.layout().addWidget(self._corOptsWidget)
1627
-
1628
- # connect signal / slot
1629
- self._qcbPosition.currentIndexChanged.connect(self._modeChanged)
1630
- self._qleNearPosQLE.editingFinished.connect(self._nearValueChanged)
1631
- self._sideCB.currentTextChanged.connect(self._sideChanged)
1632
- self._lockMethodPB.sigLockChanged.connect(self.lockMode)
1633
- self._qbPaddingMode.currentIndexChanged.connect(self._paddingModeChanged)
1634
- self._padding_widget.toggled.connect(self._paddingModeChanged)
1635
- self._corOpts.editingFinished.connect(self._corOptsChanged)
1636
-
1637
- # set up interface
1638
- self._sideWidget.setVisible(False)
1639
- self.setAxisParams(axis_params)
1640
- self._nearValueCB.setChecked(True)
1641
- self._nearOptsWidget.setHidden(True)
1642
-
1643
- def getMethodLockPB(self) -> qt.QPushButton:
1644
- return self._lockMethodPB
1645
-
1646
- def setEstimatedCorValue(self, value):
1647
- if value is not None:
1648
- self._qleNearPosQLE.setText(str(value))
1649
- # note: keep self._axis_params up to date.
1650
- if self._axis_params:
1651
- self._axis_params.estimated_cor = value
1652
-
1653
- def getEstimatedCor(self):
1654
- try:
1655
- return float(self._qleNearPosQLE.text())
1656
- except ValueError:
1657
- return 0
1658
-
1659
- def updateAutomaticallyEstimatedCor(self):
1660
- return self._nearValueCB.isChecked()
1661
-
1662
- def setUpdateAutomaticallyEstimatedCor(self, checked):
1663
- self._nearValueCB.setChecked(checked)
1664
-
1665
- def setSide(self, side):
1666
- if side is not None:
1667
- idx = self._sideCB.findText(side)
1668
- if idx >= 0:
1669
- self._sideCB.setCurrentIndex(idx)
1670
-
1671
- def getSide(self):
1672
- return self._sideCB.currentText()
1673
-
1674
- def _modeChanged(self, *args, **kwargs):
1675
- mode = self.getMode()
1676
- with block_signals(self._qcbPosition):
1677
- with block_signals(self._axis_params):
1678
- self._corOptsWidget.setVisible(
1679
- mode
1680
- not in (
1681
- axis_mode.AxisMode.manual,
1682
- axis_mode.AxisMode.read,
1683
- )
1684
- )
1685
-
1686
- self._padding_widget.setVisible(
1687
- axis_mode.AXIS_MODE_METADATAS[mode].allows_padding
1688
- )
1689
- if axis_mode.AXIS_MODE_METADATAS[mode].is_lockable:
1690
- self._lockMethodPB.setVisible(True)
1691
- else:
1692
- self._lockMethodPB.setVisible(False)
1693
- self.lockMode(False)
1694
-
1695
- sides_visible = len(axis_mode.AXIS_MODE_METADATAS[mode].valid_sides) > 0
1696
- self._sideWidget.setVisible(sides_visible)
1697
- if sides_visible is True:
1698
- self._updateSideVisible(mode)
1699
- self._nearOptsWidget.setVisible(self.getSide() == "near")
1700
- self._axis_params.mode = mode.value
1701
- self._axis_params.changed()
1702
- self.sigModeChanged.emit(mode.value)
1703
-
1704
- def _updateSideVisible(self, mode: axis_mode.AxisMode):
1705
- mode = axis_mode.AxisMode.from_value(mode)
1706
- if len(axis_mode.AXIS_MODE_METADATAS[mode].valid_sides) == 0:
1707
- return
1708
- else:
1709
- current_value = self._axis_params.side
1710
- with block_signals(self._sideCB):
1711
- self._sideCB.clear()
1712
- values = axis_mode.AXIS_MODE_METADATAS[mode].valid_sides
1713
- for value in values:
1714
- self._sideCB.addItem(value)
1715
- idx = self._sideCB.findText(current_value)
1716
- if idx == -1:
1717
- # if side doesn't exists, propose right as default when possible
1718
- idx = self._sideCB.findText("right")
1719
- if idx >= 0:
1720
- self._sideCB.setCurrentIndex(idx)
1721
- self._axis_params.side = self.getSide()
1722
-
1723
- def isModeLock(self):
1724
- return self._lockMethodPB.isLocked()
1725
-
1726
- def setModeLock(self, mode=None):
1727
- """set a specific mode and lock it.
1728
-
1729
- :param mode: mode to lock. If None then keep the current mode
1730
- """
1731
- if mode is not None:
1732
- mode = axis_mode.AxisMode.from_value(mode)
1733
- if mode is None and axis_mode.AXIS_MODE_METADATAS[self.getMode()].is_lockable():
1734
- raise ValueError(
1735
- "Unable to lock the current mode is not an automatic algorithm"
1736
- )
1737
- elif (
1738
- mode != self.getMode() and axis_mode.AXIS_MODE_METADATAS[mode].is_lockable()
1739
- ):
1740
- raise ValueError("Unable to lock %s this is not a lockable mode")
1741
-
1742
- if mode is not None:
1743
- self.setMode(mode)
1744
- if not self._lockMethodPB.isLocked():
1745
- with block_signals(self._lockMethodPB):
1746
- self._lockMethodPB.setLock(True)
1747
- self.lockMode(True)
1748
-
1749
- def lockMode(self, lock):
1750
- with block_signals(self._lockMethodPB):
1751
- self._lockMethodPB.setLock(lock)
1752
- self._qcbPosition.setEnabled(not lock)
1753
-
1754
- self.sigLockModeChanged.emit(lock)
1755
-
1756
- def getMode(self):
1757
- """Return algorithm to use for axis calculation"""
1758
- return axis_mode.AxisMode.from_value(self._qcbPosition.currentText())
1759
-
1760
- def setMode(self, mode):
1761
- with block_signals(self._qcbPosition):
1762
- index = self._qcbPosition.findText(mode.value)
1763
- if index >= 0:
1764
- self._qcbPosition.setCurrentIndex(index)
1765
- else:
1766
- raise ValueError("Unagle to find mode", mode)
1767
- self._lockMethodPB.setVisible(
1768
- mode not in (axis_mode.AxisMode.manual, axis_mode.AxisMode.read)
1769
- )
1770
-
1771
- def _nearValueChanged(self, *args, **kwargs):
1772
- self._axis_params.estimated_cor = self.getEstimatedCor()
1773
-
1774
- @deprecated(replacement="getEstimatedCor", since_version="0.6")
1775
- def getNearPosition(self):
1776
- return self.getEstimatedCor()
1777
-
1778
- @deprecated(replacement="setEstimatedCorValue", since_version="0.6")
1779
- def setNearPosition(self, position):
1780
- self.setEstimatedCorValue(position)
1781
-
1782
- def _paddingModeChanged(self, *args, **kwargs):
1783
- self._axis_params.padding_mode = self.getPaddingMode()
1784
-
1785
- def _corOptsChanged(self, *args, **kwargs):
1786
- self._axis_params.extra_cor_options = self.getCorOptions()
1787
-
1788
- def _sideChanged(self, *args, **kwargs):
1789
- side = self.getSide()
1790
- if side not in ("", None):
1791
- self._axis_params.side = side
1792
- self._nearOptsWidget.setVisible(side == "near")
1793
-
1794
- def getCorOptions(self):
1795
- return self._corOpts.text()
1796
-
1797
- def setCorOptions(self, opts: Optional[str]):
1798
- with block_signals(self._axis_params):
1799
- self._corOpts.clear()
1800
- if opts:
1801
- self._corOpts.setText(opts)
1802
- self._corOptsChanged()
1803
-
1804
- def getPaddingMode(self):
1805
- if self._padding_widget.isChecked():
1806
- return self._qbPaddingMode.currentText()
1807
- else:
1808
- return None
1809
-
1810
- def setPaddingMode(self, mode):
1811
- index = self._qbPaddingMode.findText(mode)
1812
- if index >= 0:
1813
- self._qbPaddingMode.setCurrentIndex(index)
1814
- self._paddingModeChanged()
1815
-
1816
- def setAxisParams(self, axis):
1817
- with block_signals(self):
1818
- if self._axis_params is not None:
1819
- self._axis_params.sigChanged.disconnect(self._axis_params_changed)
1820
- self._axis_params = axis
1821
- if self._axis_params.mode in (
1822
- axis_mode.AxisMode.manual,
1823
- axis_mode.AxisMode.read,
1824
- ):
1825
- # those mode cannot be handled by the auto calculation dialog
1826
- self._axis_params.mode = axis_mode.AxisMode.growing_window_radios
1827
- self._axis_params.sigChanged.connect(self._axis_params_changed)
1828
- self._axis_params_changed()
1829
- self._sideChanged()
1830
-
1831
- def _axis_params_changed(self, *args, **kwargs):
1832
- self.setMode(self._axis_params.mode)
1833
- self.setEstimatedCorValue(self._axis_params.estimated_cor)
1834
- self.setSide(self._axis_params.side)
1835
- sides_visible = (
1836
- len(axis_mode.AXIS_MODE_METADATAS[self._axis_params.mode].valid_sides) > 0
1837
- )
1838
- self._sideWidget.setVisible(sides_visible)
1839
- self._updateSideVisible(mode=self._axis_params.mode)
1840
- self.setPaddingMode(self._axis_params.padding_mode)
1841
- self.setCorOptions(self._axis_params.extra_cor_options)
1842
-
1843
-
1844
- class _SliceSelector(qt.QWidget):
1845
- sigChanged = qt.Signal()
1846
- """signal emit when the selected slice change"""
1847
-
1848
- def __init__(self, parent):
1849
- qt.QWidget.__init__(self, parent)
1850
- self.setLayout(qt.QHBoxLayout())
1851
- self.setContentsMargins(0, 0, 0, 0)
1852
- self.layout().setContentsMargins(0, 0, 0, 0)
1853
- self._modeCB = qt.QComboBox(self)
1854
- self._modeCB.addItem("middle")
1855
- self._modeCB.addItem("other")
1856
- self.layout().addWidget(self._modeCB)
1857
- self._otherSB = qt.QSpinBox(self)
1858
- self._otherSB.setRange(0, 10000)
1859
- self.layout().addWidget(self._otherSB)
1860
-
1861
- # connect signal / slot
1862
- self._otherSB.valueChanged.connect(self._valueChanged)
1863
- self._modeCB.currentIndexChanged.connect(self._modeChanged)
1864
- # set up
1865
- self._modeChanged()
1866
-
1867
- def getSlice(self) -> Union[int, str]:
1868
- "return a specific slice index or 'middle'"
1869
- if self.getMode() == "middle":
1870
- return "middle"
1871
- else:
1872
- return self._otherSB.value()
1873
-
1874
- def setSlice(self, slice_):
1875
- if slice_ is None:
1876
- return
1877
- if slice_ == "middle":
1878
- idx = self._modeCB.findText("middle")
1879
- self._modeCB.setCurrentIndex(idx)
1880
- else:
1881
- idx = self._modeCB.findText("other")
1882
- self._modeCB.setCurrentIndex(idx)
1883
- self._otherSB.setValue(slice_)
1884
- self.sigChanged.emit()
1885
-
1886
- def getMode(self):
1887
- return self._modeCB.currentText()
1888
-
1889
- def _valueChanged(self):
1890
- self.sigChanged.emit()
1891
-
1892
- def _modeChanged(self, *args, **kwargs):
1893
- self._otherSB.setVisible(self.getMode() == "other")
1894
- self._valueChanged()
1895
-
1896
-
1897
- class _InputWidget(qt.QWidget):
1898
- """Widget used to define the radio to use for axis calculation from
1899
- radios"""
1900
-
1901
- sigChanged = qt.Signal()
1902
- """Signal emitted when input changed"""
1903
-
1904
- _sigUrlChanged = qt.Signal()
1905
- """Signal emit when url to be used changed"""
1906
-
1907
- def __init__(self, parent=None, axis_params=None):
1908
- assert isinstance(axis_params, QAxisRP)
1909
- self._blockUpdateAxisParams = False
1910
- super().__init__(parent)
1911
- self.setLayout(qt.QVBoxLayout())
1912
-
1913
- # radio input
1914
- self._radioGB = qt.QGroupBox(self)
1915
- self._radioGB.setTitle("radios")
1916
- self._radioGB.setLayout(qt.QVBoxLayout())
1917
- self._radioGB.setCheckable(True)
1918
- self.layout().addWidget(self._radioGB)
1919
- ## angle mode
1920
- self._angleModeWidget = _AngleSelectionWidget(
1921
- parent=self, axis_params=axis_params
1922
- )
1923
- self._radioGB.layout().addWidget(self._angleModeWidget)
1924
- self._axis_params = axis_params
1925
-
1926
- # sinogram input
1927
- self._sinogramGB = qt.QGroupBox(self)
1928
- self._sinogramGB.setLayout(qt.QVBoxLayout())
1929
- self._standardSinogramOpts = qt.QGroupBox(self)
1930
- self._sinogramGB.layout().addWidget(self._standardSinogramOpts)
1931
- self._standardSinogramOpts.setLayout(qt.QFormLayout())
1932
- self._standardSinogramOpts.layout().setContentsMargins(0, 0, 0, 0)
1933
- self._standardSinogramOpts.setTitle("standard options")
1934
-
1935
- self._sinogramGB.setTitle("sinogram")
1936
- self._sinogramGB.setCheckable(True)
1937
- self.layout().addWidget(self._sinogramGB)
1938
- ## sinogram line
1939
- self._sinogramLineSB = _SliceSelector(self)
1940
- self._standardSinogramOpts.layout().addRow("line", self._sinogramLineSB)
1941
- ## sinogram subsampling
1942
- self._sinogramSubsampling = qt.QSpinBox(self)
1943
- self._sinogramSubsampling.setRange(1, 1000)
1944
- self._sinogramSubsampling.setValue(10)
1945
- self._standardSinogramOpts.layout().addRow(
1946
- "subsampling", self._sinogramSubsampling
1947
- )
1948
-
1949
- self._spacer = qt.QWidget(self)
1950
- self._spacer.setSizePolicy(qt.QSizePolicy.Minimum, qt.QSizePolicy.Expanding)
1951
- self.layout().addWidget(self._spacer)
1952
-
1953
- ## options for the composite mode
1954
- self._compositeOpts = qt.QGroupBox(self)
1955
- self._compositeOpts.setTitle("composite options")
1956
- self._sinogramGB.layout().addWidget(self._compositeOpts)
1957
- self._compositeOpts.setLayout(qt.QFormLayout())
1958
- self._compositeOpts.layout().setContentsMargins(0, 0, 0, 0)
1959
- self._thetaSB = qt.QSpinBox(self)
1960
- self._thetaSB.setRange(0, 360)
1961
- self._thetaSB.setValue(DEFAULT_CMP_THETA)
1962
- self._thetaSB.setToolTip("a radio will be picked each theta degres")
1963
- self._thetaLabel = qt.QLabel("angle interval (in degree)", self)
1964
- self._thetaLabel.setToolTip(
1965
- "algorithm will take one projection each 'angle interval'. Also know as 'theta'"
1966
- )
1967
- self._compositeOpts.layout().addRow(self._thetaLabel, self._thetaSB)
1968
-
1969
- self._oversamplingSB = qt.QSpinBox(self)
1970
- self._oversamplingSB.setValue(DEFAULT_CMP_OVERSAMPLING)
1971
- self._oversamplingSB.setToolTip("sinogram oversampling")
1972
- self._compositeOpts.layout().addRow("oversampling", self._oversamplingSB)
1973
-
1974
- self._nearwidthSB = qt.QSpinBox(self)
1975
- self._nearwidthSB.setRange(-40000, 40000)
1976
- self._nearwidthSB.setValue(0)
1977
- self._nearwidthSB.setToolTip("position to be used with near option")
1978
- self._nearwidthLabel = qt.QLabel("near width", self)
1979
- self._nearwidthLabel.setToolTip("position to be used with near option")
1980
- self._compositeOpts.layout().addRow(self._nearwidthLabel, self._nearwidthSB)
1981
-
1982
- self._subsamplingYSB = qt.QSpinBox(self)
1983
- self._subsamplingYSB.setValue(DEFAULT_CMP_N_SUBSAMPLING_Y)
1984
- self._subsamplingYSB.setToolTip("sinogram number of subsampling along y")
1985
- self._compositeOpts.layout().addRow("n_subsampling_y", self._subsamplingYSB)
1986
-
1987
- self._takeLogCB = qt.QCheckBox(self)
1988
- self._takeLogCB.setToolTip("Take logarithm")
1989
- self._takeLogCB.setChecked(DEFAULT_CMP_TAKE_LOG)
1990
- self._takeTheLogLabel = qt.QLabel("linearisation (-log(I/I0))")
1991
- self._takeTheLogLabel.setToolTip(
1992
- "take (-log(I/I0)) as input. Also know as 'take_log' option"
1993
- )
1994
- self._compositeOpts.layout().addRow(self._takeTheLogLabel, self._takeLogCB)
1995
-
1996
- # set up
1997
- self._sinogramGB.setChecked(False)
1998
-
1999
- # connect signal / slot
2000
- self._sinogramGB.toggled.connect(self._sinogramChecked)
2001
- self._radioGB.toggled.connect(self._radiosChecked)
2002
- self._sinogramSubsampling.valueChanged.connect(self._changed)
2003
- self._sinogramLineSB.sigChanged.connect(self._changed)
2004
- self._thetaSB.valueChanged.connect(self._changed)
2005
- self._oversamplingSB.valueChanged.connect(self._changed)
2006
- self._subsamplingYSB.valueChanged.connect(self._changed)
2007
- self._nearwidthSB.valueChanged.connect(self._changed)
2008
- self._takeLogCB.toggled.connect(self._changed)
2009
- self._angleModeWidget.sigChanged.connect(self._sigUrlChanged)
2010
-
2011
- def setScan(self, scan: TomwerScanBase):
2012
- if scan is not None:
2013
- self._angleModeWidget.setScan(scan)
2014
- self._angleModeWidget.setScanRange(scan.scan_range)
2015
-
2016
- def setAxisParams(self, axis_params):
2017
- with block_signals(axis_params):
2018
- with block_signals(self._axis_params):
2019
- self._blockUpdateAxisParams = True
2020
-
2021
- if axis_params is not None:
2022
- assert isinstance(axis_params, QAxisRP)
2023
- with block_signals(self._sinogramGB):
2024
- self._sinogramChecked(axis_params.use_sinogram, on_load=True)
2025
- self._sinogramLineSB.setSlice(axis_params.sinogram_line)
2026
- self._sinogramSubsampling.setValue(axis_params.sinogram_subsampling)
2027
- self.setCompositeOptions(axis_params.composite_options)
2028
- self._angleModeWidget.setAxisParams(axis_params)
2029
- self._axis_params = axis_params
2030
-
2031
- self._blockUpdateAxisParams = False
2032
-
2033
- def getSinogramLine(self) -> Union[str, int]:
2034
- return self._sinogramLineSB.getSlice()
2035
-
2036
- def getSinogramSubsampling(self) -> int:
2037
- return self._sinogramSubsampling.value()
2038
-
2039
- def _sinogramChecked(self, checked, on_load=False):
2040
- with block_signals(self._radioGB):
2041
- with block_signals(self._sinogramGB):
2042
- if checked:
2043
- self._radioGB.setChecked(False)
2044
- self._sinogramGB.setChecked(True)
2045
- elif self._radioGB.isEnabled():
2046
- self._radioGB.setChecked(not checked)
2047
- elif on_load:
2048
- self._sinogramGB.setChecked(checked)
2049
- else:
2050
- # ignore it if radio disabled
2051
- self._sinogramGB.setChecked(True)
2052
- self._changed()
2053
-
2054
- def _radiosChecked(self, checked, on_load=False):
2055
- with block_signals(self._radioGB):
2056
- with block_signals(self._sinogramGB):
2057
- if checked:
2058
- self._sinogramGB.setChecked(False)
2059
- self._radioGB.setChecked(True)
2060
- elif self._sinogramGB.isEnabled():
2061
- self._sinogramGB.setChecked(not checked)
2062
- elif on_load:
2063
- self._radioGB.setChecked(checked)
2064
- else:
2065
- # ignore it if sinogram disabled
2066
- self._radioGB.setChecked(True)
2067
- self._changed()
2068
-
2069
- def _changed(self, *args, **kwargs):
2070
- self._updateAxisParams()
2071
- self.sigChanged.emit()
2072
-
2073
- def _updateAxisParams(self):
2074
- if not self._blockUpdateAxisParams:
2075
- self._axis_params.use_sinogram = self._sinogramGB.isChecked()
2076
- self._axis_params.sinogram_line = self.getSinogramLine()
2077
- self._axis_params.sinogram_subsampling = self.getSinogramSubsampling()
2078
- self._axis_params.composite_options = self.getCompositeOptions()
2079
-
2080
- def setValidInputs(self, modes: Union[list, tuple]):
2081
- """
2082
- Define possible inputs.
2083
-
2084
- :param Union[list,tuple] modes:
2085
- :raises: ValueError if modes are invalid
2086
- """
2087
- modes = set(modes)
2088
- for mode in modes:
2089
- try:
2090
- axis_mode._InputType.from_value(mode)
2091
- except Exception:
2092
- raise ValueError(
2093
- f"mode {mode} should be an instance of {axis_mode._InputType}"
2094
- )
2095
- if len(modes) == 2:
2096
- self._sinogramGB.setEnabled(True)
2097
- self._radioGB.setEnabled(True)
2098
- elif len(modes) > 2:
2099
- raise ValueError(f"invalid input {modes}")
2100
- elif len(modes) < 0:
2101
- raise ValueError("modes is empty")
2102
- else:
2103
- mode = axis_mode._InputType.from_value(modes.pop())
2104
- if mode in (axis_mode._InputType.SINOGRAM, axis_mode._InputType.COMPOSITE):
2105
- self._sinogramGB.setEnabled(True)
2106
- self._radioGB.setEnabled(False)
2107
- self._sinogramGB.setChecked(True)
2108
- self._compositeOpts.setEnabled(mode is axis_mode._InputType.COMPOSITE)
2109
- self._standardSinogramOpts.setEnabled(
2110
- mode is not axis_mode._InputType.COMPOSITE
2111
- )
2112
- elif mode is axis_mode._InputType.RADIOS_X2:
2113
- self._radioGB.setEnabled(True)
2114
- self._sinogramGB.setEnabled(False)
2115
- self._radioGB.setChecked(True)
2116
- else:
2117
- raise ValueError(f"Nothing implemented for {mode.value}")
2118
-
2119
- def getCompositeOptions(self) -> dict:
2120
- return {
2121
- "theta": self.getTheta(),
2122
- "oversampling": self.getOversampling(),
2123
- "n_subsampling_y": self.getSubsamplingY(),
2124
- "take_log": self.getTakeLog(),
2125
- "near_pos": self.getNearpos(),
2126
- "near_width": self.getNearwidth(),
2127
- }
2128
-
2129
- def setCompositeOptions(self, opts: dict) -> None:
2130
- if not isinstance(opts, dict):
2131
- raise TypeError("opts should be an instance of dict")
2132
- for key in opts.keys():
2133
- if key not in (
2134
- "theta",
2135
- "oversampling",
2136
- "n_subsampling_y",
2137
- "take_log",
2138
- "near_pos",
2139
- "near_width",
2140
- ):
2141
- raise KeyError(f"{key} is not recogized")
2142
- theta = opts.get("theta", None)
2143
- if theta is not None:
2144
- self.setTheta(theta=theta)
2145
- oversampling = opts.get("oversampling", None)
2146
- if oversampling is not None:
2147
- self.setOversampling(oversampling)
2148
- n_subsampling_y = opts.get("n_subsampling_y", None)
2149
- if n_subsampling_y is not None:
2150
- self.setSubsamplingY(n_subsampling_y)
2151
-
2152
- near_width = opts.get("near_width", None)
2153
- if near_width is not None:
2154
- self.setNearwidth(near_width)
2155
-
2156
- take_log = opts.get("take_log", None)
2157
- if take_log is not None:
2158
- self.setTakeLog(take_log)
2159
-
2160
- def getTheta(self) -> int:
2161
- return self._thetaSB.value()
2162
-
2163
- def setTheta(self, theta: int) -> None:
2164
- self._thetaSB.setValue(theta)
2165
-
2166
- def getOversampling(self) -> int:
2167
- return self._oversamplingSB.value()
2168
-
2169
- def setOversampling(self, oversampling: int) -> None:
2170
- self._oversamplingSB.setValue(oversampling)
2171
-
2172
- def getNearpos(self) -> int:
2173
- cal_widget = self.parentWidget().widget(0)
2174
- assert isinstance(cal_widget, _CalculationWidget)
2175
- return cal_widget.getEstimatedCor()
2176
-
2177
- def setNearpos(self, value) -> int:
2178
- cal_widget = self.parentWidget().widget(0)
2179
- assert isinstance(cal_widget, _CalculationWidget)
2180
- cal_widget.setNearPosition(value)
2181
-
2182
- def getNearwidth(self) -> int:
2183
- return self._nearwidthSB.value()
2184
-
2185
- def setNearwidth(self, value) -> int:
2186
- return self._nearwidthSB.setValue(value)
2187
-
2188
- def getSubsamplingY(self) -> int:
2189
- return self._subsamplingYSB.value()
2190
-
2191
- def setSubsamplingY(self, subsampling: int) -> None:
2192
- self._subsamplingYSB.setValue(subsampling)
2193
-
2194
- def getTakeLog(self) -> bool:
2195
- return self._takeLogCB.isChecked()
2196
-
2197
- def setTakeLog(self, log: bool) -> None:
2198
- self._takeLogCB.setChecked(log)
2199
-
2200
-
2201
- class _AngleSelectionWidget(qt.QWidget):
2202
- """Group box to select the angle to used for cor calculation
2203
- (0-180, 90-270 or manual)"""
2204
-
2205
- sigChanged = qt.Signal()
2206
- """signal emitted when the selected angle changed"""
2207
-
2208
- def __init__(self, parent=None, axis_params=None):
2209
- assert isinstance(axis_params, QAxisRP)
2210
- qt.QWidget.__init__(
2211
- self,
2212
- parent=parent,
2213
- )
2214
- self.setLayout(qt.QVBoxLayout())
2215
- self._groupBoxMode = qt.QGroupBox(
2216
- self, title="Angles to use for axis calculation"
2217
- )
2218
- self._groupBoxMode.setLayout(qt.QHBoxLayout())
2219
- self.layout().addWidget(self._groupBoxMode)
2220
-
2221
- self._corButtonsGps = qt.QButtonGroup(parent=self)
2222
- self._corButtonsGps.setExclusive(True)
2223
- self._qrbCOR_0_180 = qt.QRadioButton("0-180", parent=self)
2224
- self._groupBoxMode.layout().addWidget(self._qrbCOR_0_180)
2225
- self._qrbCOR_90_270 = qt.QRadioButton("90-270", parent=self)
2226
- self._qrbCOR_90_270.setToolTip(
2227
- "pick radio closest to angles 90° and "
2228
- "270°. If disable mean that the scan "
2229
- "range is 180°"
2230
- )
2231
- self._groupBoxMode.layout().addWidget(self._qrbCOR_90_270)
2232
- self._qrbCOR_manual = qt.QRadioButton("other", parent=self)
2233
- self._qrbCOR_manual.setVisible(True)
2234
- self._groupBoxMode.layout().addWidget(self._qrbCOR_manual)
2235
- # add all button to the button group
2236
- for b in (self._qrbCOR_0_180, self._qrbCOR_90_270, self._qrbCOR_manual):
2237
- self._corButtonsGps.addButton(b)
2238
-
2239
- self.setAxisParams(axis_params)
2240
-
2241
- self._manualFrameSelection = _ManualFramesSelection(self)
2242
- self.layout().addWidget(self._manualFrameSelection)
2243
- self._manualFrameSelection.setVisible(False)
2244
-
2245
- # connect signal / Slot
2246
- self._corButtonsGps.buttonClicked.connect(self._angleModeChanged)
2247
- self._manualFrameSelection.sigChanged.connect(self._changed)
2248
-
2249
- def setScan(self, scan: TomwerScanBase):
2250
- if scan is not None:
2251
- self.setScanRange(scan.scan_range)
2252
- self._manualFrameSelection.setScan(scan=scan)
2253
-
2254
- def setScanRange(self, scanRange):
2255
- if scanRange == 180:
2256
- self._qrbCOR_90_270.setEnabled(False)
2257
- # force using 0-180 if was using 90-270
2258
- if self._qrbCOR_90_270.isChecked():
2259
- self._qrbCOR_0_180.setChecked(True)
2260
- self._axis_params.angle_mode = CorAngleMode.use_0_180
2261
- else:
2262
- self._qrbCOR_90_270.setEnabled(True)
2263
-
2264
- def setAngleMode(self, mode):
2265
- """
2266
-
2267
- :param mode: mode to use (can be manual , 90-270 or 0-180)
2268
- """
2269
- assert isinstance(mode, CorAngleMode)
2270
- if mode == CorAngleMode.use_0_180:
2271
- self._qrbCOR_0_180.setChecked(True)
2272
- elif mode == CorAngleMode.use_90_270:
2273
- self._qrbCOR_90_270.setChecked(True)
2274
- else:
2275
- self._qrbCOR_manual.setChecked(True)
2276
-
2277
- def getAngleMode(self):
2278
- """
2279
-
2280
- :return: the angle to use for the axis calculation
2281
- :rtype: CorAngleMode
2282
- """
2283
- if self._qrbCOR_90_270.isChecked():
2284
- return CorAngleMode.use_90_270
2285
- elif self._qrbCOR_0_180.isChecked():
2286
- return CorAngleMode.use_0_180
2287
- else:
2288
- return CorAngleMode.manual_selection
2289
-
2290
- def setAxisParams(self, axis_params):
2291
- with block_signals(self):
2292
- self._axis_params = axis_params
2293
- # set up
2294
- self.setAngleMode(axis_params.angle_mode)
2295
-
2296
- def _angleModeChanged(self, *args, **kwargs):
2297
- self._axis_params.angle_mode = self.getAngleMode()
2298
- if self.getAngleMode() is CorAngleMode.manual_selection:
2299
- self._axis_params.angle_mode_extra = (
2300
- self._manualFrameSelection.getFramesUrl()
2301
- )
2302
- else:
2303
- self._axis_params.angle_mode_extra = None
2304
- self._manualFrameSelection.setVisible(
2305
- self.getAngleMode() is CorAngleMode.manual_selection
2306
- )
2307
- self._changed()
2308
-
2309
- def _changed(self):
2310
- self.sigChanged.emit()
2311
-
2312
-
2313
- class _ManualFramesSelection(qt.QWidget):
2314
- """Allows to select frame - angle to be used."""
2315
-
2316
- sigChanged = qt.Signal()
2317
- """Signal emit when the frame selection changes"""
2318
-
2319
- def __init__(self, parent=None) -> None:
2320
- super().__init__(parent)
2321
- self._anglesAvailable = []
2322
- # cache of the angles availables from the current defined frames. Must be sorted !!!
2323
- self.setLayout(qt.QGridLayout())
2324
- self.layout().addWidget(qt.QLabel("frame 1", self), 0, 0, 1, 1)
2325
- self._frame1CB = qt.QComboBox(self)
2326
- self._frame1CB.setEditable(True)
2327
- self.layout().addWidget(self._frame1CB, 0, 1, 1, 1)
2328
-
2329
- self.layout().addWidget(qt.QLabel("frame 2", self), 1, 0, 1, 1)
2330
- self._frame2CB = qt.QComboBox(self)
2331
- self._frame2CB.setEditable(True)
2332
- self.layout().addWidget(self._frame2CB, 1, 1, 1, 1)
2333
- self._findAssociatedAnglePB = qt.QPushButton("+180°", self)
2334
- button_180_font = self.font()
2335
- button_180_font.setPixelSize(10)
2336
- self._findAssociatedAnglePB.setFont(button_180_font)
2337
- self._findAssociatedAnglePB.setFixedWidth(30)
2338
- self._findAssociatedAnglePB.setSizePolicy(
2339
- qt.QSizePolicy.Minimum, qt.QSizePolicy.Minimum
2340
- )
2341
- self.layout().addWidget(self._findAssociatedAnglePB, 1, 2, 1, 1)
2342
- self._flipLRCB = qt.QCheckBox("flip L-R")
2343
- self._flipLRCB.setChecked(True)
2344
- self.layout().addWidget(self._flipLRCB, 1, 3, 1, 1)
2345
-
2346
- self._flipLRCB.toggled.connect(self._changed)
2347
- self._frame1CB.currentIndexChanged.connect(self._changed)
2348
- self._frame2CB.currentIndexChanged.connect(self._changed)
2349
- self._findAssociatedAnglePB.released.connect(self._findAssociatedAngle)
2350
-
2351
- def _findAssociatedAngle(self):
2352
- if self._frame1CB.count() == 0 or len(self._anglesAvailable) == 0:
2353
- _logger.warning("no angles available, unable to get '+180°' frame")
2354
- else:
2355
- angle = float(self._frame1CB.currentText())
2356
- # look for the closest 'associated' angle.
2357
- # as the angles are not limited to [0-360] we need to check for any value.
2358
- # if the angle is on the first part of the acquisition we expect to find it near angle +180
2359
- # if it is on the second part (for 360 degree) we expect to find it on the first part (0-180)
2360
- closest_pls_180_angle = self._getClosestAssociatedAngle(
2361
- angle + 180.0, self._anglesAvailable
2362
- )
2363
- score_add = abs(closest_pls_180_angle - angle)
2364
- closest_minus_180_angle = self._getClosestAssociatedAngle(
2365
- angle - 180.0, self._anglesAvailable
2366
- )
2367
- score_sub = abs(closest_minus_180_angle - angle)
2368
- if score_add >= score_sub:
2369
- closest_180_angle = closest_pls_180_angle
2370
- else:
2371
- closest_180_angle = closest_minus_180_angle
2372
- item_idx = self._frame2CB.findText(self._angleToStr(closest_180_angle))
2373
- if item_idx < 0:
2374
- _logger.error(f"Unable to find item for angle {closest_180_angle}")
2375
- else:
2376
- self._frame2CB.setCurrentIndex(item_idx)
2377
-
2378
- @staticmethod
2379
- def _getClosestAssociatedAngle(angle: float, angles: tuple) -> float:
2380
- """
2381
- return the angle closest angle to 'angle' from 'angles'
2382
-
2383
- :warning: angles should be already sorted !!!
2384
- """
2385
- if angles is None or len(angles) == 0:
2386
- return None
2387
- if angle in angles:
2388
- return angle
2389
- pos = bisect_left(angles, angle)
2390
- if pos == 0:
2391
- return angles[0]
2392
- elif pos > len(angles) - 1:
2393
- return angles[-1]
2394
- else:
2395
- left_angle = angles[pos - 1]
2396
- right_angle = angles[pos]
2397
- if abs(right_angle - angle) > abs(left_angle - angle):
2398
- return left_angle
2399
- else:
2400
- return right_angle
2401
-
2402
- def _changed(self):
2403
- self.sigChanged.emit()
2404
-
2405
- @staticmethod
2406
- def _angleToStr(angle: float) -> str:
2407
- return f"{float(angle):0.3f}"
2408
-
2409
- def setScan(self, scan: Union[TomwerScanBase, None]) -> None:
2410
- self._anglesAvailable.clear()
2411
- self._frame1CB.clear()
2412
- self._frame2CB.clear()
2413
- if scan is None:
2414
- return
2415
- current_angle1 = self._getFrame1Angle()
2416
- current_angle2 = self._getFrame2Angle()
2417
- for proj_angle, proj_url in scan.get_proj_angle_url().items():
2418
- try:
2419
- angle = self._angleToStr(proj_angle)
2420
- except Exception:
2421
- angle = proj_angle
2422
- else:
2423
- self._anglesAvailable.append(float(proj_angle))
2424
- self._frame1CB.addItem(angle, proj_url)
2425
- self._frame2CB.addItem(angle, proj_url)
2426
-
2427
- self._anglesAvailable.sort()
2428
-
2429
- idx = self._frame1CB.findText(current_angle1)
2430
- if idx >= 0:
2431
- self._frame1CB.setCurrentIndex(idx)
2432
- if current_angle1 == current_angle2:
2433
- # if the two current angle are close then we consider it is better to look for angleX - angleX + 180 angles
2434
- # instead of finding back angles
2435
- self._findAssociatedAngle()
2436
- else:
2437
- idx = self._frame1CB.findText(current_angle1)
2438
- if idx >= 0:
2439
- self._frame1CB.setCurrentIndex(idx)
2440
-
2441
- idx = self._frame2CB.findText(current_angle2)
2442
- if idx >= 0:
2443
- self._frame2CB.setCurrentIndex(idx)
2444
-
2445
- def getFramesUrl(self, as_txt=False) -> tuple:
2446
- """
2447
- Return a tuple of (frame 1 url, frame 2 url). Url can be None
2448
- """
2449
- if as_txt:
2450
- return self.getFrame1Url().path(), self.getFrame2Url().path()
2451
- else:
2452
- return self.getFrame1Url(), self.getFrame2Url()
2453
-
2454
- def getFrame1Url(self) -> Optional[DataUrl]:
2455
- return self._frame1CB.currentData()
2456
-
2457
- def getFrame2Url(self) -> Optional[DataUrl]:
2458
- return self._frame2CB.currentData()
2459
-
2460
- def _getFrame1Angle(self) -> Optional[str]:
2461
- return self._frame1CB.currentText()
2462
-
2463
- def _getFrame2Angle(self) -> Optional[str]:
2464
- return self._frame2CB.currentText()
2465
-
2466
- def isFrame2LRFLip(self):
2467
- return self._flipLRCB.isChecked()