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,63 +1,20 @@
|
|
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__ = "13/05/2022"
|
29
|
-
|
30
|
-
|
31
1
|
import os
|
32
|
-
|
33
|
-
|
2
|
+
|
3
|
+
import h5py
|
34
4
|
import numpy
|
35
5
|
import pytest
|
36
|
-
from
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
from nabu.utils import Progress
|
41
|
-
from nabu.stitching.config import KEY_IMG_REG_METHOD, NormalizationBySample
|
42
|
-
from nabu.stitching.overlap import ZStichOverlapKernel, OverlapStitchingStrategy
|
43
|
-
from nabu.stitching.z_stitching import (
|
44
|
-
PostProcessZStitcher,
|
45
|
-
PreProcessZStitcher,
|
46
|
-
stitch_vertically_raw_frames,
|
47
|
-
ZStitcher,
|
48
|
-
)
|
49
|
-
from nxtomo.nxobject.nxdetector import ImageKey
|
50
|
-
from nxtomo.utils.transformation import UDDetTransformation, LRDetTransformation
|
51
|
-
from nxtomo.application.nxtomo import NXtomo
|
52
|
-
from nabu.stitching.alignment import AlignmentAxis1, AlignmentAxis2
|
6
|
+
from tqdm import tqdm
|
7
|
+
from silx.image.phantomgenerator import PhantomGenerator
|
8
|
+
from tomoscan.esrf.volume import EDFVolume, HDF5Volume
|
9
|
+
from tomoscan.esrf.volume.tiffvolume import TIFFVolume, has_tifffile
|
53
10
|
from tomoscan.factory import Factory as TomoscanFactory
|
54
11
|
from tomoscan.utils.volume import concatenate as concatenate_volumes
|
55
|
-
|
56
|
-
from
|
57
|
-
from
|
58
|
-
from
|
12
|
+
|
13
|
+
from nabu.stitching.alignment import AlignmentAxis1, AlignmentAxis2
|
14
|
+
from nabu.stitching.config import NormalizationBySample, PostProcessedZStitchingConfiguration
|
15
|
+
from nabu.stitching.overlap import OverlapStitchingStrategy
|
59
16
|
from nabu.stitching.utils import ShiftAlgorithm
|
60
|
-
import
|
17
|
+
from nabu.stitching.z_stitching import PostProcessZStitcher, PostProcessZStitcherNoDD
|
61
18
|
|
62
19
|
|
63
20
|
strategies_to_test_weights = (
|
@@ -84,365 +41,6 @@ def build_raw_volume():
|
|
84
41
|
return raw_volume
|
85
42
|
|
86
43
|
|
87
|
-
@pytest.mark.parametrize("strategy", strategies_to_test_weights)
|
88
|
-
def test_overlap_z_stitcher(strategy):
|
89
|
-
frame_width = 128
|
90
|
-
frame_height = frame_width
|
91
|
-
frame_1 = PhantomGenerator.get2DPhantomSheppLogan(n=frame_width)
|
92
|
-
stitcher = ZStichOverlapKernel(
|
93
|
-
stitching_strategy=strategy,
|
94
|
-
overlap_size=frame_height,
|
95
|
-
frame_width=128,
|
96
|
-
)
|
97
|
-
stitched_frame = stitcher.stitch(frame_1, frame_1)[0]
|
98
|
-
assert stitched_frame.shape == (frame_height, frame_width)
|
99
|
-
# check result is close to the expected one
|
100
|
-
numpy.testing.assert_allclose(frame_1, stitched_frame, atol=10e-10)
|
101
|
-
|
102
|
-
# check sum of weights ~ 1.0
|
103
|
-
numpy.testing.assert_allclose(
|
104
|
-
stitcher.weights_img_1 + stitcher.weights_img_2,
|
105
|
-
numpy.ones_like(stitcher.weights_img_1),
|
106
|
-
)
|
107
|
-
|
108
|
-
|
109
|
-
@pytest.mark.parametrize("dtype", (numpy.float16, numpy.float32))
|
110
|
-
def test_z_stitch_raw_frames(dtype):
|
111
|
-
"""
|
112
|
-
test z_stitch_raw_frames: insure a stitching with 3 frames and different overlap can be done
|
113
|
-
"""
|
114
|
-
ref_frame_width = 256
|
115
|
-
frame_ref = PhantomGenerator.get2DPhantomSheppLogan(n=ref_frame_width).astype(dtype)
|
116
|
-
|
117
|
-
# split the frame into several part
|
118
|
-
frame_1 = frame_ref[0:100]
|
119
|
-
frame_2 = frame_ref[80:164]
|
120
|
-
frame_3 = frame_ref[154:]
|
121
|
-
|
122
|
-
kernel_1 = ZStichOverlapKernel(frame_width=ref_frame_width, overlap_size=20)
|
123
|
-
kernel_2 = ZStichOverlapKernel(frame_width=ref_frame_width, overlap_size=10)
|
124
|
-
|
125
|
-
stitched = stitch_vertically_raw_frames(
|
126
|
-
frames=(frame_1, frame_2, frame_3),
|
127
|
-
output_dtype=dtype,
|
128
|
-
overlap_kernels=(kernel_1, kernel_2),
|
129
|
-
raw_frames_compositions=None,
|
130
|
-
overlap_frames_compositions=None,
|
131
|
-
key_lines=(
|
132
|
-
(
|
133
|
-
90, # frame_1 height - kernel_1 height / 2.0
|
134
|
-
10, # kernel_1 height / 2.0
|
135
|
-
),
|
136
|
-
(
|
137
|
-
79, # frame_2 height - kernel_2 height / 2.0 ou 102-20 ?
|
138
|
-
5, # kernel_2 height / 2.0
|
139
|
-
),
|
140
|
-
),
|
141
|
-
)
|
142
|
-
|
143
|
-
assert stitched.shape == frame_ref.shape
|
144
|
-
numpy.testing.assert_array_almost_equal(frame_ref, stitched)
|
145
|
-
|
146
|
-
|
147
|
-
def test_z_stitch_raw_frames_2():
|
148
|
-
"""
|
149
|
-
test z_stitch_raw_frames: insure a stitching with 3 frames and different overlap can be done
|
150
|
-
"""
|
151
|
-
ref_frame_width = 256
|
152
|
-
frame_ref = PhantomGenerator.get2DPhantomSheppLogan(n=ref_frame_width).astype(numpy.float32)
|
153
|
-
|
154
|
-
# split the frame into several part
|
155
|
-
frame_1 = frame_ref.copy()
|
156
|
-
frame_2 = frame_ref.copy()
|
157
|
-
frame_3 = frame_ref.copy()
|
158
|
-
|
159
|
-
kernel_1 = ZStichOverlapKernel(frame_width=ref_frame_width, overlap_size=10)
|
160
|
-
kernel_2 = ZStichOverlapKernel(frame_width=ref_frame_width, overlap_size=10)
|
161
|
-
|
162
|
-
stitched = stitch_vertically_raw_frames(
|
163
|
-
frames=(frame_1, frame_2, frame_3),
|
164
|
-
output_dtype=numpy.float32,
|
165
|
-
overlap_kernels=(kernel_1, kernel_2),
|
166
|
-
raw_frames_compositions=None,
|
167
|
-
overlap_frames_compositions=None,
|
168
|
-
key_lines=((20, 20), (105, 105)),
|
169
|
-
)
|
170
|
-
|
171
|
-
assert stitched.shape == frame_ref.shape
|
172
|
-
numpy.testing.assert_array_almost_equal(frame_ref, stitched)
|
173
|
-
|
174
|
-
|
175
|
-
_stitching_configurations = (
|
176
|
-
# simple case where shifts are provided
|
177
|
-
{
|
178
|
-
"n_proj": 4,
|
179
|
-
"raw_pos": ((0, 0, 0), (-90, 0, 0), (-180, 0, 0)), # requested shift to
|
180
|
-
"input_pos": ((0, 0, 0), (-90, 0, 0), (-180, 0, 0)), # requested shift to
|
181
|
-
"raw_shifts": ((0, 0), (-90, 0), (-180, 0)),
|
182
|
-
},
|
183
|
-
# simple case where shift is found from z position
|
184
|
-
{
|
185
|
-
"n_proj": 4,
|
186
|
-
"raw_pos": ((90, 0, 0), (0, 0, 0), (-90, 0, 0)),
|
187
|
-
"input_pos": ((90, 0, 0), (0, 0, 0), (-90, 0, 0)),
|
188
|
-
"check_bb": ((40, 140), (-50, 50), (-140, -40)),
|
189
|
-
"axis_0_params": {
|
190
|
-
KEY_IMG_REG_METHOD: ShiftAlgorithm.NONE,
|
191
|
-
},
|
192
|
-
"axis_2_params": {
|
193
|
-
KEY_IMG_REG_METHOD: ShiftAlgorithm.NONE,
|
194
|
-
},
|
195
|
-
"raw_shifts": ((0, 0), (-90, 0), (-180, 0)),
|
196
|
-
},
|
197
|
-
)
|
198
|
-
|
199
|
-
|
200
|
-
@pytest.mark.parametrize("configuration", _stitching_configurations)
|
201
|
-
@pytest.mark.parametrize("dtype", (numpy.float32, numpy.int16))
|
202
|
-
def test_PreProcessZStitcher(tmp_path, dtype, configuration):
|
203
|
-
"""
|
204
|
-
test PreProcessZStitcher class and insure a full stitching can be done automatically.
|
205
|
-
"""
|
206
|
-
n_proj = configuration["n_proj"]
|
207
|
-
ref_frame_width = 280
|
208
|
-
raw_frame_height = 100
|
209
|
-
ref_frame = PhantomGenerator.get2DPhantomSheppLogan(n=ref_frame_width).astype(dtype) * 256.0
|
210
|
-
|
211
|
-
# add some mark for image registration
|
212
|
-
ref_frame[:, 96] = -3.2
|
213
|
-
ref_frame[:, 125] = 9.1
|
214
|
-
ref_frame[:, 165] = 4.4
|
215
|
-
ref_frame[:, 200] = -2.5
|
216
|
-
# create raw data
|
217
|
-
frame_0_shift, frame_1_shift, frame_2_shift = configuration["raw_shifts"]
|
218
|
-
frame_0 = scipy_shift(ref_frame, shift=frame_0_shift)[:raw_frame_height]
|
219
|
-
frame_1 = scipy_shift(ref_frame, shift=frame_1_shift)[:raw_frame_height]
|
220
|
-
frame_2 = scipy_shift(ref_frame, shift=frame_2_shift)[:raw_frame_height]
|
221
|
-
|
222
|
-
frames = frame_0, frame_1, frame_2
|
223
|
-
frame_0_input_pos, frame_1_input_pos, frame_2_input_pos = configuration["input_pos"]
|
224
|
-
frame_0_raw_pos, frame_1_raw_pos, frame_2_raw_pos = configuration["raw_pos"]
|
225
|
-
|
226
|
-
# create a Nxtomo for each of those raw data
|
227
|
-
raw_data_dir = tmp_path / "raw_data"
|
228
|
-
raw_data_dir.mkdir()
|
229
|
-
output_dir = tmp_path / "output_dir"
|
230
|
-
output_dir.mkdir()
|
231
|
-
z_position = (
|
232
|
-
frame_0_raw_pos[0],
|
233
|
-
frame_1_raw_pos[0],
|
234
|
-
frame_2_raw_pos[0],
|
235
|
-
)
|
236
|
-
scans = []
|
237
|
-
for (i_frame, frame), z_pos in zip(enumerate(frames), z_position):
|
238
|
-
nx_tomo = NXtomo()
|
239
|
-
nx_tomo.sample.z_translation = [z_pos] * n_proj
|
240
|
-
nx_tomo.sample.rotation_angle = numpy.linspace(0, 180, num=n_proj, endpoint=False)
|
241
|
-
nx_tomo.instrument.detector.image_key_control = [ImageKey.PROJECTION] * n_proj
|
242
|
-
nx_tomo.instrument.detector.x_pixel_size = 1.0
|
243
|
-
nx_tomo.instrument.detector.y_pixel_size = 1.0
|
244
|
-
nx_tomo.instrument.detector.distance = 2.3
|
245
|
-
nx_tomo.energy = 19.2
|
246
|
-
nx_tomo.instrument.detector.data = numpy.asarray([frame] * n_proj)
|
247
|
-
|
248
|
-
file_path = os.path.join(raw_data_dir, f"nxtomo_{i_frame}.nx")
|
249
|
-
entry = f"entry000{i_frame}"
|
250
|
-
nx_tomo.save(file_path=file_path, data_path=entry)
|
251
|
-
scans.append(NXtomoScan(scan=file_path, entry=entry))
|
252
|
-
|
253
|
-
# if requested: check bounding box
|
254
|
-
check_bb = configuration.get("check_bb", None)
|
255
|
-
if check_bb is not None:
|
256
|
-
for scan, expected_bb in zip(scans, check_bb):
|
257
|
-
assert scan.get_bounding_box(axis="z") == expected_bb
|
258
|
-
output_file_path = os.path.join(output_dir, "stitched.nx")
|
259
|
-
output_data_path = "stitched"
|
260
|
-
z_stich_config = PreProcessedZStitchingConfiguration(
|
261
|
-
stitching_strategy=OverlapStitchingStrategy.LINEAR_WEIGHTS,
|
262
|
-
overwrite_results=True,
|
263
|
-
axis_0_pos_px=(
|
264
|
-
frame_0_input_pos[0],
|
265
|
-
frame_1_input_pos[0],
|
266
|
-
frame_2_input_pos[0],
|
267
|
-
),
|
268
|
-
axis_1_pos_px=(
|
269
|
-
frame_0_input_pos[1],
|
270
|
-
frame_1_input_pos[1],
|
271
|
-
frame_2_input_pos[1],
|
272
|
-
),
|
273
|
-
axis_2_pos_px=(
|
274
|
-
frame_0_input_pos[2],
|
275
|
-
frame_1_input_pos[2],
|
276
|
-
frame_2_input_pos[2],
|
277
|
-
),
|
278
|
-
axis_0_pos_mm=None,
|
279
|
-
axis_1_pos_mm=None,
|
280
|
-
axis_2_pos_mm=None,
|
281
|
-
input_scans=scans,
|
282
|
-
output_file_path=output_file_path,
|
283
|
-
output_data_path=output_data_path,
|
284
|
-
axis_0_params=configuration.get("axis_0_params", {}),
|
285
|
-
axis_1_params=configuration.get("axis_1_params", {}),
|
286
|
-
axis_2_params=configuration.get("axis_2_params", {}),
|
287
|
-
output_nexus_version=None,
|
288
|
-
slices=None,
|
289
|
-
slurm_config=None,
|
290
|
-
slice_for_cross_correlation="middle",
|
291
|
-
pixel_size=None,
|
292
|
-
)
|
293
|
-
stitcher = PreProcessZStitcher(z_stich_config)
|
294
|
-
output_identifier = stitcher.stitch()
|
295
|
-
assert output_identifier.file_path == output_file_path
|
296
|
-
assert output_identifier.data_path == output_data_path
|
297
|
-
|
298
|
-
created_nx_tomo = NXtomo().load(
|
299
|
-
file_path=output_identifier.file_path,
|
300
|
-
data_path=output_identifier.data_path,
|
301
|
-
detector_data_as="as_numpy_array",
|
302
|
-
)
|
303
|
-
|
304
|
-
assert created_nx_tomo.instrument.detector.data.ndim == 3
|
305
|
-
mean_abs_error = configuration.get("mean_abs_error", None)
|
306
|
-
if mean_abs_error is not None:
|
307
|
-
assert (
|
308
|
-
numpy.mean(numpy.abs(ref_frame - created_nx_tomo.instrument.detector.data[0, :ref_frame_width, :]))
|
309
|
-
< mean_abs_error
|
310
|
-
)
|
311
|
-
else:
|
312
|
-
numpy.testing.assert_array_almost_equal(
|
313
|
-
ref_frame, created_nx_tomo.instrument.detector.data[0, :ref_frame_width, :]
|
314
|
-
)
|
315
|
-
|
316
|
-
# check also other metadata are here
|
317
|
-
assert created_nx_tomo.instrument.detector.distance.value == 2.3
|
318
|
-
assert created_nx_tomo.energy.value == 19.2
|
319
|
-
numpy.testing.assert_array_equal(
|
320
|
-
created_nx_tomo.instrument.detector.image_key_control,
|
321
|
-
numpy.asarray([ImageKey.PROJECTION.PROJECTION] * n_proj),
|
322
|
-
)
|
323
|
-
|
324
|
-
# check configuration has been saved
|
325
|
-
with h5py.File(output_identifier.file_path, mode="r") as h5f:
|
326
|
-
assert "stitching_configuration" in h5f[output_identifier.data_path]
|
327
|
-
|
328
|
-
|
329
|
-
slices_to_test_pre = (
|
330
|
-
{
|
331
|
-
"slices": (None,),
|
332
|
-
"complete": True,
|
333
|
-
},
|
334
|
-
{
|
335
|
-
"slices": (("first",), ("middle",), ("last",)),
|
336
|
-
"complete": False,
|
337
|
-
},
|
338
|
-
{
|
339
|
-
"slices": ((0, 1, 2), slice(3, -1, 1)),
|
340
|
-
"complete": True,
|
341
|
-
},
|
342
|
-
)
|
343
|
-
|
344
|
-
|
345
|
-
@pytest.mark.parametrize("configuration_dist", slices_to_test_pre)
|
346
|
-
def test_DistributePreProcessZStitcher(tmp_path, configuration_dist):
|
347
|
-
slices = configuration_dist["slices"]
|
348
|
-
complete = configuration_dist["complete"]
|
349
|
-
|
350
|
-
n_projs = 100
|
351
|
-
raw_data = numpy.arange(100 * 128 * 128).reshape((100, 128, 128))
|
352
|
-
|
353
|
-
# create raw data
|
354
|
-
frame_0 = raw_data[:, 60:]
|
355
|
-
assert frame_0.ndim == 3
|
356
|
-
frame_0_pos = 40
|
357
|
-
frame_1 = raw_data[:, 0:80]
|
358
|
-
assert frame_1.ndim == 3
|
359
|
-
frame_1_pos = 94
|
360
|
-
frames = (frame_0, frame_1)
|
361
|
-
z_positions = (frame_0_pos, frame_1_pos)
|
362
|
-
|
363
|
-
# create a Nxtomo for each of those raw data
|
364
|
-
raw_data_dir = tmp_path / "raw_data"
|
365
|
-
raw_data_dir.mkdir()
|
366
|
-
output_dir = tmp_path / "output_dir"
|
367
|
-
output_dir.mkdir()
|
368
|
-
|
369
|
-
scans = []
|
370
|
-
for (i_frame, frame), z_pos in zip(enumerate(frames), z_positions):
|
371
|
-
nx_tomo = NXtomo()
|
372
|
-
nx_tomo.sample.z_translation = [z_pos] * n_projs
|
373
|
-
nx_tomo.sample.rotation_angle = numpy.linspace(0, 180, num=n_projs, endpoint=False)
|
374
|
-
nx_tomo.instrument.detector.image_key_control = [ImageKey.PROJECTION] * n_projs
|
375
|
-
nx_tomo.instrument.detector.x_pixel_size = 1.0
|
376
|
-
nx_tomo.instrument.detector.y_pixel_size = 1.0
|
377
|
-
nx_tomo.instrument.detector.distance = 2.3
|
378
|
-
nx_tomo.energy = 19.2
|
379
|
-
nx_tomo.instrument.detector.data = frame
|
380
|
-
|
381
|
-
file_path = os.path.join(raw_data_dir, f"nxtomo_{i_frame}.nx")
|
382
|
-
entry = f"entry000{i_frame}"
|
383
|
-
nx_tomo.save(file_path=file_path, data_path=entry)
|
384
|
-
scans.append(NXtomoScan(scan=file_path, entry=entry))
|
385
|
-
|
386
|
-
stitched_nx_tomo = []
|
387
|
-
for s in slices:
|
388
|
-
output_file_path = os.path.join(output_dir, "stitched_section.nx")
|
389
|
-
output_data_path = f"stitched_{s}"
|
390
|
-
z_stich_config = PreProcessedZStitchingConfiguration(
|
391
|
-
axis_0_pos_px=z_positions,
|
392
|
-
axis_1_pos_px=None,
|
393
|
-
axis_2_pos_px=(0, 0),
|
394
|
-
axis_0_pos_mm=None,
|
395
|
-
axis_1_pos_mm=None,
|
396
|
-
axis_2_pos_mm=None,
|
397
|
-
axis_0_params={},
|
398
|
-
axis_1_params={},
|
399
|
-
axis_2_params={},
|
400
|
-
stitching_strategy=OverlapStitchingStrategy.CLOSEST,
|
401
|
-
overwrite_results=True,
|
402
|
-
input_scans=scans,
|
403
|
-
output_file_path=output_file_path,
|
404
|
-
output_data_path=output_data_path,
|
405
|
-
output_nexus_version=None,
|
406
|
-
slices=s,
|
407
|
-
slurm_config=None,
|
408
|
-
slice_for_cross_correlation="middle",
|
409
|
-
pixel_size=None,
|
410
|
-
)
|
411
|
-
stitcher = PreProcessZStitcher(z_stich_config)
|
412
|
-
output_identifier = stitcher.stitch()
|
413
|
-
assert output_identifier.file_path == output_file_path
|
414
|
-
assert output_identifier.data_path == output_data_path
|
415
|
-
|
416
|
-
created_nx_tomo = NXtomo().load(
|
417
|
-
file_path=output_identifier.file_path,
|
418
|
-
data_path=output_identifier.data_path,
|
419
|
-
detector_data_as="as_numpy_array",
|
420
|
-
)
|
421
|
-
stitched_nx_tomo.append(created_nx_tomo)
|
422
|
-
assert len(stitched_nx_tomo) == len(slices)
|
423
|
-
final_nx_tomo = NXtomo.concatenate(stitched_nx_tomo)
|
424
|
-
assert isinstance(final_nx_tomo.instrument.detector.data, numpy.ndarray)
|
425
|
-
final_nx_tomo.save(
|
426
|
-
file_path=os.path.join(output_dir, "final_stitched.nx"),
|
427
|
-
data_path="entry0000",
|
428
|
-
)
|
429
|
-
|
430
|
-
if complete:
|
431
|
-
len(final_nx_tomo.instrument.detector.data) == 128
|
432
|
-
# test middle
|
433
|
-
numpy.testing.assert_array_almost_equal(raw_data[1], final_nx_tomo.instrument.detector.data[1, :, :])
|
434
|
-
else:
|
435
|
-
len(final_nx_tomo.instrument.detector.data) == 3
|
436
|
-
# test middle
|
437
|
-
numpy.testing.assert_array_almost_equal(raw_data[49], final_nx_tomo.instrument.detector.data[1, :, :])
|
438
|
-
# in the case of first, middle and last frames
|
439
|
-
# test first
|
440
|
-
numpy.testing.assert_array_almost_equal(raw_data[0], final_nx_tomo.instrument.detector.data[0, :, :])
|
441
|
-
|
442
|
-
# test last
|
443
|
-
numpy.testing.assert_array_almost_equal(raw_data[-1], final_nx_tomo.instrument.detector.data[-1, :, :])
|
444
|
-
|
445
|
-
|
446
44
|
_VOL_CLASSES_TO_TEST_FOR_POSTPROC_STITCHING = [HDF5Volume, EDFVolume]
|
447
45
|
# avoid testing glymur because doesn't handle float
|
448
46
|
# if has_minimal_openjpeg:
|
@@ -451,29 +49,10 @@ if has_tifffile:
|
|
451
49
|
_VOL_CLASSES_TO_TEST_FOR_POSTPROC_STITCHING.append(TIFFVolume)
|
452
50
|
|
453
51
|
|
454
|
-
|
455
|
-
@pytest.mark.parametrize("volume_class", (_VOL_CLASSES_TO_TEST_FOR_POSTPROC_STITCHING))
|
456
|
-
def test_PostProcessZStitcher(
|
457
|
-
tmp_path,
|
458
|
-
volume_class,
|
459
|
-
progress,
|
460
|
-
):
|
461
|
-
"""
|
462
|
-
test PreProcessZStitcher class and insure a full stitching can be done automatically.
|
463
|
-
|
464
|
-
:param bool clear_input_volumes_data: if True save the volume then clear volume.data (used to check internal management of loading volumes - used to check behavior with HDF5)
|
465
|
-
:param volume_class: class to be used (same class for input and output for now)
|
466
|
-
:param axis_0_pos: position of the different TomoObj along axis 0 (Also know as z axis)
|
467
|
-
"""
|
52
|
+
def build_volumes(output_dir: str, volume_class):
|
468
53
|
# create some random data.
|
469
54
|
raw_volume = build_raw_volume()
|
470
55
|
|
471
|
-
# create folder to save data (and debug)
|
472
|
-
raw_data_dir = tmp_path / "raw_data"
|
473
|
-
raw_data_dir.mkdir()
|
474
|
-
output_dir = tmp_path / "output_dir"
|
475
|
-
output_dir.mkdir()
|
476
|
-
|
477
56
|
# create a simple case where the volume have 10 voxel of overlap and a height (z) of 30 Voxels, 40 and 30 Voxels
|
478
57
|
vol_1_constructor_params = {
|
479
58
|
"data": raw_volume[0:30, :, :],
|
@@ -511,29 +90,55 @@ def test_PostProcessZStitcher(
|
|
511
90
|
},
|
512
91
|
}
|
513
92
|
|
514
|
-
|
93
|
+
volumes = []
|
515
94
|
axis_0_positions = []
|
516
95
|
for i_vol, vol_params in enumerate([vol_1_constructor_params, vol_2_constructor_params, vol_3_constructor_params]):
|
517
96
|
if volume_class == HDF5Volume:
|
518
97
|
vol_params.update(
|
519
98
|
{
|
520
|
-
"file_path": os.path.join(
|
99
|
+
"file_path": os.path.join(output_dir, f"raw_volume_{i_vol}.hdf5"),
|
521
100
|
"data_path": "volume",
|
522
101
|
}
|
523
102
|
)
|
524
103
|
else:
|
525
104
|
vol_params.update(
|
526
105
|
{
|
527
|
-
"folder": os.path.join(
|
106
|
+
"folder": os.path.join(output_dir, f"raw_volume_{i_vol}"),
|
528
107
|
}
|
529
108
|
)
|
530
109
|
axis_0_positions.append(vol_params["metadata"]["processing_options"]["reconstruction"]["position"][0])
|
531
110
|
|
532
111
|
volume = volume_class(**vol_params)
|
533
112
|
volume.save()
|
534
|
-
|
113
|
+
volumes.append(volume)
|
114
|
+
return volumes, axis_0_positions, raw_volume
|
535
115
|
|
536
|
-
|
116
|
+
|
117
|
+
@pytest.mark.parametrize("progress", (None, "with_tqdm"))
|
118
|
+
@pytest.mark.parametrize("volume_class", (_VOL_CLASSES_TO_TEST_FOR_POSTPROC_STITCHING))
|
119
|
+
def test_PostProcessZStitcher(
|
120
|
+
tmp_path,
|
121
|
+
volume_class,
|
122
|
+
progress,
|
123
|
+
):
|
124
|
+
"""
|
125
|
+
test PreProcessZStitcher class and insure a full stitching can be done automatically.
|
126
|
+
|
127
|
+
:param bool clear_input_volumes_data: if True save the volume then clear volume.data (used to check internal management of loading volumes - used to check behavior with HDF5)
|
128
|
+
:param volume_class: class to be used (same class for input and output for now)
|
129
|
+
:param axis_0_pos: position of the different TomoObj along axis 0 (Also know as z axis)
|
130
|
+
"""
|
131
|
+
if progress == "with_tqdm":
|
132
|
+
progress = tqdm(total=100)
|
133
|
+
|
134
|
+
# create folder to save data (and debug)
|
135
|
+
raw_data_dir = tmp_path / "raw_data"
|
136
|
+
raw_data_dir.mkdir()
|
137
|
+
output_dir = tmp_path / "output_dir"
|
138
|
+
output_dir.mkdir()
|
139
|
+
|
140
|
+
volumes, axis_0_positions, raw_volume = build_volumes(output_dir=raw_data_dir, volume_class=volume_class)
|
141
|
+
volume_1, volume_2, volume_3 = volumes
|
537
142
|
|
538
143
|
output_volume = HDF5Volume(
|
539
144
|
file_path=os.path.join(output_dir, "stitched_volume.hdf5"),
|
@@ -575,7 +180,7 @@ def test_PostProcessZStitcher(
|
|
575
180
|
numpy.testing.assert_array_almost_equal(raw_volume, output_volume.data)
|
576
181
|
|
577
182
|
metadata = output_volume.metadata
|
578
|
-
assert
|
183
|
+
assert "about" in metadata
|
579
184
|
assert "configuration" in metadata
|
580
185
|
assert output_volume.position[0] == -60.0
|
581
186
|
assert output_volume.pixel_size == (1.0, 1.0, 1.0)
|
@@ -668,10 +273,10 @@ def test_DistributePostProcessZStitcher(tmp_path, configuration_dist, flip_ud):
|
|
668
273
|
),
|
669
274
|
axis_0_pos_mm=None,
|
670
275
|
axis_0_params={},
|
671
|
-
axis_1_pos_px=
|
276
|
+
axis_1_pos_px=(0, 0),
|
672
277
|
axis_1_pos_mm=None,
|
673
278
|
axis_1_params={},
|
674
|
-
axis_2_pos_px=
|
279
|
+
axis_2_pos_px=None,
|
675
280
|
axis_2_pos_mm=None,
|
676
281
|
axis_2_params={},
|
677
282
|
overwrite_results=True,
|
@@ -701,123 +306,6 @@ def test_DistributePostProcessZStitcher(tmp_path, configuration_dist, flip_ud):
|
|
701
306
|
)
|
702
307
|
|
703
308
|
|
704
|
-
def test_get_overlap_areas():
|
705
|
-
"""test get_overlap_areas function"""
|
706
|
-
f_upper = numpy.linspace(7, 15, num=9, endpoint=True)
|
707
|
-
f_lower = numpy.linspace(0, 12, num=13, endpoint=True)
|
708
|
-
|
709
|
-
o_1, o_2 = ZStitcher.get_overlap_areas(
|
710
|
-
upper_frame=f_upper,
|
711
|
-
lower_frame=f_lower,
|
712
|
-
upper_frame_key_line=3,
|
713
|
-
lower_frame_key_line=10,
|
714
|
-
overlap_size=4,
|
715
|
-
stitching_axis=0,
|
716
|
-
)
|
717
|
-
|
718
|
-
numpy.testing.assert_array_equal(o_1, o_2)
|
719
|
-
numpy.testing.assert_array_equal(o_1, numpy.linspace(8, 11, num=4, endpoint=True))
|
720
|
-
|
721
|
-
|
722
|
-
def test_frame_flip(tmp_path):
|
723
|
-
"""check it with some NXtomo fliped"""
|
724
|
-
ref_frame_width = 280
|
725
|
-
n_proj = 10
|
726
|
-
raw_frame_width = 100
|
727
|
-
ref_frame = PhantomGenerator.get2DPhantomSheppLogan(n=ref_frame_width).astype(numpy.float32) * 256.0
|
728
|
-
# create raw data
|
729
|
-
frame_0_shift = (0, 0)
|
730
|
-
frame_1_shift = (-90, 0)
|
731
|
-
frame_2_shift = (-180, 0)
|
732
|
-
|
733
|
-
frame_0 = scipy_shift(ref_frame, shift=frame_0_shift)[:raw_frame_width]
|
734
|
-
frame_1 = scipy_shift(ref_frame, shift=frame_1_shift)[:raw_frame_width]
|
735
|
-
frame_2 = scipy_shift(ref_frame, shift=frame_2_shift)[:raw_frame_width]
|
736
|
-
frames = frame_0, frame_1, frame_2
|
737
|
-
|
738
|
-
x_flips = [False, True, True]
|
739
|
-
y_flips = [False, False, True]
|
740
|
-
|
741
|
-
def apply_flip(args):
|
742
|
-
frame, flip_x, flip_y = args
|
743
|
-
if flip_x:
|
744
|
-
frame = numpy.fliplr(frame)
|
745
|
-
if flip_y:
|
746
|
-
frame = numpy.flipud(frame)
|
747
|
-
return frame
|
748
|
-
|
749
|
-
frames = map(apply_flip, zip(frames, x_flips, y_flips))
|
750
|
-
|
751
|
-
# create a Nxtomo for each of those raw data
|
752
|
-
raw_data_dir = tmp_path / "raw_data"
|
753
|
-
raw_data_dir.mkdir()
|
754
|
-
output_dir = tmp_path / "output_dir"
|
755
|
-
output_dir.mkdir()
|
756
|
-
z_position = (90, 0, -90)
|
757
|
-
|
758
|
-
scans = []
|
759
|
-
for (i_frame, frame), z_pos, x_flip, y_flip in zip(enumerate(frames), z_position, x_flips, y_flips):
|
760
|
-
nx_tomo = NXtomo()
|
761
|
-
nx_tomo.sample.z_translation = [z_pos] * n_proj
|
762
|
-
nx_tomo.sample.rotation_angle = numpy.linspace(0, 180, num=n_proj, endpoint=False)
|
763
|
-
nx_tomo.instrument.detector.image_key_control = [ImageKey.PROJECTION] * n_proj
|
764
|
-
nx_tomo.instrument.detector.x_pixel_size = 1.0
|
765
|
-
nx_tomo.instrument.detector.y_pixel_size = 1.0
|
766
|
-
nx_tomo.instrument.detector.distance = 2.3
|
767
|
-
if x_flip:
|
768
|
-
nx_tomo.instrument.detector.transformations.add_transformation(LRDetTransformation())
|
769
|
-
if y_flip:
|
770
|
-
nx_tomo.instrument.detector.transformations.add_transformation(UDDetTransformation())
|
771
|
-
nx_tomo.energy = 19.2
|
772
|
-
nx_tomo.instrument.detector.data = numpy.asarray([frame] * n_proj)
|
773
|
-
|
774
|
-
file_path = os.path.join(raw_data_dir, f"nxtomo_{i_frame}.nx")
|
775
|
-
entry = f"entry000{i_frame}"
|
776
|
-
nx_tomo.save(file_path=file_path, data_path=entry)
|
777
|
-
scans.append(NXtomoScan(scan=file_path, entry=entry))
|
778
|
-
|
779
|
-
output_file_path = os.path.join(output_dir, "stitched.nx")
|
780
|
-
output_data_path = "stitched"
|
781
|
-
assert len(scans) == 3
|
782
|
-
z_stich_config = PreProcessedZStitchingConfiguration(
|
783
|
-
axis_0_pos_px=(0, -90, -180),
|
784
|
-
axis_1_pos_px=None,
|
785
|
-
axis_2_pos_px=(0, 0, 0),
|
786
|
-
axis_0_pos_mm=None,
|
787
|
-
axis_1_pos_mm=None,
|
788
|
-
axis_2_pos_mm=None,
|
789
|
-
axis_0_params={},
|
790
|
-
axis_1_params={},
|
791
|
-
axis_2_params={},
|
792
|
-
stitching_strategy=OverlapStitchingStrategy.LINEAR_WEIGHTS,
|
793
|
-
overwrite_results=True,
|
794
|
-
input_scans=scans,
|
795
|
-
output_file_path=output_file_path,
|
796
|
-
output_data_path=output_data_path,
|
797
|
-
output_nexus_version=None,
|
798
|
-
slices=None,
|
799
|
-
slurm_config=None,
|
800
|
-
slice_for_cross_correlation="middle",
|
801
|
-
pixel_size=None,
|
802
|
-
)
|
803
|
-
stitcher = PreProcessZStitcher(z_stich_config)
|
804
|
-
output_identifier = stitcher.stitch()
|
805
|
-
assert output_identifier.file_path == output_file_path
|
806
|
-
assert output_identifier.data_path == output_data_path
|
807
|
-
|
808
|
-
created_nx_tomo = NXtomo().load(
|
809
|
-
file_path=output_identifier.file_path,
|
810
|
-
data_path=output_identifier.data_path,
|
811
|
-
detector_data_as="as_numpy_array",
|
812
|
-
)
|
813
|
-
|
814
|
-
assert created_nx_tomo.instrument.detector.data.ndim == 3
|
815
|
-
# insure flipping has been taking into account
|
816
|
-
numpy.testing.assert_array_almost_equal(ref_frame, created_nx_tomo.instrument.detector.data[0, :ref_frame_width, :])
|
817
|
-
|
818
|
-
assert len(created_nx_tomo.instrument.detector.transformations) == 0
|
819
|
-
|
820
|
-
|
821
309
|
@pytest.mark.parametrize("alignment_axis_2", ("left", "right", "center"))
|
822
310
|
def test_vol_z_stitching_with_alignment_axis_2(tmp_path, alignment_axis_2):
|
823
311
|
"""
|
@@ -1050,8 +538,6 @@ def test_normalization_by_sample(tmp_path):
|
|
1050
538
|
simple test of a volume stitching.
|
1051
539
|
Raw volumes have 'extra' values (+2, +5, +9) that must be removed at the end thanks to the normalization
|
1052
540
|
"""
|
1053
|
-
from copy import deepcopy
|
1054
|
-
|
1055
541
|
raw_volume = build_raw_volume()
|
1056
542
|
# create folder to save data (and debug)
|
1057
543
|
raw_data_dir = tmp_path / "raw_data"
|
@@ -1163,7 +649,127 @@ def test_normalization_by_sample(tmp_path):
|
|
1163
649
|
numpy.testing.assert_array_almost_equal(raw_volume, output_volume.data)
|
1164
650
|
|
1165
651
|
metadata = output_volume.metadata
|
1166
|
-
assert metadata["program"] == "nabu-stitching"
|
1167
652
|
assert "configuration" in metadata
|
653
|
+
assert "about" in metadata
|
654
|
+
assert metadata["about"]["program"] == "nabu-stitching"
|
1168
655
|
assert output_volume.position[0] == -60.0
|
1169
656
|
assert output_volume.pixel_size == (1.0, 1.0, 1.0)
|
657
|
+
|
658
|
+
|
659
|
+
@pytest.mark.parametrize("data_duplication", (True, False))
|
660
|
+
def test_data_duplication(tmp_path, data_duplication):
|
661
|
+
"""
|
662
|
+
Test that the post-processing stitching can be done without duplicating data.
|
663
|
+
And also making sure avoid data duplication can handle frame flips
|
664
|
+
"""
|
665
|
+
raw_volume = build_raw_volume()
|
666
|
+
|
667
|
+
# create folder to save data (and debug)
|
668
|
+
raw_data_dir = tmp_path / "raw_data"
|
669
|
+
raw_data_dir.mkdir()
|
670
|
+
output_dir = tmp_path / "output_dir"
|
671
|
+
output_dir.mkdir()
|
672
|
+
|
673
|
+
volume_1 = HDF5Volume(
|
674
|
+
data=raw_volume[0:30],
|
675
|
+
metadata={
|
676
|
+
"processing_options": {
|
677
|
+
"reconstruction": {
|
678
|
+
"position": (-15.0, 0.0, 0.0),
|
679
|
+
"voxel_size_cm": (100.0, 100.0, 100.0),
|
680
|
+
}
|
681
|
+
},
|
682
|
+
},
|
683
|
+
file_path=os.path.join(raw_data_dir, f"raw_volume_1.hdf5"),
|
684
|
+
data_path="volume",
|
685
|
+
)
|
686
|
+
|
687
|
+
volume_2 = HDF5Volume(
|
688
|
+
data=raw_volume[20:80],
|
689
|
+
metadata={
|
690
|
+
"processing_options": {
|
691
|
+
"reconstruction": {
|
692
|
+
"position": (-50.0, 0.0, 0.0),
|
693
|
+
"voxel_size_cm": (100.0, 100.0, 100.0),
|
694
|
+
}
|
695
|
+
},
|
696
|
+
},
|
697
|
+
file_path=os.path.join(raw_data_dir, f"raw_volume_2.hdf5"),
|
698
|
+
data_path="volume",
|
699
|
+
)
|
700
|
+
|
701
|
+
volume_3 = HDF5Volume(
|
702
|
+
data=raw_volume[60:],
|
703
|
+
metadata={
|
704
|
+
"processing_options": {
|
705
|
+
"reconstruction": {
|
706
|
+
"position": (-90.0, 0.0, 0.0),
|
707
|
+
"voxel_size_cm": (100.0, 100.0, 100.0),
|
708
|
+
}
|
709
|
+
},
|
710
|
+
},
|
711
|
+
file_path=os.path.join(raw_data_dir, f"raw_volume_3.hdf5"),
|
712
|
+
data_path="volume",
|
713
|
+
)
|
714
|
+
|
715
|
+
for volume in (volume_1, volume_2, volume_3):
|
716
|
+
volume.save()
|
717
|
+
volume.clear_cache()
|
718
|
+
|
719
|
+
output_volume = HDF5Volume(
|
720
|
+
file_path=os.path.join(output_dir, "stitched_volume.hdf5"),
|
721
|
+
data_path="stitched_volume",
|
722
|
+
)
|
723
|
+
|
724
|
+
z_stich_config = PostProcessedZStitchingConfiguration(
|
725
|
+
stitching_strategy=OverlapStitchingStrategy.CLOSEST,
|
726
|
+
overwrite_results=True,
|
727
|
+
input_volumes=(volume_1, volume_2, volume_3),
|
728
|
+
output_volume=output_volume,
|
729
|
+
slices=None,
|
730
|
+
slurm_config=None,
|
731
|
+
axis_0_pos_px=None,
|
732
|
+
axis_0_pos_mm=None,
|
733
|
+
axis_0_params={"img_reg_method": ShiftAlgorithm.NONE},
|
734
|
+
axis_1_pos_px=None,
|
735
|
+
axis_1_pos_mm=None,
|
736
|
+
axis_1_params={"img_reg_method": ShiftAlgorithm.NONE},
|
737
|
+
axis_2_pos_px=None,
|
738
|
+
axis_2_pos_mm=None,
|
739
|
+
axis_2_params={"img_reg_method": ShiftAlgorithm.NONE},
|
740
|
+
slice_for_cross_correlation="middle",
|
741
|
+
voxel_size=None,
|
742
|
+
duplicate_data=data_duplication,
|
743
|
+
)
|
744
|
+
|
745
|
+
if data_duplication:
|
746
|
+
stitcher = PostProcessZStitcher(z_stich_config, progress=None)
|
747
|
+
else:
|
748
|
+
stitcher = PostProcessZStitcherNoDD(z_stich_config, progress=None)
|
749
|
+
output_identifier = stitcher.stitch()
|
750
|
+
|
751
|
+
assert output_identifier.file_path == output_volume.file_path
|
752
|
+
assert output_identifier.data_path == output_volume.data_path
|
753
|
+
|
754
|
+
output_volume.data = None
|
755
|
+
output_volume.metadata = None
|
756
|
+
output_volume.load_data(store=True)
|
757
|
+
output_volume.load_metadata(store=True)
|
758
|
+
|
759
|
+
assert raw_volume.shape == output_volume.data.shape
|
760
|
+
numpy.testing.assert_almost_equal(raw_volume.data, output_volume.data)
|
761
|
+
|
762
|
+
with h5py.File(output_volume.file_path, mode="r") as h5f:
|
763
|
+
if data_duplication:
|
764
|
+
assert f"{output_volume.data_path}/stitching_regions" not in h5f
|
765
|
+
assert not h5f[f"{output_volume.data_path}/results/data"].is_virtual
|
766
|
+
else:
|
767
|
+
assert f"{output_volume.data_path}/stitching_regions" in h5f
|
768
|
+
assert h5f[f"{output_volume.data_path}/results/data"].is_virtual
|
769
|
+
|
770
|
+
if not data_duplication:
|
771
|
+
# make sure an error is raised if we try to ask for no data duplication and if we get some flips
|
772
|
+
z_stich_config.flip_ud = (False, True, False)
|
773
|
+
with pytest.raises(ValueError):
|
774
|
+
stitcher = PostProcessZStitcherNoDD(z_stich_config, progress=None)
|
775
|
+
stitcher.stitch()
|