blob: bde486dca347894e9a2525b75021e1d2a323e655 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
#!/usr/bin/env python
"""Module for matching algorithm based on fixation's deviation circle coverage over AOI.
"""
__author__ = "Théo de la Hogue"
__credits__ = []
__copyright__ = "Copyright 2023, Ecole Nationale de l'Aviation Civile (ENAC)"
__license__ = "BSD"
from typing import TypeVar, Tuple
from dataclasses import dataclass, field
import math
from argaze import GazeFeatures
import numpy
import cv2
GazeMovementType = TypeVar('GazeMovement', bound="GazeMovement")
# Type definition for type annotation convenience
@dataclass
class AOIMatcher(GazeFeatures.AOIMatcher):
"""Matching algorithm based on fixation's deviation circle coverage over AOI."""
coverage_threshold: float
"""Minimal coverage ratio to consider a fixation over an AOI (1 means that whole fixation's deviation circle have to be over the AOI)."""
def __post_init__(self):
"""Init looked aoi data."""
self.__look_count = 0
self.__looked_aoi = None
self.__looked_aoi_coverage_mean = 0
self.__looked_aoi_coverage = {}
def match(self, aoi_scene, gaze_movement, exclude=[]) -> str:
"""Returns AOI with the maximal fixation's deviation circle coverage if above coverage threshold."""
if GazeFeatures.is_fixation(gaze_movement):
self.__look_count += 1
max_coverage = 0.
most_likely_looked_aoi = None
for name, aoi in aoi_scene.items():
_, _, circle_ratio = aoi.circle_intersection(gaze_movement.focus, gaze_movement.deviation_max)
if name not in exclude and circle_ratio > 0:
# Sum circle ratio to update aoi coverage
try:
self.__looked_aoi_coverage[name] += circle_ratio
except KeyError:
self.__looked_aoi_coverage[name] = circle_ratio
# Update most likely looked aoi
if self.__looked_aoi_coverage[name] > max_coverage:
most_likely_looked_aoi = name
max_coverage = self.__looked_aoi_coverage[name]
# Update looked aoi
self.__looked_aoi = most_likely_looked_aoi
# Update looked aoi coverage mean
self.__looked_aoi_coverage_mean = int(100 * max_coverage / self.__look_count) / 100
# Return
if self.looked_aoi_coverage_mean > self.coverage_threshold:
return self.__looked_aoi
elif GazeFeatures.is_saccade(gaze_movement):
self.__post_init__()
@property
def looked_aoi(self) -> str:
"""Get most likely looked aoi name for current fixation (e.g. the aoi with the highest coverage mean value)"""
return self.__looked_aoi
@property
def looked_aoi_coverage_mean(self) -> float:
"""Get looked aoi coverage mean for current fixation.
It represents the ratio of fixation deviation circle surface that used to cover the looked aoi."""
return self.__looked_aoi_coverage_mean
@property
def looked_aoi_coverage(self) -> dict:
"""Get all looked aois coverage for current fixation."""
return self.__looked_aoi_coverage
|