From a097f0495ff95a410455a134082bb7a059a8da74 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Thu, 15 Jun 2023 16:59:25 +0200 Subject: Adding and testing GazeMovementIdentifier generator. --- .../DispersionThresholdIdentification.py | 36 ++++++++++++++++++++++ src/argaze/GazeFeatures.py | 27 ++++++++++++++++ 2 files changed, 63 insertions(+) (limited to 'src') diff --git a/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py b/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py index 712308a..eba80c8 100644 --- a/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py +++ b/src/argaze.test/GazeAnalysis/DispersionThresholdIdentification.py @@ -311,6 +311,42 @@ class TestDispersionThresholdIdentificationClass(unittest.TestCase): self.assertGreaterEqual(fixation.duration, 2 * size * min_time) self.assertLessEqual(fixation.duration, 2 * size * max_time) + def test_identification_generator(self): + """Test DispersionThresholdIdentification identification using generator.""" + + size = 10 + center_A = (0, 0) + center_B = (50, 50) + deviation_max = 10 + min_time = 0.01 + max_time = 0.1 + + ts_gaze_positions_A = build_gaze_fixation(size, center_A, deviation_max, min_time, max_time) + ts_gaze_positions_B = build_gaze_fixation(size, center_B, deviation_max, min_time, max_time, start_ts=ts_gaze_positions_A.last[0]) + + 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) + + fixation_count = 0 + + for ts, gaze_movement in gaze_movement_identifier(ts_gaze_positions): + + if GazeFeatures.is_fixation(gaze_movement): + + self.assertEqual(len(gaze_movement.positions.keys()), size-fixation_count) + self.assertLessEqual(gaze_movement.deviation_max, deviation_max) + self.assertGreaterEqual(gaze_movement.duration, size * min_time) + self.assertLessEqual(gaze_movement.duration, size * max_time) + + fixation_count += 1 + + elif GazeFeatures.is_saccade(gaze_movement): + + self.assertEqual(len(gaze_movement.positions.keys()), 1) + self.assertGreaterEqual(gaze_movement.duration, 0.) + self.assertLessEqual(gaze_movement.duration, 0.) + if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/src/argaze/GazeFeatures.py b/src/argaze/GazeFeatures.py index 870cd3c..cf1f7b8 100644 --- a/src/argaze/GazeFeatures.py +++ b/src/argaze/GazeFeatures.py @@ -405,6 +405,33 @@ class GazeMovementIdentifier(): return ts_fixations, ts_saccades, ts_status + def __call__(self, ts_gaze_positions: TimeStampedGazePositions) -> Tuple[int|float, GazeMovementType]: + """GazeMovement generator. + + Parameters: + ts_gaze_positions: timestamped gaze positions to process. + + Returns: + timestamp + gaze movement + """ + + assert(type(ts_gaze_positions) == TimeStampedGazePositions) + + # Get last ts to terminate identification on last gaze position + last_ts, _ = ts_gaze_positions.last + + # Iterate on gaze positions + for ts, gaze_position in ts_gaze_positions.items(): + + gaze_movement = self.identify(ts, gaze_position, terminate=(ts == last_ts)) + + if gaze_movement: + + start_ts, start_position = gaze_movement.positions.first + + yield start_ts, gaze_movement + ScanStepType = TypeVar('ScanStep', bound="ScanStep") # Type definition for type annotation convenience -- cgit v1.1