From cb3883ccdae38609517bdac3b01947f6200b91ab Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Wed, 3 Apr 2024 14:44:02 +0200 Subject: Renaming log notion into record notion. --- docs/user_guide/gaze_analysis_pipeline/logging.md | 86 ++++++++-------- src/argaze/__main__.py | 7 +- src/argaze/utils/demo/gaze_analysis_pipeline.json | 16 +-- src/argaze/utils/demo/loggers.py | 116 ---------------------- src/argaze/utils/demo/recorders.py | 115 +++++++++++++++++++++ 5 files changed, 174 insertions(+), 166 deletions(-) delete mode 100644 src/argaze/utils/demo/loggers.py create mode 100644 src/argaze/utils/demo/recorders.py diff --git a/docs/user_guide/gaze_analysis_pipeline/logging.md b/docs/user_guide/gaze_analysis_pipeline/logging.md index 6ed497b..6ef3a85 100644 --- a/docs/user_guide/gaze_analysis_pipeline/logging.md +++ b/docs/user_guide/gaze_analysis_pipeline/logging.md @@ -1,30 +1,28 @@ -Log gaze analysis +Record gaze analysis ================= -[ArFrame](../../argaze.md/#argaze.ArFeatures.ArFrame) and [ArLayer](../../argaze.md/#argaze.ArFeatures.ArLayer) analysis can be logged by registering observers to their **look** method. +[ArFrame](../../argaze.md/#argaze.ArFeatures.ArFrame) and [ArLayer](../../argaze.md/#argaze.ArFeatures.ArLayer) analysis can be recorded by registering observers to their **look** method. ## Export gaze analysis to CSV file -[ArFrame](../../argaze.md/#argaze.ArFeatures.ArFrame) and [ArLayer](../../argaze.md/#argaze.ArFeatures.ArLayer) have an observers attribute to enable pipeline execution logging. +[ArFrame](../../argaze.md/#argaze.ArFeatures.ArFrame) and [ArLayer](../../argaze.md/#argaze.ArFeatures.ArLayer) have an *observers* attribute to enable pipeline execution recording. -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: +Here is an extract from the JSON ArFrame configuration file where recording is enabled for the [ArFrame](../../argaze.md/#argaze.ArFeatures.ArFrame) and for one [ArLayer](../../argaze.md/#argaze.ArFeatures.ArLayer) by loaded classes from Python files: ```json { "name": "My FullHD screen", "size": [1920, 1080], "observers": { - "my_frame_logger.ScanPathAnalysisLogger": { - "path": "./scan_path_metrics.csv", - "header": ["Timestamp (ms)", "Duration (ms)", "Steps number"] + "my_recorders.ScanPathAnalysisRecorder": { + "path": "./scan_path_metrics.csv" }, ... "layers": { "MyLayer": { "observers": { - "my_layer_logger.AOIScanPathAnalysisLogger": { - "path": "./aoi_scan_path_metrics.csv", - "header": ["Timestamp (ms)", "NGram counts"] + "my_recorders.AOIScanPathAnalysisRecorder": { + "path": "./aoi_scan_path_metrics.csv" } }, ... @@ -36,67 +34,73 @@ Here is an extract from the JSON ArFrame configuration file where logging is ena !!! 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: +Here is *my_recorders.py* file: ```python -from argaze import DataFeatures -from argaze.GazeAnalysis import Basic from argaze.utils import UtilsFeatures -class ScanPathAnalysisLogger(DataFeatures.PipelineStepObserver, UtilsFeatures.FileWriter): +class ScanPathAnalysisRecorder(UtilsFeatures.FileWriter): + + def __init__(self, **kwargs): + + # Init FileWriter + super().__init__(**kwargs) + + # Edit hearder line + self.header = "Timestamp (ms)", "Duration (ms)", "Steps number" def on_look(self, timestamp, ar_frame, exception): - """Log scan path metrics""" + """Record scan path metrics""" if ar_frame.is_analysis_available(): analysis = ar_frame.analysis() - log = ( + data = ( timestamp, - analysis[Basic.ScanPathAnalyzer].path_duration, - analysis[Basic.ScanPathAnalyzer].steps_number + analysis['argaze.GazeAnalysis.Basic.ScanPathAnalyzer'].path_duration, + analysis['argaze.GazeAnalysis.Basic.ScanPathAnalyzer'].steps_number ) # Write to file - self.write(log) -``` + self.write(data) -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: +class AOIScanPathAnalysisRecorder(UtilsFeatures.FileWriter): -|Timestamp (ms)|Duration (ms)|Steps number| -|:-------------|:------------|:-----------| -|3460 |1750 |2 | -|4291 |2623 |3 | -|4769 |3107 |4 | -|6077 |4411 |5 | -|6433 |4760 |6 | -|7719 |6050 |7 | -|... |... |... | + def __init__(self, **kwargs): -Here is *my_layer_logger.py* file: + # Init FileWriter + super().__init__(**kwargs) -```python -from argaze import DataFeatures -from argaze.GazeAnalysis import NGram -from argaze.utils import UtilsFeatures - -class AOIScanPathAnalysisLogger(DataFeatures.PipelineStepObserver, UtilsFeatures.FileWriter): + # Edit header line + self.header = "Timestamp (ms)", "NGram counts" def on_look(self, timestamp, ar_layer, exception): - """Log aoi scan path metrics.""" + """Record aoi scan path metrics.""" if ar_layer.is_analysis_available(): - log = ( + data = ( timestamp, - ar_layer.analysis[NGram.AOIScanPathAnalyzer].ngrams_count + ar_layer.analysis['argaze.GazeAnalysis.NGram.AOIScanPathAnalyzer'].ngrams_count ) # Write to file - self.write(log) + self.write(data) ``` +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: + +|Timestamp (ms)|Duration (ms)|Steps number| +|:-------------|:------------|:-----------| +|3460 |1750 |2 | +|4291 |2623 |3 | +|4769 |3107 |4 | +|6077 |4411 |5 | +|6433 |4760 |6 | +|7719 |6050 |7 | +|... |... |... | + 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| diff --git a/src/argaze/__main__.py b/src/argaze/__main__.py index 0682583..0184d69 100644 --- a/src/argaze/__main__.py +++ b/src/argaze/__main__.py @@ -21,7 +21,7 @@ import logging import contextlib from .DataFeatures import from_json -from .ArFeatures import ArCamera +from .ArFeatures import ArCamera, ArContext import cv2 @@ -38,6 +38,11 @@ logging.basicConfig(format = '%(levelname)s: %(message)s', level = logging.DEBUG # Load context from JSON file with from_json(args.context_file) as context: + # Loaded object must be a subclass of ArContext + if not issubclass(type(context), ArContext): + + raise TypeError('Loaded object is not a subclass of ArContext') + if args.verbose: print(context) diff --git a/src/argaze/utils/demo/gaze_analysis_pipeline.json b/src/argaze/utils/demo/gaze_analysis_pipeline.json index 0d1062b..291b3e1 100644 --- a/src/argaze/utils/demo/gaze_analysis_pipeline.json +++ b/src/argaze/utils/demo/gaze_analysis_pipeline.json @@ -49,8 +49,8 @@ "argaze.GazeAnalysis.Entropy.AOIScanPathAnalyzer":{} }, "observers": { - "loggers.AOIScanPathAnalysisLogger": { - "path": "_export/logs/aoi_scan_path_metrics.csv" + "recorders.AOIScanPathAnalysisRecorder": { + "path": "_export/records/aoi_scan_path_metrics.csv" } } } @@ -115,14 +115,14 @@ } }, "observers": { - "loggers.FixationLogger": { - "path": "_export/logs/fixations.csv" + "recorders.FixationRecorder": { + "path": "_export/records/fixations.csv" }, - "loggers.ScanPathAnalysisLogger": { - "path": "_export/logs/scan_path_metrics.csv" + "recorders.ScanPathAnalysisRecorder": { + "path": "_export/records/scan_path_metrics.csv" }, - "loggers.VideoRecorder": { - "path": "_export/logs/video.mp4", + "recorders.VideoRecorder": { + "path": "_export/records/video.mp4", "width": 1920, "height": 1080, "fps": 15 diff --git a/src/argaze/utils/demo/loggers.py b/src/argaze/utils/demo/loggers.py deleted file mode 100644 index 25b55d3..0000000 --- a/src/argaze/utils/demo/loggers.py +++ /dev/null @@ -1,116 +0,0 @@ -""" - -This program is free software: you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation, either version 3 of the License, or (at your option) any later -version. -This program is distributed in the hope that it will be useful, but WITHOUT -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -You should have received a copy of the GNU General Public License along with -this program. If not, see . -""" - -__author__ = "Théo de la Hogue" -__credits__ = [] -__copyright__ = "Copyright 2023, Ecole Nationale de l'Aviation Civile (ENAC)" -__license__ = "GPLv3" - -import logging - -from argaze import DataFeatures, GazeFeatures -from argaze.GazeAnalysis import * -from argaze.utils import UtilsFeatures - -class FixationLogger(UtilsFeatures.FileWriter): - - def __init__(self, **kwargs): - - super().__init__(**kwargs) - - self.header = "Timestamp (ms)", "Focus (px)", "Duration (ms)", "AOI" - - logging.info('%s writes into %s', type(self).__name__, self.path) - - def on_look(self, timestamp, frame, exception): - """Log frame fixations.""" - - # Log fixations - if GazeFeatures.is_fixation(frame.last_gaze_movement()) and frame.last_gaze_movement().is_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(UtilsFeatures.FileWriter): - - def __init__(self, **kwargs): - - super().__init__(**kwargs) - - self.header = "Timestamp (ms)", "Duration (ms)", "Step", "K", "NNI", "XXR" - - logging.info('%s writes into %s', type(self).__name__, self.path) - - def on_look(self, timestamp, frame, exception): - """Log frame scan path metrics.""" - - if frame.is_analysis_available(): - - analysis = frame.analysis() - - log = ( - timestamp, - analysis[Basic.ScanPathAnalyzer].path_duration, - analysis[Basic.ScanPathAnalyzer].steps_number, - analysis[KCoefficient.ScanPathAnalyzer].K, - analysis[NearestNeighborIndex.ScanPathAnalyzer].nearest_neighbor_index, - analysis[ExploreExploitRatio.ScanPathAnalyzer].explore_exploit_ratio - ) - - self.write(log) - -class VideoRecorder(UtilsFeatures.VideoWriter): - - def __init__(self, **kwargs): - - super().__init__(**kwargs) - - logging.info('%s writes into %s', type(self).__name__, self.path) - - def on_look(self, timestamp, frame, exception): - """Write frame image.""" - - self.write(frame.image()) - -class AOIScanPathAnalysisLogger(UtilsFeatures.FileWriter): - - def __init__(self, **kwargs): - - super().__init__(**kwargs) - - self.header = "Timestamp (ms)", "Duration (ms)", "Step", "K", "LZC" - - logging.info('%s writes into %s', type(self).__name__, self.path) - - def on_look(self, timestamp, layer, exception): - """Log layer aoi scan path metrics""" - - if layer.is_analysis_available(): - - analysis = layer.analysis() - - log = ( - timestamp, - analysis[Basic.AOIScanPathAnalyzer].path_duration, - analysis[Basic.AOIScanPathAnalyzer].steps_number, - analysis[KCoefficient.AOIScanPathAnalyzer].K, - analysis[LempelZivComplexity.AOIScanPathAnalyzer].lempel_ziv_complexity - ) - - self.write(log) diff --git a/src/argaze/utils/demo/recorders.py b/src/argaze/utils/demo/recorders.py new file mode 100644 index 0000000..75bca70 --- /dev/null +++ b/src/argaze/utils/demo/recorders.py @@ -0,0 +1,115 @@ +""" + +This program is free software: you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation, either version 3 of the License, or (at your option) any later +version. +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +You should have received a copy of the GNU General Public License along with +this program. If not, see . +""" + +__author__ = "Théo de la Hogue" +__credits__ = [] +__copyright__ = "Copyright 2023, Ecole Nationale de l'Aviation Civile (ENAC)" +__license__ = "GPLv3" + +import logging + +from argaze import DataFeatures, GazeFeatures +from argaze.utils import UtilsFeatures + +class FixationRecorder(UtilsFeatures.FileWriter): + + def __init__(self, **kwargs): + + super().__init__(**kwargs) + + self.header = "Timestamp (ms)", "Focus (px)", "Duration (ms)", "AOI" + + logging.info('%s writes into %s', type(self).__name__, self.path) + + def on_look(self, timestamp, frame, exception): + """Log frame fixations.""" + + # Log fixations + if GazeFeatures.is_fixation(frame.last_gaze_movement()) and frame.last_gaze_movement().is_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 ScanPathAnalysisRecorder(UtilsFeatures.FileWriter): + + def __init__(self, **kwargs): + + super().__init__(**kwargs) + + self.header = "Timestamp (ms)", "Duration (ms)", "Step", "K", "NNI", "XXR" + + logging.info('%s writes into %s', type(self).__name__, self.path) + + def on_look(self, timestamp, frame, exception): + """Log frame scan path metrics.""" + + if frame.is_analysis_available(): + + analysis = frame.analysis() + + log = ( + timestamp, + analysis['argaze.GazeAnalysis.Basic.ScanPathAnalyzer'].path_duration, + analysis['argaze.GazeAnalysis.Basic.ScanPathAnalyzer'].steps_number, + analysis['argaze.GazeAnalysis.KCoefficient.ScanPathAnalyzer'].K, + analysis['argaze.GazeAnalysis.NearestNeighborIndex.ScanPathAnalyzer'].nearest_neighbor_index, + analysis['argaze.GazeAnalysis.ExploreExploitRatio.ScanPathAnalyzer'].explore_exploit_ratio + ) + + self.write(log) + +class VideoRecorder(UtilsFeatures.VideoWriter): + + def __init__(self, **kwargs): + + super().__init__(**kwargs) + + logging.info('%s writes into %s', type(self).__name__, self.path) + + def on_look(self, timestamp, frame, exception): + """Write frame image.""" + + self.write(frame.image()) + +class AOIScanPathAnalysisRecorder(UtilsFeatures.FileWriter): + + def __init__(self, **kwargs): + + super().__init__(**kwargs) + + self.header = "Timestamp (ms)", "Duration (ms)", "Step", "K", "LZC" + + logging.info('%s writes into %s', type(self).__name__, self.path) + + def on_look(self, timestamp, layer, exception): + """Log layer aoi scan path metrics""" + + if layer.is_analysis_available(): + + analysis = layer.analysis() + + log = ( + timestamp, + analysis['argaze.GazeAnalysis.Basic.AOIScanPathAnalyzer'].path_duration, + analysis['argaze.GazeAnalysis.Basic.AOIScanPathAnalyzer'].steps_number, + analysis['argaze.GazeAnalysis.KCoefficient.AOIScanPathAnalyzer'].K, + analysis['argaze.GazeAnalysis.LempelZivComplexity.AOIScanPathAnalyzer'].lempel_ziv_complexity + ) + + self.write(log) -- cgit v1.1