From 0d7f4569e895ae3a08dfbaa33591302a5d966f66 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Mon, 29 Jan 2024 17:01:21 +0100 Subject: Updating logging documentation. Updating demo scripts. --- docs/user_guide/gaze_analysis_pipeline/logging.md | 118 +++++++++++---------- .../utils/demo_data/demo_aruco_markers_setup.json | 2 +- src/argaze/utils/demo_data/demo_frame_logger.py | 52 +++++++++ .../utils/demo_data/demo_gaze_analysis_setup.json | 2 +- src/argaze/utils/demo_data/demo_layer_logger.py | 5 +- src/argaze/utils/demo_data/frame_logger.py | 52 --------- 6 files changed, 117 insertions(+), 114 deletions(-) create mode 100644 src/argaze/utils/demo_data/demo_frame_logger.py delete mode 100644 src/argaze/utils/demo_data/frame_logger.py diff --git a/docs/user_guide/gaze_analysis_pipeline/logging.md b/docs/user_guide/gaze_analysis_pipeline/logging.md index db3cf9a..c5fe15a 100644 --- a/docs/user_guide/gaze_analysis_pipeline/logging.md +++ b/docs/user_guide/gaze_analysis_pipeline/logging.md @@ -1,97 +1,103 @@ Log gaze analysis ================= -[ArFrame](../../argaze.md/#argaze.ArFeatures.ArFrame) and [ArLayer](../../argaze.md/#argaze.ArFeatures.ArLayer) logging are optional pipeline steps. They are executed at each new scan path step to update logs. +[ArFrame](../../argaze.md/#argaze.ArFeatures.ArFrame) and [ArLayer](../../argaze.md/#argaze.ArFeatures.ArLayer) analysis can be logged by registering observers to their **look** method. -## Enable ArFrame and ArLayer logging +## Enable ArFrame and ArLayer analysis logging -[ArFrame](../../argaze.md/#argaze.ArFeatures.ArFrame) and [ArLayer](../../argaze.md/#argaze.ArFeatures.ArLayer) have a log attribute to enable analysis logging. +[ArFrame](../../argaze.md/#argaze.ArFeatures.ArFrame) and [ArLayer](../../argaze.md/#argaze.ArFeatures.ArLayer) have an observers attribute to enable pipeline execution logging. -Here is an extract from the JSON ArFrame configuration file where logging is enabled for the [ArFrame](../../argaze.md/#argaze.ArFeatures.ArFrame) and for one [ArLayer](../../argaze.md/#argaze.ArFeatures.ArLayer): +Here is an extract from the JSON ArFrame configuration file where logging is enabled for the [ArFrame](../../argaze.md/#argaze.ArFeatures.ArFrame) and for one [ArLayer](../../argaze.md/#argaze.ArFeatures.ArLayer) by pointing to dedicated Python files: ```json { "name": "My FullHD screen", "size": [1920, 1080], - "log": true, + "observers": "my_frame_logger.py", ... "layers": { "MyLayer": { - "log": true, + "observers": "my_layer_logger.py", ... } } } ``` -Then, here is how to access to logs dictionaries: +!!! note + [ArFrame](../../argaze.md/#argaze.ArFeatures.ArFrame) and its [ArLayers](../../argaze.md/#argaze.ArFeatures.ArLayer) automatically notify **look** method observers after each call. + +Here is *my_frame_logger.py* file: ```python -# Assuming that all timestamped gaze positions have been processed by ArFrame.look method -... +from argaze import DataFeatures +from argaze.utils import UtilsFeatures -# Access to ArFame scan path analysis logs -for analysis, log in ar_frame.logs.items(): - - # Do something with scan path analysis log - ... +class ScanPathAnalysisLogger(DataFeatures.PipelineStepObserver, UtilsFeatures.FileWriter): -# Access to ArLayers AOI scan path analysis logs -for layer_name, layer in ar_frame.layers.items(): + def on_look(self, timestamp, frame): + """Log scan path metrics""" - for analysis, log in layer.logs.items(): + if frame.analysis_available: - # Do something with AOI scan path analysis log - ... + log = ( + timestamp, + frame.scan_path_analyzers['argaze.GazeAnalysis.Basic'].path_duration, + frame.scan_path_analyzers['argaze.GazeAnalysis.Basic'].steps_number + ) + + # Write to file + self.write(log) + +# Export logger as observer +__observers__ = { + "Scan path analysis logger": ScanPathAnalysisLogger(path="./scan_path_metrics.csv", header="Timestamp (ms), Duration (ms), Steps number") + } ``` -!!! note - [ArFrame](../../argaze.md/#argaze.ArFeatures.ArFrame) and its [ArLayers](../../argaze.md/#argaze.ArFeatures.ArLayer) logging are automatically done each time the [ArFrame.look](../../argaze.md/#argaze.ArFeatures.ArFrame.look) method is called. - [ArFrame](../../argaze.md/#argaze.ArFeatures.ArFrame) logging records each scan path analysis into a dedicated timestamped data buffer each time a new scan step happens. - [ArLayer](../../argaze.md/#argaze.ArFeatures.ArLayer) logging records each AOI scan path analysis into a dedicated timestamped data buffer each time a new AOI scan step happens. +Assuming that [ArGaze.GazeAnalysis.Basic](../../argaze.md/#argaze.GazeAnalysis.Basic) scan path analysis module is enabled for 'My FullHD screen' ArFrame, a ***scan_path_metrics.csv*** file would be created: -## Export gaze analysis logs to CSV file +|Timestamp (ms)|Duration (ms)|Steps number| +|:-------------|:------------|:-----------| +|3460 |1750 |2 | +|4291 |2623 |3 | +|4769 |3107 |4 | +|6077 |4411 |5 | +|6433 |4760 |6 | +|7719 |6050 |7 | +|... |... |... | -It is possible to convert gaze analysis log as [Pandas DataFrame](https://pandas.pydata.org/docs/getting_started/intro_tutorials/01_table_oriented.html#min-tut-01-tableoriented) object which can be writen into a CSV file. +Here is *my_layer_logger.py* file: ```python -import pandas +from argaze import DataFeatures +from argaze.utils import UtilsFeatures -# Assuming that all timestamped gaze positions have been processed by ArFrame.look method -... +class AOIScanPathAnalysisLogger(DataFeatures.PipelineStepObserver, UtilsFeatures.FileWriter): -# Export ArFame scan path analysis logs -for analysis, log in ar_frame.logs.items(): - - log.as_dataframe().to_csv(f'./{ar_frame.name}_{analysis}.csv') + def on_look(self, timestamp, layer): + """Log aoi scan path metrics""" -# Export ArLayers layers AOI scan path analysis logs -for layer_name, layer in ar_frame.layers.items(): + if layer.analysis_available: - for analysis, log in layer.logs.items(): + log = ( + timestamp, + layer.aoi_scan_path_analyzers['argaze.GazeAnalysis.NGram'].ngrams_count + ) - log.as_dataframe().to_csv(f'./{ar_frame.name}_{layer_name}_{analysis}.csv') + # Write to file + self.write(log) +# Export logger as observer +__observers__ = { + "AOI Scan path analysis logger": AOIScanPathAnalysisLogger(path="./aoi_scan_path_metrics.csv", header="Timestamp (ms), NGram counts") + } ``` -Assuming that [ArGaze.GazeAnalysis.Basic](../../argaze.md/#argaze.GazeAnalysis.Basic) scan path analysis module is enabled for 'My FullHD screen' ArFrame, a ***My FullHD screen_argaze.GazeAnalysis.Basic.csv*** file would be created: - -|timestamped|path_duration|step_fixation_durations_average|steps_number| -|:----------|:------------|:------------------------------|:-----------| -|3460 |1750 |563.0 |2 | -|4291 |2623 |652.0 |3 | -|4769 |3107 |544.0 |4 | -|6077 |4411 |652.8 |5 | -|6433 |4760 |595.1 |6 | -|7719 |6050 |652.8 |7 | -|... |... |... |... | - -Assuming that [ArGaze.GazeAnalysis.NGram](../../argaze.md/#argaze.GazeAnalysis.NGram) AOI scan path analysis module is enabled for 'MyLayer' ArLayer, a ***My FullHD screen_MyLayer_argaze.GazeAnalysis.NGram.csv*** file would be created: - -|timestamped|ngrams_count| -|:----------|:-----------| -|5687 |"{3: {}, 4: {}, 5: {}}"| -|6208 |"{3: {('LeftPanel', 'GeoSector', 'CircularWidget'): 1}, 4: {}, 5: {}}"| -|... |... | - +Assuming that [ArGaze.GazeAnalysis.NGram](../../argaze.md/#argaze.GazeAnalysis.NGram) AOI scan path analysis module is enabled for 'MyLayer' ArLayer, a ***aoi_scan_path_metrics.csv*** file would be created: +|Timestamp (ms)|NGram counts| +|:-------------|:-----------| +|5687 |"{3: {}, 4: {}, 5: {}}"| +|6208 |"{3: {('LeftPanel', 'GeoSector', 'CircularWidget'): 1}, 4: {}, 5: {}}"| +|... |... | diff --git a/src/argaze/utils/demo_data/demo_aruco_markers_setup.json b/src/argaze/utils/demo_data/demo_aruco_markers_setup.json index 53f0bc3..37a30d4 100644 --- a/src/argaze/utils/demo_data/demo_aruco_markers_setup.json +++ b/src/argaze/utils/demo_data/demo_aruco_markers_setup.json @@ -137,7 +137,7 @@ "size": 2 } }, - "observers": "frame_logger.py" + "observers": "demo_frame_logger.py" } }, "angle_tolerance": 15.0, diff --git a/src/argaze/utils/demo_data/demo_frame_logger.py b/src/argaze/utils/demo_data/demo_frame_logger.py new file mode 100644 index 0000000..1c8046a --- /dev/null +++ b/src/argaze/utils/demo_data/demo_frame_logger.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python + +""" """ + +__author__ = "Théo de la Hogue" +__credits__ = [] +__copyright__ = "Copyright 2023, Ecole Nationale de l'Aviation Civile (ENAC)" +__license__ = "BSD" + +from argaze import DataFeatures, GazeFeatures +from argaze.utils import UtilsFeatures + +class FixationLogger(DataFeatures.PipelineStepObserver, UtilsFeatures.FileWriter): + + def on_look(self, timestamp, frame): + """Log fixations""" + + # Log fixations + if GazeFeatures.is_fixation(frame.last_gaze_movement) and frame.last_gaze_movement.finished: + + log = ( + timestamp, + frame.last_gaze_movement.focus, + frame.last_gaze_movement.duration, + frame.layers['demo_layer'].last_looked_aoi_name + ) + + self.write(log) + +class ScanPathAnalysisLogger(DataFeatures.PipelineStepObserver, UtilsFeatures.FileWriter): + + def on_look(self, timestamp, frame): + """Log scan path metrics""" + + if frame.analysis_available: + + log = ( + timestamp, + frame.scan_path_analyzers['argaze.GazeAnalysis.Basic'].path_duration, + frame.scan_path_analyzers['argaze.GazeAnalysis.Basic'].steps_number, + frame.scan_path_analyzers['argaze.GazeAnalysis.KCoefficient'].K, + frame.scan_path_analyzers['argaze.GazeAnalysis.NearestNeighborIndex'].nearest_neighbor_index, + frame.scan_path_analyzers['argaze.GazeAnalysis.ExploreExploitRatio'].explore_exploit_ratio + ) + + self.write(log) + +# Export loggers instances to register them as pipeline step object observers +__observers__ = { + "Fixation logger": FixationLogger(path="_export/logs/fixations.csv", header="Timestamp (ms), Focus (px), Duration (ms), AOI"), + "Scan path analysis logger": ScanPathAnalysisLogger(path="_export/logs/scan_path_metrics.csv", header="Timestamp (ms), Duration (ms), Step, K, NNI, XXR") + } \ No newline at end of file diff --git a/src/argaze/utils/demo_data/demo_gaze_analysis_setup.json b/src/argaze/utils/demo_data/demo_gaze_analysis_setup.json index a155693..1f6a5f5 100644 --- a/src/argaze/utils/demo_data/demo_gaze_analysis_setup.json +++ b/src/argaze/utils/demo_data/demo_gaze_analysis_setup.json @@ -109,5 +109,5 @@ "size": 2 } }, - "observers": "frame_logger.py" + "observers": "demo_frame_logger.py" } \ No newline at end of file diff --git a/src/argaze/utils/demo_data/demo_layer_logger.py b/src/argaze/utils/demo_data/demo_layer_logger.py index b9db10b..de1aea2 100644 --- a/src/argaze/utils/demo_data/demo_layer_logger.py +++ b/src/argaze/utils/demo_data/demo_layer_logger.py @@ -7,10 +7,7 @@ __credits__ = [] __copyright__ = "Copyright 2023, Ecole Nationale de l'Aviation Civile (ENAC)" __license__ = "BSD" -from argaze import ArFeatures, GazeFeatures -from argaze.utils import UtilsFeatures - -from argaze import ArFeatures, GazeFeatures, DataFeatures +from argaze import DataFeatures from argaze.utils import UtilsFeatures class AOIScanPathAnalysisLogger(DataFeatures.PipelineStepObserver, UtilsFeatures.FileWriter): diff --git a/src/argaze/utils/demo_data/frame_logger.py b/src/argaze/utils/demo_data/frame_logger.py deleted file mode 100644 index b6c0b00..0000000 --- a/src/argaze/utils/demo_data/frame_logger.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python - -""" """ - -__author__ = "Théo de la Hogue" -__credits__ = [] -__copyright__ = "Copyright 2023, Ecole Nationale de l'Aviation Civile (ENAC)" -__license__ = "BSD" - -from argaze import ArFeatures, GazeFeatures, DataFeatures -from argaze.utils import UtilsFeatures - -class FixationLogger(DataFeatures.PipelineStepObserver, UtilsFeatures.FileWriter): - - def on_look(self, timestamp, frame): - """Log fixations""" - - # Log fixations - if GazeFeatures.is_fixation(frame.last_gaze_movement) and frame.last_gaze_movement.finished: - - log = ( - timestamp, - frame.last_gaze_movement.focus, - frame.last_gaze_movement.duration, - frame.layers['demo_layer'].last_looked_aoi_name - ) - - self.write(log) - -class ScanPathAnalysisLogger(DataFeatures.PipelineStepObserver, UtilsFeatures.FileWriter): - - def on_look(self, timestamp, frame): - """Log scan path metrics""" - - if frame.analysis_available: - - log = ( - timestamp, - frame.scan_path_analyzers['argaze.GazeAnalysis.Basic'].path_duration, - frame.scan_path_analyzers['argaze.GazeAnalysis.Basic'].steps_number, - frame.scan_path_analyzers['argaze.GazeAnalysis.KCoefficient'].K, - frame.scan_path_analyzers['argaze.GazeAnalysis.NearestNeighborIndex'].nearest_neighbor_index, - frame.scan_path_analyzers['argaze.GazeAnalysis.ExploreExploitRatio'].explore_exploit_ratio - ) - - self.write(log) - -# Export loggers instances to register them as pipeline step object observers -__observers__ = { - "Fixation logger": FixationLogger(path="_export/logs/fixations.csv", header="Timestamp (ms), Focus (px), Duration (ms), AOI"), - "Scan path analysis logger": ScanPathAnalysisLogger(path="_export/logs/scan_path_metrics.csv", header="Timestamp (ms), Duration (ms), Step, K, NNI, XXR") - } \ No newline at end of file -- cgit v1.1