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.
Files changed (183) hide show
  1. doc/conf.py +1 -1
  2. doc/doc_config.py +32 -0
  3. nabu/__init__.py +2 -1
  4. nabu/app/bootstrap_stitching.py +1 -1
  5. nabu/app/cli_configs.py +122 -2
  6. nabu/app/composite_cor.py +27 -2
  7. nabu/app/correct_rot.py +70 -0
  8. nabu/app/create_distortion_map_from_poly.py +42 -18
  9. nabu/app/diag_to_pix.py +358 -0
  10. nabu/app/diag_to_rot.py +449 -0
  11. nabu/app/generate_header.py +4 -3
  12. nabu/app/histogram.py +2 -2
  13. nabu/app/multicor.py +6 -1
  14. nabu/app/parse_reconstruction_log.py +151 -0
  15. nabu/app/prepare_weights_double.py +83 -22
  16. nabu/app/reconstruct.py +5 -1
  17. nabu/app/reconstruct_helical.py +7 -0
  18. nabu/app/reduce_dark_flat.py +6 -3
  19. nabu/app/rotate.py +4 -4
  20. nabu/app/stitching.py +16 -2
  21. nabu/app/tests/test_reduce_dark_flat.py +18 -2
  22. nabu/app/validator.py +4 -4
  23. nabu/cuda/convolution.py +8 -376
  24. nabu/cuda/fft.py +4 -0
  25. nabu/cuda/kernel.py +4 -4
  26. nabu/cuda/medfilt.py +5 -158
  27. nabu/cuda/padding.py +5 -71
  28. nabu/cuda/processing.py +23 -2
  29. nabu/cuda/src/ElementOp.cu +78 -0
  30. nabu/cuda/src/backproj.cu +28 -2
  31. nabu/cuda/src/fourier_wavelets.cu +2 -2
  32. nabu/cuda/src/normalization.cu +23 -0
  33. nabu/cuda/src/padding.cu +2 -2
  34. nabu/cuda/src/transpose.cu +16 -0
  35. nabu/cuda/utils.py +39 -0
  36. nabu/estimation/alignment.py +10 -1
  37. nabu/estimation/cor.py +808 -38
  38. nabu/estimation/cor_sino.py +7 -9
  39. nabu/estimation/tests/test_cor.py +85 -3
  40. nabu/io/reader.py +26 -18
  41. nabu/io/tests/test_cast_volume.py +3 -3
  42. nabu/io/tests/test_detector_distortion.py +3 -3
  43. nabu/io/tiffwriter_zmm.py +2 -2
  44. nabu/io/utils.py +14 -4
  45. nabu/io/writer.py +5 -3
  46. nabu/misc/fftshift.py +6 -0
  47. nabu/misc/histogram.py +5 -285
  48. nabu/misc/histogram_cuda.py +8 -104
  49. nabu/misc/kernel_base.py +3 -121
  50. nabu/misc/padding_base.py +5 -69
  51. nabu/misc/processing_base.py +3 -107
  52. nabu/misc/rotation.py +5 -62
  53. nabu/misc/rotation_cuda.py +5 -65
  54. nabu/misc/transpose.py +6 -0
  55. nabu/misc/unsharp.py +3 -78
  56. nabu/misc/unsharp_cuda.py +5 -52
  57. nabu/misc/unsharp_opencl.py +8 -85
  58. nabu/opencl/fft.py +6 -0
  59. nabu/opencl/kernel.py +21 -6
  60. nabu/opencl/padding.py +5 -72
  61. nabu/opencl/processing.py +27 -5
  62. nabu/opencl/src/backproj.cl +3 -3
  63. nabu/opencl/src/fftshift.cl +65 -12
  64. nabu/opencl/src/padding.cl +2 -2
  65. nabu/opencl/src/roll.cl +96 -0
  66. nabu/opencl/src/transpose.cl +16 -0
  67. nabu/pipeline/config_validators.py +63 -3
  68. nabu/pipeline/dataset_validator.py +2 -2
  69. nabu/pipeline/estimators.py +193 -35
  70. nabu/pipeline/fullfield/chunked.py +34 -17
  71. nabu/pipeline/fullfield/chunked_cuda.py +7 -5
  72. nabu/pipeline/fullfield/computations.py +48 -13
  73. nabu/pipeline/fullfield/nabu_config.py +13 -13
  74. nabu/pipeline/fullfield/processconfig.py +10 -5
  75. nabu/pipeline/fullfield/reconstruction.py +1 -2
  76. nabu/pipeline/helical/fbp.py +5 -0
  77. nabu/pipeline/helical/filtering.py +12 -9
  78. nabu/pipeline/helical/gridded_accumulator.py +179 -33
  79. nabu/pipeline/helical/helical_chunked_regridded.py +262 -151
  80. nabu/pipeline/helical/helical_chunked_regridded_cuda.py +4 -11
  81. nabu/pipeline/helical/helical_reconstruction.py +56 -18
  82. nabu/pipeline/helical/span_strategy.py +1 -1
  83. nabu/pipeline/helical/tests/test_accumulator.py +4 -0
  84. nabu/pipeline/params.py +23 -2
  85. nabu/pipeline/processconfig.py +3 -8
  86. nabu/pipeline/tests/test_chunk_reader.py +78 -0
  87. nabu/pipeline/tests/test_estimators.py +120 -2
  88. nabu/pipeline/utils.py +25 -0
  89. nabu/pipeline/writer.py +2 -0
  90. nabu/preproc/ccd_cuda.py +9 -7
  91. nabu/preproc/ctf.py +21 -26
  92. nabu/preproc/ctf_cuda.py +25 -25
  93. nabu/preproc/double_flatfield.py +14 -2
  94. nabu/preproc/double_flatfield_cuda.py +7 -11
  95. nabu/preproc/flatfield_cuda.py +23 -27
  96. nabu/preproc/phase.py +19 -24
  97. nabu/preproc/phase_cuda.py +21 -21
  98. nabu/preproc/shift_cuda.py +58 -28
  99. nabu/preproc/tests/test_ctf.py +5 -5
  100. nabu/preproc/tests/test_double_flatfield.py +2 -2
  101. nabu/preproc/tests/test_vshift.py +13 -2
  102. nabu/processing/__init__.py +0 -0
  103. nabu/processing/convolution_cuda.py +375 -0
  104. nabu/processing/fft_base.py +163 -0
  105. nabu/processing/fft_cuda.py +256 -0
  106. nabu/processing/fft_opencl.py +54 -0
  107. nabu/processing/fftshift.py +134 -0
  108. nabu/processing/histogram.py +286 -0
  109. nabu/processing/histogram_cuda.py +103 -0
  110. nabu/processing/kernel_base.py +126 -0
  111. nabu/processing/medfilt_cuda.py +159 -0
  112. nabu/processing/muladd.py +29 -0
  113. nabu/processing/muladd_cuda.py +68 -0
  114. nabu/processing/padding_base.py +71 -0
  115. nabu/processing/padding_cuda.py +75 -0
  116. nabu/processing/padding_opencl.py +77 -0
  117. nabu/processing/processing_base.py +123 -0
  118. nabu/processing/roll_opencl.py +64 -0
  119. nabu/processing/rotation.py +63 -0
  120. nabu/processing/rotation_cuda.py +66 -0
  121. nabu/processing/tests/__init__.py +0 -0
  122. nabu/processing/tests/test_fft.py +268 -0
  123. nabu/processing/tests/test_fftshift.py +71 -0
  124. nabu/{misc → processing}/tests/test_histogram.py +2 -4
  125. nabu/{cuda → processing}/tests/test_medfilt.py +1 -1
  126. nabu/processing/tests/test_muladd.py +54 -0
  127. nabu/{cuda → processing}/tests/test_padding.py +119 -75
  128. nabu/processing/tests/test_roll.py +63 -0
  129. nabu/{misc → processing}/tests/test_rotation.py +3 -2
  130. nabu/processing/tests/test_transpose.py +72 -0
  131. nabu/{misc → processing}/tests/test_unsharp.py +41 -8
  132. nabu/processing/transpose.py +126 -0
  133. nabu/processing/unsharp.py +79 -0
  134. nabu/processing/unsharp_cuda.py +53 -0
  135. nabu/processing/unsharp_opencl.py +75 -0
  136. nabu/reconstruction/fbp.py +34 -10
  137. nabu/reconstruction/fbp_base.py +35 -16
  138. nabu/reconstruction/fbp_opencl.py +7 -12
  139. nabu/reconstruction/filtering.py +2 -2
  140. nabu/reconstruction/filtering_cuda.py +13 -14
  141. nabu/reconstruction/filtering_opencl.py +3 -4
  142. nabu/reconstruction/projection.py +2 -0
  143. nabu/reconstruction/rings.py +158 -1
  144. nabu/reconstruction/rings_cuda.py +218 -58
  145. nabu/reconstruction/sinogram_cuda.py +16 -12
  146. nabu/reconstruction/tests/test_deringer.py +116 -14
  147. nabu/reconstruction/tests/test_fbp.py +22 -31
  148. nabu/reconstruction/tests/test_filtering.py +11 -2
  149. nabu/resources/dataset_analyzer.py +89 -26
  150. nabu/resources/nxflatfield.py +2 -2
  151. nabu/resources/tests/test_nxflatfield.py +1 -1
  152. nabu/resources/utils.py +9 -2
  153. nabu/stitching/alignment.py +184 -0
  154. nabu/stitching/config.py +241 -39
  155. nabu/stitching/definitions.py +6 -0
  156. nabu/stitching/frame_composition.py +4 -2
  157. nabu/stitching/overlap.py +99 -3
  158. nabu/stitching/sample_normalization.py +60 -0
  159. nabu/stitching/slurm_utils.py +10 -10
  160. nabu/stitching/tests/test_alignment.py +99 -0
  161. nabu/stitching/tests/test_config.py +16 -1
  162. nabu/stitching/tests/test_overlap.py +68 -2
  163. nabu/stitching/tests/test_sample_normalization.py +49 -0
  164. nabu/stitching/tests/test_slurm_utils.py +5 -5
  165. nabu/stitching/tests/test_utils.py +3 -33
  166. nabu/stitching/tests/test_z_stitching.py +391 -22
  167. nabu/stitching/utils.py +144 -202
  168. nabu/stitching/z_stitching.py +309 -126
  169. nabu/testutils.py +18 -0
  170. nabu/thirdparty/tomocupy_remove_stripe.py +586 -0
  171. nabu/utils.py +32 -6
  172. {nabu-2023.2.1.dist-info → nabu-2024.1.0rc3.dist-info}/LICENSE +1 -1
  173. {nabu-2023.2.1.dist-info → nabu-2024.1.0rc3.dist-info}/METADATA +5 -5
  174. nabu-2024.1.0rc3.dist-info/RECORD +296 -0
  175. {nabu-2023.2.1.dist-info → nabu-2024.1.0rc3.dist-info}/WHEEL +1 -1
  176. {nabu-2023.2.1.dist-info → nabu-2024.1.0rc3.dist-info}/entry_points.txt +5 -1
  177. nabu/conftest.py +0 -14
  178. nabu/opencl/fftshift.py +0 -92
  179. nabu/opencl/tests/test_fftshift.py +0 -55
  180. nabu/opencl/tests/test_padding.py +0 -84
  181. nabu-2023.2.1.dist-info/RECORD +0 -252
  182. /nabu/cuda/src/{fftshift.cu → dfi_fftshift.cu} +0 -0
  183. {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.misc.rotation import Rotation, __have__skimage__
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.misc.rotation_cuda import CudaRotation
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.misc.unsharp import UnsharpMask
4
- from nabu.misc.unsharp_opencl import OpenclUnsharpMask, __have_opencl__ as __has_pyopencl__
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.misc.unsharp_cuda import CudaUnsharpMask
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 testOpenclUnsharp(self):
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 testCudaUnsharp(self):
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
@@ -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("PiifiiiiPPPf")
23
+ kern_full_sig = list("PPiifiiffPPPf")
21
24
  if self._axis_correction is None:
22
- kern_full_sig[10] = ""
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
- self.texref_proj = self.gpu_projector.module.get_texref(self._kernel_options["texture_name"])
45
- self.texref_proj.set_filter_mode(cuda.filter_mode.LINEAR)
46
- self.gpu_projector.prepare(self._kernel_options["kernel_signature"], [self.texref_proj])
47
- # Bind texture
48
- self._d_sino_cua = cuda.np_to_array(np.zeros(self.sino_shape, "f"), "C")
49
- self.texref_proj.set_array(self._d_sino_cua)
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
- copy_array(self._d_sino_cua, sino, check=do_checks)
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.