From d061d4729122cf7dbda2f78252826f4c93debc94 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Tue, 8 Nov 2022 14:42:36 +0100 Subject: Improving code documentation. --- src/argaze/ArUcoMarkers/ArUcoBoard.py | 22 +++- src/argaze/ArUcoMarkers/ArUcoCamera.py | 12 +- src/argaze/ArUcoMarkers/ArUcoCube.py | 9 +- src/argaze/ArUcoMarkers/ArUcoMarker.py | 8 +- src/argaze/ArUcoMarkers/ArUcoMarkersDictionary.py | 12 +- src/argaze/ArUcoMarkers/ArUcoTracker.py | 23 +++- src/argaze/ArUcoMarkers/README.md | 2 +- src/argaze/AreaOfInterest/AOI2DScene.py | 8 +- src/argaze/AreaOfInterest/AOI3DScene.py | 11 +- src/argaze/AreaOfInterest/AOIFeatures.py | 54 ++++++-- src/argaze/AreaOfInterest/README.md | 3 +- src/argaze/DataStructures.py | 29 +++- src/argaze/GazeFeatures.py | 28 +++- src/argaze/TobiiGlassesPro2/TobiiController.py | 43 +++++- src/argaze/TobiiGlassesPro2/TobiiData.py | 27 +++- src/argaze/TobiiGlassesPro2/TobiiEntities.py | 146 +++++++++++++++------ .../TobiiGlassesPro2/TobiiInertialMeasureUnit.py | 35 +++-- .../TobiiGlassesPro2/TobiiNetworkInterface.py | 14 +- src/argaze/TobiiGlassesPro2/TobiiSpecifications.py | 16 ++- src/argaze/TobiiGlassesPro2/TobiiVideo.py | 40 +++++- src/argaze/utils/tobii_sdcard_explore.py | 8 +- 21 files changed, 417 insertions(+), 133 deletions(-) (limited to 'src') 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: -- cgit v1.1