From 679d6f3358679d9eb3f63110ebabbe18ed5d05cc Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Thu, 25 Jan 2024 22:30:47 +0100 Subject: Fixing logging features. --- src/argaze/ArFeatures.py | 94 ++++++++++------------ src/argaze/ArUcoMarkers/ArUcoCamera.py | 6 +- src/argaze/ArUcoMarkers/ArUcoDetector.py | 10 +++ src/argaze/ArUcoMarkers/ArUcoScene.py | 5 +- src/argaze/AreaOfInterest/AOIFeatures.py | 2 + src/argaze/DataFeatures.py | 82 ++++++++++++++----- src/argaze/GazeAnalysis/DeviationCircleCoverage.py | 10 ++- .../DispersionThresholdIdentification.py | 2 + src/argaze/GazeAnalysis/FocusPointInside.py | 8 +- .../VelocityThresholdIdentification.py | 2 + src/argaze/GazeFeatures.py | 12 +++ .../utils/demo_data/demo_aruco_markers_setup.json | 2 +- .../utils/demo_data/demo_gaze_analysis_setup.json | 4 +- src/argaze/utils/demo_data/frame_logger.py | 4 +- src/argaze/utils/demo_data/main_layer_logger.py | 4 +- src/argaze/utils/demo_gaze_analysis_run.py | 8 +- 16 files changed, 168 insertions(+), 87 deletions(-) diff --git a/src/argaze/ArFeatures.py b/src/argaze/ArFeatures.py index 93a21ed..509712e 100644 --- a/src/argaze/ArFeatures.py +++ b/src/argaze/ArFeatures.py @@ -101,7 +101,7 @@ class ArLayer(DataFeatures.SharedObject, DataFeatures.PipelineStepObject): Inherits from DataFeatures.SharedObject class to be shared by multiple threads. """ - def __init__(self, name: str = None, aoi_scene: AOIFeatures.AOIScene = None, aoi_matcher: GazeFeatures.AOIMatcher = None, aoi_scan_path: GazeFeatures.AOIScanPath = None, aoi_scan_path_analyzers: dict = None, draw_parameters: dict = None): + def __init__(self, name: str = None, aoi_scene: AOIFeatures.AOIScene = None, aoi_matcher: GazeFeatures.AOIMatcher = None, aoi_scan_path: GazeFeatures.AOIScanPath = None, aoi_scan_path_analyzers: dict = None, draw_parameters: dict = None, **kwargs): """ Initialize ArLayer Parameters: @@ -114,7 +114,8 @@ class ArLayer(DataFeatures.SharedObject, DataFeatures.PipelineStepObject): """ # Init parent classes - super().__init__() + DataFeatures.SharedObject.__init__(self) + DataFeatures.PipelineStepObject.__init__(self, **kwargs) # Init private attributes self.__name = name @@ -127,16 +128,6 @@ class ArLayer(DataFeatures.SharedObject, DataFeatures.PipelineStepObject): self.__gaze_movement = GazeFeatures.UnvalidGazeMovement() self.__looked_aoi_name = None self.__aoi_scan_path_analyzed = False - - ''' - # Register loggers from logging module as pipeline step observers - if self.logging_module is not None: - - self.__observers = importlib.import_module(self.logging_module).__loggers__ - - # DEBUG - print(f'Observers registered for {self.__name} layer:', self.__observers) - ''' # Cast aoi scene to its effective dimension if self.__aoi_scene.dimension == 2: @@ -386,23 +377,23 @@ class ArLayer(DataFeatures.SharedObject, DataFeatures.PipelineStepObject): except KeyError: new_layer_draw_parameters = DEFAULT_ARLAYER_DRAW_PARAMETERS - ''' - # Load logging module - try: - new_logging_module_value = layer_data.pop('logging_module') + # Load temporary pipeline step object from layer_data then export it as dict + temp_pipeline_step_object_data = DataFeatures.PipelineStepObject.from_dict(layer_data, working_directory).as_dict() - # str: relative path to file - if type(new_logging_module_value) == str: - - new_logging_module = new_logging_module_value.split('.')[0] - - except KeyError: + # DEBUG + print('ArLayer.from_dict: temp_pipeline_step_object_data=', temp_pipeline_step_object_data) - new_logging_module = None - ''' # Create layer - return ArLayer(new_layer_name, new_aoi_scene, new_aoi_matcher, new_aoi_scan_path, new_aoi_scan_path_analyzers, new_layer_draw_parameters) + return ArLayer( \ + new_layer_name, \ + new_aoi_scene, \ + new_aoi_matcher, \ + new_aoi_scan_path, \ + new_aoi_scan_path_analyzers, \ + new_layer_draw_parameters, \ + **temp_pipeline_step_object_data \ + ) @classmethod def from_json(self, json_filepath: str) -> ArLayerType: @@ -535,7 +526,7 @@ class ArFrame(DataFeatures.SharedObject, DataFeatures.PipelineStepObject): Inherits from DataFeatures.SharedObject class to be shared by multiple threads """ - def __init__(self, name: str = None, size: tuple[int] = (1, 1), gaze_position_calibrator: GazeFeatures.GazePositionCalibrator = None, gaze_movement_identifier: GazeFeatures.GazeMovementIdentifier = None, filter_in_progress_identification: bool = True, scan_path: GazeFeatures.ScanPath = None, scan_path_analyzers: dict = None, background: numpy.array = numpy.array([]), heatmap: AOIFeatures.Heatmap = None, layers: dict = None, image_parameters: dict = DEFAULT_ARFRAME_IMAGE_PARAMETERS): + def __init__(self, name: str = None, size: tuple[int] = (1, 1), gaze_position_calibrator: GazeFeatures.GazePositionCalibrator = None, gaze_movement_identifier: GazeFeatures.GazeMovementIdentifier = None, filter_in_progress_identification: bool = True, scan_path: GazeFeatures.ScanPath = None, scan_path_analyzers: dict = None, background: numpy.array = numpy.array([]), heatmap: AOIFeatures.Heatmap = None, layers: dict = None, image_parameters: dict = DEFAULT_ARFRAME_IMAGE_PARAMETERS, **kwargs): """ Initialize ArFrame Parameters: @@ -553,10 +544,11 @@ class ArFrame(DataFeatures.SharedObject, DataFeatures.PipelineStepObject): """ # DEBUG - print(f'ArFrame.__init__ {name} {layers}') + print('ArFrame.__init__', name, layers, kwargs) # Init parent classes - super().__init__() + DataFeatures.SharedObject.__init__(self) + DataFeatures.PipelineStepObject.__init__(self, **kwargs) # Init private attributes self.__name = name @@ -579,15 +571,6 @@ class ArFrame(DataFeatures.SharedObject, DataFeatures.PipelineStepObject): for name, layer in self.__layers.items(): layer.parent = self - ''' - # Import logging module __loggers__ variable as pipeline step observers - if self.logging_module is not None: - - self.__observers = importlib.import_module(self.logging_module).__loggers__ - - # DEBUG - print(f'Observers registered for {self.__name} frame:', self.__observers) - ''' @property def name(self) -> str: @@ -688,8 +671,11 @@ class ArFrame(DataFeatures.SharedObject, DataFeatures.PipelineStepObject): yield aoi_scan_path_analyzer_module_path, aoi_scan_path_analyzer.analysis def as_dict(self) -> dict: - """Export ArFrame attributes as dictionary.""" + """Export ArFrame attributes as dictionary. + Returns: + frame_data: dictionary with frame attributes values. + """ return { "name": self.__name, "size": self.__size, @@ -905,25 +891,27 @@ class ArFrame(DataFeatures.SharedObject, DataFeatures.PipelineStepObject): new_frame_image_parameters = DEFAULT_ARFRAME_IMAGE_PARAMETERS - # Load logging module - try: - - new_logging_module_value = frame_data.pop('logging_module') - - # str: relative path to file - if type(new_logging_module_value) == str: - - new_logging_module = new_logging_module_value.split('.')[0] - - except KeyError: - - new_logging_module = None + # Load temporary pipeline step object from frame_data then export it as dict + temp_pipeline_step_object_data = DataFeatures.PipelineStepObject.from_dict(frame_data, working_directory).as_dict() # DEBUG - print('Create frame', new_frame_name) + print('ArFrame.from_dict: temp_pipeline_step_object_data=', temp_pipeline_step_object_data) # Create frame - return ArFrame(new_frame_name, new_frame_size, new_gaze_position_calibrator, new_gaze_movement_identifier, filter_in_progress_identification, new_scan_path, new_scan_path_analyzers, new_frame_background, new_heatmap, new_layers, new_frame_image_parameters) + return ArFrame( \ + new_frame_name, \ + new_frame_size, \ + new_gaze_position_calibrator, \ + new_gaze_movement_identifier, \ + filter_in_progress_identification, \ + new_scan_path, \ + new_scan_path_analyzers, \ + new_frame_background, \ + new_heatmap, \ + new_layers, \ + new_frame_image_parameters, \ + **temp_pipeline_step_object_data \ + ) @classmethod def from_json(self, json_filepath: str) -> ArFrameType: diff --git a/src/argaze/ArUcoMarkers/ArUcoCamera.py b/src/argaze/ArUcoMarkers/ArUcoCamera.py index ca58c20..ae97c96 100644 --- a/src/argaze/ArUcoMarkers/ArUcoCamera.py +++ b/src/argaze/ArUcoMarkers/ArUcoCamera.py @@ -126,7 +126,11 @@ class ArUcoCamera(ArFeatures.ArCamera): print('ArUcoCamera.from_dict: temp_camera_data=', temp_camera_data) # Create new aruco camera using temporary ar frame values - return ArUcoCamera(aruco_detector = new_aruco_detector, scenes = new_scenes, **temp_camera_data) + return ArUcoCamera( \ + aruco_detector = new_aruco_detector, \ + scenes = new_scenes, \ + **temp_camera_data \ + ) @classmethod def from_json(self, json_filepath: str) -> ArUcoCameraType: diff --git a/src/argaze/ArUcoMarkers/ArUcoDetector.py b/src/argaze/ArUcoMarkers/ArUcoDetector.py index 63f4851..9585b5e 100644 --- a/src/argaze/ArUcoMarkers/ArUcoDetector.py +++ b/src/argaze/ArUcoMarkers/ArUcoDetector.py @@ -186,12 +186,22 @@ class ArUcoDetector(DataFeatures.PipelineStepObject): def optic_parameters(self) -> ArUcoOpticCalibrator.OpticParameters: """Get aruco detector's opetic parameters object.""" return self.__optic_parameters + + @optic_parameters.setter + def optic_parameters(self, value: ArUcoOpticCalibrator.OpticParameters): + """Set aruco detector's opetic parameters object.""" + self.__optic_parameters = value @property def parameters(self) -> DetectorParameters: """Get aruco detector's parameters object.""" return self.__parameters + @parameters.setter + def parameters(self, value: DetectorParameters): + """Set aruco detector's parameters object.""" + self.__parameters = value + @classmethod def from_dict(self, aruco_detector_data: dict, working_directory: str = None) -> ArUcoDetectorType: """Load ArUcoDetector attributes from dictionary. diff --git a/src/argaze/ArUcoMarkers/ArUcoScene.py b/src/argaze/ArUcoMarkers/ArUcoScene.py index 34c3157..c09cbbc 100644 --- a/src/argaze/ArUcoMarkers/ArUcoScene.py +++ b/src/argaze/ArUcoMarkers/ArUcoScene.py @@ -95,7 +95,10 @@ class ArUcoScene(ArFeatures.ArScene): print('ArUcoScene.from_dict: temp_scene_data=', temp_scene_data) # Create new aruco scene using temporary ar scene values - return ArUcoScene(aruco_markers_group=new_aruco_markers_group, **temp_scene_data) + return ArUcoScene( + aruco_markers_group = new_aruco_markers_group, \ + **temp_scene_data \ + ) def __str__(self) -> str: """ diff --git a/src/argaze/AreaOfInterest/AOIFeatures.py b/src/argaze/AreaOfInterest/AOIFeatures.py index 002b251..e452f05 100644 --- a/src/argaze/AreaOfInterest/AOIFeatures.py +++ b/src/argaze/AreaOfInterest/AOIFeatures.py @@ -568,6 +568,8 @@ class Heatmap(DataFeatures.PipelineStepObject): def __post_init__(self): + super().__init__() + self.__rX, self.__rY = self.size # Init coordinates diff --git a/src/argaze/DataFeatures.py b/src/argaze/DataFeatures.py index 9b673cc..fe03b4c 100644 --- a/src/argaze/DataFeatures.py +++ b/src/argaze/DataFeatures.py @@ -8,6 +8,7 @@ __copyright__ = "Copyright 2023, Ecole Nationale de l'Aviation Civile (ENAC)" __license__ = "BSD" from typing import TypeVar, Tuple, Any +import os import importlib from inspect import getmembers, getmodule import collections @@ -345,6 +346,10 @@ class SharedObject(): """Abstract class to enable multiple threads sharing and timestamp management.""" def __init__(self): + + # DEBUG + print('SharedObject.__init__') + self._lock = threading.Lock() self._timestamp = math.nan self._execution_times = {} @@ -387,40 +392,81 @@ class SharedObject(): return timestamped class PipelineStepObject(): - """Abstract class to assess pipeline step methods execution time. - - Parameters: - execution_times: dictionary with each PipelineStepMethod execution time in ms. - observers: dictionary ... """ + Define class to assess pipeline step methods execution time and observe them. + """ + + def __init__(self, observers: dict = None): + """Initialize PipelineStepObject + + Parameters: + observers: dictionary with observers objects. + """ + + # DEBUG + print('PipelineStepObject.__init__', observers) - execution_times: dict = {} - __observers: dict = {} + # Init private attribute + self.__observers = observers if observers is not None else {} + self.__execution_times = {} @property def observers(self) -> dict: + """Get pipeline step object observers dictionary.""" + return self.__observers - return self.__observers - + @property + def execution_times(self): + """Get pipeline step object observers execution times dictionary.""" + return self.__execution_times + def as_dict(self) -> dict: - """ - Define abstract method to export PipelineStepObject attributes as dictionary. + """Export PipelineStepObject attributes as dictionary. Returns: - object_data: dictionary of PipelineStepObject. + object_data: dictionary with pipeline step object attributes values. """ - raise NotImplementedError('serialize() method not implemented') + return { + "observers": self.__observers, + } @classmethod def from_dict(self, object_data: dict, working_directory: str = None) -> object: - """ - Define abstract method to import PipelineStepObject attributes from dictionary. + """Load PipelineStepObject attributes from dictionary. Returns: - object_data: dictionary of PipelineStepObject + object_data: dictionary with pipeline step object attributes values. working_directory: folder path where to load files when a dictionary value is a relative filepath. """ - raise NotImplementedError('serialize() method not implemented') + + # Load observers + new_observers = {} + + try: + + new_observers_value = object_data.pop('observers') + + # str: relative path to file + if type(new_observers_value) == str: + + filepath = os.path.join(working_directory, new_observers_value) + file_format = filepath.split('.')[-1] + + # Python file format + if file_format == 'py': + + observer_module_path = new_observers_value.split('.')[0] + + observer_module = importlib.import_module(observer_module_path) + + new_observers = observer_module.__observers__ + + except KeyError: + + pass + + # Create pipeline step + return PipelineStepObject(new_observers) @classmethod def from_json(self, json_filepath: str) -> object: @@ -463,7 +509,7 @@ def PipelineStepMethod(method): # DEBUG if type(self) == ArFeatures.ArFrame: - + print(timestamp, self.name, method.__name__, len(self.observers)) # Initialize execution time assessment diff --git a/src/argaze/GazeAnalysis/DeviationCircleCoverage.py b/src/argaze/GazeAnalysis/DeviationCircleCoverage.py index e80b198..62b5e9a 100644 --- a/src/argaze/GazeAnalysis/DeviationCircleCoverage.py +++ b/src/argaze/GazeAnalysis/DeviationCircleCoverage.py @@ -30,6 +30,12 @@ class AOIMatcher(GazeFeatures.AOIMatcher): def __post_init__(self): + super().__init__() + + self.__reset() + + def __reset(self): + self.__look_count = 0 self.__looked_aoi_data = (None, None) self.__looked_probabilities = {} @@ -99,11 +105,11 @@ class AOIMatcher(GazeFeatures.AOIMatcher): elif GazeFeatures.is_saccade(gaze_movement): - self.__post_init__() + self.__reset() elif not gaze_movement.valid: - self.__post_init__() + self.__reset() return (None, None) diff --git a/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py b/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py index 39895fd..6f8c554 100644 --- a/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py +++ b/src/argaze/GazeAnalysis/DispersionThresholdIdentification.py @@ -138,6 +138,8 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier): def __post_init__(self): + super().__init__() + self.__valid_positions = GazeFeatures.TimeStampedGazePositions() self.__fixation_positions = GazeFeatures.TimeStampedGazePositions() self.__saccade_positions = GazeFeatures.TimeStampedGazePositions() diff --git a/src/argaze/GazeAnalysis/FocusPointInside.py b/src/argaze/GazeAnalysis/FocusPointInside.py index bf97d0d..19b8c27 100644 --- a/src/argaze/GazeAnalysis/FocusPointInside.py +++ b/src/argaze/GazeAnalysis/FocusPointInside.py @@ -27,6 +27,12 @@ class AOIMatcher(GazeFeatures.AOIMatcher): def __post_init__(self): + super().__init__() + + self.__reset() + + def __reset(self): + self.__looked_aoi_data = (None, None) self.__matched_gaze_movement = None @@ -50,7 +56,7 @@ class AOIMatcher(GazeFeatures.AOIMatcher): elif GazeFeatures.is_saccade(gaze_movement): - self.__post_init__() + self.__reset() return (None, None) diff --git a/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py b/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py index 62f8bdd..d246db4 100644 --- a/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py +++ b/src/argaze/GazeAnalysis/VelocityThresholdIdentification.py @@ -136,6 +136,8 @@ class GazeMovementIdentifier(GazeFeatures.GazeMovementIdentifier): def __post_init__(self): + super().__init__() + self.__last_ts = -1 self.__last_position = None diff --git a/src/argaze/GazeFeatures.py b/src/argaze/GazeFeatures.py index db4c1f4..d69a6cc 100644 --- a/src/argaze/GazeFeatures.py +++ b/src/argaze/GazeFeatures.py @@ -523,6 +523,10 @@ class TimeStampedGazeStatus(DataFeatures.TimeStampedBuffer): class GazeMovementIdentifier(DataFeatures.PipelineStepObject): """Abstract class to define what should provide a gaze movement identifier.""" + def __init__(self): + + super().__init__() + @DataFeatures.PipelineStepMethod def identify(self, timestamp: int|float, gaze_position: GazePosition, terminate:bool=False) -> Tuple[GazeMovementType, GazeMovementType]: """Identify gaze movement from successive timestamped gaze positions. @@ -821,6 +825,8 @@ class ScanPathAnalyzer(DataFeatures.PipelineStepObject): def __init__(self): + super().__init__() + self.__properties = [name for (name, value) in getmembers(type(self), lambda v: isinstance(v, property))] @property @@ -848,6 +854,10 @@ class AOIMatcher(DataFeatures.PipelineStepObject): exclude: list[str] = field(default_factory = list) + def __init__(self): + + super().__init__() + def match(self, aoi_scene: AOIFeatures.AOIScene, gaze_movement: GazeMovement) -> Tuple[str, AOIFeatures.AreaOfInterest]: """Which AOI is looked in the scene?""" @@ -1159,6 +1169,8 @@ class AOIScanPathAnalyzer(DataFeatures.PipelineStepObject): def __init__(self): + super().__init__() + self.__properties = [name for (name, value) in getmembers(type(self), lambda v: isinstance(v, property))] @property 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 36ce87b..2b54955 100644 --- a/src/argaze/utils/demo_data/demo_aruco_markers_setup.json +++ b/src/argaze/utils/demo_data/demo_aruco_markers_setup.json @@ -113,7 +113,7 @@ "size": 2 } }, - "logging_module": "frame_logger.py" + "observers": "frame_logger.py" } }, "angle_tolerance": 15.0, 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 5f3e3c0..3fffc9f 100644 --- a/src/argaze/utils/demo_data/demo_gaze_analysis_setup.json +++ b/src/argaze/utils/demo_data/demo_gaze_analysis_setup.json @@ -47,7 +47,7 @@ }, "Entropy":{} }, - "logging_module": "main_layer_logger.py" + "observers": "main_layer_logger.py" } }, "image_parameters": { @@ -109,5 +109,5 @@ "size": 2 } }, - "logging_module": "frame_logger.py" + "observers": "frame_logger.py" } \ No newline at end of file diff --git a/src/argaze/utils/demo_data/frame_logger.py b/src/argaze/utils/demo_data/frame_logger.py index f90e63f..18fc151 100644 --- a/src/argaze/utils/demo_data/frame_logger.py +++ b/src/argaze/utils/demo_data/frame_logger.py @@ -49,8 +49,8 @@ class ScanPathAnalysisLogger(DataFeatures.PipelineStepObserver, UtilsFeatures.Fi self.write(log) -# Export loggers instances to register them as pipeline step object observer -__loggers__ = { +# Export loggers instances to register them as pipeline step object observers +__observers__ = { "Fixation logger": FixationLogger(path="_export/logs/fixations.csv", header="Timestamp (ms), Focus (px), Duration (ms), AOI"), "Scan path analysis logger": ScanPathAnalysisLogger(path="_export/logs/scan_path_metrics.csv", header="Timestamp (ms), Duration (ms), Step, K, NNI, XXR") } \ No newline at end of file diff --git a/src/argaze/utils/demo_data/main_layer_logger.py b/src/argaze/utils/demo_data/main_layer_logger.py index e85dfcd..eba7c74 100644 --- a/src/argaze/utils/demo_data/main_layer_logger.py +++ b/src/argaze/utils/demo_data/main_layer_logger.py @@ -32,7 +32,7 @@ class AOIScanPathAnalysisLogger(DataFeatures.PipelineStepObserver, UtilsFeatures self.write(log) -# Export loggers instances to register them as pipeline step object observer -__loggers__ = { +# Export loggers instances to register them as pipeline step object observers +__observers__ = { "AOI Scan path analysis logger": AOIScanPathAnalysisLogger(path="_export/logs/aoi_scan_path_metrics.csv", header="Timestamp (ms), Duration (ms), Step, K, LZC") } \ No newline at end of file diff --git a/src/argaze/utils/demo_gaze_analysis_run.py b/src/argaze/utils/demo_gaze_analysis_run.py index 5b20864..acc05c4 100644 --- a/src/argaze/utils/demo_gaze_analysis_run.py +++ b/src/argaze/utils/demo_gaze_analysis_run.py @@ -45,15 +45,15 @@ def main(): # Edit millisecond timestamp timestamp = int((time.time() - start_time) * 1e3) - try: + #try: # Project gaze position into frame - ar_frame.look(timestamp, GazeFeatures.GazePosition((x, y))) + ar_frame.look(timestamp, GazeFeatures.GazePosition((x, y))) # Catch pipeline exception - except Exception as e: + #except Exception as e: - print('Gaze projection error:', e) + #print('Gaze projection error:', e) # Attach mouse callback to window cv2.setMouseCallback(ar_frame.name, on_mouse_event) -- cgit v1.1