aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDamien Mouratille2024-08-23 15:35:37 +0200
committerDamien Mouratille2024-08-23 15:35:37 +0200
commite7cace52a6c1e0af88b715fce99c46fd48aa8bcc (patch)
tree8dbab3de75b8c5512e50e7d2a54b8274394114fe
parent4325928bddea273592c5e315721c1cd179746e31 (diff)
downloadargaze-e7cace52a6c1e0af88b715fce99c46fd48aa8bcc.zip
argaze-e7cace52a6c1e0af88b715fce99c46fd48aa8bcc.tar.gz
argaze-e7cace52a6c1e0af88b715fce99c46fd48aa8bcc.tar.bz2
argaze-e7cace52a6c1e0af88b715fce99c46fd48aa8bcc.tar.xz
Add Pupil Labs Neon device
-rw-r--r--docs/user_guide/eye_tracking_context/context_modules/pupil_labs_invisible.md32
-rw-r--r--docs/user_guide/eye_tracking_context/context_modules/pupil_labs_neon.md (renamed from docs/user_guide/eye_tracking_context/context_modules/pupil_labs.md)10
-rw-r--r--docs/user_guide/eye_tracking_context/context_modules/tobii_pro_glasses_3.md32
-rw-r--r--mkdocs.yml4
-rw-r--r--src/argaze/utils/contexts/PupilLabsNeon.py140
-rw-r--r--src/argaze/utils/demo/aruco_markers_pipeline.json6
-rw-r--r--src/argaze/utils/demo/gaze_analysis_pipeline.json6
-rw-r--r--src/argaze/utils/demo/pupillabs_neon_live_stream_context.json6
8 files changed, 224 insertions, 12 deletions
diff --git a/docs/user_guide/eye_tracking_context/context_modules/pupil_labs_invisible.md b/docs/user_guide/eye_tracking_context/context_modules/pupil_labs_invisible.md
new file mode 100644
index 0000000..1f4a94f
--- /dev/null
+++ b/docs/user_guide/eye_tracking_context/context_modules/pupil_labs_invisible.md
@@ -0,0 +1,32 @@
+Pupil Labs Invisible
+==========
+
+ArGaze provides a ready-made context to work with Pupil Labs Invisible device.
+
+To select a desired context, the JSON samples have to be edited and saved inside an [ArContext configuration](../configuration_and_execution.md) file.
+Notice that the *pipeline* entry is mandatory.
+
+```json
+{
+ JSON sample
+ "pipeline": ...
+}
+```
+
+Read more about [ArContext base class in code reference](../../../argaze.md/#argaze.ArFeatures.ArContext).
+
+## Live Stream
+
+::: argaze.utils.contexts.PupilLabsInvisible.LiveStream
+
+### JSON sample
+
+```json
+{
+ "argaze.utils.contexts.PupilLabsInvisible.LiveStream": {
+ "name": "Pupil Labs Invisible live stream",
+ "project": "my_experiment",
+ "pipeline": ...
+ }
+}
+```
diff --git a/docs/user_guide/eye_tracking_context/context_modules/pupil_labs.md b/docs/user_guide/eye_tracking_context/context_modules/pupil_labs_neon.md
index d2ec336..535f5d5 100644
--- a/docs/user_guide/eye_tracking_context/context_modules/pupil_labs.md
+++ b/docs/user_guide/eye_tracking_context/context_modules/pupil_labs_neon.md
@@ -1,7 +1,7 @@
-Pupil Labs
+Pupil Labs Neon
==========
-ArGaze provides a ready-made context to work with Pupil Labs devices.
+ArGaze provides a ready-made context to work with Pupil Labs Neon device.
To select a desired context, the JSON samples have to be edited and saved inside an [ArContext configuration](../configuration_and_execution.md) file.
Notice that the *pipeline* entry is mandatory.
@@ -17,14 +17,14 @@ Read more about [ArContext base class in code reference](../../../argaze.md/#arg
## Live Stream
-::: argaze.utils.contexts.PupilLabs.LiveStream
+::: argaze.utils.contexts.PupilLabsNeon.LiveStream
### JSON sample
```json
{
- "argaze.utils.contexts.PupilLabs.LiveStream": {
- "name": "Pupil Labs live stream",
+ "argaze.utils.contexts.PupilLabsNeon.LiveStream": {
+ "name": "Pupil Labs Neon live stream",
"project": "my_experiment",
"pipeline": ...
}
diff --git a/docs/user_guide/eye_tracking_context/context_modules/tobii_pro_glasses_3.md b/docs/user_guide/eye_tracking_context/context_modules/tobii_pro_glasses_3.md
new file mode 100644
index 0000000..3d37fcc
--- /dev/null
+++ b/docs/user_guide/eye_tracking_context/context_modules/tobii_pro_glasses_3.md
@@ -0,0 +1,32 @@
+Tobii Pro Glasses 3
+===================
+
+ArGaze provides a ready-made context to work with Tobii Pro Glasses 3 devices.
+
+To select a desired context, the JSON samples have to be edited and saved inside an [ArContext configuration](../configuration_and_execution.md) file.
+Notice that the *pipeline* entry is mandatory.
+
+```json
+{
+ JSON sample
+ "pipeline": ...
+}
+```
+
+Read more about [ArContext base class in code reference](../../../argaze.md/#argaze.ArFeatures.ArContext).
+
+## Live Stream
+
+::: argaze.utils.contexts.TobiiProGlasses3.LiveStream
+
+### JSON sample
+
+```json
+{
+ "argaze.utils.contexts.TobiiProGlasses3.LiveStream": {
+ "name": "Tobii Pro Glasses 3 live stream",
+ "pipeline": ...
+ }
+}
+```
+
diff --git a/mkdocs.yml b/mkdocs.yml
index 8aadb7d..c35df15 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -9,7 +9,9 @@ nav:
- user_guide/eye_tracking_context/configuration_and_execution.md
- Context Modules:
- user_guide/eye_tracking_context/context_modules/tobii_pro_glasses_2.md
- - user_guide/eye_tracking_context/context_modules/pupil_labs.md
+ - user_guide/eye_tracking_context/context_modules/tobii_pro_glasses_3.md
+ - user_guide/eye_tracking_context/context_modules/pupil_lab_invisible.md
+ - user_guide/eye_tracking_context/context_modules/pupil_lab_neon.md
- user_guide/eye_tracking_context/context_modules/opencv.md
- user_guide/eye_tracking_context/context_modules/random.md
- Advanced Topics:
diff --git a/src/argaze/utils/contexts/PupilLabsNeon.py b/src/argaze/utils/contexts/PupilLabsNeon.py
new file mode 100644
index 0000000..e7d1f47
--- /dev/null
+++ b/src/argaze/utils/contexts/PupilLabsNeon.py
@@ -0,0 +1,140 @@
+"""Handle network connection to Pupil Labs devices. Tested with Pupil Neon.
+ Based on Pupil Labs' Realtime Python API."""
+
+"""
+This program is free software: you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <https://www.gnu.org/licenses/>.
+"""
+
+__author__ = "Damien Mouratille"
+__credits__ = []
+__copyright__ = "Copyright 2024, Ecole Nationale de l'Aviation Civile (ENAC)"
+__license__ = "GPLv3"
+
+import sys
+import logging
+import time
+
+import threading
+from dataclasses import dataclass
+
+from argaze import ArFeatures, DataFeatures, GazeFeatures
+from argaze.utils import UtilsFeatures
+
+import numpy
+import cv2
+
+from pupil_labs.realtime_api.simple import discover_one_device
+
+
+class LiveStream(ArFeatures.DataCaptureContext):
+
+ @DataFeatures.PipelineStepInit
+ def __init__(self, **kwargs):
+
+ # Init DataCaptureContext class
+ super().__init__()
+
+ def __enter__(self):
+
+ logging.info('Pupil-Labs Neon connexion starts...')
+
+ # Init timestamp
+ self.__start_time = time.time()
+
+ # Look for devices. Returns as soon as it has found the first device.
+ self.__device = discover_one_device(max_search_duration_seconds=10)
+
+ if self.__device is None:
+ logging.info('No device found. Exit!')
+ raise SystemExit(-1)
+ else:
+ logging.info('Device found. Stream loading.')
+
+ # Open gaze stream
+ self.__gaze_thread = threading.Thread(target=self.__stream_gaze)
+
+ logging.debug('> starting gaze thread...')
+
+ self.__gaze_thread.start()
+
+ # Open video stream
+ self.__video_thread = threading.Thread(target=self.__stream_video)
+
+ logging.debug('> starting video thread...')
+
+ self.__video_thread.start()
+
+ return self
+
+ def __stream_gaze(self):
+ """Stream gaze."""
+
+ logging.debug('Stream gaze from Pupil Neon')
+
+ while self.is_running():
+
+ try:
+ while True:
+ gaze = self.__device.receive_gaze_datum()
+
+ gaze_timestamp = int((gaze.timestamp_unix_seconds - self.__start_time) * 1e3)
+
+ logging.debug('Gaze received at %i timestamp', gaze_timestamp)
+
+ # When gaze position is valid
+ if gaze.worn is True:
+
+ self._process_gaze_position(
+ timestamp=gaze_timestamp,
+ x=int(gaze.x),
+ y=int(gaze.y))
+ else:
+ # Process empty gaze position
+ logging.debug('Not worn at %i timestamp', gaze_timestamp)
+
+ self._process_gaze_position(timestamp=gaze_timestamp)
+
+ except KeyboardInterrupt:
+ pass
+
+ def __stream_video(self):
+ """Stream video."""
+
+ logging.debug('Stream video from Pupil Neon')
+
+ while self.is_running():
+
+ try:
+ while True:
+ scene_frame, frame_datetime = self.__device.receive_scene_video_frame()
+
+ scene_timestamp = int((frame_datetime - self.__start_time) * 1e3)
+
+ logging.debug('Video received at %i timestamp', scene_timestamp)
+
+ self._process_camera_image(
+ timestamp=scene_timestamp,
+ image=scene_frame)
+
+ except KeyboardInterrupt:
+ pass
+
+ @DataFeatures.PipelineStepExit
+ def __exit__(self, exception_type, exception_value, exception_traceback):
+
+ logging.debug('Pupil-Labs context stops...')
+
+ # Close data stream
+ self.stop()
+
+ # Stop streaming
+ threading.Thread.join(self.__gaze_thread)
+ threading.Thread.join(self.__video_thread)
diff --git a/src/argaze/utils/demo/aruco_markers_pipeline.json b/src/argaze/utils/demo/aruco_markers_pipeline.json
index 8221cec..9dc8327 100644
--- a/src/argaze/utils/demo/aruco_markers_pipeline.json
+++ b/src/argaze/utils/demo/aruco_markers_pipeline.json
@@ -1,7 +1,7 @@
{
"argaze.ArUcoMarker.ArUcoCamera.ArUcoCamera": {
"name": "Head-mounted camera",
- "size": [1088, 1080],
+ "size": [1600, 1200],
"copy_background_into_scenes_frames": true,
"aruco_detector": {
"dictionary": "DICT_APRILTAG_16h5",
@@ -56,7 +56,7 @@
},
"frames": {
"GrayRectangle": {
- "size": [1088, 1080],
+ "size": [1600, 1200],
"background": "frame_background.jpg",
"gaze_movement_identifier": {
"argaze.GazeAnalysis.DispersionThresholdIdentification.GazeMovementIdentifier": {
@@ -71,7 +71,7 @@
"argaze.GazeAnalysis.Basic.ScanPathAnalyzer": {},
"argaze.GazeAnalysis.KCoefficient.ScanPathAnalyzer": {},
"argaze.GazeAnalysis.NearestNeighborIndex.ScanPathAnalyzer": {
- "size": [1088, 1080]
+ "size": [1600, 1200]
},
"argaze.GazeAnalysis.ExploreExploitRatio.ScanPathAnalyzer": {
"short_fixation_duration_threshold": 0
diff --git a/src/argaze/utils/demo/gaze_analysis_pipeline.json b/src/argaze/utils/demo/gaze_analysis_pipeline.json
index 6e23321..cc182ce 100644
--- a/src/argaze/utils/demo/gaze_analysis_pipeline.json
+++ b/src/argaze/utils/demo/gaze_analysis_pipeline.json
@@ -1,7 +1,7 @@
{
"argaze.ArFeatures.ArFrame": {
"name": "GrayRectangle",
- "size": [1088, 1080],
+ "size": [1600, 1200],
"background": "frame_background.jpg",
"gaze_movement_identifier": {
"argaze.GazeAnalysis.DispersionThresholdIdentification.GazeMovementIdentifier": {
@@ -126,8 +126,8 @@
},
"recorders.FrameImageRecorder": {
"path": "_export/records/video.mp4",
- "width": 1920,
- "height": 1080,
+ "width": 1600,
+ "height": 1200,
"fps": 15
}
}
diff --git a/src/argaze/utils/demo/pupillabs_neon_live_stream_context.json b/src/argaze/utils/demo/pupillabs_neon_live_stream_context.json
new file mode 100644
index 0000000..a87c30e
--- /dev/null
+++ b/src/argaze/utils/demo/pupillabs_neon_live_stream_context.json
@@ -0,0 +1,6 @@
+{
+ "argaze.utils.contexts.PupilLabsNeon.LiveStream" : {
+ "name": "PupilLabs Neon",
+ "pipeline": "aruco_markers_pipeline.json"
+ }
+} \ No newline at end of file