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
@@ -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
|