tomwer 1.2.0a1__py3-none-any.whl → 1.2.0a3__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/tutorials/append_raw_darks_and_flats_frames_to_NXtomos.ows +44 -0
- orangecontrib/tomwer/tutorials/copy_reduced_darks_and_flats_meth1.ows +55 -0
- orangecontrib/tomwer/tutorials/copy_reduced_darks_and_flats_meth2.ows +48 -0
- orangecontrib/tomwer/tutorials/default_cor_search.ows +40 -0
- orangecontrib/tomwer/tutorials/hello_world_python_script.ows +50 -0
- orangecontrib/tomwer/tutorials/simple_slice_reconstruction_on_slurm.ows +50 -0
- orangecontrib/tomwer/tutorials/simple_volume_to_slurm_reconstruction.ows +8 -8
- orangecontrib/tomwer/widgets/__init__.py +1 -1
- orangecontrib/tomwer/widgets/cluster/FutureSupervisorOW.py +0 -1
- orangecontrib/tomwer/widgets/cluster/SlurmClusterOW.py +8 -6
- orangecontrib/tomwer/widgets/control/AdvancementOW.py +0 -1
- orangecontrib/tomwer/widgets/control/DataDiscoveryOW.py +1 -6
- orangecontrib/tomwer/widgets/control/DataListOW.py +0 -1
- orangecontrib/tomwer/widgets/control/DataListenerOW.py +4 -4
- orangecontrib/tomwer/widgets/control/DataSelectorOW.py +0 -1
- orangecontrib/tomwer/widgets/control/DataTransfertOW.py +7 -7
- orangecontrib/tomwer/widgets/control/DataValidatorOW.py +0 -1
- orangecontrib/tomwer/widgets/control/DataWatcherOW.py +0 -3
- orangecontrib/tomwer/widgets/control/EDF2NXTomomillOW.py +3 -2
- orangecontrib/tomwer/widgets/control/EmailOW.py +82 -0
- orangecontrib/tomwer/widgets/control/FilterOW.py +3 -3
- orangecontrib/tomwer/widgets/control/NXTomomillOW.py +1 -1
- orangecontrib/tomwer/widgets/control/NotifierOW.py +0 -1
- orangecontrib/tomwer/widgets/control/ReduceDarkFlatSelectorOW.py +93 -0
- orangecontrib/tomwer/widgets/control/SingleTomoObjOW.py +29 -5
- orangecontrib/tomwer/widgets/control/TimerOW.py +1 -2
- orangecontrib/tomwer/widgets/control/TomoObjSerieOW.py +0 -1
- orangecontrib/tomwer/widgets/control/VolumeSelector.py +0 -1
- orangecontrib/tomwer/widgets/control/VolumeSymLinkOW.py +4 -10
- orangecontrib/tomwer/widgets/control/icons/email.png +0 -0
- orangecontrib/tomwer/widgets/control/icons/email.svg +58 -0
- orangecontrib/tomwer/widgets/control/icons/reduced_darkflat_selector.png +0 -0
- orangecontrib/tomwer/widgets/control/icons/reduced_darkflat_selector.svg +199 -0
- orangecontrib/tomwer/widgets/debugtools/DatasetGeneratorOW.py +0 -1
- orangecontrib/tomwer/widgets/debugtools/ObjectInspectorOW.py +0 -1
- orangecontrib/tomwer/widgets/edit/DarkFlatPatchOW.py +1 -2
- orangecontrib/tomwer/widgets/edit/ImageKeyEditorOW.py +1 -2
- orangecontrib/tomwer/widgets/edit/ImageKeyUpgraderOW.py +0 -1
- orangecontrib/tomwer/widgets/edit/NXtomoEditorOW.py +0 -1
- orangecontrib/tomwer/widgets/other/PythonScriptOW.py +29 -1
- orangecontrib/tomwer/widgets/other/TomoObjsHub.py +28 -0
- orangecontrib/tomwer/widgets/other/icons/hub.png +0 -0
- orangecontrib/tomwer/widgets/other/icons/hub.svg +113 -0
- orangecontrib/tomwer/widgets/reconstruction/AxisOW.py +18 -12
- orangecontrib/tomwer/widgets/reconstruction/CastNabuVolumeOW.py +0 -2
- orangecontrib/tomwer/widgets/reconstruction/DarkRefAndCopyOW.py +21 -6
- orangecontrib/tomwer/widgets/reconstruction/NabuOW.py +29 -7
- orangecontrib/tomwer/widgets/reconstruction/NabuVolumeOW.py +18 -5
- orangecontrib/tomwer/widgets/reconstruction/SAAxisOW.py +40 -13
- orangecontrib/tomwer/widgets/reconstruction/SADeltaBetaOW.py +37 -10
- orangecontrib/tomwer/widgets/reconstruction/SinoNormOW.py +2 -3
- orangecontrib/tomwer/widgets/reconstruction/TofuOW.py +5 -4
- orangecontrib/tomwer/widgets/stitching/StitcherOW.py +0 -1
- orangecontrib/tomwer/widgets/stitching/ZStitchingConfigOW.py +0 -1
- orangecontrib/tomwer/widgets/visualization/DataViewerOW.py +10 -4
- orangecontrib/tomwer/widgets/visualization/DiffViewerOW.py +1 -1
- orangecontrib/tomwer/widgets/visualization/LivesliceOW.py +0 -1
- orangecontrib/tomwer/widgets/visualization/NXtomoMetadataViewerOW.py +0 -1
- orangecontrib/tomwer/widgets/visualization/RadioStackOW.py +7 -5
- orangecontrib/tomwer/widgets/visualization/SampleMovedOW.py +1 -1
- orangecontrib/tomwer/widgets/visualization/SinogramViewerOW.py +0 -3
- orangecontrib/tomwer/widgets/visualization/SliceStackOW.py +7 -5
- orangecontrib/tomwer/widgets/visualization/VolumeViewerOW.py +4 -4
- tomwer/__main__.py +139 -5
- tomwer/app/axis.py +16 -5
- tomwer/app/canvas_launcher/config.py +1 -1
- tomwer/app/canvas_launcher/mainwindow.py +164 -6
- tomwer/app/darkref.py +10 -181
- tomwer/app/darkrefpatch.py +10 -131
- tomwer/app/diffframe.py +11 -0
- tomwer/app/imagekeyeditor.py +12 -19
- tomwer/app/intensitynormalization.py +1 -0
- tomwer/app/lamino.py +7 -2
- tomwer/app/patchrawdarkflat.py +131 -0
- tomwer/app/radiostack.py +10 -0
- tomwer/app/reducedarkflat.py +205 -0
- tomwer/app/saaxis.py +27 -8
- tomwer/app/sadeltabeta.py +29 -8
- tomwer/app/samplemoved.py +11 -0
- tomwer/app/scanviewer.py +12 -0
- tomwer/app/sinogramviewer.py +11 -0
- tomwer/app/slicestack.py +11 -0
- tomwer/app/zstitching.py +12 -0
- tomwer/core/futureobject.py +4 -2
- tomwer/core/process/conditions/filters.py +26 -4
- tomwer/core/process/control/datadiscovery.py +4 -0
- tomwer/core/process/control/datawatcher/datawatcher.py +5 -1
- tomwer/core/process/control/email.py +148 -0
- tomwer/core/process/control/nxtomoconcatenate.py +9 -2
- tomwer/core/process/control/nxtomomill.py +58 -16
- tomwer/core/process/control/scanselector.py +4 -0
- tomwer/core/process/control/scantransfer.py +52 -23
- tomwer/core/process/control/test/test_concatenate_nxtomos.py +1 -0
- tomwer/core/process/control/test/test_email.py +52 -0
- tomwer/core/process/control/test/test_h52nx_process.py +106 -0
- tomwer/core/process/control/test/test_volume_link.py +5 -4
- tomwer/core/process/control/timer.py +27 -6
- tomwer/core/process/control/tomoobjserie.py +4 -0
- tomwer/core/process/control/volumeselector.py +4 -0
- tomwer/core/process/control/volumesymlink.py +47 -8
- tomwer/core/process/edit/darkflatpatch.py +49 -8
- tomwer/core/process/edit/imagekeyeditor.py +63 -13
- tomwer/core/process/reconstruction/axis/__init__.py +1 -1
- tomwer/core/process/reconstruction/axis/axis.py +61 -41
- tomwer/core/process/reconstruction/axis/params.py +4 -6
- tomwer/core/process/reconstruction/darkref/darkrefs.py +53 -16
- tomwer/core/process/reconstruction/darkref/darkrefscopy.py +12 -2
- tomwer/core/process/reconstruction/lamino/__init__.py +1 -1
- tomwer/core/process/reconstruction/lamino/tofu.py +22 -2
- tomwer/core/process/reconstruction/nabu/nabucommon.py +93 -14
- tomwer/core/process/reconstruction/nabu/nabuscores.py +70 -33
- tomwer/core/process/reconstruction/nabu/nabuslices.py +219 -41
- tomwer/core/process/reconstruction/nabu/nabuvolume.py +240 -108
- tomwer/core/process/reconstruction/nabu/utils.py +10 -36
- tomwer/core/process/reconstruction/normalization/normalization.py +10 -3
- tomwer/core/process/reconstruction/saaxis/__init__.py +1 -0
- tomwer/core/process/reconstruction/saaxis/saaxis.py +564 -376
- tomwer/core/process/reconstruction/sadeltabeta/__init__.py +1 -0
- tomwer/core/process/reconstruction/sadeltabeta/sadeltabeta.py +481 -268
- tomwer/core/process/reconstruction/scores/params.py +21 -8
- tomwer/core/process/reconstruction/test/test_darkref_copy.py +2 -0
- tomwer/core/process/reconstruction/test/test_saaxis.py +21 -8
- tomwer/core/process/reconstruction/test/test_sadeltabeta.py +8 -5
- tomwer/core/process/script/python.py +7 -2
- tomwer/core/process/stitching/nabustitcher.py +10 -3
- tomwer/core/process/task.py +2 -9
- tomwer/core/process/test/test_axis.py +25 -15
- tomwer/core/process/test/test_conditions.py +6 -6
- tomwer/core/process/test/test_dark_and_flat.py +20 -15
- tomwer/core/process/test/test_data_transfer.py +8 -8
- tomwer/core/process/test/test_data_watcher.py +1 -1
- tomwer/core/process/test/test_lamino.py +6 -6
- tomwer/core/process/test/test_nabu.py +13 -8
- tomwer/core/process/test/test_normalization.py +1 -0
- tomwer/core/process/test/test_timer.py +6 -6
- tomwer/core/process/visualization/dataviewer.py +4 -0
- tomwer/core/process/visualization/diffviewer.py +4 -0
- tomwer/core/process/visualization/imagestackviewer.py +4 -0
- tomwer/core/process/visualization/radiostack.py +4 -0
- tomwer/core/process/visualization/samplemoved.py +4 -0
- tomwer/core/process/visualization/sinogramviewer.py +4 -0
- tomwer/core/process/visualization/slicestack.py +4 -0
- tomwer/core/process/visualization/volumeviewer.py +4 -0
- tomwer/core/scan/hdf5scan.py +4 -4
- tomwer/core/scan/scanbase.py +5 -1
- tomwer/core/scan/test/test_process_registration.py +9 -9
- tomwer/core/settings.py +59 -1
- tomwer/core/test/test_lamino.py +2 -1
- tomwer/core/utils/__init__.py +16 -0
- tomwer/core/utils/locker.py +0 -1
- tomwer/core/utils/resource.py +6 -11
- tomwer/core/utils/scanutils.py +2 -0
- tomwer/gui/cluster/slurm.py +91 -7
- tomwer/gui/cluster/supervisor.py +16 -11
- tomwer/gui/cluster/test/test_cluster.py +16 -1
- tomwer/gui/conditions/filter.py +3 -3
- tomwer/gui/control/datalist.py +24 -11
- tomwer/gui/control/email.py +183 -0
- tomwer/gui/control/reducedarkflatselector.py +545 -0
- tomwer/gui/control/singletomoobj.py +23 -1
- tomwer/gui/control/test/test_email.py +35 -0
- tomwer/gui/control/test/test_reducedarkflat_selector.py +280 -0
- tomwer/gui/reconstruction/axis/CompareImages.py +1 -1
- tomwer/gui/reconstruction/axis/axis.py +10 -6
- tomwer/gui/reconstruction/axis/radioaxis.py +14 -6
- tomwer/gui/reconstruction/darkref/darkrefcopywidget.py +2 -0
- tomwer/gui/reconstruction/darkref/darkrefwidget.py +4 -4
- tomwer/gui/reconstruction/nabu/nabuconfig/phase.py +3 -1
- tomwer/gui/reconstruction/nabu/nabuconfig/preprocessing.py +34 -33
- tomwer/gui/reconstruction/nabu/nabuconfig/reconstruction.py +1 -1
- tomwer/gui/reconstruction/normalization/intensity.py +5 -5
- tomwer/gui/reconstruction/saaxis/corrangeselector.py +1 -0
- tomwer/gui/reconstruction/saaxis/saaxis.py +6 -6
- tomwer/gui/reconstruction/sadeltabeta/saadeltabeta.py +6 -6
- tomwer/gui/reconstruction/scores/scoreplot.py +6 -4
- tomwer/gui/samplemoved/__init__.py +2 -2
- tomwer/gui/stackplot.py +18 -7
- tomwer/gui/stacks.py +2 -2
- tomwer/gui/stitching/stitchandbackground.py +2 -2
- tomwer/gui/stitching/stitching.py +1 -1
- tomwer/gui/stitching/stitching_raw.py +1 -1
- tomwer/gui/utils/__init__.py +1 -85
- tomwer/gui/utils/illustrations.py +1 -1
- tomwer/gui/utils/inputwidget.py +41 -36
- tomwer/gui/utils/slider.py +2 -2
- tomwer/gui/utils/utils.py +93 -0
- tomwer/gui/visualization/dataviewer.py +8 -5
- tomwer/gui/visualization/diffviewer/diffviewer.py +4 -4
- tomwer/gui/visualization/reconstructionparameters.py +26 -6
- tomwer/gui/visualization/sinogramviewer.py +7 -1
- tomwer/gui/visualization/test/test_reconstruction_parameters.py +2 -4
- tomwer/gui/visualization/volumeviewer.py +2 -0
- tomwer/resources/__init__.py +55 -43
- tomwer/resources/gui/icons/compose.png +0 -0
- tomwer/resources/gui/icons/compose.svg +75 -0
- tomwer/synctools/datatransfert.py +3 -1
- tomwer/synctools/stacks/edit/darkflatpatch.py +39 -34
- tomwer/synctools/stacks/edit/imagekeyeditor.py +8 -27
- tomwer/synctools/stacks/processingstack.py +45 -9
- tomwer/synctools/stacks/reconstruction/axis.py +6 -5
- tomwer/synctools/stacks/reconstruction/dkrefcopy.py +1 -0
- tomwer/synctools/stacks/reconstruction/lamino.py +3 -3
- tomwer/synctools/stacks/reconstruction/nabu.py +49 -140
- tomwer/synctools/stacks/reconstruction/normalization.py +1 -0
- tomwer/synctools/stacks/reconstruction/saaxis.py +19 -33
- tomwer/synctools/stacks/reconstruction/sadeltabeta.py +16 -32
- tomwer/synctools/test/test_darkRefs.py +19 -10
- tomwer/synctools/test/test_foldertransfer.py +7 -7
- tomwer/third_party/nabu/preproc/phase.py +6 -8
- tomwer/third_party/nabu/utils.py +2 -3
- tomwer/version.py +1 -1
- {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/METADATA +15 -54
- {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/RECORD +219 -192
- {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/WHEEL +1 -1
- {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/entry_points.txt +3 -3
- /tomwer-1.2.0a1-py3.9-nspkg.pth → /tomwer-1.2.0a3-py3.11-nspkg.pth +0 -0
- {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/LICENSE +0 -0
- {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/namespace_packages.txt +0 -0
- {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/top_level.txt +0 -0
@@ -35,17 +35,15 @@ __date__ = "10/02/2021"
|
|
35
35
|
import copy
|
36
36
|
import logging
|
37
37
|
import os
|
38
|
-
from typing import
|
38
|
+
from typing import Optional
|
39
39
|
|
40
40
|
import h5py
|
41
41
|
import numpy
|
42
|
-
from nabu.pipeline.config import get_default_nabu_config
|
43
|
-
from nabu.pipeline.fullfield.nabu_config import (
|
44
|
-
nabu_config as nabu_fullfield_default_config,
|
45
|
-
)
|
46
42
|
from processview.core.manager import DatasetState, ProcessManager
|
47
43
|
from processview.core.superviseprocess import SuperviseProcess
|
48
44
|
from silx.io.url import DataUrl
|
45
|
+
from silx.utils.deprecation import deprecated_warning
|
46
|
+
|
49
47
|
from tomoscan.io import HDF5File
|
50
48
|
|
51
49
|
import tomwer.version
|
@@ -76,9 +74,17 @@ from tomwer.core.utils.scanutils import data_identifier_to_scan
|
|
76
74
|
from tomwer.core.utils.slurm import is_slurm_available
|
77
75
|
from tomwer.core.volume.volumefactory import VolumeFactory
|
78
76
|
from tomwer.io.utils.utils import get_slice_data
|
77
|
+
from tomwer.io.utils import format_stderr_stdout
|
78
|
+
from tomwer.core.process.reconstruction.nabu.nabucommon import (
|
79
|
+
ResultsLocalRun,
|
80
|
+
ResultSlurmRun,
|
81
|
+
ResultsWithStd,
|
82
|
+
)
|
83
|
+
from tomwer.core.futureobject import FutureTomwerObject
|
84
|
+
from tomwer.core.process.reconstruction.saaxis.params import ReconstructionMode
|
79
85
|
|
80
86
|
from ..nabu import utils
|
81
|
-
from .params import
|
87
|
+
from .params import SAAxisParams
|
82
88
|
|
83
89
|
_logger = logging.getLogger(__name__)
|
84
90
|
|
@@ -87,7 +93,9 @@ DEFAULT_RECONS_FOLDER = "saaxis_results"
|
|
87
93
|
|
88
94
|
|
89
95
|
def one_slice_several_cor(
|
90
|
-
scan,
|
96
|
+
scan,
|
97
|
+
configuration: dict,
|
98
|
+
process_id: Optional[int] = None,
|
91
99
|
) -> tuple:
|
92
100
|
"""
|
93
101
|
Run a slice reconstruction using nabu per Center Of Rotation (cor) provided
|
@@ -104,322 +112,40 @@ def one_slice_several_cor(
|
|
104
112
|
(url, score) as value
|
105
113
|
:rtype: tuple
|
106
114
|
"""
|
107
|
-
|
108
|
-
configuration = SAAxisParams.from_dict(configuration)
|
109
|
-
elif not isinstance(configuration, SAAxisParams):
|
110
|
-
raise TypeError(
|
111
|
-
"configuration should be a dictionary or an instance of SAAxisParams"
|
112
|
-
)
|
113
|
-
|
114
|
-
configuration.check_configuration()
|
115
|
-
mode = ReconstructionMode.from_value(configuration.mode)
|
116
|
-
slice_index = configuration.slice_indexes
|
117
|
-
cors = configuration.cors
|
118
|
-
nabu_config = configuration.nabu_params
|
119
|
-
output_dir = configuration.output_dir
|
120
|
-
dry_run = configuration.dry_run
|
121
|
-
nabu_output_config = nabu_config.get("output", {})
|
122
|
-
file_format = nabu_output_config.get("file_format", "hdf5")
|
123
|
-
cluster_config = configuration.cluster_config
|
124
|
-
_logger.info(f"launch reconstruction of slice {slice_index} and cors {cors}")
|
125
|
-
if mode is ReconstructionMode.VERTICAL:
|
126
|
-
if isinstance(slice_index, str):
|
127
|
-
if not slice_index == "middle":
|
128
|
-
raise ValueError(f"slice index {slice_index} not recognized")
|
129
|
-
elif not len(slice_index) == 1:
|
130
|
-
raise ValueError(f"{mode.value} mode only manage one slice")
|
131
|
-
else:
|
132
|
-
slice_index = list(slice_index.values())[0]
|
133
|
-
advancement = Progress(f"saaxis - slice {slice_index} of {scan}")
|
134
|
-
|
135
|
-
_, cor_reconstructions, outs, errs, future_tomo_objs = run_slice_reconstruction(
|
136
|
-
scan=scan,
|
137
|
-
slice_index=slice_index,
|
138
|
-
cor_positions=cors,
|
139
|
-
config=nabu_config,
|
140
|
-
output_dir=output_dir,
|
141
|
-
dry_run=dry_run,
|
142
|
-
file_format=file_format,
|
143
|
-
advancement=advancement,
|
144
|
-
cluster_config=cluster_config,
|
145
|
-
process_id=process_id,
|
146
|
-
)
|
147
|
-
else:
|
148
|
-
raise ValueError(f"{mode} is not handled for now")
|
149
|
-
|
150
|
-
# treat future
|
151
|
-
if output_dir is None:
|
152
|
-
output_dir = os.path.join(scan.path, DEFAULT_RECONS_FOLDER)
|
153
|
-
|
154
|
-
db = None
|
155
|
-
pag = False
|
156
|
-
ctf = False
|
157
|
-
if "phase" in nabu_config:
|
158
|
-
phase_method = nabu_config["phase"].get("method", "").lower()
|
159
|
-
if phase_method in ("pag", "paganin"):
|
160
|
-
pag = True
|
161
|
-
elif phase_method in ("ctf",):
|
162
|
-
ctf = True
|
163
|
-
if "delta_beta" in nabu_config["phase"]:
|
164
|
-
db = round(float(nabu_config["phase"]["delta_beta"]))
|
165
|
-
|
166
|
-
for cor, future_tomo_obj in future_tomo_objs.items():
|
167
|
-
future_tomo_obj.results()
|
168
|
-
# for saaxis we need to retrieve reconstruction url
|
169
|
-
if future_tomo_obj.cancelled() or future_tomo_obj.exceptions():
|
170
|
-
continue
|
171
|
-
else:
|
172
|
-
_file_name = SingleSliceRunner.get_file_basename_reconstruction(
|
173
|
-
scan=scan,
|
174
|
-
slice_index=slice_index,
|
175
|
-
pag=pag,
|
176
|
-
db=db,
|
177
|
-
ctf=ctf,
|
178
|
-
)
|
179
|
-
file_prefix = f"cor_{_file_name}_{cor}"
|
180
|
-
|
181
|
-
recons_vol_id = utils.get_recons_volume_identifier(
|
182
|
-
scan=scan,
|
183
|
-
file_format=file_format,
|
184
|
-
file_prefix=file_prefix,
|
185
|
-
location=output_dir,
|
186
|
-
slice_index=None,
|
187
|
-
start_z=None,
|
188
|
-
end_z=None,
|
189
|
-
expects_single_slice=True,
|
190
|
-
)
|
191
|
-
assert len(recons_vol_id) == 1, "only one volume reconstructed expected"
|
192
|
-
cor_reconstructions[cor] = recons_vol_id
|
193
|
-
|
194
|
-
class PostProcessing:
|
195
|
-
def run(self, slice_index):
|
196
|
-
datasets = self.load_datasets()
|
197
|
-
|
198
|
-
mask_disk_radius = get_disk_mask_radius(datasets)
|
199
|
-
scores = {}
|
200
|
-
rois = {}
|
201
|
-
for cor, (url, data) in datasets.items():
|
202
|
-
if data is None:
|
203
|
-
score = None
|
204
|
-
else:
|
205
|
-
assert data.ndim == 2
|
206
|
-
data_roi = apply_roi(data=data, radius=mask_disk_radius, url=url)
|
207
|
-
rois[cor] = data_roi
|
208
|
-
|
209
|
-
# move data_roi to [0-1] range
|
210
|
-
# preprocessing: get percentile 0 and 99 from image and
|
211
|
-
# "clean" highest and lowest pixels from it
|
212
|
-
min_p, max_p = numpy.percentile(data_roi, (1, 99))
|
213
|
-
data_roi_int = data_roi[...]
|
214
|
-
data_roi_int[data_roi_int < min_p] = min_p
|
215
|
-
data_roi_int[data_roi_int > max_p] = max_p
|
216
|
-
data_roi_int = (data_roi_int - min_p) / (max_p - min_p)
|
217
|
-
|
218
|
-
if isinstance(scan, EDFTomoScan):
|
219
|
-
_logger.info("tomo consistency is not handled for EDF scan")
|
220
|
-
tomo_consistency_score = None
|
221
|
-
else:
|
222
|
-
try:
|
223
|
-
projections_with_angle = scan.projections_with_angle()
|
224
|
-
angles_ = [
|
225
|
-
frame_angle
|
226
|
-
for frame_angle, frame in projections_with_angle.items()
|
227
|
-
]
|
228
|
-
angles = []
|
229
|
-
for angle in angles_:
|
230
|
-
if not isinstance(angle, str):
|
231
|
-
angles.append(angle)
|
232
|
-
if slice_index == "middle":
|
233
|
-
if scan.dim_2 is not None:
|
234
|
-
slice_index = scan.dim_2 // 2
|
235
|
-
else:
|
236
|
-
_logger.warning(
|
237
|
-
"scan.dim_2 returns None, unable to deduce middle "
|
238
|
-
"pick 1024"
|
239
|
-
)
|
240
|
-
slice_index = 1024
|
241
|
-
tomo_consistency_score = compute_score(
|
242
|
-
data=data,
|
243
|
-
method=ScoreMethod.TOMO_CONSISTENCY,
|
244
|
-
angles=angles,
|
245
|
-
original_sinogram=scan.get_sinogram(slice_index),
|
246
|
-
detector_width=scan.dim_1,
|
247
|
-
original_axis_position=cor + scan.dim_1 / 2.0,
|
248
|
-
)
|
249
|
-
except Exception as e:
|
250
|
-
_logger.error(e)
|
251
|
-
tomo_consistency_score = None
|
252
|
-
score = ComputedScore(
|
253
|
-
tv=compute_score(data=data_roi_int, method=ScoreMethod.TV),
|
254
|
-
std=compute_score(data=data_roi_int, method=ScoreMethod.STD),
|
255
|
-
tomo_consistency=tomo_consistency_score,
|
256
|
-
)
|
257
|
-
scores[cor] = (url, score)
|
258
|
-
return scores, rois
|
259
|
-
|
260
|
-
def load_datasets(self):
|
261
|
-
datasets_ = {}
|
262
|
-
for cor, volume_identifiers in cor_reconstructions.items():
|
263
|
-
if len(volume_identifiers) == 0:
|
264
|
-
# in the case failed to load the url
|
265
|
-
continue
|
266
|
-
elif len(volume_identifiers) > 1:
|
267
|
-
raise ValueError("only one slice reconstructed expected per cor")
|
268
|
-
volume = VolumeFactory.create_tomo_object_from_identifier(
|
269
|
-
volume_identifiers[0]
|
270
|
-
)
|
271
|
-
urls = tuple(volume.browse_data_urls())
|
272
|
-
if len(urls) != 1:
|
273
|
-
raise ValueError(
|
274
|
-
f"volume is expected to have at most one url (single slice volume). get {len(urls)}"
|
275
|
-
)
|
276
|
-
url = urls[0]
|
277
|
-
if not isinstance(url, (DataUrl, str)):
|
278
|
-
raise TypeError(
|
279
|
-
f"url is expected to be a str or DataUrl not {type(url)}"
|
280
|
-
)
|
281
|
-
|
282
|
-
try:
|
283
|
-
data = get_slice_data(url=url)
|
284
|
-
except Exception as e:
|
285
|
-
_logger.error(
|
286
|
-
f"Fail to compute a score for {url.path()}. Reason is {e}"
|
287
|
-
)
|
288
|
-
datasets_[cor] = (url, None)
|
289
|
-
else:
|
290
|
-
if data.ndim == 3:
|
291
|
-
if data.shape[0] == 1:
|
292
|
-
data = data.reshape(data.shape[1], data.shape[2])
|
293
|
-
elif data.shape[2] == 1:
|
294
|
-
data = data.reshape(data.shape[0], data.shape[1])
|
295
|
-
else:
|
296
|
-
raise ValueError(
|
297
|
-
f"Data is expected to be 2D. Not {data.ndim}D"
|
298
|
-
)
|
299
|
-
elif data.ndim == 2:
|
300
|
-
pass
|
301
|
-
else:
|
302
|
-
raise ValueError("Data is expected to be 2D. Not {data.ndim}D")
|
303
|
-
|
304
|
-
datasets_[cor] = (url, data)
|
305
|
-
return datasets_
|
306
|
-
|
307
|
-
post_processing = PostProcessing()
|
308
|
-
scores, rois = post_processing.run(slice_index=slice_index)
|
309
|
-
return scores, outs, errs, rois
|
310
|
-
|
311
|
-
|
312
|
-
def run_slice_reconstruction(
|
313
|
-
scan: TomwerScanBase,
|
314
|
-
cor_positions: Iterable,
|
315
|
-
slice_index: int,
|
316
|
-
config: dict,
|
317
|
-
output_dir=None,
|
318
|
-
dry_run: bool = False,
|
319
|
-
file_format: str = "hdf5",
|
320
|
-
advancement=None,
|
321
|
-
cluster_config=None,
|
322
|
-
process_id: Optional[int] = None,
|
323
|
-
) -> tuple:
|
324
|
-
"""
|
325
|
-
call nabu for a reconstruction on scan with the given configuration
|
326
|
-
|
327
|
-
:param TomwerScanBase scan: scan to reconstruct
|
328
|
-
:param tuple: cor_positions cor position to used for reconstruction
|
329
|
-
:param dict config: configuration to run the reconstruction
|
330
|
-
:param Union[None,str]: output dir folder. If None then this will be store
|
331
|
-
under the acquisition folder/saaxis_results
|
332
|
-
:param bool dry_run: do we want to run dry
|
333
|
-
:param bool local: do we want to run a local reconstruction
|
334
|
-
:param advancement: optional Progress class to display advancement
|
335
|
-
|
336
|
-
:return: success: bool, cor_results: dict, outs: list, errs: list, future_tomo_obj
|
337
|
-
recons_urls is a dict with cor value as key (float) and reconstructed slice url
|
338
|
-
as value
|
339
|
-
:rtype: dict
|
340
|
-
"""
|
341
|
-
nabu_configurations = interpret_tomwer_configuration(config, scan=None)
|
342
|
-
if len(nabu_configurations) == 0:
|
343
|
-
raise RuntimeWarning(
|
344
|
-
"Unable to get a valid nabu configuration for " "reconstruction."
|
345
|
-
)
|
346
|
-
elif len(nabu_configurations) > 1:
|
347
|
-
_logger.warning(
|
348
|
-
"Several configuration found for nabu (you probably "
|
349
|
-
"ask for several delta/beta value or several slices). "
|
350
|
-
"Picking the first one."
|
351
|
-
)
|
352
|
-
|
353
|
-
# work on file name...
|
354
|
-
if output_dir is None:
|
355
|
-
output_dir = os.path.join(scan.path, DEFAULT_RECONS_FOLDER)
|
356
|
-
if scan.process_file is not None:
|
357
|
-
steps_file_basename, _ = os.path.splitext(scan.process_file)
|
358
|
-
steps_file_basename = "_".join(
|
359
|
-
("steps_file_basename", "nabu", "sinogram", "save", "step")
|
360
|
-
)
|
361
|
-
steps_file_basename = steps_file_basename + ".hdf5"
|
362
|
-
steps_file = os.path.join(output_dir, steps_file_basename)
|
363
|
-
else:
|
364
|
-
steps_file = ""
|
365
|
-
|
366
|
-
base_config = nabu_configurations[0][0]
|
367
|
-
if cluster_config == {}:
|
368
|
-
cluster_config = None
|
369
|
-
is_cluster_job = cluster_config is not None
|
370
|
-
if is_cluster_job and not is_slurm_available():
|
371
|
-
raise ValueError(
|
372
|
-
"job on cluster requested but no access to slurm cluster found"
|
373
|
-
)
|
374
|
-
configs = {}
|
375
|
-
|
376
|
-
for i_cor, cor in enumerate(cor_positions):
|
377
|
-
nabu_configuration = copy.deepcopy(base_config)
|
378
|
-
nabu_configuration["pipeline"] = {
|
379
|
-
"save_steps": "sinogram" if i_cor == 0 else "",
|
380
|
-
"resume_from_step": "sinogram",
|
381
|
-
"steps_file": steps_file,
|
382
|
-
}
|
383
|
-
# convert cor from tomwer ref to nabu ref
|
384
|
-
if scan.dim_1 is not None:
|
385
|
-
cor_nabu_ref = cor + scan.dim_1 / 2.0
|
386
|
-
else:
|
387
|
-
_logger.warning("enable to get image half width. Set it to 1024")
|
388
|
-
cor_nabu_ref = cor + 1024
|
389
|
-
# handle reconstruction section
|
390
|
-
if "reconstruction" not in nabu_configuration:
|
391
|
-
nabu_configuration["reconstruction"] = {}
|
392
|
-
nabu_configuration["reconstruction"]["rotation_axis_position"] = str(
|
393
|
-
cor_nabu_ref
|
394
|
-
)
|
395
|
-
# handle output section
|
396
|
-
if "output" not in nabu_configuration:
|
397
|
-
nabu_configuration["output"] = {}
|
398
|
-
nabu_configuration["output"]["location"] = output_dir
|
399
|
-
nabu_configuration["output"]["file_format"] = file_format
|
400
|
-
# handle resources section
|
401
|
-
nabu_configuration["resources"] = utils.get_nabu_resources_desc(
|
402
|
-
scan=scan, workers=1, method="local"
|
403
|
-
)
|
404
|
-
configs[cor] = nabu_configuration
|
405
|
-
return run_nabu_one_slice_several_config(
|
406
|
-
nabu_configs=configs,
|
407
|
-
scan=scan,
|
408
|
-
slice_index=slice_index,
|
409
|
-
dry_run=dry_run,
|
410
|
-
file_format=file_format,
|
411
|
-
advancement=advancement,
|
412
|
-
cluster_config=cluster_config.to_dict() if cluster_config is not None else None,
|
115
|
+
task = SAAxisTask(
|
413
116
|
process_id=process_id,
|
117
|
+
inputs={
|
118
|
+
"data": scan,
|
119
|
+
"sa_axis_params": configuration,
|
120
|
+
"serialize_output_data": False,
|
121
|
+
},
|
122
|
+
)
|
123
|
+
task.run()
|
124
|
+
return (
|
125
|
+
task.outputs.scores,
|
126
|
+
task.outputs.std_out,
|
127
|
+
task.outputs.std_err,
|
128
|
+
task.outputs.rois,
|
414
129
|
)
|
415
130
|
|
416
131
|
|
417
|
-
class
|
418
|
-
Task,
|
132
|
+
class SAAxisTask(
|
133
|
+
Task,
|
134
|
+
SuperviseProcess,
|
135
|
+
input_names=("data", "sa_axis_params"),
|
136
|
+
output_names=("data", "best_cor"),
|
137
|
+
optional_input_names=(
|
138
|
+
"dry_run",
|
139
|
+
"dump_roi",
|
140
|
+
"dump_process",
|
141
|
+
"serialize_output_data",
|
142
|
+
),
|
419
143
|
):
|
420
144
|
"""
|
421
145
|
Main process to launch several reconstruction of a single slice with
|
422
146
|
several Center Of Rotation (cor) values
|
147
|
+
|
148
|
+
As the saaxis is integrating the score calculation we will never get a future_tomo_scan as output
|
423
149
|
"""
|
424
150
|
|
425
151
|
def __init__(
|
@@ -434,12 +160,12 @@ class SAAxisProcess(
|
|
434
160
|
)
|
435
161
|
SuperviseProcess.__init__(self, process_id=process_id)
|
436
162
|
self._dry_run = inputs.get("dry_run", False)
|
437
|
-
self._dump_process = inputs.get("
|
163
|
+
self._dump_process = inputs.get("dump_process", True)
|
438
164
|
self._dump_roi = inputs.get("dump_roi", False)
|
439
165
|
self._std_outs = tuple()
|
440
166
|
self._std_errs = tuple()
|
441
|
-
|
442
|
-
|
167
|
+
self._current_processing = None
|
168
|
+
self._cancelled = False
|
443
169
|
|
444
170
|
@property
|
445
171
|
def std_outs(self):
|
@@ -464,16 +190,6 @@ class SAAxisProcess(
|
|
464
190
|
def dump_roi(self, dump):
|
465
191
|
self._dump_roi = dump
|
466
192
|
|
467
|
-
def set_configuration(self, configuration: dict) -> None:
|
468
|
-
if isinstance(configuration, SAAxisParams):
|
469
|
-
self._settings = configuration.to_dict()
|
470
|
-
elif isinstance(configuration, dict):
|
471
|
-
self._settings = configuration
|
472
|
-
else:
|
473
|
-
raise TypeError(
|
474
|
-
"configuration should be an instance of dict or " "SAAxisParams"
|
475
|
-
)
|
476
|
-
|
477
193
|
@staticmethod
|
478
194
|
def autofocus(scan) -> Optional[float]:
|
479
195
|
scores = scan.saaxis_params.scores
|
@@ -491,11 +207,234 @@ class SAAxisProcess(
|
|
491
207
|
best_cor, best_score = cor, score
|
492
208
|
scan.saaxis_params.autofocus = best_cor
|
493
209
|
if scan.axis_params is None:
|
210
|
+
# create parameter if needed because will set it once he find the best cor
|
494
211
|
scan.axis_params = AxisRP()
|
495
212
|
scan.axis_params.frame_width = scan.dim_1
|
496
213
|
scan.axis_params.set_relative_value(best_cor)
|
497
214
|
return best_cor
|
498
215
|
|
216
|
+
def _config_preprocessing(
|
217
|
+
self, scan, config, cor_positions, file_format, output_dir, cluster_config
|
218
|
+
):
|
219
|
+
"""convert general configuration to nabu - single reconstruction - configuration"""
|
220
|
+
nabu_configurations = interpret_tomwer_configuration(config, scan=None)
|
221
|
+
if len(nabu_configurations) == 0:
|
222
|
+
raise RuntimeWarning(
|
223
|
+
"Unable to get a valid nabu configuration for " "reconstruction."
|
224
|
+
)
|
225
|
+
elif len(nabu_configurations) > 1:
|
226
|
+
_logger.warning(
|
227
|
+
"Several configuration found for nabu (you probably "
|
228
|
+
"ask for several delta/beta value or several slices). "
|
229
|
+
"Picking the first one."
|
230
|
+
)
|
231
|
+
|
232
|
+
# work on file name...
|
233
|
+
if output_dir is None:
|
234
|
+
output_dir = os.path.join(scan.path, DEFAULT_RECONS_FOLDER)
|
235
|
+
if scan.process_file is not None:
|
236
|
+
steps_file_basename, _ = os.path.splitext(scan.process_file)
|
237
|
+
steps_file_basename = "_".join(
|
238
|
+
("steps_file_basename", "nabu", "sinogram", "save", "step")
|
239
|
+
)
|
240
|
+
steps_file_basename = steps_file_basename + ".hdf5"
|
241
|
+
steps_file = os.path.join(output_dir, steps_file_basename)
|
242
|
+
else:
|
243
|
+
steps_file = ""
|
244
|
+
|
245
|
+
base_config = nabu_configurations[0][0]
|
246
|
+
if cluster_config == {}:
|
247
|
+
cluster_config = None
|
248
|
+
is_cluster_job = cluster_config is not None
|
249
|
+
if is_cluster_job and not is_slurm_available():
|
250
|
+
raise ValueError(
|
251
|
+
"job on cluster requested but no access to slurm cluster found"
|
252
|
+
)
|
253
|
+
configs = {}
|
254
|
+
|
255
|
+
for i_cor, cor in enumerate(cor_positions):
|
256
|
+
nabu_configuration = copy.deepcopy(base_config)
|
257
|
+
nabu_configuration["pipeline"] = {
|
258
|
+
"save_steps": "sinogram" if i_cor == 0 else "",
|
259
|
+
"resume_from_step": "sinogram",
|
260
|
+
"steps_file": steps_file,
|
261
|
+
}
|
262
|
+
# convert cor from tomwer ref to nabu ref
|
263
|
+
if scan.dim_1 is not None:
|
264
|
+
cor_nabu_ref = cor + scan.dim_1 / 2.0
|
265
|
+
else:
|
266
|
+
_logger.warning("enable to get image half width. Set it to 1024")
|
267
|
+
cor_nabu_ref = cor + 1024
|
268
|
+
# handle reconstruction section
|
269
|
+
if "reconstruction" not in nabu_configuration:
|
270
|
+
nabu_configuration["reconstruction"] = {}
|
271
|
+
nabu_configuration["reconstruction"]["rotation_axis_position"] = str(
|
272
|
+
cor_nabu_ref
|
273
|
+
)
|
274
|
+
# handle output section
|
275
|
+
if "output" not in nabu_configuration:
|
276
|
+
nabu_configuration["output"] = {}
|
277
|
+
nabu_configuration["output"]["location"] = output_dir
|
278
|
+
nabu_configuration["output"]["file_format"] = file_format
|
279
|
+
# handle resources section
|
280
|
+
nabu_configuration["resources"] = utils.get_nabu_resources_desc(
|
281
|
+
scan=scan, workers=1, method="local"
|
282
|
+
)
|
283
|
+
configs[cor] = nabu_configuration
|
284
|
+
return configs
|
285
|
+
|
286
|
+
def _run_slice_recons_per_cor(
|
287
|
+
self,
|
288
|
+
scan,
|
289
|
+
configs,
|
290
|
+
slice_index,
|
291
|
+
file_format,
|
292
|
+
advancement,
|
293
|
+
cluster_config,
|
294
|
+
dry_run=False,
|
295
|
+
):
|
296
|
+
runners = run_nabu_one_slice_several_config(
|
297
|
+
nabu_configs=configs,
|
298
|
+
scan=scan,
|
299
|
+
slice_index=slice_index,
|
300
|
+
dry_run=dry_run,
|
301
|
+
file_format=file_format,
|
302
|
+
advancement=advancement,
|
303
|
+
cluster_config=cluster_config.to_dict()
|
304
|
+
if cluster_config is not None
|
305
|
+
else None,
|
306
|
+
process_id=self.process_id,
|
307
|
+
instanciate_classes_only=True,
|
308
|
+
output_file_prefix_pattern="cor_{file_name}_{value}", # as the cor is evolving, create different files to make sure the name will be unique
|
309
|
+
)
|
310
|
+
|
311
|
+
future_tomo_objs = {}
|
312
|
+
success = True
|
313
|
+
recons_urls = {}
|
314
|
+
std_outs = []
|
315
|
+
std_errs = []
|
316
|
+
|
317
|
+
for runner in runners:
|
318
|
+
if self._cancelled:
|
319
|
+
break
|
320
|
+
self._current_processing = runner
|
321
|
+
try:
|
322
|
+
results = runner.run()
|
323
|
+
except TimeoutError as e:
|
324
|
+
_logger.error(e)
|
325
|
+
else:
|
326
|
+
assert isinstance(
|
327
|
+
results, dict
|
328
|
+
), "results should be a dictionary with cor as key and urls as value"
|
329
|
+
|
330
|
+
for cor, res in results.items():
|
331
|
+
success = success and res.success
|
332
|
+
if isinstance(res, ResultsWithStd):
|
333
|
+
std_outs.append(res.std_out)
|
334
|
+
std_errs.append(res.std_err)
|
335
|
+
if isinstance(res, ResultsLocalRun):
|
336
|
+
recons_urls[cor] = res.results_urls
|
337
|
+
if isinstance(res, ResultSlurmRun):
|
338
|
+
future_tomo_obj = FutureTomwerObject(
|
339
|
+
tomo_obj=scan,
|
340
|
+
process_requester_id=self.process_id,
|
341
|
+
futures=res.future_slurm_jobs,
|
342
|
+
)
|
343
|
+
future_tomo_objs[cor] = future_tomo_obj
|
344
|
+
return success, recons_urls, future_tomo_objs, std_outs, std_errs
|
345
|
+
|
346
|
+
def _resolve_futures(
|
347
|
+
self,
|
348
|
+
scan,
|
349
|
+
nabu_config,
|
350
|
+
slice_index,
|
351
|
+
file_format,
|
352
|
+
cor_reconstructions,
|
353
|
+
future_tomo_objs: dict,
|
354
|
+
output_dir,
|
355
|
+
):
|
356
|
+
"""
|
357
|
+
in case the task is launching jobs over slurm wait for them to be finished before resuming 'standard processing'
|
358
|
+
"""
|
359
|
+
if output_dir is None:
|
360
|
+
output_dir = os.path.join(scan.path, DEFAULT_RECONS_FOLDER)
|
361
|
+
|
362
|
+
db = None
|
363
|
+
pag = False
|
364
|
+
ctf = False
|
365
|
+
if "phase" in nabu_config:
|
366
|
+
phase_method = nabu_config["phase"].get("method", "").lower()
|
367
|
+
if phase_method in ("pag", "paganin"):
|
368
|
+
pag = True
|
369
|
+
elif phase_method in ("ctf",):
|
370
|
+
ctf = True
|
371
|
+
if "delta_beta" in nabu_config["phase"]:
|
372
|
+
db = round(float(nabu_config["phase"]["delta_beta"]))
|
373
|
+
|
374
|
+
for cor, future_tomo_obj in future_tomo_objs.items():
|
375
|
+
if self._cancelled:
|
376
|
+
break
|
377
|
+
future_tomo_obj.results()
|
378
|
+
# for saaxis we need to retrieve reconstruction url
|
379
|
+
if future_tomo_obj.cancelled() or future_tomo_obj.exceptions():
|
380
|
+
continue
|
381
|
+
else:
|
382
|
+
_file_name = SingleSliceRunner.get_file_basename_reconstruction(
|
383
|
+
scan=scan,
|
384
|
+
slice_index=slice_index,
|
385
|
+
pag=pag,
|
386
|
+
db=db,
|
387
|
+
ctf=ctf,
|
388
|
+
)
|
389
|
+
file_prefix = f"cor_{_file_name}_{cor}"
|
390
|
+
|
391
|
+
recons_vol_id = utils.get_recons_volume_identifier(
|
392
|
+
scan=scan,
|
393
|
+
file_format=file_format,
|
394
|
+
file_prefix=file_prefix,
|
395
|
+
location=output_dir,
|
396
|
+
slice_index=None,
|
397
|
+
start_z=None,
|
398
|
+
end_z=None,
|
399
|
+
expects_single_slice=True,
|
400
|
+
)
|
401
|
+
assert len(recons_vol_id) == 1, "only one volume reconstructed expected"
|
402
|
+
cor_reconstructions[cor] = recons_vol_id
|
403
|
+
|
404
|
+
def _post_processing(self, scan, slice_index, cor_reconstructions):
|
405
|
+
"""
|
406
|
+
compute score along the different slices
|
407
|
+
"""
|
408
|
+
post_processing = _PostProcessing(
|
409
|
+
slice_index=slice_index, scan=scan, cor_reconstructions=cor_reconstructions
|
410
|
+
)
|
411
|
+
post_processing._cancelled = self._cancelled
|
412
|
+
self._current_processing = post_processing
|
413
|
+
return post_processing.run()
|
414
|
+
|
415
|
+
def _compute_mess_details(self, mess=""):
|
416
|
+
"""
|
417
|
+
util to join a message and nabu std err and std out
|
418
|
+
"""
|
419
|
+
nabu_logs = []
|
420
|
+
for std_err, std_out in zip(self._std_errs, self.std_outs):
|
421
|
+
nabu_logs.append(format_stderr_stdout(stdout=std_out, stderr=std_err))
|
422
|
+
self._nabu_log = nabu_logs
|
423
|
+
nabu_logs.insert(0, mess)
|
424
|
+
return "\n".join(nabu_logs)
|
425
|
+
|
426
|
+
@staticmethod
|
427
|
+
def _preprocess_slice_index(slice_index, mode: ReconstructionMode):
|
428
|
+
if isinstance(slice_index, str):
|
429
|
+
if not slice_index == "middle":
|
430
|
+
raise ValueError(f"slice index {slice_index} not recognized")
|
431
|
+
else:
|
432
|
+
return slice_index
|
433
|
+
elif not len(slice_index) == 1:
|
434
|
+
raise ValueError(f"{mode.value} mode only manage one slice")
|
435
|
+
else:
|
436
|
+
return list(slice_index.values())[0]
|
437
|
+
|
499
438
|
def run(self):
|
500
439
|
scan = data_identifier_to_scan(self.inputs.data)
|
501
440
|
if scan is None:
|
@@ -509,7 +448,7 @@ class SAAxisProcess(
|
|
509
448
|
raise ValueError(f"input type of {scan}: {type(scan)} is not managed")
|
510
449
|
# TODO: look and update if there is some nabu reconstruction
|
511
450
|
# or axis information to be used back
|
512
|
-
configuration = self.
|
451
|
+
configuration = self.inputs.sa_axis_params
|
513
452
|
params = SAAxisParams.from_dict(configuration)
|
514
453
|
# insure output dir is created
|
515
454
|
if params.output_dir in (None, ""):
|
@@ -536,64 +475,160 @@ class SAAxisProcess(
|
|
536
475
|
if scan.dim_1 is not None:
|
537
476
|
params.image_width = scan.dim_1
|
538
477
|
scan.saaxis_params = params
|
539
|
-
|
478
|
+
|
479
|
+
mode = ReconstructionMode.from_value(params.mode)
|
480
|
+
if mode is not ReconstructionMode.VERTICAL:
|
481
|
+
raise ValueError(f"{mode} is not handled for now")
|
482
|
+
|
483
|
+
output_dir = params.output_dir
|
484
|
+
if output_dir is None:
|
485
|
+
output_dir = os.path.join(scan.path, DEFAULT_RECONS_FOLDER)
|
486
|
+
nabu_output_config = configuration.get("output", {})
|
487
|
+
file_format = nabu_output_config.get("file_format", "hdf5")
|
488
|
+
slice_index = self._preprocess_slice_index(
|
489
|
+
params.slice_indexes,
|
490
|
+
mode=mode,
|
491
|
+
)
|
492
|
+
cluster_config = params.cluster_config
|
493
|
+
dry_run = self._dry_run
|
494
|
+
|
495
|
+
# step one: complete nabu configuration(s)
|
496
|
+
configs = self._config_preprocessing(
|
540
497
|
scan=scan,
|
541
|
-
configuration
|
542
|
-
|
498
|
+
config=configuration,
|
499
|
+
cor_positions=params.cors,
|
500
|
+
file_format=file_format,
|
501
|
+
output_dir=output_dir,
|
502
|
+
cluster_config=cluster_config,
|
503
|
+
)
|
504
|
+
# step 2: run reconstructions
|
505
|
+
advancement = Progress(
|
506
|
+
f"sa-axis - slice {slice_index} of {scan.get_identifier().short_description()}"
|
507
|
+
)
|
508
|
+
cors_res = {}
|
509
|
+
rois = {}
|
510
|
+
|
511
|
+
try:
|
512
|
+
(
|
513
|
+
_,
|
514
|
+
cor_reconstructions,
|
515
|
+
future_tomo_objs,
|
516
|
+
self._std_outs,
|
517
|
+
self._std_errs,
|
518
|
+
) = self._run_slice_recons_per_cor(
|
519
|
+
scan=scan,
|
520
|
+
configs=configs,
|
521
|
+
slice_index=slice_index,
|
522
|
+
file_format=file_format,
|
523
|
+
advancement=advancement,
|
524
|
+
cluster_config=cluster_config,
|
525
|
+
dry_run=dry_run,
|
526
|
+
)
|
527
|
+
except Exception as e:
|
528
|
+
_logger.error(e)
|
529
|
+
mess = f"sa-axis -nabu- computation for {str(scan)} failed."
|
530
|
+
state = DatasetState.FAILED
|
531
|
+
else:
|
532
|
+
# step 3: wait for future if any
|
533
|
+
self._resolve_futures(
|
534
|
+
scan=scan,
|
535
|
+
nabu_config=configuration,
|
536
|
+
slice_index=slice_index,
|
537
|
+
file_format=file_format,
|
538
|
+
cor_reconstructions=cor_reconstructions,
|
539
|
+
future_tomo_objs=future_tomo_objs,
|
540
|
+
output_dir=output_dir,
|
541
|
+
)
|
542
|
+
|
543
|
+
# step 4: run post processing (compute score for each slice)
|
544
|
+
try:
|
545
|
+
cors_res, rois = self._post_processing(
|
546
|
+
scan=scan,
|
547
|
+
slice_index=slice_index,
|
548
|
+
cor_reconstructions=cor_reconstructions,
|
549
|
+
)
|
550
|
+
except Exception as e:
|
551
|
+
_logger.error(e)
|
552
|
+
mess = f"sa-axis -post-processing- computation for {str(scan)} failed."
|
553
|
+
state = DatasetState.FAILED
|
554
|
+
cors_res = {}
|
555
|
+
else:
|
556
|
+
state = DatasetState.WAIT_USER_VALIDATION
|
557
|
+
mess = "sa-axis computation succeeded"
|
558
|
+
|
559
|
+
if self._cancelled:
|
560
|
+
state = DatasetState.CANCELLED
|
561
|
+
mess = "scan cancelled by the user"
|
562
|
+
|
563
|
+
ProcessManager().notify_dataset_state(
|
564
|
+
dataset=scan,
|
565
|
+
process=self,
|
566
|
+
state=state,
|
567
|
+
details=self._compute_mess_details(mess),
|
543
568
|
)
|
569
|
+
|
544
570
|
scan.saaxis_params.scores = cors_res
|
545
571
|
best_relative_cor = self.autofocus(scan=scan)
|
546
572
|
|
547
|
-
# store nabu settings to be used later like in the volume reconstruction
|
548
|
-
config = self.get_configuration()["nabu_params"]
|
549
|
-
# beam shape is not directly used by nabu (uses ctf_geometry directly)
|
550
|
-
config.get("phase", {}).pop("beam_shape", None)
|
551
|
-
|
552
|
-
# update nabu recons_params used
|
553
|
-
sc_config = get_default_nabu_config(nabu_fullfield_default_config)
|
554
|
-
sc_config.update(config)
|
555
|
-
scan.nabu_recons_params = sc_config
|
556
573
|
if best_relative_cor is not None:
|
557
|
-
scan.axis_params.
|
574
|
+
scan.axis_params.set_relative_value(best_relative_cor)
|
558
575
|
|
559
576
|
self._process_end(scan=scan, cors_res=cors_res, score_rois=rois)
|
560
|
-
|
577
|
+
|
578
|
+
if self.get_input_value("serialize_output_data", True):
|
579
|
+
self.outputs.data = scan.to_dict()
|
580
|
+
else:
|
581
|
+
self.outputs.data = scan
|
582
|
+
self.outputs.best_cor = best_relative_cor
|
561
583
|
|
562
584
|
def _process_end(self, scan, cors_res, score_rois):
|
563
585
|
assert isinstance(scan, TomwerScanBase)
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
586
|
+
state = ProcessManager().get_dataset_state(
|
587
|
+
dataset_id=scan.get_identifier(), process=self
|
588
|
+
)
|
589
|
+
if state not in (
|
590
|
+
DatasetState.CANCELLED,
|
591
|
+
DatasetState.FAILED,
|
592
|
+
DatasetState.SKIPPED,
|
593
|
+
):
|
594
|
+
try:
|
595
|
+
extra = {
|
596
|
+
logconfig.DOC_TITLE: self._scheme_title,
|
597
|
+
logconfig.SCAN_ID: str(scan),
|
598
|
+
}
|
599
|
+
slice_index = self.inputs.sa_axis_params.get("slice_index", None)
|
600
|
+
|
601
|
+
if cors_res is None:
|
602
|
+
info = f"fail to compute cor scores of slice {slice_index} for scan {scan}."
|
603
|
+
_logger.processFailed(info, extra=extra)
|
604
|
+
ProcessManager().notify_dataset_state(
|
605
|
+
dataset=scan,
|
606
|
+
process=self,
|
607
|
+
state=DatasetState.FAILED,
|
608
|
+
details=info,
|
609
|
+
)
|
610
|
+
else:
|
611
|
+
info = (
|
612
|
+
f"cor scores of slice {slice_index} for scan {scan} computed."
|
613
|
+
)
|
614
|
+
_logger.processSucceed(info, extra=extra)
|
615
|
+
ProcessManager().notify_dataset_state(
|
616
|
+
dataset=scan,
|
617
|
+
process=self,
|
618
|
+
state=DatasetState.WAIT_USER_VALIDATION,
|
619
|
+
details=info,
|
620
|
+
)
|
621
|
+
except Exception as e:
|
622
|
+
_logger.error(e)
|
577
623
|
else:
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
dataset=scan,
|
582
|
-
process=self,
|
583
|
-
state=DatasetState.WAIT_USER_VALIDATION,
|
584
|
-
details=info,
|
585
|
-
)
|
586
|
-
except Exception as e:
|
587
|
-
_logger.error(e)
|
588
|
-
else:
|
589
|
-
if self._dump_process:
|
590
|
-
process_idx = SAAxisProcess.process_to_tomwer_processes(
|
591
|
-
scan=scan,
|
592
|
-
)
|
593
|
-
if self.dump_roi and process_idx is not None:
|
594
|
-
self.dump_rois(
|
595
|
-
scan, score_rois=score_rois, process_index=process_idx
|
624
|
+
if self._dump_process:
|
625
|
+
process_idx = SAAxisTask.process_to_tomwer_processes(
|
626
|
+
scan=scan,
|
596
627
|
)
|
628
|
+
if self.dump_roi and process_idx is not None:
|
629
|
+
self.dump_rois(
|
630
|
+
scan, score_rois=score_rois, process_index=process_idx
|
631
|
+
)
|
597
632
|
|
598
633
|
@staticmethod
|
599
634
|
def dump_rois(scan, score_rois, process_index):
|
@@ -653,9 +688,9 @@ class SAAxisProcess(
|
|
653
688
|
configuration=scan.saaxis_params.to_dict(),
|
654
689
|
process_index=process_index,
|
655
690
|
overwrite=True,
|
656
|
-
process=
|
691
|
+
process=SAAxisTask,
|
657
692
|
)
|
658
|
-
|
693
|
+
SAAxisTask._extends_results(
|
659
694
|
scan=scan, entry=entry, process_index=process_index
|
660
695
|
)
|
661
696
|
except Exception as e:
|
@@ -701,3 +736,156 @@ class SAAxisProcess(
|
|
701
736
|
results_cor["reconstructed_slice"] = h5py.ExternalLink(
|
702
737
|
link_path, url.data_path()
|
703
738
|
)
|
739
|
+
|
740
|
+
def cancel(self):
|
741
|
+
"""
|
742
|
+
stop current processing
|
743
|
+
"""
|
744
|
+
if self._current_processing is not None:
|
745
|
+
self._cancelled = True
|
746
|
+
self._current_processing.cancel()
|
747
|
+
|
748
|
+
|
749
|
+
class _PostProcessing:
|
750
|
+
"""class used to run SA-axis post-processing on reconstructed slices"""
|
751
|
+
|
752
|
+
def __init__(self, cor_reconstructions, slice_index, scan) -> None:
|
753
|
+
self._cor_reconstructions = cor_reconstructions
|
754
|
+
self._slice_index = slice_index
|
755
|
+
self._scan = scan
|
756
|
+
self._cancelled = False
|
757
|
+
|
758
|
+
def run(self):
|
759
|
+
datasets = self.load_datasets()
|
760
|
+
|
761
|
+
mask_disk_radius = get_disk_mask_radius(datasets)
|
762
|
+
scores = {}
|
763
|
+
rois = {}
|
764
|
+
for cor, (url, data) in datasets.items():
|
765
|
+
if self._cancelled:
|
766
|
+
break
|
767
|
+
|
768
|
+
if data is None:
|
769
|
+
score = None
|
770
|
+
else:
|
771
|
+
assert data.ndim == 2
|
772
|
+
data_roi = apply_roi(data=data, radius=mask_disk_radius, url=url)
|
773
|
+
rois[cor] = data_roi
|
774
|
+
|
775
|
+
# move data_roi to [0-1] range
|
776
|
+
# preprocessing: get percentile 0 and 99 from image and
|
777
|
+
# "clean" highest and lowest pixels from it
|
778
|
+
min_p, max_p = numpy.percentile(data_roi, (1, 99))
|
779
|
+
data_roi_int = data_roi[...]
|
780
|
+
data_roi_int[data_roi_int < min_p] = min_p
|
781
|
+
data_roi_int[data_roi_int > max_p] = max_p
|
782
|
+
data_roi_int = (data_roi_int - min_p) / (max_p - min_p)
|
783
|
+
|
784
|
+
if isinstance(self._scan, EDFTomoScan):
|
785
|
+
_logger.info("tomo consistency is not handled for EDF scan")
|
786
|
+
tomo_consistency_score = None
|
787
|
+
else:
|
788
|
+
try:
|
789
|
+
projections_with_angle = self._scan.projections_with_angle()
|
790
|
+
angles_ = [
|
791
|
+
frame_angle
|
792
|
+
for frame_angle, frame in projections_with_angle.items()
|
793
|
+
]
|
794
|
+
angles = []
|
795
|
+
for angle in angles_:
|
796
|
+
if not isinstance(angle, str):
|
797
|
+
angles.append(angle)
|
798
|
+
if self._slice_index == "middle":
|
799
|
+
if self._scan.dim_2 is not None:
|
800
|
+
self._slice_index = self._scan.dim_2 // 2
|
801
|
+
else:
|
802
|
+
_logger.warning(
|
803
|
+
"scan.dim_2 returns None, unable to deduce middle "
|
804
|
+
"pick 1024"
|
805
|
+
)
|
806
|
+
self._slice_index = 1024
|
807
|
+
tomo_consistency_score = compute_score(
|
808
|
+
data=data,
|
809
|
+
method=ScoreMethod.TOMO_CONSISTENCY,
|
810
|
+
angles=angles,
|
811
|
+
original_sinogram=self._scan.get_sinogram(
|
812
|
+
self._slice_index
|
813
|
+
),
|
814
|
+
detector_width=self._scan.dim_1,
|
815
|
+
original_axis_position=cor + self._scan.dim_1 / 2.0,
|
816
|
+
)
|
817
|
+
except Exception as e:
|
818
|
+
_logger.error(e)
|
819
|
+
tomo_consistency_score = None
|
820
|
+
score = ComputedScore(
|
821
|
+
tv=compute_score(data=data_roi_int, method=ScoreMethod.TV),
|
822
|
+
std=compute_score(data=data_roi_int, method=ScoreMethod.STD),
|
823
|
+
tomo_consistency=tomo_consistency_score,
|
824
|
+
)
|
825
|
+
scores[cor] = (url, score)
|
826
|
+
return scores, rois
|
827
|
+
|
828
|
+
def load_datasets(self):
|
829
|
+
datasets_ = {}
|
830
|
+
for cor, volume_identifiers in self._cor_reconstructions.items():
|
831
|
+
if self._cancelled:
|
832
|
+
break
|
833
|
+
|
834
|
+
if len(volume_identifiers) == 0:
|
835
|
+
# in the case failed to load the url
|
836
|
+
continue
|
837
|
+
elif len(volume_identifiers) > 1:
|
838
|
+
raise ValueError("only one slice reconstructed expected per cor")
|
839
|
+
volume = VolumeFactory.create_tomo_object_from_identifier(
|
840
|
+
volume_identifiers[0]
|
841
|
+
)
|
842
|
+
urls = tuple(volume.browse_data_urls())
|
843
|
+
if len(urls) != 1:
|
844
|
+
raise ValueError(
|
845
|
+
f"volume is expected to have at most one url (single slice volume). get {len(urls)} - most likely nabu reconstruction failed. Do you have GPU ? Are the requested COR values valid ? - Especially for Half-acquisition"
|
846
|
+
)
|
847
|
+
url = urls[0]
|
848
|
+
if not isinstance(url, (DataUrl, str)):
|
849
|
+
raise TypeError(
|
850
|
+
f"url is expected to be a str or DataUrl not {type(url)}"
|
851
|
+
)
|
852
|
+
|
853
|
+
try:
|
854
|
+
data = get_slice_data(url=url)
|
855
|
+
except Exception as e:
|
856
|
+
_logger.error(
|
857
|
+
f"Fail to compute a score for {url.path()}. Reason is {e}"
|
858
|
+
)
|
859
|
+
datasets_[cor] = (url, None)
|
860
|
+
else:
|
861
|
+
if data.ndim == 3:
|
862
|
+
if data.shape[0] == 1:
|
863
|
+
data = data.reshape(data.shape[1], data.shape[2])
|
864
|
+
elif data.shape[2] == 1:
|
865
|
+
data = data.reshape(data.shape[0], data.shape[1])
|
866
|
+
else:
|
867
|
+
raise ValueError(f"Data is expected to be 2D. Not {data.ndim}D")
|
868
|
+
elif data.ndim == 2:
|
869
|
+
pass
|
870
|
+
else:
|
871
|
+
raise ValueError("Data is expected to be 2D. Not {data.ndim}D")
|
872
|
+
|
873
|
+
datasets_[cor] = (url, data)
|
874
|
+
return datasets_
|
875
|
+
|
876
|
+
def cancel(self):
|
877
|
+
self._cancelled = True
|
878
|
+
|
879
|
+
|
880
|
+
class SAAxisProcess(SAAxisTask):
|
881
|
+
def __init__(
|
882
|
+
self, process_id=None, inputs=None, varinfo=None, node_attrs=None, execinfo=None
|
883
|
+
):
|
884
|
+
deprecated_warning(
|
885
|
+
name="tomwer.core.process.reconstruction.saaxis.SAAxisProcess",
|
886
|
+
type_="class",
|
887
|
+
reason="improve readibility",
|
888
|
+
since_version="1.2",
|
889
|
+
replacement="SAAxisTask",
|
890
|
+
)
|
891
|
+
super().__init__(process_id, inputs, varinfo, node_attrs, execinfo)
|