aboutsummaryrefslogtreecommitdiff
path: root/src/argaze/GazeFeatures.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/argaze/GazeFeatures.py')
-rw-r--r--src/argaze/GazeFeatures.py66
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):