diff options
Diffstat (limited to 'src/argaze/utils/contexts/OpenCV.py')
-rw-r--r-- | src/argaze/utils/contexts/OpenCV.py | 172 |
1 files changed, 169 insertions, 3 deletions
diff --git a/src/argaze/utils/contexts/OpenCV.py b/src/argaze/utils/contexts/OpenCV.py index 20b01bc..c2361a8 100644 --- a/src/argaze/utils/contexts/OpenCV.py +++ b/src/argaze/utils/contexts/OpenCV.py @@ -18,6 +18,7 @@ __copyright__ = "Copyright 2023, Ecole Nationale de l'Aviation Civile (ENAC)" __license__ = "GPLv3" import logging +import threading import time import cv2 @@ -30,13 +31,13 @@ class Window(ArFeatures.LiveProcessingContext): @DataFeatures.PipelineStepInit def __init__(self, **kwargs): - # Init ArContext class + # Init LiveProcessingContext class super().__init__() @DataFeatures.PipelineStepEnter def __enter__(self): - logging.info('OpenCV context starts...') + logging.info('OpenCV window context starts...') # Create a window to display context cv2.namedWindow(self.name, cv2.WINDOW_AUTOSIZE) @@ -52,7 +53,7 @@ class Window(ArFeatures.LiveProcessingContext): @DataFeatures.PipelineStepExit def __exit__(self, exception_type, exception_value, exception_traceback): - logging.info('OpenCV context stops...') + logging.info('OpenCV window context stops...') # Delete window cv2.destroyAllWindows() @@ -66,3 +67,168 @@ class Window(ArFeatures.LiveProcessingContext): # Process timestamped gaze position self._process_gaze_position(timestamp = int((time.time() - self.__start_time) * 1e3), x = x, y = y) + + +class Movie(ArFeatures.PostProcessingContext): + + @DataFeatures.PipelineStepInit + def __init__(self, **kwargs): + + # Init PostProcessingContext class + super().__init__() + + # Init private attributes + self.__path = None + self.__movie = None + self.__movie_fps = None + self.__movie_width = None + self.__movie_height = None + self.__movie_length = None + + self.__current_image_index = None + self.__next_image_index = None + self.__refresh = False + + @property + def path(self) -> str: + """Movie file path.""" + return self.__path + + @path.setter + def path(self, path: str): + + self.__path = path + + # Load movie + self.__movie = cv2.VideoCapture(self.__path) + self.__movie_fps = self.__movie.get(cv2.CAP_PROP_FPS) + self.__movie_width = int(self.__movie.get(cv2.CAP_PROP_FRAME_WIDTH)) + self.__movie_height = int(self.__movie.get(cv2.CAP_PROP_FRAME_HEIGHT)) + self.__movie_length = self.__movie.get(cv2.CAP_PROP_FRAME_COUNT) + + @DataFeatures.PipelineStepEnter + def __enter__(self): + + logging.info('OpenCV movie context starts...') + + # Create a window to display context + cv2.namedWindow(self.name, cv2.WINDOW_AUTOSIZE) + + # Init timestamp + self.__start_time = time.time() + + # Attach mouse event callback to window + cv2.setMouseCallback(self.name, self.__on_mouse_event) + + # Open reading thread + self.__reading_thread = threading.Thread(target=self.__read) + + logging.debug('> starting reading thread...') + self.__reading_thread.start() + + return self + + def __read(self): + """Iterate on movie images.""" + + # Init image selection + _, current_image = self.__movie.read() + current_image_time = self.__movie.get(cv2.CAP_PROP_POS_MSEC) + self.__next_image_index = 0 #int(self.__start * self.__movie_fps) + + while not self._stop_event.is_set(): + + # Check pause event (and stop event) + while self._pause_event.is_set() and not self._stop_event.is_set(): + + logging.debug('> reading is paused at %i', current_image_time) + + time.sleep(1) + + # Select a new image and detect markers once + if self.__next_image_index != self.__current_image_index or self.__refresh: + + self.__movie.set(cv2.CAP_PROP_POS_FRAMES, self.__next_image_index) + + success, image = self.__movie.read() + + video_height, video_width, _ = image.shape + + if success: + + # Refresh once + self.__refresh = False + + self.__current_image_index = self.__movie.get(cv2.CAP_PROP_POS_FRAMES) - 1 + current_image_time = self.__movie.get(cv2.CAP_PROP_POS_MSEC) + + # Timestamp image + image = DataFeatures.TimestampedImage(image, timestamp=current_image_time) + + # Process movie image + self._process_camera_image(timestamp=current_image_time, image=image) + + # Wait + time.sleep(1 / self.__movie_fps) + + @DataFeatures.PipelineStepExit + def __exit__(self, exception_type, exception_value, exception_traceback): + + logging.info('OpenCV movie context stops...') + + # Close data stream + self._stop_event.set() + + # Stop reading thread + threading.Thread.join(self.__reading_thread) + + # Delete window + cv2.destroyAllWindows() + + def __on_mouse_event(self, event, x, y, flags, param): + """Process pointer position.""" + + logging.debug('Window.on_mouse_event %i %i', x, y) + + if not self.is_paused(): + + # Process timestamped gaze position + self._process_gaze_position(timestamp = int((time.time() - self.__start_time) * 1e3), x = x, y = y) + + def refresh(self): + """Refresh current frame.""" + self.__refresh = True + + def previous(self): + + self.__next_image_index -= 1 + + # Clip image index + if self.__next_image_index < 0: + self.__next_image_index = 0 + + def next(self): + + self.__next_image_index += 1 + + # Clip image index + if self.__next_image_index < 0: + self.__next_image_index = 0 + + @property + def duration(self) -> int|float: + """Get data duration.""" + + return self.__movie_length / self.__movie_fps + + @property + def progression(self) -> float: + """Get data processing progression between 0 and 1.""" + + if self.__current_image_index is not None: + + return self.__current_image_index / self.__movie_length + + else: + + return 0.
\ No newline at end of file |