aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/argaze/GazeAnalysis/Entropy.py58
-rw-r--r--src/argaze/GazeAnalysis/__init__.py2
-rw-r--r--src/argaze/utils/demo_gaze_features_run.py30
3 files changed, 89 insertions, 1 deletions
diff --git a/src/argaze/GazeAnalysis/Entropy.py b/src/argaze/GazeAnalysis/Entropy.py
new file mode 100644
index 0000000..a185852
--- /dev/null
+++ b/src/argaze/GazeAnalysis/Entropy.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+
+""" """
+
+__author__ = "Théo de la Hogue"
+__credits__ = []
+__copyright__ = "Copyright 2023, Ecole Nationale de l'Aviation Civile (ENAC)"
+__license__ = "BSD"
+
+from typing import TypeVar, Tuple, Any
+from dataclasses import dataclass, field
+
+from argaze import GazeFeatures
+
+import pandas
+import numpy
+
+@dataclass
+class AOIScanPathAnalyzer(GazeFeatures.AOIScanPathAnalyzer):
+ """Implementation of entropy algorithm as described in Krejtz et al., 2014
+ """
+
+ def __post_init__(self):
+
+ pass
+
+ def analyze(self, aoi_scan_path: GazeFeatures.AOIScanPathType, transition_matrix_probability: pandas.DataFrame) -> Any:
+ """Analyze aoi scan."""
+
+ assert(len(aoi_scan_path) > 1)
+
+ # Count total number of fixations and how many fixations are there per aoi
+ scan_fixation_count, aoi_fixation_count = aoi_scan_path.fixation_count()
+
+ # Probability to have a fixation onto each aoi
+ stationary_probalities = {aoi: count/scan_fixation_count for aoi, count in aoi_fixation_count.items()}
+
+ # Stationary entropy
+ stationary_entropy = 0
+
+ for aoi, p in stationary_probalities.items():
+
+ stationary_entropy += p * numpy.log(p + 0.00000001)
+
+ stationary_entropy *= -1
+
+ # Transition entropy
+ transition_entropy = 0
+
+ aoi_sum = transition_matrix_probability.apply(lambda row: row.apply(lambda p: p * numpy.log(p + 0.00000001)).sum(), axis=1)
+
+ for aoi, s in aoi_sum.items():
+
+ transition_entropy += s * stationary_probalities[aoi]
+
+ transition_entropy *= -1
+
+ return stationary_entropy, transition_entropy
diff --git a/src/argaze/GazeAnalysis/__init__.py b/src/argaze/GazeAnalysis/__init__.py
index d82969d..cb18a08 100644
--- a/src/argaze/GazeAnalysis/__init__.py
+++ b/src/argaze/GazeAnalysis/__init__.py
@@ -2,4 +2,4 @@
.. include:: README.md
"""
__docformat__ = "restructuredtext"
-__all__ = ['DispersionThresholdIdentification', 'VelocityThresholdIdentification', 'TransitionMatrix', 'CoefficientK', 'LempelZivComplexity', 'NGram'] \ No newline at end of file
+__all__ = ['DispersionThresholdIdentification', 'VelocityThresholdIdentification', 'TransitionMatrix', 'CoefficientK', 'LempelZivComplexity', 'NGram', 'Entropy'] \ No newline at end of file
diff --git a/src/argaze/utils/demo_gaze_features_run.py b/src/argaze/utils/demo_gaze_features_run.py
index 6384205..736ffa9 100644
--- a/src/argaze/utils/demo_gaze_features_run.py
+++ b/src/argaze/utils/demo_gaze_features_run.py
@@ -102,6 +102,10 @@ def main():
ngram_analysis = {}
enable_ngram_analysis = False
+ entropy_analyzer = Entropy.AOIScanPathAnalyzer()
+ entropy_analysis = (-1, -1)
+ enable_entropy_analysis = False
+
gaze_movement_lock = threading.Lock()
# Init timestamp
@@ -121,6 +125,7 @@ def main():
nonlocal aoi_cK_analysis
nonlocal lzc_analysis
nonlocal ngram_analysis
+ nonlocal entropy_analysis
# Edit millisecond timestamp
data_ts = int((time.time() - start_ts) * 1e3)
@@ -205,6 +210,10 @@ def main():
ngram_analysis = ngram_analyzer.analyze(aoi_scan_path, 3)
+ if enable_entropy_analysis and enable_tm_analysis:
+
+ entropy_analysis = entropy_analyzer.analyze(aoi_scan_path, tm_probabilities)
+
except GazeFeatures.AOIScanStepError as e:
print(f'Error on {e.aoi} step:', e)
@@ -280,6 +289,11 @@ def main():
display_hide = 'hide' if enable_ngram_analysis else 'display'
cv2.putText(aoi_matrix, f'Tri-Gram: {on_off} (Press \'n\' key to {display_hide})', (20, 240), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255) if enable_ngram_analysis else (255, 255, 255), 1, cv2.LINE_AA)
+ # Write entropy help
+ on_off = 'on' if enable_entropy_analysis else 'off'
+ display_hide = 'hide' if enable_entropy_analysis else 'display'
+ cv2.putText(aoi_matrix, f'Entropy: {on_off} (Press \'e\' key to {display_hide})', (20, 280), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255) if enable_entropy_analysis else (255, 255, 255), 1, cv2.LINE_AA)
+
# Check fixation identification
if gaze_movement_identifier[identification_mode].current_fixation != None:
@@ -378,6 +392,11 @@ def main():
trigram = f'{ngram[0]}>{ngram[1]}>{ngram[2]}'
cv2.putText(aoi_matrix, f'{trigram}: {count}', (window_size[0]-700, start+(i*40)), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 1, cv2.LINE_AA)
+
+ # Write entropy
+ if enable_entropy_analysis:
+
+ cv2.putText(aoi_matrix, f'Entropy: {entropy_analysis}', (20, window_size[1]-240), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 1, cv2.LINE_AA)
# Unlock gaze movement identification
gaze_movement_lock.release()
@@ -430,6 +449,17 @@ def main():
enable_ngram_analysis = not enable_ngram_analysis
+ # Enable entropy analysis with 'e' key
+ if key_pressed == 101:
+
+ enable_entropy_analysis = not enable_entropy_analysis
+
+ # Transition matrix is needed
+ if enable_entropy_analysis:
+
+ enable_tm_analysis = True
+
+
# Stop calibration by pressing 'Esc' key
if cv2.waitKey(10) == 27:
break