From f4a78005a3fe7e8b0019ad16dd83c76992933f87 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Tue, 28 May 2024 16:17:51 +0200 Subject: Defining load and patch as __main__.py module commands. Updating documentation. --- docs/user_guide/utils/demonstrations_scripts.md | 10 +- docs/user_guide/utils/ready-made_scripts.md | 4 +- src/argaze/__main__.py | 253 ++++++++++++++---------- utils/processTobiiRecords.sh | 2 +- 4 files changed, 161 insertions(+), 108 deletions(-) diff --git a/docs/user_guide/utils/demonstrations_scripts.md b/docs/user_guide/utils/demonstrations_scripts.md index d83915f..f293980 100644 --- a/docs/user_guide/utils/demonstrations_scripts.md +++ b/docs/user_guide/utils/demonstrations_scripts.md @@ -14,7 +14,7 @@ Collection of command-line scripts for demonstration purpose. Load **random_context.json** file to analyze random gaze positions: ```shell -python -m argaze ./src/argaze/utils/demo/random_context.json +python -m argaze load ./src/argaze/utils/demo/random_context.json ``` ## OpenCV window context @@ -22,7 +22,7 @@ python -m argaze ./src/argaze/utils/demo/random_context.json Load **opencv_window_context.json** file to analyze mouse pointer positions over OpenCV window: ```shell -python -m argaze ./src/argaze/utils/demo/opencv_window_context.json +python -m argaze load ./src/argaze/utils/demo/opencv_window_context.json ``` ## Tobii Pro Glasses 2 @@ -58,7 +58,7 @@ Edit **tobii_live_stream_context.json** file as to select exisiting IP *address* Then, load **tobii_live_stream_context.json** file to find ArUco marker into camera image and, project gaze positions into AOI: ```shell -python -m argaze ./src/argaze/utils/demo/tobii_live_stream_context.json +python -m argaze load ./src/argaze/utils/demo/tobii_live_stream_context.json ``` ### Post-processing context @@ -81,7 +81,7 @@ Edit **tobii_post_processing_context.json** file to select an existing Tobii *se Then, load **tobii_post_processing_context.json** file to find ArUco marker into camera image and, project gaze positions into AOI: ```shell -python -m argaze ./src/argaze/utils/demo/tobii_post_processing_context.json +python -m argaze load ./src/argaze/utils/demo/tobii_post_processing_context.json ``` ## Pupil Invisible @@ -94,5 +94,5 @@ python -m argaze ./src/argaze/utils/demo/tobii_post_processing_context.json Load **pupillabs_live_stream_context.json** file to find ArUco marker into camera image and, project gaze positions into AOI: ```shell -python -m argaze ./src/argaze/utils/demo/pupillabs_live_stream_context.json +python -m argaze load ./src/argaze/utils/demo/pupillabs_live_stream_context.json ``` diff --git a/docs/user_guide/utils/ready-made_scripts.md b/docs/user_guide/utils/ready-made_scripts.md index 5f521e1..5d34bff 100644 --- a/docs/user_guide/utils/ready-made_scripts.md +++ b/docs/user_guide/utils/ready-made_scripts.md @@ -14,7 +14,7 @@ Collection of command-line scripts to provide useful features. Load and execute any ArContext from a JSON CONFIGURATION file ```shell -python -m argaze CONFIGURATION +python -m argaze load CONFIGURATION ``` ### Send command @@ -22,7 +22,7 @@ python -m argaze CONFIGURATION Use -p option to enable pipe communication at given address: ```shell -python -m argaze CONFIGURATION -p /tmp/argaze +python -m argaze load CONFIGURATION -p /tmp/argaze ``` Open another tab in the **same** Terminal window then, you can send any Python command into the pipe. diff --git a/src/argaze/__main__.py b/src/argaze/__main__.py index 925adf0..cc83d1d 100644 --- a/src/argaze/__main__.py +++ b/src/argaze/__main__.py @@ -1,4 +1,4 @@ -"""Load and execute ArContext configuration.""" +"""ArGaze module commands.""" """ This program is free software: you can redistribute it and/or modify it under @@ -31,164 +31,217 @@ from .utils.UtilsFeatures import print_progress_bar import cv2 -# Manage arguments -parser = argparse.ArgumentParser(description=__doc__.split('-')[0]) -parser.add_argument('context_file', metavar='CONTEXT_FILE', type=str, help='JSON context filepath') -parser.add_argument('-v', '--verbose', action='store_true', default=False, help='enable verbose mode to print information in console') -parser.add_argument('-p', '--pipe_path', metavar='PIPE_PATH', type=str, default=None, help='enable pipe communication to execute external commands') -parser.add_argument('-x', '--display', metavar='DISPLAY', nargs="+", type=int, default=None, help='adapt windows to display dimension') -parser.add_argument('--no-window', action='store_true', default=False, help='disable window mode') -args = parser.parse_args() +def load_context(args): + """Load and execute ArContext configuration.""" -# Manage logging -logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG if args.verbose else logging.INFO) + # Manage logging + logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG if args.verbose else logging.INFO) -# Manage pipe communication -if args.pipe_path is not None: + # Manage pipe communication + if args.pipe_path is not None: - # Create FIFO - if not os.path.exists(args.pipe_path): + # Create FIFO + if not os.path.exists(args.pipe_path): - os.mkfifo(args.pipe_path) + os.mkfifo(args.pipe_path) - # Open the fifo in non-blocking mode or it will stalls until someone opens it for writting - pipe_file = os.open(args.pipe_path, os.O_RDONLY | os.O_NONBLOCK) + # Open the fifo in non-blocking mode or it will stalls until someone opens it for writting + pipe_file = os.open(args.pipe_path, os.O_RDONLY | os.O_NONBLOCK) - logging.info('%s pipe opened', args.pipe_path) + logging.info('%s pipe opened', args.pipe_path) -def display(name, image, factor): - """Adapt image to display dimension.""" + def display(name, image, factor): + """Adapt image to display dimension.""" - if args.display is not None: + if args.display is not None: - display_size = tuple(args.display) - height, width, _ = image.shape - image_ratio = width/height + display_size = tuple(args.display) + height, width, _ = image.shape + image_ratio = width/height - new_image_size = (int(display_size[1] * factor * image_ratio), int(display_size[1] * factor)) + new_image_size = (int(display_size[1] * factor * image_ratio), int(display_size[1] * factor)) - cv2.imshow(name, cv2.resize(image, dsize=new_image_size, interpolation=cv2.INTER_LINEAR)) + cv2.imshow(name, cv2.resize(image, dsize=new_image_size, interpolation=cv2.INTER_LINEAR)) - else: + else: - cv2.imshow(name, image) + cv2.imshow(name, image) -# Load context from JSON file -with load(args.context_file) as context: + # Load context from JSON file + with load(args.context_file) as context: - # Loaded object must be a subclass of ArContext - if not issubclass(type(context), ArContext): + # Loaded object must be a subclass of ArContext + if not issubclass(type(context), ArContext): - raise TypeError('Loaded object is not a subclass of ArContext') + raise TypeError('Loaded object is not a subclass of ArContext') - if args.verbose: + if args.verbose: - print(context) + print(context) - if not args.no_window: + if not args.no_window: - # Create a window to display context - cv2.namedWindow(context.name, cv2.WINDOW_AUTOSIZE) + # Create a window to display context + cv2.namedWindow(context.name, cv2.WINDOW_AUTOSIZE) - # Assess processing time - start_time = time.time() + # Assess processing time + start_time = time.time() - # Waiting for 'ctrl+C' interruption - with contextlib.suppress(KeyboardInterrupt), os.fdopen(pipe_file) if args.pipe_path is not None else contextlib.nullcontext() as pipe: + # Waiting for 'ctrl+C' interruption + with contextlib.suppress(KeyboardInterrupt), os.fdopen(pipe_file) if args.pipe_path is not None else contextlib.nullcontext() as pipe: - # Visualization loop - while context.is_running(): + # Visualization loop + while context.is_running(): - # Read message from pipe if required - if args.pipe_path is not None: + # Read message from pipe if required + if args.pipe_path is not None: - try: + try: - message = pipe.read().rstrip('\n') + message = pipe.read().rstrip('\n') - if message: + if message: - logging.info('%s pipe received: %s', args.pipe_path, message) + logging.info('%s pipe received: %s', args.pipe_path, message) - exec(message) + exec(message) - except Exception as e: + except Exception as e: - logging.error('%s', e) + logging.error('%s', e) - # Window mode on - if not args.no_window: + # Window mode on + if not args.no_window: - # Display context - display(context.name, context.image(), 0.75) + # Display context + display(context.name, context.image(), 0.75) - # Head-mounted eye tracker case: display environment frames image - if issubclass(type(context.pipeline), ArCamera): + # Head-mounted eye tracker case: display environment frames image + if issubclass(type(context.pipeline), ArCamera): - for scene_frame in context.pipeline.scene_frames(): + for scene_frame in context.pipeline.scene_frames(): - display(scene_frame.name, scene_frame.image(), 0.5) + display(scene_frame.name, scene_frame.image(), 0.5) - # Key interaction - key_pressed = cv2.waitKey(40) + # Key interaction + key_pressed = cv2.waitKey(40) - # Esc: close window - if key_pressed == 27: + # Esc: close window + if key_pressed == 27: - raise KeyboardInterrupt() + raise KeyboardInterrupt() - # Space bar: pause/resume pipeline processing - if key_pressed == 32: + # Space bar: pause/resume pipeline processing + if key_pressed == 32: - if context.is_paused(): + if context.is_paused(): - context.resume() + context.resume() - else: + else: - context.pause() + context.pause() - # Enter: start calibration - if key_pressed == 13: + # Enter: start calibration + if key_pressed == 13: - if issubclass(type(context), LiveProcessingContext): + if issubclass(type(context), LiveProcessingContext): - context.calibrate() + context.calibrate() - # Window mode off - else: + # Window mode off + else: - if issubclass(type(context), PostProcessingContext): + if issubclass(type(context), PostProcessingContext): - prefix = f'Progression' - suffix = f'| {int(context.progression*context.duration * 1e-3)}s in {int(time.time()-start_time)}s' + prefix = f'Progression' + suffix = f'| {int(context.progression*context.duration * 1e-3)}s in {int(time.time()-start_time)}s' - look_time, look_freq = context.process_gaze_position_performance() - suffix += f' | Look {look_time:.2f}ms at {look_freq}Hz' + look_time, look_freq = context.process_gaze_position_performance() + suffix += f' | Look {look_time:.2f}ms at {look_freq}Hz' - if issubclass(type(context.pipeline), ArCamera): + if issubclass(type(context.pipeline), ArCamera): - watch_time, watch_freq = context.process_camera_image_performance() - suffix += f' | Watch {int(watch_time)}ms at {watch_freq}Hz' + watch_time, watch_freq = context.process_camera_image_performance() + suffix += f' | Watch {int(watch_time)}ms at {watch_freq}Hz' - # Clear old longer print - suffix += ' ' + # Clear old longer print + suffix += ' ' - print_progress_bar(context.progression, 1., prefix = prefix, suffix = suffix, length = 50) + print_progress_bar(context.progression, 1., prefix = prefix, suffix = suffix, length = 50) - # Wait one second - time.sleep(1) + # Wait one second + time.sleep(1) - # Stop frame display - cv2.destroyAllWindows() + # Stop frame display + cv2.destroyAllWindows() - # Manage pipe communication - if args.pipe_path is not None: + # Manage pipe communication + if args.pipe_path is not None: + + # Remove pipe + if os.path.exists(args.pipe_path): + + os.remove(args.pipe_path) + + logging.info('%s pipe closed', args.pipe_path) + +def patch_file(args): + """ + Patch a JSON file according a JSON patch into a JSON output file. + """ + + # Open JSON files + with open(args.file) as file, open(args.patch) as patch, open(args.output, 'w', encoding='utf-8') as output: + + import collections.abc + import json - # Remove pipe - if os.path.exists(args.pipe_path): + # Load unique object + file_data = json.load(file) + patch_data = json.load(patch) - os.remove(args.pipe_path) + def update(d, u): - logging.info('%s pipe closed', args.pipe_path) + for k, v in u.items(): + + if isinstance(v, collections.abc.Mapping): + + d[k] = update(d.get(k, {}), v) + + elif v is None: + + del d[k] + + else: + + d[k] = v + + return d + + new_data = update(file_data, patch_data) + + # Write new data + json.dump(new_data, output, ensure_ascii=False, indent=' ') + +# Manage arguments +parser = argparse.ArgumentParser(description=__doc__.split('-')[0]) +subparsers = parser.add_subparsers(help='sub-command help') + +parser_load = subparsers.add_parser('load', help=load_context.__doc__) +parser_load.add_argument('context_file', metavar='CONTEXT_FILE', type=str, help='JSON context filepath') +parser_load.add_argument('-v', '--verbose', action='store_true', default=False, help='enable verbose mode to print information in console') +parser_load.add_argument('-p', '--pipe_path', metavar='PIPE_PATH', type=str, default=None, help='enable pipe communication to execute external commands') +parser_load.add_argument('-x', '--display', metavar='DISPLAY', nargs="+", type=int, default=None, help='adapt windows to display dimension') +parser_load.add_argument('--no-window', action='store_true', default=False, help='disable window mode') +parser_load.set_defaults(func=load_context) + +parser_patch = subparsers.add_parser('patch', help=patch_file.__doc__) +parser_patch.add_argument('file', metavar='FILE', type=str, default=None, help='json file path') +parser_patch.add_argument('patch', metavar='PATCH', type=str, default=None, help='json patch path') +parser_patch.add_argument('output', metavar='OUTPUT', type=str, default=None, help='json output path') +parser_patch.set_defaults(func=patch_file) + +args = parser.parse_args() +args.func(args) diff --git a/utils/processTobiiRecords.sh b/utils/processTobiiRecords.sh index 8ee5773..0cc3eb4 100644 --- a/utils/processTobiiRecords.sh +++ b/utils/processTobiiRecords.sh @@ -106,7 +106,7 @@ function process_segment() { # Launch argaze with modified context echo "*** ArGaze processing starts" - python -m argaze $context_file + python -m argaze load $context_file echo "*** ArGaze processing ends" -- cgit v1.1