aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/user_guide/gaze_analysis_pipeline/advanced_topics/scripting.md5
-rw-r--r--src/argaze/ArFeatures.py66
-rw-r--r--src/argaze/DataFeatures.py36
-rw-r--r--src/argaze/DataLog/File.py6
-rw-r--r--src/argaze/utils/demo_data/demo_gaze_analysis_setup.json6
5 files changed, 73 insertions, 46 deletions
diff --git a/docs/user_guide/gaze_analysis_pipeline/advanced_topics/scripting.md b/docs/user_guide/gaze_analysis_pipeline/advanced_topics/scripting.md
index ab0c6d0..b888b9e 100644
--- a/docs/user_guide/gaze_analysis_pipeline/advanced_topics/scripting.md
+++ b/docs/user_guide/gaze_analysis_pipeline/advanced_topics/scripting.md
@@ -100,11 +100,14 @@ for name, ar_layer in ar_frame.layers.items():
# Do something with each ArLayer look data
for layer_name, layer_look_data in layers_look_data.items():
- looked_aoi_name, aoi_scan_path_analysis, layer_execution_times, layer_exception = layer_look_data
+ looked_aoi_name, looked_aoi, aoi_scan_path_analysis, layer_execution_times, layer_exception = layer_look_data
# Do something with looked AOI name
...
+ # Do something with looked AOI shape
+ ...
+
# Do something with ArLayer AOI scan path analysis
for module, analysis in aoi_scan_path_analysis.items():
for data, value in analysis.items():
diff --git a/src/argaze/ArFeatures.py b/src/argaze/ArFeatures.py
index f90354a..e7b94c0 100644
--- a/src/argaze/ArFeatures.py
+++ b/src/argaze/ArFeatures.py
@@ -293,15 +293,7 @@ class ArLayer(DataFeatures.SharedObject):
for logger_module_path, logger_parameters in new_loggers_value.items():
- # Prepend argaze.DataLog path when a single name is provided
- if len(logger_module_path.split('.')) == 1:
- logger_module_path = f'argaze.DataLog.{logger_module_path}'
-
- logger_module = importlib.import_module(logger_module_path)
-
- logger = logger_module.TimeStampedDataLogger(**logger_parameters)
-
- new_loggers[logger_module_path] = logger
+ new_loggers[logger_module_path] = DataFeatures.TimeStampedDataLogger.from_dict(logger_module_path, logger_parameters)
except KeyError:
@@ -365,7 +357,8 @@ class ArLayer(DataFeatures.SharedObject):
gaze_movement: gaze movement to project
Returns:
- looked_aoi: most likely looked aoi name
+ looked_aoi_name: most likely looked aoi name
+ looked_aoi: most likely looked aoi shape
aoi_scan_path_analysis: aoi scan path analysis at each new scan step if aoi_scan_path is instanciated
exception: error catched during gaze movement processing
"""
@@ -446,6 +439,7 @@ class ArLayer(DataFeatures.SharedObject):
print('Warning: the following error occurs in ArLayer.look method:', e)
+ looked_aoi_name = None
looked_aoi = None
aoi_scan_path_analysis = {}
exception = e
@@ -453,19 +447,25 @@ class ArLayer(DataFeatures.SharedObject):
# Assess total execution time in ms
execution_times['total'] = (time.perf_counter() - look_start) * 1e3
- # Edit look data
- look_data = looked_aoi_name, aoi_scan_path_analysis, execution_times, exception
+ # Edit look data dictionary
+ look_data = {
+ "looked_aoi_name": looked_aoi_name,
+ "looked_aoi": looked_aoi,
+ "aoi_scan_path_analysis": DataFeatures.DataDictionary(aoi_scan_path_analysis),
+ "execution_times": DataFeatures.DataDictionary(execution_times),
+ "exception": exception
+ }
# Log look data
for logger_module_path, logger in self.loggers.items():
- logger.emit(timestamp, look_data)
+ logger.emit(timestamp, DataFeatures.DataDictionary(look_data))
# Unlock layer exploitation
self.release()
- # Return look data
- return look_data
+ # Return look data values
+ return look_data.values()
def draw(self, image: numpy.array, draw_aoi_scene: dict = None, draw_aoi_matching: dict = None):
"""
@@ -757,15 +757,6 @@ class ArFrame(DataFeatures.SharedObject):
pass
- # Load log status
- try:
-
- new_frame_log = frame_data.pop('log')
-
- except KeyError:
-
- new_frame_log = False
-
# Load loggers
new_loggers = {}
@@ -775,15 +766,7 @@ class ArFrame(DataFeatures.SharedObject):
for logger_module_path, logger_parameters in new_loggers_value.items():
- # Prepend argaze.DataLog path when a single name is provided
- if len(logger_module_path.split('.')) == 1:
- logger_module_path = f'argaze.DataLog.{logger_module_path}'
-
- logger_module = importlib.import_module(logger_module_path)
-
- logger = logger_module.TimeStampedDataLogger(**logger_parameters)
-
- new_loggers[logger_module_path] = logger
+ new_loggers[logger_module_path] = DataFeatures.TimeStampedDataLogger.from_dict(logger_module_path, logger_parameters)
except KeyError:
@@ -856,9 +839,9 @@ class ArFrame(DataFeatures.SharedObject):
current_gaze_position: calibrated gaze position if gaze_position_calibrator is instanciated else, given gaze position.
identified_gaze_movement: identified gaze movement from incoming consecutive timestamped gaze positions if gaze_movement_identifier is instanciated. Current gaze movement if filter_in_progress_identification is False.
scan_path_analysis: scan path analysis at each new scan step if scan_path is instanciated.
- layers_analysis: aoi scan path analysis at each new aoi scan step for each instanciated layers aoi scan path.
execution_times: all pipeline steps execution times.
exception: error catched during gaze position processing.
+ layers_look_data: dictionary with each layer's look data.
"""
# Lock frame exploitation
@@ -983,19 +966,26 @@ class ArFrame(DataFeatures.SharedObject):
# Assess total execution time in ms
execution_times['total'] = (time.perf_counter() - look_start) * 1e3
- # Edit look data
- look_data = self.__gaze_position, identified_gaze_movement, scan_step_analysis, execution_times, exception, layers_look_data
+ # Edit look data dictionary
+ look_data = {
+ "gaze_position": self.__gaze_position,
+ "identified_gaze_movement": identified_gaze_movement,
+ "scan_step_analysis": DataFeatures.DataDictionary(scan_step_analysis),
+ "execution_times": DataFeatures.DataDictionary(execution_times),
+ "exception": exception,
+ "layers_look_data": DataFeatures.DataDictionary(layers_look_data)
+ }
# Log look data
for logger_module_path, logger in self.loggers.items():
- logger.emit(timestamp, look_data)
+ logger.emit(timestamp, DataFeatures.DataDictionary(look_data))
# Unlock frame exploitation
self.release()
# Return look data
- return look_data
+ return look_data.values()
def __image(self, background_weight: float = None, heatmap_weight: float = None, draw_gaze_position_calibrator: dict = None, draw_scan_path: dict = None, draw_layers: dict = None, draw_gaze_positions: dict = None, draw_fixations: dict = None, draw_saccades: dict = None) -> numpy.array:
"""
diff --git a/src/argaze/DataFeatures.py b/src/argaze/DataFeatures.py
index a52e639..cf7566a 100644
--- a/src/argaze/DataFeatures.py
+++ b/src/argaze/DataFeatures.py
@@ -8,6 +8,8 @@ __copyright__ = "Copyright 2023, Ecole Nationale de l'Aviation Civile (ENAC)"
__license__ = "BSD"
from typing import TypeVar, Tuple
+from dataclasses import dataclass, field
+import importlib
from inspect import getmembers
import collections
import json
@@ -101,6 +103,13 @@ class JsonEncoder(json.JSONEncoder):
return public_dict
+class DataDictionary(dict):
+ """Enable dot.notation access to dictionary attributes"""
+
+ __getattr__ = dict.get
+ __setattr__ = dict.__setitem__
+ __delattr__ = dict.__delitem__
+
class SharedObject():
"""Enable multiple threads sharing."""
@@ -171,11 +180,34 @@ class SharedObject():
self._token = token
self._lock.release()
+TimeStampedDataLoggerType = TypeVar('TimeStampedDataLogger', bound="TimeStampedDataLogger")
+# Type definition for type annotation convenience
+
+@dataclass
class TimeStampedDataLogger():
"""Abstract class to define what should provide a timestamped data logger."""
- def emit(self, timestamp: TimeStampType, data: any):
- """Emit timestamped data to a specific log destination."""
+ selector: str = field(default='data')
+ """Code evaluated to select data. Default 'data' string means that all incoming data will be written."""
+
+ @classmethod
+ def from_dict(self, logger_module_path: str, logger_parameters: dict) -> TimeStampedDataLoggerType:
+ """Load timestamped data logger from dictionary.
+
+ Parameters:
+ logger_module_path: class name to load
+ logger_parameters: attributes to load
+ """
+
+ # Prepend argaze.DataLog path when a single name is provided
+ if len(logger_module_path.split('.')) == 1:
+ logger_module_path = f'argaze.DataLog.{logger_module_path}'
+
+ logger_module = importlib.import_module(logger_module_path)
+ return logger_module.TimeStampedDataLogger(**logger_parameters)
+
+ def emit(self, timestamp: TimeStampType, data: DataDictionary):
+ """Emit timestamped data dictionary to a specific log destination."""
raise NotImplementedError('emit() method not implemented')
diff --git a/src/argaze/DataLog/File.py b/src/argaze/DataLog/File.py
index 9253f66..12bad6d 100644
--- a/src/argaze/DataLog/File.py
+++ b/src/argaze/DataLog/File.py
@@ -12,7 +12,7 @@ from typing import TypeVar, Tuple
from dataclasses import dataclass, field
import os, pathlib
-from argaze import DataFeatures
+from argaze import DataFeatures, GazeFeatures
@dataclass
class TimeStampedDataLogger(DataFeatures.TimeStampedDataLogger):
@@ -40,8 +40,8 @@ class TimeStampedDataLogger(DataFeatures.TimeStampedDataLogger):
self._file.close()
- def emit(self, timestamp: DataFeatures.TimeStampType, data: any):
+ def emit(self, timestamp: DataFeatures.TimeStampType, data: DataFeatures.DataDictionary):
"""Write timestamp and data separated by separator char as a new line into file."""
# Write into file
- print(f'{timestamp}{self.separator}{data}', file=self._file, flush=True) \ No newline at end of file
+ print(f'{timestamp}{self.separator}{eval(self.selector)}', file=self._file, flush=True) \ 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 5ae7cf3..38db16f 100644
--- a/src/argaze/utils/demo_data/demo_gaze_analysis_setup.json
+++ b/src/argaze/utils/demo_data/demo_gaze_analysis_setup.json
@@ -50,7 +50,8 @@
"loggers": {
"File" : {
"path": "_export/logs/layer_data.txt",
- "separator": ","
+ "separator": ",",
+ "selector": "data.looked_aoi_name"
}
}
}
@@ -58,7 +59,8 @@
"loggers": {
"File" : {
"path": "_export/logs/frame_data.txt",
- "separator": ","
+ "separator": ",",
+ "selector": "data.identified_gaze_movement.focus if GazeFeatures.is_fixation(data.identified_gaze_movement) else None"
}
},
"image_parameters": {