From 4c274c2e63e67a028cbfc6b7c74b7b32760a94b5 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Wed, 30 Aug 2023 13:51:02 +0200 Subject: Improving drawing features to allow fine tuning thru JSON configuration file. --- src/argaze/AreaOfInterest/AOI2DScene.py | 49 ++++++++-------------------- src/argaze/GazeFeatures.py | 57 +++++++++++++++++++++------------ 2 files changed, 49 insertions(+), 57 deletions(-) (limited to 'src') diff --git a/src/argaze/AreaOfInterest/AOI2DScene.py b/src/argaze/AreaOfInterest/AOI2DScene.py index b2dba39..73c977f 100644 --- a/src/argaze/AreaOfInterest/AOI2DScene.py +++ b/src/argaze/AreaOfInterest/AOI2DScene.py @@ -26,15 +26,20 @@ class AOI2DScene(AOIFeatures.AOIScene): super().__init__(2, aois_2d) - def draw(self, image: numpy.array, exclude=[], color=(0, 255, 255)): - """Draw AOI polygons on image.""" + def draw(self, image: numpy.array, draw_aoi: dict = None, exclude=[]): + """Draw AOI polygons on image. + + Parameters: + draw_aoi: AOIFeatures.AOI.draw parameters (if None, no aoi is drawn) + """ for name, aoi in self.items(): if name in exclude: continue - aoi.draw(image, color) + if draw_aoi: + aoi.draw(image, **draw_aoi) def raycast(self, pointer:tuple) -> Tuple[str, "AOIFeatures.AreaOfInterest", bool]: """Iterate over aoi to know which aoi is matching the given pointer position. @@ -69,48 +74,20 @@ class AOI2DScene(AOIFeatures.AOIScene): aoi.draw(image, color) def circlecast(self, center:tuple, radius:float) -> Tuple[str, "AOIFeatures.AreaOfInterest", numpy.array, float, float]: - """Iterate over areas to know which aoi is matching circle. + """Iterate over areas to know which aoi is matched circle. Returns: aoi name aoi object matching region points - ratio of matching region area relatively to aoi area - ratio of matching region area relatively to circle area + ratio of matched region area relatively to aoi area + ratio of matched region area relatively to circle area """ for name, aoi in self.items(): - matching_region, aoi_ratio, circle_ratio = aoi.circle_intersection(center, radius) - - yield name, aoi, matching_region, aoi_ratio, circle_ratio - - def draw_circlecast(self, image: numpy.array, center:tuple, radius:float, matching_aoi = [], 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): - - if name in exclude: - continue - - color = base_color - - # Draw matching region - if aoi_ratio > 0: - matching_region.draw(image, color, 4) - - # Is aoi part of matching aoi? - if name in matching_aoi: - - color = matching_color - - top_left_corner_pixel = numpy.rint(aoi.clockwise()[0]).astype(int) - cv2.putText(image, name, top_left_corner_pixel, cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 1, cv2.LINE_AA) + matched_region, aoi_ratio, circle_ratio = aoi.circle_intersection(center, radius) - # Draw matching region - matching_region.draw(image, matching_color, 4) - - # Draw form - aoi.draw(image, color) + yield name, aoi, matched_region, aoi_ratio, circle_ratio def reframe(self, aoi: AOIFeatures.AreaOfInterest, size: tuple) -> AOI2DSceneType: """ diff --git a/src/argaze/GazeFeatures.py b/src/argaze/GazeFeatures.py index 2875c8e..3946e0c 100644 --- a/src/argaze/GazeFeatures.py +++ b/src/argaze/GazeFeatures.py @@ -91,7 +91,7 @@ class GazePosition(): else: return distance < self.precision - def draw(self, image: numpy.array, color=(0, 255, 255), draw_precision=True): + def draw(self, image: numpy.array, color=(255, 255, 255), draw_precision=True): """Draw gaze position point and precision circle.""" if self.valid: @@ -276,8 +276,13 @@ class GazeMovement(): return self - def draw_positions(self, image: numpy.array, color=(0, 55, 55)): - """Draw gaze movement positions""" + def draw_positions(self, image: numpy.array, position_color: tuple = None, line_color: tuple = None): + """Draw gaze movement positions with line between each position. + + Parameters: + position_color: color of position point + line_color: color of line between each position + """ gaze_positions = self.positions.copy() @@ -286,13 +291,17 @@ class GazeMovement(): ts_start, start_gaze_position = gaze_positions.pop_first() ts_next, next_gaze_position = gaze_positions.first - # Draw start gaze - start_gaze_position.draw(image, draw_precision=False) + # Draw position if required + if position_color: - # Draw movement from start to next - cv2.line(image, (int(start_gaze_position[0]), int(start_gaze_position[1])), (int(next_gaze_position[0]), int(next_gaze_position[1])), color, 1) + start_gaze_position.draw(image, position_color, draw_precision=False) - def draw(self, image: numpy.array, color): + # Draw line between positions if required + if line_color: + + 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): """Draw gaze movement into image.""" raise NotImplementedError('draw() method not implemented') @@ -662,19 +671,19 @@ class ScanPath(list): self.__last_fixation = fixation - def draw(self, image: numpy.array, fixation_color=(255, 255, 255), saccade_color=(255, 255, 255), deepness=0): - """Draw scan path into image.""" - - last_step = None - for step in self[-deepness:]: - - if last_step != None: + def draw(self, image: numpy.array, draw_fixations: dict = None, draw_saccades: dict = None, deepness=0): + """Draw scan path into image. - cv2.line(image, (int(last_step.first_fixation.focus[0]), int(last_step.first_fixation.focus[1])), (int(step.first_fixation.focus[0]), int(step.first_fixation.focus[1])), saccade_color, 2) + Parameters: + draw_fixations: Fixation.draw parameters (which depends of the loaded gaze movement identifier module, if None, no fixation is drawn) + draw_saccades: Saccade.draw parameters (which depends of the loaded gaze movement identifier module, if None, no saccade is drawn) + deepness: number of steps back to draw + """ - last_step.first_fixation.draw(image, fixation_color) + for step in self[-deepness:]: - last_step = step + step.first_fixation.draw(image, **draw_fixations) + step.last_saccade.draw(image, **draw_saccades) class ScanPathAnalyzer(): """Abstract class to define what should provide a scan path analyzer.""" @@ -704,17 +713,23 @@ class ScanPathAnalyzer(): class AOIMatcher(): """Abstract class to define what should provide an AOI matcher algorithm.""" - def match(self, aoi_scene: AOIFeatures.AOIScene, gaze_movement: GazeMovement, exclude=[]) -> str: + def match(self, aoi_scene: AOIFeatures.AOIScene, gaze_movement: GazeMovement, exclude=[]) -> Tuple[str, AOIFeatures.AreaOfInterest]: """Which AOI is looked in the scene?""" raise NotImplementedError('match() method not implemented') @property - def looked_aoi(self) -> str: - """Get most likely looked aoi name.""" + def looked_aoi(self) -> AOIFeatures.AreaOfInterest: + """Get most likely looked aoi.""" raise NotImplementedError('looked_aoi getter not implemented') + @property + def looked_aoi_name(self) -> str: + """Get most likely looked aoi name.""" + + raise NotImplementedError('looked_aoi_name getter not implemented') + AOIScanStepType = TypeVar('AOIScanStep', bound="AOIScanStep") # Type definition for type annotation convenience -- cgit v1.1