From b4d8bed97612dc57cd9f4df97133bad193f43e7b Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Tue, 9 May 2023 17:50:51 +0200 Subject: Defining VisualScan interface. --- src/argaze/GazeFeatures.py | 103 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/src/argaze/GazeFeatures.py b/src/argaze/GazeFeatures.py index a65f55b..bfe19fb 100644 --- a/src/argaze/GazeFeatures.py +++ b/src/argaze/GazeFeatures.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from typing import TypeVar, Tuple +from typing import TypeVar, Tuple, Any from dataclasses import dataclass, field import math import ast @@ -285,3 +285,104 @@ class GazeMovementIdentifier(): continue return ts_fixations, ts_saccades, ts_status + +VisualScanNodeType = TypeVar('VisualScanNode', bound="VisualScanNode") +# Type definition for type annotation convenience + +@dataclass(frozen=True) +class VisualScanNode(): + """Define a visual scan node as a set of successive gaze movements onto a same AOI.""" + + movements: TimeStampedGazeMovements + """ """ + + aoi: str = field(default='') + """Name of the looked AOI""" + +VisualScanArcType = TypeVar('VisualScanArc', bound="VisualScanArc") +# Type definition for type annotation convenience + +@dataclass(frozen=True) +class VisualScanArc(): + """Define a visual scan arc as a saccade to a next visual scan node.""" + + saccade: Saccade + """Saccade to the next scan node""" + + to: VisualScanNode + """Node where the saccade ends""" + +class VisualScan(list): + """Build a linear path made of nodes relied by arcs from successive timestamped gaze movements over aoi.""" + + def __init__(self): + + super().__init__() + + self.__movements = TimeStampedGazeMovements() + self.__last_aoi = '' + + def __repr__(self): + """String representation""" + + return str(super()) + + def __str__(self) -> str: + """String display""" + + output = '' + + for arc in self: + + output += f'> {arc.to.aoi} ' + + return output + + def append_saccade(self, ts, saccade): + """Append a new saccade to visual scan.""" + + self.__movements[ts] = saccade + + def append_fixation(self, ts, fixation, looked_aoi: str) -> bool: + """Append a new fixation to visual scan and return True if a new arc have been created.""" + + # Is the fixation onto a new aoi? + if looked_aoi != self.__last_aoi and len(self.__movements) > 0: + + # Last movement should be a saccade + last_ts, last_movement = self.__movements.pop_last() + assert(type(last_movement).__bases__[0] == Saccade) + + # Edit new node and arc + new_node = VisualScanNode(self.__movements, looked_aoi) + new_arc = VisualScanArc(last_movement, new_node) + + # Append new arc + super().append(new_arc) + + # Clear movements + self.__movements = TimeStampedGazeMovements() + + # Append new fixation + self.__movements[ts] = fixation + + # Remember new aoi + self.__last_aoi = looked_aoi + + # Notify that a new arc have been created + return True + + else: + + # Append new fixation + self.__movements[ts] = fixation + + return False + +class VisualScanAnalyzer(): + """Abstract class to define what should provide a visual scan analyser.""" + + def analyze(self, visual_scan: list[VisualScanArcType]) -> Any: + """Analyze visual scan.""" + + raise NotImplementedError('analyze() method not implemented') -- cgit v1.1