diff options
Diffstat (limited to 'silx/gui/plot3d/scene/test/test_utils.py')
-rw-r--r-- | silx/gui/plot3d/scene/test/test_utils.py | 275 |
1 files changed, 275 insertions, 0 deletions
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') |