aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThéo de la Hogue2022-04-13 18:24:13 +0200
committerThéo de la Hogue2022-04-13 18:24:13 +0200
commit3d3c2fd9e69a9de7e6bd8b1d0569cf0ae56e7e89 (patch)
tree3eb3526ba018ca6c285c54412cb1dd00ad6b7d5d
parent529e8ac3ea24f0aec09162bdc3be369b28c382c5 (diff)
downloadargaze-3d3c2fd9e69a9de7e6bd8b1d0569cf0ae56e7e89.zip
argaze-3d3c2fd9e69a9de7e6bd8b1d0569cf0ae56e7e89.tar.gz
argaze-3d3c2fd9e69a9de7e6bd8b1d0569cf0ae56e7e89.tar.bz2
argaze-3d3c2fd9e69a9de7e6bd8b1d0569cf0ae56e7e89.tar.xz
Refactoring FixationIdentifier to handle it as an iterator.
-rw-r--r--src/argaze/GazeFeatures.py49
-rw-r--r--src/argaze/utils/export_tobii_segment_fixations.py21
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}')