From 303890b4513c447c69c85a7ed7777e17679eb1af Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Mon, 28 Mar 2022 12:35:35 +0200 Subject: Improving segment data and video loading. --- src/argaze/TobiiGlassesPro2/README.md | 5 +- src/argaze/TobiiGlassesPro2/TobiiEntities.py | 67 +++++++++++++++------- .../utils/analyse_tobii_segment_fixations.py | 19 ++++-- src/argaze/utils/map_tobii_gaze_on_camera.py | 0 4 files changed, 62 insertions(+), 29 deletions(-) create mode 100644 src/argaze/utils/map_tobii_gaze_on_camera.py (limited to 'src') diff --git a/src/argaze/TobiiGlassesPro2/README.md b/src/argaze/TobiiGlassesPro2/README.md index 4ede0d6..d26f066 100644 --- a/src/argaze/TobiiGlassesPro2/README.md +++ b/src/argaze/TobiiGlassesPro2/README.md @@ -1,6 +1,7 @@ Class interface to handle Tobbi Glasses Pro 2 device. -It is based on [TobiiGlassesPySuite package](https://github.com/ddetommaso/TobiiGlassesPySuite): -* [Article that describes the package features](https://arxiv.org/pdf/1912.09142.pdf). +This work is greatly inspired by the features available throught David de Tommaso and Agnieszka Wykowska [TobiiGlassesPySuite package](https://github.com/ddetommaso/TobiiGlassesPySuite) as TobiiController class directly inherits from tobiiglassesctrl.TobiiGlassesController class. + +* [Article that describes TobiiGlassesPySuite package features](https://arxiv.org/pdf/1912.09142.pdf). * [Tobii Glasses Pro 2 device user manual](https://www.tobiipro.com/siteassets/tobii-pro/user-manuals/tobii-pro-glasses-2-user-manual.pdf). ## Utils diff --git a/src/argaze/TobiiGlassesPro2/TobiiEntities.py b/src/argaze/TobiiGlassesPro2/TobiiEntities.py index 5483233..96fd0af 100644 --- a/src/argaze/TobiiGlassesPro2/TobiiEntities.py +++ b/src/argaze/TobiiGlassesPro2/TobiiEntities.py @@ -7,6 +7,7 @@ import os from argaze import DataStructures +import av import cv2 as cv TOBII_DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S+%f' @@ -25,20 +26,15 @@ TOBII_SEGMENT_INFO_FILENAME = "segment.json" TOBII_SEGMENT_VIDEO_FILENAME = "fullstream.mp4" TOBII_SEGMENT_DATA_FILENAME = "livedata.json.gz" -class TobiiSegmentData: +class TobiiSegmentData(DataStructures.DictObject): """Handle Tobii Glasses Pro 2 segment data file.""" def __init__(self, segment_data_path): - """Load segment data from segment directory.""" + """Load segment data from segment directory then parse and register each recorded dataflow as a TimeStampedBuffer member of the TobiiSegmentData instance.""" self.__segment_data_path = segment_data_path self.__ts_start = 0 - def get_path(self): - return self.__segment_data_path - - def load(self): - ts_data_buffer_dict = {} # define a decoder function @@ -77,33 +73,60 @@ class TobiiSegmentData: for item in f: json.loads(item.decode('utf-8'), object_hook=decode) - return ts_data_buffer_dict + super().__init__(type(self).__name__, **ts_data_buffer_dict) + + def keys(self): + """Get all registered data keys""" + return list(self.__dict__.keys())[2:-1] + + def get_path(self): + return self.__segment_data_path + +class TobiiVideoFrame(DataStructures.DictObject): + """Define tobii video frame""" -class TobiiSegmentVideo: + def __init__(self, matrix, width, height, pts): + + super().__init__(type(self).__name__, **{'matrix': matrix, 'width': width, 'height': height, 'pts': pts}) + +class TobiiSegmentVideo(): """Handle Tobii Glasses Pro 2 segment video file.""" def __init__(self, segment_video_path): - """Load segment video from segment directory.""" + """Load segment video from segment directory""" self.__segment_video_path = segment_video_path - - video = cv.VideoCapture(self.__segment_video_path) - - self.__width = int(video.get(cv.CAP_PROP_FRAME_WIDTH)) - self.__height = int(video.get(cv.CAP_PROP_FRAME_HEIGHT)) - self.__fps = int(video.get(cv.CAP_PROP_FPS)) + self.__container = av.open(self.__segment_video_path) + self.__stream = self.__container.streams.video[0] + self.__width = int(cv.VideoCapture(self.__segment_video_path).get(cv.CAP_PROP_FRAME_WIDTH)) + self.__height = int(cv.VideoCapture(self.__segment_video_path).get(cv.CAP_PROP_FRAME_HEIGHT)) + def get_path(self): return self.__segment_video_path - def get_height(self): - return self.__height + def get_duration(self): + return self.__stream.duration + + def get_frame_number(self): + return self.__stream.frames def get_width(self): return self.__width - def get_fps(self): - return self.__fps + def get_height(self): + return self.__height + + def __iter__(self): + + # start decoding + self.__container.decode(self.__stream) + return self + + def __next__(self): + + frame = self.__container.decode(self.__stream).__next__() + return frame.time, TobiiVideoFrame(frame.to_ndarray(format='bgr24'), frame.width, frame.height, frame.pts) class TobiiSegment: """Handle Tobii Glasses Pro 2 segment info.""" @@ -143,10 +166,10 @@ class TobiiSegment: def is_calibrated(self): return self.__calibrated - def get_data(self): + def load_data(self): return TobiiSegmentData(os.path.join(self.__segment_path, TOBII_SEGMENT_DATA_FILENAME)) - def get_video(self): + def load_video(self): return TobiiSegmentVideo(os.path.join(self.__segment_path, TOBII_SEGMENT_VIDEO_FILENAME)) class TobiiRecording: diff --git a/src/argaze/utils/analyse_tobii_segment_fixations.py b/src/argaze/utils/analyse_tobii_segment_fixations.py index 32fa32d..26f0838 100644 --- a/src/argaze/utils/analyse_tobii_segment_fixations.py +++ b/src/argaze/utils/analyse_tobii_segment_fixations.py @@ -24,11 +24,15 @@ def main(): tobii_segment = TobiiEntities.TobiiSegment(args.segment_path) # Load a tobii segment video - tobii_segment_video = tobii_segment.get_video() - print(f'Video width: {tobii_segment_video.get_width()}, height: {tobii_segment_video.get_height()}, fps: {tobii_segment_video.get_fps()}') - - # Load a tobii segment timestamped gaze position data buffer - tobii_ts_gaze_position_buffer = tobii_segment.get_data().load()['gidx-l-gp'] + tobii_segment_video = tobii_segment.load_video() + print(f'Video duration: {tobii_segment_video.get_duration()}, frame number: {tobii_segment_video.get_frame_number()}, width: {tobii_segment_video.get_width()}, height: {tobii_segment_video.get_height()}') + + # Load a tobii segment data + tobii_segment_data = tobii_segment.load_data() + print(f'Data keys: {tobii_segment_data.keys()}') + + # Access to timestamped gaze position data buffer + tobii_ts_gaze_position_buffer = tobii_segment_data['gidx-l-gp'] print(f'{len(tobii_ts_gaze_position_buffer)} gaze positions loaded') @@ -49,6 +53,11 @@ def main(): for ts, f in fixation_analyser.fixations.items(): print(f'start time = {ts}, duration = {f.duration}, dispertion = {f.dispersion}, centroid = {f.centroid}') + # TODO : synchronise video and gaze + #for ts, frame in tobii_segment_video: + + #print(ts) + if __name__ == '__main__': main() \ No newline at end of file diff --git a/src/argaze/utils/map_tobii_gaze_on_camera.py b/src/argaze/utils/map_tobii_gaze_on_camera.py new file mode 100644 index 0000000..e69de29 -- cgit v1.1