From 03a2be14d6778dca8552ca28dd89e562756ccc17 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Fri, 22 Mar 2024 10:45:32 +0100 Subject: More work on serialization. --- src/argaze/ArFeatures.py | 38 ++++++++---- src/argaze/ArUcoMarkers/ArUcoCamera.py | 6 +- src/argaze/ArUcoMarkers/ArUcoScene.py | 27 +-------- src/argaze/DataFeatures.py | 97 +++++++++++++++++++++++------- src/argaze/utils/demo_aruco_markers_run.py | 5 ++ src/argaze/utils/demo_gaze_analysis_run.py | 4 ++ 6 files changed, 120 insertions(+), 57 deletions(-) (limited to 'src') diff --git a/src/argaze/ArFeatures.py b/src/argaze/ArFeatures.py index 9892b6a..24ba8e3 100644 --- a/src/argaze/ArFeatures.py +++ b/src/argaze/ArFeatures.py @@ -104,7 +104,7 @@ class ArLayer(DataFeatures.SharedObject, DataFeatures.PipelineStepObject): # Init parent classes DataFeatures.SharedObject.__init__(self) - DataFeatures.PipelineStepObject.__init__(self, **kwargs) + DataFeatures.PipelineStepObject.__init__(self) # Init private attributes self.__aoi_scene = None @@ -164,6 +164,9 @@ class ArLayer(DataFeatures.SharedObject, DataFeatures.PipelineStepObject): self.__aoi_scene = AOI3DScene.AOI3DScene(new_aoi_scene) + # Update expected AOI of AOI scan path + self.__update_expected_aoi() + # Edit parent if self.__aoi_scene is not None: @@ -196,14 +199,8 @@ class ArLayer(DataFeatures.SharedObject, DataFeatures.PipelineStepObject): self.__aoi_scan_path = aoi_scan_path - # Edit aoi_scan_path's expected aoi list by removing aoi with name equals to layer name - expected_aoi = list(self.__aoi_scene.keys()) - - if self.name in expected_aoi: - - expected_aoi.remove(self.name) - - self.__aoi_scan_path.expected_aoi = expected_aoi + # Update expected AOI of AOI scan path + self.__update_expected_aoi() # Edit parent if self.__aoi_scan_path is not None: @@ -270,6 +267,7 @@ class ArLayer(DataFeatures.SharedObject, DataFeatures.PipelineStepObject): return self.__draw_parameters @draw_parameters.setter + @DataFeatures.PipelineStepAttributeSetter def draw_parameters(self, draw_parameters: dict): self.__draw_parameters = draw_parameters @@ -304,6 +302,19 @@ class ArLayer(DataFeatures.SharedObject, DataFeatures.PipelineStepObject): "draw_parameters": self.__draw_parameters } + def __update_expected_aoi(self): + """Edit aoi_scan_path's expected aoi list by removing aoi with name equals to layer name.""" + + if self.__aoi_scene is not None and self.__aoi_scan_path is not None: + + expected_aoi = list(self.__aoi_scene.keys()) + + if self.name in expected_aoi: + + expected_aoi.remove(self.name) + + self.__aoi_scan_path.expected_aoi = expected_aoi + @DataFeatures.PipelineStepMethod def look(self, gaze_movement: GazeFeatures.GazePosition = GazeFeatures.GazePosition()): """ @@ -328,7 +339,7 @@ class ArLayer(DataFeatures.SharedObject, DataFeatures.PipelineStepObject): # Reset aoi scan path analyzed state self.__aoi_scan_path_analyzed = False - if self.__aoi_matcher is not None: + if self.__aoi_matcher is not None and self.__aoi_scene is not None: # Update looked aoi thanks to aoi matcher # Note: don't filter valid/unvalid and finished/unfinished fixation/saccade as we don't know how the aoi matcher works internally @@ -381,7 +392,7 @@ class ArLayer(DataFeatures.SharedObject, DataFeatures.PipelineStepObject): with self._lock: # Draw aoi if required - if draw_aoi_scene is not None: + if draw_aoi_scene is not None and self.__aoi_scene is not None: self.__aoi_scene.draw(image, **draw_aoi_scene) @@ -621,6 +632,7 @@ class ArFrame(DataFeatures.SharedObject, DataFeatures.PipelineStepObject): return self.__layers @layers.setter + @DataFeatures.PipelineStepAttributeSetter def layers(self, layers: dict): self.__layers = {} @@ -640,6 +652,7 @@ class ArFrame(DataFeatures.SharedObject, DataFeatures.PipelineStepObject): return self.__image_parameters @image_parameters.setter + @DataFeatures.PipelineStepAttributeSetter def image_parameters(self, image_parameters: dict): self.__image_parameters = image_parameters @@ -891,6 +904,7 @@ class ArScene(DataFeatures.PipelineStepObject): return self.__layers @layers.setter + @DataFeatures.PipelineStepAttributeSetter def layers(self, layers:dict): self.__layers = {} @@ -911,6 +925,7 @@ class ArScene(DataFeatures.PipelineStepObject): return self.__frames @frames.setter + @DataFeatures.PipelineStepAttributeSetter def frames(self, frames: dict): self.__frames = {} @@ -1104,6 +1119,7 @@ class ArCamera(ArFrame): return self.__scenes @scenes.setter + @DataFeatures.PipelineStepAttributeSetter def scenes(self, scenes: dict): self.__scenes = {} diff --git a/src/argaze/ArUcoMarkers/ArUcoCamera.py b/src/argaze/ArUcoMarkers/ArUcoCamera.py index 9fc6117..5c495dc 100644 --- a/src/argaze/ArUcoMarkers/ArUcoCamera.py +++ b/src/argaze/ArUcoMarkers/ArUcoCamera.py @@ -48,7 +48,7 @@ class ArUcoCamera(ArFeatures.ArCamera): """Initialize ArUcoCamera""" # Init parent class - super().__init__(**kwargs) + super().__init__() # Init private attribute self.__aruco_detector = None @@ -86,6 +86,7 @@ class ArUcoCamera(ArFeatures.ArCamera): self.__aruco_detector.parent = self @ArFeatures.ArCamera.scenes.setter + @DataFeatures.PipelineStepAttributeSetter def scenes(self, scenes: dict): self.__scenes = {} @@ -127,6 +128,9 @@ class ArUcoCamera(ArFeatures.ArCamera): # Clear former layers projection into camera frame for layer_name, layer in self.layers.items(): + + # DEBUG + print('ArUcoCamera.watch', layer_name, layer.aoi_scene) layer.aoi_scene.clear() diff --git a/src/argaze/ArUcoMarkers/ArUcoScene.py b/src/argaze/ArUcoMarkers/ArUcoScene.py index 999fd6f..078c9e2 100644 --- a/src/argaze/ArUcoMarkers/ArUcoScene.py +++ b/src/argaze/ArUcoMarkers/ArUcoScene.py @@ -47,31 +47,10 @@ class ArUcoScene(ArFeatures.ArScene): return self.__aruco_markers_group @aruco_markers_group.setter - def aruco_markers_group(self, aruco_markers_group_value: ArUcoMarkersGroup.ArUcoMarkersGroup): + @DataFeatures.PipelineStepAttributeSetter + def aruco_markers_group(self, aruco_markers_group: ArUcoMarkersGroup.ArUcoMarkersGroup): - if isinstance(aruco_markers_group_value, ArUcoMarkersGroup.ArUcoMarkersGroup): - - new_aruco_markers_group = aruco_markers_group_value - - # str: relative path to file - elif type(aruco_markers_group_value) == str: - - filepath = os.path.join(self.working_directory, aruco_markers_group_value) - file_format = filepath.split('.')[-1] - - # OBJ file format for 3D dimension only - if file_format == 'obj': - - new_aruco_markers_group = ArUcoMarkersGroup.ArUcoMarkersGroup.from_obj(filepath) - - elif file_format == 'json': - - with open(filepath) as file: - - new_aruco_markers_group = ArUcoMarkersGroup.ArUcoMarkersGroup(**json.load(file)) - - # Update value - self.__aruco_markers_group = new_aruco_markers_group + self.__aruco_markers_group = aruco_markers_group # Edit parent if self.__aruco_markers_group is not None: diff --git a/src/argaze/DataFeatures.py b/src/argaze/DataFeatures.py index ff88693..b6105e4 100644 --- a/src/argaze/DataFeatures.py +++ b/src/argaze/DataFeatures.py @@ -19,6 +19,7 @@ __license__ = "GPLv3" from typing import Self import os import sys +import logging import traceback import importlib import collections @@ -464,13 +465,18 @@ def PipelineStepInit(method): def PipelineStepAttributeSetter(method): """Define a decorator use into PipelineStepObject class to declare pipeline step attribute setter.""" - def wrapper(self, new_value): + def wrapper(self, new_value, unwrap: bool = False): """Wrap pipeline step attribute setter to load attribute from file. Parameters: new_value: value used to set attribute. + unwrap: call wrapped method directly. """ + if unwrap: + + return method(self, new_value) + # Get new value type new_value_type = type(new_value) @@ -483,9 +489,13 @@ def PipelineStepAttributeSetter(method): raise(ValueError(f'Missing annotations in {method.__name__}: {method.__annotations__}')) + logging.debug('@PipelineStepAttributeSetter %s.%s.setter(%s) with %s', type(self).__name__, method.__name__, expected_value_type.__name__, new_value_type.__name__) + # Define function to load dict values def load_dict(data: dict) -> any: + logging.debug('\t> load %s from %s', expected_value_type.__name__, new_value_type.__name__) + # Check if json keys are PipelineStepObject class and store them in a list new_objects_list = [] @@ -506,6 +516,8 @@ def PipelineStepAttributeSetter(method): raise(e) + logging.debug('\t+ create %s object from key using value as argument', key) + new_objects_list.append( new_class(**value) ) # Only one object have been loaded: pass the object if it is a subclass of expected type @@ -519,32 +531,57 @@ def PipelineStepAttributeSetter(method): return new_objects_list # Otherwise, data are parameters of the expected class + logging.debug('\t+ create %s object using %s as argument', expected_value_type.__name__, new_value_type.__name__) + return expected_value_type(**data) # String not expected: load value from file if new_value_type == str and new_value_type != expected_value_type: - filepath = os.path.join(self.working_directory, new_value) - file_format = filepath.split('.')[-1] - - # Load image from JPG and PNG formats - if file_format == 'jpg' or file_format == 'png': + split_point = new_value.split('.') + + # String have a dot inside: file path with format + if len(split_point) > 1: + + file_format = split_point[-1] + + logging.debug('\t> %s is a path to a %s file', new_value, file_format.upper()) + + filepath = os.path.join(self.working_directory, new_value) + + # Load image from JPG and PNG formats + if file_format == 'jpg' or file_format == 'png': + + return method(self, cv2.imread(filepath)) + + # Load image from OBJ formats + elif file_format == 'obj': + + return method(self, expected_value_type.from_obj(filepath)) - return method(self, cv2.imread(filepath)) + # Load object from JSON file + elif file_format == 'json': - # Load object from JSON file - elif file_format == 'json': + with open(filepath) as file: - with open(filepath) as file: + return method(self, load_dict(json.load(file))) - return method(self, load_dict(json.load(file))) + # No point inside string: identifier name + else: + + logging.debug('\t> %s is an identifier', new_value) + logging.debug('\t+ create %s object using string as argument', expected_value_type.__name__) + + return method(self, expected_value_type(new_value)) - # Always load value from dict - if new_value_type == dict: + # Dict not expected: load value from dict + if new_value_type == dict and expected_value_type != dict: return method(self, load_dict(new_value)) # Otherwise, pass new value to setter method + logging.debug('\t> use %s value as passed', new_value_type.__name__) + method(self, new_value) return wrapper @@ -558,6 +595,8 @@ class PipelineStepObject(): def __init__(self, **kwargs): """Initialize PipelineStepObject.""" + logging.debug('PipelineStepObject.__init__ %s %s', type(self).__name__, kwargs['name'] if 'name' in kwargs else '') + # Init private attribute self.__name = None self.__working_directory = None @@ -600,6 +639,8 @@ class PipelineStepObject(): for key, value in object_data.items(): + logging.debug('PipelineStepObject.update_attributes %s.%s with %s value', type(self).__name__, key, type(value).__name__) + setattr(self, key, value) @property @@ -677,6 +718,8 @@ class PipelineStepObject(): patch_filepath: path to json patch file to modify any configuration entries """ + logging.debug('%s.from_json', cls.__name__) + # Load configuration from JSON file with open(configuration_filepath) as configuration_file: @@ -747,10 +790,10 @@ class PipelineStepObject(): output = f'{Fore.GREEN}{Style.BRIGHT}{self.__class__.__module__}.{self.__class__.__name__}{Style.RESET_ALL}\n' if self.__name is not None: - output += f'{tabs}\t{Style.BRIGHT}name{Style.RESET_ALL}: {self.__name}\n' + output += f'{tabs}\t{Style.BRIGHT}name: {Fore.MAGENTA}{self.__name}{Style.RESET_ALL}\n' if self.__parent is not None: - output += f'{tabs}\t{Style.BRIGHT}parent{Style.RESET_ALL}: {self.__parent.name}\n' + output += f'{tabs}\t{Style.BRIGHT}parent{Style.RESET_ALL}: {Fore.MAGENTA}{self.__parent.name}{Style.RESET_ALL}\n' if len(self.__observers): output += f'{tabs}\t{Style.BRIGHT}observers{Style.RESET_ALL}:\n' @@ -767,7 +810,15 @@ class PipelineStepObject(): for k, v in value.items(): - output += f'{tabs}\t - {Fore.RED}{k}{Style.RESET_ALL}: {v}\n' + output += f'{tabs}\t - {Fore.MAGENTA}{k}{Style.RESET_ALL}: {v}\n' + + if type(value) == list: + + output += '\n' + + for v in value: + + output += f'{tabs}\t - {v}\n' elif type(value) == numpy.ndarray: @@ -779,7 +830,13 @@ class PipelineStepObject(): else: - output += f'{value}' + try: + + output += f'{value}' + + except TypeError as e: + + output += f'{Fore.RED}{Style.BRIGHT}!!! {type(self).__name__}.{name}: {e}{Style.RESET_ALL}\n\n' if output[-1] != '\n': @@ -933,15 +990,13 @@ class PipelineInputProvider(PipelineStepObject): @PipelineStepInit def __init__(self, **kwargs): - # DEBUG - print('PipelineInputProvider.__init__') + logging.debug('PipelineInputProvider.__init__') super().__init__() def attach(self, method): - # DEBUG - print('PipelineInputProvider.attach', method) + logging.debug('PipelineInputProvider.attach', method) def __enter__(self): """ diff --git a/src/argaze/utils/demo_aruco_markers_run.py b/src/argaze/utils/demo_aruco_markers_run.py index f5bc756..cdd9184 100644 --- a/src/argaze/utils/demo_aruco_markers_run.py +++ b/src/argaze/utils/demo_aruco_markers_run.py @@ -19,6 +19,7 @@ __copyright__ = "Copyright 2023, Ecole Nationale de l'Aviation Civile (ENAC)" __license__ = "GPLv3" import argparse +import logging import contextlib import os import time @@ -36,8 +37,12 @@ parser = argparse.ArgumentParser(description=__doc__.split('-')[0]) parser.add_argument('configuration', metavar='CONFIGURATION', type=str, help='configuration filepath') parser.add_argument('-s', '--source', metavar='SOURCE', type=str, default='0', help='video capture source (a number to select camera device or a filepath to load a movie)') parser.add_argument('-v', '--verbose', action='store_true', default=False, help='enable verbose mode to print information in console') + args = parser.parse_args() +# Manage logging +logging.basicConfig(format = '%(levelname)s: %(message)s', level = logging.DEBUG if args.verbose else logging.INFO) + def main(): # Load ArUcoCamera diff --git a/src/argaze/utils/demo_gaze_analysis_run.py b/src/argaze/utils/demo_gaze_analysis_run.py index b5d9a20..16644ce 100644 --- a/src/argaze/utils/demo_gaze_analysis_run.py +++ b/src/argaze/utils/demo_gaze_analysis_run.py @@ -19,6 +19,7 @@ __copyright__ = "Copyright 2023, Ecole Nationale de l'Aviation Civile (ENAC)" __license__ = "GPLv3" import argparse +import logging import contextlib import os import time @@ -37,6 +38,9 @@ parser.add_argument('configuration', metavar='CONFIGURATION', type=str, help='co parser.add_argument('-v', '--verbose', action='store_true', default=False, help='enable verbose mode to print information in console') args = parser.parse_args() +# Manage logging +logging.basicConfig(format = '%(levelname)s: %(message)s', level = logging.DEBUG if args.verbose else logging.INFO) + def main(): # Load ArFrame -- cgit v1.1