aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md5
-rw-r--r--src/argaze/ArUcoMarkers/ArUcoBoard.py22
-rw-r--r--src/argaze/ArUcoMarkers/ArUcoCamera.py12
-rw-r--r--src/argaze/ArUcoMarkers/ArUcoCube.py9
-rw-r--r--src/argaze/ArUcoMarkers/ArUcoMarker.py8
-rw-r--r--src/argaze/ArUcoMarkers/ArUcoMarkersDictionary.py12
-rw-r--r--src/argaze/ArUcoMarkers/ArUcoTracker.py23
-rw-r--r--src/argaze/ArUcoMarkers/README.md2
-rw-r--r--src/argaze/AreaOfInterest/AOI2DScene.py8
-rw-r--r--src/argaze/AreaOfInterest/AOI3DScene.py11
-rw-r--r--src/argaze/AreaOfInterest/AOIFeatures.py54
-rw-r--r--src/argaze/AreaOfInterest/README.md3
-rw-r--r--src/argaze/DataStructures.py29
-rw-r--r--src/argaze/GazeFeatures.py28
-rw-r--r--src/argaze/TobiiGlassesPro2/TobiiController.py43
-rw-r--r--src/argaze/TobiiGlassesPro2/TobiiData.py27
-rw-r--r--src/argaze/TobiiGlassesPro2/TobiiEntities.py146
-rw-r--r--src/argaze/TobiiGlassesPro2/TobiiInertialMeasureUnit.py35
-rw-r--r--src/argaze/TobiiGlassesPro2/TobiiNetworkInterface.py14
-rw-r--r--src/argaze/TobiiGlassesPro2/TobiiSpecifications.py16
-rw-r--r--src/argaze/TobiiGlassesPro2/TobiiVideo.py40
-rw-r--r--src/argaze/utils/tobii_sdcard_explore.py8
22 files changed, 420 insertions, 135 deletions
diff --git a/README.md b/README.md
index 2da1489..55c7481 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,13 @@
-An open-source python toolkit to deal with gaze tracking and analysis in Augmented Reality (AR) environnement.
+An open-source python toolkit to deal with dynamic Areas Of Interest (AOI) and gaze tracking in Augmented Reality (AR) environnement.
## Architecture
-The ArGaze toolkit provides some generics data structures and algorithms to process gaze analysis and it is divided in submodules dedicated to various specifics features:
+The ArGaze toolkit provides some generics data structures and algorithms to build AR environement with dynamic AOI and so allow gaze tracking with mobil eye tracker devices. It is divided in submodules dedicated to various specifics features:
* ArUcoMarkers: ArUco markers generator, traking, camera calibration, ...
* AreaOfInterest: Area Of Interest (AOI) scene management for 2D and 3D environment.
* TobiiGlassesPro2: A gaze tracking device interface.
+* GazeFeatures: Generic gaze data structures definitions.
* utils: Collection of command-line high level features scripts based on ArGaze toolkit.
## Installation
diff --git a/src/argaze/ArUcoMarkers/ArUcoBoard.py b/src/argaze/ArUcoMarkers/ArUcoBoard.py
index d2eea50..eae9c48 100644
--- a/src/argaze/ArUcoMarkers/ArUcoBoard.py
+++ b/src/argaze/ArUcoMarkers/ArUcoBoard.py
@@ -10,7 +10,7 @@ class ArUcoBoard():
"""Calibration chess board with ArUco markers inside."""
def __init__(self, aruco_dictionary_name: str, columns: int, rows: int, square_size: float, marker_size: float):
- """Create columnsxrows chess board with ArUco markers type at given sizes in centimeters."""
+ """Create columns x rows chess board with ArUco markers at given size in centimeters."""
# load ArUco markers dictionary
self.__aruco_dict = ArUcoMarkersDictionary.ArUcoMarkersDictionary(aruco_dictionary_name)
@@ -28,23 +28,33 @@ class ArUcoBoard():
pass
def get_model(self):
- """Get the board model."""
+ """Get the board model.
+ **Returns:** aruco.CharucoBoard"""
+
return self.__board
def get_ids(self):
- """Get board markers ids."""
+ """Get board markers ids.
+ **Returns:** list"""
+
return self.__board.ids
def get_size(self):
- """Get numbers of columns and rows."""
+ """Get numbers of columns and rows.
+ **Returns:** int"""
+
return self.__board.getChessboardSize()
def get_markers_number(self):
- """Get number of markers."""
+ """Get number of markers.
+ **Returns:** int"""
+
return len(self.__board.ids)
def get_corners_number(self):
- """Get number of corners."""
+ """Get number of corners.
+ **Returns:** int"""
+
return (self.__board.getChessboardSize()[0] - 1 ) * (self.__board.getChessboardSize()[1] - 1)
def export(self, destination_folder: str, dpi: int):
diff --git a/src/argaze/ArUcoMarkers/ArUcoCamera.py b/src/argaze/ArUcoMarkers/ArUcoCamera.py
index 116f6a2..aa7426c 100644
--- a/src/argaze/ArUcoMarkers/ArUcoCamera.py
+++ b/src/argaze/ArUcoMarkers/ArUcoCamera.py
@@ -49,15 +49,18 @@ class ArUcoCamera():
json.dump(calibration_data, calibration_file, ensure_ascii=False, indent=4)
def get_rms(self):
- """Get Root Mean Square (rms) error."""
+ """Get Root Mean Square (rms) error.
+ **Returns:** float"""
return self.__rms
def get_K(self):
- """Get camera matrix."""
+ """Get camera matrix.
+ **Returns:** numpy.array"""
return self.__K
def get_D(self):
- """Get camera distorsion coefficients."""
+ """Get camera distorsion coefficients.
+ **Returns:** numpy.array"""
return self.__D
def calibrate(self, board, frame_width, frame_height):
@@ -83,7 +86,8 @@ class ArUcoCamera():
self.__corners_set_ids.append(corners_ids)
def get_calibration_data_count(self):
- """Get how much calibration data are stored."""
+ """Get how much calibration data are stored.
+ **Returns:** int"""
return self.__corners_set_number
diff --git a/src/argaze/ArUcoMarkers/ArUcoCube.py b/src/argaze/ArUcoMarkers/ArUcoCube.py
index 356eefc..51ea314 100644
--- a/src/argaze/ArUcoMarkers/ArUcoCube.py
+++ b/src/argaze/ArUcoMarkers/ArUcoCube.py
@@ -22,7 +22,7 @@ class ArUcoCubeFace():
"""Rotation in cube referential."""
marker: dict
- """ArUco marker linked to the face """
+ """ArUco marker linked to the face."""
@dataclass
class ArUcoCube():
@@ -200,6 +200,8 @@ class ArUcoCube():
return rvec, tvec
def estimate_pose(self, tracked_markers):
+ """Estimate cube pose from tracked markers (cf ArUcoTracker.track())
+ **Returns:** numpy.array, numpy.array, bool, int"""
# Init pose data
self.__translation = numpy.zeros(3)
@@ -325,10 +327,13 @@ class ArUcoCube():
return self.get_pose()
def get_pose(self):
+ """Get cube pose.
+ **Returns:** numpy.array, numpy.array, bool, int"""
return self.__translation, self.__rotation, self.__succeded, self.__validity
def set_pose(self, tvec = numpy.array([]), rvec = numpy.array([])):
+ """Set cube pose."""
if tvec.size == 3:
self.__translation = tvec
@@ -340,6 +345,8 @@ class ArUcoCube():
self.__validity = 0
def draw(self, frame, K, D, draw_faces=True):
+ """Draw cube axis and faces.
+ **Returns:** frame"""
l = self.edge_size / 2
ll = self.edge_size
diff --git a/src/argaze/ArUcoMarkers/ArUcoMarker.py b/src/argaze/ArUcoMarkers/ArUcoMarker.py
index 4f716bb..28f1f31 100644
--- a/src/argaze/ArUcoMarkers/ArUcoMarker.py
+++ b/src/argaze/ArUcoMarkers/ArUcoMarker.py
@@ -13,10 +13,10 @@ class ArUcoMarker():
"""Define ArUco marker class."""
dictionary: ArUcoMarkersDictionary.ArUcoMarkersDictionary
- """ """
+ """Dictionary to which it belongs."""
identifier: int
- """ """
+ """Index into dictionary"""
size: float
"""Size of marker in centimeters."""
@@ -34,7 +34,9 @@ class ArUcoMarker():
"""Estimated 3D corners positions in camera referential."""
def center(self, i):
- """Get 2D center position in camera image referential."""
+ """Get 2D center position in camera image referential.
+ **Returns:** numpy.array"""
+
return self.corners[0].mean(axis=0)
def draw(self, frame, K, D):
diff --git a/src/argaze/ArUcoMarkers/ArUcoMarkersDictionary.py b/src/argaze/ArUcoMarkers/ArUcoMarkersDictionary.py
index 84a78b9..ce68703 100644
--- a/src/argaze/ArUcoMarkers/ArUcoMarkersDictionary.py
+++ b/src/argaze/ArUcoMarkers/ArUcoMarkersDictionary.py
@@ -82,7 +82,8 @@ class ArUcoMarkersDictionary():
self.__aruco_dict = aruco.Dictionary_get(all_aruco_markers_dictionaries[self.name])
def create_marker(self, i, dpi=300):
- """Create a marker image."""
+ """Create a marker image.
+ **Returns:** numpy.array"""
marker = numpy.zeros((dpi, dpi, 1), dtype="uint8")
aruco.drawMarker(self.__aruco_dict, i, dpi, marker, 1)
@@ -90,12 +91,21 @@ class ArUcoMarkersDictionary():
return numpy.repeat(marker, 3).reshape(dpi, dpi, 3)
def get_markers(self):
+ """Get all markers from dictionary.
+ **Returns:** aruco.Dictionary"""
+
return self.__aruco_dict
def get_markers_format(self):
+ """Get markers format.
+ **Returns:** str"""
+
return self.__format
def get_markers_number(self):
+ """Get markers format.
+ **Returns:** int"""
+
return self.__number
def export_as_png(self, destination_folder, dpi, i):
diff --git a/src/argaze/ArUcoMarkers/ArUcoTracker.py b/src/argaze/ArUcoMarkers/ArUcoTracker.py
index e55cbb7..f7ea23a 100644
--- a/src/argaze/ArUcoMarkers/ArUcoTracker.py
+++ b/src/argaze/ArUcoMarkers/ArUcoTracker.py
@@ -138,12 +138,14 @@ class ArUcoTracker():
self.__tracked_ids.append(marker_id)
def get_tracked_markers(self):
- """Access to tracked markers dictionary."""
+ """Access to tracked markers dictionary.
+ **Returns:** dict"""
return self.__tracked_markers
def get_tracked_markers_number(self):
- """Return tracked markers number."""
+ """Return tracked markers number.
+ **Returns:** int"""
return len(list(self.__tracked_markers.keys()))
@@ -185,22 +187,31 @@ class ArUcoTracker():
def reset_track_metrics(self):
"""Enable marker tracking metrics."""
+
self.__track_count = 0
self.__tracked_ids = []
def get_track_metrics(self):
- """Get marker tracking metrics."""
+ """Get marker tracking metrics.
+ **Returns:** int, list"""
+
return self.__track_count, Counter(self.__tracked_ids), Counter(self.__rejected_markers)
def get_board_corners_number(self):
- """Get tracked board corners number."""
+ """Get tracked board corners number.
+ **Returns:** int"""
+
return self.__board_corners_number
def get_board_corners_ids(self):
- """Get tracked board corners identifiers."""
+ """Get tracked board corners identifiers.
+ **Returns:** list"""
+
return self.__board_corners_ids
def get_board_corners(self):
- """Get tracked board corners."""
+ """Get tracked board corners.
+ **Returns:** list"""
+
return self.__board_corners
diff --git a/src/argaze/ArUcoMarkers/README.md b/src/argaze/ArUcoMarkers/README.md
index fa34c5a..011305f 100644
--- a/src/argaze/ArUcoMarkers/README.md
+++ b/src/argaze/ArUcoMarkers/README.md
@@ -11,4 +11,4 @@ Print **A3_board_35cmx25cm_markers_4X4_3cm.pdf** onto A3 paper sheet to get boar
Print **A4_markers_4x4_3cm.pdf** onto A4 paper sheet to get markers at expected dimensions.
-Load **tracker_configuration.json** file with argaze utils _export_tobii_segment_aruco_visual_scan.py_ script with -p option. This is an example file to illustrate how to setup [ArUco markers detection parameters](https://docs.opencv.org/4.x/d1/dcd/structcv_1_1aruco_1_1DetectorParameters.html). \ No newline at end of file
+Load **tracker_configuration.json** file with argaze utils **tobii_segment_aruco_aoi_export.py** script with -p option. This is an example file to illustrate how to setup [ArUco markers detection parameters](https://docs.opencv.org/4.x/d1/dcd/structcv_1_1aruco_1_1DetectorParameters.html). \ No newline at end of file
diff --git a/src/argaze/AreaOfInterest/AOI2DScene.py b/src/argaze/AreaOfInterest/AOI2DScene.py
index 1faef59..c669def 100644
--- a/src/argaze/AreaOfInterest/AOI2DScene.py
+++ b/src/argaze/AreaOfInterest/AOI2DScene.py
@@ -28,7 +28,9 @@ class AOI2DScene(AOIFeatures.AOIScene):
aoi.draw(frame, color)
def raycast(self, gaze_position):
- """Iterate over aoi to know which aoi is looked considering only gaze position value."""
+ """Iterate over aoi to know which aoi is looked considering only gaze position value.
+ **Returns:** str, AreaOfInterest, bool"""
+
for name, aoi in self.items():
looked = aoi.looked(gaze_position)
@@ -54,7 +56,9 @@ class AOI2DScene(AOIFeatures.AOIScene):
aoi.draw(frame, color)
def regioncast(self, gaze_position):
- """Iterate over areas to know which aoi is looked considering gaze position value and its accuracy."""
+ """Iterate over areas to know which aoi is looked considering gaze position value and its accuracy.
+ **Returns:** str, AreaOfInterest, numpy array, float, float"""
+
for name, aoi in self.items():
looked_region, aoi_ratio, gaze_ratio = aoi.looked_region(gaze_position)
diff --git a/src/argaze/AreaOfInterest/AOI3DScene.py b/src/argaze/AreaOfInterest/AOI3DScene.py
index ee07043..0ee4442 100644
--- a/src/argaze/AreaOfInterest/AOI3DScene.py
+++ b/src/argaze/AreaOfInterest/AOI3DScene.py
@@ -10,17 +10,18 @@ from argaze.AreaOfInterest import AOIFeatures, AOI2DScene
import numpy
import cv2 as cv
-# Define defaut translation vector
+
T0 = numpy.array([0., 0., 0.])
+"""Define defaut translation vector."""
-# Define defaut rotation vector
R0 = numpy.array([0., 0., 0.])
+"""Define defaut rotation vector."""
-# Define defaut optical parameter
K0 = numpy.array([[1., 0., 1.], [0., 1., 1.], [0., 0., 1.]])
+"""Define defaut optical parameter."""
-# Define a zero distorsion matrix
D0 = numpy.array([0.0, 0.0, 0.0, 0.0, 0.0])
+"""Define a zero distorsion matrix."""
@dataclass
class AOI3DScene(AOIFeatures.AOIScene):
@@ -136,7 +137,7 @@ class AOI3DScene(AOIFeatures.AOIScene):
def vision_cone(self, cone_radius, cone_height, cone_tip=[0., 0., 0.], cone_direction=[0., 0., 1.]):
"""Get AOI which are inside and out a given cone field.
By default, the cone have the tip at origin and the base oriented to positive Z axis.
- **Returns:** AOI3DScene"""
+ **Returns:** AOI3DScene, AOI3DScene"""
# define cone tip and direction as numpy array
cone_tip = numpy.array(cone_tip).astype(numpy.float32)
diff --git a/src/argaze/AreaOfInterest/AOIFeatures.py b/src/argaze/AreaOfInterest/AOIFeatures.py
index 7dd0964..bc6e732 100644
--- a/src/argaze/AreaOfInterest/AOIFeatures.py
+++ b/src/argaze/AreaOfInterest/AOIFeatures.py
@@ -12,14 +12,17 @@ from shapely.geometry.point import Point
@dataclass
class AreaOfInterest(numpy.ndarray):
- """Define 2D/3D Area Of Interest."""
+ """Define 2D/3D Area Of Interest as an array of points."""
def dimension(self):
- """Number of coordinates coding area points positions."""
+ """Number of coordinates coding area points positions.
+ **Returns:** int"""
+
return self.shape[1]
def bounding_box(self):
- """Get area's bounding box."""
+ """Get area's bounding box.
+ **Returns:** numpy.array"""
min_x, min_y = numpy.min(self, axis=0)
max_x, max_y = numpy.max(self, axis=0)
@@ -27,11 +30,14 @@ class AreaOfInterest(numpy.ndarray):
return numpy.array([(min_x, min_y), (max_x, min_y), (max_x, max_y), (min_x, max_y)])
def center(self):
- """Center of mass"""
+ """Center of mass.
+ **Returns:** numpy.array"""
return self.mean(axis=0)
def clockwise(self):
- """Get area points in clocwise order."""
+ """Get area points in clocwise order.
+ Available for 2D AOI only.
+ **Returns:** numpy.array"""
if self.dimension() != 2:
raise RuntimeError(f'Bad area dimension ({self.dimension()})')
@@ -43,7 +49,9 @@ class AreaOfInterest(numpy.ndarray):
return self[numpy.argsort(angles)]
def looked(self, gaze_position):
- """Is gaze position inside area ?"""
+ """Is gaze position inside area?
+ Available for 2D AOI only.
+ **Returns:** bool"""
if self.dimension() != 2:
raise RuntimeError(f'Bad area dimension ({self.dimension()})')
@@ -51,7 +59,9 @@ class AreaOfInterest(numpy.ndarray):
return mpath.Path(self).contains_points([tuple(gaze_position)])[0]
def look_at(self, pixel_position):
- """Get where the area is looked using perpespective transformation."""
+ """Get where the area is looked using perpespective transformation.
+ Available for 2D AOI only.
+ **Returns:** list"""
if self.dimension() != 2:
raise RuntimeError(f'Bad area dimension ({self.dimension()})')
@@ -71,7 +81,8 @@ class AreaOfInterest(numpy.ndarray):
return numpy.around(La, 4).tolist()
def looked_pixel(self, look_at):
- """Get which pixel is looked."""
+ """Get which pixel is looked inside 2D AOI.
+ **Returns:** numpy.array"""
if self.dimension() != 2:
raise RuntimeError(f'Bad area dimension ({self.dimension()})')
@@ -91,7 +102,9 @@ class AreaOfInterest(numpy.ndarray):
return numpy.rint(Lp).astype(int).tolist()
def looked_region(self, gaze_position):
- """Get intersection shape with gaze accuracy circle as the looked area, (looked area / AOI area) and (looked area / gaze accuracy circle area)."""
+ """Get intersection shape with gaze accuracy circle as the looked area, (looked area / AOI area) and (looked area / gaze accuracy circle area).
+ Available for 2D AOI only.
+ **Returns:** numpy.array, float, float"""
if self.dimension() != 2:
raise RuntimeError(f'Bad area dimension ({self.dimension()})')
@@ -114,6 +127,7 @@ class AreaOfInterest(numpy.ndarray):
return empty_array, 0., 0.
def draw(self, frame, color, border_size=1):
+ """Draw 2D AOI into frame."""
if self.dimension() != 2:
raise RuntimeError(f'Bad area dimension ({self.dimension()})')
@@ -142,26 +156,35 @@ class AOIScene():
def __getitem__(self, key):
"""Get an aoi from the scene."""
+
return numpy.array(self.areas[key]).astype(numpy.float32).view(AreaOfInterest)
def __setitem__(self, name, aoi: AreaOfInterest):
"""Add an aoi to the scene."""
+
self.areas[name] = aoi.tolist()
def __delitem__(self, key):
"""Remove an aoi from the scene."""
+
del self.areas[key]
def items(self):
- """Iterate over areas."""
+ """Iterate over areas.
+ **Returns:** str, AreaOfInterest"""
+
for name, area in self.areas.items():
yield name, numpy.array(area).astype(numpy.float32).view(AreaOfInterest)
def keys(self):
+ """Get areas name.
+ **Returns:** dict_keys"""
+
return self.areas.keys()
def bounds(self):
- """Get scene's bounds."""
+ """Get scene's bounds.
+ **Returns:** numpy.array"""
all_vertices = []
@@ -177,21 +200,24 @@ class AOIScene():
return numpy.array([min_bounds, max_bounds])
def center(self):
- """Get scene's center point."""
+ """Get scene's center point.
+ **Returns:** numpy.array"""
min_bounds, max_bounds = self.bounds()
return (min_bounds + max_bounds) / 2
def size(self):
- """Get scene size."""
+ """Get scene size.
+ **Returns:** numpy.array"""
min_bounds, max_bounds = self.bounds()
return max_bounds - min_bounds
def copy(self, exclude=[]):
- """Copy scene partly excluding aoi by name."""
+ """Copy scene partly excluding aoi by name.
+ **Returns:** AOIScene"""
scene_copy = type(self)()
diff --git a/src/argaze/AreaOfInterest/README.md b/src/argaze/AreaOfInterest/README.md
index 1b1fb9d..59041eb 100644
--- a/src/argaze/AreaOfInterest/README.md
+++ b/src/argaze/AreaOfInterest/README.md
@@ -1,4 +1,3 @@
Class interface to manage Areas of Interest (AOI).
-
-
+AOIFeatures defines generics 2D/3D AOI data structures which could be gathered inside 2D/3D scenes. \ No newline at end of file
diff --git a/src/argaze/DataStructures.py b/src/argaze/DataStructures.py
index c86d021..44f64cc 100644
--- a/src/argaze/DataStructures.py
+++ b/src/argaze/DataStructures.py
@@ -39,15 +39,20 @@ class TimeStampedBuffer(collections.OrderedDict):
self[ts] = value
def get_first(self):
- """Easing access to first item"""
+ """Easing access to first item.
+ **Returns:** data"""
+
return list(self.items())[0]
def pop_first(self):
- """Easing FIFO access mode"""
+ """Easing FIFO access mode.
+ **Returns:** data"""
+
return self.popitem(last=False)
def pop_first_until(self, ts):
- """Pop all item until a given timestamped value and return the last poped item"""
+ """Pop all item until a given timestamped value and return the last poped item.
+ **Returns:** data"""
# get last timestamp before given timestamp
earliest_ts = self.get_last_before(ts)
@@ -64,15 +69,20 @@ class TimeStampedBuffer(collections.OrderedDict):
return popep_ts, poped_value
def get_last(self):
- """Easing access to last item"""
+ """Easing access to last item.
+ **Returns:** data"""
+
return list(self.items())[-1]
def pop_last(self):
- """Easing FIFO access mode"""
+ """Easing FIFO access mode.
+ **Returns:** data"""
+
return self.popitem(last=True)
def get_last_before(self, ts):
- """Retreive last item timestamp before a given timestamp value."""
+ """Retreive last item timestamp before a given timestamp value.
+ **Returns:** data"""
ts_list = list(self.keys())
last_before_index = bisect.bisect_left(ts_list, ts) - 1
@@ -87,6 +97,7 @@ class TimeStampedBuffer(collections.OrderedDict):
def export_as_json(self, filepath):
"""Write buffer content into a json file."""
+
try:
with open(filepath, 'w', encoding='utf-8') as jsonfile:
json.dump(self, jsonfile, ensure_ascii = False, default=vars)
@@ -94,7 +105,8 @@ class TimeStampedBuffer(collections.OrderedDict):
raise RuntimeError(f'Can\' write {filepath}')
def as_dataframe(self, exclude=[], split={}):
- """Convert buffer as pandas dataframe. Timestamped values must be stored as dictionary where each keys will be related to a column."""
+ """Convert buffer as pandas dataframe. Timestamped values must be stored as dictionary where each keys will be related to a column.
+ **Returns:** pandas.Dataframe"""
df = pandas.DataFrame.from_dict(self.values())
df.drop(exclude, inplace=True, axis=True)
@@ -110,6 +122,7 @@ class TimeStampedBuffer(collections.OrderedDict):
def export_as_csv(self, filepath, exclude=[]):
"""Write buffer content into a csv file."""
+
try:
self.as_dataframe(exclude=exclude).to_csv(filepath, index=True)
@@ -118,6 +131,8 @@ class TimeStampedBuffer(collections.OrderedDict):
raise RuntimeError(f'Can\' write {filepath}')
def plot(self, names=[], colors=[], split={}, samples=None):
+ """Plot data into time chart.
+ **Returns:** list"""
df = self.as_dataframe(split=split)
legend_patches = []
diff --git a/src/argaze/GazeFeatures.py b/src/argaze/GazeFeatures.py
index be5c430..23364ae 100644
--- a/src/argaze/GazeFeatures.py
+++ b/src/argaze/GazeFeatures.py
@@ -15,15 +15,20 @@ class GazePosition():
"""Define gaze position as a tuple of coordinates with accuracy."""
value: tuple
+ """Positon value."""
+
accuracy: float = 0.
+ """Positon accuracy."""
def __getitem__(self, key):
"""Get a position coordinate."""
+
return self.value[key]
def __setitem__(self, key, coord):
"""Set a position coordinate."""
- self.value[name] = coord
+
+ self.value[key] = coord
def __iter__(self):
return iter(self.value)
@@ -35,9 +40,13 @@ class GazePosition():
return numpy.array(self.value)
def valid(self):
+ """Is the accuracy greater than 0 ?
+ **Returns:** bool"""
+
return self.accuracy >= 0
def draw(self, frame, color=(0, 255, 255)):
+ """Draw gaze position point and accuracy circle."""
if self.valid():
@@ -63,10 +72,13 @@ class TimeStampedGazePositions(DataStructures.TimeStampedBuffer):
@dataclass
class Movement():
- """Define abstract movement class."""
+ """Define abstract movement class as a buffer of timestamped positions."""
positions: TimeStampedGazePositions
+ """All timestamp gaze positions."""
+
duration: float = field(init=False)
+ """Inferred duration from first and last timestamps."""
def __post_init__(self):
@@ -92,8 +104,13 @@ class GazeStatus():
"""Define gaze status as a position belonging to an identified and indexed movement."""
position: GazePosition
+ """Gaze position"""
+
movement_type: str
+ """Movement type to which gaze position belongs."""
+
movement_index: int
+ """Movement index to which gaze positon belongs."""
class TimeStampedGazeStatus(DataStructures.TimeStampedBuffer):
"""Define timestamped buffer to store gaze status."""
@@ -312,10 +329,14 @@ class VisualScanGenerator():
raise NotImplementedError('__iter__() method not implemented')
def steps(self):
+ """Get visual scan steps.
+ **Returns:** list"""
+
return self.visual_scan_steps
def as_dataframe(self):
- """Convert buffer as pandas dataframe."""
+ """Convert buffer as pandas dataframe.
+ **Returns:** pandas.Dataframe"""
df = pandas.DataFrame.from_dict(self.visual_scan_steps)
df.set_index('timestamp', inplace=True)
@@ -325,6 +346,7 @@ class VisualScanGenerator():
def export_as_csv(self, filepath):
"""Write buffer content into a csv file."""
+
try:
self.as_dataframe().to_csv(filepath, index=True)
diff --git a/src/argaze/TobiiGlassesPro2/TobiiController.py b/src/argaze/TobiiGlassesPro2/TobiiController.py
index 49e0834..21db697 100644
--- a/src/argaze/TobiiGlassesPro2/TobiiController.py
+++ b/src/argaze/TobiiGlassesPro2/TobiiController.py
@@ -59,7 +59,8 @@ class TobiiController(TobiiNetworkInterface.TobiiNetworkInterface):
# STREAMING FEATURES
def enable_data_stream(self):
- """Enable Tobii Glasses Pro 2 data streaming."""
+ """Enable Tobii Glasses Pro 2 data streaming.
+ **Returns:** TobiiDataStream"""
if self.__data_stream == None:
self.__data_stream = TobiiData.TobiiDataStream(self)
@@ -67,7 +68,8 @@ class TobiiController(TobiiNetworkInterface.TobiiNetworkInterface):
return self.__data_stream
def enable_video_stream(self):
- """Enable Tobii Glasses Pro 2 video camera streaming."""
+ """Enable Tobii Glasses Pro 2 video camera streaming.
+ **Returns:** TobiiVideoStream"""
if self.__video_stream == None:
self.__video_stream = TobiiVideo.TobiiVideoStream(self)
@@ -75,6 +77,7 @@ class TobiiController(TobiiNetworkInterface.TobiiNetworkInterface):
return self.__video_stream
def start_streaming(self):
+ """Start data and/or video streaming."""
if self.__data_stream != None:
self.__data_stream.open()
@@ -83,6 +86,7 @@ class TobiiController(TobiiNetworkInterface.TobiiNetworkInterface):
self.__video_stream.open()
def stop_streaming(self):
+ """Stop data and/or video streaming."""
if self.__data_stream != None:
self.__data_stream.close()
@@ -93,7 +97,8 @@ class TobiiController(TobiiNetworkInterface.TobiiNetworkInterface):
# PROJECT FEATURES
def set_project(self, project_name = DEFAULT_PROJECT_NAME):
- """Bind to a project or create one if it doesn't exist."""
+ """Bind to a project or create one if it doesn't exist.
+ **Returns:** str"""
project_id = self.get_project_id(project_name)
@@ -117,6 +122,8 @@ class TobiiController(TobiiNetworkInterface.TobiiNetworkInterface):
return project_id
def get_project_id(self, project_name):
+ """Get project id.
+ **Returns:** str"""
project_id = None
projects = super().get_request('/api/projects')
@@ -132,12 +139,16 @@ class TobiiController(TobiiNetworkInterface.TobiiNetworkInterface):
return project_id
def get_projects(self):
+ """Get all projects id.
+ **Returns:** json str"""
+
return super().get_request('/api/projects')
# PARTICIPANT FEATURES
def set_participant(self, project_id, participant_name = DEFAULT_PARTICIPANT_NAME, participant_notes = ''):
- """Bind to a participant or create one if it doesn't exist."""
+ """Bind to a participant or create one if it doesn't exist.
+ **Returns:** str"""
participant_id = self.get_participant_id(participant_name)
@@ -162,6 +173,8 @@ class TobiiController(TobiiNetworkInterface.TobiiNetworkInterface):
return participant_id
def get_participant_id(self, participant_name):
+ """Get participant id.
+ **Returns:** str"""
participant_id = None
participants = super().get_request('/api/participants')
@@ -178,6 +191,9 @@ class TobiiController(TobiiNetworkInterface.TobiiNetworkInterface):
return participant_id
def get_participants(self):
+ """Get all participants id.
+ **Returns:** json str"""
+
return super().get_request('/api/participants')
# CALIBRATION
@@ -211,6 +227,8 @@ class TobiiController(TobiiNetworkInterface.TobiiNetworkInterface):
return super().wait_for_status('/api/recordings/' + recording_id + '/status', 'rec_state', status_array)
def create_recording(self, participant_name, recording_notes = ''):
+ """Create a new recording.
+ **Returns:** str"""
participant_id = self.get_participant_id(participant_name)
@@ -235,19 +253,22 @@ class TobiiController(TobiiNetworkInterface.TobiiNetworkInterface):
return json_data['rec_id']
def start_recording(self, recording_id):
- """Start recording on the Tobii interface's SD Card."""
+ """Start recording on the Tobii interface's SD Card.
+ **Returns:** bool"""
super().post_request('/api/recordings/' + recording_id + '/start')
return self.__wait_for_recording_status(recording_id, ['recording']) == 'recording'
def stop_recording(self, recording_id):
- """Stop recording on the Tobii interface's SD Card."""
+ """Stop recording on the Tobii interface's SD Card.
+ **Returns:** bool"""
super().post_request('/api/recordings/' + recording_id + '/stop')
return self.__wait_for_recording_status(recording_id, ['done']) == "done"
def pause_recording(self, recording_id):
- """Pause recording on the Tobii interface's SD Card."""
+ """Pause recording on the Tobii interface's SD Card.
+ **Returns:** bool"""
super().post_request('/api/recordings/' + recording_id + '/pause')
return self.__wait_for_recording_status(recording_id, ['paused']) == "paused"
@@ -256,9 +277,14 @@ class TobiiController(TobiiNetworkInterface.TobiiNetworkInterface):
return self.get_status()['sys_recording']
def get_current_recording_id(self):
+ """Get current recording id.
+ **Returns:** str"""
+
return self.__get_recording_status()['rec_id']
def is_recording(self):
+ """Is it recording?
+ **Returns:** bool"""
rec_status = self.__get_recording_status()
@@ -269,6 +295,9 @@ class TobiiController(TobiiNetworkInterface.TobiiNetworkInterface):
return False
def get_recordings(self):
+ """Get all recordings id.
+ **Returns:** json str"""
+
return super().get_request('/api/recordings')
# EVENTS AND EXPERIMENTAL VARIABLES
diff --git a/src/argaze/TobiiGlassesPro2/TobiiData.py b/src/argaze/TobiiGlassesPro2/TobiiData.py
index 71ce234..f1338a4 100644
--- a/src/argaze/TobiiGlassesPro2/TobiiData.py
+++ b/src/argaze/TobiiGlassesPro2/TobiiData.py
@@ -24,21 +24,24 @@ class DirSig():
@dataclass
class PresentationTimeStamp():
- """Define presentation time stamp data (pts)."""
+ """Define presentation time stamp (pts) data."""
value: int
+ """Pts value."""
@dataclass
class VideoTimeStamp():
- """Define video time stamp data (vts)."""
+ """Define video time stamp (vts) data."""
value: int
+ """Vts value."""
@dataclass
class EventSynch():
- """Define event synch data (evts)."""
+ """Define event synch (evts) data."""
value: int # meaning ?
+ """Evts value."""
@dataclass
class Event():
@@ -53,12 +56,14 @@ class Accelerometer():
"""Define accelerometer data (ac)."""
value: numpy.array
+ """Accelerometer value"""
@dataclass
class Gyroscope():
"""Define gyroscope data (gy)."""
value: numpy.array
+ """Gyroscope value"""
@dataclass
class PupilCenter():
@@ -111,7 +116,7 @@ class MarkerPosition():
value_3d: tuple((float, float, float))
value_2d: tuple((float, float))
-class TobiiJsonDataParser():
+class __TobiiJsonDataParser():
def parse_dir_sig(self, status, json_data):
@@ -212,7 +217,7 @@ class TobiiDataSegment():
self.__vts_offset = 0
self.__vts_ts = -1
- self.__json_data_parser = TobiiJsonDataParser()
+ self.__json_data_parser = __TobiiJsonDataParser()
self.__ts_data_buffer_dict = {
'DirSig': DataStructures.TimeStampedBuffer(),
@@ -282,16 +287,22 @@ class TobiiDataSegment():
return self.__ts_data_buffer_dict[key]
def keys(self):
- """Get all registered data keys"""
+ """Get all registered data keys.
+ **Returns:** dict_keys"""
+
return list(self.__ts_data_buffer_dict.keys())
def get_path(self):
+ """Get segment data path.
+ **Returns:** str"""
+
return self.__segment_data_path
class TobiiDataStream():
"""Capture Tobii Glasses Pro 2 data stream in separate thread."""
reading_callback = None
+ """Reading callback function to get incoming (data_ts, data_object, data_object_type) back."""
def __init__(self, network_interface: TobiiNetworkInterface.TobiiNetworkInterface):
"""Initialise network connection and prepare data reception."""
@@ -303,7 +314,7 @@ class TobiiDataStream():
# Data reception
self.__data_thread = None
self.__data_queue = queue.Queue(50) # TODO : set queue size according technical reason
- self.__json_data_parser = TobiiJsonDataParser()
+ self.__json_data_parser = __TobiiJsonDataParser()
self.__first_ts = 0
# Data capture
@@ -424,6 +435,8 @@ class TobiiDataStream():
self.__read_lock.release()
def read(self):
+ """Read incoming timestamped data.
+ **Returns:** int, dict"""
# no data to read
if self.__data_queue.empty():
diff --git a/src/argaze/TobiiGlassesPro2/TobiiEntities.py b/src/argaze/TobiiGlassesPro2/TobiiEntities.py
index 926b239..f64328f 100644
--- a/src/argaze/TobiiGlassesPro2/TobiiEntities.py
+++ b/src/argaze/TobiiGlassesPro2/TobiiEntities.py
@@ -54,30 +54,55 @@ class TobiiSegment:
self.__stop_date = datetime.datetime.strptime(item["seg_t_stop"], TOBII_DATETIME_FORMAT)
def get_path(self):
+ """Get segment path.
+ **Returns:** str"""
+
return self.__segment_path
def get_id(self):
+ """Get segment id.
+ **Returns:** str"""
return self.__segment_id
def get_start_timestamp(self):
+ """Get the timestamp where the segment loading starts.
+ **Returns:** int"""
+
return self.__start_timestamp
def get_end_timestamp(self):
+ """Get the timestamp where the segment loading ends.
+ **Returns:** int"""
+
return self.__end_timestamp
def get_start_date(self):
+ """Get the date when the segment has started.
+ **Returns:** datetime"""
+
return self.__start_date
def get_stop_date(self):
+ """Get the date when the segment has stopped.
+ **Returns:** datetime"""
+
return self.__stop_date
def is_calibrated(self):
+ """Is the segment has been calibrated?
+ **Returns:** bool"""
return self.__calibrated
def load_data(self):
+ """Load recorded data stream.
+ **Returns:** TobiiData.TobiiDataSegment"""
+
return TobiiData.TobiiDataSegment(os.path.join(self.__segment_path, TOBII_SEGMENT_DATA_FILENAME), self.__start_timestamp, self.__end_timestamp)
def load_video(self):
+ """Load recorded video stream.
+ **Returns:** TobiiData.TobiiVideoSegment"""
+
return TobiiVideo.TobiiVideoSegment(os.path.join(self.__segment_path, TOBII_SEGMENT_VIDEO_FILENAME), self.__start_timestamp, self.__end_timestamp)
class TobiiRecording:
@@ -88,6 +113,7 @@ class TobiiRecording:
self.__recording_id = os.path.basename(recording_path)
self.__recording_path = recording_path
+
self.__project_path = os.path.dirname(os.path.dirname(os.path.abspath(self.__recording_path)))
with open(os.path.join(self.__recording_path, TOBII_RECORD_FILENAME)) as f:
@@ -96,67 +122,80 @@ class TobiiRecording:
except:
raise RuntimeError(f'JSON fails to load {self.__recording_path}/{TOBII_RECORD_FILENAME}')
- self.__recording_created = datetime.datetime.strptime(item["rec_created"], TOBII_DATETIME_FORMAT)
self.__recording_name = item["rec_info"]["Name"]
+ self.__recording_created = datetime.datetime.strptime(item["rec_created"], TOBII_DATETIME_FORMAT)
+
self.__recording_length = int(item["rec_length"])
- self.__recording_segments = int(item["rec_segments"])
self.__recording_et_samples = int(item["rec_et_samples"])
self.__recording_et_valid_samples = int(item["rec_et_valid_samples"])
+
+ self.__recording_segments = int(item["rec_segments"])
+
self.__project = TobiiProject(self.__project_path)
self.__participant = TobiiParticipant(self.__recording_path)
- def get_attributes(self):
- """Get recording attributes dictionnary."""
-
- attr = []
- names = []
- names.append("Recording name"); attr.append(self.__recording_name)
- names.append("Project name"); attr.append(self.__project.getName())
- names.append("Creation Date"); attr.append(str(self.__recording_created))
- names.append("Duration (s)"); attr.append(str(self.__recording_length))
- names.append("Participant name"); attr.append(self.__participant.getName())
- names.append("Segments"); attr.append(str(self.__recording_segments))
- names.append("Et samples"); attr.append(str(self.__recording_et_samples))
- names.append("Et valid samples"); attr.append(str(self.__recording_et_valid_samples))
- return (names, attr)
-
def get_path(self):
+ """Get recording path.
+ **Returns:** str"""
+
return self.__recording_path
+ def get_id(self):
+ """Get recording id.
+ **Returns:** str"""
+
+ return self.__recording_id
+
+ def get_name(self):
+ """Get recording name.
+ **Returns:** str"""
+
+ return self.__recording_name
+
def get_creation_date(self):
+ """Get date when the recording has been done.
+ **Returns:** datetime"""
+
return self.__recording_created
+ def get_length(self):
+ """Get record duration in second.
+ **Returns:** int"""
+
+ return self.__recording_length
+
def get_et_samples(self):
+ """Get numbers of recorded eye tracker samples.
+ **Returns:** int"""
+
return self.__recording_et_samples
def get_et_valid_samples(self):
- return self.__recording_et_valid_samples
+ """Get numbers of recorded eye tracker valid samples.
+ **Returns:** int"""
- def get_id(self):
- return self.__recording_id
+ return self.__recording_et_valid_samples
- def get_length(self):
- return self.__recording_length
+ def get_project(self):
+ """Get project to which it belongs.
+ **Returns:** TobiiProject"""
- def get_name(self):
- return self.__recording_name
+ return self.__project
def get_participant(self):
- return self.__participant
-
- def get_recording_directory(self):
- return self.__recording_dir
+ """Get participant to which it belongs.
+ **Returns:** TobiiParticipant"""
- def get_segment(self, segment_id):
- if segment_id > 0:
- return self.__segments[segment_id-1]
- raise ValueError('Cannot get segment less or equal to zero')
+ return self.__participant
- def get_all_segments(self):
+ def get_segments(self):
+ """Get all recorded segments.
+ **Returns:** list of TobiiSegment"""
all_segments = []
segments_path = os.path.join(self.__recording_path, TOBII_SEGMENTS_DIRNAME)
- for item in os.listdir (segments_path):
+
+ for item in os.listdir(segments_path):
segment_path = os.path.join(segments_path, item)
if os.path.isdir(segment_path):
all_segments.append(TobiiSegment(segment_path))
@@ -181,12 +220,21 @@ class TobiiParticipant:
self.__participant_name = item["pa_info"]["Name"]
def get_path(self):
+ """Get participant path.
+ **Returns:** str"""
+
return self.__participant_path
def get_id(self):
+ """Get participant id.
+ **Returns:** str"""
+
return self.__participant_id
def get_name(self):
+ """Get participant name.
+ **Returns:** str"""
+
return self.__participant_name
class TobiiProject:
@@ -212,21 +260,36 @@ class TobiiProject:
self.__project_name = None
def get_path(self):
+ """Get project path.
+ **Returns:** str"""
+
return self.__project_path
def get_creation_date(self):
+ """Get date when the project has been created.
+ **Returns:** datetime"""
+
return self.__project_created
def get_id(self):
+ """Get project id.
+ **Returns:** str"""
+
return self.__project_id
def get_name(self):
+ """Get project name.
+ **Returns:** str"""
+
return self.__project_name
- def get_all_participants(self):
+ def get_participants(self):
+ """Get all participants.
+ **Returns:** list of TobiiParticipant"""
all_participants = []
participants_path = os.path.join(self.__project_path, TOBII_PARTICIPANTS_DIRNAME)
+
for item in os.listdir(participants_path):
participant_path = os.path.join(participants_path, item)
if os.path.isdir(participant_path):
@@ -234,10 +297,13 @@ class TobiiProject:
return all_participants
- def get_all_recordings(self):
+ def get_recordings(self):
+ """Get all recordings.
+ **Returns:** list of TobiiRecording"""
all_recordings = []
recordings_path = os.path.join(self.__project_path, TOBII_RECORDINGS_DIRNAME)
+
for item in os.listdir(recordings_path):
recording_path = os.path.join(recordings_path, item)
if os.path.isdir(recording_path):
@@ -254,12 +320,18 @@ class TobiiDrive:
self.__drive_path = drive_path
def get_path(self):
+ """Get drive path.
+ **Returns:** str"""
+
return self.__drive_path
- def get_all_projects(self):
+ def get_projects(self):
+ """Get all projects.
+ **Returns:** list of TobiiProject"""
all_projects = []
projects_path = os.path.join(self.__drive_path, TOBII_PROJECTS_DIRNAME)
+
for item in os.listdir(projects_path):
project_path = os.path.join(projects_path, item)
if os.path.isdir(project_path):
diff --git a/src/argaze/TobiiGlassesPro2/TobiiInertialMeasureUnit.py b/src/argaze/TobiiGlassesPro2/TobiiInertialMeasureUnit.py
index 12cc2c6..5c45d7c 100644
--- a/src/argaze/TobiiGlassesPro2/TobiiInertialMeasureUnit.py
+++ b/src/argaze/TobiiGlassesPro2/TobiiInertialMeasureUnit.py
@@ -11,15 +11,18 @@ import numpy
from scipy.optimize import curve_fit
import cv2 as cv
-# Earth gravity force (m/s2)
+
EARTH_GRAVITY = -9.81
+"""Earth gravity force (m/s2)."""
+
EARTH_GRAVITY_VECTOR = [0, EARTH_GRAVITY, 0]
+"""Earth gravity force vector."""
-# Translation vector from camera referential to imu referential (cm)
CAMERA_TO_IMU_TRANSLATION_VECTOR = [8, -1, -5]
+"""Translation vector from camera referential to imu referential (cm)."""
-# Rotation vector from camera referential to imu referential (euler, degree)
CAMERA_TO_IMU_ROTATION_VECTOR = [18, 0, 180]
+"""Rotation vector from camera referential to imu referential (euler, degree)."""
class TobiiInertialMeasureUnit():
"""Ease Tobbi IMU data handling."""
@@ -61,6 +64,8 @@ class TobiiInertialMeasureUnit():
json.dump(calibration_data, calibration_file, ensure_ascii=False, indent=4)
def calibrate_gyroscope_offset(self, gyroscope_ts_buffer):
+ """Calibrate gyroscope offset from a timestamped gyroscope buffer.
+ **Returns:** numpy.array"""
# Consider gyroscope values without timestamps
gyroscope_values = []
@@ -78,12 +83,14 @@ class TobiiInertialMeasureUnit():
return self.__gyroscope_offset
def get_gyroscope_offset(self):
- """Get gyroscope offset."""
+ """Get gyroscope offset.
+ **Returns:** numpy.array"""
return self.__gyroscope_offset
def apply_gyroscope_offset(self, gyroscope_data_object):
- """Remove gyroscope offset to given gyroscope data."""
+ """Remove gyroscope offset to given gyroscope data.
+ **Returns:** TobiiData.Gyroscope"""
return TobiiData.Gyroscope(gyroscope_data_object.value - self.__gyroscope_offset)
@@ -117,7 +124,8 @@ class TobiiInertialMeasureUnit():
self.__last_gyroscope = current_gyroscope
def get_rotation(self):
- """Return current rotation value."""
+ """Return current rotation value (euler angles in degree).
+ **Returns:** numpy.array"""
return self.__rotation
@@ -150,12 +158,14 @@ class TobiiInertialMeasureUnit():
self.__accelerometer_coefficients[axis] = numpy.array(optimal_coefficients)
def get_accelerometer_coefficients(self):
- """Return accelerometer coefficients."""
+ """Return accelerometer coefficients.
+ **Returns:** numpy.array"""
return self.__accelerometer_coefficients
def apply_accelerometer_coefficients(self, accelerometer_data_object):
- """Add accelerometer offset to given accelerometer data."""
+ """Add accelerometer offset to given accelerometer data.
+ **Returns:** TobiiData.Accelerometer"""
x = self._accelerometer_linear_fit(accelerometer_data_object.value[0], *self.__accelerometer_coefficients[0])
y = self._accelerometer_linear_fit(accelerometer_data_object.value[1], *self.__accelerometer_coefficients[1])
@@ -212,7 +222,8 @@ class TobiiInertialMeasureUnit():
# print('no valid head plumb')
def get_translation(self):
- """Return current translation speed and translation values."""
+ """Return current translation speed and translation values.
+ **Returns:** numpy.array, numpy array"""
return self.__translation_speed, self.__translation
@@ -226,11 +237,13 @@ class TobiiInertialMeasureUnit():
assert(math.isclose(numpy.linalg.norm(self.__plumb), math.fabs(EARTH_GRAVITY), abs_tol=1e-3))
def get_plumb(self):
- """Return plumb vector."""
+ """Return plumb vector.
+ **Returns:** numpy.array"""
return self.__plumb
def apply_plumb(self, accelerometer_data_object):
- """Remove gravity along plumb vector to given accelerometer data."""
+ """Remove gravity along plumb vector to given accelerometer data.
+ **Returns:** TobiiData.Accelerometer"""
return TobiiData.Accelerometer(accelerometer_data_object.value - self.__plumb)
diff --git a/src/argaze/TobiiGlassesPro2/TobiiNetworkInterface.py b/src/argaze/TobiiGlassesPro2/TobiiNetworkInterface.py
index 2b55eb7..3d7d1e1 100644
--- a/src/argaze/TobiiGlassesPro2/TobiiNetworkInterface.py
+++ b/src/argaze/TobiiGlassesPro2/TobiiNetworkInterface.py
@@ -64,6 +64,8 @@ class TobiiNetworkInterface():
self.__peer = (self.address, self.udpport)
def make_socket(self):
+ """Create a socket to enable network communication.
+ **Returns:** socket"""
iptype = socket.AF_INET
@@ -136,16 +138,22 @@ class TobiiNetworkInterface():
return (None, None)
def get_request(self, api_action):
+ """Send a GET request and get data back.
+ **Returns:** json str"""
url = self.base_url + api_action
res = urlopen(url).read()
+
try:
data = json.loads(res.decode('utf-8'))
except json.JSONDecodeError:
data = None
+
return data
def post_request(self, api_action, data=None, wait_for_response=True):
+ """Send a POST request and get result back.
+ **Returns:** json str"""
url = self.base_url + api_action
req = Request(url)
@@ -165,17 +173,19 @@ class TobiiNetworkInterface():
try:
res = json.loads(res.decode('utf-8'))
-
except:
pass
return res
def send_keep_alive_msg(self, socket, msg):
+ """Send a message to keep socket opened."""
res = socket.sendto(msg.encode('utf-8'), self.__peer)
def grab_data(self, socket):
+ """Read incoming socket data.
+ **Returns:** bytes"""
try:
data, address = socket.recvfrom(1024)
@@ -186,6 +196,8 @@ class TobiiNetworkInterface():
logging.error("A timeout occurred while receiving data")
def wait_for_status(self, api_action, key, values, timeout = None):
+ """Wait until a status matches given values.
+ **Returns:** status value"""
url = self.base_url + api_action
running = True
diff --git a/src/argaze/TobiiGlassesPro2/TobiiSpecifications.py b/src/argaze/TobiiGlassesPro2/TobiiSpecifications.py
index d4808de..1b2a275 100644
--- a/src/argaze/TobiiGlassesPro2/TobiiSpecifications.py
+++ b/src/argaze/TobiiGlassesPro2/TobiiSpecifications.py
@@ -1,7 +1,15 @@
#!/usr/bin/env python
-# [Tobii specifications reference](https://www.biorxiv.org/content/10.1101/299925v1)
-ACCURACY = 1.42 # degree
+"""Here is the article where from following [Tobii specifications](https://www.biorxiv.org/content/10.1101/299925v1) come."""
+
+ACCURACY = 1.42
+"""Gaze position accuracy in degree."""
+
PRECISION = 0.34 # degree
-CAMERA_HFOV = 82 # degree
-VISUAL_HFOV = 160 # degree \ No newline at end of file
+"""Gaze position precision in degree."""
+
+CAMERA_HFOV = 82
+"""Camera horizontal field of view in degree."""
+
+VISUAL_HFOV = 160
+"""Visual horizontal field of view in degree.""" \ No newline at end of file
diff --git a/src/argaze/TobiiGlassesPro2/TobiiVideo.py b/src/argaze/TobiiGlassesPro2/TobiiVideo.py
index 82ee429..1e0899e 100644
--- a/src/argaze/TobiiGlassesPro2/TobiiVideo.py
+++ b/src/argaze/TobiiGlassesPro2/TobiiVideo.py
@@ -18,15 +18,23 @@ class TobiiVideoFrame():
"""Define tobii video frame"""
matrix: list
+ """Video frame matrix."""
+
width: int = field(init=False)
+ """Inferred video frame width."""
+
height: int = field(init=False)
+ """Inferred video frame height."""
def __post_init__(self):
"""fill dimension attributes."""
+
self.height, self.width = self.matrix.shape[:2]
def copy(self):
- """Copy tobii video frame."""
+ """Copy tobii video frame.
+ **Returns:** TobiiVideoFrame"""
+
return TobiiVideoFrame(self.matrix.copy())
class TobiiVideoSegment():
@@ -50,29 +58,41 @@ class TobiiVideoSegment():
self.__container.seek(self.__start_timestamp)
def get_path(self):
+ """Get video segment path.
+ **Returns:** str"""
+
return self.__segment_video_path
def get_duration(self):
- """Duration in microsecond"""
+ """Duration in microsecond.
+ **Returns:** int"""
+
if self.__end_timestamp == None:
return int((self.__stream.duration * self.__stream.time_base) * 1e6) - self.__start_timestamp
else:
return self.__end_timestamp - self.__start_timestamp
def get_width(self):
+ """Video width dimension.
+ **Returns:** int"""
+
return self.__width
def get_height(self):
+ """Video height dimension.
+ **Returns:** int"""
+
return self.__height
def get_stream(self):
- return self.__stream
+ """Video stream.
+ **Returns:** av stream"""
- def get_vts_offset(self):
- return self.__vts_offset
+ return self.__stream
def get_frame(self, i):
- """Acces to a frame."""
+ """Access to a frame.
+ **Returns:** int, TobiiVideoFrame"""
if i < 0:
ValueError('Frame index must be a positive integer.')
@@ -97,7 +117,8 @@ class TobiiVideoSegment():
return video_ts, TobiiVideoFrame(frame.to_ndarray(format='bgr24'))
def frames(self):
- """Access to frame iterator."""
+ """Access to frame iterator.
+ **Returns:** int, TobiiVideoFrame"""
return self.__iter__()
@@ -203,6 +224,8 @@ class TobiiVideoStream(threading.Thread):
self.__read_lock.release()
def read(self):
+ """Read incoming video frames.
+ **Returns:** int, TobiiVideoFrame"""
# if the video acquisition thread have been stopped or isn't started
if self.__stop_event.isSet() or self.__frame_tuple == None:
@@ -238,6 +261,9 @@ class TobiiVideoOutput():
bit_rate=referent_stream.codec_context.bit_rate)
def get_path(self):
+ """Get video file path.
+ **Returns:** str"""
+
return self.__output_video_path
def write(self, frame):
diff --git a/src/argaze/utils/tobii_sdcard_explore.py b/src/argaze/utils/tobii_sdcard_explore.py
index e80057f..c462d0f 100644
--- a/src/argaze/utils/tobii_sdcard_explore.py
+++ b/src/argaze/utils/tobii_sdcard_explore.py
@@ -22,7 +22,7 @@ def main():
# Load all projects from a tobii drive
tobii_drive = TobiiEntities.TobiiDrive(args.drive_path)
- for project in tobii_drive.get_all_projects():
+ for project in tobii_drive.get_projects():
print(f'Project id: {project.get_id()}, name: {project.get_name()}')
elif args.project_path != None:
@@ -30,10 +30,10 @@ def main():
# Load one tobii project
tobii_project = TobiiEntities.TobiiProject(args.project_path)
- for participant in tobii_project.get_all_participants():
+ for participant in tobii_project.get_participants():
print(f'Participant id: {participant.get_id()}, name: {participant.get_name()}')
- for recording in tobii_project.get_all_recordings():
+ for recording in tobii_project.get_recordings():
print(f'Recording id: {recording.get_id()}, name: {recording.get_name()}')
elif args.recording_path != None:
@@ -41,7 +41,7 @@ def main():
# Load a tobii segment
tobii_recording = TobiiEntities.TobiiRecording(args.recording_path)
- for segment in tobii_recording.get_all_segments():
+ for segment in tobii_recording.get_segments():
print(f'Segment id: {segment.get_id()}')
elif args.segment_path != None: