nabu 2024.1.9__py3-none-any.whl → 2024.2.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.
- nabu/__init__.py +1 -1
- nabu/app/bootstrap.py +2 -3
- nabu/app/cast_volume.py +4 -2
- nabu/app/cli_configs.py +5 -0
- nabu/app/composite_cor.py +1 -1
- nabu/app/create_distortion_map_from_poly.py +5 -6
- nabu/app/diag_to_pix.py +7 -19
- nabu/app/diag_to_rot.py +14 -29
- nabu/app/double_flatfield.py +32 -44
- nabu/app/parse_reconstruction_log.py +3 -0
- nabu/app/reconstruct.py +53 -15
- nabu/app/reconstruct_helical.py +2 -2
- nabu/app/stitching.py +27 -13
- nabu/app/tests/test_reduce_dark_flat.py +4 -1
- nabu/cuda/kernel.py +11 -2
- nabu/cuda/processing.py +2 -2
- nabu/cuda/src/cone.cu +77 -0
- nabu/cuda/src/hierarchical_backproj.cu +271 -0
- nabu/cuda/utils.py +0 -6
- nabu/estimation/alignment.py +5 -19
- nabu/estimation/cor.py +173 -599
- nabu/estimation/cor_sino.py +356 -26
- nabu/estimation/focus.py +63 -11
- nabu/estimation/tests/test_cor.py +124 -58
- nabu/estimation/tests/test_focus.py +6 -6
- nabu/estimation/tilt.py +2 -1
- nabu/estimation/utils.py +5 -33
- nabu/io/__init__.py +1 -1
- nabu/io/cast_volume.py +1 -1
- nabu/io/reader.py +416 -21
- nabu/io/tests/test_readers.py +422 -0
- nabu/io/tests/test_writers.py +1 -102
- nabu/io/writer.py +4 -433
- nabu/opencl/kernel.py +14 -3
- nabu/opencl/processing.py +8 -0
- nabu/pipeline/config_validators.py +5 -2
- nabu/pipeline/datadump.py +12 -5
- nabu/pipeline/estimators.py +162 -188
- nabu/pipeline/fullfield/chunked.py +168 -92
- nabu/pipeline/fullfield/chunked_cuda.py +7 -3
- nabu/pipeline/fullfield/computations.py +2 -7
- nabu/pipeline/fullfield/dataset_validator.py +0 -4
- nabu/pipeline/fullfield/nabu_config.py +37 -13
- nabu/pipeline/fullfield/processconfig.py +22 -13
- nabu/pipeline/fullfield/reconstruction.py +13 -9
- nabu/pipeline/helical/helical_chunked_regridded.py +1 -1
- nabu/pipeline/helical/helical_chunked_regridded_cuda.py +1 -0
- nabu/pipeline/helical/helical_reconstruction.py +1 -1
- nabu/pipeline/params.py +21 -1
- nabu/pipeline/processconfig.py +1 -12
- nabu/pipeline/reader.py +146 -0
- nabu/pipeline/tests/test_estimators.py +44 -72
- nabu/pipeline/utils.py +4 -2
- nabu/pipeline/writer.py +10 -2
- nabu/preproc/ccd_cuda.py +1 -1
- nabu/preproc/ctf.py +14 -7
- nabu/preproc/ctf_cuda.py +2 -3
- nabu/preproc/double_flatfield.py +5 -12
- nabu/preproc/double_flatfield_cuda.py +2 -2
- nabu/preproc/flatfield.py +5 -1
- nabu/preproc/flatfield_cuda.py +5 -1
- nabu/preproc/phase.py +24 -73
- nabu/preproc/phase_cuda.py +5 -8
- nabu/preproc/tests/test_ctf.py +11 -7
- nabu/preproc/tests/test_flatfield.py +67 -122
- nabu/preproc/tests/test_paganin.py +54 -30
- nabu/processing/azim.py +206 -0
- nabu/processing/convolution_cuda.py +1 -1
- nabu/processing/fft_cuda.py +15 -17
- nabu/processing/histogram.py +2 -0
- nabu/processing/histogram_cuda.py +2 -1
- nabu/processing/kernel_base.py +3 -0
- nabu/processing/muladd_cuda.py +1 -0
- nabu/processing/padding_opencl.py +1 -1
- nabu/processing/roll_opencl.py +1 -0
- nabu/processing/rotation_cuda.py +2 -2
- nabu/processing/tests/test_fft.py +17 -10
- nabu/processing/unsharp_cuda.py +1 -1
- nabu/reconstruction/cone.py +104 -40
- nabu/reconstruction/fbp.py +3 -0
- nabu/reconstruction/fbp_base.py +7 -2
- nabu/reconstruction/filtering.py +20 -7
- nabu/reconstruction/filtering_cuda.py +7 -1
- nabu/reconstruction/hbp.py +424 -0
- nabu/reconstruction/mlem.py +99 -0
- nabu/reconstruction/reconstructor.py +2 -0
- nabu/reconstruction/rings_cuda.py +19 -19
- nabu/reconstruction/sinogram_cuda.py +1 -0
- nabu/reconstruction/sinogram_opencl.py +3 -1
- nabu/reconstruction/tests/test_cone.py +10 -5
- nabu/reconstruction/tests/test_deringer.py +7 -6
- nabu/reconstruction/tests/test_fbp.py +124 -10
- nabu/reconstruction/tests/test_filtering.py +13 -11
- nabu/reconstruction/tests/test_halftomo.py +30 -4
- nabu/reconstruction/tests/test_mlem.py +91 -0
- nabu/reconstruction/tests/test_reconstructor.py +8 -3
- nabu/resources/dataset_analyzer.py +142 -92
- nabu/resources/gpu.py +1 -0
- nabu/resources/nxflatfield.py +134 -125
- nabu/resources/templates/id16a_fluo.conf +42 -0
- nabu/resources/tests/test_extract.py +10 -0
- nabu/resources/tests/test_nxflatfield.py +2 -2
- nabu/stitching/alignment.py +80 -24
- nabu/stitching/config.py +105 -68
- nabu/stitching/definitions.py +1 -0
- nabu/stitching/frame_composition.py +68 -60
- nabu/stitching/overlap.py +91 -51
- nabu/stitching/single_axis_stitching.py +32 -0
- nabu/stitching/slurm_utils.py +6 -6
- nabu/stitching/stitcher/__init__.py +0 -0
- nabu/stitching/stitcher/base.py +124 -0
- nabu/stitching/stitcher/dumper/__init__.py +3 -0
- nabu/stitching/stitcher/dumper/base.py +94 -0
- nabu/stitching/stitcher/dumper/postprocessing.py +356 -0
- nabu/stitching/stitcher/dumper/preprocessing.py +60 -0
- nabu/stitching/stitcher/post_processing.py +555 -0
- nabu/stitching/stitcher/pre_processing.py +1068 -0
- nabu/stitching/stitcher/single_axis.py +484 -0
- nabu/stitching/stitcher/stitcher.py +0 -0
- nabu/stitching/stitcher/y_stitcher.py +13 -0
- nabu/stitching/stitcher/z_stitcher.py +45 -0
- nabu/stitching/stitcher_2D.py +278 -0
- nabu/stitching/tests/test_config.py +12 -37
- nabu/stitching/tests/test_frame_composition.py +33 -59
- nabu/stitching/tests/test_overlap.py +149 -7
- nabu/stitching/tests/test_utils.py +1 -1
- nabu/stitching/tests/test_y_preprocessing_stitching.py +132 -0
- nabu/stitching/tests/{test_z_stitching.py → test_z_postprocessing_stitching.py} +167 -561
- nabu/stitching/tests/test_z_preprocessing_stitching.py +431 -0
- nabu/stitching/utils/__init__.py +1 -0
- nabu/stitching/utils/post_processing.py +281 -0
- nabu/stitching/utils/tests/test_post-processing.py +21 -0
- nabu/stitching/{utils.py → utils/utils.py} +79 -52
- nabu/stitching/y_stitching.py +27 -0
- nabu/stitching/z_stitching.py +32 -2263
- nabu/testutils.py +1 -152
- nabu/thirdparty/tomocupy_remove_stripe.py +43 -9
- nabu/utils.py +158 -61
- {nabu-2024.1.9.dist-info → nabu-2024.2.0.dist-info}/METADATA +10 -3
- {nabu-2024.1.9.dist-info → nabu-2024.2.0.dist-info}/RECORD +144 -121
- nabu/io/tiffwriter_zmm.py +0 -99
- nabu/pipeline/fallback_utils.py +0 -149
- nabu/pipeline/helical/tests/test_accumulator.py +0 -158
- nabu/pipeline/helical/tests/test_pipeline_elements_full.py +0 -355
- nabu/pipeline/helical/tests/test_strategy.py +0 -61
- nabu/pipeline/helical/utils.py +0 -51
- nabu/pipeline/tests/test_chunk_reader.py +0 -74
- {nabu-2024.1.9.dist-info → nabu-2024.2.0.dist-info}/LICENSE +0 -0
- {nabu-2024.1.9.dist-info → nabu-2024.2.0.dist-info}/WHEEL +0 -0
- {nabu-2024.1.9.dist-info → nabu-2024.2.0.dist-info}/entry_points.txt +0 -0
- {nabu-2024.1.9.dist-info → nabu-2024.2.0.dist-info}/top_level.txt +0 -0
@@ -1,36 +1,33 @@
|
|
1
|
-
from copy import copy
|
2
1
|
from dataclasses import dataclass
|
3
2
|
import numpy
|
4
3
|
from math import ceil
|
5
4
|
|
6
|
-
from nabu.stitching.overlap import ZStichOverlapKernel
|
7
|
-
|
8
5
|
|
9
6
|
@dataclass
|
10
|
-
class
|
11
|
-
def compose(self, output_frame: numpy.ndarray, input_frames: tuple):
|
12
|
-
raise NotImplementedError("Base class")
|
13
|
-
|
14
|
-
|
15
|
-
@dataclass
|
16
|
-
class ZFrameComposition(_FrameCompositionBase):
|
7
|
+
class FrameComposition:
|
17
8
|
"""
|
18
9
|
class used to define intervals to know where to dump raw data or stitched data according to requested policy.
|
19
10
|
The idea is to create this once for all for one stitching operation and reuse it for each frame.
|
20
11
|
"""
|
21
12
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
13
|
+
composed_axis: int
|
14
|
+
"""axis along which the composition is done"""
|
15
|
+
local_start: tuple
|
16
|
+
"""tuple of indices on the input frames ref to know where each region start (along the composed axis)"""
|
17
|
+
local_end: tuple
|
18
|
+
"""tuple of indices on the input frames ref to know where each region end (along the composed axis)"""
|
19
|
+
global_start: tuple
|
20
|
+
"""tuple of indices on the output frame ref to know where each region start (along the composed axis)"""
|
21
|
+
global_end: tuple
|
22
|
+
"""tuple of indices on the output frame ref to know where each region end (along the composed axis)"""
|
26
23
|
|
27
24
|
def browse(self):
|
28
|
-
for i in range(len(self.
|
25
|
+
for i in range(len(self.local_start)):
|
29
26
|
yield (
|
30
|
-
self.
|
31
|
-
self.
|
32
|
-
self.
|
33
|
-
self.
|
27
|
+
self.local_start[i],
|
28
|
+
self.local_end[i],
|
29
|
+
self.global_start[i],
|
30
|
+
self.global_end[i],
|
34
31
|
)
|
35
32
|
|
36
33
|
def compose(self, output_frame: numpy.ndarray, input_frames: tuple):
|
@@ -39,20 +36,25 @@ class ZFrameComposition(_FrameCompositionBase):
|
|
39
36
|
f"output_frame is expected to be 2D (gray scale) or 3D (RGB(A)) and not {output_frame.ndim}"
|
40
37
|
)
|
41
38
|
for (
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
39
|
+
global_start,
|
40
|
+
global_end,
|
41
|
+
local_start,
|
42
|
+
local_end,
|
46
43
|
input_frame,
|
47
44
|
) in zip(
|
48
|
-
self.
|
49
|
-
self.
|
50
|
-
self.
|
51
|
-
self.
|
45
|
+
self.global_start,
|
46
|
+
self.global_end,
|
47
|
+
self.local_start,
|
48
|
+
self.local_end,
|
52
49
|
input_frames,
|
53
50
|
):
|
54
51
|
if input_frame is not None:
|
55
|
-
|
52
|
+
if self.composed_axis == 0:
|
53
|
+
output_frame[global_start:global_end] = input_frame[local_start:local_end]
|
54
|
+
elif self.composed_axis == 1:
|
55
|
+
output_frame[:, global_start:global_end] = input_frame[:, local_start:local_end]
|
56
|
+
else:
|
57
|
+
raise ValueError(f"composed axis must be in (0, 1). Get {self.composed_axis}")
|
56
58
|
|
57
59
|
@staticmethod
|
58
60
|
def compute_raw_frame_compositions(frames: tuple, key_lines: tuple, overlap_kernels: tuple, stitching_axis):
|
@@ -63,37 +65,42 @@ class ZFrameComposition(_FrameCompositionBase):
|
|
63
65
|
"""
|
64
66
|
assert len(frames) == len(overlap_kernels) + 1 == len(key_lines) + 1
|
65
67
|
|
66
|
-
|
68
|
+
global_start_indices = [0]
|
67
69
|
|
68
70
|
# extend shifts and kernels to have a first shift of 0 and two overlaps values at 0 to
|
69
71
|
# generalize processing
|
70
|
-
|
72
|
+
local_start_indices = [0]
|
71
73
|
|
72
|
-
|
74
|
+
local_start_indices.extend(
|
73
75
|
[ceil(key_line[1] + kernel.overlap_size / 2) for (key_line, kernel) in zip(key_lines, overlap_kernels)]
|
74
76
|
)
|
75
|
-
|
77
|
+
local_end_indices = list(
|
76
78
|
[ceil(key_line[0] - kernel.overlap_size / 2) for (key_line, kernel) in zip(key_lines, overlap_kernels)]
|
77
79
|
)
|
78
|
-
|
80
|
+
local_end_indices.append(frames[-1].shape[stitching_axis])
|
79
81
|
|
80
82
|
for (
|
81
|
-
|
82
|
-
|
83
|
+
new_local_start_index,
|
84
|
+
new_local_end_index,
|
83
85
|
kernel,
|
84
|
-
) in zip(
|
85
|
-
|
86
|
+
) in zip(local_start_indices, local_end_indices, overlap_kernels):
|
87
|
+
global_start_indices.append(
|
88
|
+
global_start_indices[-1] + (new_local_end_index - new_local_start_index) + kernel.overlap_size
|
89
|
+
)
|
86
90
|
|
87
91
|
# global end can be easily found from global start + local start and end
|
88
|
-
|
89
|
-
for
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
92
|
+
global_end_indices = []
|
93
|
+
for global_start_index, new_local_start_index, new_local_end_index in zip(
|
94
|
+
global_start_indices, local_start_indices, local_end_indices
|
95
|
+
):
|
96
|
+
global_end_indices.append(global_start_index + new_local_end_index - new_local_start_index)
|
97
|
+
|
98
|
+
return FrameComposition(
|
99
|
+
composed_axis=stitching_axis,
|
100
|
+
local_start=tuple(local_start_indices),
|
101
|
+
local_end=tuple(local_end_indices),
|
102
|
+
global_start=tuple(global_start_indices),
|
103
|
+
global_end=tuple(global_end_indices),
|
97
104
|
)
|
98
105
|
|
99
106
|
@staticmethod
|
@@ -102,33 +109,34 @@ class ZFrameComposition(_FrameCompositionBase):
|
|
102
109
|
compute frame composition for stiching.
|
103
110
|
"""
|
104
111
|
assert len(frames) == len(overlap_kernels) + 1 == len(key_lines) + 1
|
105
|
-
assert stitching_axis in (0, 1
|
112
|
+
assert stitching_axis in (0, 1)
|
106
113
|
|
107
114
|
# position in the stitched frame;
|
108
|
-
|
109
|
-
|
115
|
+
local_start_indices = [0] * len(overlap_kernels)
|
116
|
+
local_end_indices = [kernel.overlap_size for kernel in overlap_kernels]
|
110
117
|
|
111
118
|
# position in the global frame. For this one it is simpler to rely on the raw frame composition
|
112
|
-
composition_raw =
|
119
|
+
composition_raw = FrameComposition.compute_raw_frame_compositions(
|
113
120
|
frames=frames,
|
114
121
|
key_lines=key_lines,
|
115
122
|
overlap_kernels=overlap_kernels,
|
116
123
|
stitching_axis=stitching_axis,
|
117
124
|
)
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
return
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
125
|
+
global_start_indices = composition_raw.global_end[:-1]
|
126
|
+
global_end_indices = composition_raw.global_start[1:]
|
127
|
+
|
128
|
+
return FrameComposition(
|
129
|
+
composed_axis=stitching_axis,
|
130
|
+
local_start=tuple(local_start_indices),
|
131
|
+
local_end=tuple(local_end_indices),
|
132
|
+
global_start=tuple(global_start_indices),
|
133
|
+
global_end=tuple(global_end_indices),
|
126
134
|
)
|
127
135
|
|
128
136
|
@staticmethod
|
129
|
-
def
|
137
|
+
def pprint_composition(raw_composition, stitch_composition):
|
130
138
|
"""
|
131
|
-
util to display what the output of the
|
139
|
+
util to display what the output of the composition will looks like from composition
|
132
140
|
"""
|
133
141
|
for i_frame, (raw_comp, stitch_comp) in enumerate(zip(raw_composition.browse(), stitch_composition.browse())):
|
134
142
|
raw_local_start, raw_local_end, raw_global_start, raw_global_end = raw_comp
|
nabu/stitching/overlap.py
CHANGED
@@ -1,33 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
# /*##########################################################################
|
3
|
-
#
|
4
|
-
# Copyright (c) 2016-2017 European Synchrotron Radiation Facility
|
5
|
-
#
|
6
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
-
# of this software and associated documentation files (the "Software"), to deal
|
8
|
-
# in the Software without restriction, including without limitation the rights
|
9
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
-
# copies of the Software, and to permit persons to whom the Software is
|
11
|
-
# furnished to do so, subject to the following conditions:
|
12
|
-
#
|
13
|
-
# The above copyright notice and this permission notice shall be included in
|
14
|
-
# all copies or substantial portions of the Software.
|
15
|
-
#
|
16
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
-
# THE SOFTWARE.
|
23
|
-
#
|
24
|
-
# ###########################################################################*/
|
25
|
-
|
26
|
-
__authors__ = ["H. Payno"]
|
27
|
-
__license__ = "MIT"
|
28
|
-
__date__ = "10/05/2022"
|
29
|
-
|
30
|
-
|
31
1
|
import numpy
|
32
2
|
import logging
|
33
3
|
from typing import Optional, Union
|
@@ -59,34 +29,49 @@ class OverlapKernelBase:
|
|
59
29
|
pass
|
60
30
|
|
61
31
|
|
62
|
-
class
|
32
|
+
class ImageStichOverlapKernel(OverlapKernelBase):
|
63
33
|
"""
|
64
|
-
|
34
|
+
Stitch two images along Y (axis 0 in image space)
|
65
35
|
"""
|
66
36
|
|
67
37
|
DEFAULT_HIGH_FREQUENCY_THRESHOLD = 2
|
68
38
|
|
69
39
|
def __init__(
|
70
40
|
self,
|
71
|
-
|
41
|
+
stitching_axis: int,
|
42
|
+
frame_unstitched_axis_size: tuple,
|
72
43
|
stitching_strategy: OverlapStitchingStrategy = DEFAULT_OVERLAP_STRATEGY,
|
73
44
|
overlap_size: int = DEFAULT_OVERLAP_SIZE,
|
74
45
|
extra_params: Optional[dict] = None,
|
75
46
|
) -> None:
|
76
|
-
"""
|
47
|
+
"""
|
48
|
+
:param stitching_axis: axis along which stitching is operate. Must be in '0', '1'
|
49
|
+
:param frame_unstitched_axis_size: according to the stitching axis the stitched framed will always have a constant size:
|
50
|
+
* If stitching_axis == 0 then it will be the frame width
|
51
|
+
* If stitching_axis == 1 then it will be the frame height
|
52
|
+
:param stitching_strategy: stategy / algorithm to use in order to generate the stitching
|
53
|
+
:param overlap_size: size (int) of the overlap (stitching) between the two images
|
54
|
+
:param extra_params: possibly extra parameters to operate the stitching
|
55
|
+
"""
|
77
56
|
from nabu.stitching.config import KEY_THRESHOLD_FREQUENCY # avoid acylic import
|
78
57
|
|
79
58
|
if not isinstance(overlap_size, int) and overlap_size > 0:
|
80
59
|
raise TypeError(
|
81
60
|
f"overlap_size is expected to be a positive int, {overlap_size} - not {overlap_size} ({type(overlap_size)})"
|
82
61
|
)
|
83
|
-
if not isinstance(
|
62
|
+
if not isinstance(frame_unstitched_axis_size, int) or not frame_unstitched_axis_size > 0:
|
84
63
|
raise TypeError(
|
85
|
-
f"frame_width is expected to be a positive int, {
|
64
|
+
f"frame_width is expected to be a positive int, {frame_unstitched_axis_size} - not {frame_unstitched_axis_size} ({type(frame_unstitched_axis_size)})"
|
65
|
+
)
|
66
|
+
|
67
|
+
if not stitching_axis in (0, 1):
|
68
|
+
raise ValueError(
|
69
|
+
"stitching_axis is expected to be the axis along which stitching must be done. It should be '0' or '1'"
|
86
70
|
)
|
87
71
|
|
72
|
+
self._stitching_axis = stitching_axis
|
88
73
|
self._overlap_size = abs(overlap_size)
|
89
|
-
self.
|
74
|
+
self._frame_unstitched_axis_size = frame_unstitched_axis_size
|
90
75
|
self._stitching_strategy = OverlapStitchingStrategy.from_value(stitching_strategy)
|
91
76
|
self._weights_img_1 = None
|
92
77
|
self._weights_img_2 = None
|
@@ -97,13 +82,28 @@ class ZStichOverlapKernel(OverlapKernelBase):
|
|
97
82
|
)
|
98
83
|
|
99
84
|
def __str__(self) -> str:
|
100
|
-
return f"z-stitching kernel (policy={self.stitching_strategy.value}, overlap_size={self.overlap_size}, frame={self.
|
85
|
+
return f"z-stitching kernel (policy={self.stitching_strategy.value}, overlap_size={self.overlap_size}, frame={self._frame_unstitched_axis_size})"
|
101
86
|
|
102
87
|
@staticmethod
|
103
88
|
def __check_img(img, name):
|
104
89
|
if not isinstance(img, numpy.ndarray) and img.ndim == 2:
|
105
90
|
raise ValueError(f"{name} is expected to be 2D numpy array")
|
106
91
|
|
92
|
+
@property
|
93
|
+
def stitched_axis(self) -> int:
|
94
|
+
return self._stitching_axis
|
95
|
+
|
96
|
+
@property
|
97
|
+
def unstitched_axis(self) -> int:
|
98
|
+
"""
|
99
|
+
util function. The kernel is operating stitching on images along a single axis (`stitching_axis`).
|
100
|
+
This property is returning the other axis.
|
101
|
+
"""
|
102
|
+
if self.stitched_axis == 0:
|
103
|
+
return 1
|
104
|
+
else:
|
105
|
+
return 0
|
106
|
+
|
107
107
|
@property
|
108
108
|
def overlap_size(self) -> int:
|
109
109
|
return self._overlap_size
|
@@ -169,8 +169,22 @@ class ZStichOverlapKernel(OverlapKernelBase):
|
|
169
169
|
else:
|
170
170
|
raise NotImplementedError(f"{self.stitching_strategy} not implemented")
|
171
171
|
|
172
|
-
self.
|
173
|
-
|
172
|
+
if self._stitching_axis == 0:
|
173
|
+
self._weights_img_1 = weights_img_1.reshape(-1, 1) * numpy.ones(self._frame_unstitched_axis_size).reshape(
|
174
|
+
1, -1
|
175
|
+
)
|
176
|
+
self._weights_img_2 = weights_img_2.reshape(-1, 1) * numpy.ones(self._frame_unstitched_axis_size).reshape(
|
177
|
+
1, -1
|
178
|
+
)
|
179
|
+
elif self._stitching_axis == 1:
|
180
|
+
self._weights_img_1 = weights_img_1.reshape(1, -1) * numpy.ones(self._frame_unstitched_axis_size).reshape(
|
181
|
+
-1, 1
|
182
|
+
)
|
183
|
+
self._weights_img_2 = weights_img_2.reshape(1, -1) * numpy.ones(self._frame_unstitched_axis_size).reshape(
|
184
|
+
-1, 1
|
185
|
+
)
|
186
|
+
else:
|
187
|
+
raise ValueError(f"stitching_axis should be in (0, 1). {self._stitching_axis} provided")
|
174
188
|
|
175
189
|
def stitch(self, img_1, img_2, check_input=True) -> tuple:
|
176
190
|
"""Compute overlap region from the defined strategy"""
|
@@ -186,14 +200,20 @@ class ZStichOverlapKernel(OverlapKernelBase):
|
|
186
200
|
if self._stitching_strategy is OverlapStitchingStrategy.IMAGE_MINIMUM_DIVERGENCE:
|
187
201
|
return (
|
188
202
|
compute_image_minimum_divergence(
|
189
|
-
img_1=img_1,
|
203
|
+
img_1=img_1,
|
204
|
+
img_2=img_2,
|
205
|
+
high_frequency_threshold=self._high_frequency_threshold,
|
206
|
+
stitching_axis=self.stitched_axis,
|
190
207
|
),
|
191
208
|
None,
|
192
209
|
None,
|
193
210
|
)
|
194
211
|
elif self._stitching_strategy is OverlapStitchingStrategy.HIGHER_SIGNAL:
|
195
212
|
return (
|
196
|
-
compute_image_higher_signal(
|
213
|
+
compute_image_higher_signal(
|
214
|
+
img_1=img_1,
|
215
|
+
img_2=img_2,
|
216
|
+
),
|
197
217
|
None,
|
198
218
|
None,
|
199
219
|
)
|
@@ -208,7 +228,9 @@ class ZStichOverlapKernel(OverlapKernelBase):
|
|
208
228
|
)
|
209
229
|
|
210
230
|
|
211
|
-
def compute_image_minimum_divergence(
|
231
|
+
def compute_image_minimum_divergence(
|
232
|
+
img_1: numpy.ndarray, img_2: numpy.ndarray, high_frequency_threshold, stitching_axis: int
|
233
|
+
):
|
212
234
|
"""
|
213
235
|
Algorithm to improve treatment of high frequency.
|
214
236
|
|
@@ -242,10 +264,20 @@ def compute_image_minimum_divergence(img_1: numpy.ndarray, img_2: numpy.ndarray,
|
|
242
264
|
low_freq_img_2, high_freq_img_2 = split_image(img_2, threshold=high_frequency_threshold)
|
243
265
|
|
244
266
|
# handle low frequency
|
245
|
-
|
246
|
-
|
267
|
+
if stitching_axis == 0:
|
268
|
+
frame_cst_size = img_1.shape[1]
|
269
|
+
overlap_size = img_1.shape[0]
|
270
|
+
elif stitching_axis == 1:
|
271
|
+
frame_cst_size = img_1.shape[0]
|
272
|
+
overlap_size = img_1.shape[1]
|
273
|
+
else:
|
274
|
+
raise ValueError("")
|
275
|
+
|
276
|
+
low_freq_stitching_kernel = ImageStichOverlapKernel(
|
277
|
+
frame_unstitched_axis_size=frame_cst_size,
|
247
278
|
stitching_strategy=OverlapStitchingStrategy.COSINUS_WEIGHTS,
|
248
|
-
overlap_size=
|
279
|
+
overlap_size=overlap_size,
|
280
|
+
stitching_axis=stitching_axis,
|
249
281
|
)
|
250
282
|
low_freq_stitched = low_freq_stitching_kernel.stitch(
|
251
283
|
img_1=low_freq_img_1,
|
@@ -292,8 +324,8 @@ def check_overlaps(frames: Union[tuple, numpy.ndarray], positions: tuple, axis:
|
|
292
324
|
"""
|
293
325
|
if not isinstance(frames, (tuple, numpy.ndarray)):
|
294
326
|
raise TypeError(f"frames is expected to be a tuple or a numpy array. Get {type(frames)} instead")
|
295
|
-
if not isinstance(positions, tuple):
|
296
|
-
raise TypeError(f"positions is expected to be a tuple. Get {type(positions)} instead")
|
327
|
+
if not isinstance(positions, tuple) and len(positions) == 3:
|
328
|
+
raise TypeError(f"positions is expected to be a tuple of 3 elements. Get {type(positions)} instead")
|
297
329
|
assert isinstance(axis, int), "axis is expected to be an int"
|
298
330
|
assert isinstance(raise_error, bool), "raise_error is expected to be a bool"
|
299
331
|
|
@@ -301,14 +333,22 @@ def check_overlaps(frames: Union[tuple, numpy.ndarray], positions: tuple, axis:
|
|
301
333
|
if raise_error:
|
302
334
|
raise ValueError(error_msg)
|
303
335
|
else:
|
304
|
-
_logger.error(
|
336
|
+
_logger.error(raise_error)
|
337
|
+
|
338
|
+
if axis == 0:
|
339
|
+
axis_frame_space = 0
|
340
|
+
elif axis == 2:
|
341
|
+
raise NotImplementedError(f"overlap check along axis {axis_frame_space}")
|
342
|
+
elif axis == 1:
|
343
|
+
axis_frame_space = 1
|
305
344
|
|
306
345
|
# convert each frame to appropriate bounding box according to the axis
|
307
346
|
def convert_to_bb(frame: numpy.ndarray, position: tuple, axis: int):
|
308
347
|
assert isinstance(axis, int)
|
309
348
|
assert isinstance(position, tuple), f"position expected a tuple. Get {type(position)} instead"
|
310
|
-
|
311
|
-
|
349
|
+
assert len(position) == 3, f"Expect to have three items for the position. Get {len(position)}"
|
350
|
+
start_frame = position[axis] - frame.shape[axis_frame_space] // 2
|
351
|
+
end_frame = start_frame + frame.shape[axis_frame_space]
|
312
352
|
return BoundingBox1D(start_frame, end_frame)
|
313
353
|
|
314
354
|
bounding_boxes = {
|
@@ -0,0 +1,32 @@
|
|
1
|
+
from .y_stitching import y_stitching
|
2
|
+
from .z_stitching import z_stitching
|
3
|
+
from tomoscan.identifier import BaseIdentifier
|
4
|
+
from nabu.stitching.config import (
|
5
|
+
SingleAxisStitchingConfiguration,
|
6
|
+
PreProcessedYStitchingConfiguration,
|
7
|
+
PreProcessedZStitchingConfiguration,
|
8
|
+
PostProcessedZStitchingConfiguration,
|
9
|
+
)
|
10
|
+
|
11
|
+
|
12
|
+
def stitching(configuration: SingleAxisStitchingConfiguration, progress=None) -> BaseIdentifier:
|
13
|
+
"""
|
14
|
+
Apply stitching from provided configuration.
|
15
|
+
Stitching will be applied along a single axis at the moment.
|
16
|
+
|
17
|
+
like:
|
18
|
+
axis 0
|
19
|
+
^
|
20
|
+
|
|
21
|
+
x-ray |
|
22
|
+
--------> ------> axis 2
|
23
|
+
/
|
24
|
+
/
|
25
|
+
axis 1
|
26
|
+
"""
|
27
|
+
if isinstance(configuration, (PreProcessedYStitchingConfiguration,)):
|
28
|
+
return y_stitching(configuration=configuration, progress=progress)
|
29
|
+
elif isinstance(configuration, (PreProcessedZStitchingConfiguration, PostProcessedZStitchingConfiguration)):
|
30
|
+
return z_stitching(configuration=configuration, progress=progress)
|
31
|
+
else:
|
32
|
+
raise NotImplementedError(f"configuration type ({type(configuration)}) is not handled")
|
nabu/stitching/slurm_utils.py
CHANGED
@@ -13,8 +13,8 @@ from ..pipeline.config import generate_nabu_configfile
|
|
13
13
|
from .config import (
|
14
14
|
StitchingConfiguration,
|
15
15
|
get_default_stitching_config,
|
16
|
-
|
17
|
-
|
16
|
+
PreProcessedSingleAxisStitchingConfiguration,
|
17
|
+
PostProcessedSingleAxisStitchingConfiguration,
|
18
18
|
SLURM_SECTION,
|
19
19
|
)
|
20
20
|
|
@@ -64,11 +64,11 @@ def split_stitching_configuration_to_slurm_job(
|
|
64
64
|
configuration.settle_inputs()
|
65
65
|
|
66
66
|
slice_sub_parts = split_slices(slices=configuration.slices, n_parts=n_jobs)
|
67
|
-
if isinstance(configuration,
|
67
|
+
if isinstance(configuration, PreProcessedSingleAxisStitchingConfiguration):
|
68
68
|
stitch_prefix = os.path.basename(os.path.splitext(configuration.output_file_path)[0])
|
69
69
|
configuration.output_file_path = os.path.abspath(configuration.output_file_path)
|
70
70
|
|
71
|
-
elif isinstance(configuration,
|
71
|
+
elif isinstance(configuration, PostProcessedSingleAxisStitchingConfiguration):
|
72
72
|
stitch_prefix = os.path.basename(os.path.splitext(configuration.output_volume.file_path)[0])
|
73
73
|
configuration.output_volume.file_path = os.path.abspath(configuration.output_volume.file_path)
|
74
74
|
else:
|
@@ -81,7 +81,7 @@ def split_stitching_configuration_to_slurm_job(
|
|
81
81
|
# remove slurm configuration because once on the partition we run it manually
|
82
82
|
sub_configuration.slurm_config = None
|
83
83
|
|
84
|
-
if isinstance(sub_configuration,
|
84
|
+
if isinstance(sub_configuration, PreProcessedSingleAxisStitchingConfiguration):
|
85
85
|
original_output_file_path, file_extension = os.path.splitext(sub_configuration.output_file_path)
|
86
86
|
sub_configuration.output_file_path = os.path.join(
|
87
87
|
original_output_file_path,
|
@@ -91,7 +91,7 @@ def split_stitching_configuration_to_slurm_job(
|
|
91
91
|
scan=sub_configuration.output_file_path,
|
92
92
|
entry=sub_configuration.output_data_path,
|
93
93
|
)
|
94
|
-
elif isinstance(sub_configuration,
|
94
|
+
elif isinstance(sub_configuration, PostProcessedSingleAxisStitchingConfiguration):
|
95
95
|
if isinstance(sub_configuration.output_volume, (HDF5Volume, MultiTIFFVolume)):
|
96
96
|
original_output_file_path, file_extension = os.path.splitext(sub_configuration.output_volume.file_path)
|
97
97
|
sub_configuration.output_volume.file_path = os.path.join(
|
File without changes
|
@@ -0,0 +1,124 @@
|
|
1
|
+
from copy import copy
|
2
|
+
from typing import Union
|
3
|
+
from nabu.stitching.config import SingleAxisStitchingConfiguration
|
4
|
+
from tomoscan.esrf import NXtomoScan
|
5
|
+
from tomoscan.volumebase import VolumeBase
|
6
|
+
from tomoscan.identifier import BaseIdentifier
|
7
|
+
|
8
|
+
|
9
|
+
def get_obj_constant_side_length(obj: Union[NXtomoScan, VolumeBase], axis: int) -> int:
|
10
|
+
"""
|
11
|
+
return tomo object lenght that will be constant over 1D stitching.
|
12
|
+
In the case of a stitching along axis 0 this will be:
|
13
|
+
* the projection width for pre-processing
|
14
|
+
* volume.shape[2] for post-processing
|
15
|
+
|
16
|
+
In the case of a stitching along axis 1 this will be:
|
17
|
+
* the projection height for pre-processing
|
18
|
+
"""
|
19
|
+
if isinstance(obj, NXtomoScan):
|
20
|
+
if axis == 0:
|
21
|
+
return obj.dim_1
|
22
|
+
elif axis in (1, 2):
|
23
|
+
return obj.dim_2
|
24
|
+
elif isinstance(obj, VolumeBase) and axis == 0:
|
25
|
+
return obj.get_volume_shape()[-1]
|
26
|
+
else:
|
27
|
+
raise TypeError(f"obj type ({type(obj)}) and axis == {axis} is not handled")
|
28
|
+
|
29
|
+
|
30
|
+
class _StitcherBase:
|
31
|
+
"""
|
32
|
+
Any stitcher base class
|
33
|
+
"""
|
34
|
+
|
35
|
+
def __init__(self, configuration, progress=None) -> None:
|
36
|
+
if not isinstance(configuration, SingleAxisStitchingConfiguration):
|
37
|
+
raise TypeError
|
38
|
+
|
39
|
+
# flag to check if the serie has been ordered yet or not
|
40
|
+
self._configuration = copy(configuration)
|
41
|
+
# copy configuration because we will edit it
|
42
|
+
self._frame_composition = None
|
43
|
+
self._progress = progress
|
44
|
+
self._overlap_kernels = []
|
45
|
+
# kernels to create the stitching on overlaps.
|
46
|
+
|
47
|
+
@property
|
48
|
+
def serie_label(self) -> str:
|
49
|
+
"""return serie name for logs"""
|
50
|
+
raise NotImplementedError("Base class")
|
51
|
+
|
52
|
+
@property
|
53
|
+
def reading_orders(self):
|
54
|
+
"""
|
55
|
+
as scan can be take on one direction or the order (rotation goes from X to Y then from Y to X)
|
56
|
+
we might need to read data from one direction or another
|
57
|
+
"""
|
58
|
+
return self._reading_orders
|
59
|
+
|
60
|
+
def order_input_tomo_objects(self):
|
61
|
+
"""
|
62
|
+
order inputs tomo objects
|
63
|
+
"""
|
64
|
+
raise NotImplementedError("Base class")
|
65
|
+
|
66
|
+
def check_inputs(self):
|
67
|
+
"""
|
68
|
+
order inputs tomo objects
|
69
|
+
"""
|
70
|
+
raise NotImplementedError("Base class")
|
71
|
+
|
72
|
+
def pre_processing_computation(self):
|
73
|
+
"""
|
74
|
+
some specific pre-processing that can be call before retrieving the data
|
75
|
+
"""
|
76
|
+
pass
|
77
|
+
|
78
|
+
@staticmethod
|
79
|
+
def param_is_auto(param):
|
80
|
+
return param in ("auto", ("auto",))
|
81
|
+
|
82
|
+
def stitch(self, store_composition: bool = True) -> BaseIdentifier:
|
83
|
+
"""
|
84
|
+
Apply expected stitch from configuration and return the DataUrl of the object created
|
85
|
+
|
86
|
+
:param bool store_composition: if True then store the composition used for stitching in frame_composition.
|
87
|
+
So it can be reused by third part (like tomwer) to display composition made
|
88
|
+
"""
|
89
|
+
raise NotImplementedError("base class")
|
90
|
+
|
91
|
+
@property
|
92
|
+
def frame_composition(self):
|
93
|
+
return self._frame_composition
|
94
|
+
|
95
|
+
@staticmethod
|
96
|
+
def from_abs_pos_to_rel_pos(abs_position: tuple):
|
97
|
+
"""
|
98
|
+
return relative position from on object to the other but in relative this time
|
99
|
+
:param tuple abs_position: tuple containing the absolute positions
|
100
|
+
:return: len(abs_position) - 1 relative position
|
101
|
+
:rtype: tuple
|
102
|
+
"""
|
103
|
+
return tuple([pos_obj_b - pos_obj_a for (pos_obj_a, pos_obj_b) in zip(abs_position[:-1], abs_position[1:])])
|
104
|
+
|
105
|
+
@staticmethod
|
106
|
+
def from_rel_pos_to_abs_pos(rel_positions: tuple, init_pos: int):
|
107
|
+
"""
|
108
|
+
return absolute positions from a tuple of relative position and an initial position
|
109
|
+
:param tuple rel_positions: tuple containing the absolute positions
|
110
|
+
:return: len(rel_positions) + 1 relative position
|
111
|
+
:rtype: tuple
|
112
|
+
"""
|
113
|
+
abs_pos = [
|
114
|
+
init_pos,
|
115
|
+
]
|
116
|
+
for rel_pos in rel_positions:
|
117
|
+
abs_pos.append(abs_pos[-1] + rel_pos)
|
118
|
+
return abs_pos
|
119
|
+
|
120
|
+
def _compute_shifts(self):
|
121
|
+
"""
|
122
|
+
after this stage the final shifts must be determine
|
123
|
+
"""
|
124
|
+
raise NotImplementedError("base class")
|