From b9c6bee54f5bce60636b01190c523a4faf448a08 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Mon, 21 Nov 2022 16:57:58 +0100 Subject: Testing AreaOfInterest submodule. --- src/argaze.test/AreaOfInterest/AOI2DScene.py | 154 +++++++++++++++++++ src/argaze.test/AreaOfInterest/AOI3DScene.py | 76 +++++++++ src/argaze.test/AreaOfInterest/AOIFeatures.py | 204 +++++++++++++++++++++++++ src/argaze.test/AreaOfInterest/utils/scene.obj | 15 ++ 4 files changed, 449 insertions(+) create mode 100644 src/argaze.test/AreaOfInterest/AOI2DScene.py create mode 100644 src/argaze.test/AreaOfInterest/AOI3DScene.py create mode 100644 src/argaze.test/AreaOfInterest/AOIFeatures.py create mode 100644 src/argaze.test/AreaOfInterest/utils/scene.obj (limited to 'src/argaze.test/AreaOfInterest') diff --git a/src/argaze.test/AreaOfInterest/AOI2DScene.py b/src/argaze.test/AreaOfInterest/AOI2DScene.py new file mode 100644 index 0000000..8a71ffb --- /dev/null +++ b/src/argaze.test/AreaOfInterest/AOI2DScene.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python + +import unittest +import math + +from argaze.AreaOfInterest import AOIFeatures, AOI2DScene +from argaze import GazeFeatures + +import numpy + +class TestAOI2DSceneClass(unittest.TestCase): + """Test AOI2DScene class.""" + + def test_new(self): + """Test AOI2DScene creation.""" + + # Check empty AOI2DScene creation + aoi_2d_scene = AOI2DScene.AOI2DScene() + + self.assertEqual(aoi_2d_scene.dimension, 2) + self.assertEqual(len(aoi_2d_scene.items()), 0) + + # Check AOI2DScene creation + aoi_2D_A = AOIFeatures.AreaOfInterest([[0, 0], [0, 1], [1, 1], [1, 0]]) + aoi_2D_B = AOIFeatures.AreaOfInterest([[1, 1], [1, 2], [2, 2], [2, 1]]) + aoi_2d_scene = AOI2DScene.AOI2DScene({"A": aoi_2D_A, "B": aoi_2D_B}) + + self.assertEqual(aoi_2d_scene.dimension, 2) + + def test_raycast(self): + """Test AOI2DScene raycast method.""" + + aoi_2D_A = AOIFeatures.AreaOfInterest([[0, 0], [0, 1], [1, 1], [1, 0]]) + aoi_2D_B = AOIFeatures.AreaOfInterest([[1, 1], [1, 2], [2, 2], [2, 1]]) + aoi_2d_scene = AOI2DScene.AOI2DScene({"A": aoi_2D_A, "B": aoi_2D_B}) + + gaze_position_A = GazeFeatures.GazePosition((0.5, 0.5)) + gaze_position_B = GazeFeatures.GazePosition((1.5, 1.5)) + gaze_position_C = GazeFeatures.GazePosition((0.5, 1.5)) + + # Check raycast results for gaze postion A + for name, aoi, looked in aoi_2d_scene.raycast(gaze_position_A): + + if name == "A": + + self.assertIsNone(numpy.testing.assert_array_equal(aoi, aoi_2D_A)) + self.assertEqual(looked, True) + + elif name == "B": + + self.assertIsNone(numpy.testing.assert_array_equal(aoi, aoi_2D_B)) + self.assertEqual(looked, False) + + # Check raycast results for gaze postion B + for name, aoi, looked in aoi_2d_scene.raycast(gaze_position_B): + + if name == "A": + + self.assertIsNone(numpy.testing.assert_array_equal(aoi, aoi_2D_A)) + self.assertEqual(looked, False) + + elif name == "B": + + self.assertIsNone(numpy.testing.assert_array_equal(aoi, aoi_2D_B)) + self.assertEqual(looked, True) + + # Check raycast results for gaze postion C + for name, aoi, looked in aoi_2d_scene.raycast(gaze_position_C): + + if name == "A": + + self.assertIsNone(numpy.testing.assert_array_equal(aoi, aoi_2D_A)) + self.assertEqual(looked, False) + + elif name == "B": + + self.assertIsNone(numpy.testing.assert_array_equal(aoi, aoi_2D_B)) + self.assertEqual(looked, False) + + def test_circlecast(self): + """Test AOI2DScene circlecast method.""" + + aoi_2D_A = AOIFeatures.AreaOfInterest([[0, 0], [0, 1], [1, 1], [1, 0]]) + aoi_2D_B = AOIFeatures.AreaOfInterest([[1, 1], [1, 2], [2, 2], [2, 1]]) + aoi_2d_scene = AOI2DScene.AOI2DScene({"A": aoi_2D_A, "B": aoi_2D_B}) + + gaze_position_A = GazeFeatures.GazePosition((0.5, 0.5), accuracy=0.5) + gaze_position_B = GazeFeatures.GazePosition((1.5, 1.5), accuracy=0.5) + gaze_position_C = GazeFeatures.GazePosition((1., 1.), accuracy=1) + gaze_position_D = GazeFeatures.GazePosition((0.5, 1.5), accuracy=0.25) + + # Check circlecast results for gaze postion A + for name, aoi, looked_region, aoi_ratio, gaze_ratio in aoi_2d_scene.circlecast(gaze_position_A): + + if name == "A": + + self.assertIsNone(numpy.testing.assert_array_equal(aoi, aoi_2D_A)) + self.assertTrue(math.isclose(aoi_ratio, math.pi / 4, abs_tol=1e-2)) + self.assertTrue(math.isclose(gaze_ratio, 1., abs_tol=1e-3)) + + elif name == "B": + + self.assertIsNone(numpy.testing.assert_array_equal(aoi, aoi_2D_B)) + self.assertEqual(aoi_ratio, 0.) + self.assertEqual(gaze_ratio, 0.) + + # Check circlecast results for gaze postion B + for name, aoi, looked_region, aoi_ratio, gaze_ratio in aoi_2d_scene.circlecast(gaze_position_B): + + if name == "A": + + self.assertIsNone(numpy.testing.assert_array_equal(aoi, aoi_2D_A)) + self.assertEqual(aoi_ratio, 0.) + self.assertEqual(gaze_ratio, 0.) + + elif name == "B": + + self.assertIsNone(numpy.testing.assert_array_equal(aoi, aoi_2D_B)) + self.assertTrue(math.isclose(aoi_ratio, math.pi / 4, abs_tol=1e-2)) + self.assertTrue(math.isclose(gaze_ratio, 1., abs_tol=1e-3)) + + # Check circlecast results for gaze postion C + for name, aoi, looked_region, aoi_ratio, gaze_ratio in aoi_2d_scene.circlecast(gaze_position_C): + + if name == "A": + + self.assertIsNone(numpy.testing.assert_array_equal(aoi, aoi_2D_A)) + self.assertTrue(math.isclose(aoi_ratio, math.pi / 4, abs_tol=1e-2)) + self.assertTrue(math.isclose(gaze_ratio, 1 / 4, abs_tol=1e-3)) + + elif name == "B": + + self.assertIsNone(numpy.testing.assert_array_equal(aoi, aoi_2D_B)) + self.assertTrue(math.isclose(aoi_ratio, math.pi / 4, abs_tol=1e-2)) + self.assertTrue(math.isclose(gaze_ratio, 1 / 4, abs_tol=1e-3)) + + # Check circlecast results for gaze postion D + for name, aoi, looked_region, aoi_ratio, gaze_ratio in aoi_2d_scene.circlecast(gaze_position_D): + + if name == "A": + + self.assertIsNone(numpy.testing.assert_array_equal(aoi, aoi_2D_A)) + self.assertEqual(aoi_ratio, 0.) + self.assertEqual(gaze_ratio, 0.) + + elif name == "B": + + self.assertIsNone(numpy.testing.assert_array_equal(aoi, aoi_2D_B)) + self.assertEqual(aoi_ratio, 0.) + self.assertEqual(gaze_ratio, 0.) + +if __name__ == '__main__': + + unittest.main() \ No newline at end of file diff --git a/src/argaze.test/AreaOfInterest/AOI3DScene.py b/src/argaze.test/AreaOfInterest/AOI3DScene.py new file mode 100644 index 0000000..5a575bb --- /dev/null +++ b/src/argaze.test/AreaOfInterest/AOI3DScene.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python + +import unittest +import math +import os + +from argaze.AreaOfInterest import AOIFeatures, AOI3DScene, AOI2DScene +from argaze import GazeFeatures + +import numpy + +class TestAOI3DSceneClass(unittest.TestCase): + """Test AOI3DScene class.""" + + def test_new(self): + """Test AOI3DScene creation.""" + + # Check empty AOI3DScene creation + aoi_3d_scene = AOI3DScene.AOI3DScene() + + self.assertEqual(aoi_3d_scene.dimension, 3) + self.assertEqual(len(aoi_3d_scene.items()), 0) + + # Check AOI3DScene creation + aoi_3D_A = AOIFeatures.AreaOfInterest([[0, 0, 0], [0, 1, 0], [1, 1, 0], [1, 0, 0]]) + aoi_3D_B = AOIFeatures.AreaOfInterest([[1, 1, 0], [1, 2, 0], [2, 2, 0], [2, 1, 0]]) + aoi_3d_scene = AOI3DScene.AOI3DScene({"A": aoi_3D_A, "B": aoi_3D_B}) + + self.assertEqual(aoi_3d_scene.dimension, 3) + + def test_load(self): + """Test AOI3DScene load method.""" + + aoi_3d_scene_tt = AOI3DScene.AOI3DScene() + + current_directory = os.path.dirname(os.path.abspath(__file__)) + aoi_3d_scene_tt.load(os.path.join(current_directory, 'utils/scene.obj')) + + self.assertEqual(len(aoi_3d_scene_tt.items()), 2) + + def test_project(self): + """Test AOI3DScene project method.""" + + aoi_3D_A = AOIFeatures.AreaOfInterest([[0, 0, 0], [0, 1, 0], [1, 1, 0], [1, 0, 0]]) + aoi_3D_B = AOIFeatures.AreaOfInterest([[1, 1, 0], [1, 2, 0], [2, 2, 0], [2, 1, 0]]) + aoi_3d_scene = AOI3DScene.AOI3DScene({"A": aoi_3D_A, "B": aoi_3D_B}) + + # Check default projection + aoi_2d_scene = aoi_3d_scene.project() + + self.assertIsNone(numpy.testing.assert_array_equal(aoi_2d_scene['A'], [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0]])) + self.assertIsNone(numpy.testing.assert_array_equal(aoi_2d_scene['B'], [[1.0, 1.0], [1.0, 2.0], [2.0, 2.0], [2.0, 1.0]])) + + # Check projection with translation + aoi_2d_scene = aoi_3d_scene.project(T = numpy.array([1., 0., 0.])) + + self.assertIsNone(numpy.testing.assert_array_equal(aoi_2d_scene['A'], [[1.0, 0.0], [1.0, 1.0], [2.0, 1.0], [2.0, 0.0]])) + self.assertIsNone(numpy.testing.assert_array_equal(aoi_2d_scene['B'], [[2.0, 1.0], [2.0, 2.0], [3.0, 2.0], [3.0, 1.0]])) + + def test_transform(self): + """Test AOI3DScene transform method.""" + + aoi_3D_A = AOIFeatures.AreaOfInterest([[0, 0, 0], [0, 1, 0], [1, 1, 0], [1, 0, 0]]) + aoi_3D_B = AOIFeatures.AreaOfInterest([[1, 1, 0], [1, 2, 0], [2, 2, 0], [2, 1, 0]]) + aoi_3d_scene = AOI3DScene.AOI3DScene({"A": aoi_3D_A, "B": aoi_3D_B}) + + # Check translation transformation + aoi_3d_scene = aoi_3d_scene.transform(T = numpy.array([1., 0., 0.])) + + self.assertIsNone(numpy.testing.assert_array_equal(aoi_3d_scene['A'], [[1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [2.0, 1.0, 0.0], [2.0, 0.0, 0.0]])) + self.assertIsNone(numpy.testing.assert_array_equal(aoi_3d_scene['B'], [[2.0, 1.0, 0.0], [2.0, 2.0, 0.0], [3.0, 2.0, 0.0], [3.0, 1.0, 0.0]])) + + +if __name__ == '__main__': + + unittest.main() \ No newline at end of file diff --git a/src/argaze.test/AreaOfInterest/AOIFeatures.py b/src/argaze.test/AreaOfInterest/AOIFeatures.py new file mode 100644 index 0000000..650411a --- /dev/null +++ b/src/argaze.test/AreaOfInterest/AOIFeatures.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python + +import unittest +import math + +from argaze.AreaOfInterest import AOIFeatures + +import numpy + +class TestAreaOfInterestClass(unittest.TestCase): + """Test AreaOfInterest class.""" + + def test_new(self): + """Test AreaOfInterest creation.""" + + # Check that 0D AreaOfInterest creation fails + with self.assertRaises(TypeError): + + aoi_0D = AOIFeatures.AreaOfInterest() + + # Check 1 point 1D AreaOfInterest creation + aoi_1D = AOIFeatures.AreaOfInterest([[0]]) + + self.assertEqual(aoi_1D.dimension, 1) + self.assertEqual(aoi_1D.size, 1) + self.assertEqual(aoi_1D.center, [0]) + + with self.assertRaises(AssertionError): + self.assertEqual(aoi_1D.bounding_box, 1) + + # Check 2 points 1D AreaOfInterest creation + aoi_1D = AOIFeatures.AreaOfInterest([[0], [1]]) + + self.assertEqual(aoi_1D.dimension, 1) + self.assertEqual(aoi_1D.size, 2) + self.assertEqual(aoi_1D.center, [0.5]) + + with self.assertRaises(AssertionError): + self.assertEqual(aoi_1D.bounding_box, 1) + + # Check 4 points 2D AreaOfInterest creation + aoi_2D = AOIFeatures.AreaOfInterest([[0, 0], [0, 1], [1, 0], [1, 1]]) + + self.assertEqual(aoi_2D.dimension, 2) + self.assertEqual(aoi_2D.size, 4) + self.assertIsNone(numpy.testing.assert_array_equal(aoi_2D.center, [0.5, 0.5])) + self.assertIsNone(numpy.testing.assert_array_equal(aoi_2D.bounding_box, [[0, 0], [1, 0], [1, 1], [0, 1]])) + + # Check 8 points 3D AreaOfInterest creation + aoi_3D = AOIFeatures.AreaOfInterest([[0, 0, 0], [0, 1, 0], [1, 0, 0], [1, 1, 0], [0, 0, 1], [0, 1, 1], [1, 0, 1], [1, 1, 1]]) + + self.assertEqual(aoi_3D.dimension, 3) + self.assertEqual(aoi_3D.size, 8) + + with self.assertRaises(AssertionError): + self.assertEqual(aoi_3D.bounding_box, 1) + + def test___repr__(self): + """Test AreaOfInterest string representation.""" + + aoi_2D_int = AOIFeatures.AreaOfInterest([[0, 0], [0, 1], [1, 0], [1, 1]]) + + self.assertEqual(repr(aoi_2D_int), "[[0, 0], [0, 1], [1, 0], [1, 1]]") + + aoi_2D_float = AOIFeatures.AreaOfInterest([[0, 0], [0, math.pi], [math.pi, 0], [math.pi, math.pi]]) + + self.assertEqual(repr(aoi_2D_float), f'[[0.0, 0.0], [0.0, {repr(math.pi)}], [{repr(math.pi)}, 0.0], [{repr(math.pi)}, {repr(math.pi)}]]') + + def text_clockwise(self): + """Test AreaOfInterest clockwise method.""" + + aoi_2D_clockwise = AOIFeatures.AreaOfInterest([[0, 0], [0, 1], [1, 0], [1, 1]]).clockwise() + + assertEqual(type(aoi_2D_clockwise), AOIFeatures.AreaOfInterest) + self.assertIsNone(numpy.testing.assert_array_equal(aoi_2D_clockwise, [0, 0], [0, 1], [1, 1], [1, 0])) + + def test_contains_point(self): + """Test AreaOfInterest contains_point method.""" + + aoi_2D = AOIFeatures.AreaOfInterest([[0, 0], [0, 1], [1, 1], [1, 0]]) + + self.assertTrue(aoi_2D.contains_point((0.1, 0.9))) + self.assertFalse(aoi_2D.contains_point((1.1, 0.9))) + + def test_inner_axis(self): + + aoi_2D = AOIFeatures.AreaOfInterest([[0, 0], [0, 2], [2, 2], [2, 0]]) + + self.assertEqual(aoi_2D.inner_axis((1, 1)), (0.5, 0.5)) + + def test_outter_axis(self): + + aoi_2D = AOIFeatures.AreaOfInterest([[0, 0], [0, 2], [2, 2], [2, 0]]) + + self.assertEqual(aoi_2D.outter_axis((0.5, 0.5)), (1, 1)) + + def test_circle_intersection(self): + + aoi_2D = AOIFeatures.AreaOfInterest([[0, 0], [0, 1], [1, 1], [1, 0]]) + intersection, aoi_ratio, circle_ratio = aoi_2D.circle_intersection((0, 0), 1) + + self.assertTrue(math.isclose(aoi_ratio, math.pi / 4, abs_tol=1e-2)) + self.assertTrue(math.isclose(circle_ratio, 1 / 4, abs_tol=1e-3)) + +class TestAOISceneClass(unittest.TestCase): + """Test AOIScene class.""" + + def test_new(self): + """Test AOIScene creation.""" + + # Check that 0D AOIScene creation fails + with self.assertRaises(AssertionError): + aoi_0D_scene = AOIFeatures.AOIScene(0) + + # Check empty 2D AOIScene creation + aoi_2d_scene = AOIFeatures.AOIScene(2) + + self.assertEqual(aoi_2d_scene.dimension, 2) + self.assertEqual(len(aoi_2d_scene.items()), 0) + + # Check 2D AOIScene creation + aoi_2D = AOIFeatures.AreaOfInterest([[0, 0], [0, 1], [1, 1], [1, 0]]) + aoi_2d_scene = AOIFeatures.AOIScene(2, {"A": aoi_2D}) + + self.assertEqual(aoi_2d_scene.dimension, 2) + self.assertEqual(len(aoi_2d_scene.items()), 1) + self.assertEqual(list(aoi_2d_scene.keys()), ["A"]) + + def test___repr__(self): + """Test AOIScene string representation.""" + + # Check empty 2D AOIScene representation + self.assertEqual(repr(AOIFeatures.AOIScene(2)), "{}") + + # Check 2D AOIScene representation with integer AOI + aoi_2D_int = AOIFeatures.AreaOfInterest([[0, 0], [0, 1], [1, 1], [1, 0]]) + aoi_2d_scene = AOIFeatures.AOIScene(2, {"A": aoi_2D_int}) + + self.assertEqual(repr(aoi_2d_scene), "{'A': [[0, 0], [0, 1], [1, 1], [1, 0]]}") + + # Check 2D AOIScene representation with float AOI + aoi_2D_float = AOIFeatures.AreaOfInterest([[0, 0], [0, math.pi], [math.pi, 0], [math.pi, math.pi]]) + aoi_2d_scene = AOIFeatures.AOIScene(2, {"A": aoi_2D_float}) + + expected_representation = "{'A': " + f'[[0.0, 0.0], [0.0, {repr(math.pi)}], [{repr(math.pi)}, 0.0], [{repr(math.pi)}, {repr(math.pi)}]]' + "}" + self.assertEqual(repr(aoi_2d_scene), expected_representation) + + def test_properties(self): + """Test AOIScene properties.""" + + aoi_2D_A = AOIFeatures.AreaOfInterest([[0, 0], [0, 1], [1, 1], [1, 0]]) + aoi_2D_B = AOIFeatures.AreaOfInterest([[1, 1], [1, 2], [2, 2], [2, 1]]) + aoi_2d_scene = AOIFeatures.AOIScene(2, {"A": aoi_2D_A, "B": aoi_2D_B}) + + self.assertEqual(aoi_2d_scene.dimension, 2) + self.assertEqual(len(aoi_2d_scene.items()), 2) + self.assertEqual(list(aoi_2d_scene.keys()), ["A", "B"]) + self.assertIsNone(numpy.testing.assert_array_equal(aoi_2d_scene.bounds, [[0, 0], [2, 2]])) + self.assertIsNone(numpy.testing.assert_array_equal(aoi_2d_scene.center, [1, 1])) + self.assertIsNone(numpy.testing.assert_array_equal(aoi_2d_scene.size, [2, 2])) + + def test_copy(self): + """Test AOIScene copy method.""" + + aoi_2D_A = AOIFeatures.AreaOfInterest([[0, 0], [0, 1], [1, 1], [1, 0]]) + aoi_2D_B = AOIFeatures.AreaOfInterest([[1, 1], [1, 2], [2, 2], [2, 1]]) + aoi_2d_scene = AOIFeatures.AOIScene(2, {"A": aoi_2D_A, "B": aoi_2D_B}) + + # Check full copy + aoi_2d_scene_copy = aoi_2d_scene.copy() + + self.assertEqual(aoi_2d_scene_copy.dimension, 2) + self.assertEqual(len(aoi_2d_scene_copy.items()), 2) + self.assertEqual(list(aoi_2d_scene_copy.keys()), ["A", "B"]) + + # Check cpy with exclusion + aoi_2d_scene_copy = aoi_2d_scene.copy(exclude=["B"]) + + self.assertEqual(aoi_2d_scene_copy.dimension, 2) + self.assertEqual(len(aoi_2d_scene_copy.items()), 1) + self.assertEqual(list(aoi_2d_scene_copy.keys()), ["A"]) + +class TestTimeStampedAOIScenesClass(unittest.TestCase): + """Test TimeStampedAOIScenes class.""" + + def test___setitem__(self): + """Test TimeStampedAOIScenes creation.""" + + aoi_2D_A = AOIFeatures.AreaOfInterest([[0, 0], [0, 1], [1, 1], [1, 0]]) + aoi_2D_B = AOIFeatures.AreaOfInterest([[1, 1], [1, 2], [2, 2], [2, 1]]) + aoi_2d_scene = AOIFeatures.AOIScene(2, {"A": aoi_2D_A, "B": aoi_2D_B}) + + ts_aois_scenes = AOIFeatures.TimeStampedAOIScenes() + + ts_aois_scenes[0] = aoi_2d_scene + + # Check that only AOIScene can be added + with self.assertRaises(AssertionError): + + ts_aois_scenes[1] = "This string is not an AOIScene" + +if __name__ == '__main__': + + unittest.main() \ No newline at end of file diff --git a/src/argaze.test/AreaOfInterest/utils/scene.obj b/src/argaze.test/AreaOfInterest/utils/scene.obj new file mode 100644 index 0000000..cc9c092 --- /dev/null +++ b/src/argaze.test/AreaOfInterest/utils/scene.obj @@ -0,0 +1,15 @@ +# .OBJ file for AOI3DScene unitary test +o A_Plan +v 0.000000 0.000000 0.000000 +v 1.000000 0.000000 0.000000 +v 0.000000 1.000000 0.000000 +v 1.000000 1.000000 0.000000 +s off +f 1 2 4 3 +o B_Plan +v 1.000000 1.000000 0.000000 +v 2.000000 1.000000 0.000000 +v 1.000000 2.000000 0.000000 +v 2.000000 2.000000 0.000000 +s off +f 5 6 8 7 -- cgit v1.1