nabu 2024.2.14__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 +7 -0
- nabu/reconstruction/fbp_base.py +36 -5
- 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.14.dist-info → nabu-2025.1.0.dist-info}/METADATA +7 -7
- nabu-2025.1.0.dist-info/RECORD +328 -0
- {nabu-2024.2.14.dist-info → nabu-2025.1.0.dist-info}/WHEEL +1 -1
- {nabu-2024.2.14.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.14.dist-info/RECORD +0 -317
- /nabu/{stitching → app}/tests/__init__.py +0 -0
- {nabu-2024.2.14.dist-info → nabu-2025.1.0.dist-info}/licenses/LICENSE +0 -0
- {nabu-2024.2.14.dist-info → nabu-2025.1.0.dist-info}/top_level.txt +0 -0
nabu/reconstruction/fbp_base.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import warnings
|
|
1
2
|
import numpy as np
|
|
2
3
|
from ..utils import updiv, nextpow2, convert_index, deprecation_warning
|
|
3
4
|
from ..processing.processing_base import ProcessingBase
|
|
@@ -6,6 +7,19 @@ from .sinogram import SinoMult
|
|
|
6
7
|
from .sinogram import get_extended_sinogram_width
|
|
7
8
|
|
|
8
9
|
|
|
10
|
+
def rot_center_is_in_middle_of_roi(rot_center, roi, tol=2.0):
|
|
11
|
+
# NB. tolerance should be at least 2,
|
|
12
|
+
# because in halftomo the extended sinogram width is 2*sino_width - int(2 * XXXX)
|
|
13
|
+
# (where XXX depends on whether the CoR is on the left or on the right)
|
|
14
|
+
# because of the int(2 * stuff), we can have a jump of at most two pixels.
|
|
15
|
+
#
|
|
16
|
+
start_x, end_x, start_y, end_y = roi
|
|
17
|
+
return (
|
|
18
|
+
abs((start_x + end_x - 1) / 2 - rot_center) - 0.5 < tol
|
|
19
|
+
and abs((start_y + end_y - 1) / 2 - rot_center) - 0.5 < tol
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
9
23
|
class BackprojectorBase:
|
|
10
24
|
"""
|
|
11
25
|
Base class for backprojectors.
|
|
@@ -22,6 +36,7 @@ class BackprojectorBase:
|
|
|
22
36
|
"scale_factor": None,
|
|
23
37
|
"filter_cutoff": 1.0,
|
|
24
38
|
"outer_circle_value": 0.0,
|
|
39
|
+
"crop_filtered_data": True,
|
|
25
40
|
}
|
|
26
41
|
|
|
27
42
|
kernel_filename = None
|
|
@@ -82,7 +97,7 @@ class BackprojectorBase:
|
|
|
82
97
|
backend_options: dict, optional
|
|
83
98
|
OpenCL/Cuda options passed to the OpenCLProcessing or CudaProcessing class.
|
|
84
99
|
|
|
85
|
-
Other
|
|
100
|
+
Other Parameters
|
|
86
101
|
-----------------
|
|
87
102
|
extra_options: dict, optional
|
|
88
103
|
Dictionary with a set of advanced options. The default are the following:
|
|
@@ -160,9 +175,6 @@ class BackprojectorBase:
|
|
|
160
175
|
self.axis_pos = self.rot_center
|
|
161
176
|
self._set_angles(angles, n_angles)
|
|
162
177
|
self._set_slice_roi(slice_roi)
|
|
163
|
-
#
|
|
164
|
-
# offset = start - move
|
|
165
|
-
# move = 0 if not(centered_axis) else start + (n-1)/2. - c
|
|
166
178
|
if self.extra_options["centered_axis"]:
|
|
167
179
|
self.offsets = {
|
|
168
180
|
"x": self.rot_center - (self.n_x - 1) / 2.0,
|
|
@@ -208,6 +220,19 @@ class BackprojectorBase:
|
|
|
208
220
|
end_x = convert_index(end_x, self.n_x, self.n_x)
|
|
209
221
|
end_y = convert_index(end_y, self.n_y, self.n_y)
|
|
210
222
|
self.slice_shape = (end_y - start_y, end_x - start_x)
|
|
223
|
+
if self.extra_options["centered_axis"] and not (
|
|
224
|
+
rot_center_is_in_middle_of_roi(self.rot_center, (start_x, end_x, start_y, end_y))
|
|
225
|
+
):
|
|
226
|
+
warnings.warn(
|
|
227
|
+
"Using 'centered_axis' when doing a non-centered ROI reconstruction might have side effects: 'start_xy' and 'end_xy' have a different meaning",
|
|
228
|
+
RuntimeWarning,
|
|
229
|
+
)
|
|
230
|
+
# self.extra_options["centered_axis"] = False
|
|
231
|
+
if self.extra_options.get("clip_outer_circle", False) and (
|
|
232
|
+
start_x > 2 or start_y > 2 or abs(end_y - self.n_y) > 2 or abs(end_y - self.n_y) > 2
|
|
233
|
+
):
|
|
234
|
+
warnings.warn("clip_outer_circle is not supported when doing RoI reconstruction", RuntimeWarning)
|
|
235
|
+
self.extra_options["clip_outer_circle"] = False
|
|
211
236
|
self.n_x = self.slice_shape[-1]
|
|
212
237
|
self.n_y = self.slice_shape[-2]
|
|
213
238
|
self.offsets = {"x": start_x, "y": start_y}
|
|
@@ -245,6 +270,11 @@ class BackprojectorBase:
|
|
|
245
270
|
if filter_name in ["None", "none"]:
|
|
246
271
|
self.sino_filter = None
|
|
247
272
|
return
|
|
273
|
+
|
|
274
|
+
# TODO
|
|
275
|
+
if not (self.extra_options.get("crop_filtered_data", True)):
|
|
276
|
+
warnings.warn("crop_filtered_data = False is not supported for FBP yet", RuntimeWarning)
|
|
277
|
+
#
|
|
248
278
|
sinofilter_other_kwargs = self._get_filter_init_extra_options()
|
|
249
279
|
self.sino_filter = self.SinoFilterClass(
|
|
250
280
|
self.sino_shape,
|
|
@@ -355,7 +385,7 @@ class BackprojectorBase:
|
|
|
355
385
|
|
|
356
386
|
def backproj(self, sino, output=None, do_checks=True):
|
|
357
387
|
if self.halftomo and self.rot_center < self.dwidth:
|
|
358
|
-
self.sino_mult.prepare_sino(sino)
|
|
388
|
+
sino = self.sino_mult.prepare_sino(sino)
|
|
359
389
|
self._transfer_to_texture(sino)
|
|
360
390
|
d_slice = self._set_output(output, check=do_checks)
|
|
361
391
|
self._set_kernel_slice_arg(d_slice)
|
|
@@ -377,6 +407,7 @@ class BackprojectorBase:
|
|
|
377
407
|
# if a new device array was allocated for sinogram, then the filtering can overwrite it,
|
|
378
408
|
# since it won't affect user argument
|
|
379
409
|
if id(d_sino) != id(sino):
|
|
410
|
+
# if id(d_sino) != id(sino) and self.extra_options.get("crop_filtered_data", True):
|
|
380
411
|
filt_kwargs = {"output": d_sino}
|
|
381
412
|
#
|
|
382
413
|
sino_to_backproject = self.sino_filter(d_sino, **filt_kwargs)
|
nabu/reconstruction/filtering.py
CHANGED
|
@@ -5,15 +5,6 @@ from silx.image.tomography import compute_fourier_filter, get_next_power
|
|
|
5
5
|
from ..processing.padding_base import PaddingBase
|
|
6
6
|
from ..utils import check_supported, get_num_threads
|
|
7
7
|
|
|
8
|
-
# # COMPAT.
|
|
9
|
-
# from .filtering_cuda import CudaSinoFilter
|
|
10
|
-
|
|
11
|
-
# SinoFilter = deprecated_class(
|
|
12
|
-
# "From version 2023.1, 'filtering_cuda.CudaSinoFilter' should be used instead of 'filtering.SinoFilter'. In the future, 'filtering.SinoFilter' will be a numpy-only class.",
|
|
13
|
-
# do_print=True,
|
|
14
|
-
# )(CudaSinoFilter)
|
|
15
|
-
# #
|
|
16
|
-
|
|
17
8
|
|
|
18
9
|
class SinoFilter:
|
|
19
10
|
available_filters = [
|
|
@@ -44,11 +35,44 @@ class SinoFilter:
|
|
|
44
35
|
sino_shape,
|
|
45
36
|
filter_name=None,
|
|
46
37
|
padding_mode="zeros",
|
|
38
|
+
crop_filtered_data=True,
|
|
47
39
|
extra_options=None,
|
|
48
40
|
):
|
|
41
|
+
"""
|
|
42
|
+
Initialize a SinoFilter instance.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
sino_shape: tuple
|
|
47
|
+
Shape of sinogram, in the form (n_angles, detector_width) or (n_sinos, n_angles, detector_width)
|
|
48
|
+
filter_name: str, optional
|
|
49
|
+
Name of the filter. Default is ram-lak.
|
|
50
|
+
padding_mode: str, optional
|
|
51
|
+
How to pad the data prior to filtering. Default is zero-padding, corresponding to linear convolution with the filter kernel.
|
|
52
|
+
In practice this value is often set to "edges" for interior tomography.
|
|
53
|
+
crop_filtered_data: bool, optional
|
|
54
|
+
Whether to crop the final, filtered sinogram. Default is True. See notes below.
|
|
55
|
+
extra_options: dict, optional
|
|
56
|
+
Dictionary of advanced extra options.
|
|
57
|
+
|
|
58
|
+
Notes
|
|
59
|
+
-----
|
|
60
|
+
Sinogram filtering done in the Filtered Back-Projection (FBP) method consists, in theory, in applying a high-pass filter
|
|
61
|
+
to the sinogram prior to backprojection. This high-pass filter is normally the Ramachandran-Lakshminarayanan (Ram-Lak) filter
|
|
62
|
+
yielding a close-to-ideal reconstruction (see Natterer's "Mathematical methods in image reconstruction").
|
|
63
|
+
As the filter kernel has a large extent in spatial domain, it's best performed in Fourier domain via the Fourier-convolution theorem.
|
|
64
|
+
Filtering in Fourier domain should be done with a data padded to at least twice its size.
|
|
65
|
+
Zero-padding should be used for mathematical correctness (so that multiplication in Fourier domain corresponds to an actual linear convolution).
|
|
66
|
+
However if the sinogram does not decay to "zero" near the edges (i.e in interior tomography), padding with zeros usually gives artefacts after filtering.
|
|
67
|
+
In this case, padding with edges is preferred (corresponding to a convolution with the "edges" extension mode).
|
|
68
|
+
|
|
69
|
+
After inverse Fourier transform, the (padded and filtered) data is cropped back to its original size.
|
|
70
|
+
In some cases, it's preferable to keep the data un-cropped for further processing.
|
|
71
|
+
"""
|
|
72
|
+
|
|
49
73
|
self._init_extra_options(extra_options)
|
|
50
74
|
self._set_padding_mode(padding_mode)
|
|
51
|
-
self._calculate_shapes(sino_shape)
|
|
75
|
+
self._calculate_shapes(sino_shape, crop_filtered_data)
|
|
52
76
|
self._init_fft()
|
|
53
77
|
self._allocate_memory()
|
|
54
78
|
self._compute_filter(filter_name)
|
|
@@ -67,7 +91,7 @@ class SinoFilter:
|
|
|
67
91
|
check_supported(padding_mode, self.available_padding_modes, "padding mode")
|
|
68
92
|
self.padding_mode = padding_mode
|
|
69
93
|
|
|
70
|
-
def _calculate_shapes(self, sino_shape):
|
|
94
|
+
def _calculate_shapes(self, sino_shape, crop_filtered_data):
|
|
71
95
|
self.ndim = len(sino_shape)
|
|
72
96
|
if self.ndim == 2:
|
|
73
97
|
n_angles, dwidth = sino_shape
|
|
@@ -90,6 +114,11 @@ class SinoFilter:
|
|
|
90
114
|
self.pad_left = (self.dwidth_padded - self.dwidth) // 2
|
|
91
115
|
self.pad_right = self.dwidth_padded - self.dwidth - self.pad_left
|
|
92
116
|
|
|
117
|
+
self.crop_filtered_data = crop_filtered_data
|
|
118
|
+
self.output_shape = self.sino_shape
|
|
119
|
+
if not (self.crop_filtered_data):
|
|
120
|
+
self.output_shape = self.sino_padded_shape
|
|
121
|
+
|
|
93
122
|
def _init_fft(self):
|
|
94
123
|
pass
|
|
95
124
|
|
|
@@ -147,19 +176,16 @@ class SinoFilter:
|
|
|
147
176
|
if arr.shape != self.sino_shape:
|
|
148
177
|
raise ValueError("Expected sinogram shape %s, got %s" % (self.sino_shape, arr.shape))
|
|
149
178
|
|
|
150
|
-
def filter_sino(self, sino, output=None
|
|
179
|
+
def filter_sino(self, sino, output=None):
|
|
151
180
|
"""
|
|
152
181
|
Perform the sinogram siltering.
|
|
153
182
|
|
|
154
183
|
Parameters
|
|
155
184
|
----------
|
|
156
|
-
sino:
|
|
185
|
+
sino: array
|
|
157
186
|
Input sinogram (2D or 3D)
|
|
158
|
-
output:
|
|
187
|
+
output: array, optional
|
|
159
188
|
Output array.
|
|
160
|
-
no_output: bool, optional
|
|
161
|
-
If set to True, no copy is be done. The resulting data lies
|
|
162
|
-
in self.d_sino_padded.
|
|
163
189
|
"""
|
|
164
190
|
self._check_array(sino)
|
|
165
191
|
# sino_padded = np.pad(
|
|
@@ -169,16 +195,21 @@ class SinoFilter:
|
|
|
169
195
|
sino_padded_f = rfft(sino_padded, axis=1, workers=get_num_threads(self.extra_options["fft_threads"]))
|
|
170
196
|
sino_padded_f *= self.filter_f
|
|
171
197
|
sino_filtered = irfft(sino_padded_f, axis=1, workers=get_num_threads(self.extra_options["fft_threads"]))
|
|
198
|
+
|
|
172
199
|
if output is None:
|
|
173
|
-
|
|
200
|
+
if not (self.crop_filtered_data):
|
|
201
|
+
# No need to allocate extra memory here
|
|
202
|
+
return sino_filtered
|
|
203
|
+
res = np.zeros(self.output_shape, dtype=np.float32)
|
|
174
204
|
else:
|
|
175
205
|
res = output
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
res[:] = sino_filtered[
|
|
206
|
+
|
|
207
|
+
if self.crop_filtered_data:
|
|
208
|
+
# res[:] = sino_filtered[..., : self.dwidth] # pylint: disable=E1126 # ?!
|
|
209
|
+
res[:] = sino_filtered[..., self.pad_left : -self.pad_right] # pylint: disable=E1126 # ?!
|
|
179
210
|
else:
|
|
180
|
-
|
|
181
|
-
|
|
211
|
+
res[:] = sino_filtered[:]
|
|
212
|
+
|
|
182
213
|
return res
|
|
183
214
|
|
|
184
215
|
__call__ = filter_sino
|
|
@@ -191,6 +222,7 @@ def filter_sinogram(
|
|
|
191
222
|
padding_mode="constant",
|
|
192
223
|
normalize=True,
|
|
193
224
|
filter_cutoff=1.0,
|
|
225
|
+
crop_filtered_data=True,
|
|
194
226
|
**padding_kwargs,
|
|
195
227
|
):
|
|
196
228
|
"""
|
|
@@ -228,6 +260,8 @@ def filter_sinogram(
|
|
|
228
260
|
fourier_filter = fourier_filter[: padded_width // 2 + 1] # R2C
|
|
229
261
|
sino_f = rfft(sinogram_padded, axis=1)
|
|
230
262
|
sino_f *= fourier_filter
|
|
231
|
-
|
|
232
|
-
|
|
263
|
+
sino_filtered = irfft(sino_f, axis=1)
|
|
264
|
+
if crop_filtered_data:
|
|
265
|
+
# sino_filtered = sino_filtered[:, :width] # pylint: disable=E1126 # ?!
|
|
266
|
+
sino_filtered = sino_filtered[:, pad_left:-pad_right] # pylint: disable=E1126 # ?!
|
|
233
267
|
return sino_filtered
|
|
@@ -1,25 +1,33 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
from ..cuda.processing import CudaProcessing
|
|
3
|
-
from ..utils import get_cuda_srcfile
|
|
3
|
+
from ..utils import docstring, get_cuda_srcfile
|
|
4
4
|
from ..processing.padding_cuda import CudaPadding
|
|
5
5
|
from ..processing.fft_cuda import get_fft_class
|
|
6
6
|
from .filtering import SinoFilter
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class CudaSinoFilter(SinoFilter):
|
|
10
|
-
default_extra_options = {**SinoFilter.default_extra_options,
|
|
10
|
+
default_extra_options = {**SinoFilter.default_extra_options, "fft_backend": "vkfft"}
|
|
11
11
|
|
|
12
|
+
@docstring(SinoFilter)
|
|
12
13
|
def __init__(
|
|
13
14
|
self,
|
|
14
15
|
sino_shape,
|
|
15
16
|
filter_name=None,
|
|
16
17
|
padding_mode="zeros",
|
|
18
|
+
crop_filtered_data=True,
|
|
17
19
|
extra_options=None,
|
|
18
20
|
cuda_options=None,
|
|
19
21
|
):
|
|
20
22
|
self._cuda_options = cuda_options or {}
|
|
21
23
|
self.cuda = CudaProcessing(**self._cuda_options)
|
|
22
|
-
super().__init__(
|
|
24
|
+
super().__init__(
|
|
25
|
+
sino_shape,
|
|
26
|
+
filter_name=filter_name,
|
|
27
|
+
padding_mode=padding_mode,
|
|
28
|
+
crop_filtered_data=crop_filtered_data,
|
|
29
|
+
extra_options=extra_options,
|
|
30
|
+
)
|
|
23
31
|
self._init_kernels()
|
|
24
32
|
|
|
25
33
|
def _init_fft(self):
|
|
@@ -60,25 +68,12 @@ class CudaSinoFilter(SinoFilter):
|
|
|
60
68
|
)
|
|
61
69
|
|
|
62
70
|
def filter_sino(self, sino, output=None):
|
|
63
|
-
"""
|
|
64
|
-
Perform the sinogram siltering.
|
|
65
|
-
|
|
66
|
-
Parameters
|
|
67
|
-
----------
|
|
68
|
-
sino: numpy.ndarray or pycuda.gpuarray.GPUArray
|
|
69
|
-
Input sinogram (2D or 3D)
|
|
70
|
-
output: pycuda.gpuarray.GPUArray, optional
|
|
71
|
-
Output array.
|
|
72
|
-
no_output: bool, optional
|
|
73
|
-
If set to True, no copy is be done. The resulting data lies
|
|
74
|
-
in self.d_sino_padded.
|
|
75
|
-
"""
|
|
76
71
|
self._check_array(sino)
|
|
77
72
|
if not (isinstance(sino, self.cuda.array_class)):
|
|
78
73
|
sino = self.cuda.set_array("sino", sino)
|
|
79
74
|
elif not (sino.flags.c_contiguous):
|
|
80
75
|
# Transfer the device array into another, c-contiguous, device array
|
|
81
|
-
# We can throw an error as well in this case, but often we
|
|
76
|
+
# We can throw an error as well in this case, but often we do something like fbp(radios[:, i, :])
|
|
82
77
|
sino_tmp = self.cuda.allocate_array("sino_contig", sino.shape)
|
|
83
78
|
sino_tmp.set(sino)
|
|
84
79
|
sino = sino_tmp
|
|
@@ -97,13 +92,19 @@ class CudaSinoFilter(SinoFilter):
|
|
|
97
92
|
|
|
98
93
|
# return
|
|
99
94
|
if output is None:
|
|
100
|
-
|
|
95
|
+
if not (self.crop_filtered_data):
|
|
96
|
+
# No need to allocate extra memory here
|
|
97
|
+
return self.d_sino_padded
|
|
98
|
+
res = self.cuda.allocate_array("output", self.output_shape)
|
|
101
99
|
else:
|
|
102
100
|
res = output
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
|
|
102
|
+
if self.crop_filtered_data:
|
|
103
|
+
# res[:] = sino_filtered[..., : self.dwidth] # pylint: disable=E1126 # ?!
|
|
104
|
+
res[:] = self.d_sino_padded[..., self.pad_left : -self.pad_right] # pylint: disable=E1126 # ?!
|
|
105
105
|
else:
|
|
106
|
-
res[:] = self.d_sino_padded[
|
|
106
|
+
res[:] = self.d_sino_padded[:]
|
|
107
|
+
|
|
107
108
|
return res
|
|
108
109
|
|
|
109
110
|
__call__ = filter_sino
|
|
@@ -19,13 +19,22 @@ class OpenCLSinoFilter(SinoFilter):
|
|
|
19
19
|
sino_shape,
|
|
20
20
|
filter_name=None,
|
|
21
21
|
padding_mode="zeros",
|
|
22
|
+
crop_filtered_data=True,
|
|
22
23
|
extra_options=None,
|
|
23
24
|
opencl_options=None,
|
|
24
25
|
):
|
|
25
26
|
self._opencl_options = opencl_options or {}
|
|
26
27
|
self.opencl = OpenCLProcessing(**self._opencl_options)
|
|
27
28
|
self.queue = self.opencl.queue
|
|
28
|
-
super().__init__(
|
|
29
|
+
super().__init__(
|
|
30
|
+
sino_shape,
|
|
31
|
+
filter_name=filter_name,
|
|
32
|
+
padding_mode=padding_mode,
|
|
33
|
+
crop_filtered_data=crop_filtered_data,
|
|
34
|
+
extra_options=extra_options,
|
|
35
|
+
)
|
|
36
|
+
if not (crop_filtered_data):
|
|
37
|
+
raise NotImplementedError # TODO
|
|
29
38
|
self._init_kernels()
|
|
30
39
|
|
|
31
40
|
def _init_fft(self):
|
|
@@ -61,19 +70,6 @@ class OpenCLSinoFilter(SinoFilter):
|
|
|
61
70
|
self.memcpy2D = OpenCLMemcpy2D(queue=self.queue)
|
|
62
71
|
|
|
63
72
|
def filter_sino(self, sino, output=None):
|
|
64
|
-
"""
|
|
65
|
-
Perform the sinogram siltering.
|
|
66
|
-
|
|
67
|
-
Parameters
|
|
68
|
-
----------
|
|
69
|
-
sino: numpy.ndarray or pyopencl.array
|
|
70
|
-
Input sinogram (2D or 3D)
|
|
71
|
-
output: pyopencl.array, optional
|
|
72
|
-
Output array.
|
|
73
|
-
no_output: bool, optional
|
|
74
|
-
If set to True, no copy is be done. The resulting data lies
|
|
75
|
-
in self.d_sino_padded.
|
|
76
|
-
"""
|
|
77
73
|
self._check_array(sino)
|
|
78
74
|
sino = self.opencl.set_array("sino", sino)
|
|
79
75
|
|
nabu/reconstruction/hbp.py
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# Generalized Hierarchical Backprojection (GHBP)
|
|
2
|
+
# for fast tomographic reconstruction from ultra high resolution images at non-negligible fan angles.
|
|
3
|
+
#
|
|
4
|
+
# Authors/Contributions:
|
|
5
|
+
# - Jonas Graetz, Fraunhofer IIS / Universität Würzburg: Algorithm Design and original OpenCL/Python implementation.
|
|
6
|
+
# - Alessandro Mirone, ESRF: CUDA translation, ESRF / BM18 integration, testing <mirone@esrf.fr>
|
|
7
|
+
# - Pierre Paleo, ESRF: ESRF / BM18 integration, testing <pierre.paleo@esrf.fr>
|
|
8
|
+
#
|
|
9
|
+
# JG was funded by the German Federal Ministry of Education and Research (BMBF), grant 05E2019,
|
|
10
|
+
# funding the development of BM18 at ESRF in collaboration with the Fraunhofer Gesellschaft,
|
|
11
|
+
# the Julius-Maximilians-Universität Würzburg, and the University of Passau
|
|
12
|
+
|
|
1
13
|
import math
|
|
2
14
|
import numpy as np
|
|
3
15
|
|
|
@@ -12,6 +24,7 @@ from .fbp import CudaBackprojector
|
|
|
12
24
|
|
|
13
25
|
|
|
14
26
|
try:
|
|
27
|
+
# ruff: noqa: F401
|
|
15
28
|
import pycuda.driver as cuda
|
|
16
29
|
from pycuda import gpuarray as garray
|
|
17
30
|
|
|
@@ -60,7 +73,7 @@ class HierarchicalBackprojector(CudaBackprojector):
|
|
|
60
73
|
|
|
61
74
|
# to do the reconstruction in reduction_steps steps
|
|
62
75
|
self.reduction_steps = self.extra_options.get("hbp_reduction_steps", 2)
|
|
63
|
-
reduction_factor =
|
|
76
|
+
reduction_factor = math.ceil((sino_shape[-2]) ** (1 / self.reduction_steps))
|
|
64
77
|
|
|
65
78
|
# TODO customize
|
|
66
79
|
axis_source_meters = 1.0e9
|
|
@@ -138,15 +151,15 @@ class HierarchicalBackprojector(CudaBackprojector):
|
|
|
138
151
|
|
|
139
152
|
N = self.slice_shape[1] * fac
|
|
140
153
|
|
|
141
|
-
angularRange = abs(self.angles
|
|
154
|
+
angularRange = abs(np.ptp(self.angles)) / self.sino_shape[0] * reductionFactor
|
|
142
155
|
|
|
143
|
-
ngrids =
|
|
156
|
+
ngrids = math.ceil(self.sino_shape[0] / reductionFactor)
|
|
144
157
|
|
|
145
158
|
grid_width = int(
|
|
146
159
|
np.rint(2 * N * self.whf[0])
|
|
147
160
|
) # double sampling to account/compensate for diamond shaped grid of ray-intersections
|
|
148
|
-
grid_height =
|
|
149
|
-
|
|
161
|
+
grid_height = math.ceil(
|
|
162
|
+
angularRange * N * self.whf[1]
|
|
150
163
|
) # small-angle approximation, generates as much "lines" as needed to account for all intersection levels
|
|
151
164
|
|
|
152
165
|
m = (len(self.angles) // reductionFactor) * reductionFactor
|
|
@@ -166,7 +179,7 @@ class HierarchicalBackprojector(CudaBackprojector):
|
|
|
166
179
|
)
|
|
167
180
|
]
|
|
168
181
|
self.gridInvTransforms += [np.array([np.linalg.inv(t) for t in self.gridTransforms[-1]], dtype=np.float32)]
|
|
169
|
-
self.grids += [(grid_height, grid_width,
|
|
182
|
+
self.grids += [(grid_height, grid_width, math.ceil(ngrids / legs))]
|
|
170
183
|
self.reductionFactors += [reductionFactor]
|
|
171
184
|
|
|
172
185
|
### intermediate level grids: accumulation grids ###
|
|
@@ -177,13 +190,13 @@ class HierarchicalBackprojector(CudaBackprojector):
|
|
|
177
190
|
# for a reasonable (with regard to memory requirement) grid-aspect ratio in the intermediate levels,
|
|
178
191
|
# the covered angular range per grid should not exceed 28.6°, i.e.,
|
|
179
192
|
# fewer than 7 (6.3) or 13 (12.6) grids for a 180° / 360° scan is not reasonable
|
|
180
|
-
if
|
|
193
|
+
if math.ceil(ngrids / reductionFactor) < 20:
|
|
181
194
|
break
|
|
182
195
|
angularRange *= reductionFactor
|
|
183
|
-
ngrids =
|
|
196
|
+
ngrids = math.ceil(ngrids / reductionFactor)
|
|
184
197
|
|
|
185
|
-
grid_height =
|
|
186
|
-
|
|
198
|
+
grid_height = math.ceil(
|
|
199
|
+
angularRange * N * self.whf[1]
|
|
187
200
|
) # implicit small angle approximation, whose validity is
|
|
188
201
|
# asserted by the preceding "break"
|
|
189
202
|
gridAinvT = self._getAinvT(N, grid_height, grid_width)
|
|
@@ -205,7 +218,7 @@ class HierarchicalBackprojector(CudaBackprojector):
|
|
|
205
218
|
)
|
|
206
219
|
]
|
|
207
220
|
self.gridInvTransforms += [np.array([np.linalg.inv(t) for t in self.gridTransforms[-1]], dtype=np.float32)]
|
|
208
|
-
self.grids += [(grid_height, grid_width,
|
|
221
|
+
self.grids += [(grid_height, grid_width, math.ceil(ngrids / legs))]
|
|
209
222
|
self.reductionFactors += [reductionFactor]
|
|
210
223
|
|
|
211
224
|
##### final accumulation grid #################
|
|
@@ -331,7 +344,7 @@ class HierarchicalBackprojector(CudaBackprojector):
|
|
|
331
344
|
)
|
|
332
345
|
|
|
333
346
|
else:
|
|
334
|
-
for leg in list(range(
|
|
347
|
+
for leg in list(range(self.legs)):
|
|
335
348
|
gridOffset = leg * self.grids[0][2]
|
|
336
349
|
projOffset = gridOffset * self.reductionFactors[0]
|
|
337
350
|
gws = getGridSize(self.grids[0], lws)
|
|
@@ -418,7 +431,7 @@ def get_max_grid_size(grids):
|
|
|
418
431
|
|
|
419
432
|
|
|
420
433
|
def getGridSize(minimum, local):
|
|
421
|
-
m, l = np.array(minimum), np.array(local)
|
|
434
|
+
m, l = np.array(minimum), np.array(local) # noqa: E741
|
|
422
435
|
new = (m // l) * l
|
|
423
436
|
new[new < m] += l[new < m]
|
|
424
437
|
return tuple(map(int, new // l))
|
nabu/reconstruction/mlem.py
CHANGED
|
@@ -12,6 +12,28 @@ except ImportError:
|
|
|
12
12
|
class MLEMReconstructor:
|
|
13
13
|
"""
|
|
14
14
|
A reconstructor for MLEM reconstruction using the CorrCT toolbox.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
data_vwu_shape : tuple
|
|
19
|
+
Shape of the input data, expected to be (n_slices, n_angles, n_dets). Raises an error if the shape is not 3D.
|
|
20
|
+
angles_rad : numpy.ndarray
|
|
21
|
+
Angles in radians for the projections. Must match the second dimension of `data_vwu_shape`.
|
|
22
|
+
shifts_vu : numpy.ndarray, optional.
|
|
23
|
+
Shifts in the v and u directions for each angle. If provided, must have the same number of cols as `angles_rad`. Each col is (tv,tu)
|
|
24
|
+
cor : float, optional
|
|
25
|
+
Center of rotation, which will be adjusted based on the sinogram width.
|
|
26
|
+
n_iterations : int, optional
|
|
27
|
+
Number of iterations for the MLEM algorithm. Default is 50.
|
|
28
|
+
extra_options : dict, optional
|
|
29
|
+
Additional options for the reconstruction process. Default options include:
|
|
30
|
+
- scale_factor (float, default is 1.0): Scale factor for the reconstruction.
|
|
31
|
+
- compute_shifts (boolean, default is False): Whether to compute shifts.
|
|
32
|
+
- tomo_consistency (boolean, default is False): Whether to enforce tomographic consistency.
|
|
33
|
+
- v_min_for_v_shifts (number, default is 0): Minimum value for vertical shifts.
|
|
34
|
+
- v_max_for_v_shifts (number, default is None): Maximum value for vertical shifts.
|
|
35
|
+
- v_min_for_u_shifts (number, default is 0): Minimum value for horizontal shifts.
|
|
36
|
+
- v_max_for_u_shifts (number, default is None): Maximum value for horizontal shifts.
|
|
15
37
|
"""
|
|
16
38
|
|
|
17
39
|
default_extra_options = {
|
|
@@ -21,25 +43,32 @@ class MLEMReconstructor:
|
|
|
21
43
|
"v_max_for_v_shifts": None,
|
|
22
44
|
"v_min_for_u_shifts": 0,
|
|
23
45
|
"v_max_for_u_shifts": None,
|
|
46
|
+
"scale_factor": 1.0,
|
|
47
|
+
"centered_axis": False,
|
|
48
|
+
"clip_outer_circle": False,
|
|
49
|
+
"outer_circle_value": 0.0,
|
|
50
|
+
"filter_cutoff": 1.0,
|
|
51
|
+
"padding_mode": None,
|
|
52
|
+
"crop_filtered_data": True,
|
|
24
53
|
}
|
|
25
54
|
|
|
26
55
|
def __init__(
|
|
27
56
|
self,
|
|
28
|
-
|
|
57
|
+
data_vwu_shape,
|
|
29
58
|
angles_rad,
|
|
30
59
|
shifts_uv=None,
|
|
31
|
-
cor=None,
|
|
60
|
+
cor=None, # absolute
|
|
32
61
|
n_iterations=50,
|
|
33
62
|
extra_options=None,
|
|
34
63
|
):
|
|
35
|
-
""" """
|
|
36
64
|
if not (__have_corrct__):
|
|
37
65
|
raise ImportError("Need corrct package")
|
|
38
66
|
self.angles_rad = angles_rad
|
|
39
67
|
self.n_iterations = n_iterations
|
|
68
|
+
self.scale_factor = extra_options.get("scale_factor", 1.0)
|
|
40
69
|
|
|
41
70
|
self._configure_extra_options(extra_options)
|
|
42
|
-
self._set_sino_shape(
|
|
71
|
+
self._set_sino_shape(data_vwu_shape)
|
|
43
72
|
self._set_shifts(shifts_uv, cor)
|
|
44
73
|
|
|
45
74
|
def _configure_extra_options(self, extra_options):
|
|
@@ -58,16 +87,24 @@ class MLEMReconstructor:
|
|
|
58
87
|
|
|
59
88
|
def _set_shifts(self, shifts_uv, cor):
|
|
60
89
|
if shifts_uv is None:
|
|
61
|
-
self.
|
|
90
|
+
self.shifts_vu = None
|
|
62
91
|
else:
|
|
63
92
|
if shifts_uv.shape[0] != self.n_angles:
|
|
64
93
|
raise ValueError(
|
|
65
94
|
f"Number of shifts given ({shifts_uv.shape[0]}) does not mathc the number of projections ({self.n_angles})."
|
|
66
95
|
)
|
|
67
|
-
self.
|
|
68
|
-
|
|
96
|
+
self.shifts_vu = -shifts_uv.copy().T[::-1]
|
|
97
|
+
if cor is None:
|
|
98
|
+
self.cor = 0.0
|
|
99
|
+
else:
|
|
100
|
+
self.cor = (
|
|
101
|
+
-cor + (self.sinos_shape[-1] - 1) / 2.0
|
|
102
|
+
) # convert absolute to relative in the ASTRA convention, which is opposite to Nabu relative convention.
|
|
103
|
+
|
|
104
|
+
def reset_rot_center(self, cor):
|
|
105
|
+
self.cor = -cor + (self.sinos_shape[-1] - 1) / 2.0
|
|
69
106
|
|
|
70
|
-
def reconstruct(self, data_vwu):
|
|
107
|
+
def reconstruct(self, data_vwu, x0=None):
|
|
71
108
|
"""
|
|
72
109
|
data_align_vwu: numpy.ndarray or pycuda.gpuarray
|
|
73
110
|
Raw data, with shape (n_sinograms, n_angles, width)
|
|
@@ -76,14 +113,17 @@ class MLEMReconstructor:
|
|
|
76
113
|
"""
|
|
77
114
|
if not isinstance(data_vwu, np.ndarray):
|
|
78
115
|
data_vwu = data_vwu.get()
|
|
79
|
-
data_vwu /= data_vwu.mean()
|
|
116
|
+
# data_vwu /= data_vwu.mean()
|
|
80
117
|
|
|
81
118
|
# MLEM recons
|
|
82
119
|
self.vol_geom_align = cct.models.VolumeGeometry.get_default_from_data(data_vwu)
|
|
83
|
-
self.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
120
|
+
if self.shifts_vu is not None:
|
|
121
|
+
self.prj_geom_align = cct.models.ProjectionGeometry.get_default_parallel()
|
|
122
|
+
# Vertical shifts were handled in pipeline. Set them to ZERO
|
|
123
|
+
self.shifts_vu[:, 0] = 0.0
|
|
124
|
+
self.prj_geom_align.set_detector_shifts_vu(self.shifts_vu, self.cor)
|
|
125
|
+
else:
|
|
126
|
+
self.prj_geom_align = None
|
|
87
127
|
|
|
88
128
|
variances_align = cct.processing.compute_variance_poisson(data_vwu)
|
|
89
129
|
self.weights_align = cct.processing.compute_variance_weight(variances_align, normalized=True) # , use_std=True
|
|
@@ -94,6 +134,5 @@ class MLEMReconstructor:
|
|
|
94
134
|
with cct.projectors.ProjectorUncorrected(
|
|
95
135
|
self.vol_geom_align, self.angles_rad, rot_axis_shift_pix=self.cor, prj_geom=self.prj_geom_align
|
|
96
136
|
) as A:
|
|
97
|
-
rec, _ = solver(A, data_vwu, iterations=self.n_iterations, **self.solver_opts)
|
|
98
|
-
|
|
99
|
-
return rec
|
|
137
|
+
rec, _ = solver(A, data_vwu, iterations=self.n_iterations, x0=x0, **self.solver_opts)
|
|
138
|
+
return rec * self.scale_factor
|
|
@@ -32,7 +32,6 @@ class Projector:
|
|
|
32
32
|
|
|
33
33
|
Parameters
|
|
34
34
|
-----------
|
|
35
|
-
|
|
36
35
|
slice_shape: tuple
|
|
37
36
|
Shape of the slice: (num_rows, num_columns).
|
|
38
37
|
angles: int or sequence
|
|
@@ -198,10 +197,9 @@ class Projector:
|
|
|
198
197
|
if image.dtype != np.dtype("f"):
|
|
199
198
|
raise ValueError("Expected float32 data type, got %s" % str(image.dtype))
|
|
200
199
|
if not isinstance(image, (np.ndarray, garray.GPUArray)):
|
|
201
|
-
raise
|
|
202
|
-
if isinstance(image, np.ndarray):
|
|
203
|
-
|
|
204
|
-
raise ValueError("Please use C-contiguous arrays")
|
|
200
|
+
raise TypeError("Expected either numpy.ndarray or pyopencl.array.Array")
|
|
201
|
+
if isinstance(image, np.ndarray) and not image.flags["C_CONTIGUOUS"]:
|
|
202
|
+
raise ValueError("Please use C-contiguous arrays")
|
|
205
203
|
|
|
206
204
|
def set_image(self, image, check=True):
|
|
207
205
|
if check:
|
nabu/reconstruction/sinogram.py
CHANGED
|
@@ -290,7 +290,7 @@ def match_half_sinos_parts(sino, angles, output=None):
|
|
|
290
290
|
"""
|
|
291
291
|
n_a = angles.size
|
|
292
292
|
n_a_2 = n_a // 2
|
|
293
|
-
sino_part1 = sino[:n_a_2, :]
|
|
293
|
+
# sino_part1 = sino[:n_a_2, :]
|
|
294
294
|
sino_part2 = sino[n_a_2:, :]
|
|
295
295
|
angles = np.rad2deg(angles) # more numerically stable ?
|
|
296
296
|
angles_1 = angles[:n_a_2]
|
|
@@ -212,7 +212,6 @@ class CudaSinoNormalization(SinoNormalization):
|
|
|
212
212
|
else:
|
|
213
213
|
# This kernel seems to have an issue on arrays that are not C-contiguous.
|
|
214
214
|
# We have to process image per image.
|
|
215
|
-
nz = np.int32(1)
|
|
216
215
|
nthreadsperblock = (1, 32, 1) # TODO tune
|
|
217
216
|
nblocks = (1, int(updiv(self.n_angles, nthreadsperblock[1])), 1)
|
|
218
217
|
for i in range(sinos.shape[0]):
|