#!/usr/bin/env python import unittest import random import time from argaze import GazeFeatures from argaze.GazeAnalysis import DispersionThresholdIdentification import numpy def build_gaze_fixation(size: int, center: tuple, deviation_max: 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. """ ts_gaze_positions = GazeFeatures.TimeStampedGazePositions() for i in range(0, size): # Check position validity valid = True if len(validity) > i: valid = validity[i] if valid: # Edit gaze position random_x = center[0] + deviation_max * (random.random() - 0.5) random_y = center[1] + deviation_max * (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] = gaze_position # Sleep a random time sleep_time = random.random() * (max_time - min_time) + min_time time.sleep(sleep_time) return ts_gaze_positions def build_gaze_saccade(size: int, center_A: tuple, center_B: tuple, start_time: float, min_time: float, max_time: float, validity: list = []): """ Generate N TimeStampedGazePsoitions between 2 center points 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): # Check position validity valid = True if len(validity) > i: valid = validity[i] if valid: # Edit gaze position move_x = center_A[0] + (center_B[0] - center_A[0]) * (i / size) move_y = center_A[1] + (center_B[1] - center_A[1]) * (i / size) gaze_position = GazeFeatures.GazePosition((move_x, move_y)) else: gaze_position = GazeFeatures.UnvalidGazePosition() # Store gaze position ts = time.time() - start_time ts_gaze_positions[ts] = gaze_position # Sleep a random time sleep_time = random.random() * (max_time - min_time) + min_time time.sleep(sleep_time) return ts_gaze_positions class TestDispersionThresholdIdentificationClass(unittest.TestCase): """Test DispersionThresholdIdentification class.""" def test_fixation_identification(self): """Test DispersionThresholdIdentification fixation identification.""" size = 10 center = (0, 0) deviation_max = 10 start_time = time.time() min_time = 0.01 max_time = 0.1 ts_gaze_positions = build_gaze_fixation(size, center, deviation_max, start_time, min_time, max_time) gaze_movement_identifier = DispersionThresholdIdentification.GazeMovementIdentifier(deviation_max_threshold=deviation_max, duration_min_threshold=max_time*2) ts_fixations, ts_saccades, ts_status = gaze_movement_identifier.browse(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.deviation_max, deviation_max) self.assertGreaterEqual(fixation.duration, size * min_time) self.assertLessEqual(fixation.duration, size * max_time) def test_fixation_and_direct_saccade_identification(self): """Test DispersionThresholdIdentification fixation and saccade identification.""" size = 10 center_A = (0, 0) center_B = (50, 50) deviation_max = 10 start_time = time.time() min_time = 0.01 max_time = 0.1 ts_gaze_positions_A = build_gaze_fixation(size, center_A, deviation_max, start_time, min_time, max_time) ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, start_time, min_time, max_time) ts_gaze_positions = ts_gaze_positions_A.append(ts_gaze_positions_B) gaze_movement_identifier = DispersionThresholdIdentification.GazeMovementIdentifier(deviation_max_threshold=deviation_max, duration_min_threshold=max_time*2) ts_fixations, ts_saccades, ts_status = gaze_movement_identifier.browse(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.deviation_max, deviation_max) 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()), 1) self.assertGreaterEqual(saccade.duration, 0.) self.assertLessEqual(saccade.duration, 0.) # Check second fixation ts, fixation = ts_fixations.pop_first() self.assertEqual(len(fixation.positions.keys()), size-1) self.assertLessEqual(fixation.deviation_max, deviation_max) self.assertGreaterEqual(fixation.duration, size * min_time) self.assertLessEqual(fixation.duration, size * max_time) ''' def test_fixation_and_short_saccade_identification(self): """Test DispersionThresholdIdentification fixation and saccade identification.""" size = 10 move = 2 center_A = (0, 0) out_A = (20, 20) center_B = (50, 50) deviation_max = 10 start_time = time.time() min_time = 0.01 max_time = 0.1 ts_gaze_positions_A = build_gaze_fixation(size, center_A, deviation_max, start_time, min_time, max_time) ts_move_positions = build_gaze_saccade(move, out_A, center_B, start_time, min_time, min_time) ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, start_time, min_time, max_time) print(ts_move_positions) ts_gaze_positions = ts_gaze_positions_A.append(ts_move_positions).append(ts_gaze_positions_B) gaze_movement_identifier = DispersionThresholdIdentification.GazeMovementIdentifier(deviation_max_threshold=deviation_max, duration_min_threshold=max_time*2) ts_fixations, ts_saccades, ts_status = gaze_movement_identifier.browse(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 + move) # Check first fixation ts, fixation = ts_fixations.pop_first() self.assertEqual(len(fixation.positions.keys()), size-1) self.assertLessEqual(fixation.deviation_max, deviation_max) 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()), move + 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-1) self.assertLessEqual(fixation.deviation_max, deviation_max) self.assertGreaterEqual(fixation.duration, size * min_time) self.assertLessEqual(fixation.duration, size * max_time) ''' def test_invalid_gaze_position(self): """Test DispersionThresholdIdentification fixation and saccade identification with invalid gaze position.""" size = 15 center = (0, 0) deviation_max = 10 start_time = time.time() min_time = 0.01 max_time = 0.1 validity = [True, True, True, True, False, True, True, True, False, False, True, True, True, True, True] ts_gaze_positions = build_gaze_fixation(size, center, deviation_max, start_time, min_time, max_time, validity) gaze_movement_identifier = DispersionThresholdIdentification.GazeMovementIdentifier(deviation_max_threshold=deviation_max, duration_min_threshold=max_time*2) ts_fixations, ts_saccades, ts_status = gaze_movement_identifier.browse(ts_gaze_positions) # Check result size self.assertEqual(len(ts_fixations), 1) self.assertEqual(len(ts_saccades), 0) self.assertEqual(len(ts_status), size-3) # Check fixation ts, fixation = ts_fixations.pop_first() self.assertEqual(len(fixation.positions.keys()), size-3) self.assertLessEqual(fixation.deviation_max, deviation_max) self.assertGreaterEqual(fixation.duration, size * min_time) self.assertLessEqual(fixation.duration, size * max_time) @unittest.skip("Fixation overlapping is broken.") def test_fixation_overlapping(self): """Test Fixation overlap function.""" size = 10 center_A = (0, 0) center_B = (50, 50) center_C = (55, 55) deviation_max = 10 start_time = time.time() min_time = 0.01 max_time = 0.1 ts_gaze_positions_A = build_gaze_fixation(size, center_A, deviation_max, start_time, min_time, max_time) ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, start_time, min_time, max_time) ts_gaze_positions_C = build_gaze_fixation(size, center_C, deviation_max, start_time, min_time, max_time) fixation_A = DispersionThresholdIdentification.Fixation(ts_gaze_positions_A) fixation_B = DispersionThresholdIdentification.Fixation(ts_gaze_positions_B) fixation_C = DispersionThresholdIdentification.Fixation(ts_gaze_positions_C) # Check that fixation doesn't overlap self.assertFalse(fixation_A.overlap(fixation_B)) self.assertFalse(fixation_B.overlap(fixation_A)) # Check that fixation overlaps self.assertTrue(fixation_B.overlap(fixation_C)) self.assertTrue(fixation_C.overlap(fixation_B)) @unittest.skip("Fixation overlapping identification is broken.") def test_fixation_overlapping_identification(self): """Test DispersionThresholdIdentification identification when fixations overlap.""" size = 50 center_A = (-5, 0) center_B = (5, 0) deviation_max = 15 start_time = time.time() min_time = 0.01 max_time = 0.1 ts_gaze_positions_A = build_gaze_fixation(size, center_A, deviation_max, start_time, min_time, max_time) ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, start_time, min_time, max_time) ts_gaze_positions = ts_gaze_positions_A.append(ts_gaze_positions_B) gaze_movement_identifier = DispersionThresholdIdentification.GazeMovementIdentifier(deviation_max_threshold=deviation_max, duration_min_threshold=max_time*2) ts_fixations, ts_saccades, ts_status = gaze_movement_identifier.browse(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 unique fixation ts, fixation = ts_fixations.pop_first() self.assertEqual(len(fixation.positions.keys()), size*2) #self.assertGreaterEqual(fixation.deviation_max, deviation_max) self.assertGreaterEqual(fixation.duration, 2 * size * min_time) self.assertLessEqual(fixation.duration, 2 * size * max_time) if __name__ == '__main__': unittest.main()