From 41fe51942dac48cb23c6c946ee1d53b90562051f Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Wed, 23 Nov 2022 10:55:30 +0100 Subject: Allowing to store dict wit hcorrect keys as GazePosition in TimeStampedGazePositions buffer. --- src/argaze.test/GazeFeatures.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'src/argaze.test') diff --git a/src/argaze.test/GazeFeatures.py b/src/argaze.test/GazeFeatures.py index 5a3c2d9..dd0717c 100644 --- a/src/argaze.test/GazeFeatures.py +++ b/src/argaze.test/GazeFeatures.py @@ -117,6 +117,7 @@ class TestTimeStampedGazePositionsClass(unittest.TestCase): ts_gaze_positions = GazeFeatures.TimeStampedGazePositions() ts_gaze_positions[0] = GazeFeatures.GazePosition() ts_gaze_positions[1] = GazeFeatures.UnvalidGazePosition() + ts_gaze_positions[2] = {"value": (0, 0), "accuracy": 0.} # Check GazePosition is correctly stored and accessible as a GazePosition self.assertIsInstance(ts_gaze_positions[0], GazeFeatures.GazePosition) @@ -126,13 +127,22 @@ class TestTimeStampedGazePositionsClass(unittest.TestCase): self.assertIsInstance(ts_gaze_positions[1], GazeFeatures.UnvalidGazePosition) self.assertEqual(ts_gaze_positions[1].valid, False) + # Check dict with "value" and "accuracy" keys is correctly stored and accessible as a GazePosition + self.assertIsInstance(ts_gaze_positions[2], GazeFeatures.GazePosition) + self.assertEqual(ts_gaze_positions[2].valid, True) + # Check that bad data type insertion fails with self.assertRaises(AssertionError): - ts_gaze_positions[2] = "This string is not a gaze position value." + ts_gaze_positions[3] = "This string is not a gaze position value." + + # Check that dict with bad keys insertion fails + with self.assertRaises(AssertionError): + + ts_gaze_positions[4] = {"bad_key": (0, 0), "accuracy": 0.} # Check final lenght - self.assertEqual(len(ts_gaze_positions), 2) + self.assertEqual(len(ts_gaze_positions), 3) def test___repr__(self): """Test inherited string representation.""" -- cgit v1.1 From 3f9f27f31d8328a28133c38603b460deabb55f81 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Thu, 24 Nov 2022 08:42:03 +0100 Subject: Testing dispersion based fixation and saccade identification. --- .../DispersionBasedGazeMovementIdentifier.py | 117 +++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py (limited to 'src/argaze.test') diff --git a/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py b/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py new file mode 100644 index 0000000..6d84333 --- /dev/null +++ b/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python + +import unittest +import random +import time + +from argaze import GazeFeatures +from argaze.GazeAnalysis import DispersionBasedGazeMovementIdentifier + +import numpy + +def build_gaze_fixation(size: int, center: tuple, dispersion: float, start_time: float, min_time: float, max_time: float): + """ Generate N TimeStampedGazePsoitions dispersed around a center point for testing purpose. + Timestamps are current time after random sleep (second). + GazePositions are random values. + """ + ts_gaze_positions = GazeFeatures.TimeStampedGazePositions() + + for i in range(0, size): + + # Edit gaze position + random_x = center[0] + dispersion * (random.random() - 0.5) + random_y = center[1] + dispersion * (random.random() - 0.5) + random_gaze_position = GazeFeatures.GazePosition((random_x, random_y)) + + # Store gaze position + ts = time.time() - start_time + ts_gaze_positions[ts] = random_gaze_position + + #print(ts, random_x, random_y) + + # Sleep a random time + sleep_time = random.random() * (max_time - min_time) + min_time + time.sleep(sleep_time) + + return ts_gaze_positions + +class TestDispersionBasedGazeMovementIdentifierClass(unittest.TestCase): + """Test DispersionBasedGazeMovementIdentifier class.""" + + def test_fixation_identification(self): + """Test DispersionBasedGazeMovementIdentifier fixation identification.""" + + size = 10 + center = (0, 0) + dispersion = 10 + start_time = time.time() + min_time = 0.01 + max_time = 0.1 + + ts_gaze_positions = build_gaze_fixation(size, center, dispersion, start_time, min_time, max_time) + gaze_movement_identifier = DispersionBasedGazeMovementIdentifier.GazeMovementIdentifier(dispersion_threshold=dispersion, duration_threshold=min_time*2) + ts_fixations, ts_saccades, ts_status = gaze_movement_identifier.identify(ts_gaze_positions) + + # Check result size + self.assertEqual(len(ts_fixations), 1) + self.assertEqual(len(ts_saccades), 0) + self.assertEqual(len(ts_status), size) + + # Check fixation + ts, fixation = ts_fixations.pop_first() + + self.assertEqual(len(fixation.positions.keys()), size) + self.assertLessEqual(fixation.dispersion, dispersion) + self.assertGreaterEqual(fixation.duration, size * min_time) + self.assertLessEqual(fixation.duration, size * max_time) + + def test_fixation_and_saccade_identification(self): + """Test DispersionBasedGazeMovementIdentifier fixation and saccade identification.""" + + size = 10 + center_A = (0, 0) + center_B = (50, 50) + dispersion = 10 + start_time = time.time() + min_time = 0.01 + max_time = 0.1 + + ts_gaze_positions_A = build_gaze_fixation(size, center_A, dispersion, start_time, min_time, max_time) + ts_gaze_positions_B = build_gaze_fixation(size, center_B, dispersion, start_time, min_time, max_time) + + ts_gaze_positions = ts_gaze_positions_A.append(ts_gaze_positions_B) + + gaze_movement_identifier = DispersionBasedGazeMovementIdentifier.GazeMovementIdentifier(dispersion_threshold=dispersion, duration_threshold=min_time*2) + ts_fixations, ts_saccades, ts_status = gaze_movement_identifier.identify(ts_gaze_positions) + + # Check result size + self.assertEqual(len(ts_fixations), 2) + self.assertEqual(len(ts_saccades), 1) + self.assertEqual(len(ts_status), size*2) + + # Check first fixation + ts, fixation = ts_fixations.pop_first() + + self.assertEqual(len(fixation.positions.keys()), size) + self.assertLessEqual(fixation.dispersion, dispersion) + self.assertGreaterEqual(fixation.duration, size * min_time) + self.assertLessEqual(fixation.duration, size * max_time) + + # Check first saccade + ts, saccade = ts_saccades.pop_first() + + self.assertEqual(len(saccade.positions.keys()), 2) + self.assertGreaterEqual(saccade.duration, min_time) + self.assertLessEqual(saccade.duration, max_time) + + # Check second fixation + ts, fixation = ts_fixations.pop_first() + + self.assertEqual(len(fixation.positions.keys()), size) + self.assertLessEqual(fixation.dispersion, dispersion) + self.assertGreaterEqual(fixation.duration, size * min_time) + self.assertLessEqual(fixation.duration, size * max_time) + +if __name__ == '__main__': + + unittest.main() \ No newline at end of file -- cgit v1.1 From 5d48a62031979e81ea5ccb5a4e83d2e051a6ad07 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Thu, 24 Nov 2022 09:00:10 +0100 Subject: Adding place holder function for further invalid position testing. --- .../GazeAnalysis/DispersionBasedGazeMovementIdentifier.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/argaze.test') diff --git a/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py b/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py index 6d84333..55f075c 100644 --- a/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py +++ b/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py @@ -112,6 +112,11 @@ class TestDispersionBasedGazeMovementIdentifierClass(unittest.TestCase): self.assertGreaterEqual(fixation.duration, size * min_time) self.assertLessEqual(fixation.duration, size * max_time) + def test_invalid_gaze_position(self): + """Test DispersionBasedGazeMovementIdentifier fixation and saccade identification with invalid gaze position.""" + + pass + if __name__ == '__main__': unittest.main() \ No newline at end of file -- cgit v1.1 From 1c79530b7a1e0f8cf9bb83586f0a6e2067c2ed72 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Thu, 24 Nov 2022 09:18:43 +0100 Subject: testing identification with non valid positions. --- .../DispersionBasedGazeMovementIdentifier.py | 52 ++++++++++++++++++---- 1 file changed, 43 insertions(+), 9 deletions(-) (limited to 'src/argaze.test') diff --git a/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py b/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py index 55f075c..01b2aad 100644 --- a/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py +++ b/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py @@ -9,7 +9,7 @@ from argaze.GazeAnalysis import DispersionBasedGazeMovementIdentifier import numpy -def build_gaze_fixation(size: int, center: tuple, dispersion: float, start_time: float, min_time: float, max_time: float): +def build_gaze_fixation(size: int, center: tuple, dispersion: float, start_time: float, min_time: float, max_time: float, validity: list = []): """ Generate N TimeStampedGazePsoitions dispersed around a center point for testing purpose. Timestamps are current time after random sleep (second). GazePositions are random values. @@ -18,16 +18,26 @@ def build_gaze_fixation(size: int, center: tuple, dispersion: float, start_time: for i in range(0, size): - # Edit gaze position - random_x = center[0] + dispersion * (random.random() - 0.5) - random_y = center[1] + dispersion * (random.random() - 0.5) - random_gaze_position = GazeFeatures.GazePosition((random_x, random_y)) + # Check position validity + valid = True + if len(validity) > i: + + valid = validity[i] + + if valid: + + # Edit gaze position + random_x = center[0] + dispersion * (random.random() - 0.5) + random_y = center[1] + dispersion * (random.random() - 0.5) + gaze_position = GazeFeatures.GazePosition((random_x, random_y)) + + else: + + gaze_position = GazeFeatures.UnvalidGazePosition() # Store gaze position ts = time.time() - start_time - ts_gaze_positions[ts] = random_gaze_position - - #print(ts, random_x, random_y) + ts_gaze_positions[ts] = gaze_position # Sleep a random time sleep_time = random.random() * (max_time - min_time) + min_time @@ -115,7 +125,31 @@ class TestDispersionBasedGazeMovementIdentifierClass(unittest.TestCase): def test_invalid_gaze_position(self): """Test DispersionBasedGazeMovementIdentifier fixation and saccade identification with invalid gaze position.""" - pass + size = 10 + center = (0, 0) + dispersion = 10 + start_time = time.time() + min_time = 0.01 + max_time = 0.1 + validity = [True, True, True, True, False, False, True, True, True, True] + + ts_gaze_positions = build_gaze_fixation(size, center, dispersion, start_time, min_time, max_time, validity) + + gaze_movement_identifier = DispersionBasedGazeMovementIdentifier.GazeMovementIdentifier(dispersion_threshold=dispersion, duration_threshold=min_time*2) + ts_fixations, ts_saccades, ts_status = gaze_movement_identifier.identify(ts_gaze_positions) + + # Check result size + self.assertEqual(len(ts_fixations), 1) + self.assertEqual(len(ts_saccades), 0) + self.assertEqual(len(ts_status), size-2) + + # Check fixation + ts, fixation = ts_fixations.pop_first() + + self.assertEqual(len(fixation.positions.keys()), size-2) + self.assertLessEqual(fixation.dispersion, dispersion) + self.assertGreaterEqual(fixation.duration, size * min_time) + self.assertLessEqual(fixation.duration, size * max_time) if __name__ == '__main__': -- cgit v1.1