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
@@ -0,0 +1,29 @@
1
+ import numpy as np
2
+ from .processing_base import ProcessingBase
3
+
4
+
5
+ class MulAdd:
6
+ processing_cls = ProcessingBase
7
+
8
+ def __init__(self, **backend_options):
9
+ self.processing = self.processing_cls(**(backend_options or {}))
10
+ self._init_finalize()
11
+
12
+ def _init_finalize(self):
13
+ pass
14
+
15
+ def mul_add(self, dst, other, fac_dst, fac_other, dst_region=None, other_region=None):
16
+ if dst_region is None:
17
+ dst_slice_y = dst_slice_x = slice(None, None)
18
+ else:
19
+ dst_slice_y, dst_slice_x = dst_region
20
+ if other_region is None:
21
+ other_slice_y = other_slice_x = slice(None, None)
22
+ else:
23
+ other_slice_y, other_slice_x = other_region
24
+
25
+ dst[dst_slice_y, dst_slice_x] = (
26
+ fac_dst * dst[dst_slice_y, dst_slice_x] + fac_other * other[other_slice_y, other_slice_x]
27
+ )
28
+
29
+ __call__ = mul_add
@@ -0,0 +1,68 @@
1
+ import numpy as np
2
+ from nabu.utils import get_cuda_srcfile, updiv
3
+ from .muladd import MulAdd
4
+ from ..cuda.utils import __has_pycuda__
5
+ from ..cuda.processing import CudaProcessing
6
+
7
+ if __has_pycuda__:
8
+ import pycuda.gpuarray as garray
9
+
10
+
11
+ class CudaMulAdd(MulAdd):
12
+ processing_cls = CudaProcessing
13
+
14
+ def _init_finalize(self):
15
+ self._init_kernel()
16
+
17
+ def _init_kernel(self):
18
+ self.muladd_kernel = self.processing.kernel(
19
+ "mul_add",
20
+ filename=get_cuda_srcfile("ElementOp.cu"), # signature="PPiiffiiii"
21
+ )
22
+
23
+ def mul_add(self, dst, other, fac_dst, fac_other, dst_region=None, other_region=None):
24
+ """
25
+ 'region' should be a tuple (slice(y_start, y_end), slice(x_start, x_end))
26
+ """
27
+
28
+ if dst_region is None:
29
+ dst_coords = (0, dst.shape[1], 0, dst.shape[0])
30
+ else:
31
+ dst_coords = (dst_region[1].start, dst_region[1].stop, dst_region[0].start, dst_region[0].stop)
32
+ if other_region is None:
33
+ other_coords = (0, other.shape[1], 0, other.shape[0])
34
+ else:
35
+ other_coords = (other_region[1].start, other_region[1].stop, other_region[0].start, other_region[0].stop)
36
+
37
+ delta_x = np.diff(dst_coords[:2])
38
+ delta_y = np.diff(dst_coords[2:])
39
+ if delta_x != np.diff(other_coords[:2]) or delta_y != np.diff(other_coords[2:]):
40
+ raise ValueError("Invalid dst_region and other_region provided. Regions must have the same size")
41
+ if delta_x == 0 or delta_y == 0:
42
+ raise ValueError("delta_x or delta_y is 0")
43
+
44
+ # can't use "int4" in pycuda ? int2 seems fine. Go figure
45
+ dst_x_range = np.array(dst_coords[:2], dtype=garray.vec.int2)
46
+ dst_y_range = np.array(dst_coords[2:], dtype=garray.vec.int2)
47
+ other_x_range = np.array(other_coords[:2], dtype=garray.vec.int2)
48
+ other_y_range = np.array(other_coords[2:], dtype=garray.vec.int2)
49
+
50
+ block = (32, 32, 1)
51
+ grid = [updiv(length, b) for (length, b) in zip((delta_x[0], delta_y[0]), block)]
52
+
53
+ self.muladd_kernel(
54
+ dst,
55
+ other,
56
+ np.int32(dst.shape[1]),
57
+ np.int32(other.shape[1]),
58
+ np.float32(fac_dst),
59
+ np.float32(fac_other),
60
+ dst_x_range,
61
+ dst_y_range,
62
+ other_x_range,
63
+ other_y_range,
64
+ grid=grid,
65
+ block=block,
66
+ )
67
+
68
+ __call__ = mul_add
@@ -0,0 +1,71 @@
1
+ import numpy as np
2
+ from ..utils import check_supported
3
+
4
+
5
+ class PaddingBase:
6
+ """
7
+ A class for performing padding based on coordinate transform.
8
+ The Cuda and OpenCL backends will subclass this class.
9
+ """
10
+
11
+ supported_modes = ["constant", "edge", "reflect", "symmetric", "wrap"]
12
+
13
+ def __init__(self, shape, pad_width, mode="constant", **kwargs):
14
+ """
15
+ Initialize a Padding object.
16
+
17
+ Parameters
18
+ ----------
19
+ shape: tuple
20
+ Image shape
21
+ pad_width: tuple
22
+ Padding width for each axis. Please see the documentation of numpy.pad().
23
+ mode: str
24
+ Padding mode
25
+
26
+ Other parameters
27
+ ----------------
28
+ constant_values: tuple
29
+ Tuple containing the values to fill when mode="constant" (as in numpy.pad)
30
+ """
31
+ if len(shape) != 2:
32
+ raise ValueError("This class only works on images")
33
+ self.shape = shape
34
+ self._set_mode(mode, **kwargs)
35
+ self._get_padding_arrays(pad_width)
36
+
37
+ def _set_mode(self, mode, **kwargs):
38
+ # COMPAT.
39
+ if mode == "edges":
40
+ mode = "edge"
41
+ #
42
+ check_supported(mode, self.supported_modes, "padding mode")
43
+ self.mode = mode
44
+ self._kwargs = kwargs
45
+
46
+ def _get_padding_arrays(self, pad_width):
47
+ self.pad_width = pad_width
48
+ if isinstance(pad_width, tuple) and isinstance(pad_width[0], np.ndarray):
49
+ # user-defined coordinate transform
50
+ err_msg = "pad_width must be either a scalar, a tuple in the form ((a, b), (c, d)), or a tuple of two one-dimensional numpy arrays (eg. use numpy.indices(..., sparse=True))"
51
+ if len(pad_width) != 2:
52
+ raise ValueError(err_msg)
53
+ if any([np.squeeze(pw).ndim > 1 for pw in pad_width]):
54
+ raise ValueError(err_msg)
55
+ if self.mode == "constant":
56
+ raise ValueError("Custom coordinate transform does not work with mode='constant'")
57
+ self.mode = "custom"
58
+ self.coords_rows, self.coords_cols = pad_width
59
+ else:
60
+ if self.mode == "constant":
61
+ # no need for coordinate transform here
62
+ constant_values = self._kwargs.get("constant_values", 0)
63
+ self.padded_array_constant = np.pad(
64
+ np.zeros(self.shape, dtype="f"), self.pad_width, mode="constant", constant_values=constant_values
65
+ )
66
+ self.padded_shape = self.padded_array_constant.shape
67
+ return
68
+ R, C = np.indices(self.shape, dtype=np.int32, sparse=True)
69
+ self.coords_rows = np.pad(R.ravel(), self.pad_width[0], mode=self.mode)
70
+ self.coords_cols = np.pad(C.ravel(), self.pad_width[1], mode=self.mode)
71
+ self.padded_shape = (self.coords_rows.size, self.coords_cols.size)
@@ -0,0 +1,75 @@
1
+ import numpy as np
2
+ from ..utils import get_cuda_srcfile, updiv
3
+ from ..cuda.processing import CudaProcessing
4
+ from ..cuda.utils import __has_pycuda__
5
+ from .padding_base import PaddingBase
6
+
7
+
8
+ class CudaPadding(PaddingBase):
9
+ """
10
+ A class for performing padding on GPU using Cuda
11
+ """
12
+
13
+ backend = "cuda"
14
+
15
+ # TODO docstring from base class
16
+ def __init__(self, shape, pad_width, mode="constant", cuda_options=None, **kwargs):
17
+ super().__init__(shape, pad_width, mode=mode, **kwargs)
18
+ self.cuda_processing = self.processing = CudaProcessing(**(cuda_options or {}))
19
+ self._init_cuda_coordinate_transform()
20
+
21
+ def _init_cuda_coordinate_transform(self):
22
+ if self.mode == "constant":
23
+ self.d_padded_array_constant = self.processing.to_device(
24
+ "d_padded_array_constant", self.padded_array_constant
25
+ )
26
+ return
27
+ self._coords_transform_kernel = self.processing.kernel(
28
+ "coordinate_transform",
29
+ filename=get_cuda_srcfile("padding.cu"),
30
+ signature="PPPPiii",
31
+ )
32
+ self._coords_transform_block = (32, 32, 1)
33
+ self._coords_transform_grid = [
34
+ updiv(a, b) for a, b in zip(self.padded_shape[::-1], self._coords_transform_block)
35
+ ]
36
+ self.d_coords_rows = self.processing.to_device("d_coords_rows", self.coords_rows)
37
+ self.d_coords_cols = self.processing.to_device("d_coords_cols", self.coords_cols)
38
+
39
+ def _pad_constant(self, image, output):
40
+ pad_y, pad_x = self.pad_width
41
+ self.d_padded_array_constant[pad_y[0] : pad_y[0] + self.shape[0], pad_x[0] : pad_x[0] + self.shape[1]] = image[
42
+ :
43
+ ]
44
+ output[:] = self.d_padded_array_constant[:]
45
+ return output
46
+
47
+ def pad(self, image, output=None):
48
+ """
49
+ Pad an array.
50
+
51
+ Parameters
52
+ ----------
53
+ image: pycuda.gpuarray.GPUArray
54
+ Image to pad
55
+ output: pycuda.gpuarray.GPUArray, optional
56
+ Output image. If provided, must be in the expected shape.
57
+ """
58
+ if output is None:
59
+ output = self.processing.allocate_array("d_output", self.padded_shape)
60
+ if self.mode == "constant":
61
+ return self._pad_constant(image, output)
62
+ self._coords_transform_kernel(
63
+ image,
64
+ output,
65
+ self.d_coords_cols,
66
+ self.d_coords_rows,
67
+ np.int32(self.shape[1]),
68
+ np.int32(self.padded_shape[1]),
69
+ np.int32(self.padded_shape[0]),
70
+ grid=self._coords_transform_grid,
71
+ block=self._coords_transform_block,
72
+ )
73
+ return output
74
+
75
+ __call__ = pad
@@ -0,0 +1,77 @@
1
+ import numpy as np
2
+ from ..utils import get_opencl_srcfile
3
+ from ..opencl.processing import OpenCLProcessing
4
+ from .padding_base import PaddingBase
5
+ from ..opencl.utils import __has_pyopencl__
6
+
7
+ if __has_pyopencl__:
8
+ from ..opencl.memcpy import OpenCLMemcpy2D
9
+
10
+
11
+ class OpenCLPadding(PaddingBase):
12
+ """
13
+ A class for performing padding on GPU using OpenCL
14
+ """
15
+
16
+ backend = "opencl"
17
+
18
+ # TODO docstring from base class
19
+ def __init__(self, shape, pad_width, mode="constant", opencl_options=None, **kwargs):
20
+ super().__init__(shape, pad_width, mode=mode, **kwargs)
21
+ self.opencl_processing = self.processing = OpenCLProcessing(**(opencl_options or {}))
22
+ self.queue = self.opencl_processing.queue
23
+ self._init_opencl_coordinate_transform()
24
+
25
+ def _init_opencl_coordinate_transform(self):
26
+ if self.mode == "constant":
27
+ self.d_padded_array_constant = self.processing.to_device(
28
+ "d_padded_array_constant", self.padded_array_constant
29
+ )
30
+ self.memcpy2D = OpenCLMemcpy2D(ctx=self.processing.ctx, queue=self.queue)
31
+ return
32
+ self._coords_transform_kernel = self.processing.kernel(
33
+ "coordinate_transform",
34
+ filename=get_opencl_srcfile("padding.cl"),
35
+ )
36
+ self._coords_transform_global_size = self.padded_shape[::-1]
37
+ self.d_coords_rows = self.processing.to_device("d_coords_rows", self.coords_rows)
38
+ self.d_coords_cols = self.processing.to_device("d_coords_cols", self.coords_cols)
39
+
40
+ def _pad_constant(self, image, output):
41
+ pad_y, pad_x = self.pad_width
42
+ # the following line is not implemented in pyopencl
43
+ # self.d_padded_array_constant[pad_y[0] : pad_y[0] + self.shape[0], pad_x[0] : pad_x[0] + self.shape[1]] = image[:]
44
+ # cl.enqueue_copy is too cumbersome to use for Buffer <-> Buffer.
45
+ # Use a dedicated kernel instead.
46
+ # This is not optimal (two copies) - TODO write a constant padding kernel
47
+ self.memcpy2D(self.d_padded_array_constant, image, image.shape[::-1], dst_offset_xy=(pad_x[0], pad_y[0]))
48
+ output[:] = self.d_padded_array_constant[:]
49
+ return output
50
+
51
+ def pad(self, image, output=None):
52
+ """
53
+ Pad an array.
54
+
55
+ Parameters
56
+ ----------
57
+ image: pyopencl array
58
+ Image to pad
59
+ output: pyopencl array
60
+ Output image. If provided, must be in the expected shape.
61
+ """
62
+ if output is None:
63
+ output = self.processing.allocate_array("d_output", self.padded_shape)
64
+ if self.mode == "constant":
65
+ return self._pad_constant(image, output)
66
+ self._coords_transform_kernel(
67
+ self.queue,
68
+ image,
69
+ output,
70
+ self.d_coords_cols,
71
+ self.d_coords_rows,
72
+ np.int32(self.shape[1]),
73
+ np.int32(self.padded_shape[1]),
74
+ np.int32(self.padded_shape[0]),
75
+ global_size=self._coords_transform_global_size,
76
+ )
77
+ return output
@@ -0,0 +1,123 @@
1
+ import numpy as np
2
+ from ..utils import BaseClassError
3
+
4
+ """
5
+ Base class for OpenCLProcessing and CudaProcessing
6
+ Should not be used directly
7
+ """
8
+
9
+
10
+ class ProcessingBase:
11
+ array_class = None
12
+ dtype_to_ctype = BaseClassError
13
+
14
+ def __init__(self):
15
+ self._allocated = {}
16
+
17
+ def init_arrays_to_none(self, arrays_names):
18
+ """
19
+ Initialize arrays to None. After calling this method, the current instance will
20
+ have self.array_name = None, and self._old_array_name = None.
21
+
22
+ Parameters
23
+ ----------
24
+ arrays_names: list of str
25
+ List of arrays names.
26
+ """
27
+ for array_name in arrays_names:
28
+ setattr(self, array_name, None)
29
+ setattr(self, "_old_" + array_name, None)
30
+ self._allocated[array_name] = False
31
+
32
+ def recover_arrays_references(self, arrays_names):
33
+ """
34
+ Performs self._array_name = self._old_array_name,
35
+ for each array_name in arrays_names.
36
+
37
+ Parameters
38
+ ----------
39
+ arrays_names: list of str
40
+ List of array names
41
+ """
42
+ for array_name in arrays_names:
43
+ old_arr = getattr(self, "_old_" + array_name, None)
44
+ if old_arr is not None:
45
+ setattr(self, array_name, old_arr)
46
+
47
+ def _allocate_array_mem(self, shape, dtype):
48
+ raise ValueError("Base class")
49
+
50
+ def allocate_array(self, array_name, shape, dtype=np.float32):
51
+ """
52
+ Allocate a GPU array on the current context/stream/device,
53
+ and set 'self.array_name' to this array.
54
+
55
+ Parameters
56
+ ----------
57
+ array_name: str
58
+ Name of the array (for book-keeping)
59
+ shape: tuple of int
60
+ Array shape
61
+ dtype: numpy.dtype, optional
62
+ Data type. Default is float32.
63
+ """
64
+ if not self._allocated.get(array_name, False):
65
+ new_device_arr = self._allocate_array_mem(shape, dtype)
66
+ setattr(self, array_name, new_device_arr)
67
+ self._allocated[array_name] = True
68
+ return getattr(self, array_name)
69
+
70
+ def set_array(self, array_name, array_ref, dtype=np.float32):
71
+ """
72
+ Set the content of a device array.
73
+
74
+ Parameters
75
+ ----------
76
+ array_name: str
77
+ Array name. This method will look for self.array_name.
78
+ array_ref: array (numpy or GPU array)
79
+ Array containing the data to copy to 'array_name'.
80
+ dtype: numpy.dtype, optional
81
+ Data type. Default is float32.
82
+ """
83
+ if isinstance(array_ref, self.array_class):
84
+ current_arr = getattr(self, array_name, None)
85
+ setattr(self, "_old_" + array_name, current_arr)
86
+ setattr(self, array_name, array_ref)
87
+ elif isinstance(array_ref, np.ndarray):
88
+ self.allocate_array(array_name, array_ref.shape, dtype=dtype)
89
+ getattr(self, array_name).set(array_ref)
90
+ else:
91
+ raise ValueError("Expected numpy array or pycuda array")
92
+ return getattr(self, array_name)
93
+
94
+ def get_array(self, array_name):
95
+ return getattr(self, array_name, None)
96
+
97
+ # COMPAT.
98
+ _init_arrays_to_none = init_arrays_to_none
99
+ _recover_arrays_references = recover_arrays_references
100
+ _allocate_array = allocate_array
101
+ _set_array = set_array
102
+
103
+ def check_array(self, arr, expected_shape, expected_dtype="f", check_contiguous=True):
104
+ """
105
+ Check whether a given array is suitable for being processed (shape, dtype, contiguous)
106
+ """
107
+ if arr.shape != expected_shape:
108
+ raise ValueError("Expected shape %s but got %s" % (str(expected_shape), str(arr.shape)))
109
+ if arr.dtype != np.dtype(expected_dtype):
110
+ raise ValueError("Expected data type %s but got %s" % (str(expected_dtype), str(arr.dtype)))
111
+ if check_contiguous:
112
+ if isinstance(arr, np.ndarray) and not (arr.flags["C_CONTIGUOUS"]):
113
+ raise ValueError("Expected C-contiguous array")
114
+ if isinstance(arr, self.array_class) and not arr.flags.c_contiguous:
115
+ raise ValueError("Expected C-contiguous array")
116
+
117
+ def kernel(self, *args, **kwargs):
118
+ raise ValueError("Base class")
119
+
120
+ def to_device(self, array_name, array):
121
+ arr_ref = self.allocate_array(array_name, array.shape, dtype=array.dtype)
122
+ arr_ref.set(array)
123
+ return arr_ref
@@ -0,0 +1,64 @@
1
+ #
2
+ # WIP !
3
+ #
4
+ import numpy as np
5
+ from ..opencl.utils import __has_pyopencl__
6
+ from ..utils import get_opencl_srcfile
7
+
8
+ if __has_pyopencl__:
9
+ import pyopencl as cl
10
+ from ..opencl.processing import OpenCLProcessing
11
+ from ..opencl.kernel import OpenCLKernel
12
+ from pyopencl.tools import dtype_to_ctype as cl_dtype_to_ctype
13
+
14
+
15
+ class OpenCLRoll:
16
+ def __init__(self, dtype, direction=1, offset=None, **processing_kwargs):
17
+ self.processing = OpenCLProcessing(queue=processing_kwargs.get("queue", None))
18
+ self.dtype = np.dtype(dtype)
19
+ compile_options = ["-DDTYPE=%s" % cl_dtype_to_ctype(self.dtype)]
20
+ self.offset = offset or 0
21
+ self.roll_kernel = OpenCLKernel(
22
+ "roll_forward_x",
23
+ None,
24
+ queue=self.processing.queue,
25
+ filename=get_opencl_srcfile("roll.cl"),
26
+ options=compile_options,
27
+ )
28
+ self.shmem = cl.LocalMemory(self.dtype.itemsize)
29
+ self.direction = direction
30
+ if self.direction < 0:
31
+ self.revert_kernel = OpenCLKernel(
32
+ "revert_array_x",
33
+ None,
34
+ queue=self.processing.queue,
35
+ filename=get_opencl_srcfile("roll.cl"),
36
+ options=compile_options,
37
+ )
38
+
39
+ def __call__(self, arr):
40
+ ny, nx = arr.shape
41
+ # Launch one big horizontal workgroup
42
+ wg_x = min((nx - self.offset) // 2, self.processing.queue.device.max_work_group_size)
43
+ local_size = (wg_x, 1, 1)
44
+ global_size = [wg_x, ny]
45
+ if self.direction < 0:
46
+ local_size2 = None
47
+ global_size2 = [nx - self.offset, ny]
48
+ self.revert_kernel(
49
+ arr, np.int32(nx), np.int32(ny), np.int32(self.offset), local_size=local_size2, global_size=global_size2
50
+ )
51
+ self.roll_kernel(
52
+ arr,
53
+ np.int32(nx),
54
+ np.int32(ny),
55
+ np.int32(self.offset),
56
+ self.shmem,
57
+ local_size=local_size,
58
+ global_size=global_size,
59
+ )
60
+ if self.direction < 0:
61
+ self.revert_kernel(
62
+ arr, np.int32(nx), np.int32(ny), np.int32(self.offset), local_size=local_size2, global_size=global_size2
63
+ )
64
+ return arr
@@ -0,0 +1,63 @@
1
+ try:
2
+ from skimage.transform import rotate
3
+
4
+ __have__skimage__ = True
5
+ except ImportError:
6
+ __have__skimage__ = False
7
+
8
+
9
+ class Rotation:
10
+ supported_modes = {
11
+ "constant": "constant",
12
+ "zeros": "constant",
13
+ "edge": "edge",
14
+ "edges": "edge",
15
+ "symmetric": "symmetric",
16
+ "sym": "symmetric",
17
+ "reflect": "reflect",
18
+ "wrap": "wrap",
19
+ "periodic": "wrap",
20
+ }
21
+
22
+ def __init__(self, shape, angle, center=None, mode="edge", reshape=False, **sk_kwargs):
23
+ """
24
+ Initiate a Rotation object.
25
+
26
+ Parameters
27
+ ----------
28
+ shape: tuple of int
29
+ Shape of the images to process
30
+ angle: float
31
+ Rotation angle in DEGREES
32
+ center: tuple of float, optional
33
+ Coordinates of the center of rotation, in the format (X, Y) (mind the non-python
34
+ convention !).
35
+ Default is ((Nx - 1)/2.0, (Ny - 1)/2.0)
36
+ mode: str, optional
37
+ Padding mode. Default is "edge".
38
+ reshape: bool, optional
39
+
40
+
41
+ Other Parameters
42
+ -----------------
43
+ All the other parameters are passed directly to scikit image 'rotate' function:
44
+ order, cval, clip, preserve_range.
45
+ """
46
+ self.shape = shape
47
+ self.angle = angle
48
+ self.center = center
49
+ self.mode = mode
50
+ self.reshape = reshape
51
+ self.sk_kwargs = sk_kwargs
52
+
53
+ def rotate(self, img, output=None):
54
+ if not __have__skimage__:
55
+ raise ValueError("scikit-image is needed for using rotate()")
56
+ res = rotate(img, self.angle, resize=self.reshape, center=self.center, mode=self.mode, **self.sk_kwargs)
57
+ if output is not None:
58
+ output[:] = res[:]
59
+ return output
60
+ else:
61
+ return res
62
+
63
+ __call__ = rotate
@@ -0,0 +1,66 @@
1
+ import numpy as np
2
+ from .rotation import Rotation
3
+ from ..utils import get_cuda_srcfile, updiv
4
+ from ..cuda.utils import __has_pycuda__, copy_array
5
+ from ..cuda.processing import CudaProcessing
6
+
7
+ if __has_pycuda__:
8
+ from ..cuda.kernel import CudaKernel
9
+ import pycuda.driver as cuda
10
+
11
+
12
+ class CudaRotation(Rotation):
13
+ def __init__(self, shape, angle, center=None, mode="edge", reshape=False, cuda_options=None, **sk_kwargs):
14
+ if center is None:
15
+ center = ((shape[1] - 1) / 2.0, (shape[0] - 1) / 2.0)
16
+ super().__init__(shape, angle, center=center, mode=mode, reshape=reshape, **sk_kwargs)
17
+ self._init_cuda_rotation(cuda_options)
18
+
19
+ def _init_cuda_rotation(self, cuda_options):
20
+ cuda_options = cuda_options or {}
21
+ self.cuda_processing = CudaProcessing(**cuda_options)
22
+ self._allocate_arrays()
23
+ self._init_rotation_kernel()
24
+
25
+ def _allocate_arrays(self):
26
+ self._d_image_cua = cuda.np_to_array(np.zeros(self.shape, "f"), "C")
27
+ self.cuda_processing.init_arrays_to_none(["d_output"])
28
+
29
+ def _init_rotation_kernel(self):
30
+ self.cuda_rotation_kernel = CudaKernel("rotate", get_cuda_srcfile("rotation.cu"))
31
+ self.texref_image = self.cuda_rotation_kernel.module.get_texref("tex_image")
32
+ self.texref_image.set_filter_mode(cuda.filter_mode.LINEAR) # bilinear
33
+ self.texref_image.set_address_mode(0, cuda.address_mode.CLAMP) # TODO tune
34
+ self.texref_image.set_address_mode(1, cuda.address_mode.CLAMP) # TODO tune
35
+ self.cuda_rotation_kernel.prepare("Piiffff", [self.texref_image])
36
+ self.texref_image.set_array(self._d_image_cua)
37
+ self._cos_theta = np.cos(np.deg2rad(self.angle))
38
+ self._sin_theta = np.sin(np.deg2rad(self.angle))
39
+ self._Nx = np.int32(self.shape[1])
40
+ self._Ny = np.int32(self.shape[0])
41
+ self._center_x = np.float32(self.center[0])
42
+ self._center_y = np.float32(self.center[1])
43
+ self._block = (32, 32, 1) # tune ?
44
+ self._grid = (updiv(self.shape[1], self._block[1]), updiv(self.shape[0], self._block[0]), 1)
45
+
46
+ def rotate(self, img, output=None, do_checks=True):
47
+ copy_array(self._d_image_cua, img, check=do_checks)
48
+ if output is not None:
49
+ d_out = output
50
+ else:
51
+ self.cuda_processing.allocate_array("d_output", self.shape, np.float32)
52
+ d_out = self.cuda_processing.d_output
53
+ self.cuda_rotation_kernel(
54
+ d_out,
55
+ self._Nx,
56
+ self._Ny,
57
+ self._cos_theta,
58
+ self._sin_theta,
59
+ self._center_x,
60
+ self._center_y,
61
+ grid=self._grid,
62
+ block=self._block,
63
+ )
64
+ return d_out
65
+
66
+ __call__ = rotate
File without changes