From f93d3e2b011fe1c1de93d0094ca45584ddd49936 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Mon, 28 Aug 2023 15:05:01 +0200 Subject: Improving gaze analysis pipeline documentation. --- .../advanced_topics/module_loading.md | 45 ++++++++++ .../advanced_topics/plugin_loading.md | 46 ---------- .../ar_frame_configuration_and_execution.md | 29 ++----- .../ar_layer_configuration_and_execution.md | 23 +++-- .../gaze_analysis_logging.md | 97 ++++++++++++++++++++++ .../heatmap_visualisation.md | 71 ++++++++++++++++ .../gaze_analysis_pipeline/introduction.md | 5 +- .../timestamped_gaze_positions_edition.md | 9 +- mkdocs.yml | 4 +- 9 files changed, 248 insertions(+), 81 deletions(-) create mode 100644 docs/user_guide/gaze_analysis_pipeline/advanced_topics/module_loading.md delete mode 100644 docs/user_guide/gaze_analysis_pipeline/advanced_topics/plugin_loading.md create mode 100644 docs/user_guide/gaze_analysis_pipeline/gaze_analysis_logging.md create mode 100644 docs/user_guide/gaze_analysis_pipeline/heatmap_visualisation.md diff --git a/docs/user_guide/gaze_analysis_pipeline/advanced_topics/module_loading.md b/docs/user_guide/gaze_analysis_pipeline/advanced_topics/module_loading.md new file mode 100644 index 0000000..0b45368 --- /dev/null +++ b/docs/user_guide/gaze_analysis_pipeline/advanced_topics/module_loading.md @@ -0,0 +1,45 @@ +Loading modules from another package +==================================== + +It possible to load GazeMovementIdentifier, ScanPathAnalyzer or AOIScanPathAnalyzer modules from another [python package](https://docs.python.org/3/tutorial/modules.html#packages). + +To do so, simply prepend the package where to find the module into the JSON configuration file: + +``` +{ + ... + "gaze_movement_identifier": { + "my_package.MyGazeMovementIdentifierAlgorithm": { + "specific_plugin_parameter": 0 + } + }, + ... + "scan_path_analyzers": { + "my_package.MyScanPathAnalyzerAlgorithm": { + "specific_plugin_parameter": 0 + } + } + ... + "aoi_scan_path_analyzers": { + "my_package.MyAOIScanPathAnalyzerAlgorithm": { + "specific_plugin_parameter": 0 + } + } +} +``` + +Then, load your package from the python script where the ArFrame is created. + +```python +from argaze import ArFeatures + +# Import your own package +import my_package + +# Load ArFrame +ar_frame = ArFeatures.ArFrame.from_json('./configuration.json') + +# Print ArFrame attributes +for module, scan_path_analyzer in ar_frame.scan_path_analyzers.items(): + print('scan path analyzer type:', type(scan_path_analyzer)) +``` diff --git a/docs/user_guide/gaze_analysis_pipeline/advanced_topics/plugin_loading.md b/docs/user_guide/gaze_analysis_pipeline/advanced_topics/plugin_loading.md deleted file mode 100644 index 21e1f8b..0000000 --- a/docs/user_guide/gaze_analysis_pipeline/advanced_topics/plugin_loading.md +++ /dev/null @@ -1,46 +0,0 @@ -Loading plugins from another package -==================================== - -It possible to load GazeMovementIdentifier, ScanPathAnalyzer or AOIScanPathAnalyzer plugins from another [python package](https://docs.python.org/3/tutorial/modules.html#packages). - -To do so, simply prepend the package where to find the plugin into the JSON configuration file: - -``` -{ - ... - "gaze_movement_identifier": { - "my_package.MyGazeMovementIdentifierMethod": { - "specific_plugin_parameter": 0 - } - }, - ... - "scan_path_analyzers": { - "my_package.MyScanPathAnalyzerAlgorithm": { - "specific_plugin_parameter": 0 - } - } - ... - "aoi_scan_path_analyzers": { - "my_package.MyAOIScanPathAnalyzerAlgorithm": { - "specific_plugin_parameter": 0 - } - } -} -``` - -Then, load your package from the python script where the ArFrame is created. - -```python -from argaze import ArFeatures - -# Import your own package -import my_package - -# Load ArFrame -ar_frame = ArFeatures.ArFrame.from_json('./configuration.json') - -# Print ArFrame attributes - -for name, scan_path_analyzer in ar_frame.scan_path_analyzers.items(): - print('scan path analyzer type:', type(scan_path_analyzer)) -``` diff --git a/docs/user_guide/gaze_analysis_pipeline/ar_frame_configuration_and_execution.md b/docs/user_guide/gaze_analysis_pipeline/ar_frame_configuration_and_execution.md index f1264c7..3e18f3e 100644 --- a/docs/user_guide/gaze_analysis_pipeline/ar_frame_configuration_and_execution.md +++ b/docs/user_guide/gaze_analysis_pipeline/ar_frame_configuration_and_execution.md @@ -29,11 +29,6 @@ Here is a simple JSON ArFrame configuration file example: "ExploitExploreRatio": { "short_fixation_duration_threshold": 0 } - }, - "heatmap": { - "size": [320, 180], - "sigma": 0.025, - "buffer": 0 } } ``` @@ -54,8 +49,6 @@ print("scan path:", ar_frame.scan_path) for module, analyzer in ar_frame.scan_path_analyzers.items(): print('scan path analyzer module:', module) - -print("heatmap:", ar_frame.heatmap) ``` Finally, here is what the program writes in console: @@ -67,7 +60,6 @@ gaze movement identifier type: AOI scan path: [] AOI scan path analyzer module: argaze.GazeAnalysis.Basic AOI scan path analyzer module: argaze.GazeAnalysis.TransitionMatrix @@ -91,6 +96,10 @@ Now, let's understand the meaning of each JSON entry. The name of the [ArLayer](../../../argaze/#argaze.ArFeatures.ArLayer). Basically useful for visualisation purpose. +### AOI Color + +The color of [ArLayer](../../../argaze/#argaze.ArFeatures.ArLayer)'s AOI. Basically useful for visualisation purpose. + ### AOI Scene The [AOIScene](../../../argaze/#argaze.AreaOfInterest.AOIFeatures.AOIScene) defines a set of 2D [AreaOfInterest](../../../argaze/#argaze.AreaOfInterest.AOIFeatures.AreaOfInterest) registered by name. @@ -103,9 +112,9 @@ The first [ArLayer](../../../argaze/#argaze.ArFeatures.ArLayer) pipeline step ai ![AOI Matcher](../../img/ar_layer_aoi_matcher.png) -The matching method can be selected by instantiating a particular [AOIMatcher](../../../argaze/#argaze.GazeFeatures.AOIMatcher) from the [argaze.GazeAnalysis](../../../argaze/#argaze.GazeAnalysis) submodule or [another python package](../advanced_topics/plugin_loading). +The matching algorithm can be selected by instantiating a particular [AOIMatcher](../../../argaze/#argaze.GazeFeatures.AOIMatcher) from the [argaze.GazeAnalysis](../../../argaze/#argaze.GazeAnalysis) submodule or [from another python package](../advanced_topics/module_loading). -In the example file, the choosen matching method is the [Deviation Circle Coverage](../../../argaze/#argaze.GazeAnalysis.DeviationCircleCoverage) which has one specific *coverage_threshold* attribute. +In the example file, the choosen matching algorithm is the [Deviation Circle Coverage](../../../argaze/#argaze.GazeAnalysis.DeviationCircleCoverage) which has one specific *coverage_threshold* attribute. !!! warning JSON *aoi_matcher* entry is mandatory. Otherwise, the AOIScanPath and AOIScanPathAnalyzers steps are disabled. @@ -116,18 +125,18 @@ The second [ArLayer](../../../argaze/#argaze.ArFeatures.ArLayer) pipeline step a ![AOI Scan Path](../../img/ar_layer_aoi_scan_path.png) -Once fixations and saccades are identified and fixations are matched to AOI, they are automatically appended to the AOIScanPath if required. +Once identified gaze movements are matched to AOI, they are automatically appended to the AOIScanPath if required. The [AOIScanPath.duration_max](../../../argaze/#argaze.GazeFeatures.AOIScanPath.duration_max) attribute is the duration from which older AOI scan steps are removed each time new AOI scan steps are added. +!!! note + JSON *aoi_scan_path* entry is not mandatory. If aoi_scan_path_analyzers entry is not empty, the AOIScanPath step is automatically enabled. + ### AOI Scan Path Analyzers Finally, the last [ArLayer](../../../argaze/#argaze.ArFeatures.ArLayer) pipeline step consists in passing the previously built [AOIScanPath](../../../argaze/#argaze.GazeFeatures.AOIScanPath) to each loaded [AOIScanPathAnalyzer](../../../argaze/#argaze.GazeFeatures.AOIScanPathAnalyzer). -Each analysis algorithm can be selected by instantiating a particular [AOIScanPathAnalyzer](../../../argaze/#argaze.GazeFeatures.AOIScanPathAnalyzer) from the [argaze.GazeAnalysis](../../../argaze/#argaze.GazeAnalysis) submodule or [another python package](../advanced_topics/plugin_loading). - -!!! note - JSON *aoi_scan_path* entry is not mandatory. If aoi_scan_path_analyzers entry is not empty, the AOIScanPath step is automatically enabled. +Each analysis algorithm can be selected by instantiating a particular [AOIScanPathAnalyzer](../../../argaze/#argaze.GazeFeatures.AOIScanPathAnalyzer) from the [argaze.GazeAnalysis](../../../argaze/#argaze.GazeAnalysis) submodule or [from another python package](../advanced_topics/module_loading). ## Pipeline execution diff --git a/docs/user_guide/gaze_analysis_pipeline/gaze_analysis_logging.md b/docs/user_guide/gaze_analysis_pipeline/gaze_analysis_logging.md new file mode 100644 index 0000000..d79e3a6 --- /dev/null +++ b/docs/user_guide/gaze_analysis_pipeline/gaze_analysis_logging.md @@ -0,0 +1,97 @@ +Log gaze analysis +================= + +[ArFrame](../../../argaze/#argaze.ArFeatures.ArFrame) and [ArLayer](../../../argaze/#argaze.ArFeatures.ArLayer) logging are optional pipeline steps. They are executed at each new scan path step to update logs. + +## Enable ArFrame and ArLayer logging + +[ArFrame](../../../argaze/#argaze.ArFeatures.ArFrame) and [ArLayer](../../../argaze/#argaze.ArFeatures.ArLayer) have a log attribute to enable analysis logging. + +Here is the JSON ArFrame configuration file example where logging is enabled for the ArFrame and for one ArLayer: + +```json +{ + "name": "My FullHD screen", + "size": [1920, 1080], + "log": true, + ... + "layers": { + "MyLayer": { + "log": true, + ... + } + } +} +``` + +Then, here is how to access to logs dictionaries: + +```python +# Assuming that all timestamped gaze positions have been processed by ArFrame.look method +... + +# Access to ArFame scan path analysis logs +for analysis, log in ar_frame.logs.items(): + + # Do something with scan path analysis log + ... + +# Access to ArLayers AOI scan path analysis logs +for layer_name, layer in ar_frame.layers.items(): + + for analysis, log in layer.logs.items(): + + # Do something with AOI scan path analysis log + ... +``` + +!!! note + [ArFrame](../../../argaze/#argaze.ArFeatures.ArFrame) and its [ArLayers](../../../argaze/#argaze.ArFeatures.ArLayer) logging are automatically done each time the [ArFrame.look](../../../argaze/#argaze.ArFeatures.ArFrame.look) method is called. + [ArFrame](../../../argaze/#argaze.ArFeatures.ArFrame) logging records each scan path analysis into a dedicated timestamped data buffer each time a new scan path step happens. + [ArLayer](../../../argaze/#argaze.ArFeatures.ArLayer) logging records each AOI scan path analysis into a dedicated timestamped data buffer each time a new AOI scan path step happens. + +## Export gaze analysis logs to CSV file + +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. + +```python +import pandas + +# Assuming that all timestamped gaze positions have been processed by ArFrame.look method +... + +# 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') + +# Export ArLayers layers AOI scan path analysis logs +for layer_name, layer in ar_frame.layers.items(): + + for analysis, log in layer.logs.items(): + + log.as_dataframe().to_csv(f'./{ar_frame.name}_{layer_name}_{analysis}.csv') + +``` + +Assuming that [ArGaze.GazeAnalysis.Basic](../../../argaze/#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/#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: {('upper_left_corner', 'lower_left_corner', 'lower_right_corner'): 1}, 4: {}, 5: {}}"| +|... |... | + + diff --git a/docs/user_guide/gaze_analysis_pipeline/heatmap_visualisation.md b/docs/user_guide/gaze_analysis_pipeline/heatmap_visualisation.md new file mode 100644 index 0000000..24a8f32 --- /dev/null +++ b/docs/user_guide/gaze_analysis_pipeline/heatmap_visualisation.md @@ -0,0 +1,71 @@ +Visualize heatmap +================= + +Heatmap is an optional [ArFrame](../../../argaze/#argaze.ArFeatures.ArFrame) pipeline step. It is executed at each new gaze position to update heatmap image. + +![Heatmap](../../img/ar_frame_heatmap.png) + +## Enable ArFrame heatmap + +[ArFrame](../../../argaze/#argaze.ArFeatures.ArFrame) heatmap visualization can be enabled thanks to a dedicated JSON entry. + +Here is the JSON ArFrame configuration file example where heatmap visualization is enabled: + +```json +{ + "name": "My FullHD screen", + "size": [1920, 1080], + ... + "heatmap": { + "size": [320, 180], + "sigma": 0.025, + "buffer": 0 + } +} +``` + +Then, here is how to access to heatmap object: + +```python + +# Assuming an ArFrame is loaded +... + +print("heatmap:", ar_frame.heatmap) +``` + +Finally, here is what the program writes in console: + +```txt +heatmap: Heatmap(size=[320, 180], buffer=0, sigma=0.025) +``` + +Now, let's understand the meaning of each JSON entry. + +### Size + +The heatmap image size in pixel. Higher size implies higher CPU load. + +### Sigma + +The gaussian point spreading to draw at each gaze position. + +![Point spread](../../img/point_spread.png) + +### Buffer + +The size of point spread images buffer (0 means no buffering) to visualize only last N gaze positions. + +## Export heatmap to PNG file + +Once timestamped gaze positions have been processed by [ArFrame.look](../../../argaze/#argaze.ArFeatures.ArFrame.look) method, it is possible to write heatmap image thanks to OpenCV package. + +```python +import cv2 + +# Assuming that timestamped gaze positions have been processed by ArFrame.look method +... + +# Export heatmap image +cv2.imwrite('./heatmap.png', ar_frame.heatmap.image) +``` \ No newline at end of file diff --git a/docs/user_guide/gaze_analysis_pipeline/introduction.md b/docs/user_guide/gaze_analysis_pipeline/introduction.md index ee67c9d..e696088 100644 --- a/docs/user_guide/gaze_analysis_pipeline/introduction.md +++ b/docs/user_guide/gaze_analysis_pipeline/introduction.md @@ -12,8 +12,9 @@ To build your own gaze analysis pipeline, you need to know: * [How to edit timestamped gaze positions](../timestamped_gaze_positions_edition), * [How to deal with an ArFrame instance](../ar_frame_configuration_and_execution), * [How to deal with an ArLayer instance](../ar_layer_configuration_and_execution), -* [How to log resulted gaze analysis](../analysis). +* [How to log resulted gaze analysis](../gaze_analysis_logging), +* [How to visualize heatmap](../heatmap_visualisation). More advanced features are also explained like: -* [How to load plugin from another package](../advanced_topics/plugin_loading) +* [How to load module from another package](../advanced_topics/module_loading) diff --git a/docs/user_guide/gaze_analysis_pipeline/timestamped_gaze_positions_edition.md b/docs/user_guide/gaze_analysis_pipeline/timestamped_gaze_positions_edition.md index e7deab2..3b30bf8 100644 --- a/docs/user_guide/gaze_analysis_pipeline/timestamped_gaze_positions_edition.md +++ b/docs/user_guide/gaze_analysis_pipeline/timestamped_gaze_positions_edition.md @@ -22,8 +22,8 @@ ts_gaze_positions = GazeFeatures.TimeStampedGazePositions.from_dataframe(datafra # Iterate over timestamped gaze positions for timestamp, gaze_position in ts_gaze_positions.items(): - # Do something with each timestamped gaze position - ... + # Do something with each timestamped gaze position + ... ``` ## Edit gaze positions from live stream @@ -36,6 +36,7 @@ from argaze import GazeFeatures # Assuming to be inside the function where timestamp_µs, gaze_x and gaze_y values are catched ... + # Edit a second timestamp from a microsecond second timestamp timestamp = timestamp_µs * 1e-6 @@ -43,7 +44,7 @@ from argaze import GazeFeatures gaze_position = GazeFeatures.GazePosition((gaze_x, gaze_y)) # Do something with each timestamped gaze position - ... + ... ``` ``` python @@ -64,7 +65,7 @@ start_time = time.time() gaze_position = GazeFeatures.GazePosition((gaze_x, gaze_y)) # Do something with each timestamped gaze position - ... + ... ``` !!! warning diff --git a/mkdocs.yml b/mkdocs.yml index 1aaae5a..733883d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -9,8 +9,10 @@ nav: - user_guide/gaze_analysis_pipeline/timestamped_gaze_positions_edition.md - user_guide/gaze_analysis_pipeline/ar_frame_configuration_and_execution.md - user_guide/gaze_analysis_pipeline/ar_layer_configuration_and_execution.md + - user_guide/gaze_analysis_pipeline/gaze_analysis_logging.md + - user_guide/gaze_analysis_pipeline/heatmap_visualisation.md - Advanced Topics: - - user_guide/gaze_analysis_pipeline/advanced_topics/plugin_loading.md + - user_guide/gaze_analysis_pipeline/advanced_topics/module_loading.md # - ArUco Markers: # - user_guide/aruco_markers/introduction.md # - user_guide/aruco_markers/dictionary_selection.md -- cgit v1.1