From 4df82841d44fab86edaef7bc93b80d83a8a1980f Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Tue, 27 Jun 2023 16:45:13 +0200 Subject: Adding new UnvalidGazeMovement class. --- src/argaze/ArFeatures.py | 6 +-- .../DispersionThresholdIdentification.py | 11 ++++- .../VelocityThresholdIdentification.py | 11 ++++- src/argaze/GazeFeatures.py | 47 +++++++++++++++------- 4 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/argaze/ArFeatures.py b/src/argaze/ArFeatures.py index 321bd1b..493ca8a 100644 --- a/src/argaze/ArFeatures.py +++ b/src/argaze/ArFeatures.py @@ -545,7 +545,7 @@ class ArScreen(): # Init gaze data self.__gaze_position = GazeFeatures.UnvalidGazePosition() - self.__gaze_movement = None + self.__gaze_movement = GazeFeatures.UnvalidGazeMovement() @classmethod def from_scene(self, aoi_scene, aoi_name, size, gaze_movement_identifier) -> ArScreenType: @@ -593,6 +593,4 @@ class ArScreen(): def draw_gaze_movement(self, color=(255, 255, 255)): """Draw current gaze movement into screen image.""" - if self.__gaze_movement: - - self.__gaze_movement.draw(self.__image, color) + self.__gaze_movement.draw_positions(self.__image, color) diff --git a/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py b/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py index 1883d69..147046a 100644 --- a/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py +++ b/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py @@ -124,7 +124,7 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier): # Ignore non valid gaze position if not gaze_position.valid: - return None if not terminate else self.current_fixation + return GazeFeatures.UnvalidGazeMovement() if not terminate else self.current_fixation # Check if too much time elapsed since last gaze position if len(self.__valid_positions) > 0: @@ -199,6 +199,9 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier): first_ts, first_position = self.__valid_positions.pop_first() self.__saccade_positions[first_ts] = first_position + # Always return unvalid gaze movement at least + return GazeFeatures.UnvalidGazeMovement() + @property def current_fixation(self) -> FixationType: @@ -206,9 +209,15 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier): return Fixation(self.__fixation_positions) + # Always return unvalid gaze movement at least + return GazeFeatures.UnvalidGazeMovement() + @property def current_saccade(self) -> SaccadeType: if len(self.__saccade_positions) > 0: return Saccade(self.__saccade_positions) + + # Always return unvalid gaze movement at least + return GazeFeatures.UnvalidGazeMovement() diff --git a/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py b/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py index 7d6c7b2..7131373 100644 --- a/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py +++ b/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py @@ -133,7 +133,7 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier): # Ignore non valid gaze position if not gaze_position.valid: - return None if not terminate else self.current_fixation + return GazeFeatures.UnvalidGazeMovement() if not terminate else self.current_fixation # Store first valid position if self.__last_ts < 0: @@ -211,6 +211,9 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier): return self.current_fixation + # Always return unvalid gaze movement at least + return GazeFeatures.UnvalidGazeMovement() + @property def current_fixation(self) -> FixationType: @@ -218,9 +221,15 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier): return Fixation(self.__fixation_positions) + # Always return unvalid gaze movement at least + return GazeFeatures.UnvalidGazeMovement() + @property def current_saccade(self) -> SaccadeType: if len(self.__saccade_positions) > 0: return Saccade(self.__saccade_positions) + + # Always return unvalid gaze movement at least + return GazeFeatures.UnvalidGazeMovement() diff --git a/src/argaze/GazeFeatures.py b/src/argaze/GazeFeatures.py index 218858a..65afdf8 100644 --- a/src/argaze/GazeFeatures.py +++ b/src/argaze/GazeFeatures.py @@ -223,30 +223,40 @@ class GazeMovement(): def __post_init__(self): - start_position_ts, start_position = self.positions.first - end_position_ts, end_position = self.positions.last + if self.valid: + + start_position_ts, start_position = self.positions.first + end_position_ts, end_position = self.positions.last - # Update frozen duration attribute - object.__setattr__(self, 'duration', end_position_ts - start_position_ts) + # Update frozen duration attribute + object.__setattr__(self, 'duration', end_position_ts - start_position_ts) - _, start_position = self.positions.first - _, end_position = self.positions.last + _, start_position = self.positions.first + _, end_position = self.positions.last - amplitude = numpy.linalg.norm( numpy.array(start_position.value) - numpy.array(end_position.value)) + amplitude = numpy.linalg.norm( numpy.array(start_position.value) - numpy.array(end_position.value)) - # Update frozen amplitude attribute - object.__setattr__(self, 'amplitude', amplitude) + # Update frozen amplitude attribute + object.__setattr__(self, 'amplitude', amplitude) def __str__(self) -> str: """String display""" - output = f'{type(self)}:\n\tduration={self.duration}\n\tsize={len(self.positions)}' + if self.valid: - for ts, position in self.positions.items(): + output = f'{type(self)}:\n\tduration={self.duration}\n\tsize={len(self.positions)}' - output += f'\n\t{ts}:\n\t\tvalue={position.value},\n\t\taccurracy={position.precision}' + for ts, position in self.positions.items(): - return output + output += f'\n\t{ts}:\n\t\tvalue={position.value},\n\t\taccurracy={position.precision}' + + return output + + @property + def valid(self) -> bool: + """Is there positions?""" + + return len(self.positions) > 0 def draw_positions(self, image: numpy.array, color=(0, 55, 55)): """Draw gaze movement positions""" @@ -262,7 +272,16 @@ class GazeMovement(): start_gaze_position.draw(image, draw_precision=False) # Draw movement from start to next - cv2.line(image, start_gaze_position, next_gaze_position, color, 1) + cv2.line(image, (int(start_gaze_position[0]), int(start_gaze_position[1])), (int(next_gaze_position[0]), int(next_gaze_position[1])), color, 1) + +class UnvalidGazeMovement(GazeMovement): + """Unvalid gaze movement.""" + + def __init__(self, message=None): + + self.message = message + + super().__init__([]) FixationType = TypeVar('Fixation', bound="Fixation") # Type definition for type annotation convenience -- cgit v1.1