aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorThéo de la Hogue2022-11-08 14:42:36 +0100
committerThéo de la Hogue2022-11-08 14:42:36 +0100
commitd061d4729122cf7dbda2f78252826f4c93debc94 (patch)
tree9cba1bc4f3ee2e8cb4fbd3aff334a85653b1e190 /src
parentb92a114386c9abf5b2d22d013a18ae848e1eeca7 (diff)
downloadargaze-d061d4729122cf7dbda2f78252826f4c93debc94.zip
argaze-d061d4729122cf7dbda2f78252826f4c93debc94.tar.gz
argaze-d061d4729122cf7dbda2f78252826f4c93debc94.tar.bz2
argaze-d061d4729122cf7dbda2f78252826f4c93debc94.tar.xz
Improving code documentation.
Diffstat (limited to 'src')
-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
21 files changed, 417 insertions, 133 deletions
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: