From 3d3c2fd9e69a9de7e6bd8b1d0569cf0ae56e7e89 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Wed, 13 Apr 2022 18:24:13 +0200 Subject: Refactoring FixationIdentifier to handle it as an iterator. --- src/argaze/GazeFeatures.py | 49 ++++++++++++++-------- src/argaze/utils/export_tobii_segment_fixations.py | 21 ++++++++-- 2 files changed, 49 insertions(+), 21 deletions(-) diff --git a/src/argaze/GazeFeatures.py b/src/argaze/GazeFeatures.py index bb72583..2f1d167 100644 --- a/src/argaze/GazeFeatures.py +++ b/src/argaze/GazeFeatures.py @@ -45,19 +45,16 @@ class TimeStampedFixations(DataStructures.TimeStampedBuffer): class FixationIdentifier(): """Abstract class to define what should provide a fixation identifier""" - fixations = TimeStampedFixations() - saccades = DataStructures.TimeStampedBuffer() - - def identify(self, ts_gaze_positions): - raise NotImplementedError('identify() method not implemented') - def __init__(self, ts_gaze_positions: TimeStampedGazePositions): if type(ts_gaze_positions) != TimeStampedGazePositions: raise ValueError('argument must be a TimeStampedGazePositions') - # do identification on a copy - self.identify(ts_gaze_positions.copy()) + def __iter__(self): + raise NotImplementedError('__iter__() method not implemented') + + def __next__(self): + raise NotImplementedError('__next__() method not implemented') class DispersionBasedFixationIdentifier(FixationIdentifier): """Implementation of the I-DT algorithm as described in: @@ -70,10 +67,13 @@ class DispersionBasedFixationIdentifier(FixationIdentifier): def __init__(self, ts_gaze_positions, dispersion_threshold = 10, duration_threshold = 100): + super().__init__(ts_gaze_positions) + self.__dispersion_threshold = dispersion_threshold self.__duration_threshold = duration_threshold - super().__init__(ts_gaze_positions) + # process identification on a copy + self.__ts_gaze_positions = ts_gaze_positions.copy() def __getEuclideanDispersion(self, ts_gaze_positions_list): """Euclidian dispersion algorithm""" @@ -102,13 +102,17 @@ class DispersionBasedFixationIdentifier(FixationIdentifier): return (max(x_list) - min(x_list)) + (max(y_list) - min(y_list)) - def identify(self, ts_gaze_positions): + def __iter__(self): + """Start fixation identification""" + return self + + def __next__(self): # while there are 2 gaze positions at least - while len(ts_gaze_positions) >= 2: + if len(self.__ts_gaze_positions) >= 2: # copy remaining timestamped gaze positions - remaining_ts_gaze_positions = ts_gaze_positions.copy() + remaining_ts_gaze_positions = self.__ts_gaze_positions.copy() # select timestamped gaze position until a duration threshold (ts_start, gaze_position_start) = remaining_ts_gaze_positions.pop_first() @@ -128,13 +132,12 @@ class DispersionBasedFixationIdentifier(FixationIdentifier): # how much gaze is dispersed ? dispersion, cx, cy = self.__getEuclideanDispersion(ts_gaze_positions_list) - # little dispersion if dispersion <= self.__dispersion_threshold: # remove selected gaze positions for gp in ts_gaze_positions_list: - ts_gaze_positions.pop_first() + self.__ts_gaze_positions.pop_first() # are next gaze positions not too dispersed ? while len(remaining_ts_gaze_positions) > 0: @@ -157,7 +160,7 @@ class DispersionBasedFixationIdentifier(FixationIdentifier): cy = new_cy # remove selected gaze position - ts_gaze_positions.pop_first() + self.__ts_gaze_positions.pop_first() # we have a new fixation ts_list = [ts for (ts, gp) in ts_gaze_positions_list] @@ -168,9 +171,19 @@ class DispersionBasedFixationIdentifier(FixationIdentifier): if duration > 0: - # append fixation to timestamped buffer - self.fixations[ts_list[0]] = Fixation(duration, dispersion, cx, cy) + # return timestamp and fixation + return ts_list[0], Fixation(duration, dispersion, cx, cy) + + return -1, None # dispersion too wide : consider next gaze position else: - ts_gaze_positions.pop_first() + self.__ts_gaze_positions.pop_first() + + # if no fixation found, go to next + return -1, None + + else: + raise StopIteration + + return -1, None diff --git a/src/argaze/utils/export_tobii_segment_fixations.py b/src/argaze/utils/export_tobii_segment_fixations.py index 4181c77..d40c4d3 100644 --- a/src/argaze/utils/export_tobii_segment_fixations.py +++ b/src/argaze/utils/export_tobii_segment_fixations.py @@ -6,7 +6,7 @@ import os from argaze import GazeFeatures from argaze.TobiiGlassesPro2 import TobiiEntities - +from argaze.utils import MiscFeatures def main(): """ @@ -64,11 +64,26 @@ def main(): print(f'Duration threshold: {args.duration_threshold}') fixation_analyser = GazeFeatures.DispersionBasedFixationIdentifier(generic_ts_gaze_positions, args.dispersion_threshold, args.duration_threshold) + fixations = GazeFeatures.TimeStampedFixations() + + # Start fixation identification + MiscFeatures.printProgressBar(0, int(tobii_segment_video.get_duration()*1000), prefix = 'Progress:', suffix = 'Complete', length = 100) + + for ts, item in fixation_analyser: + + if item == None: + continue + + if item.get_type() == 'Fixation': + + fixations[ts] = item + + MiscFeatures.printProgressBar(ts, int(tobii_segment_video.get_duration()*1000), prefix = 'Progress:', suffix = 'Complete', length = 100) - print(f'{len(fixation_analyser.fixations)} fixations found') + print(f'\n{len(fixations)} fixations found') # Export fixations analysis results - fixation_analyser.fixations.export_as_json(fixations_filepath) + fixations.export_as_json(fixations_filepath) print(f'Fixations saved into {fixations_filepath}') -- cgit v1.1