From 366b6add45ec4be430be8f0ce7ccfe1d5b3d7072 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Mon, 19 Dec 2022 17:00:56 +0100 Subject: Fixing GazeAnalysis test (rare) case. --- .../DispersionBasedGazeMovementIdentifier.py | 92 +++++++++++----------- .../DispersionBasedGazeMovementIdentifier.py | 55 ++++++++----- 2 files changed, 81 insertions(+), 66 deletions(-) diff --git a/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py b/src/argaze.test/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py index 55fab29..b342ea9 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, validity: list = []): +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. @@ -27,8 +27,8 @@ def build_gaze_fixation(size: int, center: tuple, dispersion: float, start_time: if valid: # Edit gaze position - random_x = center[0] + dispersion * (random.random() - 0.5) - random_y = center[1] + dispersion * (random.random() - 0.5) + 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: @@ -89,26 +89,26 @@ class TestDispersionBasedGazeMovementIdentifierClass(unittest.TestCase): size = 10 center = (0, 0) - dispersion = 10 + deviation_max = 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(deviation_max_threshold=dispersion, duration_min_threshold=max_time*2) - ts_fixations, ts_saccades, ts_unknown, ts_status = gaze_movement_identifier.identify(ts_gaze_positions) + ts_gaze_positions = build_gaze_fixation(size, center, deviation_max, start_time, min_time, max_time) + gaze_movement_identifier = DispersionBasedGazeMovementIdentifier.GazeMovementIdentifier(deviation_max_threshold=deviation_max, duration_min_threshold=max_time*2) + ts_fixations, ts_saccades, ts_movements, 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_unknown), 0) + self.assertEqual(len(ts_movements), 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.assertLessEqual(fixation.deviation_max, deviation_max) self.assertGreaterEqual(fixation.duration, size * min_time) self.assertLessEqual(fixation.duration, size * max_time) @@ -118,30 +118,30 @@ class TestDispersionBasedGazeMovementIdentifierClass(unittest.TestCase): size = 10 center_A = (0, 0) center_B = (50, 50) - dispersion = 10 + 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, 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_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 = DispersionBasedGazeMovementIdentifier.GazeMovementIdentifier(deviation_max_threshold=dispersion, duration_min_threshold=max_time*2) - ts_fixations, ts_saccades, ts_unknown, ts_status = gaze_movement_identifier.identify(ts_gaze_positions) + gaze_movement_identifier = DispersionBasedGazeMovementIdentifier.GazeMovementIdentifier(deviation_max_threshold=deviation_max, duration_min_threshold=max_time*2) + ts_fixations, ts_saccades, ts_movements, 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_unknown), 0) + self.assertEqual(len(ts_movements), 0) self.assertEqual(len(ts_status), size*2) # Check first fixation ts, fixation = ts_fixations.pop_first() self.assertEqual(len(fixation.positions.keys()), size-1) - self.assertLessEqual(fixation.dispersion, dispersion) + self.assertLessEqual(fixation.deviation_max, deviation_max) self.assertGreaterEqual(fixation.duration, size * min_time) self.assertLessEqual(fixation.duration, size * max_time) @@ -156,7 +156,7 @@ class TestDispersionBasedGazeMovementIdentifierClass(unittest.TestCase): ts, fixation = ts_fixations.pop_first() self.assertEqual(len(fixation.positions.keys()), size-1) - self.assertLessEqual(fixation.dispersion, dispersion) + self.assertLessEqual(fixation.deviation_max, deviation_max) self.assertGreaterEqual(fixation.duration, size * min_time) self.assertLessEqual(fixation.duration, size * max_time) ''' @@ -168,33 +168,33 @@ class TestDispersionBasedGazeMovementIdentifierClass(unittest.TestCase): center_A = (0, 0) out_A = (20, 20) center_B = (50, 50) - dispersion = 10 + 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, dispersion, start_time, min_time, max_time) + 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, dispersion, start_time, min_time, max_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 = DispersionBasedGazeMovementIdentifier.GazeMovementIdentifier(deviation_max_threshold=dispersion, duration_min_threshold=max_time*2) - ts_fixations, ts_saccades, ts_unknown, ts_status = gaze_movement_identifier.identify(ts_gaze_positions) + gaze_movement_identifier = DispersionBasedGazeMovementIdentifier.GazeMovementIdentifier(deviation_max_threshold=deviation_max, duration_min_threshold=max_time*2) + ts_fixations, ts_saccades, ts_movements, 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_unknown), 0) + self.assertEqual(len(ts_movements), 0) 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.dispersion, dispersion) + self.assertLessEqual(fixation.deviation_max, deviation_max) self.assertGreaterEqual(fixation.duration, size * min_time) self.assertLessEqual(fixation.duration, size * max_time) @@ -209,37 +209,37 @@ class TestDispersionBasedGazeMovementIdentifierClass(unittest.TestCase): ts, fixation = ts_fixations.pop_first() self.assertEqual(len(fixation.positions.keys()), size-1) - self.assertLessEqual(fixation.dispersion, dispersion) + 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 DispersionBasedGazeMovementIdentifier fixation and saccade identification with invalid gaze position.""" - size = 10 + size = 15 center = (0, 0) - dispersion = 10 + deviation_max = 10 start_time = time.time() min_time = 0.01 max_time = 0.1 - validity = [True, True, True, True, False, False, True, True, True, True] + validity = [True, True, True, True, False, True, True, True, False, False, True, True, True, True, True] - ts_gaze_positions = build_gaze_fixation(size, center, dispersion, start_time, min_time, max_time, validity) + ts_gaze_positions = build_gaze_fixation(size, center, deviation_max, start_time, min_time, max_time, validity) - gaze_movement_identifier = DispersionBasedGazeMovementIdentifier.GazeMovementIdentifier(deviation_max_threshold=dispersion, duration_min_threshold=max_time*2) - ts_fixations, ts_saccades, ts_unknown, ts_status = gaze_movement_identifier.identify(ts_gaze_positions) + gaze_movement_identifier = DispersionBasedGazeMovementIdentifier.GazeMovementIdentifier(deviation_max_threshold=deviation_max, duration_min_threshold=max_time*2) + ts_fixations, ts_saccades, ts_movements, 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_unknown), 0) - self.assertEqual(len(ts_status), size-2) + self.assertEqual(len(ts_movements), 0) + self.assertEqual(len(ts_status), size-3) # Check fixation ts, fixation = ts_fixations.pop_first() - self.assertEqual(len(fixation.positions.keys()), size-2) - self.assertLessEqual(fixation.dispersion, dispersion) + 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) @@ -250,14 +250,14 @@ class TestDispersionBasedGazeMovementIdentifierClass(unittest.TestCase): center_A = (0, 0) center_B = (50, 50) center_C = (55, 55) - dispersion = 10 + 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, 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_C = build_gaze_fixation(size, center_C, dispersion, start_time, min_time, max_time) + 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 = DispersionBasedGazeMovementIdentifier.Fixation(ts_gaze_positions_A) fixation_B = DispersionBasedGazeMovementIdentifier.Fixation(ts_gaze_positions_B) @@ -277,30 +277,30 @@ class TestDispersionBasedGazeMovementIdentifierClass(unittest.TestCase): size = 50 center_A = (-5, 0) center_B = (5, 0) - dispersion = 15 + 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, 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_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 = DispersionBasedGazeMovementIdentifier.GazeMovementIdentifier(deviation_max_threshold=dispersion, duration_min_threshold=max_time*2) - ts_fixations, ts_saccades, ts_unknown, ts_status = gaze_movement_identifier.identify(ts_gaze_positions) + gaze_movement_identifier = DispersionBasedGazeMovementIdentifier.GazeMovementIdentifier(deviation_max_threshold=deviation_max, duration_min_threshold=max_time*2) + ts_fixations, ts_saccades, ts_movements, 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_unknown), 0) + self.assertEqual(len(ts_movements), 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.dispersion, dispersion) + #self.assertGreaterEqual(fixation.deviation_max, deviation_max) self.assertGreaterEqual(fixation.duration, 2 * size * min_time) self.assertLessEqual(fixation.duration, 2 * size * max_time) diff --git a/src/argaze/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py b/src/argaze/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py index 1364c99..5cbf6c4 100644 --- a/src/argaze/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py +++ b/src/argaze/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py @@ -204,32 +204,25 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier): # Does a former fixation have been identified ? if self.__last_fixation != None: - # Inter fixations movement should: - # - starts at last position of last fixation (this position is out so it have to be popped) - # - stops at the first position inside new fixation - start_movement_ts, start_position = self.__last_fixation.positions.pop_last() - stop_movement_ts, stop_position = new_fixation.positions.pop_first() - # Remove last gaze position of the new fixation as it is it's out position last_new_ts, last_new_position = new_fixation.positions.pop_last() - # Rare case : the last fixation position is the same than the first position of the new fixation - if start_movement_ts == stop_movement_ts: - start_movement_ts, start_position = self.__last_fixation.positions.pop_last() - - # Update last and new fixations - self.__last_fixation.update() - new_fixation.update() - # Edit inter fixations movement gaze positions movement_gaze_positions = GazeFeatures.TimeStampedGazePositions() - # Edit first movement gaze position - movement_gaze_positions[start_movement_ts] = start_position - - # Is there unmatched gaze positions that belong to the movement? + # If such unmatched positions exist if len(unmatched_gaze_positions) > 0: + # Inter fixations movement should: + # - starts at last position of last fixation (this position is out so it have to be popped) + # - stops at the first position inside new fixation + start_movement_ts, start_position = self.__last_fixation.positions.pop_last() + stop_movement_ts, stop_position = new_fixation.positions.pop_first() + + # Edit first movement gaze position + movement_gaze_positions[start_movement_ts] = start_position + + # Edit movement positions with unmatched positions if there are between the 2 fixations start_unmatched_ts, _ = unmatched_gaze_positions.first end_unmatched_ts, _ = unmatched_gaze_positions.last @@ -245,12 +238,34 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier): # Ignore them: GazeMovementIdentifier have to output movements according their time apparition pass - # Edit last movement gaze position - movement_gaze_positions[stop_movement_ts] = stop_position + # Edit last movement gaze position + movement_gaze_positions[stop_movement_ts] = stop_position + + # When there is no unmatched positions between 2 fixations (*rare case) + else: + + # the last fixation position is the same than the first position of the new fixation + stop_movement_ts, stop_position = self.__last_fixation.positions.pop_last() + start_movement_ts, start_position = self.__last_fixation.positions.pop_last() + + # Edit first movement gaze position + movement_gaze_positions[start_movement_ts] = start_position + + # Edit last movement gaze position + movement_gaze_positions[stop_movement_ts] = stop_position + + # Update last and new fixations + self.__last_fixation.update() + new_fixation.update() # Does new fixation overlap last fixation? if self.__last_fixation.overlap(new_fixation): + # (*rare case) + if len(unmatched_gaze_positions) == 0: + self.__last_fixation.positions[start_movement_ts] = start_position + self.__last_fixation.positions[stop_movement_ts] = stop_position + # Merge new fixation into last fixation self.__last_fixation.merge(new_fixation) -- cgit v1.1