From f9046f1c979f2daf9d7180286df002059a97fc3b Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Wed, 20 Sep 2023 12:07:50 +0200 Subject: Working on ArUcoMarkersGroup and ArUcoScene drawing features. --- src/argaze/ArFeatures.py | 28 +++++-- src/argaze/ArUcoMarkers/ArUcoCamera.py | 20 +++-- src/argaze/ArUcoMarkers/ArUcoMarkersGroup.py | 95 ++++++++++++++-------- src/argaze/ArUcoMarkers/ArUcoScene.py | 18 ++-- src/argaze/GazeFeatures.py | 4 +- .../utils/demo_data/demo_aruco_markers_setup.json | 4 +- .../utils/demo_data/demo_gaze_analysis_setup.json | 5 +- 7 files changed, 106 insertions(+), 68 deletions(-) diff --git a/src/argaze/ArFeatures.py b/src/argaze/ArFeatures.py index b9a29de..96976c2 100644 --- a/src/argaze/ArFeatures.py +++ b/src/argaze/ArFeatures.py @@ -510,7 +510,7 @@ DEFAULT_ARFRAME_IMAGE_PARAMETERS = { }, "deepness": 0 }, - "draw_gaze_position": { + "draw_gaze_positions": { "color": (0, 255, 255), "size": 2 } @@ -976,7 +976,7 @@ class ArFrame(): # Return look data return identified_gaze_movement, scan_step_analysis, layer_analysis, execution_times, exception - def __image(self, background_weight: float = None, heatmap_weight: float = None, draw_scan_path: dict = None, draw_layers: dict = None, draw_gaze_position: dict = None) -> numpy.array: + def __image(self, background_weight: float = None, heatmap_weight: float = None, draw_scan_path: dict = None, draw_layers: dict = None, draw_gaze_positions: dict = None, draw_fixations: dict = None, draw_saccades: dict = None) -> numpy.array: """ Get background image with overlaid visualisations. @@ -985,7 +985,9 @@ class ArFrame(): heatmap_weight: weight of heatmap overlay draw_scan_path: [GazeFeatures.ScanPath.draw](argaze.md/#argaze.GazeFeatures.ScanPath.draw) parameters (if None, no scan path is drawn) draw_layers: dictionary of [ArLayer.draw](argaze.md/#argaze.ArFeatures.ArLayer.draw) parameters per layer (if None, no layer is drawn) - draw_gaze_position: [GazeFeatures.GazePosition.draw](argaze.md/#argaze.GazeFeatures.GazePosition.draw) parameters (if None, no gaze position is drawn) + draw_gaze_positions: [GazeFeatures.GazePosition.draw](argaze.md/#argaze.GazeFeatures.GazePosition.draw) parameters (if None, no gaze position is drawn) + draw_fixations: [GazeFeatures.Fixation.draw](argaze.md/#argaze.GazeFeatures.Fixation.draw) parameters (if None, no fixation is drawn) + draw_saccades: [GazeFeatures.Saccade.draw](argaze.md/#argaze.GazeFeatures.Saccade.draw) parameters (if None, no saccade is drawn) """ # Lock frame exploitation @@ -1025,10 +1027,20 @@ class ArFrame(): self.layers[layer_name].draw(image, **draw_layer) + # Draw current fixation if required + if draw_fixations is not None and self.gaze_movement_identifier is not None: + + self.gaze_movement_identifier.current_fixation.draw(image, **draw_fixations) + + # Draw current saccade if required + if draw_saccades is not None and self.gaze_movement_identifier is not None: + + self.gaze_movement_identifier.current_saccade.draw(image, **draw_saccades) + # Draw current gaze position if required - if draw_gaze_position is not None: + if draw_gaze_positions is not None: - self.__gaze_position.draw(image, **draw_gaze_position) + self.__gaze_position.draw(image, **draw_gaze_positions) # Unlock frame exploitation self.__look_lock.release() @@ -1280,15 +1292,15 @@ class ArScene(): # Project layer aoi scene yield name, aoi_scene_copy.project(tvec, rvec, self.parent.aruco_detector.optic_parameters.K) - def draw_axis(self, image: numpy.array): + def draw(self, image: numpy.array, **kwargs): """ - Draw scene axis into image. + Draw scene into image. Parameters: image: where to draw """ - raise NotImplementedError('draw_axis() method not implemented') + raise NotImplementedError('draw() method not implemented') @dataclass class ArCamera(ArFrame): diff --git a/src/argaze/ArUcoMarkers/ArUcoCamera.py b/src/argaze/ArUcoMarkers/ArUcoCamera.py index b067666..4f555fb 100644 --- a/src/argaze/ArUcoMarkers/ArUcoCamera.py +++ b/src/argaze/ArUcoMarkers/ArUcoCamera.py @@ -211,11 +211,12 @@ class ArUcoCamera(ArFeatures.ArCamera): # Return dection time and exceptions return detection_time, exceptions - def __image(self, draw_detected_markers: dict = None, draw_optic_parameters_grid: dict = None, **kwargs) -> numpy.array: + def __image(self, draw_detected_markers: dict = None, draw_scenes: dict = None, draw_optic_parameters_grid: dict = None, **kwargs) -> numpy.array: """Get frame image with ArUco detection visualisation. Parameters: draw_detected_markers: ArucoMarker.draw parameters (if None, no marker drawn) + draw_scenes: ArUcoScene.draw parameters (if None, no scene drawn) draw_optic_parameters_grid: OpticParameter.draw parameters (if None, no grid drawn) kwargs: ArCamera.image parameters """ @@ -230,16 +231,23 @@ class ArUcoCamera(ArFeatures.ArCamera): # Get camera frame image image = super().image(**kwargs) - # Draw detected markers if required - if draw_detected_markers is not None: - - self.aruco_detector.draw_detected_markers(image, draw_detected_markers) - # Draw optic parameters grid if required if draw_optic_parameters_grid is not None: self.aruco_detector.optic_parameters.draw(image, **draw_optic_parameters_grid) + # Draw scenes if required + if draw_scenes is not None: + + for scene_name, draw_scenes_parameters in draw_scenes.items(): + + self.scenes[scene_name].draw(image, **draw_scenes_parameters) + + # Draw detected markers if required + if draw_detected_markers is not None: + + self.aruco_detector.draw_detected_markers(image, draw_detected_markers) + # Unlock camera frame exploitation self._frame_lock.release() diff --git a/src/argaze/ArUcoMarkers/ArUcoMarkersGroup.py b/src/argaze/ArUcoMarkers/ArUcoMarkersGroup.py index bdcf70c..5b6c69d 100644 --- a/src/argaze/ArUcoMarkers/ArUcoMarkersGroup.py +++ b/src/argaze/ArUcoMarkers/ArUcoMarkersGroup.py @@ -617,40 +617,26 @@ class ArUcoMarkersGroup(): self._rotation = rmat - def draw_axis(self, image: numpy.array, K, D, consistency=2): - """Draw group axis according a consistency score.""" - - l = self.marker_size / 2 - ll = self.marker_size - - # Select color according consistency score - n = 95 * consistency if consistency < 2 else 0 - f = 159 * consistency if consistency < 2 else 255 + def draw_axes(self, image: numpy.array, K, D, thickness: int = 0, length: float = 0): + """Draw group axes.""" try: - - # Draw axis - axisPoints = numpy.float32([[ll, 0, 0], [0, ll, 0], [0, 0, ll], [0, 0, 0]]).reshape(-1, 3) + axisPoints = numpy.float32([[length, 0, 0], [0, length, 0], [0, 0, length], [0, 0, 0]]).reshape(-1, 3) axisPoints, _ = cv.projectPoints(axisPoints, self._rotation, self._translation, numpy.array(K), numpy.array(D)) axisPoints = axisPoints.astype(int) - cv.line(image, tuple(axisPoints[3].ravel()), tuple(axisPoints[0].ravel()), (n,n,f), 6) # X (red) - cv.line(image, tuple(axisPoints[3].ravel()), tuple(axisPoints[1].ravel()), (n,f,n), 6) # Y (green) - cv.line(image, tuple(axisPoints[3].ravel()), tuple(axisPoints[2].ravel()), (f,n,n), 6) # Z (blue) + cv.line(image, tuple(axisPoints[3].ravel()), tuple(axisPoints[0].ravel()), (0, 0, 255), thickness) # X (red) + cv.line(image, tuple(axisPoints[3].ravel()), tuple(axisPoints[1].ravel()), (0, 255, 0), thickness) # Y (green) + cv.line(image, tuple(axisPoints[3].ravel()), tuple(axisPoints[2].ravel()), (255, 0, 0), thickness) # Z (blue) # Ignore errors due to out of field axis: their coordinate are larger than int32 limitations. except cv.error: pass - def draw_places(self, image: numpy.array, K, D, consistency=2): - """Draw group places and their axis according a consistency score.""" + def draw_places(self, image: numpy.array, K, D, color: tuple = None, border_size: int = 0): + """Draw group places.""" l = self.marker_size / 2 - ll = self.marker_size - - # Select color according consistency score - n = 95 * consistency if consistency < 2 else 0 - f = 159 * consistency if consistency < 2 else 255 for identifier, place in self.places.items(): @@ -659,29 +645,66 @@ class ArUcoMarkersGroup(): T = self.places[identifier].translation R = self.places[identifier].rotation - # Draw place axis - axisPoints = (T + numpy.float32([R.dot([l/2, 0, 0]), R.dot([0, l/2, 0]), R.dot([0, 0, l/2]), R.dot([0, 0, 0])])).reshape(-1, 3) - axisPoints, _ = cv.projectPoints(axisPoints, self._rotation, self._translation, numpy.array(K), numpy.array(D)) - axisPoints = axisPoints.astype(int) - - cv.line(image, tuple(axisPoints[3].ravel()), tuple(axisPoints[0].ravel()), (n,n,f), 6) # X (red) - cv.line(image, tuple(axisPoints[3].ravel()), tuple(axisPoints[1].ravel()), (n,f,n), 6) # Y (green) - cv.line(image, tuple(axisPoints[3].ravel()), tuple(axisPoints[2].ravel()), (f,n,n), 6) # Z (blue) - - # Draw place placePoints = (T + numpy.float32([R.dot([-l, -l, 0]), R.dot([l, -l, 0]), R.dot([l, l, 0]), R.dot([-l, l, 0])])).reshape(-1, 3) placePoints, _ = cv.projectPoints(placePoints, self._rotation, self._translation, numpy.array(K), numpy.array(D)) placePoints = placePoints.astype(int) - cv.line(image, tuple(placePoints[0].ravel()), tuple(placePoints[1].ravel()), (f,f,f), 3) - cv.line(image, tuple(placePoints[1].ravel()), tuple(placePoints[2].ravel()), (f,f,f), 3) - cv.line(image, tuple(placePoints[2].ravel()), tuple(placePoints[3].ravel()), (f,f,f), 3) - cv.line(image, tuple(placePoints[3].ravel()), tuple(placePoints[0].ravel()), (f,f,f), 3) + cv.line(image, tuple(placePoints[0].ravel()), tuple(placePoints[1].ravel()), color, border_size) + cv.line(image, tuple(placePoints[1].ravel()), tuple(placePoints[2].ravel()), color, border_size) + cv.line(image, tuple(placePoints[2].ravel()), tuple(placePoints[3].ravel()), color, border_size) + cv.line(image, tuple(placePoints[3].ravel()), tuple(placePoints[0].ravel()), color, border_size) + + # Ignore errors due to out of field places: their coordinate are larger than int32 limitations. + except cv.error: + pass + + def draw_places_axes(self, image: numpy.array, K, D, thickness: int = 0, length: float = 0): + """Draw group place axes.""" + + for identifier, place in self.places.items(): + + try: + T = self.places[identifier].translation + R = self.places[identifier].rotation + + axisPoints = (T + numpy.float32([R.dot([length, 0, 0]), R.dot([0, length, 0]), R.dot([0, 0, length]), R.dot([0, 0, 0])])).reshape(-1, 3) + axisPoints, _ = cv.projectPoints(axisPoints, self._rotation, self._translation, numpy.array(K), numpy.array(D)) + axisPoints = axisPoints.astype(int) + + cv.line(image, tuple(axisPoints[3].ravel()), tuple(axisPoints[0].ravel()), (0, 0, 255), thickness) # X (red) + cv.line(image, tuple(axisPoints[3].ravel()), tuple(axisPoints[1].ravel()), (0, 255, 0), thickness) # Y (green) + cv.line(image, tuple(axisPoints[3].ravel()), tuple(axisPoints[2].ravel()), (255, 0, 0), thickness) # Z (blue) + # Ignore errors due to out of field places: their coordinate are larger than int32 limitations. except cv.error: pass + def draw(self, image: numpy.array, K, D, draw_axes: dict = None, draw_places: dict = None, draw_places_axes: dict = None): + """Draw group axes and places. + + Parameters: + + draw_axes: draw_axes parameters (if None, no axes drawn) + draw_places: draw_places parameters (if None, no places drawn) + draw_places_axes: draw_places_axes parameters (if None, no places axes drawn) + """ + + # Draw axes if required + if draw_axes is not None: + + self.draw_axes(image, K, D, **draw_axes) + + # Draw places if required + if draw_places is not None: + + self.draw_places(image, K, D, **draw_places) + + # Draw places axes if required + if draw_places_axes is not None: + + self.draw_places_axes(image, K, D, **draw_places_axes) + def to_obj(self, obj_filepath): """Save group to .obj file.""" diff --git a/src/argaze/ArUcoMarkers/ArUcoScene.py b/src/argaze/ArUcoMarkers/ArUcoScene.py index 3bbd3f4..f6b303a 100644 --- a/src/argaze/ArUcoMarkers/ArUcoScene.py +++ b/src/argaze/ArUcoMarkers/ArUcoScene.py @@ -139,22 +139,16 @@ class ArUcoScene(ArFeatures.ArScene): return tvec, rmat, 'estimate_pose_from_markers', consistent_markers - def draw_axis(self, image: numpy.array): + def draw(self, image: numpy.array, draw_aruco_markers_group: dict = None): """ - Draw scene axis into image. + Draw scene into image. Parameters: image: where to draw + draw_aruco_markers_group: ArUcoMarkersGroup.draw parameters (if None, no group drawn) """ - self.aruco_markers_group.draw_axis(image, self.parent.aruco_detector.optic_parameters.K, self.parent.aruco_detector.optic_parameters.D) + # Draw group if required + if draw_aruco_markers_group is not None: - def draw_places(self, image: numpy.array): - """ - Draw scene places into image. - - Parameters: - image: where to draw - """ - - self.aruco_markers_group.draw_places(image, self.parent.aruco_detector.optic_parameters.K, self.parent.aruco_detector.optic_parameters.D) + self.aruco_markers_group.draw(image, self.parent.aruco_detector.optic_parameters.K, self.parent.aruco_detector.optic_parameters.D, **draw_aruco_markers_group) diff --git a/src/argaze/GazeFeatures.py b/src/argaze/GazeFeatures.py index 5b89558..2dd1cab 100644 --- a/src/argaze/GazeFeatures.py +++ b/src/argaze/GazeFeatures.py @@ -302,7 +302,7 @@ class GazeMovement(): cv2.line(image, (int(start_gaze_position[0]), int(start_gaze_position[1])), (int(next_gaze_position[0]), int(next_gaze_position[1])), line_color, 1) - def draw(self, image: numpy.array): + def draw(self, image: numpy.array, **kwargs): """Draw gaze movement into image.""" raise NotImplementedError('draw() method not implemented') @@ -316,7 +316,7 @@ class UnvalidGazeMovement(GazeMovement): super().__init__(TimeStampedGazePositions()) - def draw(self, image: numpy.array, color): + def draw(self, image: numpy.array, **kwargs): pass diff --git a/src/argaze/utils/demo_data/demo_aruco_markers_setup.json b/src/argaze/utils/demo_data/demo_aruco_markers_setup.json index 14ebc15..9a3b79f 100644 --- a/src/argaze/utils/demo_data/demo_aruco_markers_setup.json +++ b/src/argaze/utils/demo_data/demo_aruco_markers_setup.json @@ -25,7 +25,7 @@ } } }, - "draw_gaze_position": { + "draw_gaze_positions": { "color": [0, 255, 255], "size": 4 }, @@ -115,7 +115,7 @@ } } }, - "draw_gaze_position": { + "draw_gaze_positions": { "color": [0, 255, 255], "size": 2 } diff --git a/src/argaze/utils/demo_data/demo_gaze_analysis_setup.json b/src/argaze/utils/demo_data/demo_gaze_analysis_setup.json index 0826d6f..414a6fe 100644 --- a/src/argaze/utils/demo_data/demo_gaze_analysis_setup.json +++ b/src/argaze/utils/demo_data/demo_gaze_analysis_setup.json @@ -92,8 +92,9 @@ } } }, - "draw_gaze_position": { - "color": [0, 255, 255] + "draw_gaze_positions": { + "color": [0, 255, 255], + "size": 2 } } } \ No newline at end of file -- cgit v1.1