summaryrefslogtreecommitdiff
path: root/silx/math/test
diff options
context:
space:
mode:
authorPicca Frédéric-Emmanuel <picca@synchrotron-soleil.fr>2018-07-31 16:22:25 +0200
committerPicca Frédéric-Emmanuel <picca@synchrotron-soleil.fr>2018-07-31 16:22:25 +0200
commit159ef14fb9e198bb0066ea14e6b980f065de63dd (patch)
treebc37c7d4ba09ee59deb708897fa0571709aec293 /silx/math/test
parent270d5ddc31c26b62379e3caa9044dd75ccc71847 (diff)
New upstream version 0.8.0+dfsg
Diffstat (limited to 'silx/math/test')
-rw-r--r--silx/math/test/__init__.py7
-rw-r--r--silx/math/test/test_calibration.py158
-rw-r--r--silx/math/test/test_colormap.py190
3 files changed, 353 insertions, 2 deletions
diff --git a/silx/math/test/__init__.py b/silx/math/test/__init__.py
index 7171bda..fdab38f 100644
--- a/silx/math/test/__init__.py
+++ b/silx/math/test/__init__.py
@@ -1,6 +1,6 @@
# coding: utf-8
# /*##########################################################################
-# Copyright (C) 2016 European Synchrotron Radiation Facility
+# Copyright (C) 2016-2018 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
@@ -36,7 +36,8 @@ from ..fit.test import suite as test_fit_suite
from .test_marchingcubes import suite as test_marchingcubes_suite
from ..medianfilter.test import suite as test_medianfilter_suite
from .test_combo import suite as test_combo_suite
-
+from .test_calibration import suite as test_calibration_suite
+from .test_colormap import suite as test_colormap_suite
def suite():
test_suite = unittest.TestSuite()
@@ -48,4 +49,6 @@ def suite():
test_suite.addTest(test_marchingcubes_suite())
test_suite.addTest(test_medianfilter_suite())
test_suite.addTest(test_combo_suite())
+ test_suite.addTest(test_calibration_suite())
+ test_suite.addTest(test_colormap_suite())
return test_suite
diff --git a/silx/math/test/test_calibration.py b/silx/math/test/test_calibration.py
new file mode 100644
index 0000000..5a0c20e
--- /dev/null
+++ b/silx/math/test/test_calibration.py
@@ -0,0 +1,158 @@
+# coding: utf-8
+# /*##########################################################################
+# Copyright (C) 2018 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.
+#
+# ############################################################################*/
+"""Tests of the calibration module"""
+
+from __future__ import division
+
+__authors__ = ["P. Knobel"]
+__license__ = "MIT"
+__date__ = "14/05/2018"
+
+
+import unittest
+
+import numpy
+
+from silx.math.calibration import NoCalibration, LinearCalibration, \
+ ArrayCalibration, FunctionCalibration
+
+
+X = numpy.array([3.14, 2.73, 1337])
+
+
+class TestNoCalibration(unittest.TestCase):
+ def setUp(self):
+ self.calib = NoCalibration()
+
+ def testIsAffine(self):
+ self.assertTrue(self.calib.is_affine())
+
+ def testSlope(self):
+ self.assertEqual(self.calib.get_slope(), 1.)
+
+ def testYIntercept(self):
+ self.assertEqual(self.calib(0.),
+ 0.)
+
+ def testCall(self):
+ self.assertTrue(numpy.array_equal(self.calib(X), X))
+
+
+class TestLinearCalibration(unittest.TestCase):
+ def setUp(self):
+ self.y_intercept = 1.5
+ self.slope = 2.5
+ self.calib = LinearCalibration(y_intercept=self.y_intercept,
+ slope=self.slope)
+
+ def testIsAffine(self):
+ self.assertTrue(self.calib.is_affine())
+
+ def testSlope(self):
+ self.assertEqual(self.calib.get_slope(), self.slope)
+
+ def testYIntercept(self):
+ self.assertEqual(self.calib(0.),
+ self.y_intercept)
+
+ def testCall(self):
+ self.assertTrue(numpy.array_equal(self.calib(X),
+ self.y_intercept + self.slope * X))
+
+
+class TestArrayCalibration(unittest.TestCase):
+ def setUp(self):
+ self.arr = numpy.array([45.2, 25.3, 666., -8.])
+ self.calib = ArrayCalibration(self.arr)
+ self.affine_calib = ArrayCalibration([0.1, 0.2, 0.3])
+
+ def testIsAffine(self):
+ self.assertFalse(self.calib.is_affine())
+ self.assertTrue(self.affine_calib.is_affine())
+
+ def testSlope(self):
+ with self.assertRaises(AttributeError):
+ self.calib.get_slope()
+ self.assertEqual(self.affine_calib.get_slope(),
+ 0.1)
+
+ def testYIntercept(self):
+ self.assertEqual(self.calib(0),
+ self.arr[0])
+
+ def testCall(self):
+ with self.assertRaises(ValueError):
+ # X is an array with a different shape
+ self.calib(X)
+
+ with self.assertRaises(ValueError):
+ # floats are not valid indices
+ self.calib(3.14)
+
+ self.assertTrue(
+ numpy.array_equal(self.calib([1, 2, 3, 4]),
+ self.arr))
+
+ for idx, value in enumerate(self.arr):
+ self.assertEqual(self.calib(idx), value)
+
+
+class TestFunctionCalibration(unittest.TestCase):
+ def setUp(self):
+ self.non_affine_fun = numpy.sin
+ self.non_affine_calib = FunctionCalibration(self.non_affine_fun)
+
+ self.affine_fun = lambda x: 52. * x + 0.01
+ self.affine_calib = FunctionCalibration(self.affine_fun,
+ is_affine=True)
+
+ def testIsAffine(self):
+ self.assertFalse(self.non_affine_calib.is_affine())
+ self.assertTrue(self.affine_calib.is_affine())
+
+ def testSlope(self):
+ with self.assertRaises(AttributeError):
+ self.non_affine_calib.get_slope()
+ self.assertAlmostEqual(self.affine_calib.get_slope(),
+ 52.)
+
+ def testCall(self):
+ for x in X:
+ self.assertAlmostEqual(self.non_affine_calib(x),
+ self.non_affine_fun(x))
+ self.assertAlmostEqual(self.affine_calib(x),
+ self.affine_fun(x))
+
+
+def suite():
+ test_suite = unittest.TestSuite()
+ loadTests = unittest.defaultTestLoader.loadTestsFromTestCase
+ test_suite.addTest(loadTests(TestNoCalibration))
+ test_suite.addTest(loadTests(TestArrayCalibration))
+ test_suite.addTest(loadTests(TestLinearCalibration))
+ test_suite.addTest(loadTests(TestFunctionCalibration))
+ return test_suite
+
+if __name__ == '__main__':
+ unittest.main(defaultTest="suite")
diff --git a/silx/math/test/test_colormap.py b/silx/math/test/test_colormap.py
new file mode 100644
index 0000000..ede7d8d
--- /dev/null
+++ b/silx/math/test/test_colormap.py
@@ -0,0 +1,190 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2018 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 for colormap mapping implementation"""
+
+from __future__ import division
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "16/05/2018"
+
+
+import logging
+import sys
+import unittest
+
+import numpy
+
+from silx.utils.testutils import ParametricTestCase
+from silx.math import colormap
+
+
+_logger = logging.getLogger(__name__)
+
+
+class TestColormap(ParametricTestCase):
+ """Test silx.image.colormap.cmap"""
+
+ NORMALIZATIONS = 'linear', 'log', 'arcsinh', 'sqrt'
+
+ @staticmethod
+ def ref_colormap(data, colors, vmin, vmax, normalization, nan_color):
+ """Reference implementation of colormap
+
+ :param numpy.ndarray data: Data to convert
+ :param numpy.ndarray colors: Color look-up-table
+ :param float vmin: Lower bound of the colormap range
+ :param float vmax: Upper bound of the colormap range
+ :param str normalization: Normalization to use
+ :param Union[numpy.ndarray, None] nan_color: Color to use for NaN
+ """
+ norm_functions = {'linear': lambda v: v,
+ 'log': numpy.log10,
+ 'arcsinh': numpy.arcsinh,
+ 'sqrt': numpy.sqrt}
+
+ norm_function = norm_functions[normalization]
+ norm_data, vmin, vmax = map(norm_function, (data, vmin, vmax))
+
+ if normalization == 'arcsinh' and sys.platform == 'win32':
+ # There is a difference of behavior of numpy.arcsinh
+ # between Windows and other OS for results of infinite values
+ # This makes Windows behaves as Linux and MacOS
+ norm_data[data == numpy.inf] = numpy.inf
+ norm_data[data == -numpy.inf] = -numpy.inf
+
+ nb_colors = len(colors)
+ scale = nb_colors / (vmax - vmin)
+
+ # Substraction must be done in float to avoid overflow with uint
+ indices = numpy.clip(scale * (norm_data - float(vmin)),
+ 0, nb_colors - 1)
+ indices[numpy.isnan(indices)] = nb_colors # Use an extra index for NaN
+ indices = indices.astype('uint')
+
+ # Add NaN color to array
+ if nan_color is None:
+ nan_color = (0,) * colors.shape[-1]
+ colors = numpy.append(colors, numpy.atleast_2d(nan_color), axis=0)
+
+ return colors[indices]
+
+ def _test(self, data, colors, vmin, vmax, normalization, nan_color):
+ """Run test of colormap against alternative implementation
+
+ :param numpy.ndarray data: Data to convert
+ :param numpy.ndarray colors: Color look-up-table
+ :param float vmin: Lower bound of the colormap range
+ :param float vmax: Upper bound of the colormap range
+ :param str normalization: Normalization to use
+ :param Union[numpy.ndarray, None] nan_color: Color to use for NaN
+ """
+ image = colormap.cmap(
+ data, colors, vmin, vmax, normalization, nan_color)
+
+ ref_image = self.ref_colormap(
+ data, colors, vmin, vmax, normalization, nan_color)
+
+ self.assertTrue(numpy.allclose(ref_image, image))
+ self.assertEqual(image.dtype, colors.dtype)
+ self.assertEqual(image.shape, data.shape + (colors.shape[-1],))
+
+ def test(self):
+ """Test all dtypes with finite data
+
+ Test all supported types and endianness
+ """
+ colors = numpy.zeros((256, 4), dtype=numpy.uint8)
+ colors[:, 0] = numpy.arange(len(colors))
+ colors[:, 3] = 255
+
+ # Generates (u)int and floats types
+ dtypes = [e + k + i for e in '<>' for k in 'uif' for i in '1248'
+ if k != 'f' or i != '1']
+ dtypes.append(numpy.dtype(numpy.longdouble).name) # Add long double
+
+ for normalization in self.NORMALIZATIONS:
+ for dtype in dtypes:
+ with self.subTest(dtype=dtype, normalization=normalization):
+ _logger.info('normalization: %s, dtype: %s',
+ normalization, dtype)
+ data = numpy.arange(-5, 15, dtype=dtype).reshape(4, 5)
+
+ self._test(data, colors, 1, 10, normalization, None)
+
+ def test_not_finite(self):
+ """Test float data with not finite values"""
+ colors = numpy.zeros((256, 4), dtype=numpy.uint8)
+ colors[:, 0] = numpy.arange(len(colors))
+ colors[:, 3] = 255
+
+ test_data = { # message: data
+ 'no finite values': (float('inf'), float('-inf'), float('nan')),
+ 'only NaN': (float('nan'), float('nan'), float('nan')),
+ 'mix finite/not finite': (float('inf'), float('-inf'), 1., float('nan')),
+ }
+
+ for normalization in self.NORMALIZATIONS:
+ for msg, data in test_data.items():
+ with self.subTest(msg, normalization=normalization):
+ _logger.info('normalization: %s, %s', normalization, msg)
+ data = numpy.array(data, dtype=numpy.float64)
+ self._test(data, colors, 1, 10, normalization, (0, 0, 0, 0))
+
+ def test_errors(self):
+ """Test raising exception for bad vmin, vmax, normalization parameters
+ """
+ colors = numpy.zeros((256, 4), dtype=numpy.uint8)
+ colors[:, 0] = numpy.arange(len(colors))
+ colors[:, 3] = 255
+
+ data = numpy.arange(10, dtype=numpy.float64)
+
+ test_params = [ # (vmin, vmax, normalization)
+ (-1., 2., 'log'),
+ (0., 1., 'log'),
+ (1., 0., 'log'),
+ (-1., 1., 'sqrt'),
+ (1., -1., 'sqrt'),
+ ]
+
+ for vmin, vmax, normalization in test_params:
+ with self.subTest(
+ vmin=vmin, vmax=vmax, normalization=normalization):
+ _logger.info('normalization: %s, range: [%f, %f]',
+ normalization, vmin, vmax)
+ with self.assertRaises(ValueError):
+ self._test(data, colors, vmin, vmax, normalization, None)
+
+
+def suite():
+ test_suite = unittest.TestSuite()
+ test_suite.addTest(
+ unittest.defaultTestLoader.loadTestsFromTestCase(TestColormap))
+ return test_suite
+
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='suite')