From e6d7257954ea343329460a4d8e3863eacc47d222 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Wed, 30 Aug 2023 20:32:48 +0200 Subject: Fixing empty parameters cases. --- src/argaze/ArFeatures.py | 66 ++++++++++++---------- src/argaze/ArUcoMarkers/ArUcoDetector.py | 15 +++-- src/argaze/ArUcoMarkers/ArUcoMarker.py | 28 +++++---- src/argaze/GazeAnalysis/DeviationCircleCoverage.py | 14 ++--- .../DispersionThresholdIdentification.py | 4 +- src/argaze/GazeAnalysis/FocusPointInside.py | 12 ++-- .../VelocityThresholdIdentification.py | 13 ++++- src/argaze/GazeFeatures.py | 22 +++++--- 8 files changed, 104 insertions(+), 70 deletions(-) (limited to 'src') diff --git a/src/argaze/ArFeatures.py b/src/argaze/ArFeatures.py index d92c754..6553697 100644 --- a/src/argaze/ArFeatures.py +++ b/src/argaze/ArFeatures.py @@ -65,7 +65,6 @@ class LoadingFailed(Exception): super().__init__(message) - # Define default ArLayer draw parameters DEFAULT_ARLAYER_DRAW_PARAMETERS = { "draw_aoi_scene": { @@ -413,7 +412,7 @@ class ArLayer(): # Check gaze movement validity if gaze_movement.valid: - if self.aoi_matcher: + if self.aoi_matcher is not None: # Store aoi matching start date matching_start = time.perf_counter() @@ -431,12 +430,12 @@ class ArLayer(): if GazeFeatures.is_fixation(gaze_movement): # Append fixation to aoi scan path - if self.aoi_scan_path != None and looked_aoi != None: + if self.aoi_scan_path is not None and looked_aoi is not None: aoi_scan_step = self.aoi_scan_path.append_fixation(timestamp, gaze_movement, looked_aoi) # Is there a new step? - if aoi_scan_step and len(self.aoi_scan_path) > 1: + if aoi_scan_step is not None and len(self.aoi_scan_path) > 1: for aoi_scan_path_analyzer_module_path, aoi_scan_path_analyzer in self.aoi_scan_path_analyzers.items(): @@ -460,7 +459,7 @@ class ArLayer(): elif GazeFeatures.is_saccade(gaze_movement): # Append saccade to aoi scan path - if self.aoi_scan_path != None: + if self.aoi_scan_path is not None: self.aoi_scan_path.append_saccade(timestamp, gaze_movement) @@ -501,7 +500,7 @@ class ArLayer(): """ # Use draw_parameters attribute if no parameters - if not draw_aoi_scene and not draw_aoi_matching: + if draw_aoi_scene is None and draw_aoi_matching is None: return self.draw(image, **self.draw_parameters) @@ -509,12 +508,12 @@ class ArLayer(): self.__look_lock.acquire() # Draw aoi if required - if draw_aoi_scene: + if draw_aoi_scene is not None: self.aoi_scene.draw(image, **draw_aoi_scene) # Draw aoi matching if required - if draw_aoi_matching and self.aoi_matcher: + if draw_aoi_matching is not None and self.aoi_matcher is not None: self.aoi_matcher.draw(image, **draw_aoi_matching) @@ -537,7 +536,8 @@ DEFAULT_ARFRAME_IMAGE_PARAMETERS = { "deepness": 0 }, "draw_gaze_position": { - "color": (0, 255, 255) + "color": (0, 255, 255), + "size": 2 } } @@ -880,7 +880,7 @@ class ArFrame(): try: # Identify gaze movement - if self.gaze_movement_identifier: + if self.gaze_movement_identifier is not None: # Store movement identification start date identification_start = time.perf_counter() @@ -897,14 +897,14 @@ class ArFrame(): if GazeFeatures.is_fixation(identified_gaze_movement): # Append fixation to scan path - if self.scan_path != None: + if self.scan_path is not None: self.scan_path.append_fixation(timestamp, identified_gaze_movement) elif GazeFeatures.is_saccade(identified_gaze_movement): # Append saccade to scan path - if self.scan_path != None: + if self.scan_path is not None: scan_step = self.scan_path.append_saccade(timestamp, identified_gaze_movement) @@ -931,7 +931,7 @@ class ArFrame(): self.__ts_logs[scan_path_analyzer_module_path][timestamp] = scan_path_analyzer.analysis # No valid finished gaze movement: optionnaly stop in progress fixation filtering - elif self.gaze_movement_identifier and not self.filter_in_progress_fixation: + elif self.gaze_movement_identifier is not None and not self.filter_in_progress_fixation: current_fixation = self.gaze_movement_identifier.current_fixation @@ -940,7 +940,7 @@ class ArFrame(): identified_gaze_movement = current_fixation # Update heatmap - if self.heatmap: + if self.heatmap is not None: # Store heatmap start date heatmap_start = time.perf_counter() @@ -1016,7 +1016,7 @@ class ArFrame(): """ # Use image_parameters attribute if no parameters - if not background_weight and not heatmap_weight and not draw_scan_path and not draw_layers and not draw_gaze_position: + if background_weight is None and heatmap_weight is None and draw_scan_path is None and draw_layers is None and draw_gaze_position is None: return self.image(**self.image_parameters) @@ -1024,19 +1024,19 @@ class ArFrame(): self.__look_lock.acquire() # Draw background only - if background_weight and not heatmap_weight: + if background_weight is not None and heatmap_weight is None: image = self.background.copy() # Draw mix background and heatmap if required - elif background_weight and heatmap_weight and self.heatmap: + elif background_weight is not None and heatmap_weight is not None and self.heatmap: background_image = self.background.copy() heatmap_image = cv2.resize(self.heatmap.image, dsize=self.size, interpolation=cv2.INTER_LINEAR) image = cv2.addWeighted(heatmap_image, heatmap_weight, background_image, background_weight, 0) # Draw heatmap only - elif not background_weight and heatmap_weight and self.heatmap: + elif background_weight is None and heatmap_weight is not None and self.heatmap: image = cv2.resize(self.heatmap.image, dsize=self.size, interpolation=cv2.INTER_LINEAR) @@ -1046,19 +1046,19 @@ class ArFrame(): image = numpy.full((self.size[1], self.size[0], 3), 0).astype(numpy.uint8) # Draw scan path if required - if draw_scan_path and self.scan_path != None: + if draw_scan_path is not None and self.scan_path is not None: self.scan_path.draw(image, **draw_scan_path) # Draw layers if required - if draw_layers: + if draw_layers is not None: for layer_name, draw_layer in draw_layers.items(): self.layers[layer_name].draw(image, **draw_layer) # Draw current gaze position if required - if draw_gaze_position: + if draw_gaze_position is not None: self.__gaze_position.draw(image, **draw_gaze_position) @@ -1250,7 +1250,7 @@ class ArScene(): frame_layer.aoi_scene = layer_aoi_scene_projection.reframe(aoi_frame_projection, new_frame.size) - if frame_layer.aoi_scan_path != None: + if frame_layer.aoi_scan_path is not None: # Edit expected AOI list by removing AOI with name equals to frame layer name expected_aois = list(layer.aoi_scene.keys()) @@ -1441,7 +1441,12 @@ class ArScene(): # Define default ArEnvironment image_paremeters values DEFAULT_ARENVIRONMENT_IMAGE_PARAMETERS = { - "draw_detected_markers": True + "draw_detected_markers": { + "color": (0, 255, 0), + "draw_axes": { + "thickness": 3 + } + } } @dataclass @@ -1465,7 +1470,7 @@ class ArEnvironment(): def __post_init__(self): # Setup camera frame parent attribute - if self.camera_frame != None: + if self.camera_frame is not None: self.camera_frame.parent = self @@ -1556,11 +1561,11 @@ class ArEnvironment(): new_scenes[scene_name] = new_scene # Setup expected aoi of each camera frame layer aoi scan path with the aoi of corresponding scene layer - if new_camera_frame != None: + if new_camera_frame is not None: for camera_frame_layer_name, camera_frame_layer in new_camera_frame.layers.items(): - if camera_frame_layer.aoi_scan_path != None: + if camera_frame_layer.aoi_scan_path is not None: all_aoi_list = [] @@ -1798,11 +1803,12 @@ class ArEnvironment(): """Get camera frame projections with ArUco detection visualisation. Parameters: - draw_detected_markers: ArUcoDetector.draw_detected_markers parameters (If None, detected markers are not drawn) + image: image where to draw + draw_detected_markers: ArucoMarker.draw parameters (if None, no marker drawn) """ # Use image_parameters attribute if no parameters - if not draw_detected_markers: + if draw_detected_markers is None: return self.image(**self.image_parameters) @@ -1817,9 +1823,9 @@ class ArEnvironment(): image = self.camera_frame.image() # Draw detected markers if required - if draw_detected_markers: + if draw_detected_markers is not None: - self.aruco_detector.draw_detected_markers(image) + self.aruco_detector.draw_detected_markers(image, draw_detected_markers) # Unlock camera frame exploitation self.__camera_frame_lock.release() diff --git a/src/argaze/ArUcoMarkers/ArUcoDetector.py b/src/argaze/ArUcoMarkers/ArUcoDetector.py index 5076f3d..135eb08 100644 --- a/src/argaze/ArUcoMarkers/ArUcoDetector.py +++ b/src/argaze/ArUcoMarkers/ArUcoDetector.py @@ -274,12 +274,19 @@ class ArUcoDetector(): return len(list(self.__detected_markers.keys())) - def draw_detected_markers(self, image: numpy.array): - """Draw traked markers.""" + def draw_detected_markers(self, image: numpy.array, draw_marker: dict = None): + """Draw detected markers. - for marker_id, marker in self.__detected_markers.items(): + Parameters: + image: image where to draw + draw_marker: ArucoMarker.draw parameters (if None, no marker drawn) + """ + + if draw_marker is not None: + + for marker_id, marker in self.__detected_markers.items(): - marker.draw(image, self.optic_parameters.K, self.optic_parameters.D) + marker.draw(image, self.optic_parameters.K, self.optic_parameters.D, **draw_marker) def detect_board(self, image: numpy.array, board, expected_markers_number): """Detect ArUco markers board in image setting up the number of detected markers needed to agree detection. diff --git a/src/argaze/ArUcoMarkers/ArUcoMarker.py b/src/argaze/ArUcoMarkers/ArUcoMarker.py index 3a13c10..57bd8bd 100644 --- a/src/argaze/ArUcoMarkers/ArUcoMarker.py +++ b/src/argaze/ArUcoMarkers/ArUcoMarker.py @@ -12,7 +12,7 @@ from dataclasses import dataclass, field from argaze.ArUcoMarkers import ArUcoMarkersDictionary import numpy -import cv2 as cv +import cv2 import cv2.aruco as aruco @dataclass @@ -40,9 +40,6 @@ class ArUcoMarker(): points: numpy.array = field(init=False, repr=False) """Estimated 3D corners positions in camera world referential.""" - color: tuple = field(init=False, repr=False, default_factory=lambda : (0, 255, 0)) - """Color used to draw marker on image.""" - @property def center(self) -> numpy.array: """Get 2D center position in camera image referential.""" @@ -59,15 +56,24 @@ class ArUcoMarker(): return numpy.repeat(matrix, 3).reshape(dimension, dimension, 3) - def draw(self, image: numpy.array, K, D): - """Draw marker in image.""" + def draw(self, image: numpy.array, K, D, color: tuple = None, draw_axes: dict = None): + """Draw marker in image. + + Parameters: + image: image where to draw + color: marker color (if None, no marker drawn) + draw_axes: enable marker axes drawing + """ + + # Draw marker if required + if color is not None: - # Draw marker axis if pose has been estimated - if self.translation.size == 3 and self.rotation.size == 9: + aruco.drawDetectedMarkers(image, [self.corners], numpy.array([self.identifier]), color) - cv.drawFrameAxes(image, numpy.array(K), numpy.array(D), self.rotation, self.translation, self.size) + # Draw marker axes if pose has been estimated and if required + if self.translation.size == 3 and self.rotation.size == 9 and draw_axes is not None: - aruco.drawDetectedMarkers(image, [self.corners], numpy.array([self.identifier]), self.color) + cv2.drawFrameAxes(image, numpy.array(K), numpy.array(D), self.rotation, self.translation, self.size, **draw_axes) def save(self, destination_folder, dpi): """Save marker image as .png file into a destination folder.""" @@ -75,5 +81,5 @@ class ArUcoMarker(): filename = f'{self.dictionary.name}_{self.dictionary.format}_{self.identifier}.png' filepath = f'{destination_folder}/{filename}' - cv.imwrite(filepath, self.image(dpi)) + cv2.imwrite(filepath, self.image(dpi)) diff --git a/src/argaze/GazeAnalysis/DeviationCircleCoverage.py b/src/argaze/GazeAnalysis/DeviationCircleCoverage.py index 98706f7..e1b33f1 100644 --- a/src/argaze/GazeAnalysis/DeviationCircleCoverage.py +++ b/src/argaze/GazeAnalysis/DeviationCircleCoverage.py @@ -108,35 +108,35 @@ class AOIMatcher(GazeFeatures.AOIMatcher): looked_aoi_name_offset: ofset of text from the upper left aoi bounding box corner """ - if self.__matched_gaze_movement: + if self.__matched_gaze_movement is not None: if GazeFeatures.is_fixation(self.__matched_gaze_movement): # Draw matched fixation if required - if draw_matched_fixation: + if draw_matched_fixation is not None: self.__matched_gaze_movement.draw(image, **draw_matched_fixation) # Draw matched fixation positions if required - if draw_matched_fixation_positions: + if draw_matched_fixation_positions is not None: self.__matched_gaze_movement.draw_positions(image, **draw_matched_fixation_positions) # Draw matched aoi - if self.looked_aoi.all() != None: + if self.looked_aoi.all() is not None: # Draw looked aoi if required - if draw_looked_aoi: + if draw_looked_aoi is not None: self.looked_aoi.draw(image, **draw_looked_aoi) # Draw matched region if required - if draw_matched_region: + if draw_matched_region is not None: self.__matched_region.draw(image, **draw_matched_region) # Draw looked aoi name if required - if looked_aoi_name_color: + if looked_aoi_name_color is not None: top_left_corner_pixel = numpy.rint(self.looked_aoi.bounding_box[0]).astype(int) + looked_aoi_name_offset cv2.putText(image, self.looked_aoi_name, top_left_corner_pixel, cv2.FONT_HERSHEY_SIMPLEX, 1, looked_aoi_name_color, 1, cv2.LINE_AA) diff --git a/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py b/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py index bf7b862..85f2fb5 100644 --- a/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py +++ b/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py @@ -83,12 +83,12 @@ class Fixation(GazeFeatures.Fixation): """ # Draw deviation circle if required - if deviation_circle_color: + if deviation_circle_color is not None: cv2.circle(image, (int(self.focus[0]), int(self.focus[1])), int(self.deviation_max), deviation_circle_color, -1) # Draw duration border if required - if duration_border_color: + if duration_border_color is not None: cv2.circle(image, (int(self.focus[0]), int(self.focus[1])), int(self.deviation_max), duration_border_color, int(self.duration * duration_factor)) diff --git a/src/argaze/GazeAnalysis/FocusPointInside.py b/src/argaze/GazeAnalysis/FocusPointInside.py index 2ac4411..c071f82 100644 --- a/src/argaze/GazeAnalysis/FocusPointInside.py +++ b/src/argaze/GazeAnalysis/FocusPointInside.py @@ -63,30 +63,30 @@ class AOIMatcher(GazeFeatures.AOIMatcher): looked_aoi_name_offset: ofset of text from the upper left aoi bounding box corner """ - if self.__matched_gaze_movement: + if self.__matched_gaze_movement is not None: if GazeFeatures.is_fixation(self.__matched_gaze_movement): # Draw matched fixation if required - if draw_matched_fixation: + if draw_matched_fixation is not None: self.__matched_gaze_movement.draw(image, **draw_matched_fixation) # Draw matched fixation positions if required - if draw_matched_fixation_positions: + if draw_matched_fixation_positions is not None: self.__matched_gaze_movement.draw_positions(image, **draw_matched_fixation_positions) # Draw matched aoi - if self.looked_aoi.all() != None: + if self.looked_aoi.all() is not None: # Draw looked aoi if required - if draw_looked_aoi: + if draw_looked_aoi is not None: self.looked_aoi.draw(image, **draw_looked_aoi) # Draw looked aoi name if required - if looked_aoi_name_color: + if looked_aoi_name_color is not None: top_left_corner_pixel = numpy.rint(self.looked_aoi.bounding_box[0]).astype(int) + looked_aoi_name_offset cv2.putText(image, self.looked_aoi_name, top_left_corner_pixel, cv2.FONT_HERSHEY_SIMPLEX, 1, looked_aoi_name_color, 1, cv2.LINE_AA) diff --git a/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py b/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py index 1160a0d..ab90fe7 100644 --- a/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py +++ b/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py @@ -72,7 +72,7 @@ class Fixation(GazeFeatures.Fixation): return self - def draw(self, image: numpy.array, deviation_circle_color=(255, 255, 255), duration_border_color=(255, 255, 255), duration_factor: float = 1.): + def draw(self, image: numpy.array, deviation_circle_color: tuple = None, duration_border_color: tuple = None, duration_factor: float = 1.): """Draw fixation into image. Parameters: @@ -81,8 +81,15 @@ class Fixation(GazeFeatures.Fixation): duration_factor: how many pixels per duration unit """ - cv2.circle(image, (int(self.focus[0]), int(self.focus[1])), int(self.deviation_max), deviation_circle_color, -1) - cv2.circle(image, (int(self.focus[0]), int(self.focus[1])), int(self.deviation_max), duration_border_color, int(self.duration * duration_factor)) + # Draw deviation circle if required + if deviation_circle_color is not None: + + cv2.circle(image, (int(self.focus[0]), int(self.focus[1])), int(self.deviation_max), deviation_circle_color, -1) + + # Draw duration border if required + if duration_border_color is not None: + + cv2.circle(image, (int(self.focus[0]), int(self.focus[1])), int(self.deviation_max), duration_border_color, int(self.duration * duration_factor)) @dataclass(frozen=True) class Saccade(GazeFeatures.Saccade): diff --git a/src/argaze/GazeFeatures.py b/src/argaze/GazeFeatures.py index 3946e0c..4c377d1 100644 --- a/src/argaze/GazeFeatures.py +++ b/src/argaze/GazeFeatures.py @@ -91,15 +91,16 @@ class GazePosition(): else: return distance < self.precision - def draw(self, image: numpy.array, color=(255, 255, 255), draw_precision=True): + def draw(self, image: numpy.array, color: tuple = None, size: int = None, draw_precision=True): """Draw gaze position point and precision circle.""" if self.valid: int_value = (int(self.value[0]), int(self.value[1])) - # Draw point at position - cv2.circle(image, int_value, 2, color, -1) + # Draw point at position if required + if color is not None: + cv2.circle(image, int_value, size, color, -1) # Draw precision circle if self.precision > 0 and draw_precision: @@ -292,12 +293,12 @@ class GazeMovement(): ts_next, next_gaze_position = gaze_positions.first # Draw position if required - if position_color: + if position_color is not None: start_gaze_position.draw(image, position_color, draw_precision=False) # Draw line between positions if required - if line_color: + if line_color is not None: 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) @@ -682,8 +683,15 @@ class ScanPath(list): for step in self[-deepness:]: - step.first_fixation.draw(image, **draw_fixations) - step.last_saccade.draw(image, **draw_saccades) + # Draw fixation if required + if draw_fixations is not None: + + step.first_fixation.draw(image, **draw_fixations) + + # Draw saccade if required + if draw_saccades is not None: + + step.last_saccade.draw(image, **draw_saccades) class ScanPathAnalyzer(): """Abstract class to define what should provide a scan path analyzer.""" -- cgit v1.1