diff options
author | Alexandre Marie <alexandre.marie@synchrotron-soleil.fr> | 2019-07-09 10:20:39 +0200 |
---|---|---|
committer | Alexandre Marie <alexandre.marie@synchrotron-soleil.fr> | 2019-07-09 10:20:39 +0200 |
commit | 032cc0bed452e96456cdc499f98ccaf473416978 (patch) | |
tree | 514f4532d1ab4dcbea0495488e6dd30bc422a94b /silx/opencl/test | |
parent | 8ff15764a99df31a5d75e1e19a89b413408cfcc2 (diff) | |
parent | 654a6ac93513c3cc1ef97cacd782ff674c6f4559 (diff) |
Update upstream source from tag 'upstream/0.11.0+dfsg'
Update to upstream version '0.11.0+dfsg'
with Debian dir 711605a3a57c11c3b5d699da5819c94403f8ac62
Diffstat (limited to 'silx/opencl/test')
-rw-r--r-- | silx/opencl/test/__init__.py | 4 | ||||
-rw-r--r-- | silx/opencl/test/test_backprojection.py | 21 | ||||
-rw-r--r-- | silx/opencl/test/test_convolution.py | 263 | ||||
-rw-r--r-- | silx/opencl/test/test_sparse.py | 192 |
4 files changed, 479 insertions, 1 deletions
diff --git a/silx/opencl/test/__init__.py b/silx/opencl/test/__init__.py index f3121d5..2e90e66 100644 --- a/silx/opencl/test/__init__.py +++ b/silx/opencl/test/__init__.py @@ -38,6 +38,8 @@ from ..codec import test as test_codec from . import test_image from . import test_kahan from . import test_stats +from . import test_convolution +from . import test_sparse def suite(): @@ -52,6 +54,8 @@ def suite(): test_suite.addTests(test_image.suite()) test_suite.addTests(test_kahan.suite()) test_suite.addTests(test_stats.suite()) + test_suite.addTests(test_convolution.suite()) + test_suite.addTests(test_sparse.suite()) # Allow to remove sift from the project test_base_dir = os.path.dirname(__file__) sift_dir = os.path.join(test_base_dir, "..", "sift") diff --git a/silx/opencl/test/test_backprojection.py b/silx/opencl/test/test_backprojection.py index 7ab416d..b2f2070 100644 --- a/silx/opencl/test/test_backprojection.py +++ b/silx/opencl/test/test_backprojection.py @@ -45,7 +45,7 @@ except ImportError: from ..common import ocl if ocl: from .. import backprojection - from ..sinofilter import compute_fourier_filter + from ...image.tomography import compute_fourier_filter from silx.test.utils import utilstest logger = logging.getLogger(__name__) @@ -199,11 +199,30 @@ class TestFBP(unittest.TestCase): "Something wrong with FBP(filter=%s)" % filter_name ) + @unittest.skipUnless(ocl and mako, "pyopencl is missing") + def test_fbp_oddsize(self): + # Generate a 513-sinogram. + # The padded width will be nextpow(513*2). + # silx [0.10, 0.10.1] will give 1029, which makes R2C transform fail. + sino = np.pad(self.sino, ((0, 0), (1, 0)), mode='edge') + B = backprojection.Backprojection(sino.shape, axis_position=self.fbp.axis_pos+1) + res = B(sino) + # Compare with self.reference_rec. Tolerance is high as backprojector + # is not fully shift-invariant. + errmax = np.max(np.abs(clip_circle(res[1:, 1:] - self.reference_rec))) + self.assertLess( + errmax, 1.e-1, + "Something wrong with FBP on odd-sized sinogram" + ) + + + def suite(): testSuite = unittest.TestSuite() testSuite.addTest(TestFBP("test_fbp")) testSuite.addTest(TestFBP("test_fbp_filters")) + testSuite.addTest(TestFBP("test_fbp_oddsize")) return testSuite diff --git a/silx/opencl/test/test_convolution.py b/silx/opencl/test/test_convolution.py new file mode 100644 index 0000000..8acd385 --- /dev/null +++ b/silx/opencl/test/test_convolution.py @@ -0,0 +1,263 @@ +#!/usr/bin/env python +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2019 European Synchrotron Radiation Facility +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ + +""" +Test of the Convolution class. +""" + +from __future__ import division, print_function + +__authors__ = ["Pierre Paleo"] +__contact__ = "pierre.paleo@esrf.fr" +__license__ = "MIT" +__copyright__ = "2019 European Synchrotron Radiation Facility, Grenoble, France" +__date__ = "15/02/2019" + +import logging +from itertools import product +import numpy as np +from silx.utils.testutils import parameterize +try: + from scipy.ndimage import convolve, convolve1d + from scipy.misc import ascent + scipy_convolve = convolve + scipy_convolve1d = convolve1d +except ImportError: + scipy_convolve = None +import unittest +from ..common import ocl +if ocl: + import pyopencl as cl + import pyopencl.array as parray + from ..convolution import Convolution, gaussian_kernel +logger = logging.getLogger(__name__) + + +@unittest.skipUnless(ocl and scipy_convolve, "PyOpenCl/scipy is missing") +class TestConvolution(unittest.TestCase): + + @classmethod + def setUpClass(cls): + super(TestConvolution, cls).setUpClass() + cls.image = np.ascontiguousarray(ascent()[:, :511], dtype="f") + cls.data1d = cls.image[0] + cls.data2d = cls.image + cls.data3d = np.tile(cls.image[224:-224, 224:-224], (62, 1, 1)) + cls.kernel1d = gaussian_kernel(1.) + cls.kernel2d = np.outer(cls.kernel1d, cls.kernel1d) + cls.kernel3d = np.multiply.outer(cls.kernel2d, cls.kernel1d) + cls.ctx = ocl.create_context() + cls.tol = { + "1D": 1e-4, + "2D": 1e-3, + "3D": 1e-3, + } + + @classmethod + def tearDownClass(cls): + cls.data1d = cls.data2d = cls.data3d = cls.image = None + cls.kernel1d = cls.kernel2d = cls.kernel3d = None + + @staticmethod + def compare(arr1, arr2): + return np.max(np.abs(arr1 - arr2)) + + @staticmethod + def print_err(conv): + errmsg = str( + """ + Something wrong with %s + mode=%s, texture=%s + """ + % (conv.use_case_desc, conv.mode, conv.use_textures) + ) + return errmsg + + def __init__(self, methodName='runTest', param=None): + unittest.TestCase.__init__(self, methodName) + self.param = param + self.mode = param["boundary_handling"] + logger.debug( + """ + Testing convolution with boundary_handling=%s, + use_textures=%s, input_device=%s, output_device=%s + """ + % ( + self.mode, param["use_textures"], + param["input_on_device"], param["output_on_device"] + ) + ) + + def instantiate_convol(self, shape, kernel, axes=None): + if (self.mode == "constant") and ( + not(self.param["use_textures"]) + or (self.ctx.devices[0].type == cl._cl.device_type.CPU) + ): + self.skipTest("mode=constant not implemented without textures") + C = Convolution( + shape, kernel, + mode=self.mode, + ctx=self.ctx, + axes=axes, + extra_options={"dont_use_textures": not(self.param["use_textures"])} + ) + return C + + def get_data_and_kernel(self, test_name): + dims = { + "test_1D": (1, 1), + "test_separable_2D": (2, 1), + "test_separable_3D": (3, 1), + "test_nonseparable_2D": (2, 2), + "test_nonseparable_3D": (3, 3), + } + dim_data = { + 1: self.data1d, + 2: self.data2d, + 3: self.data3d + } + dim_kernel = { + 1: self.kernel1d, + 2: self.kernel2d, + 3: self.kernel3d, + } + dd, kd = dims[test_name] + return dim_data[dd], dim_kernel[kd] + + def get_reference_function(self, test_name): + ref_func = { + "test_1D": + lambda x, y : scipy_convolve1d(x, y, mode=self.mode), + "test_separable_2D": + lambda x, y : scipy_convolve1d( + scipy_convolve1d(x, y, mode=self.mode, axis=1), + y, mode=self.mode, axis=0 + ), + "test_separable_3D": + lambda x, y: scipy_convolve1d( + scipy_convolve1d( + scipy_convolve1d(x, y, mode=self.mode, axis=2), + y, mode=self.mode, axis=1), + y, mode=self.mode, axis=0 + ), + "test_nonseparable_2D": + lambda x, y: scipy_convolve(x, y, mode=self.mode), + "test_nonseparable_3D": + lambda x, y : scipy_convolve(x, y, mode=self.mode), + } + return ref_func[test_name] + + def template_test(self, test_name): + data, kernel = self.get_data_and_kernel(test_name) + conv = self.instantiate_convol(data.shape, kernel) + if self.param["input_on_device"]: + data_ref = parray.to_device(conv.queue, data) + else: + data_ref = data + if self.param["output_on_device"]: + d_res = parray.zeros_like(conv.data_out) + res = d_res + else: + res = None + res = conv(data_ref, output=res) + if self.param["output_on_device"]: + res = res.get() + ref_func = self.get_reference_function(test_name) + ref = ref_func(data, kernel) + metric = self.compare(res, ref) + logger.info("%s: max error = %.2e" % (test_name, metric)) + tol = self.tol[str("%dD" % kernel.ndim)] + self.assertLess(metric, tol, self.print_err(conv)) + + def test_1D(self): + self.template_test("test_1D") + + def test_separable_2D(self): + self.template_test("test_separable_2D") + + def test_separable_3D(self): + self.template_test("test_separable_3D") + + def test_nonseparable_2D(self): + self.template_test("test_nonseparable_2D") + + def test_nonseparable_3D(self): + self.template_test("test_nonseparable_3D") + + def test_batched_2D(self): + """ + Test batched (nonseparable) 2D convolution on 3D data. + In this test: batch along "z" (axis 0) + """ + data = self.data3d + kernel = self.kernel2d + conv = self.instantiate_convol(data.shape, kernel, axes=(0,)) + res = conv(data) # 3D + ref = scipy_convolve(data[0], kernel, mode=self.mode) # 2D + + std = np.std(res, axis=0) + std_max = np.max(np.abs(std)) + self.assertLess(std_max, self.tol["2D"], self.print_err(conv)) + metric = self.compare(res[0], ref) + logger.info("test_nonseparable_3D: max error = %.2e" % metric) + self.assertLess(metric, self.tol["2D"], self.print_err(conv)) + + +def test_convolution(): + boundary_handling_ = ["reflect", "nearest", "wrap", "constant"] + use_textures_ = [True, False] + input_on_device_ = [True, False] + output_on_device_ = [True, False] + testSuite = unittest.TestSuite() + + param_vals = list(product( + boundary_handling_, + use_textures_, + input_on_device_, + output_on_device_ + )) + for boundary_handling, use_textures, input_dev, output_dev in param_vals: + testcase = parameterize( + TestConvolution, + param={ + "boundary_handling": boundary_handling, + "input_on_device": input_dev, + "output_on_device": output_dev, + "use_textures": use_textures, + } + ) + testSuite.addTest(testcase) + return testSuite + + + +def suite(): + testSuite = test_convolution() + return testSuite + + +if __name__ == '__main__': + unittest.main(defaultTest="suite") diff --git a/silx/opencl/test/test_sparse.py b/silx/opencl/test/test_sparse.py new file mode 100644 index 0000000..56f1ba4 --- /dev/null +++ b/silx/opencl/test/test_sparse.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2018-2019 European Synchrotron Radiation Facility +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""Test of the sparse module""" + +import numpy as np +import unittest +import logging +from itertools import product +from ..common import ocl +if ocl: + import pyopencl.array as parray + from silx.opencl.sparse import CSR +try: + import scipy.sparse as sp +except ImportError: + sp = None +logger = logging.getLogger(__name__) + + + +def generate_sparse_random_data( + shape=(1000,), + data_min=0, data_max=100, + density=0.1, + use_only_integers=True, + dtype="f"): + """ + Generate random sparse data where. + + Parameters + ------------ + shape: tuple + Output data shape. + data_min: int or float + Minimum value of data + data_max: int or float + Maximum value of data + density: float + Density of non-zero elements in the output data. + Low value of density mean low number of non-zero elements. + use_only_integers: bool + If set to True, the output data items will be primarily integers, + possibly casted to float if dtype is a floating-point type. + This can be used for ease of debugging. + dtype: str or numpy.dtype + Output data type + """ + mask = np.random.binomial(1, density, size=shape) + if use_only_integers: + d = np.random.randint(data_min, high=data_max, size=shape) + else: + d = data_min + (data_max - data_min) * np.random.rand(*shape) + return (d * mask).astype(dtype) + + + +@unittest.skipUnless(ocl and sp, "PyOpenCl/scipy is missing") +class TestCSR(unittest.TestCase): + """Test CSR format""" + + def setUp(self): + self.array = generate_sparse_random_data(shape=(512, 511)) + # Compute reference sparsification + a_s = sp.csr_matrix(self.array) + self.ref_data = a_s.data + self.ref_indices = a_s.indices + self.ref_indptr = a_s.indptr + self.ref_nnz = a_s.nnz + # Test possible configurations + input_on_device = [False, True] + output_on_device = [False, True] + self._test_configs = list(product(input_on_device, output_on_device)) + + + def test_sparsification(self): + for input_on_device, output_on_device in self._test_configs: + self._test_sparsification(input_on_device, output_on_device) + + + def _test_sparsification(self, input_on_device, output_on_device): + current_config = "input on device: %s, output on device: %s" % ( + str(input_on_device), str(output_on_device) + ) + # Sparsify on device + csr = CSR(self.array.shape) + if input_on_device: + # The array has to be flattened + arr = parray.to_device(csr.queue, self.array.ravel()) + else: + arr = self.array + if output_on_device: + d_data = parray.zeros_like(csr.data) + d_indices = parray.zeros_like(csr.indices) + d_indptr = parray.zeros_like(csr.indptr) + output = (d_data, d_indices, d_indptr) + else: + output = None + data, indices, indptr = csr.sparsify(arr, output=output) + if output_on_device: + data = data.get() + indices = indices.get() + indptr = indptr.get() + # Compare + nnz = self.ref_nnz + self.assertTrue( + np.allclose(data[:nnz], self.ref_data), + "something wrong with sparsified data (%s)" + % current_config + ) + self.assertTrue( + np.allclose(indices[:nnz], self.ref_indices), + "something wrong with sparsified indices (%s)" + % current_config + ) + self.assertTrue( + np.allclose(indptr, self.ref_indptr), + "something wrong with sparsified indices pointers (indptr) (%s)" + % current_config + ) + + + def test_desparsification(self): + for input_on_device, output_on_device in self._test_configs: + self._test_desparsification(input_on_device, output_on_device) + + + def _test_desparsification(self, input_on_device, output_on_device): + current_config = "input on device: %s, output on device: %s" % ( + str(input_on_device), str(output_on_device) + ) + # De-sparsify on device + csr = CSR(self.array.shape, max_nnz=self.ref_nnz) + if input_on_device: + data = parray.to_device(csr.queue, self.ref_data) + indices = parray.to_device(csr.queue, self.ref_indices) + indptr = parray.to_device(csr.queue, self.ref_indptr) + else: + data = self.ref_data + indices = self.ref_indices + indptr = self.ref_indptr + if output_on_device: + d_arr = parray.zeros_like(csr.array) + output = d_arr + else: + output = None + arr = csr.densify(data, indices, indptr, output=output) + if output_on_device: + arr = arr.get() + # Compare + self.assertTrue( + np.allclose(arr.reshape(self.array.shape), self.array), + "something wrong with densified data (%s)" + % current_config + ) + + + +def suite(): + suite = unittest.TestSuite() + suite.addTest( + unittest.defaultTestLoader.loadTestsFromTestCase(TestCSR) + ) + return suite + + +if __name__ == '__main__': + unittest.main(defaultTest="suite") + + |