From bd9cd27c9d44c072164f564ffffeb22e37106b89 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Tue, 6 Jun 2023 17:47:16 +0200 Subject: Updating documentation format. --- src/argaze/ArFeatures.py | 118 ++++++++++++++------- src/argaze/ArUcoMarkers/ArUcoBoard.py | 4 +- src/argaze/ArUcoMarkers/ArUcoCamera.py | 2 +- src/argaze/ArUcoMarkers/ArUcoDetector.py | 8 +- src/argaze/ArUcoMarkers/ArUcoMarker.py | 2 +- src/argaze/ArUcoMarkers/ArUcoScene.py | 4 +- src/argaze/AreaOfInterest/AOI2DScene.py | 6 +- src/argaze/AreaOfInterest/AOIFeatures.py | 2 +- .../DispersionThresholdIdentification.py | 4 +- .../VelocityThresholdIdentification.py | 6 +- src/argaze/GazeFeatures.py | 10 +- src/argaze/PupilAnalysis/__init__.py | 1 - src/argaze/utils/README.md | 10 +- src/argaze/utils/__init__.py | 3 +- 14 files changed, 112 insertions(+), 68 deletions(-) diff --git a/src/argaze/ArFeatures.py b/src/argaze/ArFeatures.py index e60f792..5899545 100644 --- a/src/argaze/ArFeatures.py +++ b/src/argaze/ArFeatures.py @@ -24,21 +24,20 @@ ArEnvironmentType = TypeVar('ArEnvironment', bound="ArEnvironment") ArSceneType = TypeVar('ArScene', bound="ArScene") # Type definition for type annotation convenience -AOI2DSceneType = TypeVar('AOI2DScene', bound="AOI2DScene") -# Type definition for type annotation convenience - @dataclass class ArEnvironment(): - """Define an Augmented Reality environment based ArUco marker detection.""" + """ + Define Augmented Reality environment based on ArUco marker detection. - name: str - """Environment name""" + Parameters: + name: Environment name + aruco_detector: ArUco detector + scenes: All environment scenes + """ + name: str aruco_detector: ArUcoDetector.ArUcoDetector = field(default_factory=ArUcoDetector.ArUcoDetector) - """ArUco detector""" - scenes: dict = field(default_factory=dict) - """All environment scenes""" def __post_init__(self): @@ -48,7 +47,12 @@ class ArEnvironment(): @classmethod def from_json(self, json_filepath: str) -> ArSceneType: - """Load ArEnvironment from .json file.""" + """ + Load ArEnvironment from .json file. + + Parameters: + json_filepath: path to json file + """ with open(json_filepath) as configuration_file: @@ -117,7 +121,10 @@ class ArEnvironment(): return ArEnvironment(new_name, new_aruco_detector, new_scenes) def __str__(self) -> str: - """String display""" + """ + Returns: + String representation + """ output = f'Name:\n{self.name}\n' output += f'ArUcoDetector:\n{self.aruco_detector}\n' @@ -135,7 +142,9 @@ class ArEnvironment(): json.dump(self, file, ensure_ascii=False, indent=4, cls=DataStructures.JsonEncoder) class PoseEstimationFailed(Exception): - """Exception raised by ArScene estimate_pose method when the pose can't be estimated due to unconsistencies.""" + """ + Exception raised by ArScene estimate_pose method when the pose can't be estimated due to unconsistencies. + """ def __init__(self, message, unconsistencies=None): @@ -144,7 +153,9 @@ class PoseEstimationFailed(Exception): self.unconsistencies = unconsistencies class SceneProjectionFailed(Exception): - """Exception raised by ArScene project method when the scene can't be projected.""" + """ + Exception raised by ArScene project method when the scene can't be projected. + """ def __init__(self, message): @@ -152,26 +163,30 @@ class SceneProjectionFailed(Exception): @dataclass class ArScene(): - """Define an Augmented Reality scene with ArUco markers and AOI scenes.""" + """ + Define an Augmented Reality scene with ArUco markers and AOI scenes. - aruco_scene: ArUcoScene.ArUcoScene = field(default_factory=ArUcoScene.ArUcoScene) - """ArUco markers 3D scene description used to estimate scene pose from detected markers: see `estimate_pose` function below.""" + Parameters: + aruco_scene: ArUco markers 3D scene description used to estimate scene pose from detected markers: see [estimate_pose][argaze.ArFeatures.ArScene.estimate_pose] function below. - aoi_scene: AOI3DScene.AOI3DScene = field(default_factory=AOI3DScene.AOI3DScene) - """AOI 3D scene description that will be projected onto estimated scene once its pose will be estimated : see `project` function below.""" + aoi_scene: AOI 3D scene description that will be projected onto estimated scene once its pose will be estimated : see [project][argaze.ArFeatures.ArScene.project] function below. - aruco_axis: dict = field(default_factory=dict) - """Optional dictionary to define orthogonal axis where each axis is defined by list of 3 markers identifier (first is origin). \ - This pose estimation strategy is used by `estimate_pose` function when at least 3 markers are detected.""" + aruco_axis: Optional dictionary to define orthogonal axis where each axis is defined by list of 3 markers identifier (first is origin). \ + This pose estimation strategy is used by [estimate_pose][argaze.ArFeatures.ArScene.estimate_pose] function when at least 3 markers are detected. - aruco_aoi: dict = field(default_factory=dict) - """Optional dictionary of AOI defined by list of markers identifier and markers corners index tuples: see `build_aruco_aoi_scene` function below.""" + aruco_aoi: Optional dictionary of AOI defined by list of markers identifier and markers corners index tuples: see [build_aruco_aoi_scene][argaze.ArFeatures.ArScene.build_aruco_aoi_scene] function below. - angle_tolerance: float = field(default=0.) - """Optional angle error tolerance to validate marker pose in degree used into `estimate_pose` function.""" + angle_tolerance: Optional angle error tolerance to validate marker pose in degree used into [estimate_pose][argaze.ArFeatures.ArScene.estimate_pose] function. + + distance_tolerance: Optional distance error tolerance to validate marker pose in centimeter used into [estimate_pose][argaze.ArFeatures.ArScene.estimate_pose] function. + """ + aruco_scene: ArUcoScene.ArUcoScene = field(default_factory=ArUcoScene.ArUcoScene) + aoi_scene: AOI3DScene.AOI3DScene = field(default_factory=AOI3DScene.AOI3DScene) + aruco_axis: dict = field(default_factory=dict) + aruco_aoi: dict = field(default_factory=dict) + angle_tolerance: float = field(default=0.) distance_tolerance: float = field(default=0.) - """Optional distance error tolerance to validate marker pose in centimeter used into `estimate_pose` function.""" def __post_init__(self): @@ -182,7 +197,10 @@ class ArScene(): self.__orthogonal_projection_cache = self.orthogonal_projection def __str__(self) -> str: - """String display""" + """ + Returns: + String representation + """ output = f'ArEnvironment:\n{self._environment.name}\n' output += f'ArUcoScene:\n{self.aruco_scene}\n' @@ -191,8 +209,13 @@ class ArScene(): return output @property - def orthogonal_projection(self) -> AOI2DSceneType: - """Orthogonal projection of whole AOI scene.""" + def orthogonal_projection(self) -> AOI2DScene.AOI2DScene: + """ + Orthogonal projection of whole AOI scene. + + Returns: + projected AOI 2D scene + """ scene_size = self.aoi_scene.size @@ -268,13 +291,13 @@ class ArScene(): return tvec, rmat, 'estimate_pose_from_markers', consistent_markers - def project(self, tvec: numpy.array, rvec: numpy.array, visual_hfov=0) -> AOI2DSceneType: + def project(self, tvec: numpy.array, rvec: numpy.array, visual_hfov: float = 0.) -> AOI2DScene.AOI2DScene: """Project AOI scene according estimated pose and optional horizontal field of view clipping angle. - * **Arguments:** - - translation vector - - rotation vector - - horizontal field of view clipping angle + Parameters: + tvec: translation vector + rvec: rotation vector + visual_hfov: horizontal field of view clipping angle """ # Clip AOI out of the visual horizontal field of view (optional) @@ -305,8 +328,13 @@ class ArScene(): return aoi_scene_projection - def build_aruco_aoi_scene(self, detected_markers) -> AOI2DSceneType: - """Build AOI scene from ArUco markers into frame as defined in aruco_aoi dictionary.""" + def build_aruco_aoi_scene(self, detected_markers) -> AOI2DScene.AOI2DScene: + """ + Build AOI scene from ArUco markers into frame as defined in aruco_aoi dictionary. + + Returns: + built AOI 2D scene + """ # AOI projection fails when no marker is detected if len(detected_markers) == 0: @@ -343,13 +371,23 @@ class ArScene(): return AOI2DScene.AOI2DScene(aruco_aoi_scene) - def draw_axis(self, frame): - """Draw scene axis into frame.""" + def draw_axis(self, frame: numpy.array): + """ + Draw scene axis into frame. + + Parameters: + frame: where to draw + """ self.aruco_scene.draw_axis(frame, self._environment.aruco_detector.camera.K, self._environment.aruco_detector.camera.D) - def draw_places(self, frame): - """Draw scene places into frame.""" + def draw_places(self, frame: numpy.array): + """ + Draw scene places into frame. + + Parameters: + frame: where to draw + """ self.aruco_scene.draw_places(frame, self._environment.aruco_detector.camera.K, self._environment.aruco_detector.camera.D) diff --git a/src/argaze/ArUcoMarkers/ArUcoBoard.py b/src/argaze/ArUcoMarkers/ArUcoBoard.py index 7cab792..e9a2b2d 100644 --- a/src/argaze/ArUcoMarkers/ArUcoBoard.py +++ b/src/argaze/ArUcoMarkers/ArUcoBoard.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -""" """ +"""Calibration chess board with ArUco markers inside.""" __author__ = "Théo de la Hogue" __credits__ = [] @@ -17,7 +17,7 @@ import cv2.aruco as aruco @dataclass class ArUcoBoard(): - """Calibration chess board with ArUco markers inside.""" + """ """ columns: int = field(default=0) """Number of columns.""" diff --git a/src/argaze/ArUcoMarkers/ArUcoCamera.py b/src/argaze/ArUcoMarkers/ArUcoCamera.py index 4f943e2..205c591 100644 --- a/src/argaze/ArUcoMarkers/ArUcoCamera.py +++ b/src/argaze/ArUcoMarkers/ArUcoCamera.py @@ -63,7 +63,7 @@ class CalibrationData(): return output - def draw(self, frame, width:float, height:float, z:float, color=(0, 0, 255)): + def draw(self, frame: numpy.array, width:float, height:float, z:float, color=(0, 0, 255)): """Draw grid to display K and D""" # Edit 3D grid diff --git a/src/argaze/ArUcoMarkers/ArUcoDetector.py b/src/argaze/ArUcoMarkers/ArUcoDetector.py index 2f424f6..4392e7a 100644 --- a/src/argaze/ArUcoMarkers/ArUcoDetector.py +++ b/src/argaze/ArUcoMarkers/ArUcoDetector.py @@ -175,7 +175,7 @@ class ArUcoDetector(): return output - def detect_markers(self, frame): + def detect_markers(self, frame: numpy.array): """Detect all ArUco markers into a frame. .. danger:: DON'T MIRROR FRAME @@ -262,14 +262,14 @@ class ArUcoDetector(): return len(list(self.__detected_markers.keys())) - def draw_detected_markers(self, frame): + def draw_detected_markers(self, frame: numpy.array): """Draw traked markers.""" for marker_id, marker in self.__detected_markers.items(): marker.draw(frame, self.camera.K, self.camera.D) - def detect_board(self, frame, board, expected_markers_number): + def detect_board(self, frame: numpy.array, board, expected_markers_number): """Detect ArUco markers board in frame setting up the number of detected markers needed to agree detection. .. danger:: DON'T MIRROR FRAME @@ -293,7 +293,7 @@ class ArUcoDetector(): self.__board_corners = [] self.__board_corners_ids = [] - def draw_board(self, frame): + def draw_board(self, frame: numpy.array): """Draw detected board corners in frame.""" if self.__board != None: diff --git a/src/argaze/ArUcoMarkers/ArUcoMarker.py b/src/argaze/ArUcoMarkers/ArUcoMarker.py index 1e02ad3..8b59166 100644 --- a/src/argaze/ArUcoMarkers/ArUcoMarker.py +++ b/src/argaze/ArUcoMarkers/ArUcoMarker.py @@ -59,7 +59,7 @@ class ArUcoMarker(): return numpy.repeat(matrix, 3).reshape(dimension, dimension, 3) - def draw(self, frame, K, D): + def draw(self, frame: numpy.array, K, D): """Draw marker in frame.""" # Draw marker axis if pose has been estimated diff --git a/src/argaze/ArUcoMarkers/ArUcoScene.py b/src/argaze/ArUcoMarkers/ArUcoScene.py index d069df0..672e31c 100644 --- a/src/argaze/ArUcoMarkers/ArUcoScene.py +++ b/src/argaze/ArUcoMarkers/ArUcoScene.py @@ -612,7 +612,7 @@ class ArUcoScene(): self._rotation = rmat - def draw_axis(self, frame, K, D, consistency=2): + def draw_axis(self, frame: numpy.array, K, D, consistency=2): """Draw scene axis according a consistency score.""" l = self.marker_size / 2 @@ -637,7 +637,7 @@ class ArUcoScene(): except cv.error: pass - def draw_places(self, frame, K, D, consistency=2): + def draw_places(self, frame: numpy.array, K, D, consistency=2): """Draw scene places and their axis according a consistency score.""" l = self.marker_size / 2 diff --git a/src/argaze/AreaOfInterest/AOI2DScene.py b/src/argaze/AreaOfInterest/AOI2DScene.py index 73988b7..8301e23 100644 --- a/src/argaze/AreaOfInterest/AOI2DScene.py +++ b/src/argaze/AreaOfInterest/AOI2DScene.py @@ -23,7 +23,7 @@ class AOI2DScene(AOIFeatures.AOIScene): super().__init__(2, aois_2d) - def draw(self, frame, exclude=[], color=(0, 255, 255)): + def draw(self, frame: numpy.array, exclude=[], color=(0, 255, 255)): """Draw AOI polygons on frame.""" for name, aoi in self.items(): @@ -47,7 +47,7 @@ class AOI2DScene(AOIFeatures.AOIScene): yield name, aoi, matching - def draw_raycast(self, frame, pointer:tuple, exclude=[], base_color=(0, 0, 255), matching_color=(0, 255, 0)): + def draw_raycast(self, frame: numpy.array, pointer:tuple, exclude=[], base_color=(0, 0, 255), matching_color=(0, 255, 0)): """Draw AOIs with their matching status.""" for name, aoi, matching in self.raycast(pointer): @@ -81,7 +81,7 @@ class AOI2DScene(AOIFeatures.AOIScene): yield name, aoi, matching_region, aoi_ratio, circle_ratio - def draw_circlecast(self, frame, center:tuple, radius:float, exclude=[], base_color=(0, 0, 255), matching_color=(0, 255, 0)): + def draw_circlecast(self, frame: numpy.array, center:tuple, radius:float, exclude=[], base_color=(0, 0, 255), matching_color=(0, 255, 0)): """Draw AOIs with their matching status and matching region.""" for name, aoi, matching_region, aoi_ratio, circle_ratio in self.circlecast(center, radius): diff --git a/src/argaze/AreaOfInterest/AOIFeatures.py b/src/argaze/AreaOfInterest/AOIFeatures.py index 438e15f..4a109a8 100644 --- a/src/argaze/AreaOfInterest/AOIFeatures.py +++ b/src/argaze/AreaOfInterest/AOIFeatures.py @@ -184,7 +184,7 @@ class AreaOfInterest(numpy.ndarray): return empty_array, 0., 0. - def draw(self, frame, color, border_size=1): + def draw(self, frame: numpy.array, color, border_size=1): """Draw 2D AOI into frame. !!! warning Available for 2D AOI only.""" diff --git a/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py b/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py index 9d5a8d7..c79b4fc 100644 --- a/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py +++ b/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py @@ -78,7 +78,7 @@ class Fixation(GazeFeatures.Fixation): return self - def draw(self, frame, color=(127, 127, 127), border_color=(255, 255, 255)): + def draw(self, frame: numpy.array, color=(127, 127, 127), border_color=(255, 255, 255)): """Draw fixation into frame.""" cv2.circle(frame, (int(self.focus[0]), int(self.focus[1])), int(self.deviation_max), color, -1) @@ -91,7 +91,7 @@ class Saccade(GazeFeatures.Saccade): def __post_init__(self): super().__post_init__() - def draw(self, frame, color=(255, 255, 255)): + def draw(self, frame: numpy.array, color=(255, 255, 255)): """Draw saccade into frame.""" _, start_position = self.positions.first diff --git a/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py b/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py index 4883eb0..32af1c5 100644 --- a/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py +++ b/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py @@ -78,7 +78,7 @@ class Fixation(GazeFeatures.Fixation): return self - def draw(self, frame, color=(127, 127, 127), border_color=(255, 255, 255)): + def draw(self, frame: numpy.array, color=(127, 127, 127), border_color=(255, 255, 255)): """Draw fixation into frame.""" cv2.circle(frame, (int(self.focus[0]), int(self.focus[1])), int(self.deviation_max), color, -1) @@ -91,7 +91,7 @@ class Saccade(GazeFeatures.Saccade): def __post_init__(self): super().__post_init__() - def draw(self, frame, color=(255, 255, 255)): + def draw(self, frame: numpy.array, color=(255, 255, 255)): """Draw saccade into frame.""" _, start_position = self.positions.first @@ -106,7 +106,7 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier): Dario D. Salvucci and Joseph H. Goldberg. 2000. Identifying fixations and saccades in eye-tracking protocols. In Proceedings of the 2000 symposium on Eye tracking research & applications (ETRA '00). ACM, New York, NY, USA, - 71-78. [DOI=http://dx.doi.org/10.1145/355017.355028](DOI=http://dx.doi.org/10.1145/355017.355028) + 71-78. [http://dx.doi.org/10.1145/355017.355028](http://dx.doi.org/10.1145/355017.355028) """ velocity_max_threshold: int|float diff --git a/src/argaze/GazeFeatures.py b/src/argaze/GazeFeatures.py index 6209dc9..0e40238 100644 --- a/src/argaze/GazeFeatures.py +++ b/src/argaze/GazeFeatures.py @@ -88,7 +88,7 @@ class GazePosition(): else: return distance < self.precision - def draw(self, frame, color=(0, 255, 255), draw_precision=True): + def draw(self, frame: numpy.array, color=(0, 255, 255), draw_precision=True): """Draw gaze position point and precision circle.""" if self.valid: @@ -190,7 +190,7 @@ class GazeMovement(): return output - def draw_positions(self, frame, color=(0, 55, 55)): + def draw_positions(self, frame: numpy.array, color=(0, 55, 55)): """Draw gaze movement positions""" gaze_positions = self.positions.copy() @@ -224,7 +224,7 @@ class Fixation(GazeMovement): raise NotImplementedError('merge() method not implemented') - def draw(self, frame, color): + def draw(self, frame: numpy.array, color): """Draw fixation into frame.""" raise NotImplementedError('draw() method not implemented') @@ -241,7 +241,7 @@ class Saccade(GazeMovement): super().__post_init__() - def draw(self, frame, color): + def draw(self, frame: numpy.array, color): """Draw saccade into frame.""" raise NotImplementedError('draw() method not implemented') @@ -442,7 +442,7 @@ class ScanPath(list): self.__last_fixation = fixation - def draw(self, frame, fixation_color=(255, 255, 255), saccade_color=(255, 255, 255), deepness=0): + def draw(self, frame: numpy.array, fixation_color=(255, 255, 255), saccade_color=(255, 255, 255), deepness=0): """Draw scan path into frame.""" last_step = None diff --git a/src/argaze/PupilAnalysis/__init__.py b/src/argaze/PupilAnalysis/__init__.py index 41698b5..c563968 100644 --- a/src/argaze/PupilAnalysis/__init__.py +++ b/src/argaze/PupilAnalysis/__init__.py @@ -1,5 +1,4 @@ """ Class interface to work with various pupil analysis algorithms. """ -__docformat__ = "restructuredtext" __all__ = ['WorkloadIndex'] \ No newline at end of file diff --git a/src/argaze/utils/README.md b/src/argaze/utils/README.md index d5af45a..3c7d616 100644 --- a/src/argaze/utils/README.md +++ b/src/argaze/utils/README.md @@ -6,6 +6,8 @@ Collection of command-line high level features scripts. !!! note *Use -h option to get command arguments documentation.* + + # ArUco Markers factory Export all markers from *DICT_APRILTAG_16h5* dictionary as 5 cm pictures with 300 dpi resolution into an *./src/argaze/utils/_export/markers* folder: @@ -31,6 +33,10 @@ python ./src/argaze/utils/camera_calibrate.py 7 5 5 3 DICT_APRILTAG_16h5 -d DEVI !!! note Use **A3_DICT_APRILTAG_16h5_3cm_35cmx25cm.pdf** file located in *./src/argaze/ArUcoMarkers/utils/* folder ready to be printed on A3 paper sheet. + + + + # AR environment demonstration Load AR environment from **setup.json** file, detect ArUco markers into camera device (-d DEVICE) frames and estimate envirnoment pose. @@ -60,4 +66,6 @@ A picture is saved by pressing escape key. ``` python ./src/argaze/utils/demo_heatmap_run.py -``` \ No newline at end of file +``` + + \ No newline at end of file diff --git a/src/argaze/utils/__init__.py b/src/argaze/utils/__init__.py index 8134e6f..94deb78 100644 --- a/src/argaze/utils/__init__.py +++ b/src/argaze/utils/__init__.py @@ -1,5 +1,4 @@ """ -{!./src/argaze/utils/README.md!} +Collection of command-line high level features scripts. """ -__docformat__ = "restructuredtext" __all__ = ['MiscFeatures'] \ No newline at end of file -- cgit v1.1