aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/argaze/TobiiGlassesPro2/TobiiData.py22
-rw-r--r--src/argaze/TobiiGlassesPro2/TobiiEntities.py24
-rw-r--r--src/argaze/TobiiGlassesPro2/TobiiVideo.py36
-rw-r--r--src/argaze/utils/MiscFeatures.py21
-rw-r--r--src/argaze/utils/README.md4
-rw-r--r--src/argaze/utils/export_tobii_segment_aruco_rois.py18
-rw-r--r--src/argaze/utils/export_tobii_segment_fixations.py14
-rw-r--r--src/argaze/utils/replay_tobii_session.py5
8 files changed, 90 insertions, 54 deletions
diff --git a/src/argaze/TobiiGlassesPro2/TobiiData.py b/src/argaze/TobiiGlassesPro2/TobiiData.py
index 0a68ff9..8024fa2 100644
--- a/src/argaze/TobiiGlassesPro2/TobiiData.py
+++ b/src/argaze/TobiiGlassesPro2/TobiiData.py
@@ -13,11 +13,11 @@ from argaze.TobiiGlassesPro2 import TobiiNetworkInterface
class TobiiDataSegment(DataStructures.DictObject):
"""Handle Tobii Glasses Pro 2 segment data file."""
- def __init__(self, segment_data_path):
+ def __init__(self, segment_data_path, start_timestamp = 0, end_timestamp = None):
"""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
+ self.__first_ts = 0
ts_data_buffer_dict = {}
@@ -31,13 +31,13 @@ class TobiiDataSegment(DataStructures.DictObject):
ts = json_data.pop('ts')
# keep first timestamp to offset all timestamps
- if self.__ts_start == 0:
- self.__ts_start = ts
+ if self.__first_ts == 0:
+ self.__first_ts = ts
- ts -= self.__ts_start
+ ts -= self.__first_ts
- # ignore negative timestamp
- if ts < 0:
+ # ignore timestamps out of the given time range
+ if ts < start_timestamp or ts >= end_timestamp:
return
# convert json data into data object
@@ -105,7 +105,7 @@ class TobiiDataStream(threading.Thread):
def open(self):
"""Start data reception."""
- self.__ts_start = 0
+ self.__first_ts = 0
self.__keep_alive_thread.start()
threading.Thread.start(self)
@@ -159,10 +159,10 @@ class TobiiDataStream(threading.Thread):
ts = json_data.pop('ts')
# keep first timestamp to offset all timestamps
- if self.__ts_start == 0:
- self.__ts_start = ts
+ if self.__first_ts == 0:
+ self.__first_ts = ts
- ts -= self.__ts_start
+ ts -= self.__first_ts
# ignore negative timestamp
if ts < 0:
diff --git a/src/argaze/TobiiGlassesPro2/TobiiEntities.py b/src/argaze/TobiiGlassesPro2/TobiiEntities.py
index c5a7055..17cd29f 100644
--- a/src/argaze/TobiiGlassesPro2/TobiiEntities.py
+++ b/src/argaze/TobiiGlassesPro2/TobiiEntities.py
@@ -29,8 +29,9 @@ TOBII_SEGMENT_DATA_FILENAME = "livedata.json.gz"
class TobiiSegment:
"""Handle Tobii Glasses Pro 2 segment info."""
- def __init__(self, segment_path):
- """Load segment info from segment directory."""
+ def __init__(self, segment_path, start_timestamp:int = 0, end_timestamp:int = None):
+ """Load segment info from segment directory.
+ Optionnaly select a time range in microsecond."""
self.__segment_id = os.path.basename(segment_path)
self.__segment_path = segment_path
@@ -41,8 +42,14 @@ class TobiiSegment:
except:
raise RuntimeError(f'JSON fails to load {self.__segment_path}/{TOBII_SEGMENT_INFO_FILENAME}')
- self.__length_us = int(item["seg_length_us"])
+ self.__start_timestamp = start_timestamp
+ self.__end_timestamp = min(end_timestamp, int(item["seg_length_us"])) if end_timestamp != None else int(item["seg_length_us"])
+
+ if self.__start_timestamp >= self.__end_timestamp:
+ raise ValueError('start time is equal or greater than end time.')
+
self.__calibrated = bool(item["seg_calibrated"])
+
self.__start_date = datetime.datetime.strptime(item["seg_t_start"], TOBII_DATETIME_FORMAT)
self.__stop_date = datetime.datetime.strptime(item["seg_t_stop"], TOBII_DATETIME_FORMAT)
@@ -52,8 +59,11 @@ class TobiiSegment:
def get_id(self):
return self.__segment_id
- def get_length_us(self):
- return self.__length_us
+ def get_start_timestamp(self):
+ return self.__start_timestamp
+
+ def get_end_timestamp(self):
+ return self.__end_timestamp
def get_start_date(self):
return self.__start_date
@@ -65,10 +75,10 @@ class TobiiSegment:
return self.__calibrated
def load_data(self):
- return TobiiData.TobiiDataSegment(os.path.join(self.__segment_path, TOBII_SEGMENT_DATA_FILENAME))
+ return TobiiData.TobiiDataSegment(os.path.join(self.__segment_path, TOBII_SEGMENT_DATA_FILENAME), self.__start_timestamp, self.__end_timestamp)
def load_video(self):
- return TobiiVideo.TobiiVideoSegment(os.path.join(self.__segment_path, TOBII_SEGMENT_VIDEO_FILENAME))
+ return TobiiVideo.TobiiVideoSegment(os.path.join(self.__segment_path, TOBII_SEGMENT_VIDEO_FILENAME), self.__start_timestamp, self.__end_timestamp)
class TobiiRecording:
"""Handle Tobii Glasses Pro 2 recording info and segments."""
diff --git a/src/argaze/TobiiGlassesPro2/TobiiVideo.py b/src/argaze/TobiiGlassesPro2/TobiiVideo.py
index 6318de8..e6ec064 100644
--- a/src/argaze/TobiiGlassesPro2/TobiiVideo.py
+++ b/src/argaze/TobiiGlassesPro2/TobiiVideo.py
@@ -22,10 +22,11 @@ class TobiiVideoFrame(DataStructures.DictObject):
class TobiiVideoSegment():
"""Handle Tobii Glasses Pro 2 segment video file."""
- def __init__(self, segment_video_path):
+ def __init__(self, segment_video_path, start_timestamp:int = 0, end_timestamp:int = None):
"""Load segment video from segment directory"""
self.__segment_video_path = segment_video_path
+
self.__container = av.open(self.__segment_video_path)
self.__stream = self.__container.streams.video[0]
@@ -33,15 +34,23 @@ class TobiiVideoSegment():
self.__height = int(cv.VideoCapture(self.__segment_video_path).get(cv.CAP_PROP_FRAME_HEIGHT))
self.__vts_data_buffer = None
+ self.__vts_offset = 0
+
+ self.__start_timestamp = start_timestamp
+ self.__end_timestamp = end_timestamp
+
+ # position at the given start time
+ self.__container.seek(self.__start_timestamp)
def get_path(self):
return self.__segment_video_path
def get_duration(self):
- return float(self.__stream.duration * self.__stream.time_base)
-
- def get_frame_number(self):
- return self.__stream.frames
+ """Duration in microsecond"""
+ if self.__end_timestamp == None:
+ return int((self.__stream.duration * self.__stream.time_base) * 1000000) - self.__start_timestamp
+ else:
+ return self.__end_timestamp - self.__start_timestamp
def get_width(self):
return self.__width
@@ -52,8 +61,11 @@ class TobiiVideoSegment():
def get_stream(self):
return self.__stream
+ def get_vts_offset(self):
+ return self.__vts_offset
+
def frames(self, vts_data_buffer = None):
- """Access to frame iterator and optionnaly setup vide / data timestamp synchronisation through vts data buffer."""
+ """Access to frame iterator and optionnaly setup video / data timestamp synchronisation through vts data buffer."""
self.__vts_data_buffer = vts_data_buffer
@@ -61,6 +73,12 @@ class TobiiVideoSegment():
if self.__vts_data_buffer != None:
self.__vts_ts, self.__vts = self.__vts_data_buffer.pop_first()
+
+ # pop vts buffer until start timestamp
+ while self.__start_timestamp > self.__vts.vts:
+ if len(self.__vts_data_buffer) > 0:
+ self.__vts_ts, self.__vts = self.__vts_data_buffer.pop_first()
+
self.__vts_offset = (self.__vts_ts - self.__vts.vts)
return self.__iter__()
@@ -78,6 +96,12 @@ class TobiiVideoSegment():
video_ts = int(frame.time * 1000000)
+ # Ignore frames after end timestamp
+ if self.__end_timestamp != None:
+
+ if video_ts >= self.__end_timestamp:
+ raise StopIteration
+
# If video / data synchronisation is active
if self.__vts_data_buffer != None:
diff --git a/src/argaze/utils/MiscFeatures.py b/src/argaze/utils/MiscFeatures.py
index 24f1791..9c62d89 100644
--- a/src/argaze/utils/MiscFeatures.py
+++ b/src/argaze/utils/MiscFeatures.py
@@ -1,18 +1,17 @@
#!/usr/bin/env python
# Print iterations progress
-def printProgressBar (iteration, total, prefix = '', suffix = '', decimals = 1, length = 100, fill = '█', printEnd = "\r"):
+def printProgressBar (iteration:int, total:int, prefix:str = '', suffix:str = '', decimals:int = 1, length:int = 100, fill:str = '█', printEnd:str = "\r"):
"""Print iterations progress.
- Call in a loop to create terminal progress bar
- @params:
- iteration - Required : current iteration (Int)
- total - Required : total iterations (Int)
- prefix - Optional : prefix string (Str)
- suffix - Optional : suffix string (Str)
- decimals - Optional : positive number of decimals in percent complete (Int)
- length - Optional : character length of bar (Int)
- fill - Optional : bar fill character (Str)
- printEnd - Optional : end character (e.g. "\r", "\r\n") (Str)
+ Call in a loop to create terminal progress bar.
+ - current iteration
+ - total iterations
+ - prefix string
+ - suffix string
+ - positive number of decimals in percent complete
+ - character length of bar
+ - bar fill character
+ - end character (e.g. "\r", "\r\n")
"""
percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
filledLength = int(length * iteration // total)
diff --git a/src/argaze/utils/README.md b/src/argaze/utils/README.md
index dc40f30..ccf5f3f 100644
--- a/src/argaze/utils/README.md
+++ b/src/argaze/utils/README.md
@@ -75,11 +75,11 @@ python ./src/argaze/utils/export_tobii_segment_fixations.py -s SEGMENT_PATH
- Track ArUco markers into a Tobii camera video segment (replace SEGMENT_PATH). Load an roi scene (replace ROI_SCENE) .obj file, position it virtually relatively to any detected ArUco markers and project the scene into camera frame. Then, detect if Tobii gaze point is inside any ROI. Export ROIs video and data.
```
-python ./src/argaze/utils/export_tobii_segment_aruco_rois.py -s SEGMENT_PATH -c export/tobii_camera.json -m 7.5 -r ROI_SCENE
+python ./src/argaze/utils/export_tobii_segment_aruco_rois.py -s SEGMENT_PATH -c export/tobii_camera.json -m 7.5 -a ROI_SCENE
```
- Track ArUco markers into Tobii camera video stream (replace IP_ADDRESS). Load an roi scene (replace ROI_SCENE) .obj file, position it virtually relatively to any detected ArUco markers and project the scene into camera frame. Then, detect if Tobii gaze point is inside any ROI.
```
-python ./src/argaze/utils/live_tobii_aruco_rois.py -t IP_ADDRESS -c export/tobii_camera.json -m 7.5 -r ROI_SCENE
+python ./src/argaze/utils/live_tobii_aruco_rois.py -t IP_ADDRESS -c export/tobii_camera.json -m 7.5 -a ROI_SCENE
```
diff --git a/src/argaze/utils/export_tobii_segment_aruco_rois.py b/src/argaze/utils/export_tobii_segment_aruco_rois.py
index d28f887..d31ebb3 100644
--- a/src/argaze/utils/export_tobii_segment_aruco_rois.py
+++ b/src/argaze/utils/export_tobii_segment_aruco_rois.py
@@ -26,8 +26,9 @@ def main():
# Manage arguments
parser = argparse.ArgumentParser(description=main.__doc__.split('-')[0])
parser.add_argument('-s', '--segment_path', metavar='SEGMENT_PATH', type=str, default=None, help='segment path')
+ parser.add_argument('-r', '--time_range', metavar=('START_TIME', 'END_TIME'), nargs=2, type=float, default=(0., None), help='start and end time (in second)')
parser.add_argument('-c', '--camera_calibration', metavar='CAM_CALIB', type=str, default='tobii_camera.json', help='json camera calibration filepath')
- parser.add_argument('-r', '--roi_scene', metavar='ROI_SCENE', type=str, default='roi3D_scene.obj', help='obj roi scene filepath')
+ parser.add_argument('-a', '--roi_scene', metavar='ROI_SCENE', type=str, default='roi3D_scene.obj', help='obj roi scene filepath')
parser.add_argument('-d', '--dictionary', metavar='DICT', type=str, default='DICT_ARUCO_ORIGINAL', help='aruco marker dictionnary')
parser.add_argument('-m', '--marker_size', metavar='MKR', type=float, default=6, help='aruco marker size (cm)')
parser.add_argument('-o', '--output', metavar='OUT', type=str, default=None, help='destination folder path (segment folder by default)')
@@ -52,11 +53,11 @@ def main():
video_filepath = f'{args.segment_path}/fullstream+visu.mp4'
# Load a tobii segment
- tobii_segment = TobiiEntities.TobiiSegment(args.segment_path)
+ tobii_segment = TobiiEntities.TobiiSegment(args.segment_path, int(args.time_range[0] * 1000000), int(args.time_range[1] * 1000000) if args.time_range[1] != None else None)
# Load a tobii segment video
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()}')
+ print(f'Video duration: {tobii_segment_video.get_duration()/1000000}, width: {tobii_segment_video.get_width()}, height: {tobii_segment_video.get_height()}')
# Load a tobii segment data
tobii_segment_data = tobii_segment.load_data()
@@ -87,9 +88,8 @@ def main():
# Video and data replay loop
try:
- # print 0% progress
- frame_count = 0
- MiscFeatures.printProgressBar(frame_count, tobii_segment_video.get_frame_number(), prefix = 'Progress:', suffix = 'Complete', length = 100)
+ # Count frame to display a progress bar
+ MiscFeatures.printProgressBar(0, tobii_segment_video.get_duration(), prefix = 'Progress:', suffix = 'Complete', length = 100)
# Iterate on video frames activating video / data synchronisation through vts data buffer
for video_ts, video_frame in tobii_segment_video.frames(tobii_segment_data.vts):
@@ -153,8 +153,8 @@ def main():
output_video.write(video_frame.matrix)
# Update Progress Bar
- frame_count += 1
- MiscFeatures.printProgressBar(frame_count, tobii_segment_video.get_frame_number(), prefix = 'Progress:', suffix = 'Complete', length = 100)
+ progress = video_ts - tobii_segment_video.get_vts_offset() - int(args.time_range[0] * 1000000)
+ MiscFeatures.printProgressBar(progress, tobii_segment_video.get_duration(), prefix = 'Progress:', suffix = 'Complete', length = 100)
# Exit on 'ctrl+C' interruption
except KeyboardInterrupt:
@@ -166,7 +166,7 @@ def main():
# End output video file
output_video.close()
- print(f'ROIs video saved into {video_filepath}')
+ print(f'\nROIs video saved into {video_filepath}')
# Export 2D rois
roi2D_timestamped_buffer.export_as_json(rois_filepath)
diff --git a/src/argaze/utils/export_tobii_segment_fixations.py b/src/argaze/utils/export_tobii_segment_fixations.py
index d40c4d3..91a44e9 100644
--- a/src/argaze/utils/export_tobii_segment_fixations.py
+++ b/src/argaze/utils/export_tobii_segment_fixations.py
@@ -13,9 +13,10 @@ def main():
Analyse Tobii segment fixations
"""
- # manage arguments
+ # Manage arguments
parser = argparse.ArgumentParser(description=main.__doc__.split('-')[0])
parser.add_argument('-s', '--segment_path', metavar='SEGMENT_PATH', type=str, default=None, help='path to a tobii segment folder')
+ parser.add_argument('-r', '--time_range', metavar=('START_TIME', 'END_TIME'), nargs=2, type=float, default=(0., None), help='start and end time (in second)')
parser.add_argument('-d', '--dispersion_threshold', metavar='DISPERSION_THRESHOLD', type=int, default=10, help='dispersion threshold in pixel')
parser.add_argument('-t', '--duration_threshold', metavar='DURATION_THRESHOLD', type=int, default=100, help='duration threshold in millisecond')
parser.add_argument('-o', '--output', metavar='OUT', type=str, default=None, help='destination folder path (segment folder by default)')
@@ -38,11 +39,11 @@ def main():
fixations_filepath = f'{args.segment_path}/fixations.json'
# Load a tobii segment
- tobii_segment = TobiiEntities.TobiiSegment(args.segment_path)
+ tobii_segment = TobiiEntities.TobiiSegment(args.segment_path, int(args.time_range[0] * 1000000), int(args.time_range[1] * 1000000) if args.time_range[1] != None else None)
# Load a tobii segment video
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()}')
+ print(f'Video duration: {tobii_segment_video.get_duration()/1000000}, width: {tobii_segment_video.get_width()}, height: {tobii_segment_video.get_height()}')
# Load a tobii segment data
tobii_segment_data = tobii_segment.load_data()
@@ -64,10 +65,11 @@ def main():
print(f'Duration threshold: {args.duration_threshold}')
fixation_analyser = GazeFeatures.DispersionBasedFixationIdentifier(generic_ts_gaze_positions, args.dispersion_threshold, args.duration_threshold)
+
+ # Start fixation identification
fixations = GazeFeatures.TimeStampedFixations()
- # Start fixation identification
- MiscFeatures.printProgressBar(0, int(tobii_segment_video.get_duration()*1000), prefix = 'Progress:', suffix = 'Complete', length = 100)
+ MiscFeatures.printProgressBar(0, int(tobii_segment_video.get_duration()/1000), prefix = 'Progress:', suffix = 'Complete', length = 100)
for ts, item in fixation_analyser:
@@ -78,7 +80,7 @@ def main():
fixations[ts] = item
- MiscFeatures.printProgressBar(ts, int(tobii_segment_video.get_duration()*1000), prefix = 'Progress:', suffix = 'Complete', length = 100)
+ MiscFeatures.printProgressBar(ts-int(args.time_range[0]*1000), int(tobii_segment_video.get_duration()/1000), prefix = 'Progress:', suffix = 'Complete', length = 100)
print(f'\n{len(fixations)} fixations found')
diff --git a/src/argaze/utils/replay_tobii_session.py b/src/argaze/utils/replay_tobii_session.py
index 87f043d..0471506 100644
--- a/src/argaze/utils/replay_tobii_session.py
+++ b/src/argaze/utils/replay_tobii_session.py
@@ -18,16 +18,17 @@ def main():
# manage arguments
parser = argparse.ArgumentParser(description=main.__doc__.split('-')[0])
parser.add_argument('-s', '--segment_path', metavar='SEGMENT_PATH', type=str, default=None, help='segment path')
+ parser.add_argument('-r', '--time_range', metavar=('START_TIME', 'END_TIME'), nargs=2, type=float, default=(0., None), help='start and end time (in second)')
args = parser.parse_args()
if args.segment_path != None:
# Load a tobii segment
- tobii_segment = TobiiEntities.TobiiSegment(args.segment_path)
+ tobii_segment = TobiiEntities.TobiiSegment(args.segment_path, int(args.time_range[0] * 1000000), int(args.time_range[1] * 1000000) if args.time_range[1] != None else None)
# Load a tobii segment video
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()}')
+ print(f'Video duration: {tobii_segment_video.get_duration()/1000000}, width: {tobii_segment_video.get_width()}, height: {tobii_segment_video.get_height()}')
# Load a tobii segment data
tobii_segment_data = tobii_segment.load_data()