From 8c8f6f859e63e913257d60891005cfd95c280ed4 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Tue, 3 May 2022 14:57:59 +0200 Subject: Improving gaze position management to export data with appropriate precisions. --- src/argaze/AreaOfInterest/AOI2DScene.py | 2 -- src/argaze/AreaOfInterest/AOIFeatures.py | 8 +++--- src/argaze/DataStructures.py | 2 +- src/argaze/GazeFeatures.py | 31 +++++++++------------- .../export_tobii_segment_aruco_visual_scan.py | 4 +-- src/argaze/utils/export_tobii_segment_movements.py | 2 +- src/argaze/utils/live_tobii_aruco_aois.py | 4 +-- src/argaze/utils/live_tobii_session.py | 4 +-- src/argaze/utils/replay_tobii_session.py | 4 +-- 9 files changed, 26 insertions(+), 35 deletions(-) diff --git a/src/argaze/AreaOfInterest/AOI2DScene.py b/src/argaze/AreaOfInterest/AOI2DScene.py index 6eb0f7c..d902bea 100644 --- a/src/argaze/AreaOfInterest/AOI2DScene.py +++ b/src/argaze/AreaOfInterest/AOI2DScene.py @@ -41,13 +41,11 @@ class AOI2DScene(AOIFeatures.AOIScene): for name, aoi2D in self.areas.items(): looked = aoi2D.looked(gaze_position) - looked_at = aoi2D.look_at(gaze_position) color = (0, 255, 0) if looked else (0, 0, 255) if looked: cv.putText(frame, name, aoi2D[3], cv.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 1, cv.LINE_AA) - cv.circle(frame, looked_at.astype(int), 10, (255, 255, 255), -1) # Draw form aoi2D.draw(frame, color) diff --git a/src/argaze/AreaOfInterest/AOIFeatures.py b/src/argaze/AreaOfInterest/AOIFeatures.py index a33db26..dc38eb8 100644 --- a/src/argaze/AreaOfInterest/AOIFeatures.py +++ b/src/argaze/AreaOfInterest/AOIFeatures.py @@ -2,7 +2,7 @@ from dataclasses import dataclass, field -from argaze import DataStructures +from argaze import DataStructures, GazeFeatures import cv2 as cv import matplotlib.path as mpath @@ -38,7 +38,7 @@ class AreaOfInterest(numpy.ndarray): if self.dimension() != 2: raise RuntimeError(f'Bad area dimension ({self.dimension()})') - return mpath.Path(self).contains_points([(gaze_position.x, gaze_position.y)])[0] + return mpath.Path(self).contains_points([gaze_position])[0] def look_at(self, gaze_position): """Get where the area is looked.""" @@ -46,7 +46,7 @@ class AreaOfInterest(numpy.ndarray): if self.dimension() != 2: raise RuntimeError(f'Bad area dimension ({self.dimension()})') - P = numpy.array([gaze_position.x, gaze_position.y]) + P = numpy.array(gaze_position) clockwise_area = self.clockwise() @@ -54,7 +54,7 @@ class AreaOfInterest(numpy.ndarray): OX, OY = clockwise_area[1] - O, clockwise_area[-1] - O OP = P - O - return numpy.array([numpy.dot(OP, OX) / numpy.dot(OX, OX), numpy.dot(OP, OY) / numpy.dot(OY, OY)]) + return ( round(numpy.dot(OP, OX) / numpy.dot(OX, OX), 3), round(numpy.dot(OP, OY) / numpy.dot(OY, OY), 3)) def draw(self, frame, color): diff --git a/src/argaze/DataStructures.py b/src/argaze/DataStructures.py index 44c2583..7b6ad9b 100644 --- a/src/argaze/DataStructures.py +++ b/src/argaze/DataStructures.py @@ -52,7 +52,7 @@ class TimeStampedBuffer(collections.OrderedDict): super().__setitem__(key, value) def __str__(self): - return json.dumps(self, default = vars) + return str(dict(self)) def append(self, timestamped_buffer): """Append a timestamped buffer.""" diff --git a/src/argaze/GazeFeatures.py b/src/argaze/GazeFeatures.py index 63a1e55..50104d4 100644 --- a/src/argaze/GazeFeatures.py +++ b/src/argaze/GazeFeatures.py @@ -8,15 +8,8 @@ from argaze.AreaOfInterest import AOIFeatures import numpy -@dataclass -class GazePosition(): - """Define gaze position.""" - - x: float - y: float - - def as_tuple(self): - return (self.x, self.y) +GazePosition = tuple +"""Define gaze position as a tuple of coordinates.""" class TimeStampedGazePositions(DataStructures.TimeStampedBuffer): """Define timestamped buffer to store gaze positions.""" @@ -114,8 +107,8 @@ class DispersionBasedMovementIdentifier(MovementIdentifier): def __getEuclideanDispersion(self, ts_gaze_positions_list): """Euclidian dispersion algorithm""" - x_list = [gp.x for (ts, gp) in ts_gaze_positions_list] - y_list = [gp.y for (ts, gp) in ts_gaze_positions_list] + x_list = [gp[0] for (ts, gp) in ts_gaze_positions_list] + y_list = [gp[1] for (ts, gp) in ts_gaze_positions_list] cx = numpy.mean(x_list) cy = numpy.mean(y_list) @@ -127,7 +120,7 @@ class DispersionBasedMovementIdentifier(MovementIdentifier): dist = numpy.sum(dist, axis=1) dist = numpy.sqrt(dist) - return max(dist), cx, cy + return round(max(dist)), cx, cy def __getDispersion(self, ts_gaze_positions_list): """Basic dispersion algorithm""" @@ -205,9 +198,9 @@ class DispersionBasedMovementIdentifier(MovementIdentifier): ts_gaze_positions = TimeStampedGazePositions() for (ts, gp) in ts_gaze_positions_list: - ts_gaze_positions[ts] = gp + ts_gaze_positions[round(ts)] = gp - new_fixation = Fixation(duration, dispersion, GazePosition(cx, cy), ts_gaze_positions) + new_fixation = Fixation(round(duration), dispersion, (round(cx), round(cy)), ts_gaze_positions) new_fixation_ts = ts_list[0] if self.__last_fixation != None: @@ -217,14 +210,14 @@ class DispersionBasedMovementIdentifier(MovementIdentifier): if new_saccade_duration > 0: - new_saccade = Saccade(new_saccade_duration, self.__last_fixation.positions.pop_last()[1], new_fixation.positions.pop_first()[1]) + new_saccade = Saccade(round(new_saccade_duration), self.__last_fixation.positions.pop_last()[1], new_fixation.positions.pop_first()[1]) - yield new_saccade_ts, new_saccade + yield round(new_saccade_ts), new_saccade self.__last_fixation = new_fixation self.__last_fixation_ts = new_fixation_ts - yield new_fixation_ts, new_fixation + yield round(new_fixation_ts), new_fixation # dispersion too wide : consider next gaze position else: @@ -313,14 +306,14 @@ class PointerBasedVisualScan(VisualScanGenerator): } # store where the aoi is looked - self.__step_dict[name]['look_at'][ts_current] = aoi.look_at(gaze_position).tolist() + self.__step_dict[name]['look_at'][round(ts_current)] = aoi.look_at(gaze_position) elif name in self.__step_dict.keys(): ts_start = self.__step_dict[name]['start'] # aoi stops to be looked - yield ts_start, VisualScanStep(ts_current - ts_start, name, self.__step_dict[name]['look_at']) + yield round(ts_start), VisualScanStep(round(ts_current - ts_start), name, self.__step_dict[name]['look_at']) # forget the aoi del self.__step_dict[name] diff --git a/src/argaze/utils/export_tobii_segment_aruco_visual_scan.py b/src/argaze/utils/export_tobii_segment_aruco_visual_scan.py index 02f5cb2..466f0b3 100644 --- a/src/argaze/utils/export_tobii_segment_aruco_visual_scan.py +++ b/src/argaze/utils/export_tobii_segment_aruco_visual_scan.py @@ -110,8 +110,8 @@ def main(): closest_gaze_ts, closest_gaze_position = tobii_ts_gaze_positions.pop_first_until(video_ts) # Draw gaze position - gaze_position = GazeFeatures.GazePosition(int(closest_gaze_position.gp[0] * video_frame.width), int(closest_gaze_position.gp[1] * video_frame.height)) - cv.circle(video_frame.matrix, gaze_position.as_tuple(), 4, (0, 255, 255), -1) + gaze_position = (int(closest_gaze_position.gp[0] * video_frame.width), int(closest_gaze_position.gp[1] * video_frame.height)) + cv.circle(video_frame.matrix, gaze_position, 4, (0, 255, 255), -1) # Store gaze position at this time in millisecond ts_gaze_positions[video_ts/1000] = gaze_position diff --git a/src/argaze/utils/export_tobii_segment_movements.py b/src/argaze/utils/export_tobii_segment_movements.py index 624d758..1c741ab 100644 --- a/src/argaze/utils/export_tobii_segment_movements.py +++ b/src/argaze/utils/export_tobii_segment_movements.py @@ -59,7 +59,7 @@ def main(): generic_ts_gaze_positions = GazeFeatures.TimeStampedGazePositions() for ts, tobii_data in tobii_ts_gaze_positions.items(): - generic_data = GazeFeatures.GazePosition(tobii_data.gp[0] * tobii_segment_video.get_width(), tobii_data.gp[1] * tobii_segment_video.get_height()) + generic_data = (int(tobii_data.gp[0] * tobii_segment_video.get_width()), int(tobii_data.gp[1] * tobii_segment_video.get_height())) generic_ts_gaze_positions[ts/1000] = generic_data print(f'Dispersion threshold: {args.dispersion_threshold}') diff --git a/src/argaze/utils/live_tobii_aruco_aois.py b/src/argaze/utils/live_tobii_aruco_aois.py index 8ad458b..4f00dea 100644 --- a/src/argaze/utils/live_tobii_aruco_aois.py +++ b/src/argaze/utils/live_tobii_aruco_aois.py @@ -83,8 +83,8 @@ def main(): earliest_ts, earliest_gaze_position = past_gaze_positions.pop_first_until(video_ts) # Draw gaze position - gaze_position = GazeFeatures.GazePosition(int(earliest_gaze_position.gp[0] * video_frame.width), int(earliest_gaze_position.gp[1] * video_frame.height)) - cv.circle(video_frame.matrix, gaze_position.as_tuple(), 4, (0, 255, 255), -1) + gaze_position = (int(earliest_gaze_position.gp[0] * video_frame.width), int(earliest_gaze_position.gp[1] * video_frame.height)) + cv.circle(video_frame.matrix, gaze_position, 4, (0, 255, 255), -1) # Wait for gaze position except (AttributeError, ValueError): diff --git a/src/argaze/utils/live_tobii_session.py b/src/argaze/utils/live_tobii_session.py index 561dc6c..f71b18f 100644 --- a/src/argaze/utils/live_tobii_session.py +++ b/src/argaze/utils/live_tobii_session.py @@ -56,8 +56,8 @@ def main(): earliest_ts, earliest_gaze_position = past_gaze_positions.pop_first_until(video_ts) # Draw gaze position - gaze_position = GazeFeatures.GazePosition(int(earliest_gaze_position.gp[0] * video_frame.width), int(earliest_gaze_position.gp[1] * video_frame.height)) - cv.circle(video_frame.matrix, gaze_position.as_tuple(), 4, (0, 255, 255), -1) + gaze_position = (int(earliest_gaze_position.gp[0] * video_frame.width), int(earliest_gaze_position.gp[1] * video_frame.height)) + cv.circle(video_frame.matrix, gaze_position, 4, (0, 255, 255), -1) # Wait for gaze position except (AttributeError, ValueError): diff --git a/src/argaze/utils/replay_tobii_session.py b/src/argaze/utils/replay_tobii_session.py index 62c2a7e..bbde63f 100644 --- a/src/argaze/utils/replay_tobii_session.py +++ b/src/argaze/utils/replay_tobii_session.py @@ -54,8 +54,8 @@ def main(): closest_gaze_ts, closest_gaze_position = tobii_ts_gaze_positions.pop_first_until(video_ts) # Draw gaze position - gaze_position = GazeFeatures.GazePosition(int(closest_gaze_position.gp[0] * video_frame.width), int(closest_gaze_position.gp[1] * video_frame.height)) - cv.circle(video_frame.matrix, gaze_position.as_tuple(), 4, (0, 255, 255), -1) + gaze_position = (int(closest_gaze_position.gp[0] * video_frame.width), int(closest_gaze_position.gp[1] * video_frame.height)) + cv.circle(video_frame.matrix, gaze_position, 4, (0, 255, 255), -1) # Wait for gaze position except ValueError: -- cgit v1.1