nabu 2023.2.1__py3-none-any.whl → 2024.1.0rc3__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/conf.py +1 -1
- doc/doc_config.py +32 -0
- nabu/__init__.py +2 -1
- nabu/app/bootstrap_stitching.py +1 -1
- nabu/app/cli_configs.py +122 -2
- nabu/app/composite_cor.py +27 -2
- nabu/app/correct_rot.py +70 -0
- nabu/app/create_distortion_map_from_poly.py +42 -18
- nabu/app/diag_to_pix.py +358 -0
- nabu/app/diag_to_rot.py +449 -0
- nabu/app/generate_header.py +4 -3
- nabu/app/histogram.py +2 -2
- nabu/app/multicor.py +6 -1
- nabu/app/parse_reconstruction_log.py +151 -0
- nabu/app/prepare_weights_double.py +83 -22
- nabu/app/reconstruct.py +5 -1
- nabu/app/reconstruct_helical.py +7 -0
- nabu/app/reduce_dark_flat.py +6 -3
- nabu/app/rotate.py +4 -4
- nabu/app/stitching.py +16 -2
- nabu/app/tests/test_reduce_dark_flat.py +18 -2
- nabu/app/validator.py +4 -4
- nabu/cuda/convolution.py +8 -376
- nabu/cuda/fft.py +4 -0
- nabu/cuda/kernel.py +4 -4
- nabu/cuda/medfilt.py +5 -158
- nabu/cuda/padding.py +5 -71
- nabu/cuda/processing.py +23 -2
- nabu/cuda/src/ElementOp.cu +78 -0
- nabu/cuda/src/backproj.cu +28 -2
- nabu/cuda/src/fourier_wavelets.cu +2 -2
- nabu/cuda/src/normalization.cu +23 -0
- nabu/cuda/src/padding.cu +2 -2
- nabu/cuda/src/transpose.cu +16 -0
- nabu/cuda/utils.py +39 -0
- nabu/estimation/alignment.py +10 -1
- nabu/estimation/cor.py +808 -38
- nabu/estimation/cor_sino.py +7 -9
- nabu/estimation/tests/test_cor.py +85 -3
- nabu/io/reader.py +26 -18
- nabu/io/tests/test_cast_volume.py +3 -3
- nabu/io/tests/test_detector_distortion.py +3 -3
- nabu/io/tiffwriter_zmm.py +2 -2
- nabu/io/utils.py +14 -4
- nabu/io/writer.py +5 -3
- nabu/misc/fftshift.py +6 -0
- nabu/misc/histogram.py +5 -285
- nabu/misc/histogram_cuda.py +8 -104
- nabu/misc/kernel_base.py +3 -121
- nabu/misc/padding_base.py +5 -69
- nabu/misc/processing_base.py +3 -107
- nabu/misc/rotation.py +5 -62
- nabu/misc/rotation_cuda.py +5 -65
- nabu/misc/transpose.py +6 -0
- nabu/misc/unsharp.py +3 -78
- nabu/misc/unsharp_cuda.py +5 -52
- nabu/misc/unsharp_opencl.py +8 -85
- nabu/opencl/fft.py +6 -0
- nabu/opencl/kernel.py +21 -6
- nabu/opencl/padding.py +5 -72
- nabu/opencl/processing.py +27 -5
- nabu/opencl/src/backproj.cl +3 -3
- nabu/opencl/src/fftshift.cl +65 -12
- nabu/opencl/src/padding.cl +2 -2
- nabu/opencl/src/roll.cl +96 -0
- nabu/opencl/src/transpose.cl +16 -0
- nabu/pipeline/config_validators.py +63 -3
- nabu/pipeline/dataset_validator.py +2 -2
- nabu/pipeline/estimators.py +193 -35
- nabu/pipeline/fullfield/chunked.py +34 -17
- nabu/pipeline/fullfield/chunked_cuda.py +7 -5
- nabu/pipeline/fullfield/computations.py +48 -13
- nabu/pipeline/fullfield/nabu_config.py +13 -13
- nabu/pipeline/fullfield/processconfig.py +10 -5
- nabu/pipeline/fullfield/reconstruction.py +1 -2
- nabu/pipeline/helical/fbp.py +5 -0
- nabu/pipeline/helical/filtering.py +12 -9
- nabu/pipeline/helical/gridded_accumulator.py +179 -33
- nabu/pipeline/helical/helical_chunked_regridded.py +262 -151
- nabu/pipeline/helical/helical_chunked_regridded_cuda.py +4 -11
- nabu/pipeline/helical/helical_reconstruction.py +56 -18
- nabu/pipeline/helical/span_strategy.py +1 -1
- nabu/pipeline/helical/tests/test_accumulator.py +4 -0
- nabu/pipeline/params.py +23 -2
- nabu/pipeline/processconfig.py +3 -8
- nabu/pipeline/tests/test_chunk_reader.py +78 -0
- nabu/pipeline/tests/test_estimators.py +120 -2
- nabu/pipeline/utils.py +25 -0
- nabu/pipeline/writer.py +2 -0
- nabu/preproc/ccd_cuda.py +9 -7
- nabu/preproc/ctf.py +21 -26
- nabu/preproc/ctf_cuda.py +25 -25
- nabu/preproc/double_flatfield.py +14 -2
- nabu/preproc/double_flatfield_cuda.py +7 -11
- nabu/preproc/flatfield_cuda.py +23 -27
- nabu/preproc/phase.py +19 -24
- nabu/preproc/phase_cuda.py +21 -21
- nabu/preproc/shift_cuda.py +58 -28
- nabu/preproc/tests/test_ctf.py +5 -5
- nabu/preproc/tests/test_double_flatfield.py +2 -2
- nabu/preproc/tests/test_vshift.py +13 -2
- nabu/processing/__init__.py +0 -0
- nabu/processing/convolution_cuda.py +375 -0
- nabu/processing/fft_base.py +163 -0
- nabu/processing/fft_cuda.py +256 -0
- nabu/processing/fft_opencl.py +54 -0
- nabu/processing/fftshift.py +134 -0
- nabu/processing/histogram.py +286 -0
- nabu/processing/histogram_cuda.py +103 -0
- nabu/processing/kernel_base.py +126 -0
- nabu/processing/medfilt_cuda.py +159 -0
- nabu/processing/muladd.py +29 -0
- nabu/processing/muladd_cuda.py +68 -0
- nabu/processing/padding_base.py +71 -0
- nabu/processing/padding_cuda.py +75 -0
- nabu/processing/padding_opencl.py +77 -0
- nabu/processing/processing_base.py +123 -0
- nabu/processing/roll_opencl.py +64 -0
- nabu/processing/rotation.py +63 -0
- nabu/processing/rotation_cuda.py +66 -0
- nabu/processing/tests/__init__.py +0 -0
- nabu/processing/tests/test_fft.py +268 -0
- nabu/processing/tests/test_fftshift.py +71 -0
- nabu/{misc → processing}/tests/test_histogram.py +2 -4
- nabu/{cuda → processing}/tests/test_medfilt.py +1 -1
- nabu/processing/tests/test_muladd.py +54 -0
- nabu/{cuda → processing}/tests/test_padding.py +119 -75
- nabu/processing/tests/test_roll.py +63 -0
- nabu/{misc → processing}/tests/test_rotation.py +3 -2
- nabu/processing/tests/test_transpose.py +72 -0
- nabu/{misc → processing}/tests/test_unsharp.py +41 -8
- nabu/processing/transpose.py +126 -0
- nabu/processing/unsharp.py +79 -0
- nabu/processing/unsharp_cuda.py +53 -0
- nabu/processing/unsharp_opencl.py +75 -0
- nabu/reconstruction/fbp.py +34 -10
- nabu/reconstruction/fbp_base.py +35 -16
- nabu/reconstruction/fbp_opencl.py +7 -12
- nabu/reconstruction/filtering.py +2 -2
- nabu/reconstruction/filtering_cuda.py +13 -14
- nabu/reconstruction/filtering_opencl.py +3 -4
- nabu/reconstruction/projection.py +2 -0
- nabu/reconstruction/rings.py +158 -1
- nabu/reconstruction/rings_cuda.py +218 -58
- nabu/reconstruction/sinogram_cuda.py +16 -12
- nabu/reconstruction/tests/test_deringer.py +116 -14
- nabu/reconstruction/tests/test_fbp.py +22 -31
- nabu/reconstruction/tests/test_filtering.py +11 -2
- nabu/resources/dataset_analyzer.py +89 -26
- nabu/resources/nxflatfield.py +2 -2
- nabu/resources/tests/test_nxflatfield.py +1 -1
- nabu/resources/utils.py +9 -2
- nabu/stitching/alignment.py +184 -0
- nabu/stitching/config.py +241 -39
- nabu/stitching/definitions.py +6 -0
- nabu/stitching/frame_composition.py +4 -2
- nabu/stitching/overlap.py +99 -3
- nabu/stitching/sample_normalization.py +60 -0
- nabu/stitching/slurm_utils.py +10 -10
- nabu/stitching/tests/test_alignment.py +99 -0
- nabu/stitching/tests/test_config.py +16 -1
- nabu/stitching/tests/test_overlap.py +68 -2
- nabu/stitching/tests/test_sample_normalization.py +49 -0
- nabu/stitching/tests/test_slurm_utils.py +5 -5
- nabu/stitching/tests/test_utils.py +3 -33
- nabu/stitching/tests/test_z_stitching.py +391 -22
- nabu/stitching/utils.py +144 -202
- nabu/stitching/z_stitching.py +309 -126
- nabu/testutils.py +18 -0
- nabu/thirdparty/tomocupy_remove_stripe.py +586 -0
- nabu/utils.py +32 -6
- {nabu-2023.2.1.dist-info → nabu-2024.1.0rc3.dist-info}/LICENSE +1 -1
- {nabu-2023.2.1.dist-info → nabu-2024.1.0rc3.dist-info}/METADATA +5 -5
- nabu-2024.1.0rc3.dist-info/RECORD +296 -0
- {nabu-2023.2.1.dist-info → nabu-2024.1.0rc3.dist-info}/WHEEL +1 -1
- {nabu-2023.2.1.dist-info → nabu-2024.1.0rc3.dist-info}/entry_points.txt +5 -1
- nabu/conftest.py +0 -14
- nabu/opencl/fftshift.py +0 -92
- nabu/opencl/tests/test_fftshift.py +0 -55
- nabu/opencl/tests/test_padding.py +0 -84
- nabu-2023.2.1.dist-info/RECORD +0 -252
- /nabu/cuda/src/{fftshift.cu → dfi_fftshift.cu} +0 -0
- {nabu-2023.2.1.dist-info → nabu-2024.1.0rc3.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,8 @@
|
|
1
1
|
import numpy as np
|
2
2
|
import pytest
|
3
3
|
from nabu.testutils import generate_tests_scenarios
|
4
|
-
from nabu.
|
4
|
+
from nabu.processing.rotation_cuda import Rotation
|
5
|
+
from nabu.processing.rotation import __have__skimage__
|
5
6
|
from nabu.cuda.utils import __has_pycuda__, get_cuda_context
|
6
7
|
|
7
8
|
if __have__skimage__:
|
@@ -10,7 +11,7 @@ if __have__skimage__:
|
|
10
11
|
|
11
12
|
ny, nx = chelsea().shape[:2]
|
12
13
|
if __has_pycuda__:
|
13
|
-
from nabu.
|
14
|
+
from nabu.processing.rotation_cuda import CudaRotation
|
14
15
|
import pycuda.gpuarray as garray
|
15
16
|
|
16
17
|
if __have__skimage__:
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import pytest
|
3
|
+
from nabu.cuda.utils import get_cuda_context, __has_pycuda__
|
4
|
+
from nabu.opencl.utils import __has_pyopencl__, get_opencl_context
|
5
|
+
from nabu.testutils import get_data, generate_tests_scenarios, __do_long_tests__
|
6
|
+
from nabu.processing.transpose import CudaTranspose, OpenCLTranspose
|
7
|
+
|
8
|
+
configs = {
|
9
|
+
"shape": [(300, 451), (300, 300), (255, 300)],
|
10
|
+
"output_is_none": [True, False],
|
11
|
+
"dtype_in_out": [(np.float32, np.float32)],
|
12
|
+
}
|
13
|
+
|
14
|
+
if __do_long_tests__:
|
15
|
+
configs["dtype_in_out"].extend(
|
16
|
+
[(np.float32, np.complex64), (np.complex64, np.complex64), (np.uint8, np.uint16), (np.uint8, np.int32)]
|
17
|
+
)
|
18
|
+
|
19
|
+
scenarios = generate_tests_scenarios(configs)
|
20
|
+
|
21
|
+
|
22
|
+
@pytest.fixture(scope="class")
|
23
|
+
def bootstrap(request):
|
24
|
+
cls = request.cls
|
25
|
+
cls.data = get_data("chelsea.npz")["data"]
|
26
|
+
cls.tol = 1e-7
|
27
|
+
if __has_pycuda__:
|
28
|
+
cls.cu_ctx = get_cuda_context(cleanup_at_exit=False)
|
29
|
+
if __has_pyopencl__:
|
30
|
+
cls.cl_ctx = get_opencl_context(device_type="all")
|
31
|
+
yield
|
32
|
+
if __has_pycuda__:
|
33
|
+
cls.cu_ctx.pop()
|
34
|
+
|
35
|
+
|
36
|
+
@pytest.mark.usefixtures("bootstrap")
|
37
|
+
class TestTranspose:
|
38
|
+
def _do_test_transpose(self, config, transpose_cls):
|
39
|
+
shape = config["shape"]
|
40
|
+
dtype = config["dtype_in_out"][0]
|
41
|
+
dtype_out = config["dtype_in_out"][1]
|
42
|
+
data = np.ascontiguousarray(self.data[: shape[0], : shape[1]], dtype=dtype)
|
43
|
+
|
44
|
+
backend = transpose_cls.backend
|
45
|
+
if backend == "opencl" and not (np.iscomplexobj(dtype(1))) and np.iscomplexobj(dtype_out(1)):
|
46
|
+
pytest.skip("pyopencl does not support real to complex scalar cast")
|
47
|
+
ctx = self.cu_ctx if backend == "cuda" else self.cl_ctx
|
48
|
+
backend_options = {"ctx": ctx}
|
49
|
+
transpose = transpose_cls(data.shape, dtype, dst_dtype=dtype_out, **backend_options)
|
50
|
+
|
51
|
+
d_data = transpose.processing.allocate_array("data", shape, dtype)
|
52
|
+
d_data.set(data)
|
53
|
+
if config["output_is_none"]:
|
54
|
+
d_out = None
|
55
|
+
else:
|
56
|
+
d_out = transpose.processing.allocate_array("output", shape[::-1], dtype_out)
|
57
|
+
|
58
|
+
d_res = transpose(d_data, dst=d_out)
|
59
|
+
|
60
|
+
assert (
|
61
|
+
np.max(np.abs(d_res.get() - data.T)) == 0
|
62
|
+
), "something wrong with transpose(shape=%s, dtype=%s, dtype_out=%s)" % (shape, dtype, dtype_out)
|
63
|
+
|
64
|
+
@pytest.mark.skipif(not (__has_pycuda__), reason="Need pycuda for this test")
|
65
|
+
@pytest.mark.parametrize("config", scenarios)
|
66
|
+
def test_cuda_transpose(self, config):
|
67
|
+
self._do_test_transpose(config, CudaTranspose)
|
68
|
+
|
69
|
+
@pytest.mark.skipif(not (__has_pyopencl__), reason="Need pyopencl for this test")
|
70
|
+
@pytest.mark.parametrize("config", scenarios)
|
71
|
+
def test_opencl_transpose(self, config):
|
72
|
+
self._do_test_transpose(config, OpenCLTranspose)
|
@@ -1,7 +1,8 @@
|
|
1
|
+
from itertools import product
|
1
2
|
import numpy as np
|
2
3
|
import pytest
|
3
|
-
from nabu.
|
4
|
-
from nabu.
|
4
|
+
from nabu.processing.unsharp import UnsharpMask
|
5
|
+
from nabu.processing.unsharp_opencl import OpenclUnsharpMask, __have_opencl__ as __has_pyopencl__
|
5
6
|
from nabu.cuda.utils import __has_pycuda__, get_cuda_context
|
6
7
|
from nabu.testutils import get_data
|
7
8
|
|
@@ -11,13 +12,21 @@ if __has_pyopencl__:
|
|
11
12
|
from silx.opencl.common import ocl
|
12
13
|
if __has_pycuda__:
|
13
14
|
import pycuda.gpuarray as garray
|
14
|
-
from nabu.
|
15
|
+
from nabu.processing.unsharp_cuda import CudaUnsharpMask
|
16
|
+
|
17
|
+
try:
|
18
|
+
from skimage.filters import unsharp_mask
|
19
|
+
|
20
|
+
__has_skimage__ = True
|
21
|
+
except ImportError:
|
22
|
+
__has_skimage__ = False
|
15
23
|
|
16
24
|
|
17
25
|
@pytest.fixture(scope="class")
|
18
26
|
def bootstrap(request):
|
19
27
|
cls = request.cls
|
20
28
|
cls.data = get_data("brain_phantom.npz")["data"]
|
29
|
+
cls.imagej_results = get_data("dirac_unsharp_imagej.npz")
|
21
30
|
cls.tol = 1e-4
|
22
31
|
cls.sigma = 1.6
|
23
32
|
cls.coeff = 0.5
|
@@ -46,8 +55,34 @@ class TestUnsharp:
|
|
46
55
|
)
|
47
56
|
assert mae < self.tol, err_msg
|
48
57
|
|
58
|
+
@pytest.mark.skipif(not (__has_skimage__), reason="Need scikit-image for this test")
|
59
|
+
def test_mode_gaussian(self):
|
60
|
+
dirac = np.zeros((43, 43), "f")
|
61
|
+
dirac[dirac.shape[0] // 2, dirac.shape[1] // 2] = 1
|
62
|
+
sigma_list = [0.2, 0.5, 1.0, 2.0, 3.0]
|
63
|
+
coeff_list = [0.5, 1.0, 3.0]
|
64
|
+
for sigma, coeff in product(sigma_list, coeff_list):
|
65
|
+
res = UnsharpMask(dirac.shape, sigma, coeff, method="gaussian").unsharp(dirac)
|
66
|
+
ref = unsharp_mask(dirac, radius=sigma, amount=coeff, preserve_range=True)
|
67
|
+
assert np.max(np.abs(res - ref)) < 1e-6, "Something wrong with mode='gaussian', sigma=%.2f, coeff=%.2f" % (
|
68
|
+
sigma,
|
69
|
+
coeff,
|
70
|
+
)
|
71
|
+
|
72
|
+
def test_mode_imagej(self):
|
73
|
+
dirac = np.zeros(self.imagej_results["images"][0].shape, dtype="f")
|
74
|
+
dirac[dirac.shape[0] // 2, dirac.shape[1] // 2] = 1
|
75
|
+
for sigma, coeff, ref in zip(
|
76
|
+
self.imagej_results["sigma"], self.imagej_results["amount"], self.imagej_results["images"]
|
77
|
+
):
|
78
|
+
res = UnsharpMask(dirac.shape, sigma, coeff, method="imagej").unsharp(dirac)
|
79
|
+
assert np.max(np.abs(res - ref)) < 1e-3, "Something wrong with mode='imagej', sigma=%.2f, coeff=%.2f" % (
|
80
|
+
sigma,
|
81
|
+
coeff,
|
82
|
+
)
|
83
|
+
|
49
84
|
@pytest.mark.skipif(not (__has_pyopencl__), reason="Need pyopencl for this test")
|
50
|
-
def
|
85
|
+
def test_opencl_unsharp(self):
|
51
86
|
cl_queue = CommandQueue(self.cl_ctx)
|
52
87
|
d_image = parray.to_device(cl_queue, self.data)
|
53
88
|
d_out = parray.zeros_like(d_image)
|
@@ -61,13 +96,11 @@ class TestUnsharp:
|
|
61
96
|
self.check_result(res, method, error_msg_prefix="OpenclUnsharpMask")
|
62
97
|
|
63
98
|
@pytest.mark.skipif(not (__has_pycuda__), reason="Need cuda/pycuda for this test")
|
64
|
-
def
|
99
|
+
def test_cuda_unsharp(self):
|
65
100
|
d_image = garray.to_gpu(self.data)
|
66
101
|
d_out = garray.zeros_like(d_image)
|
67
102
|
for method in CudaUnsharpMask.avail_methods:
|
68
|
-
cuda_unsharp = CudaUnsharpMask(
|
69
|
-
self.data.shape, self.sigma, self.coeff, method=method, cuda_options={"ctx": self.ctx}
|
70
|
-
)
|
103
|
+
cuda_unsharp = CudaUnsharpMask(self.data.shape, self.sigma, self.coeff, method=method, ctx=self.ctx)
|
71
104
|
cuda_unsharp.unsharp(d_image, output=d_out)
|
72
105
|
res = d_out.get()
|
73
106
|
self.check_result(res, method, error_msg_prefix="CudaUnsharpMask")
|
@@ -0,0 +1,126 @@
|
|
1
|
+
import numpy as np
|
2
|
+
from ..utils import get_opencl_srcfile, get_cuda_srcfile, updiv, BaseClassError, MissingComponentError
|
3
|
+
from ..opencl.utils import __has_pyopencl__
|
4
|
+
from ..cuda.utils import __has_pycuda__
|
5
|
+
|
6
|
+
if __has_pyopencl__:
|
7
|
+
from ..opencl.kernel import OpenCLKernel
|
8
|
+
from ..opencl.processing import OpenCLProcessing
|
9
|
+
from pyopencl.tools import dtype_to_ctype as cl_dtype_to_ctype
|
10
|
+
else:
|
11
|
+
OpenCLKernel = OpenCLProcessing = cl_dtype_to_ctype = MissingComponentError("need pyopencl to use this class")
|
12
|
+
if __has_pycuda__:
|
13
|
+
from ..cuda.kernel import CudaKernel
|
14
|
+
from ..cuda.processing import CudaProcessing
|
15
|
+
from pycuda.tools import base_dtype_to_ctype as cu_dtype_to_ctype
|
16
|
+
else:
|
17
|
+
CudaKernel = CudaProcessing = cu_dtype_to_ctype = MissingComponentError("need pycuda to use this class")
|
18
|
+
|
19
|
+
|
20
|
+
# pylint: disable=E1101, E1102
|
21
|
+
class TransposeBase:
|
22
|
+
"""
|
23
|
+
A class for transposing (out-of-place) a cuda or opencl array
|
24
|
+
"""
|
25
|
+
|
26
|
+
KernelCls = BaseClassError
|
27
|
+
ProcessingCls = BaseClassError
|
28
|
+
dtype_to_ctype = BaseClassError
|
29
|
+
backend = "none"
|
30
|
+
|
31
|
+
def __init__(self, shape, dtype, dst_dtype=None, **backend_options):
|
32
|
+
self.processing = self.ProcessingCls(**(backend_options or {}))
|
33
|
+
self.shape = shape
|
34
|
+
self.dtype = dtype
|
35
|
+
self.dst_dtype = dst_dtype or dtype
|
36
|
+
if len(shape) != 2:
|
37
|
+
raise ValueError("Expected 2D array")
|
38
|
+
|
39
|
+
self._kernel_init_args = [
|
40
|
+
"transpose",
|
41
|
+
]
|
42
|
+
self._kernel_init_kwargs = {
|
43
|
+
"options": [
|
44
|
+
"-DSRC_DTYPE=%s" % self.dtype_to_ctype(self.dtype),
|
45
|
+
"-DDST_DTYPE=%s" % self.dtype_to_ctype(self.dst_dtype),
|
46
|
+
],
|
47
|
+
}
|
48
|
+
self._configure_kenel_initialization()
|
49
|
+
self._transpose_kernel = self.KernelCls(*self._kernel_init_args, **self._kernel_init_kwargs)
|
50
|
+
self._configure_kernel_call()
|
51
|
+
|
52
|
+
def __call__(self, arr, dst=None):
|
53
|
+
if dst is None:
|
54
|
+
dst = self.processing.allocate_array("dst", self.shape[::-1], dtype=self.dst_dtype)
|
55
|
+
self._transpose_kernel(arr, dst, np.int32(self.shape[1]), np.int32(self.shape[0]), **self._kernel_kwargs)
|
56
|
+
return dst
|
57
|
+
|
58
|
+
|
59
|
+
class CudaTranspose(TransposeBase):
|
60
|
+
KernelCls = CudaKernel
|
61
|
+
ProcessingCls = CudaProcessing
|
62
|
+
dtype_to_ctype = cu_dtype_to_ctype
|
63
|
+
backend = "cuda"
|
64
|
+
|
65
|
+
def _configure_kenel_initialization(self):
|
66
|
+
self._kernel_init_kwargs.update(
|
67
|
+
{
|
68
|
+
"filename": get_cuda_srcfile("transpose.cu"),
|
69
|
+
"signature": "PPii",
|
70
|
+
}
|
71
|
+
)
|
72
|
+
|
73
|
+
def _configure_kernel_call(self):
|
74
|
+
block = (32, 32, 1)
|
75
|
+
grid = [updiv(a, b) for a, b in zip(self.shape, block)]
|
76
|
+
self._kernel_kwargs = {"grid": grid, "block": block}
|
77
|
+
|
78
|
+
|
79
|
+
class OpenCLTranspose(TransposeBase):
|
80
|
+
KernelCls = OpenCLKernel
|
81
|
+
ProcessingCls = OpenCLProcessing
|
82
|
+
dtype_to_ctype = cl_dtype_to_ctype
|
83
|
+
backend = "opencl"
|
84
|
+
|
85
|
+
def _configure_kenel_initialization(self):
|
86
|
+
self._kernel_init_args.append(self.processing.ctx)
|
87
|
+
self._kernel_init_kwargs.update(
|
88
|
+
{
|
89
|
+
"filename": get_opencl_srcfile("transpose.cl"),
|
90
|
+
"queue": self.processing.queue,
|
91
|
+
}
|
92
|
+
)
|
93
|
+
|
94
|
+
def _configure_kernel_call(self):
|
95
|
+
block = (16, 16, 1)
|
96
|
+
grid = [updiv(a, b) * b for a, b in zip(self.shape, block)]
|
97
|
+
self._kernel_kwargs = {"global_size": grid, "local_size": block}
|
98
|
+
|
99
|
+
|
100
|
+
#
|
101
|
+
# An attempt to have a simplified access to transpose operation
|
102
|
+
#
|
103
|
+
|
104
|
+
# (backend, shape, dtype, dtype_out)
|
105
|
+
_transposes_store = {}
|
106
|
+
|
107
|
+
|
108
|
+
def transpose(array, dst=None, **backend_options):
|
109
|
+
if hasattr(array, "with_queue"):
|
110
|
+
backend = "opencl"
|
111
|
+
transpose_cls = OpenCLTranspose
|
112
|
+
backend_options["queue"] = array.queue # !
|
113
|
+
elif hasattr(array, "bind_to_texref"):
|
114
|
+
backend = "cuda"
|
115
|
+
transpose_cls = CudaTranspose
|
116
|
+
else:
|
117
|
+
raise ValueError("array should be either a pycuda.gpuarray.GPUArray or pyopencl.array.Array instance")
|
118
|
+
|
119
|
+
dst_dtype = dst.dtype if dst is not None else None
|
120
|
+
key = (backend, array.shape, np.dtype(array.dtype), dst_dtype)
|
121
|
+
transpose_instance = _transposes_store.get(key, None)
|
122
|
+
if transpose_instance is None:
|
123
|
+
transpose_instance = transpose_cls(array.shape, array.dtype, dst_dtype=dst_dtype, **backend_options)
|
124
|
+
_transposes_store[key] = transpose_instance
|
125
|
+
|
126
|
+
return transpose_instance(array, dst=dst)
|
@@ -0,0 +1,79 @@
|
|
1
|
+
import numpy as np
|
2
|
+
from scipy.ndimage import convolve1d
|
3
|
+
from silx.image.utils import gaussian_kernel
|
4
|
+
|
5
|
+
|
6
|
+
class UnsharpMask:
|
7
|
+
"""
|
8
|
+
A helper class for unsharp masking.
|
9
|
+
"""
|
10
|
+
|
11
|
+
avail_methods = ["gaussian", "log", "imagej"]
|
12
|
+
|
13
|
+
def __init__(self, shape, sigma, coeff, mode="reflect", method="gaussian"):
|
14
|
+
"""
|
15
|
+
Initialize a Unsharp mask.
|
16
|
+
`UnsharpedImage = (1 + coeff)*Image - coeff * ConvolutedImage`
|
17
|
+
|
18
|
+
If method == "log":
|
19
|
+
`UnsharpedImage = Image + coeff*ConvolutedImage`
|
20
|
+
|
21
|
+
Parameters
|
22
|
+
-----------
|
23
|
+
shape: tuple
|
24
|
+
Shape of the image.
|
25
|
+
sigma: float
|
26
|
+
Standard deviation of the Gaussian kernel
|
27
|
+
coeff: float
|
28
|
+
Coefficient in the linear combination of unsharp mask
|
29
|
+
mode: str, optional
|
30
|
+
Convolution mode. Default is "reflect"
|
31
|
+
method: str, optional
|
32
|
+
Method of unsharp mask. Can be "gaussian" (default) or "log" (Laplacian of Gaussian),
|
33
|
+
or "imagej".
|
34
|
+
|
35
|
+
|
36
|
+
Notes
|
37
|
+
-----
|
38
|
+
The computation is the following depending on the method:
|
39
|
+
|
40
|
+
- For method="gaussian": output = (1 + coeff) * image - coeff * image_blurred
|
41
|
+
- For method="log": output = image + coeff * image_blurred
|
42
|
+
- For method="imagej": output = (image - coeff*image_blurred)/(1-coeff)
|
43
|
+
"""
|
44
|
+
self.shape = shape
|
45
|
+
self.ndim = len(self.shape)
|
46
|
+
self.sigma = sigma
|
47
|
+
self.coeff = coeff
|
48
|
+
self._set_method(method)
|
49
|
+
self.mode = mode
|
50
|
+
self._compute_gaussian_kernel()
|
51
|
+
|
52
|
+
def _set_method(self, method):
|
53
|
+
if method not in self.avail_methods:
|
54
|
+
raise ValueError("Unknown unsharp method '%s'. Available are %s" % (method, str(self.avail_methods)))
|
55
|
+
self.method = method
|
56
|
+
|
57
|
+
def _compute_gaussian_kernel(self):
|
58
|
+
self._gaussian_kernel = np.ascontiguousarray(gaussian_kernel(self.sigma), dtype=np.float32)
|
59
|
+
|
60
|
+
def _blur2d(self, image):
|
61
|
+
res1 = convolve1d(image, self._gaussian_kernel, axis=1, mode=self.mode)
|
62
|
+
res = convolve1d(res1, self._gaussian_kernel, axis=0, mode=self.mode)
|
63
|
+
return res
|
64
|
+
|
65
|
+
def unsharp(self, image, output=None):
|
66
|
+
"""
|
67
|
+
Reference unsharp mask implementation.
|
68
|
+
"""
|
69
|
+
image_b = self._blur2d(image)
|
70
|
+
if self.method == "gaussian":
|
71
|
+
res = (1 + self.coeff) * image - self.coeff * image_b
|
72
|
+
elif self.method == "log":
|
73
|
+
res = image + self.coeff * image_b
|
74
|
+
else: # "imagej":
|
75
|
+
res = (image - self.coeff * image_b) / (1 - self.coeff)
|
76
|
+
if output is not None:
|
77
|
+
output[:] = res[:]
|
78
|
+
return output
|
79
|
+
return res
|
@@ -0,0 +1,53 @@
|
|
1
|
+
from ..cuda.utils import __has_pycuda__
|
2
|
+
from ..processing.convolution_cuda import Convolution
|
3
|
+
from ..cuda.processing import CudaProcessing
|
4
|
+
from .unsharp import UnsharpMask
|
5
|
+
|
6
|
+
if __has_pycuda__:
|
7
|
+
from pycuda.elementwise import ElementwiseKernel
|
8
|
+
|
9
|
+
|
10
|
+
class CudaUnsharpMask(UnsharpMask):
|
11
|
+
def __init__(self, shape, sigma, coeff, mode="reflect", method="gaussian", **cuda_options):
|
12
|
+
"""
|
13
|
+
Unsharp Mask, cuda backend.
|
14
|
+
"""
|
15
|
+
super().__init__(shape, sigma, coeff, mode=mode, method=method)
|
16
|
+
self.cuda_processing = CudaProcessing(**(cuda_options or {}))
|
17
|
+
self._init_convolution()
|
18
|
+
self._init_mad_kernel()
|
19
|
+
self.cuda_processing.init_arrays_to_none(["_d_out"])
|
20
|
+
|
21
|
+
def _init_convolution(self):
|
22
|
+
self.convolution = Convolution(
|
23
|
+
self.shape,
|
24
|
+
self._gaussian_kernel,
|
25
|
+
mode=self.mode,
|
26
|
+
extra_options={ # Use the lowest amount of memory
|
27
|
+
"allocate_input_array": False,
|
28
|
+
"allocate_output_array": False,
|
29
|
+
"allocate_tmp_array": True,
|
30
|
+
},
|
31
|
+
)
|
32
|
+
|
33
|
+
def _init_mad_kernel(self):
|
34
|
+
# garray.GPUArray.mul_add is out of place...
|
35
|
+
self.mad_kernel = ElementwiseKernel(
|
36
|
+
"float* array, float fac, float* other, float otherfac",
|
37
|
+
"array[i] = fac * array[i] + otherfac * other[i]",
|
38
|
+
name="mul_add",
|
39
|
+
)
|
40
|
+
|
41
|
+
def unsharp(self, image, output=None):
|
42
|
+
if output is None:
|
43
|
+
output = self.cuda_processing.allocate_array("_d_out", self.shape, "f")
|
44
|
+
self.convolution(image, output=output)
|
45
|
+
if self.method == "gaussian":
|
46
|
+
self.mad_kernel(output, -self.coeff, image, 1.0 + self.coeff)
|
47
|
+
elif self.method == "log":
|
48
|
+
# output = output * coeff + image where output was image_blurred
|
49
|
+
self.mad_kernel(output, self.coeff, image, 1.0)
|
50
|
+
else: # "imagej":
|
51
|
+
# output = (image - coeff*image_blurred)/(1-coeff) where output was image_blurred
|
52
|
+
self.mad_kernel(output, -self.coeff / (1 - self.coeff), image, 1.0 / (1 - self.coeff))
|
53
|
+
return output
|
@@ -0,0 +1,75 @@
|
|
1
|
+
try:
|
2
|
+
import pyopencl.array as parray
|
3
|
+
from pyopencl.elementwise import ElementwiseKernel
|
4
|
+
from ..opencl.processing import OpenCLProcessing
|
5
|
+
|
6
|
+
__have_opencl__ = True
|
7
|
+
except ImportError:
|
8
|
+
__have_opencl__ = False
|
9
|
+
from .unsharp import UnsharpMask
|
10
|
+
|
11
|
+
|
12
|
+
class OpenclUnsharpMask(UnsharpMask):
|
13
|
+
def __init__(
|
14
|
+
self,
|
15
|
+
shape,
|
16
|
+
sigma,
|
17
|
+
coeff,
|
18
|
+
mode="reflect",
|
19
|
+
method="gaussian",
|
20
|
+
**opencl_options,
|
21
|
+
):
|
22
|
+
"""
|
23
|
+
NB: For now, this class is designed to use the lowest amount of GPU memory
|
24
|
+
as possible. Therefore, the input and output image/volumes are assumed
|
25
|
+
to be already on device.
|
26
|
+
"""
|
27
|
+
if not (__have_opencl__):
|
28
|
+
raise ImportError("Need pyopencl")
|
29
|
+
super().__init__(shape, sigma, coeff, mode=mode, method=method)
|
30
|
+
self.cl_processing = OpenCLProcessing(**(opencl_options or {}))
|
31
|
+
self._init_convolution()
|
32
|
+
self._init_mad_kernel()
|
33
|
+
|
34
|
+
def _init_convolution(self):
|
35
|
+
# Do it here because silx creates OpenCL contexts all over the place at import
|
36
|
+
from silx.opencl.convolution import Convolution as CLConvolution
|
37
|
+
|
38
|
+
self.convolution = CLConvolution(
|
39
|
+
self.shape,
|
40
|
+
self._gaussian_kernel,
|
41
|
+
mode=self.mode,
|
42
|
+
ctx=self.cl_processing.ctx,
|
43
|
+
extra_options={ # Use the lowest amount of memory
|
44
|
+
"allocate_input_array": False,
|
45
|
+
"allocate_output_array": False,
|
46
|
+
"allocate_tmp_array": True,
|
47
|
+
"dont_use_textures": True,
|
48
|
+
},
|
49
|
+
)
|
50
|
+
|
51
|
+
def _init_mad_kernel(self):
|
52
|
+
# parray.Array.mul_add is out of place...
|
53
|
+
self.mad_kernel = ElementwiseKernel(
|
54
|
+
self.cl_processing.ctx,
|
55
|
+
"float* array, float fac, float* other, float otherfac",
|
56
|
+
"array[i] = fac * array[i] + otherfac * other[i]",
|
57
|
+
name="mul_add",
|
58
|
+
)
|
59
|
+
|
60
|
+
def unsharp(self, image, output):
|
61
|
+
# For now image and output are assumed to be already allocated on device
|
62
|
+
assert isinstance(image, self.cl_processing.array_class)
|
63
|
+
assert isinstance(output, self.cl_processing.array_class)
|
64
|
+
self.convolution(image, output=output)
|
65
|
+
if self.method == "gaussian":
|
66
|
+
self.mad_kernel(output, -self.coeff, image, 1.0 + self.coeff)
|
67
|
+
elif self.method == "log":
|
68
|
+
self.mad_kernel(output, self.coeff, image, 1.0)
|
69
|
+
else: # "imagej":
|
70
|
+
self.mad_kernel(output, -self.coeff / (1 - self.coeff), image, 1.0 / (1 - self.coeff))
|
71
|
+
return output
|
72
|
+
|
73
|
+
|
74
|
+
# Alias
|
75
|
+
OpenCLUnsharpMask = OpenclUnsharpMask
|
nabu/reconstruction/fbp.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import numpy as np
|
2
2
|
import pycuda.driver as cuda
|
3
3
|
from ..utils import updiv, get_cuda_srcfile
|
4
|
-
from ..cuda.utils import copy_array
|
4
|
+
from ..cuda.utils import copy_array, check_textures_availability
|
5
5
|
from ..cuda.processing import CudaProcessing
|
6
6
|
from ..cuda.kernel import CudaKernel
|
7
7
|
from .filtering_cuda import CudaSinoFilter
|
@@ -16,10 +16,16 @@ class CudaBackprojector(BackprojectorBase):
|
|
16
16
|
SinoFilterClass = CudaSinoFilter
|
17
17
|
SinoMultClass = CudaSinoMult
|
18
18
|
|
19
|
+
def _check_textures_availability(self):
|
20
|
+
self._use_textures = self.extra_options.get("use_textures", True) and check_textures_availability()
|
21
|
+
|
19
22
|
def _get_kernel_signature(self):
|
20
|
-
kern_full_sig = list("
|
23
|
+
kern_full_sig = list("PPiifiiffPPPf")
|
21
24
|
if self._axis_correction is None:
|
22
|
-
kern_full_sig[
|
25
|
+
kern_full_sig[11] = ""
|
26
|
+
if self._use_textures:
|
27
|
+
# texture references - no object is passed (deprecated, removed in Cuda 12)
|
28
|
+
kern_full_sig[1] = ""
|
23
29
|
return "".join(kern_full_sig)
|
24
30
|
|
25
31
|
def _get_kernel_options(self):
|
@@ -39,17 +45,30 @@ class CudaBackprojector(BackprojectorBase):
|
|
39
45
|
"shared_size": self._kernel_options["shared_size"],
|
40
46
|
}
|
41
47
|
)
|
48
|
+
# texture references - no object is passed (deprecated, removed in Cuda 12)
|
49
|
+
if self._use_textures:
|
50
|
+
self.kern_proj_args.pop(1)
|
51
|
+
else:
|
52
|
+
self._d_sino = self._processing.allocate_array("_d_sino", self.sino_shape)
|
53
|
+
self.kern_proj_args[1] = self._d_sino.gpudata
|
42
54
|
|
43
55
|
def _prepare_textures(self):
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
56
|
+
if self._use_textures:
|
57
|
+
self.texref_proj = self.gpu_projector.module.get_texref(self._kernel_options["texture_name"])
|
58
|
+
self.texref_proj.set_filter_mode(cuda.filter_mode.LINEAR)
|
59
|
+
self.gpu_projector.prepare(self._kernel_options["kernel_signature"], [self.texref_proj])
|
60
|
+
# Bind texture
|
61
|
+
self._d_sino_cua = cuda.np_to_array(np.zeros(self.sino_shape, "f"), "C")
|
62
|
+
self.texref_proj.set_array(self._d_sino_cua)
|
63
|
+
else:
|
64
|
+
# d_sino_ref = self._d_sino.gpudata
|
65
|
+
# self.kern_proj_args.insert(2, d_sino_ref)
|
66
|
+
self.gpu_projector.prepare(self._kernel_options["kernel_signature"], [])
|
50
67
|
|
51
68
|
def _compile_kernels(self):
|
52
69
|
self._prepare_kernel_args()
|
70
|
+
if self._use_textures:
|
71
|
+
self._kernel_options["sourcemodule_options"].append("-DUSE_TEXTURES")
|
53
72
|
self.gpu_projector = CudaKernel(
|
54
73
|
self._kernel_options["kernel_name"],
|
55
74
|
filename=self._kernel_options["file_name"],
|
@@ -60,7 +79,12 @@ class CudaBackprojector(BackprojectorBase):
|
|
60
79
|
self._prepare_textures() # has to be done after compilation for Cuda (to bind texture to built kernel)
|
61
80
|
|
62
81
|
def _transfer_to_texture(self, sino, do_checks=True):
|
63
|
-
|
82
|
+
if self._use_textures:
|
83
|
+
copy_array(self._d_sino_cua, sino, check=do_checks)
|
84
|
+
else:
|
85
|
+
if id(self._d_sino) == id(sino):
|
86
|
+
return
|
87
|
+
self._d_sino[:] = sino[:]
|
64
88
|
|
65
89
|
|
66
90
|
# COMPAT.
|