nabu 2024.1.10__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.
Files changed (152) hide show
  1. nabu/__init__.py +1 -1
  2. nabu/app/bootstrap.py +2 -3
  3. nabu/app/cast_volume.py +4 -2
  4. nabu/app/cli_configs.py +5 -0
  5. nabu/app/composite_cor.py +1 -1
  6. nabu/app/create_distortion_map_from_poly.py +5 -6
  7. nabu/app/diag_to_pix.py +7 -19
  8. nabu/app/diag_to_rot.py +14 -29
  9. nabu/app/double_flatfield.py +32 -44
  10. nabu/app/parse_reconstruction_log.py +3 -0
  11. nabu/app/reconstruct.py +53 -15
  12. nabu/app/reconstruct_helical.py +2 -2
  13. nabu/app/stitching.py +27 -13
  14. nabu/app/tests/__init__.py +0 -0
  15. nabu/app/tests/test_reduce_dark_flat.py +4 -1
  16. nabu/cuda/kernel.py +11 -2
  17. nabu/cuda/processing.py +2 -2
  18. nabu/cuda/src/cone.cu +77 -0
  19. nabu/cuda/src/hierarchical_backproj.cu +271 -0
  20. nabu/cuda/utils.py +0 -6
  21. nabu/estimation/alignment.py +5 -19
  22. nabu/estimation/cor.py +173 -599
  23. nabu/estimation/cor_sino.py +356 -26
  24. nabu/estimation/focus.py +63 -11
  25. nabu/estimation/tests/test_cor.py +124 -58
  26. nabu/estimation/tests/test_focus.py +6 -6
  27. nabu/estimation/tilt.py +2 -1
  28. nabu/estimation/utils.py +5 -33
  29. nabu/io/__init__.py +1 -1
  30. nabu/io/cast_volume.py +1 -1
  31. nabu/io/reader.py +416 -21
  32. nabu/io/tests/test_readers.py +422 -0
  33. nabu/io/tests/test_writers.py +1 -102
  34. nabu/io/writer.py +4 -433
  35. nabu/opencl/kernel.py +14 -3
  36. nabu/opencl/processing.py +8 -0
  37. nabu/pipeline/config_validators.py +5 -2
  38. nabu/pipeline/datadump.py +12 -5
  39. nabu/pipeline/estimators.py +162 -188
  40. nabu/pipeline/fullfield/chunked.py +168 -92
  41. nabu/pipeline/fullfield/chunked_cuda.py +7 -3
  42. nabu/pipeline/fullfield/computations.py +2 -7
  43. nabu/pipeline/fullfield/dataset_validator.py +0 -4
  44. nabu/pipeline/fullfield/nabu_config.py +37 -13
  45. nabu/pipeline/fullfield/processconfig.py +22 -13
  46. nabu/pipeline/fullfield/reconstruction.py +13 -9
  47. nabu/pipeline/helical/helical_chunked_regridded.py +1 -1
  48. nabu/pipeline/helical/helical_chunked_regridded_cuda.py +1 -0
  49. nabu/pipeline/helical/helical_reconstruction.py +1 -1
  50. nabu/pipeline/params.py +21 -1
  51. nabu/pipeline/processconfig.py +1 -12
  52. nabu/pipeline/reader.py +146 -0
  53. nabu/pipeline/tests/test_estimators.py +44 -72
  54. nabu/pipeline/utils.py +4 -2
  55. nabu/pipeline/writer.py +10 -2
  56. nabu/preproc/ccd_cuda.py +1 -1
  57. nabu/preproc/ctf.py +14 -7
  58. nabu/preproc/ctf_cuda.py +2 -3
  59. nabu/preproc/double_flatfield.py +5 -12
  60. nabu/preproc/double_flatfield_cuda.py +2 -2
  61. nabu/preproc/flatfield.py +5 -1
  62. nabu/preproc/flatfield_cuda.py +5 -1
  63. nabu/preproc/phase.py +24 -73
  64. nabu/preproc/phase_cuda.py +5 -8
  65. nabu/preproc/tests/test_ctf.py +11 -7
  66. nabu/preproc/tests/test_flatfield.py +67 -122
  67. nabu/preproc/tests/test_paganin.py +54 -30
  68. nabu/processing/azim.py +206 -0
  69. nabu/processing/convolution_cuda.py +1 -1
  70. nabu/processing/fft_cuda.py +15 -17
  71. nabu/processing/histogram.py +2 -0
  72. nabu/processing/histogram_cuda.py +2 -1
  73. nabu/processing/kernel_base.py +3 -0
  74. nabu/processing/muladd_cuda.py +1 -0
  75. nabu/processing/padding_opencl.py +1 -1
  76. nabu/processing/roll_opencl.py +1 -0
  77. nabu/processing/rotation_cuda.py +2 -2
  78. nabu/processing/tests/test_fft.py +17 -10
  79. nabu/processing/unsharp_cuda.py +1 -1
  80. nabu/reconstruction/cone.py +104 -40
  81. nabu/reconstruction/fbp.py +3 -0
  82. nabu/reconstruction/fbp_base.py +7 -2
  83. nabu/reconstruction/filtering.py +20 -7
  84. nabu/reconstruction/filtering_cuda.py +7 -1
  85. nabu/reconstruction/hbp.py +424 -0
  86. nabu/reconstruction/mlem.py +99 -0
  87. nabu/reconstruction/reconstructor.py +2 -0
  88. nabu/reconstruction/rings_cuda.py +19 -19
  89. nabu/reconstruction/sinogram_cuda.py +1 -0
  90. nabu/reconstruction/sinogram_opencl.py +3 -1
  91. nabu/reconstruction/tests/test_cone.py +10 -5
  92. nabu/reconstruction/tests/test_deringer.py +7 -6
  93. nabu/reconstruction/tests/test_fbp.py +124 -10
  94. nabu/reconstruction/tests/test_filtering.py +13 -11
  95. nabu/reconstruction/tests/test_halftomo.py +30 -4
  96. nabu/reconstruction/tests/test_mlem.py +91 -0
  97. nabu/reconstruction/tests/test_reconstructor.py +8 -3
  98. nabu/resources/dataset_analyzer.py +142 -92
  99. nabu/resources/gpu.py +1 -0
  100. nabu/resources/nxflatfield.py +134 -125
  101. nabu/resources/templates/id16a_fluo.conf +42 -0
  102. nabu/resources/tests/test_extract.py +10 -0
  103. nabu/resources/tests/test_nxflatfield.py +2 -2
  104. nabu/stitching/alignment.py +80 -24
  105. nabu/stitching/config.py +105 -68
  106. nabu/stitching/definitions.py +1 -0
  107. nabu/stitching/frame_composition.py +68 -60
  108. nabu/stitching/overlap.py +91 -51
  109. nabu/stitching/single_axis_stitching.py +32 -0
  110. nabu/stitching/slurm_utils.py +6 -6
  111. nabu/stitching/stitcher/__init__.py +0 -0
  112. nabu/stitching/stitcher/base.py +124 -0
  113. nabu/stitching/stitcher/dumper/__init__.py +3 -0
  114. nabu/stitching/stitcher/dumper/base.py +94 -0
  115. nabu/stitching/stitcher/dumper/postprocessing.py +356 -0
  116. nabu/stitching/stitcher/dumper/preprocessing.py +60 -0
  117. nabu/stitching/stitcher/post_processing.py +555 -0
  118. nabu/stitching/stitcher/pre_processing.py +1068 -0
  119. nabu/stitching/stitcher/single_axis.py +484 -0
  120. nabu/stitching/stitcher/stitcher.py +0 -0
  121. nabu/stitching/stitcher/y_stitcher.py +13 -0
  122. nabu/stitching/stitcher/z_stitcher.py +45 -0
  123. nabu/stitching/stitcher_2D.py +278 -0
  124. nabu/stitching/tests/test_config.py +12 -37
  125. nabu/stitching/tests/test_frame_composition.py +33 -59
  126. nabu/stitching/tests/test_overlap.py +149 -7
  127. nabu/stitching/tests/test_utils.py +1 -1
  128. nabu/stitching/tests/test_y_preprocessing_stitching.py +132 -0
  129. nabu/stitching/tests/{test_z_stitching.py → test_z_postprocessing_stitching.py} +167 -561
  130. nabu/stitching/tests/test_z_preprocessing_stitching.py +431 -0
  131. nabu/stitching/utils/__init__.py +1 -0
  132. nabu/stitching/utils/post_processing.py +281 -0
  133. nabu/stitching/utils/tests/test_post-processing.py +21 -0
  134. nabu/stitching/{utils.py → utils/utils.py} +79 -52
  135. nabu/stitching/y_stitching.py +27 -0
  136. nabu/stitching/z_stitching.py +32 -2281
  137. nabu/testutils.py +1 -152
  138. nabu/thirdparty/tomocupy_remove_stripe.py +43 -9
  139. nabu/utils.py +158 -61
  140. {nabu-2024.1.10.dist-info → nabu-2024.2.0.dist-info}/METADATA +24 -17
  141. {nabu-2024.1.10.dist-info → nabu-2024.2.0.dist-info}/RECORD +145 -121
  142. {nabu-2024.1.10.dist-info → nabu-2024.2.0.dist-info}/WHEEL +1 -1
  143. nabu/io/tiffwriter_zmm.py +0 -99
  144. nabu/pipeline/fallback_utils.py +0 -149
  145. nabu/pipeline/helical/tests/test_accumulator.py +0 -158
  146. nabu/pipeline/helical/tests/test_pipeline_elements_full.py +0 -355
  147. nabu/pipeline/helical/tests/test_strategy.py +0 -61
  148. nabu/pipeline/helical/utils.py +0 -51
  149. nabu/pipeline/tests/test_chunk_reader.py +0 -74
  150. {nabu-2024.1.10.dist-info → nabu-2024.2.0.dist-info}/LICENSE +0 -0
  151. {nabu-2024.1.10.dist-info → nabu-2024.2.0.dist-info}/entry_points.txt +0 -0
  152. {nabu-2024.1.10.dist-info → nabu-2024.2.0.dist-info}/top_level.txt +0 -0
@@ -3,14 +3,52 @@ import pytest
3
3
 
4
4
  from nabu.stitching.overlap import compute_image_minimum_divergence, compute_image_higher_signal, check_overlaps
5
5
  from nabu.testutils import get_data
6
+ from nabu.stitching.overlap import ImageStichOverlapKernel, OverlapStitchingStrategy
7
+ from nabu.stitching.stitcher_2D import stitch_raw_frames
8
+ from silx.image.phantomgenerator import PhantomGenerator
9
+
10
+
11
+ strategies_to_test_weights = (
12
+ OverlapStitchingStrategy.CLOSEST,
13
+ OverlapStitchingStrategy.COSINUS_WEIGHTS,
14
+ OverlapStitchingStrategy.LINEAR_WEIGHTS,
15
+ OverlapStitchingStrategy.MEAN,
16
+ )
17
+
18
+
19
+ @pytest.mark.parametrize("strategy", strategies_to_test_weights)
20
+ @pytest.mark.parametrize("stitching_axis", (0, 1))
21
+ def test_overlap_stitcher(strategy, stitching_axis):
22
+ frame_width = 128
23
+ frame_height = frame_width
24
+ frame_1 = PhantomGenerator.get2DPhantomSheppLogan(n=frame_width)
25
+ stitcher = ImageStichOverlapKernel(
26
+ stitching_strategy=strategy,
27
+ overlap_size=frame_height,
28
+ frame_unstitched_axis_size=128,
29
+ stitching_axis=stitching_axis,
30
+ )
31
+ stitched_frame = stitcher.stitch(frame_1, frame_1)[0]
32
+ assert stitched_frame.shape == (frame_height, frame_width)
33
+ # check result is close to the expected one
34
+ numpy.testing.assert_allclose(frame_1, stitched_frame, atol=10e-10)
35
+
36
+ # check sum of weights ~ 1.0
37
+ numpy.testing.assert_allclose(
38
+ stitcher.weights_img_1 + stitcher.weights_img_2,
39
+ numpy.ones_like(stitcher.weights_img_1),
40
+ )
6
41
 
7
42
 
8
- def test_compute_image_minimum_divergence():
43
+ @pytest.mark.parametrize("stitching_axis", (0, 1))
44
+ def test_compute_image_minimum_divergence(stitching_axis):
9
45
  """make sure the compute_image_minimum_divergence function is processing"""
10
46
  raw_data_1 = get_data("brain_phantom.npz")["data"]
11
47
  raw_data_2 = numpy.random.rand(*raw_data_1.shape) * 255.0
12
48
 
13
- stitching = compute_image_minimum_divergence(raw_data_1, raw_data_2, high_frequency_threshold=2)
49
+ stitching = compute_image_minimum_divergence(
50
+ raw_data_1, raw_data_2, high_frequency_threshold=2, stitching_axis=stitching_axis
51
+ )
14
52
  assert stitching.shape == raw_data_1.shape
15
53
 
16
54
 
@@ -43,7 +81,7 @@ def test_check_overlaps():
43
81
  numpy.ones(10),
44
82
  numpy.ones(20),
45
83
  ),
46
- positions=((10,), (0,)),
84
+ positions=((10, 0, 0), (0, 0, 0)),
47
85
  axis=0,
48
86
  raise_error=True,
49
87
  )
@@ -55,7 +93,7 @@ def test_check_overlaps():
55
93
  numpy.ones(10),
56
94
  numpy.ones(20),
57
95
  ),
58
- positions=((0,), (100,)),
96
+ positions=((0, 0, 0), (100, 0, 0)),
59
97
  axis=0,
60
98
  raise_error=True,
61
99
  )
@@ -67,7 +105,7 @@ def test_check_overlaps():
67
105
  numpy.ones(20),
68
106
  numpy.ones(10),
69
107
  ),
70
- positions=((8,), (5,)),
108
+ positions=((8, 0, 0), (5, 0, 0)),
71
109
  axis=0,
72
110
  raise_error=True,
73
111
  )
@@ -79,7 +117,7 @@ def test_check_overlaps():
79
117
  numpy.ones(20),
80
118
  numpy.ones(10),
81
119
  ),
82
- positions=((20,), (10,), (0,)),
120
+ positions=((20, 0, 0), (10, 0, 0), (0, 0, 0)),
83
121
  axis=0,
84
122
  raise_error=True,
85
123
  )
@@ -92,7 +130,111 @@ def test_check_overlaps():
92
130
  numpy.ones(10),
93
131
  numpy.ones(10),
94
132
  ),
95
- positions=((20,), (15,), (11,)),
133
+ positions=((20, 0, 0), (15, 0, 0), (11, 0, 0)),
96
134
  axis=0,
97
135
  raise_error=True,
98
136
  )
137
+
138
+
139
+ @pytest.mark.parametrize("dtype", (numpy.float16, numpy.float32))
140
+ def test_stitch_vertically_raw_frames(dtype):
141
+ """
142
+ ensure a stitching with 3 frames and different overlap can be done
143
+ """
144
+ ref_frame_width = 256
145
+ frame_ref = PhantomGenerator.get2DPhantomSheppLogan(n=ref_frame_width).astype(dtype)
146
+
147
+ # split the frame into several part
148
+ frame_1 = frame_ref[0:100]
149
+ frame_2 = frame_ref[80:164]
150
+ frame_3 = frame_ref[154:]
151
+
152
+ kernel_1 = ImageStichOverlapKernel(frame_unstitched_axis_size=ref_frame_width, overlap_size=20, stitching_axis=0)
153
+ kernel_2 = ImageStichOverlapKernel(frame_unstitched_axis_size=ref_frame_width, overlap_size=10, stitching_axis=0)
154
+
155
+ stitched = stitch_raw_frames(
156
+ frames=(frame_1, frame_2, frame_3),
157
+ output_dtype=dtype,
158
+ overlap_kernels=(kernel_1, kernel_2),
159
+ raw_frames_compositions=None,
160
+ overlap_frames_compositions=None,
161
+ key_lines=(
162
+ (
163
+ 90, # frame_1 height - kernel_1 height / 2.0
164
+ 10, # kernel_1 height / 2.0
165
+ ),
166
+ (
167
+ 79, # frame_2 height - kernel_2 height / 2.0 ou 102-20 ?
168
+ 5, # kernel_2 height / 2.0
169
+ ),
170
+ ),
171
+ )
172
+
173
+ assert stitched.shape == frame_ref.shape
174
+ numpy.testing.assert_array_almost_equal(frame_ref, stitched)
175
+
176
+
177
+ def test_stitch_vertically_raw_frames_2():
178
+ """
179
+ ensure a stitching with 3 frames and different overlap can be done
180
+ """
181
+ ref_frame_width = 256
182
+ frame_ref = PhantomGenerator.get2DPhantomSheppLogan(n=ref_frame_width).astype(numpy.float32)
183
+
184
+ # split the frame into several part
185
+ frame_1 = frame_ref.copy()
186
+ frame_2 = frame_ref.copy()
187
+ frame_3 = frame_ref.copy()
188
+
189
+ kernel_1 = ImageStichOverlapKernel(frame_unstitched_axis_size=ref_frame_width, overlap_size=10, stitching_axis=0)
190
+ kernel_2 = ImageStichOverlapKernel(frame_unstitched_axis_size=ref_frame_width, overlap_size=10, stitching_axis=0)
191
+
192
+ stitched = stitch_raw_frames(
193
+ frames=(frame_1, frame_2, frame_3),
194
+ output_dtype=numpy.float32,
195
+ overlap_kernels=(kernel_1, kernel_2),
196
+ raw_frames_compositions=None,
197
+ overlap_frames_compositions=None,
198
+ key_lines=((20, 20), (105, 105)),
199
+ )
200
+
201
+ assert stitched.shape == frame_ref.shape
202
+ numpy.testing.assert_array_almost_equal(frame_ref, stitched)
203
+
204
+
205
+ @pytest.mark.parametrize("dtype", (numpy.float16, numpy.float32))
206
+ def test_stitch_horizontally_raw_frames(dtype):
207
+ """
208
+ ensure a stitching with 3 frames and different overlap can be done along axis 1
209
+ """
210
+ ref_frame_width = 256
211
+ frame_ref = PhantomGenerator.get2DPhantomSheppLogan(n=ref_frame_width).astype(dtype)
212
+
213
+ # split the frame into several part
214
+ frame_1 = frame_ref[:, 0:100]
215
+ frame_2 = frame_ref[:, 80:164]
216
+ frame_3 = frame_ref[:, 154:]
217
+
218
+ kernel_1 = ImageStichOverlapKernel(frame_unstitched_axis_size=ref_frame_width, overlap_size=20, stitching_axis=1)
219
+ kernel_2 = ImageStichOverlapKernel(frame_unstitched_axis_size=ref_frame_width, overlap_size=10, stitching_axis=1)
220
+
221
+ stitched = stitch_raw_frames(
222
+ frames=(frame_1, frame_2, frame_3),
223
+ output_dtype=dtype,
224
+ overlap_kernels=(kernel_1, kernel_2),
225
+ raw_frames_compositions=None,
226
+ overlap_frames_compositions=None,
227
+ key_lines=(
228
+ (
229
+ 90, # frame_1 height - kernel_1 height / 2.0
230
+ 10, # kernel_1 height / 2.0
231
+ ),
232
+ (
233
+ 79, # frame_2 height - kernel_2 height / 2.0 ou 102-20 ?
234
+ 5, # kernel_2 height / 2.0
235
+ ),
236
+ ),
237
+ )
238
+
239
+ assert stitched.shape == frame_ref.shape
240
+ numpy.testing.assert_array_almost_equal(frame_ref, stitched)
@@ -1,4 +1,4 @@
1
- from nabu.stitching.utils import has_itk, find_shift_with_itk
1
+ from nabu.stitching.utils.utils import has_itk, find_shift_with_itk
2
2
  from scipy.ndimage import shift as shift_scipy
3
3
  import numpy
4
4
  import pytest
@@ -0,0 +1,132 @@
1
+ import os
2
+ import pytest
3
+ import numpy
4
+ from tqdm import tqdm
5
+
6
+ from nabu.stitching.y_stitching import y_stitching
7
+ from nabu.stitching.config import PreProcessedYStitchingConfiguration
8
+ from nxtomo.application.nxtomo import NXtomo
9
+ from nxtomo.nxobject.nxdetector import ImageKey
10
+ from tomoscan.esrf.scan.nxtomoscan import NXtomoScan
11
+
12
+
13
+ def build_nxtomos(output_dir, flip_lr, flip_ud) -> tuple:
14
+ r"""
15
+ build two nxtomos in output_dir and return the list of NXtomos ready to be stitched
16
+ /\
17
+ | ______________ ______________
18
+ | |~ ~~| |~ |
19
+ | |~ nxtomo 1 ~~| |~ nxtomo 0 |
20
+ Z | |~ frame ~~| |~ frame |
21
+ |______________| |______________|
22
+ <-----------------------------------------------
23
+ 90 40 0
24
+ y (in acquisition space)
25
+ * ~: represent the overlap area
26
+ """
27
+ dark_data = numpy.array([0] * 64 * 120, dtype=numpy.float32).reshape((64, 120))
28
+ flat_data = numpy.array([1] * 64 * 120, dtype=numpy.float32).reshape((64, 120))
29
+ normalized_data = numpy.linspace(128, 1024, num=64 * 120, dtype=numpy.float32).reshape((64, 120))
30
+ if flip_lr:
31
+ dark_data = numpy.fliplr(dark_data)
32
+ flat_data = numpy.fliplr(flat_data)
33
+ normalized_data = numpy.fliplr(normalized_data)
34
+ if flip_ud:
35
+ dark_data = numpy.flipud(dark_data)
36
+ flat_data = numpy.flipud(flat_data)
37
+ normalized_data = numpy.flipud(normalized_data)
38
+
39
+ raw_data = (normalized_data + dark_data) * (flat_data + dark_data)
40
+
41
+ # create raw data
42
+ scans = []
43
+ slices = (slice(0, 80), slice(60, -1))
44
+ frame_y_positions = (40, 90)
45
+ for i_nxtomo, (my_slice, frame_y_position) in enumerate(zip(slices, frame_y_positions)):
46
+ my_raw_data = raw_data[:, my_slice]
47
+ assert my_raw_data.ndim == 2
48
+ my_dark_data = dark_data[:, my_slice]
49
+ assert my_dark_data.ndim == 2
50
+ my_flat_data = flat_data[:, my_slice]
51
+ assert my_flat_data.ndim == 2
52
+
53
+ n_projs = 3
54
+ nx_tomo = NXtomo()
55
+ nx_tomo.sample.x_translation = [0] * (n_projs + 2)
56
+ nx_tomo.sample.y_translation = [frame_y_position] * (n_projs + 2)
57
+ nx_tomo.sample.z_translation = [0] * (n_projs + 2)
58
+ nx_tomo.sample.rotation_angle = numpy.linspace(0, 180, num=(n_projs + 2), endpoint=False)
59
+ nx_tomo.instrument.detector.image_key_control = (
60
+ ImageKey.DARK_FIELD,
61
+ ImageKey.FLAT_FIELD,
62
+ ImageKey.PROJECTION,
63
+ ImageKey.PROJECTION,
64
+ ImageKey.PROJECTION,
65
+ )
66
+ nx_tomo.instrument.detector.x_pixel_size = 1.0
67
+ nx_tomo.instrument.detector.y_pixel_size = 1.0
68
+ nx_tomo.instrument.detector.distance = 2.3
69
+ nx_tomo.energy = 19.2
70
+ nx_tomo.instrument.detector.data = numpy.stack(
71
+ (
72
+ my_dark_data,
73
+ my_flat_data,
74
+ my_raw_data,
75
+ my_raw_data,
76
+ my_raw_data,
77
+ )
78
+ )
79
+
80
+ file_path = os.path.join(output_dir, f"nxtomo_{i_nxtomo}.nx")
81
+ entry = f"entry000{i_nxtomo}"
82
+ nx_tomo.save(file_path=file_path, data_path=entry)
83
+ scans.append(NXtomoScan(scan=file_path, entry=entry))
84
+ return scans, frame_y_positions, normalized_data
85
+
86
+
87
+ @pytest.mark.parametrize("flip_lr", (True, False))
88
+ @pytest.mark.parametrize("flip_ud", (True, False))
89
+ @pytest.mark.parametrize("progress", (None, "with_tqdm"))
90
+ def test_preprocessing_stitching(tmp_path, flip_lr, flip_ud, progress):
91
+ if progress == "with_tqdm":
92
+ progress = tqdm(total=100)
93
+
94
+ nxtomo_dir = tmp_path / "nxtomos"
95
+ nxtomo_dir.mkdir()
96
+ output_dir = tmp_path / "output"
97
+ output_dir.mkdir()
98
+
99
+ output_file_path = os.path.join(output_dir, "nxtomo.nxs")
100
+
101
+ nxtomos, _, normalized_data = build_nxtomos(
102
+ output_dir=nxtomo_dir,
103
+ flip_lr=flip_lr,
104
+ flip_ud=flip_ud,
105
+ )
106
+
107
+ configuration = PreProcessedYStitchingConfiguration(
108
+ input_scans=nxtomos,
109
+ axis_0_pos_px=None,
110
+ axis_1_pos_px=None,
111
+ axis_2_pos_px=None,
112
+ output_file_path=output_file_path,
113
+ output_data_path="stitched_volume",
114
+ )
115
+
116
+ output_identifier = y_stitching(
117
+ configuration=configuration,
118
+ progress=progress,
119
+ )
120
+ created_nx_tomo = NXtomo().load(
121
+ file_path=output_identifier.file_path,
122
+ data_path=output_identifier.data_path,
123
+ detector_data_as="as_numpy_array",
124
+ )
125
+ assert created_nx_tomo.instrument.detector.data.shape == (
126
+ 3,
127
+ 64,
128
+ 120,
129
+ ) # 3 == number of projections, dark and flat will not be exported when doing the stitching
130
+ # TODO: improve me: the relative tolerance is pretty high. This doesn't comes from the algorithm on itself
131
+ # but more on the numerical calculation and the flat field normalization
132
+ numpy.testing.assert_allclose(normalized_data, created_nx_tomo.instrument.detector.data[0], rtol=0.06)