diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/argaze/DataAnalysis/DictObject.py | 20 | ||||
-rw-r--r-- | src/argaze/DataAnalysis/README.md | 1 | ||||
-rw-r--r-- | src/argaze/DataAnalysis/__init__.py | 5 | ||||
-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.py | 6 | ||||
-rw-r--r-- | src/argaze/__init__.py | 3 | ||||
-rw-r--r-- | src/argaze/utils/analyse_tobii_segment_fixations.py | 9 |
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') |