diff options
Diffstat (limited to 'silx/gui/plot3d/scene/test')
-rw-r--r-- | silx/gui/plot3d/scene/test/__init__.py | 43 | ||||
-rw-r--r-- | silx/gui/plot3d/scene/test/test_transform.py | 91 | ||||
-rw-r--r-- | silx/gui/plot3d/scene/test/test_utils.py | 275 |
3 files changed, 409 insertions, 0 deletions
diff --git a/silx/gui/plot3d/scene/test/__init__.py b/silx/gui/plot3d/scene/test/__init__.py new file mode 100644 index 0000000..fc4621e --- /dev/null +++ b/silx/gui/plot3d/scene/test/__init__.py @@ -0,0 +1,43 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2015-2017 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. +# +# ###########################################################################*/ + +from __future__ import absolute_import, division, unicode_literals + +__authors__ = ["T. Vincent"] +__license__ = "MIT" +__date__ = "25/07/2016" + + +import unittest + +from .test_transform import suite as test_transform_suite +from .test_utils import suite as test_utils_suite + + +def suite(): + testsuite = unittest.TestSuite() + testsuite.addTest(test_transform_suite()) + testsuite.addTest(test_utils_suite()) + return testsuite diff --git a/silx/gui/plot3d/scene/test/test_transform.py b/silx/gui/plot3d/scene/test/test_transform.py new file mode 100644 index 0000000..9ea0af1 --- /dev/null +++ b/silx/gui/plot3d/scene/test/test_transform.py @@ -0,0 +1,91 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2015-2017 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. +# +# ###########################################################################*/ + +from __future__ import absolute_import, division, unicode_literals + +__authors__ = ["T. Vincent"] +__license__ = "MIT" +__date__ = "05/01/2017" + + +import numpy +import unittest + +from silx.gui.plot3d.scene import transform + + +class TestTransformList(unittest.TestCase): + + def assertSameArrays(self, a, b): + return self.assertTrue(numpy.allclose(a, b, atol=1e-06)) + + def testTransformList(self): + """Minimalistic test of TransformList""" + transforms = transform.TransformList() + refmatrix = numpy.identity(4, dtype=numpy.float32) + self.assertSameArrays(refmatrix, transforms.matrix) + + # Append translate + transforms.append(transform.Translate(1., 1., 1.)) + refmatrix = numpy.array(((1., 0., 0., 1.), + (0., 1., 0., 1.), + (0., 0., 1., 1.), + (0., 0., 0., 1.)), dtype=numpy.float32) + self.assertSameArrays(refmatrix, transforms.matrix) + + # Extend scale + transforms.extend([transform.Scale(0.1, 2., 1.)]) + refmatrix = numpy.dot(refmatrix, + numpy.array(((0.1, 0., 0., 0.), + (0., 2., 0., 0.), + (0., 0., 1., 0.), + (0., 0., 0., 1.)), + dtype=numpy.float32)) + self.assertSameArrays(refmatrix, transforms.matrix) + + # Insert rotate + transforms.insert(0, transform.Rotate(360.)) + self.assertSameArrays(refmatrix, transforms.matrix) + + # Update translate and check for listener called + self._callCount = 0 + + def listener(source): + self._callCount += 1 + transforms.addListener(listener) + + transforms[1].tx += 1 + self.assertEqual(self._callCount, 1) + + +def suite(): + testsuite = unittest.TestSuite() + testsuite.addTest( + unittest.defaultTestLoader.loadTestsFromTestCase(TestTransformList)) + return testsuite + + +if __name__ == '__main__': + unittest.main(defaultTest='suite') diff --git a/silx/gui/plot3d/scene/test/test_utils.py b/silx/gui/plot3d/scene/test/test_utils.py new file mode 100644 index 0000000..65c2407 --- /dev/null +++ b/silx/gui/plot3d/scene/test/test_utils.py @@ -0,0 +1,275 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2015-2017 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. +# +# ###########################################################################*/ + +from __future__ import absolute_import, division, unicode_literals + +__authors__ = ["T. Vincent"] +__license__ = "MIT" +__date__ = "25/07/2016" + + +import unittest +from silx.test.utils import ParametricTestCase + +import numpy + +from silx.gui.plot3d.scene import utils + + +# angleBetweenVectors ######################################################### + +class TestAngleBetweenVectors(ParametricTestCase): + + TESTS = { # name: (refvector, vectors, norm, refangles) + 'single vector': + ((1., 0., 0.), (1., 0., 0.), (0., 0., 1.), 0.), + 'single vector, no norm': + ((1., 0., 0.), (1., 0., 0.), None, 0.), + + 'with orthogonal norm': + ((1., 0., 0.), + ((1., 0., 0.), (0., 1., 0.), (-1., 0., 0.), (0., -1., 0.)), + (0., 0., 1.), + (0., 90., 180., 270.)), + + 'with coplanar norm': # = similar to no norm + ((1., 0., 0.), + ((1., 0., 0.), (0., 1., 0.), (-1., 0., 0.), (0., -1., 0.)), + (1., 0., 0.), + (0., 90., 180., 90.)), + + 'without norm': + ((1., 0., 0.), + ((1., 0., 0.), (0., 1., 0.), (-1., 0., 0.), (0., -1., 0.)), + None, + (0., 90., 180., 90.)), + + 'not unit vectors': + ((2., 2., 0.), ((1., 1., 0.), (1., -1., 0.)), None, (0., 90.)), + } + + def testAngleBetweenVectorsFunction(self): + for name, params in self.TESTS.items(): + refvector, vectors, norm, refangles = params + with self.subTest(name): + refangles = numpy.radians(refangles) + + refvector = numpy.array(refvector) + vectors = numpy.array(vectors) + if norm is not None: + norm = numpy.array(norm) + + testangles = utils.angleBetweenVectors( + refvector, vectors, norm) + + self.assertTrue( + numpy.allclose(testangles, refangles, atol=1e-5)) + + +# Plane ####################################################################### + +class AssertNotificationContext(object): + """Context that checks if an event.Notifier is sending events.""" + + def __init__(self, notifier, count=1): + """Initializer. + + :param event.Notifier notifier: The notifier to test. + :param int count: The expected number of calls. + """ + self._notifier = notifier + self._callCount = None + self._count = count + + def __enter__(self): + self._callCount = 0 + self._notifier.addListener(self._callback) + + def __exit__(self, exc_type, exc_value, traceback): + # Do not return True so exceptions are propagated + self._notifier.removeListener(self._callback) + assert self._callCount == self._count + self._callCount = None + + def _callback(self, *args, **kwargs): + self._callCount += 1 + + +class TestPlaneParameters(ParametricTestCase): + """Test Plane.parameters read/write and notifications.""" + + PARAMETERS = { + 'unit normal': (1., 0., 0., 1.), + 'not unit normal': (1., 1., 0., 1.), + 'd = 0': (1., 0., 0., 0.) + } + + def testParameters(self): + """Check parameters read/write and notification.""" + plane = utils.Plane() + + for name, parameters in self.PARAMETERS.items(): + with self.subTest(name, parameters=parameters): + with AssertNotificationContext(plane): + plane.parameters = parameters + + # Plane parameters are converted to have a unit normal + normparams = parameters / numpy.linalg.norm(parameters[:3]) + self.assertTrue(numpy.allclose(plane.parameters, normparams)) + + ZEROS_PARAMETERS = ( + (0., 0., 0., 0.), + (0., 0., 0., 1.) + ) + + ZEROS = 0., 0., 0., 0. + + def testParametersNoPlane(self): + """Test Plane.parameters with ||normal|| == 0 .""" + plane = utils.Plane() + plane.parameters = self.ZEROS + + for parameters in self.ZEROS_PARAMETERS: + with self.subTest(parameters=parameters): + with AssertNotificationContext(plane, count=0): + plane.parameters = parameters + self.assertTrue( + numpy.allclose(plane.parameters, self.ZEROS, 0., 0.)) + + +# unindexArrays ############################################################### + +class TestUnindexArrays(ParametricTestCase): + """Test unindexArrays function.""" + + def testBasicModes(self): + """Test for modes: points, lines and triangles""" + indices = numpy.array((1, 2, 0)) + arrays = (numpy.array((0., 1., 2.)), + numpy.array(((0, 0), (1, 1), (2, 2)))) + refresults = (numpy.array((1., 2., 0.)), + numpy.array(((1, 1), (2, 2), (0, 0)))) + + for mode in ('points', 'lines', 'triangles'): + with self.subTest(mode=mode): + testresults = utils.unindexArrays(mode, indices, *arrays) + for ref, test in zip(refresults, testresults): + self.assertTrue(numpy.equal(ref, test).all()) + + def testPackedLines(self): + """Test for modes: line_strip, loop""" + indices = numpy.array((1, 2, 0)) + arrays = (numpy.array((0., 1., 2.)), + numpy.array(((0, 0), (1, 1), (2, 2)))) + results = { + 'line_strip': ( + numpy.array((1., 2., 2., 0.)), + numpy.array(((1, 1), (2, 2), (2, 2), (0, 0)))), + 'loop': ( + numpy.array((1., 2., 2., 0., 0., 1.)), + numpy.array(((1, 1), (2, 2), (2, 2), (0, 0), (0, 0), (1, 1)))), + } + + for mode, refresults in results.items(): + with self.subTest(mode=mode): + testresults = utils.unindexArrays(mode, indices, *arrays) + for ref, test in zip(refresults, testresults): + self.assertTrue(numpy.equal(ref, test).all()) + + def testPackedTriangles(self): + """Test for modes: triangle_strip, fan""" + indices = numpy.array((1, 2, 0, 3)) + arrays = (numpy.array((0., 1., 2., 3.)), + numpy.array(((0, 0), (1, 1), (2, 2), (3, 3)))) + results = { + 'triangle_strip': ( + numpy.array((1., 2., 0., 2., 0., 3.)), + numpy.array(((1, 1), (2, 2), (0, 0), (2, 2), (0, 0), (3, 3)))), + 'fan': ( + numpy.array((1., 2., 0., 1., 0., 3.)), + numpy.array(((1, 1), (2, 2), (0, 0), (1, 1), (0, 0), (3, 3)))), + } + + for mode, refresults in results.items(): + with self.subTest(mode=mode): + testresults = utils.unindexArrays(mode, indices, *arrays) + for ref, test in zip(refresults, testresults): + self.assertTrue(numpy.equal(ref, test).all()) + + def testBadIndices(self): + """Test with negative indices and indices higher than array length""" + arrays = numpy.array((0, 1)), numpy.array((0, 1, 2)) + + # negative indices + with self.assertRaises(AssertionError): + utils.unindexArrays('points', (-1, 0), *arrays) + + # Too high indices + with self.assertRaises(AssertionError): + utils.unindexArrays('points', (0, 10), *arrays) + + +# triangleNormals ############################################################# + +class TestTriangleNormals(ParametricTestCase): + """Test triangleNormals function.""" + + def test(self): + """Test for modes: points, lines and triangles""" + positions = numpy.array( + ((0., 0., 0.), (1., 0., 0.), (0., 1., 0.), # normal = Z + (1., 1., 1.), (1., 2., 3.), (4., 5., 6.), # Random triangle + # Degenerated triangles: + (0., 0., 0.), (1., 0., 0.), (2., 0., 0.), # Colinear points + (1., 1., 1.), (1., 1., 1.), (1., 1., 1.), # All same point + ), + dtype='float32') + + normals = numpy.array( + ((0., 0., 1.), + (-0.40824829, 0.81649658, -0.40824829), + (0., 0., 0.), + (0., 0., 0.)), + dtype='float32') + + testnormals = utils.trianglesNormal(positions) + self.assertTrue(numpy.allclose(testnormals, normals)) + + +# suite ####################################################################### + +def suite(): + testsuite = unittest.TestSuite() + for test in (TestAngleBetweenVectors, + TestPlaneParameters, + TestUnindexArrays, + TestTriangleNormals): + testsuite.addTest( + unittest.defaultTestLoader.loadTestsFromTestCase(test)) + return testsuite + + +if __name__ == '__main__': + unittest.main(defaultTest='suite') |