aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorThéo de la Hogue2023-05-22 11:33:22 +0200
committerThéo de la Hogue2023-05-22 11:33:22 +0200
commit846b5bf301b79e6725c32114532eaf50e1379a81 (patch)
treeabbee94f8e6fca30d0aed5c727dd70e06ea763bb /src
parenta2ac49bc14e695c302f04b8fed2f3fb2675cf5d4 (diff)
downloadargaze-846b5bf301b79e6725c32114532eaf50e1379a81.zip
argaze-846b5bf301b79e6725c32114532eaf50e1379a81.tar.gz
argaze-846b5bf301b79e6725c32114532eaf50e1379a81.tar.bz2
argaze-846b5bf301b79e6725c32114532eaf50e1379a81.tar.xz
Adding fixation, saccade and scan path drawing features.
Diffstat (limited to 'src')
-rw-r--r--src/argaze/GazeAnalysis/DispersionThresholdIdentification.py15
-rw-r--r--src/argaze/GazeAnalysis/VelocityThresholdIdentification.py15
-rw-r--r--src/argaze/GazeFeatures.py46
-rw-r--r--src/argaze/utils/demo_gaze_features_run.py35
4 files changed, 83 insertions, 28 deletions
diff --git a/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py b/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py
index 3219c56..18c9c49 100644
--- a/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py
+++ b/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py
@@ -14,6 +14,7 @@ import math
from argaze import GazeFeatures
import numpy
+import cv2
FixationType = TypeVar('Fixation', bound="Fixation")
# Type definition for type annotation convenience
@@ -68,6 +69,12 @@ class Fixation(GazeFeatures.Fixation):
return self
+ def draw(self, frame, color=(127, 127, 127), border_color=(255, 255, 255)):
+ """Draw fixation into frame."""
+
+ cv2.circle(frame, (int(self.focus[0]), int(self.focus[1])), int(self.deviation_max), color, -1)
+ cv2.circle(frame, (int(self.focus[0]), int(self.focus[1])), int(self.deviation_max), border_color, len(self.positions))
+
@dataclass(frozen=True)
class Saccade(GazeFeatures.Saccade):
"""Define dispersion based saccade."""
@@ -75,6 +82,14 @@ class Saccade(GazeFeatures.Saccade):
def __post_init__(self):
super().__post_init__()
+ def draw(self, frame, color=(255, 255, 255)):
+ """Draw saccade into frame."""
+
+ _, start_position = self.positions.first
+ _, last_position = self.positions.last
+
+ cv2.line(frame, (int(start_position[0]), int(start_position[1])), (int(last_position[0]), int(last_position[1])), color, 2)
+
@dataclass
class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier):
"""Implementation of the I-DT algorithm as described in:
diff --git a/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py b/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py
index 4aa6384..3c3d58f 100644
--- a/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py
+++ b/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py
@@ -14,6 +14,7 @@ import math
from argaze import GazeFeatures
import numpy
+import cv2
FixationType = TypeVar('Fixation', bound="Fixation")
# Type definition for type annotation convenience
@@ -68,6 +69,12 @@ class Fixation(GazeFeatures.Fixation):
return self
+ def draw(self, frame, color=(127, 127, 127), border_color=(255, 255, 255)):
+ """Draw fixation into frame."""
+
+ cv2.circle(frame, (int(self.focus[0]), int(self.focus[1])), int(self.deviation_max), color, -1)
+ cv2.circle(frame, (int(self.focus[0]), int(self.focus[1])), int(self.deviation_max), border_color, len(self.positions))
+
@dataclass(frozen=True)
class Saccade(GazeFeatures.Saccade):
"""Define dispersion based saccade."""
@@ -75,6 +82,14 @@ class Saccade(GazeFeatures.Saccade):
def __post_init__(self):
super().__post_init__()
+ def draw(self, frame, color=(255, 255, 255)):
+ """Draw saccade into frame."""
+
+ _, start_position = self.positions.first
+ _, last_position = self.positions.last
+
+ cv2.line(frame, (int(start_position[0]), int(start_position[1])), (int(last_position[0]), int(last_position[1])), color, 2)
+
@dataclass
class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier):
"""Implementation of the I-VT algorithm as described in:
diff --git a/src/argaze/GazeFeatures.py b/src/argaze/GazeFeatures.py
index bfa6303..3592b13 100644
--- a/src/argaze/GazeFeatures.py
+++ b/src/argaze/GazeFeatures.py
@@ -24,7 +24,7 @@ from argaze import DataStructures
import numpy
import pandas
-import cv2 as cv
+import cv2
@dataclass(frozen=True)
class GazePosition():
@@ -96,11 +96,11 @@ class GazePosition():
int_value = (int(self.value[0]), int(self.value[1]))
# Draw point at position
- cv.circle(frame, int_value, 2, color, -1)
+ cv2.circle(frame, int_value, 2, color, -1)
# Draw precision circle
if self.precision > 0 and draw_precision:
- cv.circle(frame, int_value, round(self.precision), color, 1)
+ cv2.circle(frame, int_value, round(self.precision), color, 1)
class UnvalidGazePosition(GazePosition):
"""Unvalid gaze position."""
@@ -190,6 +190,22 @@ class GazeMovement():
return output
+ def draw_positions(self, frame, color=(0, 55, 55)):
+ """Draw gaze movement positions"""
+
+ gaze_positions = self.positions.copy()
+
+ while len(gaze_positions) >= 2:
+
+ ts_start, start_gaze_position = gaze_positions.pop_first()
+ ts_next, next_gaze_position = gaze_positions.first
+
+ # Draw start gaze
+ start_gaze_position.draw(frame, draw_precision=False)
+
+ # Draw movement from start to next
+ cv2.line(frame, start_gaze_position, next_gaze_position, color, 1)
+
FixationType = TypeVar('Fixation', bound="Fixation")
# Type definition for type annotation convenience
@@ -208,6 +224,11 @@ class Fixation(GazeMovement):
raise NotImplementedError('merge() method not implemented')
+ def draw(self, frame, color):
+ """Draw fixation into frame."""
+
+ raise NotImplementedError('draw() method not implemented')
+
def is_fixation(gaze_movement):
"""Is a gaze movement a fixation?"""
@@ -220,6 +241,11 @@ class Saccade(GazeMovement):
super().__post_init__()
+ def draw(self, frame, color):
+ """Draw saccade into frame."""
+
+ raise NotImplementedError('draw() method not implemented')
+
def is_saccade(gaze_movement):
"""Is a gaze movement a saccade?"""
@@ -431,6 +457,20 @@ class ScanPath(list):
print(e)
+ def draw(self, frame, fixation_color=(255, 255, 255), saccade_color=(255, 255, 255), deepness=0):
+ """Draw scan path into frame."""
+
+ last_step = None
+ for step in self[-deepness:]:
+
+ if last_step != None:
+
+ cv2.line(frame, (int(last_step.first_fixation.focus[0]), int(last_step.first_fixation.focus[1])), (int(step.first_fixation.focus[0]), int(step.first_fixation.focus[1])), saccade_color, 2)
+
+ last_step.first_fixation.draw(frame, fixation_color)
+
+ last_step = step
+
class ScanPathAnalyzer():
"""Abstract class to define what should provide a scan path analyzer."""
diff --git a/src/argaze/utils/demo_gaze_features_run.py b/src/argaze/utils/demo_gaze_features_run.py
index a957ce0..6e7e01e 100644
--- a/src/argaze/utils/demo_gaze_features_run.py
+++ b/src/argaze/utils/demo_gaze_features_run.py
@@ -231,20 +231,10 @@ def main():
aoi_scene_projection.draw_circlecast(aoi_matrix, current_fixation.focus, current_fixation.deviation_max, base_color=(0, 0, 0), matching_color=(255, 255, 255))
# Draw current fixation
- cv2.circle(aoi_matrix, (int(current_fixation.focus[0]), int(current_fixation.focus[1])), int(current_fixation.deviation_max), (255, 255, 255), len(current_fixation.positions))
-
- # Draw current fixation gaze positions
- gaze_positions = current_fixation.positions.copy()
- while len(gaze_positions) >= 2:
-
- ts_start, start_gaze_position = gaze_positions.pop_first()
- ts_next, next_gaze_position = gaze_positions.first
-
- # Draw start gaze
- start_gaze_position.draw(aoi_matrix, draw_precision=False)
+ current_fixation.draw(aoi_matrix, color=(255, 255, 0))
- # Draw movement from start to next
- cv2.line(aoi_matrix, start_gaze_position, next_gaze_position, (0, 55, 55), 1)
+ # Draw current fixation gaze positions
+ current_fixation.draw_positions(aoi_matrix)
else:
@@ -260,22 +250,17 @@ def main():
current_saccade = gaze_movement_identifier[identification_mode].current_saccade
# Draw current saccade gaze positions
- gaze_positions = current_saccade.positions.copy()
- while len(gaze_positions) >= 2:
-
- ts_start, start_gaze_position = gaze_positions.pop_first()
- ts_next, next_gaze_position = gaze_positions.first
+ current_saccade.draw_positions(aoi_matrix)
- # Draw start gaze
- start_gaze_position.draw(aoi_matrix, draw_precision=False)
-
- # Draw movement from start to next
- cv2.line(aoi_matrix, start_gaze_position, next_gaze_position, (0, 0, 255), 1)
+ # Draw last 10 steps of raw scan path
+ raw_scan_path.draw(aoi_matrix, fixation_color=(255, 0, 255), deepness=10)
# Write last 5 steps of aoi scan path
path = ''
for step in aoi_scan_path[-5:]:
+
path += f'> {step.aoi} '
+
path += f'> {aoi_scan_path.current_aoi}'
cv2.putText(aoi_matrix, path, (20, window_size[1]-40), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 1, cv2.LINE_AA)
@@ -326,8 +311,8 @@ def main():
key_pressed = cv2.waitKey(10)
- if key_pressed != -1:
- print(key_pressed)
+ #if key_pressed != -1:
+ # print(key_pressed)
# Switch identification mode with 'm' key
if key_pressed == 109: