nabu 2024.2.13__py3-none-any.whl → 2025.1.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.
- doc/doc_config.py +32 -0
- nabu/__init__.py +1 -1
- nabu/app/bootstrap_stitching.py +4 -2
- nabu/app/cast_volume.py +16 -14
- nabu/app/cli_configs.py +102 -9
- nabu/app/compare_volumes.py +1 -1
- nabu/app/composite_cor.py +2 -4
- nabu/app/diag_to_pix.py +5 -6
- nabu/app/diag_to_rot.py +10 -11
- nabu/app/double_flatfield.py +18 -5
- nabu/app/estimate_motion.py +75 -0
- nabu/app/multicor.py +28 -15
- nabu/app/parse_reconstruction_log.py +1 -0
- nabu/app/pcaflats.py +122 -0
- nabu/app/prepare_weights_double.py +1 -2
- nabu/app/reconstruct.py +1 -7
- nabu/app/reconstruct_helical.py +5 -9
- nabu/app/reduce_dark_flat.py +5 -4
- nabu/app/rotate.py +3 -1
- nabu/app/stitching.py +7 -2
- nabu/app/tests/test_reduce_dark_flat.py +2 -2
- nabu/app/validator.py +1 -4
- nabu/cuda/convolution.py +1 -1
- nabu/cuda/fft.py +1 -1
- nabu/cuda/medfilt.py +1 -1
- nabu/cuda/padding.py +1 -1
- nabu/cuda/src/backproj.cu +6 -6
- nabu/cuda/src/cone.cu +4 -0
- nabu/cuda/src/hierarchical_backproj.cu +14 -0
- nabu/cuda/utils.py +2 -2
- nabu/estimation/alignment.py +17 -31
- nabu/estimation/cor.py +27 -33
- nabu/estimation/cor_sino.py +2 -8
- nabu/estimation/focus.py +4 -8
- nabu/estimation/motion.py +557 -0
- nabu/estimation/tests/test_alignment.py +2 -0
- nabu/estimation/tests/test_motion_estimation.py +471 -0
- nabu/estimation/tests/test_tilt.py +1 -1
- nabu/estimation/tilt.py +6 -5
- nabu/estimation/translation.py +47 -1
- nabu/io/cast_volume.py +108 -18
- nabu/io/detector_distortion.py +5 -6
- nabu/io/reader.py +45 -6
- nabu/io/reader_helical.py +5 -4
- nabu/io/tests/test_cast_volume.py +2 -2
- nabu/io/tests/test_readers.py +41 -38
- nabu/io/tests/test_remove_volume.py +152 -0
- nabu/io/tests/test_writers.py +2 -2
- nabu/io/utils.py +8 -4
- nabu/io/writer.py +1 -2
- nabu/misc/fftshift.py +1 -1
- nabu/misc/fourier_filters.py +1 -1
- nabu/misc/histogram.py +1 -1
- nabu/misc/histogram_cuda.py +1 -1
- nabu/misc/padding_base.py +1 -1
- nabu/misc/rotation.py +1 -1
- nabu/misc/rotation_cuda.py +1 -1
- nabu/misc/tests/test_binning.py +1 -1
- nabu/misc/transpose.py +1 -1
- nabu/misc/unsharp.py +1 -1
- nabu/misc/unsharp_cuda.py +1 -1
- nabu/misc/unsharp_opencl.py +1 -1
- nabu/misc/utils.py +1 -1
- nabu/opencl/fft.py +1 -1
- nabu/opencl/padding.py +1 -1
- nabu/opencl/src/backproj.cl +6 -6
- nabu/opencl/utils.py +8 -8
- nabu/pipeline/config.py +2 -2
- nabu/pipeline/config_validators.py +46 -46
- nabu/pipeline/datadump.py +3 -3
- nabu/pipeline/estimators.py +271 -11
- nabu/pipeline/fullfield/chunked.py +103 -67
- nabu/pipeline/fullfield/chunked_cuda.py +5 -2
- nabu/pipeline/fullfield/computations.py +4 -1
- nabu/pipeline/fullfield/dataset_validator.py +0 -1
- nabu/pipeline/fullfield/get_double_flatfield.py +147 -0
- nabu/pipeline/fullfield/nabu_config.py +36 -17
- nabu/pipeline/fullfield/processconfig.py +41 -7
- nabu/pipeline/fullfield/reconstruction.py +14 -10
- nabu/pipeline/helical/dataset_validator.py +3 -4
- nabu/pipeline/helical/fbp.py +4 -4
- nabu/pipeline/helical/filtering.py +5 -4
- nabu/pipeline/helical/gridded_accumulator.py +10 -11
- nabu/pipeline/helical/helical_chunked_regridded.py +1 -0
- nabu/pipeline/helical/helical_reconstruction.py +12 -9
- nabu/pipeline/helical/helical_utils.py +1 -2
- nabu/pipeline/helical/nabu_config.py +2 -1
- nabu/pipeline/helical/span_strategy.py +1 -0
- nabu/pipeline/helical/weight_balancer.py +2 -3
- nabu/pipeline/params.py +20 -3
- nabu/pipeline/tests/__init__.py +0 -0
- nabu/pipeline/tests/test_estimators.py +240 -3
- nabu/pipeline/utils.py +1 -1
- nabu/pipeline/writer.py +1 -1
- nabu/preproc/alignment.py +0 -10
- nabu/preproc/ccd.py +53 -3
- nabu/preproc/ctf.py +8 -8
- nabu/preproc/ctf_cuda.py +1 -1
- nabu/preproc/double_flatfield_cuda.py +2 -2
- nabu/preproc/double_flatfield_variable_region.py +0 -1
- nabu/preproc/flatfield.py +307 -2
- nabu/preproc/flatfield_cuda.py +1 -2
- nabu/preproc/flatfield_variable_region.py +3 -3
- nabu/preproc/phase.py +2 -4
- nabu/preproc/phase_cuda.py +2 -2
- nabu/preproc/shift.py +4 -2
- nabu/preproc/shift_cuda.py +0 -1
- nabu/preproc/tests/test_ctf.py +4 -4
- nabu/preproc/tests/test_double_flatfield.py +1 -1
- nabu/preproc/tests/test_flatfield.py +1 -1
- nabu/preproc/tests/test_paganin.py +1 -3
- nabu/preproc/tests/test_pcaflats.py +154 -0
- nabu/preproc/tests/test_vshift.py +4 -1
- nabu/processing/azim.py +9 -5
- nabu/processing/convolution_cuda.py +6 -4
- nabu/processing/fft_base.py +7 -3
- nabu/processing/fft_cuda.py +25 -164
- nabu/processing/fft_opencl.py +28 -6
- nabu/processing/fftshift.py +1 -1
- nabu/processing/histogram.py +1 -1
- nabu/processing/muladd.py +0 -1
- nabu/processing/padding_base.py +1 -1
- nabu/processing/padding_cuda.py +0 -2
- nabu/processing/processing_base.py +12 -6
- nabu/processing/rotation_cuda.py +3 -1
- nabu/processing/tests/test_fft.py +2 -64
- nabu/processing/tests/test_fftshift.py +1 -1
- nabu/processing/tests/test_medfilt.py +1 -3
- nabu/processing/tests/test_padding.py +1 -1
- nabu/processing/tests/test_roll.py +1 -1
- nabu/processing/tests/test_rotation.py +4 -2
- nabu/processing/unsharp_opencl.py +1 -1
- nabu/reconstruction/astra.py +245 -0
- nabu/reconstruction/cone.py +39 -9
- nabu/reconstruction/fbp.py +14 -0
- nabu/reconstruction/fbp_base.py +40 -8
- nabu/reconstruction/fbp_opencl.py +8 -0
- nabu/reconstruction/filtering.py +59 -25
- nabu/reconstruction/filtering_cuda.py +22 -21
- nabu/reconstruction/filtering_opencl.py +10 -14
- nabu/reconstruction/hbp.py +26 -13
- nabu/reconstruction/mlem.py +55 -16
- nabu/reconstruction/projection.py +3 -5
- nabu/reconstruction/sinogram.py +1 -1
- nabu/reconstruction/sinogram_cuda.py +0 -1
- nabu/reconstruction/tests/test_cone.py +37 -2
- nabu/reconstruction/tests/test_deringer.py +4 -4
- nabu/reconstruction/tests/test_fbp.py +36 -15
- nabu/reconstruction/tests/test_filtering.py +27 -7
- nabu/reconstruction/tests/test_halftomo.py +28 -2
- nabu/reconstruction/tests/test_mlem.py +94 -64
- nabu/reconstruction/tests/test_projector.py +7 -2
- nabu/reconstruction/tests/test_reconstructor.py +1 -1
- nabu/reconstruction/tests/test_sino_normalization.py +0 -1
- nabu/resources/dataset_analyzer.py +210 -24
- nabu/resources/gpu.py +4 -4
- nabu/resources/logger.py +4 -4
- nabu/resources/nxflatfield.py +103 -37
- nabu/resources/tests/test_dataset_analyzer.py +37 -0
- nabu/resources/tests/test_extract.py +11 -0
- nabu/resources/tests/test_nxflatfield.py +5 -5
- nabu/resources/utils.py +16 -10
- nabu/stitching/alignment.py +8 -11
- nabu/stitching/config.py +44 -35
- nabu/stitching/definitions.py +2 -2
- nabu/stitching/frame_composition.py +8 -10
- nabu/stitching/overlap.py +4 -4
- nabu/stitching/sample_normalization.py +5 -5
- nabu/stitching/slurm_utils.py +2 -2
- nabu/stitching/stitcher/base.py +2 -0
- nabu/stitching/stitcher/dumper/base.py +0 -1
- nabu/stitching/stitcher/dumper/postprocessing.py +1 -1
- nabu/stitching/stitcher/post_processing.py +11 -9
- nabu/stitching/stitcher/pre_processing.py +37 -31
- nabu/stitching/stitcher/single_axis.py +2 -3
- nabu/stitching/stitcher_2D.py +2 -1
- nabu/stitching/tests/test_config.py +10 -11
- nabu/stitching/tests/test_sample_normalization.py +1 -1
- nabu/stitching/tests/test_slurm_utils.py +1 -2
- nabu/stitching/tests/test_y_preprocessing_stitching.py +11 -8
- nabu/stitching/tests/test_z_postprocessing_stitching.py +3 -3
- nabu/stitching/tests/test_z_preprocessing_stitching.py +27 -24
- nabu/stitching/utils/tests/__init__.py +0 -0
- nabu/stitching/utils/tests/test_post-processing.py +1 -0
- nabu/stitching/utils/utils.py +16 -18
- nabu/tests.py +0 -3
- nabu/testutils.py +62 -9
- nabu/utils.py +50 -20
- {nabu-2024.2.13.dist-info → nabu-2025.1.0.dist-info}/METADATA +7 -7
- nabu-2025.1.0.dist-info/RECORD +328 -0
- {nabu-2024.2.13.dist-info → nabu-2025.1.0.dist-info}/WHEEL +1 -1
- {nabu-2024.2.13.dist-info → nabu-2025.1.0.dist-info}/entry_points.txt +2 -1
- nabu/app/correct_rot.py +0 -70
- nabu/io/tests/test_detector_distortion.py +0 -178
- nabu-2024.2.13.dist-info/RECORD +0 -317
- /nabu/{stitching → app}/tests/__init__.py +0 -0
- {nabu-2024.2.13.dist-info → nabu-2025.1.0.dist-info}/licenses/LICENSE +0 -0
- {nabu-2024.2.13.dist-info → nabu-2025.1.0.dist-info}/top_level.txt +0 -0
|
@@ -88,7 +88,7 @@ class ProcessingBase:
|
|
|
88
88
|
self.allocate_array(array_name, array_ref.shape, dtype=dtype)
|
|
89
89
|
getattr(self, array_name).set(array_ref)
|
|
90
90
|
else:
|
|
91
|
-
raise
|
|
91
|
+
raise TypeError("Expected numpy array or pycuda array")
|
|
92
92
|
return getattr(self, array_name)
|
|
93
93
|
|
|
94
94
|
def get_array(self, array_name):
|
|
@@ -99,6 +99,15 @@ class ProcessingBase:
|
|
|
99
99
|
_recover_arrays_references = recover_arrays_references
|
|
100
100
|
_allocate_array = allocate_array
|
|
101
101
|
_set_array = set_array
|
|
102
|
+
# --
|
|
103
|
+
|
|
104
|
+
def is_contiguous(self, arr):
|
|
105
|
+
if isinstance(arr, self.array_class):
|
|
106
|
+
return arr.flags.c_contiguous
|
|
107
|
+
elif isinstance(arr, np.ndarray):
|
|
108
|
+
return arr.flags["C_CONTIGUOUS"]
|
|
109
|
+
else:
|
|
110
|
+
raise TypeError
|
|
102
111
|
|
|
103
112
|
def check_array(self, arr, expected_shape, expected_dtype="f", check_contiguous=True):
|
|
104
113
|
"""
|
|
@@ -108,11 +117,8 @@ class ProcessingBase:
|
|
|
108
117
|
raise ValueError("Expected shape %s but got %s" % (str(expected_shape), str(arr.shape)))
|
|
109
118
|
if arr.dtype != np.dtype(expected_dtype):
|
|
110
119
|
raise ValueError("Expected data type %s but got %s" % (str(expected_dtype), str(arr.dtype)))
|
|
111
|
-
if check_contiguous:
|
|
112
|
-
|
|
113
|
-
raise ValueError("Expected C-contiguous array")
|
|
114
|
-
if isinstance(arr, self.array_class) and not arr.flags.c_contiguous:
|
|
115
|
-
raise ValueError("Expected C-contiguous array")
|
|
120
|
+
if check_contiguous and not (self.is_contiguous(arr)):
|
|
121
|
+
raise ValueError("Expected C-contiguous array")
|
|
116
122
|
|
|
117
123
|
def kernel(self, *args, **kwargs):
|
|
118
124
|
raise ValueError("Base class")
|
nabu/processing/rotation_cuda.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
from .rotation import Rotation
|
|
3
3
|
from ..utils import get_cuda_srcfile, updiv
|
|
4
|
-
from ..cuda.utils import __has_pycuda__, copy_array
|
|
4
|
+
from ..cuda.utils import __has_pycuda__, copy_array, check_textures_availability
|
|
5
5
|
from ..cuda.processing import CudaProcessing
|
|
6
6
|
|
|
7
7
|
if __has_pycuda__:
|
|
@@ -11,6 +11,8 @@ if __has_pycuda__:
|
|
|
11
11
|
|
|
12
12
|
class CudaRotation(Rotation):
|
|
13
13
|
def __init__(self, shape, angle, center=None, mode="edge", reshape=False, cuda_options=None, **sk_kwargs):
|
|
14
|
+
if not (check_textures_availability()):
|
|
15
|
+
raise RuntimeError("Need cuda textures for this class")
|
|
14
16
|
if center is None:
|
|
15
17
|
center = ((shape[1] - 1) / 2.0, (shape[0] - 1) / 2.0)
|
|
16
18
|
super().__init__(shape, angle, center=center, mode=mode, reshape=reshape, **sk_kwargs)
|
|
@@ -4,14 +4,13 @@ import numpy as np
|
|
|
4
4
|
from scipy.fft import fftn, ifftn, rfftn, irfftn
|
|
5
5
|
from nabu.testutils import generate_tests_scenarios, get_data, get_array_of_given_shape, __do_long_tests__
|
|
6
6
|
from nabu.cuda.utils import get_cuda_context, __has_pycuda__
|
|
7
|
-
from nabu.processing.fft_cuda import
|
|
7
|
+
from nabu.processing.fft_cuda import VKCUFFT, get_available_fft_implems
|
|
8
8
|
from nabu.opencl.utils import __has_pyopencl__, get_opencl_context
|
|
9
9
|
from nabu.processing.fft_opencl import VKCLFFT, has_vkfft as has_cl_vkfft
|
|
10
10
|
from nabu.processing.fft_base import is_fast_axes
|
|
11
11
|
|
|
12
12
|
available_cuda_fft = get_available_fft_implems()
|
|
13
13
|
__has_vkfft__ = "vkfft" in available_cuda_fft
|
|
14
|
-
__has_skcuda__ = "skcuda" in available_cuda_fft
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
scenarios = {
|
|
@@ -113,67 +112,6 @@ class TestFFT:
|
|
|
113
112
|
ref = ref_ifft_func(data, axes=axes)
|
|
114
113
|
return ref
|
|
115
114
|
|
|
116
|
-
@pytest.mark.skipif(
|
|
117
|
-
not (__has_skcuda__ and __has_pycuda__), reason="Need pycuda and (scikit-cuda or vkfft) for this test"
|
|
118
|
-
)
|
|
119
|
-
@pytest.mark.parametrize("config", scenarios)
|
|
120
|
-
def test_sckcuda(self, config):
|
|
121
|
-
r2c = config["r2c"]
|
|
122
|
-
shape = config["shape"]
|
|
123
|
-
precision = config["precision"]
|
|
124
|
-
ndim = len(shape)
|
|
125
|
-
if ndim == 3 and not (__do_long_tests__):
|
|
126
|
-
pytest.skip("3D FFTs are done only for long tests - use NABU_LONG_TESTS=1")
|
|
127
|
-
|
|
128
|
-
data = self._get_data_array(config)
|
|
129
|
-
|
|
130
|
-
res, cufft = self._do_fft(data, r2c, return_fft_obj=True, backend_cls=SKCUFFT)
|
|
131
|
-
ref = self._do_reference_fft(data, r2c)
|
|
132
|
-
|
|
133
|
-
tol = self.abs_tol[precision][ndim]
|
|
134
|
-
self.check_result(res, ref, config, tol, name="skcuda")
|
|
135
|
-
|
|
136
|
-
# Complex-to-complex can also be performed on real data (as in numpy.fft.fft(real_data))
|
|
137
|
-
if not (r2c):
|
|
138
|
-
res = self._do_fft(data, False, backend_cls=SKCUFFT)
|
|
139
|
-
ref = self._do_reference_fft(data, False)
|
|
140
|
-
self.check_result(res, ref, config, tol, name="skcuda")
|
|
141
|
-
|
|
142
|
-
# IFFT
|
|
143
|
-
res = cufft.ifft(cufft.output_fft).get()
|
|
144
|
-
self.check_result(res, data, config, tol, name="skcuda")
|
|
145
|
-
# Perhaps we should also check against numpy/scipy ifft,
|
|
146
|
-
# but it does not yield the good shape for R2C on odd-sized data
|
|
147
|
-
|
|
148
|
-
@pytest.mark.skipif(
|
|
149
|
-
not (__has_skcuda__ and __has_pycuda__), reason="Need pycuda and (scikit-cuda or vkfft) for this test"
|
|
150
|
-
)
|
|
151
|
-
@pytest.mark.parametrize("config", scenarios)
|
|
152
|
-
def test_skcuda_batched(self, config):
|
|
153
|
-
shape = config["shape"]
|
|
154
|
-
if len(shape) == 1:
|
|
155
|
-
return
|
|
156
|
-
elif len(shape) == 3 and not (__do_long_tests__):
|
|
157
|
-
pytest.skip("3D FFTs are done only for long tests - use NABU_LONG_TESTS=1")
|
|
158
|
-
r2c = config["r2c"]
|
|
159
|
-
tol = self.abs_tol[config["precision"]][len(shape)]
|
|
160
|
-
|
|
161
|
-
data = self._get_data_array(config)
|
|
162
|
-
|
|
163
|
-
if data.ndim == 2:
|
|
164
|
-
axes_to_test = [(0,), (1,)]
|
|
165
|
-
elif data.ndim == 3:
|
|
166
|
-
# axes_to_test = [(1, 2), (2, 1), (2,)] # See fft.py: works for C2C but not R2C ?
|
|
167
|
-
axes_to_test = [(2,)]
|
|
168
|
-
|
|
169
|
-
for axes in axes_to_test:
|
|
170
|
-
res, cufft = self._do_fft(data, r2c, axes=axes, return_fft_obj=True, backend_cls=SKCUFFT)
|
|
171
|
-
ref = self._do_reference_fft(data, r2c, axes=axes)
|
|
172
|
-
self.check_result(res, ref, config, tol, name="skcuda batched axes=%s" % (str(axes)))
|
|
173
|
-
# IFFT
|
|
174
|
-
res = cufft.ifft(cufft.output_fft).get()
|
|
175
|
-
self.check_result(res, data, config, tol, name="skcuda")
|
|
176
|
-
|
|
177
115
|
@pytest.mark.parametrize("config", scenarios)
|
|
178
116
|
def test_vkfft(self, config):
|
|
179
117
|
backend = config["backend"]
|
|
@@ -190,7 +128,7 @@ class TestFFT:
|
|
|
190
128
|
pytest.skip("R2C with odd-sized fast dimension is not supported in VKFFT")
|
|
191
129
|
|
|
192
130
|
# FIXME - vkfft + POCL fail for R2C in one dimension
|
|
193
|
-
if config["backend"] == "opencl" and r2c and ndim == 1:
|
|
131
|
+
if config["backend"] == "opencl" and r2c and ndim == 1: # noqa: SIM102
|
|
194
132
|
if self.cl_ctx.devices[0].platform.name.strip().lower() == "portable computing language":
|
|
195
133
|
pytest.skip("Something wrong with vkfft + pocl for R2C 1D")
|
|
196
134
|
# ---
|
|
@@ -2,7 +2,7 @@ import numpy as np
|
|
|
2
2
|
import pytest
|
|
3
3
|
from nabu.cuda.utils import get_cuda_context, __has_pycuda__
|
|
4
4
|
from nabu.opencl.utils import __has_pyopencl__, get_opencl_context
|
|
5
|
-
from nabu.testutils import get_data, generate_tests_scenarios
|
|
5
|
+
from nabu.testutils import get_data, generate_tests_scenarios
|
|
6
6
|
|
|
7
7
|
if __has_pyopencl__:
|
|
8
8
|
from nabu.processing.fftshift import OpenCLFFTshift
|
|
@@ -34,16 +34,14 @@ def bootstrap(request):
|
|
|
34
34
|
|
|
35
35
|
@pytest.mark.skipif(not (__has_pycuda__), reason="Need Cuda/pycuda for this test")
|
|
36
36
|
@pytest.mark.usefixtures("bootstrap")
|
|
37
|
-
class TestMedianFilter
|
|
37
|
+
class TestMedianFilter:
|
|
38
38
|
@classmethod
|
|
39
39
|
def allocate_numpy_arrays(cls):
|
|
40
|
-
shape = cls.data.shape
|
|
41
40
|
cls.input = cls.data
|
|
42
41
|
cls.input3d = np.tile(cls.input, (2, 1, 1))
|
|
43
42
|
|
|
44
43
|
@classmethod
|
|
45
44
|
def allocate_cuda_arrays(cls):
|
|
46
|
-
shape = cls.data.shape
|
|
47
45
|
cls.d_input = garray.to_gpu(cls.input)
|
|
48
46
|
cls.d_output = garray.zeros_like(cls.d_input)
|
|
49
47
|
cls.d_input3d = garray.to_gpu(cls.input3d)
|
|
@@ -108,7 +108,7 @@ class TestPadding:
|
|
|
108
108
|
d_img.set(data)
|
|
109
109
|
d_out = padding.processing.allocate_array("d_out", padding.padded_shape, dtype="f")
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
padding.pad(d_img, output=d_out)
|
|
112
112
|
|
|
113
113
|
ref = np.roll(np.pad(data, pad_width, mode=mode), (-pad_width[0][0], -pad_width[1][0]), axis=(0, 1))
|
|
114
114
|
|
|
@@ -2,7 +2,7 @@ import numpy as np
|
|
|
2
2
|
import pytest
|
|
3
3
|
from nabu.cuda.utils import get_cuda_context, __has_pycuda__
|
|
4
4
|
from nabu.opencl.utils import __has_pyopencl__, get_opencl_context
|
|
5
|
-
from nabu.testutils import get_data, generate_tests_scenarios
|
|
5
|
+
from nabu.testutils import get_data, generate_tests_scenarios
|
|
6
6
|
from nabu.processing.roll_opencl import OpenCLRoll
|
|
7
7
|
|
|
8
8
|
configs_roll = {
|
|
@@ -3,7 +3,7 @@ import pytest
|
|
|
3
3
|
from nabu.testutils import generate_tests_scenarios
|
|
4
4
|
from nabu.processing.rotation_cuda import Rotation
|
|
5
5
|
from nabu.processing.rotation import __have__skimage__
|
|
6
|
-
from nabu.cuda.utils import __has_pycuda__, get_cuda_context
|
|
6
|
+
from nabu.cuda.utils import __has_pycuda__, get_cuda_context, check_textures_availability
|
|
7
7
|
|
|
8
8
|
if __have__skimage__:
|
|
9
9
|
from skimage.transform import rotate
|
|
@@ -68,7 +68,9 @@ class TestRotation:
|
|
|
68
68
|
res = R(self.image)
|
|
69
69
|
self._check_result(res, config, 1e-6)
|
|
70
70
|
|
|
71
|
-
@pytest.mark.skipif(
|
|
71
|
+
@pytest.mark.skipif(
|
|
72
|
+
not (__has_pycuda__) or not (check_textures_availability()), reason="Need cuda rotation (and textures)"
|
|
73
|
+
)
|
|
72
74
|
@pytest.mark.parametrize("config", scenarios)
|
|
73
75
|
def test_cuda_rotation(self, config):
|
|
74
76
|
R = CudaRotation(
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# ruff: noqa
|
|
2
|
+
try:
|
|
3
|
+
import astra
|
|
4
|
+
|
|
5
|
+
__have_astra__ = True
|
|
6
|
+
except ImportError:
|
|
7
|
+
__have_astra__ = False
|
|
8
|
+
astra = None
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AstraReconstructor:
|
|
12
|
+
"""
|
|
13
|
+
Base class for reconstructors based on the Astra toolbox
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
default_extra_options = {
|
|
17
|
+
"axis_correction": None,
|
|
18
|
+
"clip_outer_circle": False,
|
|
19
|
+
"scale_factor": None,
|
|
20
|
+
"filter_cutoff": 1.0,
|
|
21
|
+
"outer_circle_value": 0.0,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
sinos_shape,
|
|
27
|
+
angles=None,
|
|
28
|
+
volume_shape=None,
|
|
29
|
+
rot_center=None,
|
|
30
|
+
pixel_size=None,
|
|
31
|
+
padding_mode="zeros",
|
|
32
|
+
filter_name=None,
|
|
33
|
+
slice_roi=None,
|
|
34
|
+
cuda_options=None,
|
|
35
|
+
extra_options=None,
|
|
36
|
+
):
|
|
37
|
+
self._configure_extra_options(extra_options)
|
|
38
|
+
self._init_cuda(cuda_options)
|
|
39
|
+
self._set_sino_shape(sinos_shape)
|
|
40
|
+
self._orig_prog_geom = None
|
|
41
|
+
self._init_geometry(
|
|
42
|
+
source_origin_dist,
|
|
43
|
+
origin_detector_dist,
|
|
44
|
+
pixel_size,
|
|
45
|
+
angles,
|
|
46
|
+
volume_shape,
|
|
47
|
+
rot_center,
|
|
48
|
+
relative_z_position,
|
|
49
|
+
slice_roi,
|
|
50
|
+
)
|
|
51
|
+
self._init_fdk(padding_mode, filter_name)
|
|
52
|
+
self._alg_id = None
|
|
53
|
+
self._vol_id = None
|
|
54
|
+
self._proj_id = None
|
|
55
|
+
|
|
56
|
+
def _configure_extra_options(self, extra_options):
|
|
57
|
+
self.extra_options = self.default_extra_options.copy()
|
|
58
|
+
self.extra_options.update(extra_options or {})
|
|
59
|
+
|
|
60
|
+
def _init_cuda(self, cuda_options):
|
|
61
|
+
cuda_options = cuda_options or {}
|
|
62
|
+
self.cuda = CudaProcessing(**cuda_options)
|
|
63
|
+
|
|
64
|
+
def _set_sino_shape(self, sinos_shape):
|
|
65
|
+
if len(sinos_shape) != 3:
|
|
66
|
+
raise ValueError("Expected a 3D shape")
|
|
67
|
+
self.sinos_shape = sinos_shape
|
|
68
|
+
self.n_sinos, self.n_angles, self.prj_width = sinos_shape
|
|
69
|
+
|
|
70
|
+
def _set_pixel_size(self, pixel_size):
|
|
71
|
+
if pixel_size is None:
|
|
72
|
+
det_spacing_y = det_spacing_x = 1
|
|
73
|
+
elif np.iterable(pixel_size):
|
|
74
|
+
det_spacing_y, det_spacing_x = pixel_size
|
|
75
|
+
else:
|
|
76
|
+
# assuming scalar
|
|
77
|
+
det_spacing_y = det_spacing_x = pixel_size
|
|
78
|
+
self._det_spacing_y = det_spacing_y
|
|
79
|
+
self._det_spacing_x = det_spacing_x
|
|
80
|
+
|
|
81
|
+
def _set_slice_roi(self, slice_roi):
|
|
82
|
+
self.slice_roi = slice_roi
|
|
83
|
+
self._vol_geom_n_x = self.n_x
|
|
84
|
+
self._vol_geom_n_y = self.n_y
|
|
85
|
+
self._crop_data = True
|
|
86
|
+
if slice_roi is None:
|
|
87
|
+
return
|
|
88
|
+
start_x, end_x, start_y, end_y = slice_roi
|
|
89
|
+
if roi_is_centered(self.volume_shape[1:], (slice(start_y, end_y), slice(start_x, end_x))):
|
|
90
|
+
# Astra can only reconstruct subregion centered around the origin
|
|
91
|
+
self._vol_geom_n_x = self.n_x - start_x * 2
|
|
92
|
+
self._vol_geom_n_y = self.n_y - start_y * 2
|
|
93
|
+
else:
|
|
94
|
+
raise NotImplementedError(
|
|
95
|
+
"Astra supports only slice_roi centered around origin (got slice_roi=%s with n_x=%d, n_y=%d)"
|
|
96
|
+
% (str(slice_roi), self.n_x, self.n_y)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def _init_geometry(
|
|
100
|
+
self,
|
|
101
|
+
source_origin_dist,
|
|
102
|
+
origin_detector_dist,
|
|
103
|
+
pixel_size,
|
|
104
|
+
angles,
|
|
105
|
+
volume_shape,
|
|
106
|
+
rot_center,
|
|
107
|
+
relative_z_position,
|
|
108
|
+
slice_roi,
|
|
109
|
+
):
|
|
110
|
+
if angles is None:
|
|
111
|
+
self.angles = np.linspace(0, 2 * np.pi, self.n_angles, endpoint=True)
|
|
112
|
+
else:
|
|
113
|
+
self.angles = angles
|
|
114
|
+
if volume_shape is None:
|
|
115
|
+
volume_shape = (self.sinos_shape[0], self.sinos_shape[2], self.sinos_shape[2])
|
|
116
|
+
self.volume_shape = volume_shape
|
|
117
|
+
self.n_z, self.n_y, self.n_x = self.volume_shape
|
|
118
|
+
self.source_origin_dist = source_origin_dist
|
|
119
|
+
self.origin_detector_dist = origin_detector_dist
|
|
120
|
+
self.magnification = 1 + origin_detector_dist / source_origin_dist
|
|
121
|
+
self._set_slice_roi(slice_roi)
|
|
122
|
+
self.vol_geom = astra.create_vol_geom(self._vol_geom_n_y, self._vol_geom_n_x, self.n_z)
|
|
123
|
+
self.vol_shape = astra.geom_size(self.vol_geom)
|
|
124
|
+
self._cor_shift = 0.0
|
|
125
|
+
self.rot_center = rot_center
|
|
126
|
+
if rot_center is not None:
|
|
127
|
+
self._cor_shift = (self.sinos_shape[-1] - 1) / 2.0 - rot_center
|
|
128
|
+
self._set_pixel_size(pixel_size)
|
|
129
|
+
self._axis_corrections = self.extra_options.get("axis_correction", None)
|
|
130
|
+
self._create_astra_proj_geometry(relative_z_position)
|
|
131
|
+
|
|
132
|
+
def _create_astra_proj_geometry(self, relative_z_position):
|
|
133
|
+
# This object has to be re-created each time, because once the modifications below are done,
|
|
134
|
+
# it is no more a "cone" geometry but a "cone_vec" geometry, and cannot be updated subsequently
|
|
135
|
+
# (see astra/functions.py:271)
|
|
136
|
+
self.proj_geom = astra.create_proj_geom(
|
|
137
|
+
"cone",
|
|
138
|
+
self._det_spacing_x,
|
|
139
|
+
self._det_spacing_y,
|
|
140
|
+
self.n_sinos,
|
|
141
|
+
self.prj_width,
|
|
142
|
+
self.angles,
|
|
143
|
+
self.source_origin_dist,
|
|
144
|
+
self.origin_detector_dist,
|
|
145
|
+
)
|
|
146
|
+
self.relative_z_position = relative_z_position or 0.0
|
|
147
|
+
# This will turn the geometry of type "cone" into a geometry of type "cone_vec"
|
|
148
|
+
if self._orig_prog_geom is None:
|
|
149
|
+
self._orig_prog_geom = self.proj_geom
|
|
150
|
+
self.proj_geom = astra.geom_postalignment(self.proj_geom, (self._cor_shift, 0))
|
|
151
|
+
# (src, detector_center, u, v) = (srcX, srcY, srcZ, dX, dY, dZ, uX, uY, uZ, vX, vY, vZ)
|
|
152
|
+
vecs = self.proj_geom["Vectors"]
|
|
153
|
+
|
|
154
|
+
# To adapt the center of rotation:
|
|
155
|
+
# dX = cor_shift * cos(theta) - origin_detector_dist * sin(theta)
|
|
156
|
+
# dY = origin_detector_dist * cos(theta) + cor_shift * sin(theta)
|
|
157
|
+
if self._axis_corrections is not None:
|
|
158
|
+
# should we check that dX and dY match the above formulas ?
|
|
159
|
+
cor_shifts = self._cor_shift + self._axis_corrections
|
|
160
|
+
vecs[:, 3] = cor_shifts * np.cos(self.angles) - self.origin_detector_dist * np.sin(self.angles)
|
|
161
|
+
vecs[:, 4] = self.origin_detector_dist * np.cos(self.angles) + cor_shifts * np.sin(self.angles)
|
|
162
|
+
|
|
163
|
+
# To adapt the z position:
|
|
164
|
+
# Component 2 of vecs is the z coordinate of the source, component 5 is the z component of the detector position
|
|
165
|
+
# We need to re-create the same inclination of the cone beam, thus we need to keep the inclination of the two z positions.
|
|
166
|
+
# The detector is centered on the rotation axis, thus moving it up or down, just moves it out of the reconstruction volume.
|
|
167
|
+
# We can bring back the detector in the correct volume position, by applying a rigid translation of both the detector and the source.
|
|
168
|
+
# The translation is exactly the amount that brought the detector up or down, but in the opposite direction.
|
|
169
|
+
vecs[:, 2] = -self.relative_z_position
|
|
170
|
+
|
|
171
|
+
def _set_output(self, volume):
|
|
172
|
+
if volume is not None:
|
|
173
|
+
expected_shape = self.vol_shape # if not (self._crop_data) else self._output_cropped_shape
|
|
174
|
+
self.cuda.check_array(volume, expected_shape)
|
|
175
|
+
self.cuda.set_array("output", volume)
|
|
176
|
+
if volume is None:
|
|
177
|
+
self.cuda.allocate_array("output", self.vol_shape)
|
|
178
|
+
d_volume = self.cuda.get_array("output")
|
|
179
|
+
z, y, x = d_volume.shape
|
|
180
|
+
self._vol_link = astra.data3d.GPULink(d_volume.ptr, x, y, z, d_volume.strides[-2])
|
|
181
|
+
self._vol_id = astra.data3d.link("-vol", self.vol_geom, self._vol_link)
|
|
182
|
+
|
|
183
|
+
def _set_input(self, sinos):
|
|
184
|
+
self.cuda.check_array(sinos, self.sinos_shape)
|
|
185
|
+
self.cuda.set_array("sinos", sinos) # self.cuda.sinos is now a GPU array
|
|
186
|
+
# TODO don't create new link/proj_id if ptr is the same ?
|
|
187
|
+
# But it seems Astra modifies the input sinogram while doing FDK, so this might be not relevant
|
|
188
|
+
d_sinos = self.cuda.get_array("sinos")
|
|
189
|
+
|
|
190
|
+
# self._proj_data_link = astra.data3d.GPULink(d_sinos.ptr, self.prj_width, self.n_angles, self.n_z, sinos.strides[-2])
|
|
191
|
+
self._proj_data_link = astra.data3d.GPULink(
|
|
192
|
+
d_sinos.ptr, self.prj_width, self.n_angles, self.n_sinos, d_sinos.strides[-2]
|
|
193
|
+
)
|
|
194
|
+
self._proj_id = astra.data3d.link("-sino", self.proj_geom, self._proj_data_link)
|
|
195
|
+
|
|
196
|
+
def _preprocess_data(self):
|
|
197
|
+
d_sinos = self.cuda.sinos
|
|
198
|
+
for i in range(d_sinos.shape[0]):
|
|
199
|
+
self.sino_filter.filter_sino(d_sinos[i], output=d_sinos[i])
|
|
200
|
+
|
|
201
|
+
def _update_reconstruction(self):
|
|
202
|
+
cfg = astra.astra_dict("BP3D_CUDA")
|
|
203
|
+
cfg["ReconstructionDataId"] = self._vol_id
|
|
204
|
+
cfg["ProjectionDataId"] = self._proj_id
|
|
205
|
+
if self._alg_id is not None:
|
|
206
|
+
astra.algorithm.delete(self._alg_id)
|
|
207
|
+
self._alg_id = astra.algorithm.create(cfg)
|
|
208
|
+
|
|
209
|
+
def reconstruct(self, sinos, output=None, relative_z_position=None):
|
|
210
|
+
"""
|
|
211
|
+
sinos: numpy.ndarray or pycuda.gpuarray
|
|
212
|
+
Sinograms, with shape (n_sinograms, n_angles, width)
|
|
213
|
+
output: pycuda.gpuarray, optional
|
|
214
|
+
Output array. If not provided, a new numpy array is returned
|
|
215
|
+
relative_z_position: int, optional
|
|
216
|
+
Position of the central slice of the slab, with respect to the full stack of slices.
|
|
217
|
+
By default it is set to zero, meaning that the current slab is assumed in the middle of the stack
|
|
218
|
+
"""
|
|
219
|
+
self._create_astra_proj_geometry(relative_z_position)
|
|
220
|
+
self._set_input(sinos)
|
|
221
|
+
self._set_output(output)
|
|
222
|
+
self._preprocess_data()
|
|
223
|
+
self._update_reconstruction()
|
|
224
|
+
astra.algorithm.run(self._alg_id)
|
|
225
|
+
#
|
|
226
|
+
# NB: Could also be done with
|
|
227
|
+
# from astra.experimental import direct_BP3D
|
|
228
|
+
# projector_id = astra.create_projector("cuda3d", self.proj_geom, self.vol_geom, options=None)
|
|
229
|
+
# direct_BP3D(projector_id, self._vol_link, self._proj_data_link)
|
|
230
|
+
#
|
|
231
|
+
result = self.cuda.get_array("output")
|
|
232
|
+
if output is None:
|
|
233
|
+
result = result.get()
|
|
234
|
+
if self.extra_options.get("scale_factor", None) is not None:
|
|
235
|
+
result *= np.float32(self.extra_options["scale_factor"]) # in-place for pycuda
|
|
236
|
+
self.cuda.recover_arrays_references(["sinos", "output"])
|
|
237
|
+
return result
|
|
238
|
+
|
|
239
|
+
def __del__(self):
|
|
240
|
+
if getattr(self, "_alg_id", None) is not None:
|
|
241
|
+
astra.algorithm.delete(self._alg_id)
|
|
242
|
+
if getattr(self, "_vol_id", None) is not None:
|
|
243
|
+
astra.data3d.delete(self._vol_id)
|
|
244
|
+
if getattr(self, "_proj_id", None) is not None:
|
|
245
|
+
astra.data3d.delete(self._proj_id)
|
nabu/reconstruction/cone.py
CHANGED
|
@@ -31,6 +31,7 @@ class ConebeamReconstructor:
|
|
|
31
31
|
"outer_circle_value": 0.0,
|
|
32
32
|
# "use_astra_fdk": True,
|
|
33
33
|
"use_astra_fdk": False,
|
|
34
|
+
"crop_filtered_data": True,
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
def __init__(
|
|
@@ -131,6 +132,7 @@ class ConebeamReconstructor:
|
|
|
131
132
|
self._init_cuda(cuda_options)
|
|
132
133
|
self._set_sino_shape(sinos_shape)
|
|
133
134
|
self._orig_prog_geom = None
|
|
135
|
+
self._use_astra_fdk = bool(self.extra_options.get("use_astra_fdk", True))
|
|
134
136
|
self._init_geometry(
|
|
135
137
|
source_origin_dist,
|
|
136
138
|
origin_detector_dist,
|
|
@@ -149,6 +151,7 @@ class ConebeamReconstructor:
|
|
|
149
151
|
def _configure_extra_options(self, extra_options):
|
|
150
152
|
self.extra_options = self.default_extra_options.copy()
|
|
151
153
|
self.extra_options.update(extra_options or {})
|
|
154
|
+
self._crop_filtered_data = self.extra_options.get("crop_filtered_data", True)
|
|
152
155
|
|
|
153
156
|
def _init_cuda(self, cuda_options):
|
|
154
157
|
cuda_options = cuda_options or {}
|
|
@@ -162,7 +165,6 @@ class ConebeamReconstructor:
|
|
|
162
165
|
|
|
163
166
|
def _init_fdk(self, padding_mode, filter_name):
|
|
164
167
|
self.padding_mode = padding_mode
|
|
165
|
-
self._use_astra_fdk = bool(self.extra_options.get("use_astra_fdk", True))
|
|
166
168
|
if self._use_astra_fdk and padding_mode not in ["zeros", "constant", None, "none"]:
|
|
167
169
|
self._use_astra_fdk = False
|
|
168
170
|
_logger.warning("padding_mode was set to %s, cannot use native astra FDK" % padding_mode)
|
|
@@ -172,6 +174,7 @@ class ConebeamReconstructor:
|
|
|
172
174
|
self.sinos_shape[1:],
|
|
173
175
|
filter_name=filter_name,
|
|
174
176
|
padding_mode=self.padding_mode,
|
|
177
|
+
crop_filtered_data=self.extra_options.get("crop_filtered_data", True),
|
|
175
178
|
# TODO (?) configure FFT backend
|
|
176
179
|
extra_options={"cutoff": self.extra_options.get("filter_cutoff", 1.0)},
|
|
177
180
|
cuda_options={"ctx": self.cuda.ctx},
|
|
@@ -248,12 +251,18 @@ class ConebeamReconstructor:
|
|
|
248
251
|
# This object has to be re-created each time, because once the modifications below are done,
|
|
249
252
|
# it is no more a "cone" geometry but a "cone_vec" geometry, and cannot be updated subsequently
|
|
250
253
|
# (see astra/functions.py:271)
|
|
254
|
+
|
|
255
|
+
if not (self._crop_filtered_data) and hasattr(self, "sino_filter"):
|
|
256
|
+
prj_width = self.sino_filter.sino_padded_shape[-1]
|
|
257
|
+
else:
|
|
258
|
+
prj_width = self.prj_width
|
|
259
|
+
|
|
251
260
|
self.proj_geom = astra.create_proj_geom(
|
|
252
261
|
"cone",
|
|
253
262
|
self._det_spacing_x,
|
|
254
263
|
self._det_spacing_y,
|
|
255
264
|
self.n_sinos,
|
|
256
|
-
|
|
265
|
+
prj_width,
|
|
257
266
|
self.angles,
|
|
258
267
|
self.source_origin_dist,
|
|
259
268
|
self.origin_detector_dist,
|
|
@@ -283,6 +292,11 @@ class ConebeamReconstructor:
|
|
|
283
292
|
# The translation is exactly the amount that brought the detector up or down, but in the opposite direction.
|
|
284
293
|
vecs[:, 2] = -self.relative_z_position
|
|
285
294
|
|
|
295
|
+
def reset_rot_center(self, rot_center):
|
|
296
|
+
self.rot_center = rot_center
|
|
297
|
+
self._cor_shift = (self.sinos_shape[-1] - 1) / 2.0 - rot_center
|
|
298
|
+
self._create_astra_proj_geometry(self.relative_z_position)
|
|
299
|
+
|
|
286
300
|
def _set_output(self, volume):
|
|
287
301
|
if volume is not None:
|
|
288
302
|
expected_shape = self.vol_shape # if not (self._crop_data) else self._output_cropped_shape
|
|
@@ -296,15 +310,25 @@ class ConebeamReconstructor:
|
|
|
296
310
|
self._vol_id = astra.data3d.link("-vol", self.vol_geom, self._vol_link)
|
|
297
311
|
|
|
298
312
|
def _set_input(self, sinos):
|
|
299
|
-
self.cuda.check_array(sinos, self.sinos_shape)
|
|
300
|
-
self.cuda.set_array("sinos", sinos) # self.cuda.sinos is now a GPU array
|
|
313
|
+
self.cuda.check_array(sinos, self.sinos_shape, check_contiguous=False)
|
|
301
314
|
# TODO don't create new link/proj_id if ptr is the same ?
|
|
302
315
|
# But it seems Astra modifies the input sinogram while doing FDK, so this might be not relevant
|
|
303
|
-
d_sinos = self.cuda.
|
|
304
|
-
|
|
305
|
-
|
|
316
|
+
d_sinos = self.cuda.set_array("sinos", sinos) # self.cuda.sinos is now a GPU array
|
|
317
|
+
|
|
318
|
+
self._reallocate_sinos = False
|
|
319
|
+
if not (self.cuda.is_contiguous(d_sinos)) or not (self._crop_filtered_data):
|
|
320
|
+
self._reallocate_sinos = True
|
|
321
|
+
if self._crop_filtered_data:
|
|
322
|
+
sinos_shape = self.sinos_shape
|
|
323
|
+
# Sometimes, the user does not want to crop data after filtering
|
|
324
|
+
# In this case, the backprojector input should be directly the filtered-but-uncropped data.
|
|
325
|
+
# For cone-beam reconstruction, the FDK pre-weighting takes place on input sinogram (not filtered yet),
|
|
326
|
+
# then filter, then 3D backprojection the un-cropped data.
|
|
327
|
+
else:
|
|
328
|
+
sinos_shape = (self.n_z,) + self.sino_filter.sino_padded_shape
|
|
329
|
+
d_sinos = self.cuda.allocate_array("sinos_contig", sinos_shape)
|
|
306
330
|
self._proj_data_link = astra.data3d.GPULink(
|
|
307
|
-
d_sinos.ptr,
|
|
331
|
+
d_sinos.ptr, d_sinos.shape[-1], self.n_angles, self.n_sinos, d_sinos.strides[-2]
|
|
308
332
|
)
|
|
309
333
|
self._proj_id = astra.data3d.link("-sino", self.proj_geom, self._proj_data_link)
|
|
310
334
|
|
|
@@ -315,8 +339,12 @@ class ConebeamReconstructor:
|
|
|
315
339
|
fdk_preweighting(
|
|
316
340
|
d_sinos, self._orig_prog_geom, relative_z_position=self.relative_z_position, cor_shift=self._cor_shift
|
|
317
341
|
)
|
|
342
|
+
d_sinos_filtered = d_sinos
|
|
343
|
+
if self._reallocate_sinos:
|
|
344
|
+
d_sinos_filtered = self.cuda.sinos_contig
|
|
345
|
+
|
|
318
346
|
for i in range(d_sinos.shape[0]):
|
|
319
|
-
self.sino_filter.filter_sino(d_sinos[i], output=
|
|
347
|
+
self.sino_filter.filter_sino(d_sinos[i], output=d_sinos_filtered[i])
|
|
320
348
|
|
|
321
349
|
def _update_reconstruction(self):
|
|
322
350
|
if self._use_astra_fdk:
|
|
@@ -385,11 +413,13 @@ def roi_is_centered(shape, slice_):
|
|
|
385
413
|
|
|
386
414
|
|
|
387
415
|
def fdk_preweighting(d_sinos, proj_geom, relative_z_position=0.0, cor_shift=0.0):
|
|
416
|
+
discontiguous_sinograms = not (d_sinos.flags.c_contiguous)
|
|
388
417
|
|
|
389
418
|
preweight_kernel = CudaKernel(
|
|
390
419
|
"devFDK_preweight",
|
|
391
420
|
filename=get_cuda_srcfile("cone.cu"),
|
|
392
421
|
signature="Piiifffffiii",
|
|
422
|
+
options=["-DRADIOS_LAYOUT"] if discontiguous_sinograms else None,
|
|
393
423
|
)
|
|
394
424
|
|
|
395
425
|
n_z, n_angles, n_x = d_sinos.shape
|
nabu/reconstruction/fbp.py
CHANGED
|
@@ -56,6 +56,13 @@ class CudaBackprojector(BackprojectorBase):
|
|
|
56
56
|
if self._use_textures:
|
|
57
57
|
self.texref_proj = self.gpu_projector.module.get_texref(self._kernel_options["texture_name"])
|
|
58
58
|
self.texref_proj.set_filter_mode(cuda.filter_mode.LINEAR)
|
|
59
|
+
# Set boundary extension to "zero", i.e array[n] = 0 for n < 0 and n >= array.size
|
|
60
|
+
# address_mode.BORDER : extension with zeros
|
|
61
|
+
# address_mode.CLAMP : extension with edges
|
|
62
|
+
# pycuda does not tell if first argument "dim" is 0-based ?
|
|
63
|
+
self.texref_proj.set_address_mode(0, cuda.address_mode.BORDER)
|
|
64
|
+
self.texref_proj.set_address_mode(1, cuda.address_mode.BORDER)
|
|
65
|
+
self.texref_proj.set_address_mode(2, cuda.address_mode.BORDER)
|
|
59
66
|
self.gpu_projector.prepare(self._kernel_options["kernel_signature"], [self.texref_proj])
|
|
60
67
|
# Bind texture
|
|
61
68
|
self._d_sino_cua = cuda.np_to_array(np.zeros(self.sino_shape, "f"), "C")
|
|
@@ -79,6 +86,13 @@ class CudaBackprojector(BackprojectorBase):
|
|
|
79
86
|
self.sino_mult = CudaSinoMult(self.sino_shape, self.rot_center, ctx=self._processing.ctx)
|
|
80
87
|
self._prepare_textures() # has to be done after compilation for Cuda (to bind texture to built kernel)
|
|
81
88
|
|
|
89
|
+
def _get_filter_init_extra_options(self):
|
|
90
|
+
return {
|
|
91
|
+
"cuda_options": {
|
|
92
|
+
"ctx": self._processing.ctx,
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
|
|
82
96
|
def _transfer_to_texture(self, sino, do_checks=True):
|
|
83
97
|
if do_checks and not (sino.flags.c_contiguous):
|
|
84
98
|
raise ValueError("Expected C-Contiguous array")
|