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.
- orangecontrib/tomwer/orange/managedprocess.py +1 -29
- orangecontrib/tomwer/orange/settings.py +2 -28
- orangecontrib/tomwer/tests/TestAcquisition.py +220 -0
- orangecontrib/tomwer/tutorials/append_raw_darks_and_flats_frames_to_NXtomos.ows +2 -2
- orangecontrib/tomwer/tutorials/copy_reduced_darks_and_flats_meth1.ows +1 -1
- orangecontrib/tomwer/tutorials/{icat_publication.ows → drac_publication.ows} +7 -21
- orangecontrib/tomwer/tutorials/id16b/ID16b_full_volume.ows +22 -0
- orangecontrib/tomwer/tutorials/id16b/ID16b_insitu.ows +302 -0
- orangecontrib/tomwer/tutorials/id16b/ID16b_normalization.ows +218 -0
- orangecontrib/tomwer/tutorials/simple_volume_to_slurm_reconstruction.ows +29 -24
- orangecontrib/tomwer/tutorials/test_cor.ows +19 -0
- orangecontrib/tomwer/tutorials/volume_casting_on_slurm.ows +54 -0
- orangecontrib/tomwer/widgets/__init__.py +3 -5
- orangecontrib/tomwer/widgets/cluster/FutureSupervisorOW.py +35 -54
- orangecontrib/tomwer/widgets/cluster/SlurmClusterOW.py +1 -31
- orangecontrib/tomwer/widgets/control/AdvancementOW.py +1 -29
- orangecontrib/tomwer/widgets/control/DataDiscoveryOW.py +5 -4
- orangecontrib/tomwer/widgets/control/DataListenerOW.py +1 -29
- orangecontrib/tomwer/widgets/control/DataSelectorOW.py +11 -30
- orangecontrib/tomwer/widgets/control/DataTransfertOW.py +2 -28
- orangecontrib/tomwer/widgets/control/DataValidatorOW.py +1 -28
- orangecontrib/tomwer/widgets/control/DataWatcherOW.py +1 -28
- orangecontrib/tomwer/widgets/control/EDF2NXTomomillOW.py +48 -51
- orangecontrib/tomwer/widgets/control/EmailOW.py +12 -2
- orangecontrib/tomwer/widgets/control/FilterOW.py +1 -28
- orangecontrib/tomwer/widgets/control/NXTomomillOW.py +37 -53
- orangecontrib/tomwer/widgets/control/NXtomoConcatenate.py +21 -20
- orangecontrib/tomwer/widgets/control/NotifierOW.py +9 -28
- orangecontrib/tomwer/widgets/control/ReduceDarkFlatSelectorOW.py +3 -2
- orangecontrib/tomwer/widgets/control/SingleTomoObjOW.py +1 -27
- orangecontrib/tomwer/widgets/control/TimerOW.py +9 -28
- orangecontrib/tomwer/widgets/control/TomoObjSeriesOW.py +58 -0
- orangecontrib/tomwer/widgets/control/VolumeSelector.py +8 -30
- orangecontrib/tomwer/widgets/control/icons/nxtomomill.svg +173 -119
- orangecontrib/tomwer/widgets/control/icons/reduced_darkflat_selector.png +0 -0
- orangecontrib/tomwer/widgets/control/icons/reduced_darkflat_selector.svg +55 -195
- orangecontrib/tomwer/widgets/control/icons/{tomoobjserie.svg → tomoobjseries.svg} +2 -2
- orangecontrib/tomwer/widgets/dataportal/PublishProcessedDataOW.py +172 -0
- orangecontrib/tomwer/widgets/{icat → dataportal}/__init__.py +2 -2
- orangecontrib/tomwer/widgets/debugtools/DatasetGeneratorOW.py +2 -29
- orangecontrib/tomwer/widgets/debugtools/ObjectInspectorOW.py +1 -29
- orangecontrib/tomwer/widgets/edit/DarkFlatPatchOW.py +48 -73
- orangecontrib/tomwer/widgets/edit/ImageKeyEditorOW.py +48 -75
- orangecontrib/tomwer/widgets/edit/ImageKeyUpgraderOW.py +5 -30
- orangecontrib/tomwer/widgets/edit/NXtomoEditorOW.py +51 -52
- orangecontrib/tomwer/widgets/other/PythonScriptOW.py +22 -10
- orangecontrib/tomwer/widgets/reconstruction/AxisOW.py +51 -60
- orangecontrib/tomwer/widgets/reconstruction/CastNabuVolumeOW.py +22 -68
- orangecontrib/tomwer/widgets/reconstruction/DarkRefAndCopyOW.py +3 -30
- orangecontrib/tomwer/widgets/reconstruction/NabuHelicalPrepareWeightsDoubleOW.py +21 -19
- orangecontrib/tomwer/widgets/reconstruction/NabuOW.py +7 -70
- orangecontrib/tomwer/widgets/reconstruction/NabuVolumeOW.py +41 -84
- orangecontrib/tomwer/widgets/reconstruction/SAAxisOW.py +15 -40
- orangecontrib/tomwer/widgets/reconstruction/SADeltaBetaOW.py +13 -35
- orangecontrib/tomwer/widgets/reconstruction/SinoNormOW.py +2 -30
- orangecontrib/tomwer/widgets/stitching/ZStitchingConfigOW.py +11 -11
- orangecontrib/tomwer/widgets/utils.py +2 -29
- orangecontrib/tomwer/widgets/visualization/DataViewerOW.py +1 -27
- orangecontrib/tomwer/widgets/visualization/DiffViewerOW.py +1 -27
- orangecontrib/tomwer/widgets/visualization/NXtomoMetadataViewerOW.py +1 -1
- orangecontrib/tomwer/widgets/visualization/RadioStackOW.py +2 -28
- orangecontrib/tomwer/widgets/visualization/SampleMovedOW.py +1 -27
- orangecontrib/tomwer/widgets/visualization/SinogramViewerOW.py +1 -27
- orangecontrib/tomwer/widgets/visualization/SliceStackOW.py +2 -28
- tomwer/__init__.py +3 -28
- tomwer/__main__.py +8 -4
- tomwer/app/__init__.py +2 -0
- tomwer/app/axis.py +8 -10
- tomwer/app/canvas.py +23 -29
- tomwer/app/canvas_launcher/config.py +14 -102
- tomwer/app/canvas_launcher/environ.py +5 -8
- tomwer/app/canvas_launcher/mainwindow.py +175 -69
- tomwer/app/canvas_launcher/splash.py +1 -3
- tomwer/app/canvas_launcher/utils.py +47 -0
- tomwer/app/canvas_launcher/widgetsscheme.py +3 -10
- tomwer/app/diffframe.py +2 -0
- tomwer/app/imagekeyeditor.py +2 -1
- tomwer/app/imagekeyupgrader.py +2 -0
- tomwer/app/multicor.py +5 -2
- tomwer/app/multipag.py +16 -5
- tomwer/app/nabuapp.py +2 -1
- tomwer/app/nxtomoeditor.py +17 -12
- tomwer/app/patchrawdarkflat.py +2 -0
- tomwer/app/radiostack.py +3 -2
- tomwer/app/reducedarkflat.py +3 -0
- tomwer/app/samplemoved.py +2 -0
- tomwer/app/scanviewer.py +2 -0
- tomwer/app/sinogramviewer.py +2 -0
- tomwer/app/slicestack.py +11 -13
- tomwer/app/stitching/common.py +431 -0
- tomwer/app/stopdatalistener.py +3 -0
- tomwer/app/ystitching.py +26 -0
- tomwer/app/zstitching.py +8 -363
- tomwer/core/__init__.py +2 -28
- tomwer/core/cluster/__init__.py +3 -0
- tomwer/core/cluster/cluster.py +10 -26
- tomwer/core/futureobject.py +17 -43
- tomwer/core/log/__init__.py +2 -0
- tomwer/core/log/processlog.py +0 -28
- tomwer/core/process/cluster/supervisor.py +52 -34
- tomwer/core/process/conditions/filters.py +3 -28
- tomwer/core/process/control/datalistener/datalistener.py +7 -75
- tomwer/core/process/control/datalistener/rpcserver.py +8 -38
- tomwer/core/process/control/datawatcher/datawatcher.py +11 -40
- tomwer/core/process/control/datawatcher/datawatcherobserver.py +30 -64
- tomwer/core/process/control/datawatcher/datawatcherprocess.py +11 -32
- tomwer/core/process/control/datawatcher/edfdwprocess.py +2 -27
- tomwer/core/process/control/datawatcher/hdf5dwprocess.py +1 -26
- tomwer/core/process/control/datawatcher/status.py +1 -26
- tomwer/core/process/control/emailnotifier.py +11 -23
- tomwer/core/process/control/nxtomoconcatenate.py +20 -18
- tomwer/core/process/control/nxtomomill.py +9 -44
- tomwer/core/process/control/scanlist.py +0 -27
- tomwer/core/process/control/scantransfer.py +15 -13
- tomwer/core/process/control/scanvalidator.py +4 -30
- tomwer/core/process/control/{test → tests}/test_concatenate_nxtomos.py +5 -5
- tomwer/core/process/control/timer.py +1 -27
- tomwer/core/process/control/tomoobjseries.py +12 -0
- tomwer/core/process/drac/binning.py +37 -0
- tomwer/core/process/drac/dracbase.py +170 -0
- tomwer/core/process/drac/gallery.py +109 -0
- tomwer/core/process/drac/output.py +12 -0
- tomwer/core/process/drac/processeddataset.py +147 -0
- tomwer/core/process/drac/publish.py +118 -0
- tomwer/core/process/drac/rawdataset.py +142 -0
- tomwer/core/process/drac/tests/test_gallery.py +71 -0
- tomwer/core/process/drac/tests/test_icat_processed_dataset.py +80 -0
- tomwer/core/process/drac/tests/test_icat_raw_dataset.py +90 -0
- tomwer/core/process/edit/darkflatpatch.py +1 -28
- tomwer/core/process/edit/imagekeyeditor.py +4 -32
- tomwer/core/process/edit/nxtomoeditor.py +307 -0
- tomwer/core/process/edit/tests/test_darkflatpatch.py +243 -0
- tomwer/core/process/edit/tests/test_imagekey_editor.py +99 -0
- tomwer/core/process/output.py +9 -2
- tomwer/core/process/reconstruction/__init__.py +0 -26
- tomwer/core/process/reconstruction/axis/anglemode.py +1 -29
- tomwer/core/process/reconstruction/axis/axis.py +47 -804
- tomwer/core/process/reconstruction/axis/mode.py +89 -25
- tomwer/core/process/reconstruction/axis/params.py +131 -283
- tomwer/core/process/reconstruction/axis/projectiontype.py +0 -28
- tomwer/core/process/reconstruction/axis/side.py +8 -0
- tomwer/core/process/reconstruction/darkref/darkrefs.py +14 -39
- tomwer/core/process/reconstruction/darkref/darkrefscopy.py +7 -39
- tomwer/core/process/reconstruction/darkref/params.py +1 -28
- tomwer/core/process/reconstruction/nabu/castvolume.py +19 -34
- tomwer/core/process/reconstruction/nabu/nabucommon.py +18 -43
- tomwer/core/process/reconstruction/nabu/nabuscores.py +64 -68
- tomwer/core/process/reconstruction/nabu/nabuslices.py +63 -105
- tomwer/core/process/reconstruction/nabu/nabuvolume.py +44 -70
- tomwer/core/process/reconstruction/nabu/plane.py +10 -0
- tomwer/core/process/reconstruction/nabu/settings.py +1 -28
- tomwer/core/process/reconstruction/nabu/target.py +0 -28
- tomwer/core/process/reconstruction/nabu/test/test_castvolume.py +116 -0
- tomwer/core/process/reconstruction/nabu/test/test_nabu_utils.py +277 -0
- tomwer/core/process/reconstruction/nabu/test/test_nabunormalization.py +199 -0
- tomwer/core/process/reconstruction/nabu/utils.py +11 -60
- tomwer/core/process/reconstruction/normalization/normalization.py +2 -32
- tomwer/core/process/reconstruction/normalization/params.py +3 -35
- tomwer/core/process/reconstruction/output.py +14 -19
- tomwer/core/process/reconstruction/paramsbase.py +4 -33
- tomwer/core/process/reconstruction/saaxis/params.py +5 -33
- tomwer/core/process/reconstruction/saaxis/saaxis.py +22 -51
- tomwer/core/process/reconstruction/sadeltabeta/params.py +2 -31
- tomwer/core/process/reconstruction/sadeltabeta/sadeltabeta.py +18 -46
- tomwer/core/process/reconstruction/scores/params.py +9 -39
- tomwer/core/process/reconstruction/scores/scores.py +10 -42
- tomwer/core/process/reconstruction/tests/__init__.py +0 -0
- tomwer/core/process/reconstruction/tests/test_axis.py +46 -0
- tomwer/core/process/reconstruction/tests/test_darkref.py +33 -0
- tomwer/core/process/reconstruction/{test → tests}/test_saaxis.py +1 -27
- tomwer/core/process/reconstruction/tests/test_sadeltabeta.py +48 -0
- tomwer/core/process/reconstruction/{test → tests}/test_utils.py +4 -4
- tomwer/core/process/reconstruction/utils/cor.py +8 -4
- tomwer/core/process/script/python.py +1 -27
- tomwer/core/process/script/tests/test_script.py +41 -0
- tomwer/core/process/stitching/metadataholder.py +5 -4
- tomwer/core/process/stitching/nabustitcher.py +35 -5
- tomwer/core/process/stitching/tests/test_metadataholder.py +17 -0
- tomwer/core/process/task.py +20 -63
- tomwer/core/process/tests/__init__.py +0 -0
- tomwer/core/process/{test → tests}/test_conditions.py +1 -28
- tomwer/core/process/{test → tests}/test_dark_and_flat.py +1 -27
- tomwer/core/process/{test → tests}/test_data_listener.py +1 -27
- tomwer/core/process/{test → tests}/test_data_transfer.py +2 -28
- tomwer/core/process/{test → tests}/test_data_watcher.py +1 -27
- tomwer/core/process/{test → tests}/test_nabu.py +2 -33
- tomwer/core/process/{test → tests}/test_normalization.py +1 -26
- tomwer/core/process/{test → tests}/test_timer.py +1 -27
- tomwer/core/process/utils.py +2 -31
- tomwer/core/process/visualization/dataviewer.py +1 -26
- tomwer/core/process/visualization/diffviewer.py +1 -26
- tomwer/core/process/visualization/imagestackviewer.py +0 -26
- tomwer/core/process/visualization/radiostack.py +0 -26
- tomwer/core/process/visualization/samplemoved.py +0 -28
- tomwer/core/process/visualization/sinogramviewer.py +0 -27
- tomwer/core/process/visualization/slicestack.py +0 -28
- tomwer/core/process/visualization/tests/test_data_viewer.py +12 -0
- tomwer/core/process/visualization/tests/test_diff_viewer.py +12 -0
- tomwer/core/process/visualization/tests/test_image_stack_viewer.py +14 -0
- tomwer/core/process/visualization/tests/test_radio_stack.py +12 -0
- tomwer/core/process/visualization/tests/test_sample_moved.py +14 -0
- tomwer/core/process/visualization/tests/test_sinogram_viewer.py +13 -0
- tomwer/core/process/visualization/tests/test_slice_stack.py +13 -0
- tomwer/core/process/visualization/tests/test_volume_viewer.py +12 -0
- tomwer/core/process/visualization/volumeviewer.py +0 -29
- tomwer/core/scan/__init__.py +3 -27
- tomwer/core/scan/blissscan.py +5 -34
- tomwer/core/scan/edfscan.py +19 -53
- tomwer/core/scan/hdf5scan.py +2 -2
- tomwer/core/scan/nxtomoscan.py +48 -54
- tomwer/core/scan/scanbase.py +107 -203
- tomwer/core/scan/scanfactory.py +11 -41
- tomwer/core/scan/tests/__init__.py +0 -0
- tomwer/core/scan/tests/test_edf.py +25 -0
- tomwer/core/scan/tests/test_future_scan.py +35 -0
- tomwer/core/scan/tests/test_nxtomoscan.py +143 -0
- tomwer/core/scan/tests/test_process_registration.py +64 -0
- tomwer/core/settings.py +18 -40
- tomwer/core/tests/__init__.py +0 -0
- tomwer/core/tests/test_scanutils.py +26 -0
- tomwer/core/{test → tests}/test_utils.py +1 -28
- tomwer/core/tomwer_object.py +4 -0
- tomwer/core/utils/__init__.py +2 -0
- tomwer/core/utils/char.py +0 -28
- tomwer/core/utils/deprecation.py +12 -38
- tomwer/core/utils/dictutils.py +1 -3
- tomwer/core/utils/ftseriesutils.py +1 -27
- tomwer/core/utils/gpu.py +0 -28
- tomwer/core/utils/image.py +8 -39
- tomwer/core/utils/locker.py +1 -28
- tomwer/core/utils/logconfig.py +0 -27
- tomwer/core/utils/normalization.py +4 -31
- tomwer/core/utils/nxtomoutils.py +8 -38
- tomwer/core/utils/resource.py +0 -26
- tomwer/core/utils/scanutils.py +23 -52
- tomwer/core/utils/slurm.py +7 -30
- tomwer/core/utils/spec.py +6 -6
- tomwer/core/utils/tests/__init__.py +0 -0
- tomwer/core/utils/tests/test_image.py +30 -0
- tomwer/core/utils/tests/test_nxtomo.py +38 -0
- tomwer/core/utils/tests/test_scan_utils.py +46 -0
- tomwer/core/utils/tests/test_time.py +6 -0
- tomwer/core/utils/threads.py +0 -26
- tomwer/core/utils/time.py +0 -27
- tomwer/core/volume/__init__.py +4 -0
- tomwer/core/volume/edfvolume.py +1 -28
- tomwer/core/volume/hdf5volume.py +1 -28
- tomwer/core/volume/jp2kvolume.py +1 -27
- tomwer/core/volume/rawvolume.py +1 -28
- tomwer/core/volume/tests/test_volumes.py +21 -0
- tomwer/core/volume/tiffvolume.py +1 -28
- tomwer/core/volume/volumebase.py +5 -0
- tomwer/core/volume/volumefactory.py +7 -33
- tomwer/gui/__init__.py +0 -28
- tomwer/gui/cluster/__init__.py +2 -0
- tomwer/gui/cluster/slurm.py +297 -95
- tomwer/gui/cluster/supervisor.py +1 -27
- tomwer/gui/cluster/tests/__init__.py +0 -0
- tomwer/gui/cluster/{test → tests}/test_cluster.py +21 -26
- tomwer/gui/cluster/{test → tests}/test_supervisor.py +1 -27
- tomwer/gui/conditions/__init__.py +2 -0
- tomwer/gui/conditions/filter.py +1 -26
- tomwer/gui/configuration/__init__.py +2 -0
- tomwer/gui/control/__init__.py +2 -0
- tomwer/gui/control/actions.py +2 -28
- tomwer/gui/control/datadiscovery.py +4 -3
- tomwer/gui/control/datalist.py +64 -69
- tomwer/gui/control/datalistener.py +1 -39
- tomwer/gui/control/datareacheractions.py +1 -28
- tomwer/gui/control/datatransfert.py +2 -29
- tomwer/gui/control/datavalidator.py +3 -37
- tomwer/gui/control/datawatcher/__init__.py +0 -28
- tomwer/gui/control/datawatcher/configuration.py +1 -28
- tomwer/gui/control/datawatcher/datawatcher.py +3 -32
- tomwer/gui/control/datawatcher/datawatcherobserver.py +2 -28
- tomwer/gui/control/history.py +5 -35
- tomwer/gui/control/nxtomomill.py +3 -30
- tomwer/gui/control/observations.py +61 -55
- tomwer/gui/control/reducedarkflatselector.py +24 -20
- tomwer/gui/control/scanselectorwidget.py +2 -28
- tomwer/gui/control/selectorwidgetbase.py +17 -17
- tomwer/gui/control/series/__init__.py +0 -0
- tomwer/gui/control/{serie/seriecreator.py → series/seriescreator.py} +214 -235
- tomwer/gui/control/series/serieswaiter.py +0 -0
- tomwer/gui/control/series/test/test_creator.py +424 -0
- tomwer/gui/control/series/test/test_nxtomo_concatenate.py +21 -0
- tomwer/gui/control/singletomoobj.py +1 -1
- tomwer/gui/control/tests/__init__.py +0 -0
- tomwer/gui/control/tests/test_datalist.py +47 -0
- tomwer/gui/control/{test → tests}/test_datalistener.py +1 -28
- tomwer/gui/control/tests/test_datavalidator.py +27 -0
- tomwer/gui/control/{test → tests}/test_inputwidget.py +1 -27
- tomwer/gui/control/tests/test_process_manager.py +38 -0
- tomwer/gui/control/tests/test_scan_observations.py +23 -0
- tomwer/gui/control/tests/test_scanselector.py +42 -0
- tomwer/gui/control/{test → tests}/test_scanvalidator.py +1 -27
- tomwer/gui/control/{test → tests}/test_volume_dialog.py +2 -30
- tomwer/gui/control/{test → tests}/test_volumeselector.py +1 -27
- tomwer/gui/control/volumeselectorwidget.py +2 -30
- tomwer/gui/dataportal/__init__.py +2 -0
- tomwer/gui/{icat → dataportal}/createscreenshots.py +3 -2
- tomwer/gui/dataportal/gallery.py +133 -0
- tomwer/gui/dataportal/outputformat.py +0 -0
- tomwer/gui/dataportal/publish.py +96 -0
- tomwer/gui/dataportal/tests/test_create_screenshots_gui.py +23 -0
- tomwer/gui/dataportal/tests/test_gallery_gui.py +21 -0
- tomwer/gui/debugtools/__init__.py +2 -0
- tomwer/gui/debugtools/datasetgenerator.py +1 -30
- tomwer/gui/debugtools/objectinspector.py +1 -29
- tomwer/gui/dialog/QDataDialog.py +89 -0
- tomwer/gui/{qfolderdialog.py → dialog/QVolumeDialog.py} +8 -107
- tomwer/gui/dialog/__init__.py +1 -0
- tomwer/gui/edit/__init__.py +2 -0
- tomwer/gui/edit/dkrfpatch.py +52 -87
- tomwer/gui/edit/imagekeyeditor.py +18 -54
- tomwer/gui/edit/nxtomoeditor.py +113 -300
- tomwer/gui/edit/nxtomowarmer.py +3 -2
- tomwer/gui/edit/tests/__init__.py +0 -0
- tomwer/gui/edit/{test → tests}/test_dkrf_patch.py +3 -29
- tomwer/gui/edit/{test → tests}/test_image_key_editor.py +1 -27
- tomwer/gui/edit/{test → tests}/test_nx_editor.py +45 -23
- tomwer/gui/fonts.py +5 -0
- tomwer/gui/icons.py +28 -66
- tomwer/gui/illustrations.py +4 -34
- tomwer/gui/imagefromfile.py +5 -30
- tomwer/gui/metadataloader.py +36 -0
- tomwer/gui/qconfigfile.py +3 -0
- tomwer/gui/qlefilesystem.py +3 -0
- tomwer/gui/reconstruction/__init__.py +2 -0
- tomwer/gui/reconstruction/axis/AxisMainWindow.py +275 -0
- tomwer/gui/reconstruction/axis/AxisOptionsWidget.py +313 -0
- tomwer/gui/reconstruction/axis/AxisSettingsWidget.py +797 -0
- tomwer/gui/reconstruction/axis/AxisWidget.py +534 -0
- tomwer/gui/reconstruction/axis/CalculationWidget.py +218 -0
- tomwer/gui/reconstruction/axis/CompareImages.py +11 -44
- tomwer/gui/reconstruction/axis/ControlWidget.py +285 -0
- tomwer/gui/reconstruction/axis/EstimatedCORWidget.py +394 -0
- tomwer/gui/reconstruction/axis/EstimatedCorComboBox.py +118 -0
- tomwer/gui/reconstruction/axis/InputWidget.py +347 -0
- tomwer/gui/reconstruction/axis/ManualFramesSelection.py +168 -0
- tomwer/gui/reconstruction/axis/__init__.py +2 -2
- tomwer/gui/reconstruction/darkref/__init__.py +0 -27
- tomwer/gui/reconstruction/darkref/darkrefcopywidget.py +5 -34
- tomwer/gui/reconstruction/darkref/darkrefwidget.py +1 -27
- tomwer/gui/reconstruction/nabu/castvolume.py +40 -59
- tomwer/gui/reconstruction/nabu/check.py +7 -33
- tomwer/gui/reconstruction/nabu/nabuconfig/base.py +7 -34
- tomwer/gui/reconstruction/nabu/nabuconfig/ctf.py +6 -5
- tomwer/gui/reconstruction/nabu/nabuconfig/nabuconfig.py +10 -69
- tomwer/gui/reconstruction/nabu/nabuconfig/output.py +3 -47
- tomwer/gui/reconstruction/nabu/nabuconfig/phase.py +54 -36
- tomwer/gui/reconstruction/nabu/nabuconfig/preprocessing.py +103 -54
- tomwer/gui/reconstruction/nabu/nabuconfig/reconstruction.py +116 -65
- tomwer/gui/reconstruction/nabu/nabuflow.py +31 -61
- tomwer/gui/reconstruction/nabu/platform.py +94 -0
- tomwer/gui/reconstruction/nabu/slices.py +81 -45
- tomwer/gui/reconstruction/nabu/slurm.py +1 -27
- tomwer/gui/reconstruction/nabu/test/test_cast_volume.py +82 -0
- tomwer/gui/reconstruction/nabu/test/test_check.py +66 -0
- tomwer/gui/reconstruction/nabu/test/test_ctf.py +46 -0
- tomwer/gui/reconstruction/nabu/test/test_helical.py +21 -0
- tomwer/gui/reconstruction/nabu/test/test_nabu_preprocessing.py +81 -0
- tomwer/gui/reconstruction/nabu/volume.py +62 -90
- tomwer/gui/reconstruction/normalization/intensity.py +5 -32
- tomwer/gui/reconstruction/normalization/test/test_intensity.py +89 -0
- tomwer/gui/reconstruction/saaxis/corrangeselector.py +32 -56
- tomwer/gui/reconstruction/saaxis/dimensionwidget.py +56 -96
- tomwer/gui/reconstruction/saaxis/saaxis.py +17 -38
- tomwer/gui/reconstruction/saaxis/sliceselector.py +8 -39
- tomwer/gui/reconstruction/sadeltabeta/saadeltabeta.py +19 -37
- tomwer/gui/reconstruction/scores/control.py +2 -30
- tomwer/gui/reconstruction/scores/scoreplot.py +23 -49
- tomwer/gui/reconstruction/tests/__init__.py +0 -0
- tomwer/gui/reconstruction/{test → tests}/test_axis.py +23 -49
- tomwer/gui/reconstruction/{test → tests}/test_nabu.py +16 -31
- tomwer/gui/reconstruction/{test → tests}/test_saaxis.py +10 -37
- tomwer/gui/reconstruction/{test → tests}/test_sadeltabeta.py +1 -26
- tomwer/gui/samplemoved/__init__.py +2 -30
- tomwer/gui/samplemoved/selectiontable.py +3 -33
- tomwer/gui/settings.py +7 -0
- tomwer/gui/stackplot.py +33 -661
- tomwer/gui/stacks.py +261 -135
- tomwer/gui/stitching/SingleAxisStitchingWidget.py +326 -0
- tomwer/gui/stitching/StitchingOptionsWidget.py +438 -0
- tomwer/gui/stitching/StitchingWindow.py +586 -0
- tomwer/gui/stitching/__init__.py +2 -0
- tomwer/gui/stitching/alignment.py +90 -0
- tomwer/gui/stitching/axisorderedlist.py +44 -34
- tomwer/gui/stitching/config/axisparams.py +25 -11
- tomwer/gui/stitching/config/output.py +6 -5
- tomwer/gui/stitching/config/positionoveraxis.py +313 -51
- tomwer/gui/stitching/config/stitchingstrategies.py +22 -16
- tomwer/gui/stitching/config/tests/test_axisparams.py +25 -0
- tomwer/gui/stitching/config/tomoobjdetails.py +3 -5
- tomwer/gui/stitching/normalization.py +1 -0
- tomwer/gui/stitching/preview.py +59 -0
- tomwer/gui/stitching/singleaxis.py +57 -0
- tomwer/gui/stitching/stitchandbackground.py +3 -2
- tomwer/gui/stitching/stitching_preview.py +44 -36
- tomwer/gui/stitching/stitching_raw.py +5 -3
- tomwer/gui/stitching/tests/test_ZStitchingWindow.py +88 -0
- tomwer/gui/stitching/tests/test_axis_ordered_list.py +21 -0
- tomwer/gui/stitching/tests/test_normalization.py +27 -0
- tomwer/gui/stitching/tests/test_preview.py +68 -0
- tomwer/gui/stitching/tests/test_stitching_raw.py +110 -0
- tomwer/gui/stitching/tests/utils.py +92 -0
- tomwer/gui/stitching/utils.py +14 -0
- tomwer/gui/stitching/z_stitching/fineestimation.py +5 -33
- tomwer/gui/stitching/z_stitching/tests/test_fine_estimation.py +35 -0
- tomwer/gui/stitching/z_stitching/tests/test_raw_estimation.py +215 -0
- tomwer/gui/stitching/z_stitching/tests/test_stitching_window.py +51 -0
- tomwer/gui/tests/__init__.py +0 -0
- tomwer/gui/tests/test_axis_gui.py +43 -0
- tomwer/gui/{test → tests}/test_qfolder_dialog.py +1 -1
- tomwer/gui/utils/RangeWidget.py +44 -0
- tomwer/gui/utils/buttons.py +4 -3
- tomwer/gui/utils/completer.py +2 -33
- tomwer/gui/utils/flow.py +11 -40
- tomwer/gui/utils/gpu.py +60 -0
- tomwer/gui/utils/illustrations.py +1 -26
- tomwer/gui/utils/inputwidget.py +35 -73
- tomwer/gui/utils/lineselector/lineselector.py +9 -46
- tomwer/gui/utils/loadingmode.py +81 -0
- tomwer/gui/utils/qt_utils.py +9 -0
- tomwer/gui/utils/sandboxes.py +1 -26
- tomwer/gui/utils/scandescription.py +2 -31
- tomwer/gui/utils/scrollarea.py +6 -55
- tomwer/gui/utils/slider.py +4 -28
- tomwer/gui/utils/splashscreen.py +0 -28
- tomwer/gui/utils/step.py +14 -13
- tomwer/gui/utils/tests/test_completer.py +41 -0
- tomwer/gui/utils/tests/test_line_selector.py +21 -0
- tomwer/gui/utils/tests/test_splashscreen.py +8 -0
- tomwer/gui/utils/tests/test_vignettes.py +68 -0
- tomwer/gui/utils/unitsystem.py +15 -69
- tomwer/gui/utils/utils.py +4 -5
- tomwer/gui/utils/vignettes.py +10 -41
- tomwer/gui/utils/waiterthread.py +0 -26
- tomwer/gui/visualization/__init__.py +2 -0
- tomwer/gui/visualization/dataviewer.py +68 -421
- tomwer/gui/visualization/diffviewer/diffviewer.py +2 -30
- tomwer/gui/visualization/diffviewer/shiftwidget.py +4 -29
- tomwer/gui/visualization/fullscreenplot.py +5 -5
- tomwer/gui/visualization/imagestack.py +403 -0
- tomwer/gui/visualization/nxtomometadata.py +0 -4
- tomwer/gui/visualization/reconstructionparameters.py +14 -32
- tomwer/gui/visualization/scanoverview.py +33 -66
- tomwer/gui/visualization/sinogramviewer.py +2 -28
- tomwer/gui/visualization/test/__init__.py +0 -28
- tomwer/gui/visualization/test/test_dataviewer.py +1 -28
- tomwer/gui/visualization/test/test_diffviewer.py +1 -28
- tomwer/gui/visualization/test/test_nx_tomo_metadata_viewer.py +0 -5
- tomwer/gui/visualization/test/test_reconstruction_parameters.py +1 -27
- tomwer/gui/visualization/test/test_sinogramviewer.py +1 -28
- tomwer/gui/visualization/test/test_stacks.py +184 -115
- tomwer/gui/visualization/test/test_volumeviewer.py +3 -2
- tomwer/gui/visualization/tomoobjoverview.py +2 -2
- tomwer/gui/visualization/volumeoverview.py +3 -2
- tomwer/gui/visualization/volumeviewer.py +47 -43
- tomwer/io/__init__.py +2 -0
- tomwer/io/utils/h5pyutils.py +1 -27
- tomwer/io/utils/test/test_raw_and_processed_data.py +10 -0
- tomwer/io/utils/test/test_utils.py +67 -0
- tomwer/io/utils/utils.py +2 -31
- tomwer/resources/__init__.py +13 -33
- tomwer/resources/gui/icons/edit_downstream.svg +114 -0
- tomwer/resources/gui/icons/edit_upstream.svg +112 -0
- tomwer/resources/gui/icons/free_edition.svg +23 -0
- tomwer/resources/gui/icons/icat_gallery_opts.png +0 -0
- tomwer/resources/gui/icons/icat_gallery_opts.svg +80 -0
- tomwer/resources/gui/icons/search.png +0 -0
- tomwer/resources/gui/icons/search.svg +23 -0
- tomwer/resources/gui/icons/warning.png +0 -0
- tomwer/synctools/__init__.py +2 -0
- tomwer/synctools/axis.py +1 -27
- tomwer/synctools/darkref.py +1 -26
- tomwer/synctools/datalistener.py +1 -27
- tomwer/synctools/datatransfert.py +2 -27
- tomwer/synctools/imageloaderthread.py +1 -28
- tomwer/synctools/rsyncmanager.py +1 -25
- tomwer/synctools/saaxis.py +1 -26
- tomwer/synctools/sadeltabeta.py +1 -26
- tomwer/synctools/stacks/control/datalistener.py +1 -26
- tomwer/synctools/stacks/processingstack.py +4 -33
- tomwer/synctools/stacks/reconstruction/axis.py +6 -53
- tomwer/synctools/stacks/reconstruction/castvolume.py +12 -43
- tomwer/synctools/stacks/reconstruction/dkrefcopy.py +4 -27
- tomwer/synctools/stacks/reconstruction/nabu.py +3 -28
- tomwer/synctools/stacks/reconstruction/normalization.py +2 -27
- tomwer/synctools/stacks/reconstruction/saaxis.py +2 -27
- tomwer/synctools/stacks/reconstruction/sadeltabeta.py +2 -27
- tomwer/synctools/tests/__init__.py +0 -0
- tomwer/synctools/{test → tests}/test_darkRefs.py +16 -40
- tomwer/synctools/{test → tests}/test_foldertransfer.py +2 -33
- tomwer/synctools/utils/scanstages.py +2 -31
- tomwer/tests/__init__.py +1 -0
- tomwer/tests/app/test_stitching.py +95 -0
- tomwer/tests/datasets.py +1 -5
- tomwer/tests/orangecontrib/tomwer/widgets/cluster/tests/test_future_supervisorow.py +48 -0
- tomwer/tests/orangecontrib/tomwer/widgets/cluster/tests/test_slurm_clusterow.py +40 -0
- tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_advancement.py +8 -0
- tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_data_validator.py +55 -0
- tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_datadiscovery.py +129 -0
- tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_datalistener.py +111 -0
- tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_dataselector.py +69 -0
- tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_datawatcher.py +411 -0
- tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_emailow.py +29 -0
- tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_notifier.py +24 -0
- tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_nxtomo_concatenate_ow.py +64 -0
- tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_nxtomomill.py +133 -0
- tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_reduce_dark_flat_selector.py +40 -0
- tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_singletomoobj.py +40 -0
- tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_timerow.py +25 -0
- tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_tomoobj_series.py +96 -0
- tomwer/tests/orangecontrib/tomwer/widgets/control/tests/test_volume_selector.py +69 -0
- orangecontrib/tomwer/widgets/edit/test/test_image_key_editor.py → tomwer/tests/orangecontrib/tomwer/widgets/debugtools/tests/test_dataset_generator.py +17 -16
- tomwer/tests/orangecontrib/tomwer/widgets/debugtools/tests/test_object_inspector.py +36 -0
- {orangecontrib/tomwer/widgets/edit/test → tomwer/tests/orangecontrib/tomwer/widgets/edit/tests}/test_dark_flat_patch.py +1 -27
- tomwer/tests/orangecontrib/tomwer/widgets/edit/tests/test_image_key_editor.py +30 -0
- tomwer/tests/orangecontrib/tomwer/widgets/edit/tests/test_nxtomo_editor.py +138 -0
- tomwer/tests/orangecontrib/tomwer/widgets/other/tests/test_pythonscript.py +31 -0
- tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_axis.py +199 -0
- tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_cast_volumeow.py +58 -0
- tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_dark_refs_widget.py +136 -0
- tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_delta_beta_selector.py +15 -0
- tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_i_norm.py +200 -0
- tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_nabu_helical_prepare_weights_double.py +20 -0
- tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_nabu_volume.py +74 -0
- tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_nabu_widget.py +107 -0
- tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_sa_delta_beta.py +194 -0
- tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_saaxis.py +194 -0
- tomwer/tests/orangecontrib/tomwer/widgets/stitching/tests/test_zstitching.py +313 -0
- tomwer/tests/orangecontrib/tomwer/widgets/tests/test_conditions.py +85 -0
- tomwer/tests/orangecontrib/tomwer/widgets/tests/test_darkref.py +225 -0
- tomwer/tests/orangecontrib/tomwer/widgets/tests/test_foldertransfert.py +105 -0
- tomwer/tests/orangecontrib/tomwer/widgets/visualization/tests/test_dataviewerow.py +57 -0
- tomwer/tests/orangecontrib/tomwer/widgets/visualization/tests/test_diffviewerow.py +39 -0
- tomwer/tests/orangecontrib/tomwer/widgets/visualization/tests/test_nxtomo_metadata_viewer.py +29 -0
- tomwer/tests/orangecontrib/tomwer/widgets/visualization/tests/test_radio_stackow.py +31 -0
- tomwer/tests/orangecontrib/tomwer/widgets/visualization/tests/test_sample_movedow.py +46 -0
- tomwer/tests/orangecontrib/tomwer/widgets/visualization/tests/test_sinogram_viewerow.py +31 -0
- tomwer/tests/orangecontrib/tomwer/widgets/visualization/tests/test_slice_stackow.py +31 -0
- tomwer/tests/orangecontrib/tomwer/widgets/visualization/tests/test_volume_viewerow.py +32 -0
- tomwer/tests/test_ewoks/test_conversion.py +104 -0
- tomwer/tests/test_ewoks/test_single_node_execution.py +87 -0
- tomwer/tests/test_ewoks/test_workflows.py +138 -0
- tomwer/utils.py +11 -39
- tomwer/version.py +2 -2
- {tomwer-1.3.26.dist-info → tomwer-1.4.0.dist-info}/LICENSE +3 -3
- tomwer-1.4.0.dist-info/METADATA +337 -0
- tomwer-1.4.0.dist-info/RECORD +912 -0
- {tomwer-1.3.26.dist-info → tomwer-1.4.0.dist-info}/WHEEL +1 -1
- {tomwer-1.3.26.dist-info → tomwer-1.4.0.dist-info}/entry_points.txt +1 -0
- orangecontrib/tomwer/widgets/control/DataListOW.py +0 -129
- orangecontrib/tomwer/widgets/control/TomoObjSerieOW.py +0 -86
- orangecontrib/tomwer/widgets/control/VolumeSymLinkOW.py +0 -182
- orangecontrib/tomwer/widgets/edit/test/test_nxtomo_editor.py +0 -141
- orangecontrib/tomwer/widgets/icat/PublishProcessedDataOW.py +0 -115
- orangecontrib/tomwer/widgets/icat/RawDataScreenshotCreatorOW.py +0 -98
- orangecontrib/tomwer/widgets/icat/SaveToGalleryAndPublishOW.py +0 -129
- orangecontrib/tomwer/widgets/icat/icons/add_gallery.png +0 -0
- orangecontrib/tomwer/widgets/icat/icons/add_gallery.svg +0 -82
- orangecontrib/tomwer/widgets/icat/icons/raw_screenshots.png +0 -0
- orangecontrib/tomwer/widgets/icat/icons/raw_screenshots.svg +0 -143
- orangecontrib/tomwer/widgets/visualization/LivesliceOW.py +0 -87
- orangecontrib/tomwer/widgets/visualization/icons/liveslice.png +0 -0
- orangecontrib/tomwer/widgets/visualization/icons/liveslice.svg +0 -110
- tomwer/core/log/logger.py +0 -130
- tomwer/core/process/control/test/test_volume_link.py +0 -74
- tomwer/core/process/control/tomoobjserie.py +0 -12
- tomwer/core/process/control/volumesymlink.py +0 -200
- tomwer/core/process/icat/createscreenshots.py +0 -100
- tomwer/core/process/icat/gallery.py +0 -377
- tomwer/core/process/icat/icatbase.py +0 -36
- tomwer/core/process/icat/publish.py +0 -228
- tomwer/core/process/icat/screenshots.py +0 -27
- tomwer/core/process/reconstruction/test/test_darkref.py +0 -60
- tomwer/core/process/reconstruction/test/test_sadeltabeta.py +0 -74
- tomwer/core/process/test/test_axis.py +0 -309
- tomwer/core/process/visualization/liveslice.py +0 -6
- tomwer/core/progress.py +0 -100
- tomwer/core/scan/test/test_edf.py +0 -53
- tomwer/core/scan/test/test_future_scan.py +0 -61
- tomwer/core/scan/test/test_h5.py +0 -96
- tomwer/core/scan/test/test_process_registration.py +0 -109
- tomwer/core/test/test_scanutils.py +0 -53
- tomwer/gui/control/emailnotifier.py +0 -174
- tomwer/gui/control/serie/seriewaiter.py +0 -28
- tomwer/gui/control/test/__init__.py +0 -28
- tomwer/gui/control/test/test_datalist.py +0 -96
- tomwer/gui/control/test/test_datavalidator.py +0 -54
- tomwer/gui/control/test/test_email.py +0 -35
- tomwer/gui/control/test/test_process_manager.py +0 -65
- tomwer/gui/control/test/test_scanselector.py +0 -67
- tomwer/gui/edit/test/__init__.py +0 -28
- tomwer/gui/icat/gallery.py +0 -214
- tomwer/gui/icat/publish.py +0 -187
- tomwer/gui/reconstruction/axis/axis.py +0 -733
- tomwer/gui/reconstruction/axis/radioaxis.py +0 -2467
- tomwer/gui/stitching/stitching.py +0 -1392
- tomwer/gui/test/__init__.py +0 -28
- tomwer/gui/test/test_axis_gui.py +0 -34
- tomwer/synctools/stacks/edit/darkflatpatch.py +0 -169
- tomwer/synctools/stacks/edit/imagekeyeditor.py +0 -135
- tomwer/third_part/WaitingOverlay.py +0 -110
- tomwer-1.3.26-py3.11-nspkg.pth +0 -1
- tomwer-1.3.26.dist-info/METADATA +0 -292
- tomwer-1.3.26.dist-info/RECORD +0 -785
- tomwer-1.3.26.dist-info/namespace_packages.txt +0 -1
- /orangecontrib/tomwer/{widgets/edit/test → tests}/__init__.py +0 -0
- {tomwer/core/process/control/test → orangecontrib/tomwer/tutorials/id16b}/__init__.py +0 -0
- {tomwer/core/process/icat → orangecontrib/tomwer/widgets/cluster/tests}/__init__.py +0 -0
- /orangecontrib/tomwer/widgets/control/icons/{tomoobjserie.png → tomoobjseries.png} +0 -0
- {tomwer/core/process/reconstruction/test → orangecontrib/tomwer/widgets/control/tests}/__init__.py +0 -0
- /orangecontrib/tomwer/widgets/{icat → dataportal}/icons/publish_processed_data.png +0 -0
- /orangecontrib/tomwer/widgets/{icat → dataportal}/icons/publish_processed_data.svg +0 -0
- {tomwer/core/process/test → orangecontrib/tomwer/widgets/debugtools/tests}/__init__.py +0 -0
- {tomwer/core/scan/test → orangecontrib/tomwer/widgets/edit/tests}/__init__.py +0 -0
- {tomwer/core/test → orangecontrib/tomwer/widgets/other/tests}/__init__.py +0 -0
- {tomwer/gui/cluster/test → orangecontrib/tomwer/widgets/reconstruction/tests}/__init__.py +0 -0
- {tomwer/gui/control/serie → orangecontrib/tomwer/widgets/stitching/tests}/__init__.py +0 -0
- {tomwer/gui/icat → orangecontrib/tomwer/widgets/tests}/__init__.py +0 -0
- {tomwer/gui/reconstruction/test → orangecontrib/tomwer/widgets/visualization/tests}/__init__.py +0 -0
- /tomwer/{synctools/stacks/edit → app/stitching}/__init__.py +0 -0
- /tomwer/{synctools/test → core/process/control/tests}/__init__.py +0 -0
- /tomwer/core/process/control/{test → tests}/test_email.py +0 -0
- /tomwer/core/process/control/{test → tests}/test_h52nx_process.py +0 -0
- /tomwer/{third_part → core/process/drac}/__init__.py +0 -0
- /tomwer/core/process/reconstruction/{test → tests}/test_axis_params.py +0 -0
- /tomwer/core/process/reconstruction/{test → tests}/test_darkref_copy.py +0 -0
- /tomwer/core/process/reconstruction/{test → tests}/test_paramsbase.py +0 -0
- /tomwer/core/scan/{test → tests}/test_scan.py +0 -0
- /tomwer/gui/control/{serie → series}/nxtomoconcatenate.py +0 -0
- /tomwer/gui/control/{test → tests}/test_datadiscovery.py +0 -0
- /tomwer/gui/control/{test → tests}/test_reducedarkflat_selector.py +0 -0
- /tomwer/gui/control/{test → tests}/test_single_tomo_obj.py +0 -0
- {orangecontrib/tomwer/widgets/edit/test → tomwer/tests/orangecontrib/tomwer/widgets/edit/tests}/test_image_key_upgrader.py +0 -0
- {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()
|