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,268 @@
|
|
1
|
+
from itertools import permutations
|
2
|
+
import pytest
|
3
|
+
import numpy as np
|
4
|
+
from scipy.fft import fftn, ifftn, rfftn, irfftn
|
5
|
+
from nabu.testutils import generate_tests_scenarios, get_data, get_array_of_given_shape, __do_long_tests__
|
6
|
+
from nabu.cuda.utils import get_cuda_context, __has_pycuda__
|
7
|
+
from nabu.processing.fft_cuda import SKCUFFT, VKCUFFT, has_vkfft as has_cuda_vkfft
|
8
|
+
from nabu.opencl.utils import __has_pyopencl__, get_opencl_context
|
9
|
+
from nabu.processing.fft_opencl import VKCLFFT, has_vkfft as has_cl_vkfft
|
10
|
+
from nabu.processing.fft_base import is_fast_axes
|
11
|
+
|
12
|
+
try:
|
13
|
+
import skcuda
|
14
|
+
|
15
|
+
__has_skcuda__ = True
|
16
|
+
except ImportError:
|
17
|
+
__has_skcuda__ = False
|
18
|
+
|
19
|
+
|
20
|
+
scenarios = {
|
21
|
+
"shape": [(256,), (300,), (300, 301), (300, 302)],
|
22
|
+
"r2c": [True, False],
|
23
|
+
"precision": ["simple"],
|
24
|
+
"backend": ["cuda", "opencl"],
|
25
|
+
}
|
26
|
+
|
27
|
+
if __do_long_tests__:
|
28
|
+
scenarios["shape"].extend([(307,), (125, 126, 260)])
|
29
|
+
scenarios["precision"].append("double")
|
30
|
+
|
31
|
+
scenarios = generate_tests_scenarios(scenarios)
|
32
|
+
|
33
|
+
|
34
|
+
@pytest.fixture(scope="class")
|
35
|
+
def bootstrap(request):
|
36
|
+
cls = request.cls
|
37
|
+
|
38
|
+
cls.data = get_data("chelsea.npz")["data"]
|
39
|
+
cls.abs_tol = {
|
40
|
+
"simple": {
|
41
|
+
1: 5e-3,
|
42
|
+
2: 1.0e0,
|
43
|
+
3: 5e2, # !
|
44
|
+
},
|
45
|
+
"double": {
|
46
|
+
1: 1e-10,
|
47
|
+
2: 1e-9,
|
48
|
+
3: 1e-7,
|
49
|
+
},
|
50
|
+
}
|
51
|
+
if __has_pycuda__:
|
52
|
+
cls.cu_ctx = get_cuda_context(cleanup_at_exit=False)
|
53
|
+
if __has_pyopencl__:
|
54
|
+
cls.cl_ctx = get_opencl_context("all")
|
55
|
+
yield
|
56
|
+
if __has_pycuda__:
|
57
|
+
cls.cu_ctx.pop()
|
58
|
+
|
59
|
+
|
60
|
+
def _get_fft_cls(backend):
|
61
|
+
fft_cls = None
|
62
|
+
if backend == "cuda":
|
63
|
+
if not (has_cuda_vkfft() and __has_pycuda__):
|
64
|
+
pytest.skip("Need vkfft and pycuda to use VKCUFFT")
|
65
|
+
fft_cls = VKCUFFT
|
66
|
+
if backend == "opencl":
|
67
|
+
if not (has_cl_vkfft() and __has_pyopencl__):
|
68
|
+
pytest.skip("Need vkfft and pyopencl to use VKCLFFT")
|
69
|
+
fft_cls = VKCLFFT
|
70
|
+
return fft_cls
|
71
|
+
|
72
|
+
|
73
|
+
@pytest.mark.usefixtures("bootstrap")
|
74
|
+
class TestFFT:
|
75
|
+
def _get_data_array(self, config):
|
76
|
+
r2c = config["r2c"]
|
77
|
+
shape = config["shape"]
|
78
|
+
precision = config["precision"]
|
79
|
+
dtype = {
|
80
|
+
True: {"simple": np.float32, "double": np.float64},
|
81
|
+
False: {"simple": np.complex64, "double": np.complex128},
|
82
|
+
}[r2c][precision]
|
83
|
+
data = get_array_of_given_shape(self.data, shape, dtype)
|
84
|
+
return data
|
85
|
+
|
86
|
+
@staticmethod
|
87
|
+
def check_result(res, ref, config, tol, name=""):
|
88
|
+
err_max = np.max(np.abs(res - ref))
|
89
|
+
err_msg = "%s FFT(%s, r2c=%s): tol=%.2e, but max error = %.2e" % (
|
90
|
+
name,
|
91
|
+
str(config["shape"]),
|
92
|
+
str(config["r2c"]),
|
93
|
+
tol,
|
94
|
+
err_max,
|
95
|
+
)
|
96
|
+
assert np.allclose(res, ref, atol=tol), err_msg
|
97
|
+
|
98
|
+
def _do_fft(self, data, r2c, axes=None, return_fft_obj=False, backend_cls=None):
|
99
|
+
ctx = self.cu_ctx if backend_cls.backend == "cuda" else self.cl_ctx
|
100
|
+
fft = backend_cls(data.shape, data.dtype, r2c=r2c, axes=axes, ctx=ctx)
|
101
|
+
d_data = fft.processing.allocate_array("_data", data.shape, dtype=data.dtype)
|
102
|
+
d_data.set(data)
|
103
|
+
d_out = fft.fft(d_data)
|
104
|
+
res = d_out.get()
|
105
|
+
return (res, fft) if return_fft_obj else res
|
106
|
+
|
107
|
+
@staticmethod
|
108
|
+
def _do_reference_fft(data, r2c, axes=None):
|
109
|
+
ref_fft_func = rfftn if r2c else fftn
|
110
|
+
ref = ref_fft_func(data, axes=axes)
|
111
|
+
return ref
|
112
|
+
|
113
|
+
@staticmethod
|
114
|
+
def _do_reference_ifft(data, r2c, axes=None):
|
115
|
+
ref_ifft_func = irfftn if r2c else ifftn
|
116
|
+
ref = ref_ifft_func(data, axes=axes)
|
117
|
+
return ref
|
118
|
+
|
119
|
+
@pytest.mark.skipif(not (__has_skcuda__ and __has_pycuda__), reason="Need pycuda scikit-cuda for this test")
|
120
|
+
@pytest.mark.parametrize("config", scenarios)
|
121
|
+
def test_sckcuda(self, config):
|
122
|
+
r2c = config["r2c"]
|
123
|
+
shape = config["shape"]
|
124
|
+
precision = config["precision"]
|
125
|
+
ndim = len(shape)
|
126
|
+
if ndim == 3 and not (__do_long_tests__):
|
127
|
+
pytest.skip("3D FFTs are done only for long tests - use NABU_LONG_TESTS=1")
|
128
|
+
|
129
|
+
data = self._get_data_array(config)
|
130
|
+
|
131
|
+
res, cufft = self._do_fft(data, r2c, return_fft_obj=True, backend_cls=SKCUFFT)
|
132
|
+
ref = self._do_reference_fft(data, r2c)
|
133
|
+
|
134
|
+
tol = self.abs_tol[precision][ndim]
|
135
|
+
self.check_result(res, ref, config, tol, name="skcuda")
|
136
|
+
|
137
|
+
# Complex-to-complex can also be performed on real data (as in numpy.fft.fft(real_data))
|
138
|
+
if not (r2c):
|
139
|
+
res = self._do_fft(data, False, backend_cls=SKCUFFT)
|
140
|
+
ref = self._do_reference_fft(data, False)
|
141
|
+
self.check_result(res, ref, config, tol, name="skcuda")
|
142
|
+
|
143
|
+
# IFFT
|
144
|
+
res = cufft.ifft(cufft.output_fft).get()
|
145
|
+
self.check_result(res, data, config, tol, name="skcuda")
|
146
|
+
# Perhaps we should also check against numpy/scipy ifft,
|
147
|
+
# but it does not yield the good shape for R2C on odd-sized data
|
148
|
+
|
149
|
+
@pytest.mark.skipif(not (__has_skcuda__ and __has_pycuda__), reason="Need pycuda scikit-cuda for this test")
|
150
|
+
@pytest.mark.parametrize("config", scenarios)
|
151
|
+
def test_skcuda_batched(self, config):
|
152
|
+
shape = config["shape"]
|
153
|
+
if len(shape) == 1:
|
154
|
+
return
|
155
|
+
elif len(shape) == 3 and not (__do_long_tests__):
|
156
|
+
pytest.skip("3D FFTs are done only for long tests - use NABU_LONG_TESTS=1")
|
157
|
+
r2c = config["r2c"]
|
158
|
+
tol = self.abs_tol[config["precision"]][len(shape)]
|
159
|
+
|
160
|
+
data = self._get_data_array(config)
|
161
|
+
|
162
|
+
if data.ndim == 2:
|
163
|
+
axes_to_test = [(0,), (1,)]
|
164
|
+
elif data.ndim == 3:
|
165
|
+
# axes_to_test = [(1, 2), (2, 1), (2,)] # See fft.py: works for C2C but not R2C ?
|
166
|
+
axes_to_test = [(2,)]
|
167
|
+
|
168
|
+
for axes in axes_to_test:
|
169
|
+
res, cufft = self._do_fft(data, r2c, axes=axes, return_fft_obj=True, backend_cls=SKCUFFT)
|
170
|
+
ref = self._do_reference_fft(data, r2c, axes=axes)
|
171
|
+
self.check_result(res, ref, config, tol, name="skcuda batched axes=%s" % (str(axes)))
|
172
|
+
# IFFT
|
173
|
+
res = cufft.ifft(cufft.output_fft).get()
|
174
|
+
self.check_result(res, data, config, tol, name="skcuda")
|
175
|
+
|
176
|
+
@pytest.mark.parametrize("config", scenarios)
|
177
|
+
def test_vkfft(self, config):
|
178
|
+
backend = config["backend"]
|
179
|
+
fft_cls = _get_fft_cls(backend)
|
180
|
+
|
181
|
+
r2c = config["r2c"]
|
182
|
+
shape = config["shape"]
|
183
|
+
precision = config["precision"]
|
184
|
+
ndim = len(shape)
|
185
|
+
if ndim == 3 and not (__do_long_tests__):
|
186
|
+
pytest.skip("3D FFTs are done only for long tests - use NABU_LONG_TESTS=1")
|
187
|
+
|
188
|
+
if ndim >= 2 and r2c and shape[-1] & 1:
|
189
|
+
pytest.skip("R2C with odd-sized fast dimension is not supported in VKFFT")
|
190
|
+
|
191
|
+
data = self._get_data_array(config)
|
192
|
+
|
193
|
+
res, fft_obj = self._do_fft(data, r2c, return_fft_obj=True, backend_cls=fft_cls)
|
194
|
+
ref = self._do_reference_fft(data, r2c)
|
195
|
+
|
196
|
+
tol = self.abs_tol[precision][ndim]
|
197
|
+
self.check_result(res, ref, config, tol, name="vkfft_%s" % backend)
|
198
|
+
|
199
|
+
# Complex-to-complex can also be performed on real data (as in numpy.fft.fft(real_data))
|
200
|
+
if not (r2c):
|
201
|
+
res = self._do_fft(data, False, backend_cls=fft_cls)
|
202
|
+
ref = self._do_reference_fft(data, False)
|
203
|
+
self.check_result(res, ref, config, tol, name="vkfft_%s" % backend)
|
204
|
+
|
205
|
+
# IFFT
|
206
|
+
res = fft_obj.ifft(fft_obj.output_fft).get()
|
207
|
+
self.check_result(res, data, config, tol, name="vkfft_%s" % backend)
|
208
|
+
|
209
|
+
@pytest.mark.parametrize("config", scenarios)
|
210
|
+
def test_vkfft_batched(self, config):
|
211
|
+
backend = config["backend"]
|
212
|
+
fft_cls = _get_fft_cls(backend)
|
213
|
+
shape = config["shape"]
|
214
|
+
if len(shape) == 1:
|
215
|
+
return
|
216
|
+
elif len(shape) == 3 and not (__do_long_tests__):
|
217
|
+
pytest.skip("3D FFTs are done only for long tests - use NABU_LONG_TESTS=1")
|
218
|
+
r2c = config["r2c"]
|
219
|
+
tol = self.abs_tol[config["precision"]][len(shape)]
|
220
|
+
|
221
|
+
data = self._get_data_array(config)
|
222
|
+
|
223
|
+
if data.ndim >= 2 and r2c and shape[-1] & 1:
|
224
|
+
pytest.skip("R2C with odd-sized fast dimension is not supported in VKFFT")
|
225
|
+
|
226
|
+
# For R2C, only fastest axes are supported by vkfft
|
227
|
+
if data.ndim == 2:
|
228
|
+
axes_to_test = [(1,)]
|
229
|
+
elif data.ndim == 3:
|
230
|
+
axes_to_test = [
|
231
|
+
(1, 2),
|
232
|
+
(2,),
|
233
|
+
]
|
234
|
+
for axes in axes_to_test:
|
235
|
+
res, cufft = self._do_fft(data, r2c, axes=axes, return_fft_obj=True, backend_cls=fft_cls)
|
236
|
+
ref = self._do_reference_fft(data, r2c, axes=axes)
|
237
|
+
self.check_result(res, ref, config, tol, name="vkfft_%s batched axes=%s" % (backend, str(axes)))
|
238
|
+
# IFFT
|
239
|
+
res = cufft.ifft(cufft.output_fft).get()
|
240
|
+
self.check_result(res, data, config, tol, name="vkfft_%s" % backend)
|
241
|
+
|
242
|
+
@pytest.mark.skipif(not (__do_long_tests__), reason="Use NABU_LONG_TESTS=1 for this test")
|
243
|
+
def test_fast_axes_utility_function(self):
|
244
|
+
axes_to_test = {
|
245
|
+
2: {
|
246
|
+
(0, 1): True,
|
247
|
+
(1,): True,
|
248
|
+
(-1,): True,
|
249
|
+
(-2,): False,
|
250
|
+
(0,): False,
|
251
|
+
},
|
252
|
+
3: {
|
253
|
+
(0, 1, 2): True,
|
254
|
+
(0, 1): False,
|
255
|
+
(1, 2): True,
|
256
|
+
(2, 1): True,
|
257
|
+
(-2, -1): True,
|
258
|
+
(2,): True,
|
259
|
+
(-1,): True,
|
260
|
+
},
|
261
|
+
}
|
262
|
+
for ndim, axes_ in axes_to_test.items():
|
263
|
+
for axes, is_fast in axes_.items():
|
264
|
+
possible_axes = [axes]
|
265
|
+
if len(axes) > 1:
|
266
|
+
possible_axes = list(permutations(axes, len(axes)))
|
267
|
+
for ax in possible_axes:
|
268
|
+
assert is_fast_axes(ndim, ax) is is_fast
|
@@ -0,0 +1,71 @@
|
|
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
|
+
|
7
|
+
if __has_pyopencl__:
|
8
|
+
from nabu.processing.fftshift import OpenCLFFTshift
|
9
|
+
|
10
|
+
configs = {
|
11
|
+
"shape": [(300, 451), (300, 300), (255, 300)],
|
12
|
+
"axes": [(1,)],
|
13
|
+
"dtype_in_out": [(np.float32, np.complex64), (np.complex64, np.float32)],
|
14
|
+
"inplace": [True, False],
|
15
|
+
}
|
16
|
+
|
17
|
+
scenarios = generate_tests_scenarios(configs)
|
18
|
+
|
19
|
+
|
20
|
+
@pytest.fixture(scope="class")
|
21
|
+
def bootstrap(request):
|
22
|
+
cls = request.cls
|
23
|
+
cls.data = get_data("chelsea.npz")["data"]
|
24
|
+
cls.tol = 1e-7
|
25
|
+
if __has_pycuda__:
|
26
|
+
cls.cu_ctx = get_cuda_context(cleanup_at_exit=False)
|
27
|
+
if __has_pyopencl__:
|
28
|
+
cls.cl_ctx = get_opencl_context(device_type="all")
|
29
|
+
yield
|
30
|
+
if __has_pycuda__:
|
31
|
+
cls.cu_ctx.pop()
|
32
|
+
|
33
|
+
|
34
|
+
@pytest.mark.usefixtures("bootstrap")
|
35
|
+
class TestFFTshift:
|
36
|
+
def _do_test_fftshift(self, config, fftshift_cls):
|
37
|
+
shape = config["shape"]
|
38
|
+
dtype = config["dtype_in_out"][0]
|
39
|
+
dst_dtype = config["dtype_in_out"][1]
|
40
|
+
axes = config["axes"]
|
41
|
+
inplace = config["inplace"]
|
42
|
+
if inplace and shape[-1] & 1:
|
43
|
+
pytest.skip("Not Implemented")
|
44
|
+
data = np.ascontiguousarray(self.data[: shape[0], : shape[1]], dtype=dtype)
|
45
|
+
|
46
|
+
backend = fftshift_cls.backend
|
47
|
+
ctx = self.cu_ctx if backend == "cuda" else self.cl_ctx
|
48
|
+
backend_options = {"ctx": ctx}
|
49
|
+
if not (inplace):
|
50
|
+
fftshift = fftshift_cls(data.shape, dtype, dst_dtype=dst_dtype, axes=axes, **backend_options)
|
51
|
+
else:
|
52
|
+
fftshift = fftshift_cls(data.shape, dtype, axes=axes, **backend_options)
|
53
|
+
|
54
|
+
d_data = fftshift.processing.allocate_array("data", shape, dtype)
|
55
|
+
d_data.set(data)
|
56
|
+
|
57
|
+
d_res = fftshift.fftshift(d_data)
|
58
|
+
|
59
|
+
assert (
|
60
|
+
np.max(np.abs(d_res.get() - np.fft.fftshift(data, axes=axes))) == 0
|
61
|
+
), "something wrong with fftshift_%s(%s)" % (backend, str(config))
|
62
|
+
|
63
|
+
# @pytest.mark.skipif(not (__has_pycuda__), reason="Need pycuda for this test")
|
64
|
+
# @pytest.mark.parametrize("config", scenarios)
|
65
|
+
# def test_cuda_transpose(self, config):
|
66
|
+
# self._do_test_transpose(config, CudaTranspose)
|
67
|
+
|
68
|
+
@pytest.mark.skipif(not (__has_pyopencl__), reason="Need pyopencl for this test")
|
69
|
+
@pytest.mark.parametrize("config", scenarios)
|
70
|
+
def test_opencl_fftshift(self, config):
|
71
|
+
self._do_test_fftshift(config, OpenCLFFTshift)
|
@@ -1,13 +1,11 @@
|
|
1
|
-
from os import path
|
2
|
-
from tempfile import mkdtemp
|
3
1
|
import pytest
|
4
2
|
import numpy as np
|
5
3
|
from nabu.testutils import get_data
|
6
|
-
from nabu.
|
4
|
+
from nabu.processing.histogram import PartialHistogram
|
7
5
|
from nabu.cuda.utils import __has_pycuda__, get_cuda_context
|
8
6
|
|
9
7
|
if __has_pycuda__:
|
10
|
-
from nabu.
|
8
|
+
from nabu.processing.histogram_cuda import CudaPartialHistogram
|
11
9
|
import pycuda.gpuarray as garray
|
12
10
|
|
13
11
|
|
@@ -5,7 +5,7 @@ from nabu.testutils import generate_tests_scenarios, get_data
|
|
5
5
|
from nabu.cuda.utils import get_cuda_context, __has_pycuda__
|
6
6
|
|
7
7
|
if __has_pycuda__:
|
8
|
-
from nabu.
|
8
|
+
from nabu.processing.medfilt_cuda import MedianFilter
|
9
9
|
import pycuda.gpuarray as garray
|
10
10
|
|
11
11
|
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import pytest
|
2
|
+
import numpy as np
|
3
|
+
from nabu.processing.muladd import MulAdd
|
4
|
+
from nabu.testutils import get_data
|
5
|
+
from nabu.cuda.utils import get_cuda_context, __has_pycuda__
|
6
|
+
|
7
|
+
if __has_pycuda__:
|
8
|
+
from nabu.processing.muladd_cuda import CudaMulAdd
|
9
|
+
|
10
|
+
|
11
|
+
@pytest.fixture(scope="class")
|
12
|
+
def bootstrap(request):
|
13
|
+
cls = request.cls
|
14
|
+
cls.data = get_data("chelsea.npz")["data"].astype("f") # (300, 451)
|
15
|
+
cls.tol = 1e-7
|
16
|
+
if __has_pycuda__:
|
17
|
+
cls.cu_ctx = get_cuda_context(cleanup_at_exit=False)
|
18
|
+
yield
|
19
|
+
if __has_pycuda__:
|
20
|
+
cls.cu_ctx.pop()
|
21
|
+
|
22
|
+
|
23
|
+
@pytest.mark.usefixtures("bootstrap")
|
24
|
+
class TestMulad:
|
25
|
+
def test_muladd(self):
|
26
|
+
dst = self.data.copy()
|
27
|
+
other = self.data.copy()
|
28
|
+
mul_add = MulAdd()
|
29
|
+
|
30
|
+
# Test with no subregion
|
31
|
+
mul_add(dst, other, 1, 2)
|
32
|
+
assert np.allclose(dst, self.data * 1 + other * 2)
|
33
|
+
|
34
|
+
# Test with x-y subregion
|
35
|
+
dst = self.data.copy()
|
36
|
+
mul_add(dst, other, 0.5, 1.7, (slice(10, 200), slice(15, 124)), (slice(100, 290), slice(200, 309)))
|
37
|
+
assert np.allclose(dst[10:200, 15:124], self.data[10:200, 15:124] * 0.5 + self.data[100:290, 200:309] * 1.7)
|
38
|
+
|
39
|
+
@pytest.mark.skipif(not (__has_pycuda__), reason="Need Cuda/pycuda for this test")
|
40
|
+
def test_cuda_muladd(self):
|
41
|
+
mul_add = CudaMulAdd(ctx=self.cu_ctx)
|
42
|
+
dst = mul_add.processing.to_device("dst", self.data)
|
43
|
+
other = mul_add.processing.to_device("other", (self.data / 2).astype("f"))
|
44
|
+
|
45
|
+
# Test with no subregion
|
46
|
+
mul_add(dst, other, 3, 5)
|
47
|
+
assert np.allclose(dst.get(), self.data * 3 + (self.data / 2) * 5)
|
48
|
+
|
49
|
+
# Test with x-y subregion
|
50
|
+
dst.set(self.data)
|
51
|
+
mul_add(dst, other, 0.5, 1.7, (slice(10, 200), slice(15, 124)), (slice(100, 290), slice(200, 309)))
|
52
|
+
assert np.allclose(
|
53
|
+
dst.get()[10:200, 15:124], self.data[10:200, 15:124] * 0.5 + (self.data / 2)[100:290, 200:309] * 1.7
|
54
|
+
)
|
@@ -1,13 +1,130 @@
|
|
1
1
|
import numpy as np
|
2
2
|
import pytest
|
3
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.processing.padding_cuda import CudaPadding
|
6
|
+
from nabu.processing.padding_opencl import OpenCLPadding
|
4
7
|
from nabu.utils import calc_padding_lengths, get_cuda_srcfile
|
8
|
+
from nabu.testutils import __do_long_tests__
|
5
9
|
from nabu.testutils import get_data, generate_tests_scenarios
|
6
10
|
|
11
|
+
scenarios = {
|
12
|
+
"shape": [(511, 512), (512, 511)],
|
13
|
+
"pad_width": [((256, 255), (128, 127))],
|
14
|
+
"mode_cuda": CudaPadding.supported_modes[:2] if __has_pycuda__ else [],
|
15
|
+
"mode_opencl": OpenCLPadding.supported_modes[:2] if __has_pyopencl__ else [],
|
16
|
+
"constant_values": [0, ((1.0, 2.0), (3.0, 4.0))],
|
17
|
+
"output_is_none": [True, False],
|
18
|
+
"backend": ["cuda", "opencl"],
|
19
|
+
}
|
20
|
+
|
21
|
+
|
22
|
+
if __do_long_tests__:
|
23
|
+
scenarios["mode_cuda"] = CudaPadding.supported_modes if __has_pycuda__ else []
|
24
|
+
scenarios["mode_opencl"] = OpenCLPadding.supported_modes if __has_pyopencl__ else []
|
25
|
+
scenarios["pad_width"].extend([((0, 0), (6, 7))])
|
26
|
+
|
27
|
+
scenarios = generate_tests_scenarios(scenarios)
|
28
|
+
|
29
|
+
|
30
|
+
@pytest.fixture(scope="class")
|
31
|
+
def bootstrap(request):
|
32
|
+
cls = request.cls
|
33
|
+
cls.data = get_data("brain_phantom.npz")["data"]
|
34
|
+
cls.tol = 1e-7
|
35
|
+
if __has_pycuda__:
|
36
|
+
cls.cu_ctx = get_cuda_context(cleanup_at_exit=False)
|
37
|
+
if __has_pyopencl__:
|
38
|
+
cls.cl_ctx = get_opencl_context(device_type="all")
|
39
|
+
yield
|
40
|
+
if __has_pycuda__:
|
41
|
+
cls.cu_ctx.pop()
|
42
|
+
|
43
|
+
|
44
|
+
@pytest.mark.usefixtures("bootstrap")
|
45
|
+
class TestPadding:
|
46
|
+
@pytest.mark.parametrize("config", scenarios)
|
47
|
+
def test_padding(self, config):
|
48
|
+
backend = config["backend"]
|
49
|
+
shape = config["shape"]
|
50
|
+
padding_mode = config["mode_%s" % backend]
|
51
|
+
data = self.data[: shape[0], : shape[1]]
|
52
|
+
kwargs = {}
|
53
|
+
if padding_mode == "constant":
|
54
|
+
kwargs["constant_values"] = config["constant_values"]
|
55
|
+
ref = np.pad(data, config["pad_width"], mode=padding_mode, **kwargs)
|
56
|
+
|
57
|
+
PaddingCls = CudaPadding if backend == "cuda" else OpenCLPadding
|
58
|
+
if backend == "cuda":
|
59
|
+
backend_options = {"cuda_options": {"ctx": self.cu_ctx}}
|
60
|
+
else:
|
61
|
+
backend_options = {"opencl_options": {"ctx": self.cl_ctx}}
|
62
|
+
|
63
|
+
padding = PaddingCls(
|
64
|
+
config["shape"],
|
65
|
+
config["pad_width"],
|
66
|
+
mode=padding_mode,
|
67
|
+
constant_values=config["constant_values"],
|
68
|
+
**backend_options,
|
69
|
+
)
|
70
|
+
if config["output_is_none"]:
|
71
|
+
output = None
|
72
|
+
else:
|
73
|
+
output = padding.processing.allocate_array("output", ref.shape, dtype="f")
|
74
|
+
|
75
|
+
d_img = padding.processing.allocate_array("d_img", data.shape, dtype="f")
|
76
|
+
d_img.set(np.ascontiguousarray(data, dtype="f"))
|
77
|
+
res = padding.pad(d_img, output=output)
|
78
|
+
|
79
|
+
err_max = np.max(np.abs(res.get() - ref))
|
80
|
+
assert err_max < self.tol, str("Something wrong with padding for configuration %s" % (str(config)))
|
81
|
+
|
82
|
+
@pytest.mark.skipif(not (__has_pycuda__) and not (__has_pyopencl__), reason="need pycuda or pyopencl")
|
83
|
+
def test_custom_coordinate_transform(self):
|
84
|
+
data = self.data
|
85
|
+
R, C = np.indices(data.shape, dtype=np.int32)
|
86
|
+
|
87
|
+
pad_width = ((256, 255), (254, 251))
|
88
|
+
mode = "reflect"
|
89
|
+
|
90
|
+
coords_R = np.pad(R, pad_width[0], mode=mode)[:, 0]
|
91
|
+
coords_C = np.pad(C, pad_width[1], mode=mode)[0, :]
|
92
|
+
|
93
|
+
# Further transform of coordinates - here FFT layout
|
94
|
+
coords_R = np.roll(coords_R, -pad_width[0][0])
|
95
|
+
coords_C = np.roll(coords_C, -pad_width[1][0])
|
96
|
+
|
97
|
+
padding_classes_to_test = []
|
98
|
+
if __has_pycuda__:
|
99
|
+
padding_classes_to_test.append(CudaPadding)
|
100
|
+
if __has_pyopencl__:
|
101
|
+
padding_classes_to_test.append(OpenCLPadding)
|
102
|
+
|
103
|
+
for padding_cls in padding_classes_to_test:
|
104
|
+
ctx = self.cl_ctx if padding_cls.backend == "opencl" else self.cu_ctx
|
105
|
+
padding = padding_cls(data.shape, (coords_R, coords_C), mode=mode, ctx=ctx)
|
106
|
+
|
107
|
+
d_img = padding.processing.allocate_array("d_img", data.shape, dtype="f")
|
108
|
+
d_img.set(data)
|
109
|
+
d_out = padding.processing.allocate_array("d_out", padding.padded_shape, dtype="f")
|
110
|
+
|
111
|
+
res = padding.pad(d_img, output=d_out)
|
112
|
+
|
113
|
+
ref = np.roll(np.pad(data, pad_width, mode=mode), (-pad_width[0][0], -pad_width[1][0]), axis=(0, 1))
|
114
|
+
|
115
|
+
err_max = np.max(np.abs(d_out.get() - ref))
|
116
|
+
assert err_max < self.tol, "Something wrong with custom padding"
|
117
|
+
|
118
|
+
|
119
|
+
#
|
120
|
+
# The following is testing a previous version of padding kernels
|
121
|
+
# They use specific code (instead of a generic coordinate transform)
|
122
|
+
#
|
123
|
+
|
7
124
|
if __has_pycuda__:
|
8
|
-
import pycuda.gpuarray as garray
|
9
125
|
from nabu.cuda.kernel import CudaKernel
|
10
|
-
|
126
|
+
import pycuda.gpuarray as garray
|
127
|
+
|
11
128
|
|
12
129
|
scenarios_legacy = [
|
13
130
|
{
|
@@ -128,76 +245,3 @@ class TestPaddingLegacy:
|
|
128
245
|
# Compare
|
129
246
|
errmax = np.max(np.abs(self.d_data_padded.get() - data_padded_ref))
|
130
247
|
assert errmax < self.tol, "Max error is too high"
|
131
|
-
|
132
|
-
|
133
|
-
scenarios = generate_tests_scenarios(
|
134
|
-
{
|
135
|
-
"shape": [(511, 512), (512, 511)],
|
136
|
-
"pad_width": [((256, 255), (128, 127)), ((0, 0), (6, 7))],
|
137
|
-
"mode": CudaPadding.supported_modes if __has_pycuda__ else [],
|
138
|
-
"constant_values": [0, ((1.0, 2.0), (3.0, 4.0))],
|
139
|
-
"output_is_none": [True, False],
|
140
|
-
}
|
141
|
-
)
|
142
|
-
|
143
|
-
|
144
|
-
@pytest.fixture(scope="class")
|
145
|
-
def bootstrap(request):
|
146
|
-
cls = request.cls
|
147
|
-
cls.data = get_data("brain_phantom.npz")["data"]
|
148
|
-
cls.tol = 1e-7
|
149
|
-
cls.ctx = get_cuda_context(cleanup_at_exit=False)
|
150
|
-
yield
|
151
|
-
cls.ctx.pop()
|
152
|
-
|
153
|
-
|
154
|
-
@pytest.mark.skipif(not (__has_pycuda__), reason="Need Cuda and pycuda for this test")
|
155
|
-
@pytest.mark.usefixtures("bootstrap")
|
156
|
-
class TestCudaPadding:
|
157
|
-
@pytest.mark.parametrize("config", scenarios)
|
158
|
-
def test_padding(self, config):
|
159
|
-
shape = config["shape"]
|
160
|
-
data = self.data[: shape[0], : shape[1]]
|
161
|
-
kwargs = {}
|
162
|
-
if config["mode"] == "constant":
|
163
|
-
kwargs["constant_values"] = config["constant_values"]
|
164
|
-
ref = np.pad(data, config["pad_width"], mode=config["mode"], **kwargs)
|
165
|
-
if config["output_is_none"]:
|
166
|
-
output = None
|
167
|
-
else:
|
168
|
-
output = garray.zeros(ref.shape, "f")
|
169
|
-
cuda_padding = CudaPadding(
|
170
|
-
config["shape"],
|
171
|
-
config["pad_width"],
|
172
|
-
mode=config["mode"],
|
173
|
-
constant_values=config["constant_values"],
|
174
|
-
cuda_options={"ctx": self.ctx},
|
175
|
-
)
|
176
|
-
d_img = garray.to_gpu(np.ascontiguousarray(data, dtype="f"))
|
177
|
-
res = cuda_padding.pad(d_img, output=output)
|
178
|
-
|
179
|
-
err_max = np.max(np.abs(res.get() - ref))
|
180
|
-
assert err_max < self.tol, str("Something wrong with padding for configuration %s" % (str(config)))
|
181
|
-
|
182
|
-
def test_custom_coordinate_transform(self):
|
183
|
-
data = self.data
|
184
|
-
R, C = np.indices(data.shape, dtype=np.int32)
|
185
|
-
|
186
|
-
pad_width = ((256, 255), (254, 251))
|
187
|
-
mode = "reflect"
|
188
|
-
|
189
|
-
coords_R = np.pad(R, pad_width, mode=mode)
|
190
|
-
coords_C = np.pad(C, pad_width, mode=mode)
|
191
|
-
# Further transform of coordinates - here FFT layout
|
192
|
-
coords_R = np.roll(coords_R, (-pad_width[0][0], -pad_width[1][0]), axis=(0, 1))
|
193
|
-
coords_C = np.roll(coords_C, (-pad_width[0][0], -pad_width[1][0]), axis=(0, 1))
|
194
|
-
|
195
|
-
cuda_padding = CudaPadding(data.shape, (coords_R, coords_C), mode=mode, cuda_options={"ctx": self.ctx})
|
196
|
-
d_img = garray.to_gpu(data)
|
197
|
-
d_out = garray.zeros(cuda_padding.padded_shape, "f")
|
198
|
-
res = cuda_padding.pad(d_img, output=d_out)
|
199
|
-
|
200
|
-
ref = np.roll(np.pad(data, pad_width, mode=mode), (-pad_width[0][0], -pad_width[1][0]), axis=(0, 1))
|
201
|
-
|
202
|
-
err_max = np.max(np.abs(d_out.get() - ref))
|
203
|
-
assert err_max < self.tol, "Something wrong with custom padding"
|
@@ -0,0 +1,63 @@
|
|
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.roll_opencl import OpenCLRoll
|
7
|
+
|
8
|
+
configs_roll = {
|
9
|
+
"shape": [(300, 451), (300, 300), (255, 300)],
|
10
|
+
"offset_x": [0, 10, 155],
|
11
|
+
"dtype": [np.float32], # , np.complex64],
|
12
|
+
}
|
13
|
+
|
14
|
+
|
15
|
+
scenarios_roll = generate_tests_scenarios(configs_roll)
|
16
|
+
|
17
|
+
|
18
|
+
@pytest.fixture(scope="class")
|
19
|
+
def bootstrap_roll(request):
|
20
|
+
cls = request.cls
|
21
|
+
cls.data = get_data("chelsea.npz")["data"]
|
22
|
+
cls.tol = 1e-7
|
23
|
+
if __has_pycuda__:
|
24
|
+
cls.cu_ctx = get_cuda_context(cleanup_at_exit=False)
|
25
|
+
if __has_pyopencl__:
|
26
|
+
cls.cl_ctx = get_opencl_context(device_type="all")
|
27
|
+
yield
|
28
|
+
if __has_pycuda__:
|
29
|
+
cls.cu_ctx.pop()
|
30
|
+
|
31
|
+
|
32
|
+
@pytest.mark.usefixtures("bootstrap_roll")
|
33
|
+
class TestRoll:
|
34
|
+
@staticmethod
|
35
|
+
def _compute_ref(data, direction, offset):
|
36
|
+
ref = data.copy()
|
37
|
+
ref[:, offset:] = np.roll(data[:, offset:], direction, axis=1)
|
38
|
+
return ref
|
39
|
+
|
40
|
+
@pytest.mark.skipif(not (__has_pyopencl__), reason="Need pyopencl for this test")
|
41
|
+
@pytest.mark.parametrize("config", scenarios_roll)
|
42
|
+
def test_opencl_roll(self, config):
|
43
|
+
shape = config["shape"]
|
44
|
+
dtype = config["dtype"]
|
45
|
+
offset_x = config["offset_x"]
|
46
|
+
data = np.ascontiguousarray(self.data[: shape[0], : shape[1]], dtype=dtype)
|
47
|
+
|
48
|
+
ref_forward = self._compute_ref(data, 1, offset_x)
|
49
|
+
ref_backward = self._compute_ref(data, -1, offset_x)
|
50
|
+
|
51
|
+
roll_forward = OpenCLRoll(dtype, direction=1, offset=offset_x, ctx=self.cl_ctx)
|
52
|
+
d_data = roll_forward.processing.allocate_array("data", data.shape, dtype=dtype)
|
53
|
+
d_data.set(data)
|
54
|
+
roll_backward = OpenCLRoll(dtype, direction=-1, offset=offset_x, queue=roll_forward.processing.queue)
|
55
|
+
|
56
|
+
roll_forward(d_data)
|
57
|
+
# from spire.utils import ims
|
58
|
+
# ims([d_data.get(), ref_forward, d_data.get() - ref_forward])
|
59
|
+
assert np.allclose(d_data.get(), ref_forward), "roll_forward: something wrong with config=%s" % (str(config))
|
60
|
+
|
61
|
+
d_data.set(data)
|
62
|
+
roll_backward(d_data)
|
63
|
+
assert np.allclose(d_data.get(), ref_backward), "roll_backward: something wrong with config=%s" % (str(config))
|