aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThéo de la Hogue2022-03-26 01:01:32 +0100
committerThéo de la Hogue2022-03-26 01:01:32 +0100
commitcb1d236be929fd9d6bbc75debacb2de827acc852 (patch)
tree24d8c0cba7f7ebf40d330d8e1e00e70b9b081564
parent5ca8f2304655f9de27c2c4a930d1c20a995ed768 (diff)
downloadargaze-cb1d236be929fd9d6bbc75debacb2de827acc852.zip
argaze-cb1d236be929fd9d6bbc75debacb2de827acc852.tar.gz
argaze-cb1d236be929fd9d6bbc75debacb2de827acc852.tar.bz2
argaze-cb1d236be929fd9d6bbc75debacb2de827acc852.tar.xz
reorganizing library elements
-rw-r--r--src/argaze/DataAnalysis/DictObject.py20
-rw-r--r--src/argaze/DataAnalysis/README.md1
-rw-r--r--src/argaze/DataAnalysis/__init__.py5
-rw-r--r--src/argaze/DataStructures.py (renamed from src/argaze/DataAnalysis/TimeStampedDataBuffer.py)21
-rw-r--r--src/argaze/GazeFeatures.py (renamed from src/argaze/DataAnalysis/GazeAnalysis.py)95
-rw-r--r--src/argaze/TobiiGlassesPro2/TobiiEntities.py6
-rw-r--r--src/argaze/__init__.py3
-rw-r--r--src/argaze/utils/analyse_tobii_segment_fixations.py9
8 files changed, 77 insertions, 83 deletions
diff --git a/src/argaze/DataAnalysis/DictObject.py b/src/argaze/DataAnalysis/DictObject.py
deleted file mode 100644
index 2d6aa66..0000000
--- a/src/argaze/DataAnalysis/DictObject.py
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env python
-
-class DictObject():
- """Convert dictionnary into object"""
-
- def __init__(self, object_type, **dictionnary):
-
- self.__dict__.update(dictionnary)
- self.__type = object_type
-
- def __getitem__(self, key):
- return self.__dict__[key]
-
- def type(self):
- return self.__type
-
- def keys(self):
- return list(self.__dict__.keys())[:-1]
-
-
diff --git a/src/argaze/DataAnalysis/README.md b/src/argaze/DataAnalysis/README.md
deleted file mode 100644
index b7431a9..0000000
--- a/src/argaze/DataAnalysis/README.md
+++ /dev/null
@@ -1 +0,0 @@
-Class interface to manage [data analysis](https://en.wikipedia.org/wiki/Eye_tracking).
diff --git a/src/argaze/DataAnalysis/__init__.py b/src/argaze/DataAnalysis/__init__.py
deleted file mode 100644
index ff8fb6f..0000000
--- a/src/argaze/DataAnalysis/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-"""
-.. include:: README.md
-"""
-__docformat__ = "restructuredtext"
-__all__ = ['TimeStampedDataBuffer','DictObject','GazeAnalysis'] \ No newline at end of file
diff --git a/src/argaze/DataAnalysis/TimeStampedDataBuffer.py b/src/argaze/DataStructures.py
index 23bcf06..9c0414c 100644
--- a/src/argaze/DataAnalysis/TimeStampedDataBuffer.py
+++ b/src/argaze/DataStructures.py
@@ -2,7 +2,24 @@
import collections
-class TimeStampedDataBuffer(collections.OrderedDict):
+class DictObject():
+ """Convert dictionnary into object"""
+
+ def __init__(self, object_type, **dictionnary):
+
+ self.__dict__.update(dictionnary)
+ self.__type = object_type
+
+ def __getitem__(self, key):
+ return self.__dict__[key]
+
+ def type(self):
+ return self.__type
+
+ def keys(self):
+ return list(self.__dict__.keys())[:-1]
+
+class TimeStampedBuffer(collections.OrderedDict):
"""Ordered dictionary to handle timestamped data.
```
{
@@ -14,7 +31,7 @@ class TimeStampedDataBuffer(collections.OrderedDict):
"""
def __new__(cls):
- return super(TimeStampedDataBuffer, cls).__new__(cls)
+ return super(TimeStampedBuffer, cls).__new__(cls)
def __init__(self):
pass
diff --git a/src/argaze/DataAnalysis/GazeAnalysis.py b/src/argaze/GazeFeatures.py
index d0e4b9e..f223029 100644
--- a/src/argaze/DataAnalysis/GazeAnalysis.py
+++ b/src/argaze/GazeFeatures.py
@@ -2,21 +2,21 @@
import math
-from argaze.DataAnalysis import TimeStampedDataBuffer, DictObject
+from argaze import DataStructures
import numpy
FIXATION_MAX_DURATION = 1000
-class GazePosition(DictObject.DictObject):
- """Define gaze position data"""
+class GazePosition(DataStructures.DictObject):
+ """Define gaze position"""
def __init__(self, x, y):
super().__init__(type(self).__name__, **{'x': x, 'y': y})
-class TimeStampedGazePositionBuffer(TimeStampedDataBuffer.TimeStampedDataBuffer):
- """Define timestamped data buffer for gaze position only"""
+class TimeStampedGazePositions(DataStructures.TimeStampedBuffer):
+ """Define timestamped buffer to store gaze positions"""
def __setitem__(self, key, value: GazePosition):
"""Force value to be a GazePosition"""
@@ -25,15 +25,15 @@ class TimeStampedGazePositionBuffer(TimeStampedDataBuffer.TimeStampedDataBuffer)
super().__setitem__(key, value)
-class Fixation(DictObject.DictObject):
- """Define fixation data"""
+class Fixation(DataStructures.DictObject):
+ """Define fixation"""
def __init__(self, duration, dispersion, cx, cy):
super().__init__(type(self).__name__, **{'duration': duration, 'dispersion': dispersion, 'centroid': [cx, cy]})
-class TimeStampedFixationBuffer(TimeStampedDataBuffer.TimeStampedDataBuffer):
- """Define timestamped data buffer for fixation only"""
+class TimeStampedFixations(DataStructures.TimeStampedBuffer):
+ """Define timestamped buffer to store fixations"""
def __setitem__(self, key, value: Fixation):
"""Force value to be a Fixation"""
@@ -42,23 +42,24 @@ class TimeStampedFixationBuffer(TimeStampedDataBuffer.TimeStampedDataBuffer):
super().__setitem__(key, value)
-class FixationAnalyser():
+class FixationIdentifier():
+ """Abstract class to define what should provide a fixation identifier"""
- fixations = TimeStampedFixationBuffer()
- saccades = TimeStampedDataBuffer.TimeStampedDataBuffer()
+ fixations = TimeStampedFixations()
+ saccades = DataStructures.TimeStampedBuffer()
- def analyse(self, ts_gaze_position_buffer):
- raise NotImplementedError('analyse() method not implemented')
+ def identify(self, ts_gaze_positions):
+ raise NotImplementedError('identify() method not implemented')
- def __init__(self, ts_gaze_position_buffer: TimeStampedGazePositionBuffer):
+ def __init__(self, ts_gaze_positions: TimeStampedGazePositions):
- if type(ts_gaze_position_buffer) != TimeStampedGazePositionBuffer:
- raise ValueError('argument must be a TimeStampedGazePositionBuffer')
+ if type(ts_gaze_positions) != TimeStampedGazePositions:
+ raise ValueError('argument must be a TimeStampedGazePositions')
- # do analysis on a copy
- self.analyse(ts_gaze_position_buffer.copy())
+ # do identification on a copy
+ self.identify(ts_gaze_positions.copy())
-class DispersionBasedFixationAnalyser(FixationAnalyser):
+class DispersionBasedFixationIdentifier(FixationIdentifier):
"""Implementation of the I-DT algorithm as described in:
Dario D. Salvucci and Joseph H. Goldberg. 2000. Identifying fixations and
@@ -67,18 +68,18 @@ class DispersionBasedFixationAnalyser(FixationAnalyser):
71-78. DOI=http://dx.doi.org/10.1145/355017.355028
"""
- def __init__(self, ts_gaze_position_buffer, dispersion_threshold = 10, duration_threshold = 100):
+ def __init__(self, ts_gaze_positions, dispersion_threshold = 10, duration_threshold = 100):
self.__dispersion_threshold = dispersion_threshold
self.__duration_threshold = duration_threshold
- super().__init__(ts_gaze_position_buffer)
+ super().__init__(ts_gaze_positions)
# euclidian dispersion
- def __getEuclideanDispersion(self, ts_gaze_position_buffer_list):
+ def __getEuclideanDispersion(self, ts_gaze_positions_list):
- x_list = [gp.x for (ts, gp) in ts_gaze_position_buffer_list]
- y_list = [gp.y for (ts, gp) in ts_gaze_position_buffer_list]
+ x_list = [gp.x for (ts, gp) in ts_gaze_positions_list]
+ y_list = [gp.y for (ts, gp) in ts_gaze_positions_list]
cx = numpy.mean(x_list)
cy = numpy.mean(y_list)
@@ -93,60 +94,60 @@ class DispersionBasedFixationAnalyser(FixationAnalyser):
return max(dist), cx, cy
# basic dispersion
- def __getDispersion(self, ts_gaze_position_buffer_list):
+ def __getDispersion(self, ts_gaze_positions_list):
- x_list = [gp.x for (ts, gp) in ts_gaze_position_buffer_list]
- y_list = [gp.y for (ts, gp) in ts_gaze_position_buffer_list]
+ x_list = [gp.x for (ts, gp) in ts_gaze_positions_list]
+ y_list = [gp.y for (ts, gp) in ts_gaze_positions_list]
return (max(x_list) - min(x_list)) + (max(y_list) - min(y_list))
- def analyse(self, ts_gaze_position_buffer):
+ def identify(self, ts_gaze_positions):
# while there are 2 gaze positions at least
- while len(ts_gaze_position_buffer) >= 2:
+ while len(ts_gaze_positions) >= 2:
# copy remaining timestamped gaze positions
- remaining_ts_gaze_position_buffer = ts_gaze_position_buffer.copy()
+ remaining_ts_gaze_positions = ts_gaze_positions.copy()
# select timestamped gaze position until a duration threshold
- (ts_start, gaze_position_start) = remaining_ts_gaze_position_buffer.pop_first()
- (ts_current, gaze_position_current) = remaining_ts_gaze_position_buffer.pop_first()
+ (ts_start, gaze_position_start) = remaining_ts_gaze_positions.pop_first()
+ (ts_current, gaze_position_current) = remaining_ts_gaze_positions.pop_first()
- ts_gaze_position_buffer_list = [(ts_start, gaze_position_start)]
+ ts_gaze_positions_list = [(ts_start, gaze_position_start)]
while (ts_current - ts_start) < self.__duration_threshold:
- ts_gaze_position_buffer_list.append( (ts_current, gaze_position_current) )
+ ts_gaze_positions_list.append( (ts_current, gaze_position_current) )
- if len(remaining_ts_gaze_position_buffer) > 0:
- (ts_current, gaze_position_current) = remaining_ts_gaze_position_buffer.pop_first()
+ if len(remaining_ts_gaze_positions) > 0:
+ (ts_current, gaze_position_current) = remaining_ts_gaze_positions.pop_first()
else:
break
# how much gaze is dispersed ?
- dispersion, cx, cy = self.__getEuclideanDispersion(ts_gaze_position_buffer_list)
+ 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_position_buffer_list:
- ts_gaze_position_buffer.pop_first()
+ for gp in ts_gaze_positions_list:
+ ts_gaze_positions.pop_first()
# are next gaze positions not too dispersed ?
- while len(remaining_ts_gaze_position_buffer) > 0:
+ while len(remaining_ts_gaze_positions) > 0:
# select next gaze position
- ts_gaze_position_buffer_list.append(remaining_ts_gaze_position_buffer.pop_first())
+ ts_gaze_positions_list.append(remaining_ts_gaze_positions.pop_first())
- new_dispersion, new_cx, new_cy = self.__getEuclideanDispersion(ts_gaze_position_buffer_list)
+ new_dispersion, new_cx, new_cy = self.__getEuclideanDispersion(ts_gaze_positions_list)
# dispersion too wide
if new_dispersion > self.__dispersion_threshold:
# remove last gaze position
- ts_gaze_position_buffer_list.pop(-1)
+ ts_gaze_positions_list.pop(-1)
break
# store new dispersion data
@@ -155,10 +156,10 @@ class DispersionBasedFixationAnalyser(FixationAnalyser):
cy = new_cy
# remove selected gaze position
- ts_gaze_position_buffer.pop_first()
+ ts_gaze_positions.pop_first()
# we have a new fixation
- ts_list = [ts for (ts, gp) in ts_gaze_position_buffer_list]
+ ts_list = [ts for (ts, gp) in ts_gaze_positions_list]
duration = ts_list[-1] - ts_list[0]
if duration > FIXATION_MAX_DURATION:
@@ -171,4 +172,4 @@ class DispersionBasedFixationAnalyser(FixationAnalyser):
# dispersion too wide : consider next gaze position
else:
- ts_gaze_position_buffer.pop_first()
+ ts_gaze_positions.pop_first()
diff --git a/src/argaze/TobiiGlassesPro2/TobiiEntities.py b/src/argaze/TobiiGlassesPro2/TobiiEntities.py
index e41bbc9..3cfbf91 100644
--- a/src/argaze/TobiiGlassesPro2/TobiiEntities.py
+++ b/src/argaze/TobiiGlassesPro2/TobiiEntities.py
@@ -5,7 +5,7 @@ import json
import gzip
import os
-from argaze.DataAnalysis import TimeStampedDataBuffer, DictObject
+from argaze import *
import cv2 as cv
@@ -62,11 +62,11 @@ class TobiiSegmentData:
# convert json data into data object
data_object_type = '-'.join(json_item.keys())
- data_object = DictObject.DictObject(data_object_type, **json_item)
+ data_object = DataStructures.DictObject(data_object_type, **json_item)
# append a dedicated timestamped buffer for each data object type
if data_object.type() not in ts_data_buffer_dict.keys():
- ts_data_buffer_dict[data_object.type()] = TimeStampedDataBuffer.TimeStampedDataBuffer()
+ ts_data_buffer_dict[data_object.type()] = DataStructures.TimeStampedBuffer()
# store data object into the timestamped buffer dedicated to its type
ts_data_buffer_dict[data_object.type()][ts] = data_object
diff --git a/src/argaze/__init__.py b/src/argaze/__init__.py
index 0252f36..e475324 100644
--- a/src/argaze/__init__.py
+++ b/src/argaze/__init__.py
@@ -1,4 +1,5 @@
"""
.. include:: ../../README.md
"""
-__docformat__ = "restructuredtext" \ No newline at end of file
+__docformat__ = "restructuredtext"
+__all__ = ['DataStructures', 'GazeFeatures'] \ No newline at end of file
diff --git a/src/argaze/utils/analyse_tobii_segment_fixations.py b/src/argaze/utils/analyse_tobii_segment_fixations.py
index a07a515..b34979e 100644
--- a/src/argaze/utils/analyse_tobii_segment_fixations.py
+++ b/src/argaze/utils/analyse_tobii_segment_fixations.py
@@ -2,8 +2,9 @@
import argparse
+from argaze import *
from argaze.TobiiGlassesPro2 import TobiiEntities
-from argaze.DataAnalysis import *
+
def main():
"""
@@ -32,16 +33,16 @@ def main():
print(f'{len(tobii_ts_gaze_position_buffer)} gaze positions loaded')
# format tobii gaze data into generic gaze data
- generic_ts_gaze_position_buffer = GazeAnalysis.TimeStampedGazePositionBuffer()
+ generic_ts_gaze_position_buffer = GazeFeatures.TimeStampedGazePositions()
for ts, tobii_data in tobii_ts_gaze_position_buffer.items():
- generic_data = GazeAnalysis.GazePosition(tobii_data.gp[0] * tobii_segment_video.get_width(), tobii_data.gp[1] * tobii_segment_video.get_height())
+ generic_data = GazeFeatures.GazePosition(tobii_data.gp[0] * tobii_segment_video.get_width(), tobii_data.gp[1] * tobii_segment_video.get_height())
generic_ts_gaze_position_buffer[ts] = generic_data
print(f'dispersion_threshold = {args.dispersion_threshold}')
print(f'duration_threshold = {args.duration_threshold}')
- fixation_analyser = GazeAnalysis.DispersionBasedFixationAnalyser(generic_ts_gaze_position_buffer, args.dispersion_threshold, args.duration_threshold)
+ fixation_analyser = GazeFeatures.DispersionBasedFixationIdentifier(generic_ts_gaze_position_buffer, args.dispersion_threshold, args.duration_threshold)
print(f'{len(fixation_analyser.fixations)} fixations found')