aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/argaze/ArFeatures.py4
-rw-r--r--src/argaze/DataFeatures.py91
-rw-r--r--src/argaze/DataLog/File.py25
-rw-r--r--src/argaze/utils/demo_data/demo_gaze_analysis_setup.json14
4 files changed, 81 insertions, 53 deletions
diff --git a/src/argaze/ArFeatures.py b/src/argaze/ArFeatures.py
index e7b94c0..faa3d9a 100644
--- a/src/argaze/ArFeatures.py
+++ b/src/argaze/ArFeatures.py
@@ -459,7 +459,7 @@ class ArLayer(DataFeatures.SharedObject):
# Log look data
for logger_module_path, logger in self.loggers.items():
- logger.emit(timestamp, DataFeatures.DataDictionary(look_data))
+ logger(timestamp, DataFeatures.DataDictionary(look_data))
# Unlock layer exploitation
self.release()
@@ -979,7 +979,7 @@ class ArFrame(DataFeatures.SharedObject):
# Log look data
for logger_module_path, logger in self.loggers.items():
- logger.emit(timestamp, DataFeatures.DataDictionary(look_data))
+ logger(timestamp, DataFeatures.DataDictionary(look_data))
# Unlock frame exploitation
self.release()
diff --git a/src/argaze/DataFeatures.py b/src/argaze/DataFeatures.py
index cf7566a..ac3000c 100644
--- a/src/argaze/DataFeatures.py
+++ b/src/argaze/DataFeatures.py
@@ -7,7 +7,7 @@ __credits__ = []
__copyright__ = "Copyright 2023, Ecole Nationale de l'Aviation Civile (ENAC)"
__license__ = "BSD"
-from typing import TypeVar, Tuple
+from typing import TypeVar, Tuple, Any
from dataclasses import dataclass, field
import importlib
from inspect import getmembers
@@ -103,13 +103,6 @@ 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."""
@@ -180,37 +173,6 @@ 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."""
-
- 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')
-
class TimeStampedBuffer(collections.OrderedDict):
"""Ordered dictionary to handle timestamped data.
```
@@ -458,3 +420,54 @@ class TimeStampedBuffer(collections.OrderedDict):
legend_patches.append(mpatches.Patch(color=color, label=name.upper()))
return legend_patches
+
+class DataDictionary(dict):
+ """Enable dot.notation access to dictionary attributes"""
+
+ __getattr__ = dict.get
+ __setattr__ = dict.__setitem__
+ __delattr__ = dict.__delitem__
+
+# Import libraries that can be used in selector or formatter codes
+from argaze import GazeFeatures
+
+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."""
+
+ selector: str = field(default='True')
+ """Code evaluated to select data under a condition. Default 'True' string means that all incoming data will be accepted."""
+
+ formatter: str|list = field(default='timestamp, data')
+ """Code (or list of codes) evaluated to format timestamp and data to emit. Default 'timestamp, data' string means that all incoming timestamp and data values will be emitted."""
+
+ @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 __call__(self, timestamp: TimeStampType, data: DataDictionary) -> Any:
+ """Apply selector code to decide if data have to be logged, then apply formatter code before to call specific logger emit method."""
+
+ if eval(self.selector):
+
+ return self.emit(eval(self.formatter))
+
+ def emit(self, formatted_log: any):
+ """Emit formatted log to a destination."""
+
+ raise NotImplementedError('handle() method not implemented')
diff --git a/src/argaze/DataLog/File.py b/src/argaze/DataLog/File.py
index 12bad6d..8179f8f 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, GazeFeatures
+from argaze import DataFeatures
@dataclass
class TimeStampedDataLogger(DataFeatures.TimeStampedDataLogger):
@@ -21,8 +21,11 @@ class TimeStampedDataLogger(DataFeatures.TimeStampedDataLogger):
path: str = field(default=None)
"""File path where to write data."""
- separator: chr = field(default=' ')
- """Char used to separate timestamp from data"""
+ header: str = field(default=None)
+ """String to write first."""
+
+ separator: str = field(default=", ")
+ """String used to separate list or tuple formatted log."""
def __post_init__(self):
"""Check that folder structure exist and create file."""
@@ -35,13 +38,23 @@ class TimeStampedDataLogger(DataFeatures.TimeStampedDataLogger):
# Open file
self._file = open(self.path, 'w', encoding='utf-8', buffering=1)
+ # Write header if required
+ if self.header is not None:
+
+ print(self.header, file=self._file, flush=True)
+
def __del__(self):
"""Close file."""
self._file.close()
- def emit(self, timestamp: DataFeatures.TimeStampType, data: DataFeatures.DataDictionary):
- """Write timestamp and data separated by separator char as a new line into file."""
+ def emit(self, formatted_log: any):
+ """Write log as a new line into file. List or tuple are converted into strings separated by separator char."""
+
+ # Format list or tuple element into quoted strings
+ if not isinstance(formatted_log, str):
+
+ formatted_log = self.separator.join(f'\"{d}\"' for d in formatted_log)
# Write into file
- print(f'{timestamp}{self.separator}{eval(self.selector)}', file=self._file, flush=True) \ No newline at end of file
+ print(formatted_log, 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 38db16f..39eaeca 100644
--- a/src/argaze/utils/demo_data/demo_gaze_analysis_setup.json
+++ b/src/argaze/utils/demo_data/demo_gaze_analysis_setup.json
@@ -49,18 +49,20 @@
},
"loggers": {
"File" : {
- "path": "_export/logs/layer_data.txt",
- "separator": ",",
- "selector": "data.looked_aoi_name"
+ "path": "_export/logs/aoi.csv",
+ "header": "Timestamp, AOI",
+ "selector": "data.looked_aoi_name is not None",
+ "formatter": "timestamp, data.looked_aoi_name"
}
}
}
},
"loggers": {
"File" : {
- "path": "_export/logs/frame_data.txt",
- "separator": ",",
- "selector": "data.identified_gaze_movement.focus if GazeFeatures.is_fixation(data.identified_gaze_movement) else None"
+ "path": "_export/logs/fixations.csv",
+ "header": "Timestamp, Focus, Duration",
+ "selector": "GazeFeatures.is_fixation(data.identified_gaze_movement)",
+ "formatter": "timestamp, data.identified_gaze_movement.focus, data.identified_gaze_movement.duration"
}
},
"image_parameters": {