aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/argaze/ArFeatures.py96
-rw-r--r--src/argaze/GazeFeatures.py6
-rw-r--r--src/argaze/utils/demo_gaze_features_run.py19
3 files changed, 76 insertions, 45 deletions
diff --git a/src/argaze/ArFeatures.py b/src/argaze/ArFeatures.py
index b4b1e39..476e413 100644
--- a/src/argaze/ArFeatures.py
+++ b/src/argaze/ArFeatures.py
@@ -65,14 +65,21 @@ class SceneProjectionFailed(Exception):
@dataclass
class ArFrame():
"""
- Define Augmented Reality frame as an AOI2DScene made from a projected then reframed parent AOI3DScene.
+ Defines rectangular area where to project in timestamped gaze positions and inside which they need to be analyzed.
Parameters:
name: name of the frame
- size: frame dimension in pixel.
+ size: defines the dimension of the rectangular area where gaze positions are projected.
+ aoi_2d_scene: AOI 2D scene description
background: image to draw behind
- aoi_2d_scene: AOI 2D scene description ... : see [orthogonal_projection][argaze.ArFeatures.ArScene.orthogonal_projection] and [reframe][argaze.AreaOfInterest.AOI2DScene.reframe] functions.
- ...
+ gaze_movement_identifier: gaze movement identification algorithm
+ current_fixation_matching: enable AOI fixation matching even for in progress fixation
+ looked_aoi_covering_threshold:
+ scan_path: scan path object
+ scan_path_analyzers: dictionary of scan path analysis to apply on scan path
+ aoi_scan_path: AOI scan path object
+ aoi_scan_path_analyzers: dictionary of scan path analysis to apply on AOI scan path
+ heatmap: heatmap object
"""
name: str
@@ -104,6 +111,12 @@ class ArFrame():
@classmethod
def from_dict(self, frame_data, working_directory: str = None) -> ArFrameType:
+ """Load attributes from dictionary.
+
+ Parameters:
+ frame_data: dictionary with attributes to load
+ working_directory: folder path where to load files when a dictionary value is a relative filepath.
+ """
# Load name
try:
@@ -152,16 +165,20 @@ class ArFrame():
except KeyError:
- new_frame_background = numpy.zeros((new_frame_size[1], new_frame_size[0], 3)).astype(numpy.uint8)
+ new_frame_background = numpy.full((new_frame_size[1], new_frame_size[0], 3), 127).astype(numpy.uint8)
# Load gaze movement identifier
try:
gaze_movement_identifier_value = frame_data.pop('gaze_movement_identifier')
- gaze_movement_identifier_type, gaze_movement_identifier_parameters = gaze_movement_identifier_value.popitem()
+ gaze_movement_identifier_module_path, gaze_movement_identifier_parameters = gaze_movement_identifier_value.popitem()
+
+ # Prepend argaze.GazeAnalysis path when a single name is provided
+ if len(gaze_movement_identifier_module_path.split('.')) == 1:
+ gaze_movement_identifier_module_path = f'argaze.GazeAnalysis.{gaze_movement_identifier_module_path}'
- gaze_movement_identifier_module = importlib.import_module(f'argaze.GazeAnalysis.{gaze_movement_identifier_type}')
+ gaze_movement_identifier_module = importlib.import_module(gaze_movement_identifier_module_path)
new_gaze_movement_identifier = gaze_movement_identifier_module.GazeMovementIdentifier(**gaze_movement_identifier_parameters)
except KeyError:
@@ -204,9 +221,15 @@ class ArFrame():
new_scan_path_analyzers_value = frame_data.pop('scan_path_analyzers')
- for scan_path_analyzer_type, scan_path_analyzer_parameters in new_scan_path_analyzers_value.items():
+ for scan_path_analyzer_module_path, scan_path_analyzer_parameters in new_scan_path_analyzers_value.items():
- scan_path_analyzer_module = importlib.import_module(f'argaze.GazeAnalysis.{scan_path_analyzer_type}')
+ print(scan_path_analyzer_module_path)
+
+ # Prepend argaze.GazeAnalysis path when a single name is provided
+ if len(scan_path_analyzer_module_path.split('.')) == 1:
+ scan_path_analyzer_module_path = f'argaze.GazeAnalysis.{scan_path_analyzer_module_path}'
+
+ scan_path_analyzer_module = importlib.import_module(scan_path_analyzer_module_path)
# Check scan path analyzer parameters type
members = getmembers(scan_path_analyzer_module.ScanPathAnalyzer)
@@ -231,11 +254,11 @@ class ArFrame():
except KeyError:
- raise EnvironmentJSONLoadingFailed(f'{scan_path_analyzer_type} scan path analyzer loading fails because {parameter_module_path[2]} scan path analyzer is missing.')
+ raise EnvironmentJSONLoadingFailed(f'{scan_path_analyzer_module_path} scan path analyzer loading fails because {parameter_module_path[2]} scan path analyzer is missing.')
scan_path_analyzer = scan_path_analyzer_module.ScanPathAnalyzer(**scan_path_analyzer_parameters)
- new_scan_path_analyzers[scan_path_analyzer_type] = scan_path_analyzer
+ new_scan_path_analyzers[scan_path_analyzer_module_path] = scan_path_analyzer
# Force scan path creation
if len(new_scan_path_analyzers) > 0 and new_scan_path == None:
@@ -266,9 +289,13 @@ class ArFrame():
new_aoi_scan_path_analyzers_value = frame_data.pop('aoi_scan_path_analyzers')
- for aoi_scan_path_analyzer_type, aoi_scan_path_analyzer_parameters in new_aoi_scan_path_analyzers_value.items():
+ for aoi_scan_path_analyzer_module_path, aoi_scan_path_analyzer_parameters in new_aoi_scan_path_analyzers_value.items():
+
+ # Prepend argaze.GazeAnalysis path when a single name is provided
+ if len(aoi_scan_path_analyzer_module_path.split('.')) == 1:
+ aoi_scan_path_analyzer_module_path = f'argaze.GazeAnalysis.{aoi_scan_path_analyzer_module_path}'
- aoi_scan_path_analyzer_module = importlib.import_module(f'argaze.GazeAnalysis.{aoi_scan_path_analyzer_type}')
+ aoi_scan_path_analyzer_module = importlib.import_module(aoi_scan_path_analyzer_module_path)
# Check aoi scan path analyzer parameters type
members = getmembers(aoi_scan_path_analyzer_module.AOIScanPathAnalyzer)
@@ -293,11 +320,11 @@ class ArFrame():
except KeyError:
- raise EnvironmentJSONLoadingFailed(f'{aoi_scan_path_analyzer_type} aoi scan path analyzer loading fails because {parameter_module_path[2]} aoi scan path analyzer is missing.')
+ raise EnvironmentJSONLoadingFailed(f'{aoi_scan_path_analyzer_module_path} aoi scan path analyzer loading fails because {parameter_module_path[2]} aoi scan path analyzer is missing.')
aoi_scan_path_analyzer = aoi_scan_path_analyzer_module.AOIScanPathAnalyzer(**aoi_scan_path_analyzer_parameters)
- new_aoi_scan_path_analyzers[aoi_scan_path_analyzer_type] = aoi_scan_path_analyzer
+ new_aoi_scan_path_analyzers[aoi_scan_path_analyzer_module_path] = aoi_scan_path_analyzer
# Force AOI scan path creation
if len(new_aoi_scan_path_analyzers) > 0 and new_aoi_scan_path == None:
@@ -343,7 +370,7 @@ class ArFrame():
@classmethod
def from_json(self, json_filepath: str) -> ArEnvironmentType:
"""
- Load ArFrame from .json file.
+ Load attributes from .json file.
Parameters:
json_filepath: path to json file
@@ -452,15 +479,22 @@ class ArFrame():
# Update looked aoi covering mean
self.__looked_aoi_covering_mean = int(100 * max_covering / self.__look_count) / 100
- def look(self, timestamp: int|float, inner_gaze_position: GazeFeatures.GazePosition = GazeFeatures.UnvalidGazePosition(), identified_gaze_movement: GazeFeatures.GazeMovement = GazeFeatures.UnvalidGazeMovement()) -> Tuple[GazeFeatures.GazeMovement, dict, dict, dict]:
- """
-
- GazeFeatures.AOIScanStepError
+ def look(self, timestamp: int|float, gaze_position: GazeFeatures.GazePosition = GazeFeatures.UnvalidGazePosition(), identified_gaze_movement: GazeFeatures.GazeMovement = GazeFeatures.UnvalidGazeMovement()) -> Tuple[GazeFeatures.GazeMovement, dict, dict, dict]:
+ """
+ Project gaze position into frame.
+
+ !!! warning
+ Be aware that gaze positions are in the same range of value than size attribute.
+
+ Parameters:
+ timestamp:
+ gaze_position: gaze position to project
+ identified_gaze_movement: pass identified gaze movement instead of timestamped gaze position to avoid double identification process.
Returns:
- temp_gaze_movement: identified gaze movement (if gaze_movement_identifier is instanciated) or current gaze movement (if current_fixation_matching is True)
- scan_step: new scan step (if scan_path is instanciated)
- aoi_scan_step: new scan step (if aoi_scan_path is instanciated)
+ identified_gaze_movement: identified gaze movement from incoming consecutive timestamped gaze positions if gaze_movement_identifier is instanciated. Current gaze movement if current_fixation_matching is True.
+ scan_path_analysis: scan path analysis at each new scan step if scan_path is instanciated
+ aoi_scan_path_analysis: new scan step at each new aoi scan step if aoi_scan_path is instanciated
exception: error catched during gaze position processing
"""
@@ -468,7 +502,7 @@ class ArFrame():
self.__look_lock.acquire()
# Update current gaze position
- self.__gaze_position = inner_gaze_position
+ self.__gaze_position = gaze_position
# No gaze movement identified by default
temp_gaze_movement = GazeFeatures.UnvalidGazeMovement()
@@ -535,7 +569,7 @@ class ArFrame():
# Is there a new step?
if aoi_scan_step and len(self.aoi_scan_path) > 1:
- for aoi_scan_path_analyzer_type, aoi_scan_path_analyzer in self.aoi_scan_path_analyzers.items():
+ for aoi_scan_path_analyzer_module_path, aoi_scan_path_analyzer in self.aoi_scan_path_analyzers.items():
# Store aoi scan step analysis start date
aoi_scan_step_analysis_start = time.perf_counter()
@@ -544,10 +578,10 @@ class ArFrame():
aoi_scan_path_analyzer.analyze(self.aoi_scan_path)
# Assess aoi scan step analysis time in ms
- times['aoi_scan_step_analyzers'][aoi_scan_path_analyzer_type] = (time.perf_counter() - aoi_scan_step_analysis_start) * 1e3
+ times['aoi_scan_step_analyzers'][aoi_scan_path_analyzer_module_path] = (time.perf_counter() - aoi_scan_step_analysis_start) * 1e3
# Store analysis
- aoi_scan_step_analysis[aoi_scan_path_analyzer_type] = aoi_scan_path_analyzer.analysis
+ aoi_scan_step_analysis[aoi_scan_path_analyzer_module_path] = aoi_scan_path_analyzer.analysis
elif GazeFeatures.is_saccade(temp_gaze_movement):
@@ -562,7 +596,7 @@ class ArFrame():
# Is there a new step?
if scan_step and len(self.scan_path) > 1:
- for scan_path_analyzer_type, scan_path_analyzer in self.scan_path_analyzers.items():
+ for scan_path_analyzer_module_path, scan_path_analyzer in self.scan_path_analyzers.items():
# Store scan step analysis start date
scan_step_analysis_start = time.perf_counter()
@@ -571,10 +605,10 @@ class ArFrame():
scan_path_analyzer.analyze(self.scan_path)
# Assess scan step analysis time in ms
- times['scan_step_analyzers'][scan_path_analyzer_type] = (time.perf_counter() - scan_step_analysis_start) * 1e3
+ times['scan_step_analyzers'][scan_path_analyzer_module_path] = (time.perf_counter() - scan_step_analysis_start) * 1e3
# Store analysis
- scan_step_analysis[scan_path_analyzer_type] = scan_path_analyzer.analysis
+ scan_step_analysis[scan_path_analyzer_module_path] = scan_path_analyzer.analysis
# Append saccade to aoi scan path
if self.aoi_scan_path != None:
@@ -1280,7 +1314,7 @@ class ArEnvironment():
def look(self, timestamp: int|float, gaze_position: GazeFeatures.GazePosition):
"""Project timestamped gaze position into each frame.
- .. warning:: detect_and_project method needs to be called first.
+ !!! warning detect_and_project method needs to be called first.
"""
# Can't use camera frame when it is locked
diff --git a/src/argaze/GazeFeatures.py b/src/argaze/GazeFeatures.py
index 67e5822..224e2d9 100644
--- a/src/argaze/GazeFeatures.py
+++ b/src/argaze/GazeFeatures.py
@@ -590,7 +590,11 @@ ScanPathType = TypeVar('ScanPathType', bound="ScanPathType")
# Type definition for type annotation convenience
class ScanPath(list):
- """List of scan steps."""
+ """List of scan steps.
+
+ Parameters:
+ duration_max: duration from which older scan steps are removed each time new scan steps are added. 0 means no maximal duration.
+ """
def __init__(self, duration_max: int|float = 0):
diff --git a/src/argaze/utils/demo_gaze_features_run.py b/src/argaze/utils/demo_gaze_features_run.py
index 76d683a..7088639 100644
--- a/src/argaze/utils/demo_gaze_features_run.py
+++ b/src/argaze/utils/demo_gaze_features_run.py
@@ -10,7 +10,6 @@ __license__ = "BSD"
import argparse
import os
import time
-import queue
from argaze import ArFeatures, GazeFeatures
from argaze.AreaOfInterest import AOIFeatures
@@ -50,20 +49,14 @@ def main():
# Update pointer position
def on_mouse_event(event, x, y, flags, param):
- try:
+ # Edit millisecond timestamp
+ timestamp = int((time.time() - start_time) * 1e3)
- # Edit millisecond timestamp
- timestamp = int((time.time() - start_time) * 1e3)
+ # Project gaze position into frame
+ movement, scan_step_analysis, aoi_scan_step_analysis, times, exception = ar_frame.look(timestamp, GazeFeatures.GazePosition((x, y)))
- # Project gaze position into frame
- movement, scan_step_analysis, aoi_scan_step_analysis, times, exception = ar_frame.look(timestamp, GazeFeatures.GazePosition((x, y)))
-
- # Do something with look data
- # ...
-
- except GazeFeatures.AOIScanStepError as e:
-
- print(f'Error on {e.aoi} step:', e)
+ # Do something with look data
+ # ...
# Attach mouse callback to window
cv2.setMouseCallback(ar_frame.name, on_mouse_event)