From 3aa72812a5f45dce412f2f355dcac430e440564b Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Tue, 9 May 2023 12:08:41 +0200 Subject: Adding a new utils script --- src/argaze/utils/README.md | 13 ++- src/argaze/utils/demo_gaze_features_run.py | 171 +++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+), 3 deletions(-) create mode 100644 src/argaze/utils/demo_gaze_features_run.py (limited to 'src') diff --git a/src/argaze/utils/README.md b/src/argaze/utils/README.md index c1b09a5..e73b75f 100644 --- a/src/argaze/utils/README.md +++ b/src/argaze/utils/README.md @@ -36,12 +36,19 @@ python ./src/argaze/utils/camera_calibrate.py 7 5 5 3 DICT_APRILTAG_16h5 -d DEVI Load AR environment from **setup.json** file, detect ArUco markers into camera device (-d DEVICE) frames and estimate envirnoment pose. ``` -python ./src/argaze/utils/demo_environment_run.py ./src/argaze/utils/demo_environment/setup.json -d DEVICE +python ./src/argaze/utils/demo_ar_features_run.py ./src/argaze/utils/demo_environment/setup.json -d DEVICE ``` .. note:: This demonstration assumes that camera calibration step is done and a **calibration.json** has been exported into *./src/argaze/utils/demo_environment/* folder. - .. note:: - Use **A3_demo.pdf** file located in *./src/argaze/utils/demo_environment/* folder ready to be printed on A3 paper sheet. \ No newline at end of file + Use **A3_demo.pdf** file located in *./src/argaze/utils/demo_environment/* folder ready to be printed on A3 paper sheet. + +# Gaze features demonstration + +Simulate gaze position using mouse pointer to illustrate gaze features. + +``` +python ./src/argaze/utils/gaze_analysis.py ./src/argaze/utils/demo_environment/setup.json +``` \ No newline at end of file diff --git a/src/argaze/utils/demo_gaze_features_run.py b/src/argaze/utils/demo_gaze_features_run.py new file mode 100644 index 0000000..0efb232 --- /dev/null +++ b/src/argaze/utils/demo_gaze_features_run.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python + +import argparse +import time +import threading + +from argaze import ArFeatures, GazeFeatures +from argaze.GazeAnalysis import * + +import cv2 +import numpy + +def main(): + """ + Load AR environment from .json file to project AOI scene on screen and use mouse pointer to simulate gaze positions. + """ + + # Manage arguments + parser = argparse.ArgumentParser(description=main.__doc__.split('-')[0]) + parser.add_argument('environment', metavar='ENVIRONMENT', type=str, help='ar environment filepath') + + parser.add_argument('-dev', '--deviation_max_threshold', metavar='DEVIATION_MAX_THRESHOLD', type=int, default=50, help='maximal distance for fixation identification in pixel') + parser.add_argument('-dmin', '--duration_min_threshold', metavar='DURATION_MIN_THRESHOLD', type=int, default=200, help='minimal duration for fixation identification in millisecond') + parser.add_argument('-s', '--window-size', metavar='WINDOW_SIZE', type=tuple, default=(1920, 1080), help='size of window in pixel') + args = parser.parse_args() + + # Load AR enviroment + demo_environment = ArFeatures.ArEnvironment.from_json(args.environment) + + # Access to main AR scene + demo_scene = demo_environment.scenes["AR Scene Demo"] + + # Project AOI scene onto Full HD screen + aoi_scene_projection = demo_scene.orthogonal_projection * args.window_size + + # Create a window to display AR environment + window_name = "AOI Scene" + cv2.namedWindow(window_name, cv2.WINDOW_AUTOSIZE) + + # Init gaze movement identification + gaze_position = GazeFeatures.GazePosition() + gaze_movement_identifier = DispersionBasedGazeMovementIdentifier.GazeMovementIdentifier(args.deviation_max_threshold, args.duration_min_threshold) + gaze_movement_lock = threading.Lock() + + # Init timestamp + start_ts = time.time() + + # Update pointer position + def on_mouse_event(event, x, y, flags, param): + + nonlocal gaze_position + + # Edit millisecond timestamp + data_ts = int((time.time() - start_ts) * 1e3) + + # Update gaze position with mouse pointer position + gaze_position = GazeFeatures.GazePosition((x, y)) + + # Don't identify gaze movement while former identification is exploited in video loop + if gaze_movement_lock.locked(): + return + + # Lock gaze movement exploitation + gaze_movement_lock.acquire() + + # Identify gaze movement + gaze_movement = gaze_movement_identifier.identify(data_ts, gaze_position) + + if isinstance(gaze_movement, DispersionBasedGazeMovementIdentifier.Fixation): + + # Does the fixation match an AOI? + for name, aoi in aoi_scene_projection.items(): + + _, _, circle_ratio = aoi.circle_intersection(gaze_movement.centroid, args.deviation_max_threshold) + + if circle_ratio > 0.25: + + if name != 'Screen': + + print(f'{data_ts}: gaze step on {name} aoi') + #gaze_step = GazeStep(gaze_movement, name) + + # Unlock gaze movement exploitation + gaze_movement_lock.release() + + return + + # Attach mouse callback to window + cv2.setMouseCallback(window_name, on_mouse_event) + + # Waiting for 'ctrl+C' interruption + try: + + # Analyse mouse positions + while True: + + aoi_matrix = numpy.full((int(args.window_size[1]), int(args.window_size[0]), 3), 0, dtype=numpy.uint8) + + # Lock gaze movement identification + gaze_movement_lock.acquire() + + # Check fixation identification + if gaze_movement_identifier.current_fixation != None: + + current_fixation = gaze_movement_identifier.current_fixation + + # Draw looked AOI + aoi_scene_projection.draw_circlecast(aoi_matrix, current_fixation.centroid, current_fixation.deviation_max) + + # Draw current fixation + cv2.circle(aoi_matrix, (int(current_fixation.centroid[0]), int(current_fixation.centroid[1])), int(current_fixation.deviation_max), (0, 255, 0), 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) + + # Draw movement from start to next + cv2.line(aoi_matrix, start_gaze_position, next_gaze_position, (0, 55, 55), 1) + + else: + + # Draw pointer as gaze position + gaze_position.draw(aoi_matrix, draw_precision=False) + + # Draw AOI scene projection + aoi_scene_projection.draw(aoi_matrix, color=(0, 0, 255)) + + # Check saccade identification + if gaze_movement_identifier.current_saccade != None: + + current_saccade = gaze_movement_identifier.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 + + # 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) + + # Unlock gaze movement identification + gaze_movement_lock.release() + + # Draw frame + cv2.imshow(window_name, aoi_matrix) + + # Stop calibration by pressing 'Esc' key + if cv2.waitKey(10) == 27: + break + + # Stop calibration on 'ctrl+C' interruption + except KeyboardInterrupt: + pass + + # Stop frame display + cv2.destroyAllWindows() + +if __name__ == '__main__': + + main() -- cgit v1.1