aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/user_guide/utils/demonstrations_scripts.md10
-rw-r--r--docs/user_guide/utils/ready-made_scripts.md4
-rw-r--r--src/argaze/__main__.py253
-rw-r--r--utils/processTobiiRecords.sh2
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"