aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/argaze/utils/contexts/PupilLabs.py144
-rw-r--r--src/argaze/utils/demo/pupillabs_live_stream_context_setup.json10
2 files changed, 154 insertions, 0 deletions
diff --git a/src/argaze/utils/contexts/PupilLabs.py b/src/argaze/utils/contexts/PupilLabs.py
new file mode 100644
index 0000000..9265f2c
--- /dev/null
+++ b/src/argaze/utils/contexts/PupilLabs.py
@@ -0,0 +1,144 @@
+"""Handle network connection to Pupil Labs devices. Tested with Pupil Invisible.
+ 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 <http://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.ArContext):
+
+ @DataFeatures.PipelineStepInit
+ def __init__(self, **kwargs):
+
+ # Init ArContext class
+ super().__init__()
+
+ def __enter__(self):
+
+ logging.info('Pupil-Labs Device connexion starts...')
+
+ # Create stop event
+ self.__stop_event = threading.Event()
+
+ # 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 Device')
+
+ while not self.__stop_event.is_set():
+
+ 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 Device')
+
+ while not self.__stop_event.is_set():
+
+ 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_event.set()
+
+ # Stop streaming
+ threading.Thread.join(self.__gaze_thread)
+ threading.Thread.join(self.__video_thread) \ No newline at end of file
diff --git a/src/argaze/utils/demo/pupillabs_live_stream_context_setup.json b/src/argaze/utils/demo/pupillabs_live_stream_context_setup.json
new file mode 100644
index 0000000..3837a19
--- /dev/null
+++ b/src/argaze/utils/demo/pupillabs_live_stream_context_setup.json
@@ -0,0 +1,10 @@
+{
+ "argaze.utils.contexts.PupilLabs.LiveStream" : {
+ "name": "PupilLabs",
+ "pipeline": "aruco_markers_pipeline.json",
+ "image_parameters": {
+ "draw_times": true,
+ "draw_exceptions": true
+ }
+ }
+} \ No newline at end of file