diff options
Diffstat (limited to 'src/argaze/GazeFeatures.py')
-rw-r--r-- | src/argaze/GazeFeatures.py | 66 |
1 files changed, 44 insertions, 22 deletions
diff --git a/src/argaze/GazeFeatures.py b/src/argaze/GazeFeatures.py index c0e5a36..dbeee61 100644 --- a/src/argaze/GazeFeatures.py +++ b/src/argaze/GazeFeatures.py @@ -16,24 +16,24 @@ __credits__ = [] __copyright__ = "Copyright 2023, Ecole Nationale de l'Aviation Civile (ENAC)" __license__ = "GPLv3" -from typing import Self -import math import json -import importlib +import math +from typing import Self + +import cv2 +import numpy +import pandas from argaze import DataFeatures from argaze.AreaOfInterest import AOIFeatures -import numpy -import pandas -import cv2 class GazePosition(tuple, DataFeatures.TimestampedObject): """Define gaze position as a tuple of coordinates with precision. Parameters: precision: the radius of a circle around value where other same gaze position measurements could be. - message: a string to describe why the the position is what it is. + message: a string to describe why the position is what it is. """ def __new__(cls, position: tuple = (), precision: int|float = None, message: str = None, timestamp: int|float = math.nan): @@ -62,7 +62,7 @@ class GazePosition(tuple, DataFeatures.TimestampedObject): return self.__message @classmethod - def from_dict(self, position_data: dict) -> Self: + def from_dict(cls, position_data: dict) -> Self: if 'value' in position_data.keys(): @@ -102,7 +102,7 @@ class GazePosition(tuple, DataFeatures.TimestampedObject): __radd__ = __add__ def __sub__(self, position: Self) -> Self: - """Substract position. + """Subtract position. !!! note The returned position precision is the maximal precision. @@ -119,7 +119,7 @@ class GazePosition(tuple, DataFeatures.TimestampedObject): return GazePosition(numpy.array(self) - numpy.array(position), timestamp=self.timestamp) def __rsub__(self, position: Self) -> Self: - """Reversed substract position. + """Reversed subtract position. !!! note The returned position precision is the maximal precision. @@ -194,7 +194,10 @@ class GazePosition(tuple, DataFeatures.TimestampedObject): class TimeStampedGazePositions(DataFeatures.TimestampedObjectsList): """Handle timestamped gaze positions into a list.""" - def __init__(self, gaze_positions: list = []): + def __init__(self, gaze_positions=None): + + if gaze_positions is None: + gaze_positions = [] DataFeatures.TimestampedObjectsList.__init__(self, GazePosition, gaze_positions) @@ -215,10 +218,11 @@ class TimeStampedGazePositions(DataFeatures.TimestampedObjectsList): ''' @classmethod - def from_dataframe(self, dataframe: pandas.DataFrame, timestamp: str, x: str, y: str, precision: str = None, message: str = None) -> Self: + def from_dataframe(cls, dataframe: pandas.DataFrame, timestamp: str, x: str, y: str, precision: str = None, message: str = None) -> Self: """Create a TimeStampedGazePositions from [Pandas DataFrame](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html). Parameters: + dataframe: timestamp: specific timestamp column label. x: specific x column label. y: specific y column label. @@ -354,11 +358,14 @@ class GazeMovement(TimeStampedGazePositions, DataFeatures.TimestampedObject): message: a string to describe why the movement is what it is. """ - def __new__(cls, positions: TimeStampedGazePositions = TimeStampedGazePositions(), finished: bool = False, message: str = None, timestamp: int|float = math.nan): + def __new__(cls, positions: TimeStampedGazePositions = None, finished: bool = False, + message: str = None, timestamp: int|float = math.nan): + # noinspection PyArgumentList return TimeStampedGazePositions.__new__(cls, positions) - def __init__(self, positions: TimeStampedGazePositions = TimeStampedGazePositions(), finished: bool = False, message: str = None, timestamp: int|float = math.nan): + def __init__(self, positions: TimeStampedGazePositions = None, finished: bool = False, + message: str = None, timestamp: int|float = math.nan): """Initialize GazeMovement""" TimeStampedGazePositions.__init__(self, positions) @@ -428,6 +435,7 @@ class GazeMovement(TimeStampedGazePositions, DataFeatures.TimestampedObject): """Draw gaze movement positions with line between each position. Parameters: + image: where to draw position_color: color of position point line_color: color of line between each position """ @@ -610,7 +618,7 @@ class GazeMovementIdentifier(DataFeatures.PipelineStepObject): gaze_status.append(len(ts_fixations), type(gaze_movement)) - # Store gaze movment into the appropriate list + # Store gaze movement into the appropriate list if is_fixation(gaze_movement): ts_fixations.append(gaze_movement) @@ -782,7 +790,7 @@ class ScanPath(list): def append_fixation(self, fixation): """Append new fixation to scan path. !!! warning - Consecutives fixations are ignored keeping the last fixation""" + Consecutive fixations are ignored keeping the last fixation""" self.__last_fixation = fixation @@ -790,8 +798,11 @@ class ScanPath(list): """Draw scan path into image. Parameters: - draw_fixations: Fixation.draw parameters (which depends of the loaded gaze movement identifier module, if None, no fixation is drawn) - draw_saccades: Saccade.draw parameters (which depends of the loaded gaze movement identifier module, if None, no saccade is drawn) + image: where to draw + draw_fixations: Fixation.draw parameters (which depends on the loaded gaze movement identifier module, + if None, no fixation is drawn) + draw_saccades: Saccade.draw parameters (which depends on the loaded gaze movement identifier module, + if None, no saccade is drawn) deepness: number of steps back to draw """ @@ -869,7 +880,10 @@ class AOIMatcher(DataFeatures.PipelineStepObject): raise NotImplementedError('looked_aoi_name() method not implemented') class AOIScanStepError(Exception): - """Exception raised at AOIScanStep creation if a aoi scan step doesn't start by a fixation or doesn't end by a saccade.""" + """ + Exception raised at AOIScanStep creation if an aoi scan step doesn't start by a fixation or + doesn't end by a saccade. + """ def __init__(self, message, aoi=''): @@ -978,7 +992,7 @@ class AOIScanPath(list): This will clear the AOIScanPath """ - # Check expected aoi are not the same than previous ones + # Check expected aoi are not the same as previous ones if len(expected_aoi) == len(self.__expected_aoi[1:]): equal = [a == b for a, b in zip(expected_aoi, self.__expected_aoi[1:])] @@ -1031,13 +1045,19 @@ class AOIScanPath(list): super().clear() + # noinspection PyAttributeOutsideInit self.__movements = TimeStampedGazeMovements() + # noinspection PyAttributeOutsideInit self.__current_aoi = '' + # noinspection PyAttributeOutsideInit self.__index = ord('A') + # noinspection PyAttributeOutsideInit self.__aoi_letter = {} + # noinspection PyAttributeOutsideInit self.__letter_aoi = {} size = len(self.__expected_aoi) + # noinspection PyAttributeOutsideInit self.__transition_matrix = pandas.DataFrame(numpy.zeros((size, size)), index=self.__expected_aoi, columns=self.__expected_aoi) def __get_aoi_letter(self, aoi): @@ -1054,7 +1074,7 @@ class AOIScanPath(list): return letter def get_letter_aoi(self, letter): - """Get which aoi is related to an unique letter.""" + """Get which aoi is related to a unique letter.""" return self.__letter_aoi[letter] @@ -1140,6 +1160,7 @@ class AOIScanPath(list): finally: # Clear movements + # noinspection PyAttributeOutsideInit self.__movements = TimeStampedGazeMovements() # Append new fixation @@ -1153,6 +1174,7 @@ class AOIScanPath(list): self.__movements.append(fixation) # Remember aoi + # noinspection PyAttributeOutsideInit self.__current_aoi = looked_aoi return None @@ -1173,7 +1195,7 @@ class AOIScanPath(list): return scan_fixations_count, aoi_fixations_count class AOIScanPathAnalyzer(DataFeatures.PipelineStepObject): - """Abstract class to define what should provide a aoi scan path analyzer.""" + """Abstract class to define what should provide an aoi scan path analyzer.""" @DataFeatures.PipelineStepInit def __init__(self, **kwargs): |