From 837168f2f174bdb94c457601e529361b98e59101 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Mon, 30 Jan 2023 17:51:48 +0100 Subject: Refactoring realtime gaze movement identification. --- .../DispersionBasedGazeMovementIdentifier.py | 96 +++++++++++----------- 1 file changed, 50 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/argaze/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py b/src/argaze/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py index 849223c..51286ee 100644 --- a/src/argaze/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py +++ b/src/argaze/GazeAnalysis/DispersionBasedGazeMovementIdentifier.py @@ -370,86 +370,90 @@ class TobiiDataStream_GazeMovementIdentifier(): def __post_init__(self): - self.__valid_gaze_positions = GazeFeatures.TimeStampedGazePositions() - self.__current_fixation = None - self.__current_saccade = None + self.__valid_positions = GazeFeatures.TimeStampedGazePositions() + self.__fixation_positions = GazeFeatures.TimeStampedGazePositions() + self.__saccade_positions = GazeFeatures.TimeStampedGazePositions() def identify(self, ts, gaze_position): + # Ignore non valid gaze position + #if not gaze_position.valid: + # return + # Check if too much time elapsed since last gaze position - if len(self.__valid_gaze_positions) > 0: + if len(self.__valid_positions) > 0: - ts_last, _ = self.__valid_gaze_positions.last + ts_last, _ = self.__valid_positions.last if (ts - ts_last) > self.duration_min_threshold: - # Clear valid positions - self.__valid_gaze_positions = GazeFeatures.TimeStampedGazePositions() - - # Clear current movements - self.__current_fixation = None - self.__current_saccade = None + # Clear all former gaze positions + self.__valid_positions = GazeFeatures.TimeStampedGazePositions() + self.__fixation_positions = GazeFeatures.TimeStampedGazePositions() + self.__saccade_positions = GazeFeatures.TimeStampedGazePositions() # Store gaze positions until a minimal duration - self.__valid_gaze_positions[ts] = gaze_position + self.__valid_positions[ts] = gaze_position - first_ts, _ = self.__valid_gaze_positions.first - last_ts, _ = self.__valid_gaze_positions.last + first_ts, _ = self.__valid_positions.first + last_ts, _ = self.__valid_positions.last # Once the minimal duration is reached if last_ts - first_ts >= self.duration_min_threshold: # Calculate the deviation of valid gaze positions - extended_fixation = Fixation(self.__valid_gaze_positions) - deviation = extended_fixation.deviation_max + deviation = Fixation(self.__valid_positions).deviation_max # Valid gaze positions deviation small enough if deviation <= self.deviation_max_threshold: - # Clear current saccade - # Question : Should we emit a SaccadeStop event? - self.__current_saccade = None + # Store last saccade + last_saccade = self.current_saccade - # Start/extend current fixation - # Question : Should we emit a FixationStart event? - self.__current_fixation = extended_fixation + # Clear saccade positions + self.__saccade_positions = GazeFeatures.TimeStampedGazePositions() - # Valid gaze positions deviation too wide while extending fixation - elif self.__current_fixation != None: + # Copy valid gaze positions into fixation positions + self.__fixation_positions = self.__valid_positions.copy() - # Create saccade from last position of current fixation to current gaze position - # Question : Should we emit a SaccadeStart event? - saccade_positions = GazeFeatures.TimeStampedGazePositions() - last_ts, last_position = self.__current_fixation.positions.last - saccade_positions[last_ts] = last_position - saccade_positions[ts] = gaze_position + # Output last saccade + return last_saccade + + # Valid gaze positions deviation too wide while identifying fixation + elif len(self.__fixation_positions) > 0: - self.__current_saccade = Saccade(saccade_positions) + # Store last fixation + last_fixation = self.current_fixation - # Clear current fixation - # Question : Should we emit a FixationStop event? - self.__current_fixation = None + # Start saccade positions with current gaze position + self.__saccade_positions[ts] = gaze_position - # Reset valid positions with current gaze position - self.__valid_gaze_positions = GazeFeatures.TimeStampedGazePositions() - self.__valid_gaze_positions[ts] = gaze_position + # Clear fixation positions + self.__fixation_positions = GazeFeatures.TimeStampedGazePositions() - # Valid gaze positions deviation too wide with no current fixation - else: + # Clear valid positions + self.__valid_positions = GazeFeatures.TimeStampedGazePositions() + + # Output last fixation + return last_fixation - # Create saccade with valid gaze positions - self.__current_saccade = Saccade(self.__valid_gaze_positions) + # Valid gaze positions deviation too wide while identifying saccade (or not) + else: - # Reset valid positions with current gaze position - self.__valid_gaze_positions = GazeFeatures.TimeStampedGazePositions() - self.__valid_gaze_positions[ts] = gaze_position + # Move oldest valid position into saccade positions + first_ts, first_position = self.__valid_positions.pop_first() + self.__saccade_positions[first_ts] = first_position @property def current_fixation(self) -> FixationType: - return self.__current_fixation + if len(self.__fixation_positions) > 0: + + return Fixation(self.__fixation_positions) @property def current_saccade(self) -> SaccadeType: - return self.__current_saccade + if len(self.__saccade_positions) > 0: + + return Saccade(self.__saccade_positions) -- cgit v1.1