From 3067eafea0b4959075f68b9d3f66384474208d7e Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Mon, 22 May 2023 16:15:23 +0200 Subject: Adding new LempelZiv complexity metric. --- setup.py | 2 +- src/argaze/GazeAnalysis/LempelZivComplexity.py | 55 ++++++++++++++++++++++++++ src/argaze/GazeAnalysis/__init__.py | 2 +- src/argaze/utils/demo_gaze_features_run.py | 31 +++++++++++++-- 4 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 src/argaze/GazeAnalysis/LempelZivComplexity.py diff --git a/setup.py b/setup.py index eb81438..5c9973c 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ setup( packages=find_packages(where='src'), python_requires='>=3.11', - install_requires=['opencv-python>=4.7.0', 'opencv-contrib-python>=4.7.0', 'numpy', 'pandas', 'matplotlib', 'shapely'], + install_requires=['opencv-python>=4.7.0', 'opencv-contrib-python>=4.7.0', 'numpy', 'pandas', 'matplotlib', 'shapely', 'lempel_ziv_complexity'], project_urls={ 'Bug Reports': 'https://git.recherche.enac.fr/projects/argaze/issues', diff --git a/src/argaze/GazeAnalysis/LempelZivComplexity.py b/src/argaze/GazeAnalysis/LempelZivComplexity.py new file mode 100644 index 0000000..b8d14ca --- /dev/null +++ b/src/argaze/GazeAnalysis/LempelZivComplexity.py @@ -0,0 +1,55 @@ +#!/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 + +from lempel_ziv_complexity import lempel_ziv_complexity + +@dataclass +class AOIScanPathAnalyzer(GazeFeatures.AOIScanPathAnalyzer): + """Implementation of Lempel ziv complexity algorithm as described in Lounis paper. + """ + + def __post_init__(self): + + pass + + def analyze(self, aoi_scan_path: GazeFeatures.AOIScanPathType) -> Any: + """Analyze aoi scan.""" + + assert(len(aoi_scan_path) > 1) + + self.__index = ord('A') + aoi_letter = {} + + def __get_letter(aoi): + + try : + + return aoi_letter[aoi_scan_step.aoi] + + except KeyError: + + letter = chr(self.__index) + aoi_letter[aoi_scan_step.aoi] = letter + self.__index += 1 + return letter + + # Convet each AOI name into a single char + sequence = '' + for aoi_scan_step in aoi_scan_path: + + sequence += __get_letter(aoi_scan_step.aoi) + + print(sequence) + + return lempel_ziv_complexity(sequence) diff --git a/src/argaze/GazeAnalysis/__init__.py b/src/argaze/GazeAnalysis/__init__.py index a980ed3..0d5cd2c 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'] \ No newline at end of file +__all__ = ['DispersionThresholdIdentification', 'VelocityThresholdIdentification', 'TransitionMatrix', 'CoefficientK', 'LempelZivComplexity'] \ 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 5ab3ed0..3326bc8 100644 --- a/src/argaze/utils/demo_gaze_features_run.py +++ b/src/argaze/utils/demo_gaze_features_run.py @@ -89,13 +89,15 @@ def main(): raw_cK_analyzer = CoefficientK.ScanPathAnalyzer() raw_cK_analysis = 0 - aoi_cK_analyzer = CoefficientK.AOIScanPathAnalyzer() aoi_cK_analysis = 0 - ck_mode = 'raw' enable_ck_analysis = False + lzc_analyzer = LempelZivComplexity.AOIScanPathAnalyzer() + lzc_analysis = 0 + enable_lzc_analysis = False + gaze_movement_lock = threading.Lock() # Init timestamp @@ -113,6 +115,7 @@ def main(): nonlocal tm_density nonlocal raw_cK_analysis nonlocal aoi_cK_analysis + nonlocal lzc_analysis # Edit millisecond timestamp data_ts = int((time.time() - start_ts) * 1e3) @@ -189,6 +192,10 @@ def main(): aoi_cK_analysis = aoi_cK_analyzer.analyze(aoi_scan_path) + if enable_lzc_analysis: + + lzc_analysis = lzc_analyzer.analyze(aoi_scan_path) + except GazeFeatures.AOIScanStepError as e: print(f'Error on {e.aoi} step:', e) @@ -254,6 +261,12 @@ def main(): display_hide = 'hide' if enable_ck_analysis else 'display' cv2.putText(aoi_matrix, f'coefficient K: {on_off} (Press \'k\' key to {display_hide})', (20, 160), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255) if enable_ck_analysis else (255, 255, 255), 1, cv2.LINE_AA) + # Write LZC help + on_off = 'on' if enable_lzc_analysis else 'off' + display_hide = 'hide' if enable_lzc_analysis else 'display' + cv2.putText(aoi_matrix, f'Lempel-Ziv complexity: {on_off} (Press \'z\' key to {display_hide})', (20, 200), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255) if enable_lzc_analysis else (255, 255, 255), 1, cv2.LINE_AA) + + # Check fixation identification if gaze_movement_identifier[identification_mode].current_fixation != None: @@ -337,6 +350,11 @@ def main(): cv2.putText(aoi_matrix, f'AOI: Focal attention', (20, window_size[1]-80), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 0), 1, cv2.LINE_AA) + # Write LZC + if enable_lzc_analysis: + + cv2.putText(aoi_matrix, f'Lempel-Ziv complexity: {lzc_analysis}', (20, window_size[1]-200), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 1, cv2.LINE_AA) + # Unlock gaze movement identification gaze_movement_lock.release() @@ -345,8 +363,8 @@ def main(): key_pressed = cv2.waitKey(10) - #if key_pressed != -1: - # print(key_pressed) + if key_pressed != -1: + print(key_pressed) # Switch identification mode with 'm' key if key_pressed == 109: @@ -378,6 +396,11 @@ def main(): enable_tm_analysis = not enable_tm_analysis + # Enable LZC analysis with 'z' key + if key_pressed == 122: + + enable_lzc_analysis = not enable_lzc_analysis + # Stop calibration by pressing 'Esc' key if cv2.waitKey(10) == 27: break -- cgit v1.1