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.
- 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/__init__.py +0 -0
- 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 -2281
- nabu/testutils.py +1 -152
- nabu/thirdparty/tomocupy_remove_stripe.py +43 -9
- nabu/utils.py +158 -61
- {nabu-2024.1.10.dist-info → nabu-2024.2.0.dist-info}/METADATA +24 -17
- {nabu-2024.1.10.dist-info → nabu-2024.2.0.dist-info}/RECORD +145 -121
- {nabu-2024.1.10.dist-info → nabu-2024.2.0.dist-info}/WHEEL +1 -1
- 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.10.dist-info → nabu-2024.2.0.dist-info}/LICENSE +0 -0
- {nabu-2024.1.10.dist-info → nabu-2024.2.0.dist-info}/entry_points.txt +0 -0
- {nabu-2024.1.10.dist-info → nabu-2024.2.0.dist-info}/top_level.txt +0 -0
@@ -4,17 +4,16 @@ import pytest
|
|
4
4
|
import scipy.ndimage
|
5
5
|
import h5py
|
6
6
|
from nabu.testutils import utilstest, __do_long_tests__
|
7
|
-
from nabu.testutils import get_data
|
7
|
+
from nabu.testutils import get_data
|
8
8
|
|
9
9
|
from nabu.estimation.cor import (
|
10
10
|
CenterOfRotation,
|
11
11
|
CenterOfRotationAdaptiveSearch,
|
12
12
|
CenterOfRotationGrowingWindow,
|
13
13
|
CenterOfRotationSlidingWindow,
|
14
|
-
CenterOfRotationFourierAngles,
|
15
14
|
CenterOfRotationOctaveAccurate,
|
16
15
|
)
|
17
|
-
from nabu.estimation.cor_sino import SinoCor
|
16
|
+
from nabu.estimation.cor_sino import SinoCor, CenterOfRotationFourierAngles, CenterOfRotationVo, __have_algotom__
|
18
17
|
|
19
18
|
|
20
19
|
@pytest.fixture(scope="class")
|
@@ -51,7 +50,6 @@ def bootstrap_cor_fourier(request):
|
|
51
50
|
cls.sinos = a["sinos"]
|
52
51
|
cls.angles = a["angles"]
|
53
52
|
cls.true_cor = a["true_cor"]
|
54
|
-
cls.estimated_cor_from_motor = a["estimated_cor_from_motor"]
|
55
53
|
|
56
54
|
|
57
55
|
def get_cor_data_h5(*dataset_path):
|
@@ -64,11 +62,9 @@ def get_cor_data_h5(*dataset_path):
|
|
64
62
|
dataset_downloaded_path = utilstest.getfile(dataset_relpath)
|
65
63
|
with h5py.File(dataset_downloaded_path, "r") as hf:
|
66
64
|
data = hf["/entry/instrument/detector/data"][()]
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
cor_highlow_pix = hf["/calibration/alignment/highlow/x_rotation_axis_pixel_position"][()]
|
71
|
-
tilt_deg = hf["/calibration/alignment/highlow/z_camera_tilt"][()]
|
65
|
+
cor_global_pix = hf["/calibration/alignment/global/x_rotation_axis_pixel_position"][()][0]
|
66
|
+
cor_highlow_pix = hf["/calibration/alignment/highlow/x_rotation_axis_pixel_position"][()][0]
|
67
|
+
tilt_deg = hf["/calibration/alignment/highlow/z_camera_tilt"][()][0]
|
72
68
|
|
73
69
|
return data, (cor_global_pix, cor_highlow_pix, tilt_deg)
|
74
70
|
|
@@ -109,14 +105,16 @@ class TestCor:
|
|
109
105
|
radio2 = np.fliplr(self.data[1, :, :])
|
110
106
|
|
111
107
|
CoR_calc = CenterOfRotation()
|
112
|
-
cor_position = CoR_calc.find_shift(radio1, radio2)
|
108
|
+
cor_position = CoR_calc.find_shift(radio1, radio2, return_relative_to_middle=True)
|
113
109
|
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
114
110
|
|
115
111
|
message = "Computed CoR %f " % cor_position + " and real CoR %f do not coincide" % self.cor_gl_pix
|
116
112
|
assert np.isclose(self.cor_gl_pix, cor_position, atol=self.abs_tol), message
|
117
113
|
|
118
114
|
# testing again with the validity return value
|
119
|
-
cor_position, result_validity = CoR_calc.find_shift(
|
115
|
+
cor_position, result_validity = CoR_calc.find_shift(
|
116
|
+
radio1, radio2, return_validity=True, return_relative_to_middle=True
|
117
|
+
)
|
120
118
|
assert np.isscalar(cor_position)
|
121
119
|
|
122
120
|
message = (
|
@@ -133,7 +131,7 @@ class TestCor:
|
|
133
131
|
radio2 = np.random.poisson(np.fliplr(radio2) * 400)
|
134
132
|
|
135
133
|
CoR_calc = CenterOfRotation()
|
136
|
-
cor_position = CoR_calc.find_shift(radio1, radio2, median_filt_shape=(3, 3))
|
134
|
+
cor_position = CoR_calc.find_shift(radio1, radio2, median_filt_shape=(3, 3), return_relative_to_middle=True)
|
137
135
|
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
138
136
|
|
139
137
|
message = "Computed CoR %f " % cor_position + " and real CoR %f do not coincide" % self.cor_gl_pix
|
@@ -157,8 +155,7 @@ class TestCor:
|
|
157
155
|
|
158
156
|
CoR_calc = CenterOfRotation()
|
159
157
|
|
160
|
-
|
161
|
-
cor_position = CoR_calc.find_shift(radio1, radio2, low_pass=(6.0, 0.3))
|
158
|
+
cor_position = CoR_calc.find_shift(radio1, radio2, low_pass=(6.0, 0.3), return_relative_to_middle=True)
|
162
159
|
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
163
160
|
|
164
161
|
message = "Computed CoR %f " % cor_position + " and real CoR %f do not coincide" % self.cor_gl_pix
|
@@ -168,7 +165,7 @@ class TestCor:
|
|
168
165
|
def test_half_tomo_cor_exp(self):
|
169
166
|
"""test the half_tomo algorithm on experimental data"""
|
170
167
|
|
171
|
-
radios =
|
168
|
+
radios = get_data("ha_autocor_radios.npz")
|
172
169
|
radio1 = radios["radio1"]
|
173
170
|
radio2 = radios["radio2"]
|
174
171
|
cor_pos = radios["cor_pos"]
|
@@ -177,20 +174,22 @@ class TestCor:
|
|
177
174
|
|
178
175
|
CoR_calc = CenterOfRotationAdaptiveSearch()
|
179
176
|
|
180
|
-
cor_position = CoR_calc.find_shift(
|
177
|
+
cor_position = CoR_calc.find_shift(
|
178
|
+
radio1, radio2, low_pass=1, high_pass=20, filtered_cost=True, return_relative_to_middle=True
|
179
|
+
)
|
181
180
|
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
182
181
|
|
183
182
|
message = (
|
184
183
|
"Computed CoR %f " % cor_position
|
185
|
-
+ " and real CoR %f should coincide when using the halftomo algorithm with
|
184
|
+
+ " and real CoR %f should coincide when using the halftomo algorithm with half tomo data" % cor_pos
|
186
185
|
)
|
187
|
-
assert np.isclose(cor_pos, cor_position, atol=self.abs_tol), message
|
186
|
+
assert np.isclose(cor_pos, cor_position, atol=self.abs_tol + 0.5), message
|
188
187
|
|
189
188
|
@pytest.mark.skipif(not (__do_long_tests__), reason="need environment variable NABU_LONG_TESTS=1")
|
190
189
|
def test_half_tomo_cor_exp_limited(self):
|
191
190
|
"""test the hal_tomo algorithm on experimental data and global search with limits"""
|
192
191
|
|
193
|
-
radios =
|
192
|
+
radios = get_data("ha_autocor_radios.npz")
|
194
193
|
radio1 = radios["radio1"]
|
195
194
|
radio2 = radios["radio2"]
|
196
195
|
cor_pos = radios["cor_pos"]
|
@@ -200,15 +199,22 @@ class TestCor:
|
|
200
199
|
CoR_calc = CenterOfRotationAdaptiveSearch()
|
201
200
|
|
202
201
|
cor_position, result_validity = CoR_calc.find_shift(
|
203
|
-
radio1,
|
202
|
+
radio1,
|
203
|
+
radio2,
|
204
|
+
low_pass=1,
|
205
|
+
high_pass=20,
|
206
|
+
margins=(100, 10),
|
207
|
+
filtered_cost=False,
|
208
|
+
return_validity=True,
|
209
|
+
return_relative_to_middle=True,
|
204
210
|
)
|
205
211
|
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
206
212
|
|
207
213
|
message = (
|
208
214
|
"Computed CoR %f " % cor_position
|
209
|
-
+ " and real CoR %f should coincide when using the halftomo algorithm with
|
215
|
+
+ " and real CoR %f should coincide when using the halftomo algorithm with half tomo data" % cor_pos
|
210
216
|
)
|
211
|
-
assert np.isclose(cor_pos, cor_position, atol=self.abs_tol), message
|
217
|
+
assert np.isclose(cor_pos, cor_position, atol=self.abs_tol + 0.5), message
|
212
218
|
|
213
219
|
message = "returned result_validity is %s " % result_validity + " while it should be sound"
|
214
220
|
|
@@ -219,7 +225,7 @@ class TestCor:
|
|
219
225
|
radio2 = np.fliplr(self.data[1, :, :])
|
220
226
|
|
221
227
|
CoR_calc = CenterOfRotation()
|
222
|
-
cor_position = CoR_calc.find_shift(radio1, radio2, padding_mode="edge")
|
228
|
+
cor_position = CoR_calc.find_shift(radio1, radio2, padding_mode="edge", return_relative_to_middle=True)
|
223
229
|
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
224
230
|
|
225
231
|
message = "Computed CoR %f " % cor_position + " and real CoR %f do not coincide" % self.cor_gl_pix
|
@@ -232,7 +238,7 @@ class TestCor:
|
|
232
238
|
radio2 = self.data[1, :, :]
|
233
239
|
|
234
240
|
with pytest.raises(ValueError) as ex:
|
235
|
-
CoR_calc.find_shift(radio1, radio2)
|
241
|
+
CoR_calc.find_shift(radio1, radio2, return_relative_to_middle=True)
|
236
242
|
|
237
243
|
message = "Error should have been raised about img #1 shape, other error raised instead:\n%s" % str(ex.value)
|
238
244
|
assert "Images need to be 2-dimensional. Shape of image #1" in str(ex.value), message
|
@@ -244,7 +250,7 @@ class TestCor:
|
|
244
250
|
radio2 = self.data
|
245
251
|
|
246
252
|
with pytest.raises(ValueError) as ex:
|
247
|
-
CoR_calc.find_shift(radio1, radio2)
|
253
|
+
CoR_calc.find_shift(radio1, radio2, return_relative_to_middle=True)
|
248
254
|
|
249
255
|
message = "Error should have been raised about img #2 shape, other error raised instead:\n%s" % str(ex.value)
|
250
256
|
assert "Images need to be 2-dimensional. Shape of image #2" in str(ex.value), message
|
@@ -256,7 +262,7 @@ class TestCor:
|
|
256
262
|
radio2 = self.data[1, :, 0:10]
|
257
263
|
|
258
264
|
with pytest.raises(ValueError) as ex:
|
259
|
-
CoR_calc.find_shift(radio1, radio2)
|
265
|
+
CoR_calc.find_shift(radio1, radio2, return_relative_to_middle=True)
|
260
266
|
|
261
267
|
message = (
|
262
268
|
"Error should have been raised about different image shapes, "
|
@@ -274,7 +280,11 @@ class TestCorWindowSlide:
|
|
274
280
|
|
275
281
|
CoR_calc = CenterOfRotationSlidingWindow()
|
276
282
|
cor_position = CoR_calc.find_shift(
|
277
|
-
radio1,
|
283
|
+
radio1,
|
284
|
+
radio2,
|
285
|
+
side="left",
|
286
|
+
window_width=round(radio1.shape[-1] / 4.0 * 3.0),
|
287
|
+
return_relative_to_middle=True,
|
278
288
|
)
|
279
289
|
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
280
290
|
|
@@ -282,7 +292,12 @@ class TestCorWindowSlide:
|
|
282
292
|
assert np.isclose(self.cor_gl_pix, cor_position, atol=self.abs_tol), message
|
283
293
|
|
284
294
|
cor_position, result_validity = CoR_calc.find_shift(
|
285
|
-
radio1,
|
295
|
+
radio1,
|
296
|
+
radio2,
|
297
|
+
side="left",
|
298
|
+
window_width=round(radio1.shape[-1] / 4.0 * 3.0),
|
299
|
+
return_validity=True,
|
300
|
+
return_relative_to_middle=True,
|
286
301
|
)
|
287
302
|
|
288
303
|
message = "returned result_validity is %s " % result_validity + " while it should be sound"
|
@@ -294,7 +309,7 @@ class TestCorWindowSlide:
|
|
294
309
|
radio2 = np.fliplr(self.data[1, :, :])
|
295
310
|
|
296
311
|
CoR_calc = CenterOfRotationSlidingWindow()
|
297
|
-
cor_position = CoR_calc.find_shift(radio1, radio2, side="center")
|
312
|
+
cor_position = CoR_calc.find_shift(radio1, radio2, side="center", return_relative_to_middle=True)
|
298
313
|
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
299
314
|
|
300
315
|
message = "Computed CoR %f " % cor_position + " and expected CoR %f do not coincide" % self.cor_gl_pix
|
@@ -305,7 +320,7 @@ class TestCorWindowSlide:
|
|
305
320
|
radio2 = np.fliplr(self.data_ha_proj[1, :, :])
|
306
321
|
|
307
322
|
CoR_calc = CenterOfRotationSlidingWindow()
|
308
|
-
cor_position = CoR_calc.find_shift(radio1, radio2, side="right")
|
323
|
+
cor_position = CoR_calc.find_shift(radio1, radio2, side="right", return_relative_to_middle=True)
|
309
324
|
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
310
325
|
|
311
326
|
message = "Computed CoR %f " % cor_position + " and expected CoR %f do not coincide" % self.cor_ha_pr_pix
|
@@ -316,7 +331,7 @@ class TestCorWindowSlide:
|
|
316
331
|
radio2 = self.data_ha_proj[1, :, :]
|
317
332
|
|
318
333
|
CoR_calc = CenterOfRotationSlidingWindow()
|
319
|
-
cor_position = CoR_calc.find_shift(radio1, radio2, side="left")
|
334
|
+
cor_position = CoR_calc.find_shift(radio1, radio2, side="left", return_relative_to_middle=True)
|
320
335
|
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
321
336
|
|
322
337
|
message = "Computed CoR %f " % cor_position + " and expected CoR %f do not coincide" % -self.cor_ha_pr_pix
|
@@ -327,7 +342,7 @@ class TestCorWindowSlide:
|
|
327
342
|
sino2 = np.fliplr(self.data_ha_sino[1, :, :])
|
328
343
|
|
329
344
|
CoR_calc = CenterOfRotationSlidingWindow()
|
330
|
-
cor_position = CoR_calc.find_shift(sino1, sino2, side="right")
|
345
|
+
cor_position = CoR_calc.find_shift(sino1, sino2, side="right", return_relative_to_middle=True)
|
331
346
|
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
332
347
|
|
333
348
|
message = "Computed CoR %f " % cor_position + " and expected CoR %f do not coincide" % self.cor_ha_sn_pix
|
@@ -342,7 +357,7 @@ class TestCorWindowGrow:
|
|
342
357
|
radio2 = np.fliplr(self.data[1, :, :])
|
343
358
|
|
344
359
|
CoR_calc = CenterOfRotationGrowingWindow()
|
345
|
-
cor_position = CoR_calc.find_shift(radio1, radio2, side="center")
|
360
|
+
cor_position = CoR_calc.find_shift(radio1, radio2, side="center", return_relative_to_middle=True)
|
346
361
|
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
347
362
|
message = "Computed CoR %f " % cor_position + " and expected CoR %f do not coincide" % self.cor_gl_pix
|
348
363
|
assert np.isclose(self.cor_gl_pix, cor_position, atol=self.abs_tol), message
|
@@ -352,7 +367,7 @@ class TestCorWindowGrow:
|
|
352
367
|
radio2 = np.fliplr(self.data_ha_proj[1, :, :])
|
353
368
|
|
354
369
|
CoR_calc = CenterOfRotationGrowingWindow()
|
355
|
-
cor_position = CoR_calc.find_shift(radio1, radio2, side="right")
|
370
|
+
cor_position = CoR_calc.find_shift(radio1, radio2, side="right", return_relative_to_middle=True)
|
356
371
|
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
357
372
|
|
358
373
|
message = "Computed CoR %f " % cor_position + " and expected CoR %f do not coincide" % self.cor_ha_pr_pix
|
@@ -363,13 +378,15 @@ class TestCorWindowGrow:
|
|
363
378
|
radio2 = self.data_ha_proj[1, :, :]
|
364
379
|
|
365
380
|
CoR_calc = CenterOfRotationGrowingWindow()
|
366
|
-
cor_position = CoR_calc.find_shift(radio1, radio2, side="left")
|
381
|
+
cor_position = CoR_calc.find_shift(radio1, radio2, side="left", return_relative_to_middle=True)
|
367
382
|
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
368
383
|
|
369
384
|
message = "Computed CoR %f " % cor_position + " and expected CoR %f do not coincide" % -self.cor_ha_pr_pix
|
370
385
|
assert np.isclose(-self.cor_ha_pr_pix, cor_position, atol=self.abs_tol), message
|
371
386
|
|
372
|
-
cor_position, result_validity = CoR_calc.find_shift(
|
387
|
+
cor_position, result_validity = CoR_calc.find_shift(
|
388
|
+
radio1, radio2, side="left", return_validity=True, return_relative_to_middle=True
|
389
|
+
)
|
373
390
|
|
374
391
|
message = "returned result_validity is %s " % result_validity + " while it should be sound"
|
375
392
|
|
@@ -380,7 +397,7 @@ class TestCorWindowGrow:
|
|
380
397
|
radio2 = np.fliplr(self.data_ha_proj[1, :, :])
|
381
398
|
|
382
399
|
CoR_calc = CenterOfRotationGrowingWindow()
|
383
|
-
cor_position = CoR_calc.find_shift(radio1, radio2, side="all")
|
400
|
+
cor_position = CoR_calc.find_shift(radio1, radio2, side="all", return_relative_to_middle=True)
|
384
401
|
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
385
402
|
|
386
403
|
message = "Computed CoR %f " % cor_position + " and expected CoR %f do not coincide" % self.cor_ha_pr_pix
|
@@ -391,7 +408,7 @@ class TestCorWindowGrow:
|
|
391
408
|
sino2 = np.fliplr(self.data_ha_sino[1, :, :])
|
392
409
|
|
393
410
|
CoR_calc = CenterOfRotationGrowingWindow()
|
394
|
-
cor_position = CoR_calc.find_shift(sino1, sino2, side="right")
|
411
|
+
cor_position = CoR_calc.find_shift(sino1, sino2, side="right", return_relative_to_middle=True)
|
395
412
|
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
396
413
|
|
397
414
|
message = "Computed CoR %f " % cor_position + " and expected CoR %f do not coincide" % self.cor_ha_sn_pix
|
@@ -421,7 +438,9 @@ class TestCorOctaveAccurate:
|
|
421
438
|
def test_cor_accurate_positive_shift(self):
|
422
439
|
detector_width = self.image_pair_stylo[0].shape[1]
|
423
440
|
CoR_calc = CenterOfRotationOctaveAccurate()
|
424
|
-
cor_position = CoR_calc.find_shift(
|
441
|
+
cor_position = CoR_calc.find_shift(
|
442
|
+
self.image_pair_stylo[0], np.fliplr(self.image_pair_stylo[1]), "center", return_relative_to_middle=True
|
443
|
+
)
|
425
444
|
cor_position = cor_position + detector_width / 2
|
426
445
|
assert np.isscalar(cor_position), f"cor_position expected to be a scalar, {type(cor_position)} returned"
|
427
446
|
message = f"Computed CoR {cor_position} and expected CoR {self.cor_pos_abs_stylo} do not coincide."
|
@@ -431,7 +450,10 @@ class TestCorOctaveAccurate:
|
|
431
450
|
detector_width = self.image_pair_blc12781[0].shape[1]
|
432
451
|
CoR_calc = CenterOfRotationOctaveAccurate()
|
433
452
|
cor_position = CoR_calc.find_shift(
|
434
|
-
self.image_pair_blc12781[0],
|
453
|
+
self.image_pair_blc12781[0],
|
454
|
+
np.fliplr(self.image_pair_blc12781[1]),
|
455
|
+
"center",
|
456
|
+
return_relative_to_middle=True,
|
435
457
|
)
|
436
458
|
cor_position = cor_position + detector_width / 2
|
437
459
|
assert np.isscalar(cor_position), f"cor_position expected to be a scalar, {type(cor_position)} returned"
|
@@ -441,32 +463,76 @@ class TestCorOctaveAccurate:
|
|
441
463
|
|
442
464
|
@pytest.mark.usefixtures("bootstrap_cor_fourier", "bootstrap_cor_win")
|
443
465
|
class TestCorFourierAngle:
|
466
|
+
@pytest.mark.skip("Broken function")
|
444
467
|
def test_sino_right_axis_with_near_pos(self):
|
445
|
-
|
446
|
-
sino2 = np.fliplr(self.data_ha_sino[1, :, :])
|
468
|
+
sino = np.vstack([self.data_ha_sino[0], self.data_ha_sino[1]])
|
447
469
|
start_angle = np.pi / 4
|
448
|
-
angles = np.linspace(start_angle, start_angle + 2 * np.pi,
|
470
|
+
angles = np.linspace(start_angle, start_angle + 2 * np.pi, sino.shape[0])
|
449
471
|
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
472
|
+
CoR_calc = CenterOfRotationFourierAngles()
|
473
|
+
cor_position = CoR_calc.find_shift(
|
474
|
+
sino, angles, side="right", crop_around_cor=True, return_relative_to_middle=True
|
475
|
+
) # side=sino.shape[1]/2+740)
|
454
476
|
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
455
477
|
|
456
478
|
message = "Computed CoR %f " % cor_position + " and expected CoR %f do not coincide" % self.cor_ha_sn_pix
|
457
479
|
assert np.isclose(self.cor_ha_sn_pix, cor_position, atol=self.abs_tol * 3), message
|
458
480
|
|
459
|
-
def
|
460
|
-
sino1 = self.data_ha_sino[0, :, :]
|
461
|
-
sino2 = np.fliplr(self.data_ha_sino[1, :, :])
|
462
|
-
start_angle = np.pi / 4
|
463
|
-
angles = np.linspace(start_angle, start_angle + 2 * np.pi, 2 * sino1.shape[0])
|
464
|
-
|
465
|
-
cor_options = {"side": "ignore", "refine": True}
|
481
|
+
def test_sino_right_axis_with_near_pos_jl(self):
|
466
482
|
|
467
|
-
CoR_calc = CenterOfRotationFourierAngles(
|
468
|
-
cor_position = CoR_calc.find_shift(
|
483
|
+
CoR_calc = CenterOfRotationFourierAngles()
|
484
|
+
cor_position = CoR_calc.find_shift(
|
485
|
+
self.sinos, self.angles, side="right", crop_around_cor=True, return_relative_to_middle=True
|
486
|
+
) # side=sino.shape[1]/2+740)
|
469
487
|
assert np.isscalar(cor_position), f"cor_position expected to be a scale, {type(cor_position)} returned"
|
470
488
|
|
471
|
-
message = "Computed CoR %f " % cor_position + " and expected CoR %f do not coincide" % self.
|
472
|
-
assert np.isclose(self.
|
489
|
+
message = "Computed CoR %f " % cor_position + " and expected CoR %f do not coincide" % self.true_cor
|
490
|
+
assert np.isclose(self.true_cor, cor_position, atol=self.abs_tol * 3), message
|
491
|
+
|
492
|
+
|
493
|
+
@pytest.fixture(scope="class")
|
494
|
+
def bootstrap_vo_cor(request):
|
495
|
+
cls = request.cls
|
496
|
+
cls.tol = 1e-2
|
497
|
+
cls.test_sinograms = {name: get_data("sino_%s.npz" % name) for name in ["pencil", "coffee", "mousebrains"]}
|
498
|
+
sino_bamboo = get_data("sino_bamboo_hercules_for_test.npz")
|
499
|
+
cls.test_sinograms["bamboo_hercules"] = {
|
500
|
+
"data": sino_bamboo["sinos"],
|
501
|
+
# FIXME the test file needs to be re-generated, "true_cor" has an incorrect offset
|
502
|
+
"cor": sino_bamboo["true_cor"] + (2560) / 2,
|
503
|
+
}
|
504
|
+
|
505
|
+
|
506
|
+
@pytest.mark.skipif(not (__have_algotom__), reason="need algotom for this test")
|
507
|
+
@pytest.mark.usefixtures("bootstrap_vo_cor")
|
508
|
+
class TestVoCOR:
|
509
|
+
|
510
|
+
def _test_cor(self, dataset_name, tolerance=1e-2, **cor_options):
|
511
|
+
cor_finder = CenterOfRotationVo()
|
512
|
+
cor = cor_finder.find_shift(
|
513
|
+
self.test_sinograms[dataset_name]["data"], return_relative_to_middle=False, **cor_options
|
514
|
+
)
|
515
|
+
cor_ref = self.test_sinograms[dataset_name]["cor"]
|
516
|
+
assert (
|
517
|
+
np.abs(cor - cor_ref) < tolerance
|
518
|
+
), "CoR estimation failed for %s: expected %.3f, got %.3f (tol = %.2e)" % (
|
519
|
+
dataset_name,
|
520
|
+
cor_ref,
|
521
|
+
cor,
|
522
|
+
tolerance,
|
523
|
+
)
|
524
|
+
|
525
|
+
def test_cor_180(self):
|
526
|
+
self._test_cor("pencil", tolerance=0.6)
|
527
|
+
|
528
|
+
def test_cor_180_more_complex(self): ...
|
529
|
+
|
530
|
+
def test_cor_360_halftomo(self):
|
531
|
+
self._test_cor("bamboo_hercules", tolerance=0.1, halftomo=True)
|
532
|
+
|
533
|
+
def test_cor_360_halftomo_hard(self):
|
534
|
+
# This one is difficult
|
535
|
+
self._test_cor("mousebrains", tolerance=2, halftomo=True)
|
536
|
+
|
537
|
+
def test_cor_360_not_halftomo(self):
|
538
|
+
self._test_cor("coffee", tolerance=0.5, halftomo=False, is_360=True)
|
@@ -46,13 +46,13 @@ def get_focus_data(*dataset_path):
|
|
46
46
|
]
|
47
47
|
)
|
48
48
|
|
49
|
-
angle_best_ind = hf["/calibration/focus/angle/best_img"][()]
|
50
|
-
angle_best_pos = hf["/calibration/focus/angle/best_pos"][()]
|
51
|
-
angle_tilt_v = hf["/calibration/focus/angle/tilt_v_rad"][()]
|
52
|
-
angle_tilt_h = hf["/calibration/focus/angle/tilt_h_rad"][()]
|
49
|
+
angle_best_ind = hf["/calibration/focus/angle/best_img"][()][0]
|
50
|
+
angle_best_pos = hf["/calibration/focus/angle/best_pos"][()][0]
|
51
|
+
angle_tilt_v = hf["/calibration/focus/angle/tilt_v_rad"][()][0]
|
52
|
+
angle_tilt_h = hf["/calibration/focus/angle/tilt_h_rad"][()][0]
|
53
53
|
|
54
|
-
std_best_ind = hf["/calibration/focus/std/best_img"][()]
|
55
|
-
std_best_pos = hf["/calibration/focus/std/best_pos"][()]
|
54
|
+
std_best_ind = hf["/calibration/focus/std/best_img"][()][0]
|
55
|
+
std_best_pos = hf["/calibration/focus/std/best_pos"][()][0]
|
56
56
|
|
57
57
|
calib_data_angle = (angle_best_ind, angle_best_pos, angle_tilt_v, angle_tilt_h)
|
58
58
|
calib_data_std = (std_best_ind, std_best_pos)
|
nabu/estimation/tilt.py
CHANGED
@@ -188,7 +188,7 @@ class CameraTilt(CenterOfRotation):
|
|
188
188
|
img_fft_1 = img_fft_1[..., : img_fft_1.shape[-2] // 2, :]
|
189
189
|
img_fft_2 = img_fft_2[..., : img_fft_2.shape[-2] // 2, :]
|
190
190
|
|
191
|
-
tilt_pix = self.find_shift(img_fft_1, img_fft_2, shift_axis=-2)
|
191
|
+
tilt_pix = self.find_shift(img_fft_1, img_fft_2, shift_axis=-2, return_relative_to_middle=True)
|
192
192
|
tilt_deg = -(360 / img_shape[0]) * tilt_pix
|
193
193
|
|
194
194
|
img_1 = skt.rotate(img_1, tilt_deg)
|
@@ -201,6 +201,7 @@ class CameraTilt(CenterOfRotation):
|
|
201
201
|
peak_fit_radius=peak_fit_radius,
|
202
202
|
high_pass=high_pass,
|
203
203
|
low_pass=low_pass,
|
204
|
+
return_relative_to_middle=True,
|
204
205
|
)
|
205
206
|
|
206
207
|
if self.verbose:
|
nabu/estimation/utils.py
CHANGED
@@ -1,39 +1,11 @@
|
|
1
1
|
import numpy as np
|
2
|
-
from scipy.signal import find_peaks
|
3
2
|
|
4
3
|
|
5
|
-
def is_fullturn_scan(
|
4
|
+
def is_fullturn_scan(angles_rad, tol=None):
|
6
5
|
"""
|
7
6
|
Return True if the angles correspond to a full-turn (360 degrees) scan.
|
8
7
|
"""
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
maxima = find_peaks(sin_angles, distance=min_dist)[0]
|
14
|
-
minima = find_peaks(-sin_angles, distance=min_dist)[0]
|
15
|
-
n_max = maxima.size
|
16
|
-
n_min = minima.size
|
17
|
-
# abs(n_max - n_min) actually means the following:
|
18
|
-
# * 0: All turns are full (eg. 2pi, 4pi)
|
19
|
-
# * 1: At least one half-turn remains (eg. pi, 3pi)
|
20
|
-
if abs(n_max - n_min) == 0:
|
21
|
-
return True
|
22
|
-
else:
|
23
|
-
return False
|
24
|
-
|
25
|
-
|
26
|
-
def get_halfturn_indices(angles):
|
27
|
-
angles = angles % (2 * np.pi)
|
28
|
-
angles -= angles.min()
|
29
|
-
sin_angles = np.sin(angles)
|
30
|
-
min_dist = 5 # TODO find a more robust angles-based min distance, though this should cover most of the cases
|
31
|
-
maxima = find_peaks(sin_angles, distance=min_dist)[0]
|
32
|
-
minima = find_peaks(-sin_angles, distance=min_dist)[0]
|
33
|
-
extrema = np.sort(np.hstack([maxima, minima]))
|
34
|
-
extrema -= extrema.min()
|
35
|
-
extrema = np.hstack([extrema, [angles.size - 1]])
|
36
|
-
res = []
|
37
|
-
for i in range(extrema.size - 1):
|
38
|
-
res.append((extrema[i], extrema[i + 1]))
|
39
|
-
return res
|
8
|
+
angles_rad = np.sort(angles_rad)
|
9
|
+
if tol is None:
|
10
|
+
tol = np.min(np.abs(np.diff(angles_rad))) * 1.1
|
11
|
+
return np.abs((angles_rad.max() - angles_rad.min()) - (2 * np.pi)) < tol
|
nabu/io/__init__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
from .reader import NPReader, EDFReader, HDF5File, HDF5Loader, ChunkReader, Readers
|
2
|
-
from .writer import NXProcessWriter
|
2
|
+
from .writer import NXProcessWriter
|
nabu/io/cast_volume.py
CHANGED
@@ -66,7 +66,7 @@ def get_default_output_volume(
|
|
66
66
|
Constructor = EDFVolume
|
67
67
|
elif output_type == "jp2":
|
68
68
|
Constructor = JP2KVolume
|
69
|
-
return Constructor(
|
69
|
+
return Constructor( # pylint: disable=E0601
|
70
70
|
folder=os.path.join(
|
71
71
|
os.path.dirname(input_volume.data_url.file_path()),
|
72
72
|
output_dir,
|