diff --git a/openflexure_microscope/camera/base.py b/openflexure_microscope/camera/base.py index 3af1e7b5..8dbf339f 100644 --- a/openflexure_microscope/camera/base.py +++ b/openflexure_microscope/camera/base.py @@ -5,6 +5,8 @@ import threading import datetime import logging +from abc import ABCMeta, abstractmethod + try: from greenlet import getcurrent as get_ident except ImportError: @@ -43,6 +45,11 @@ def generate_numbered_basename(obj_list: list) -> str: return basename +def shunt_captures(target_list: list): + for obj in target_list: # For each older capture + obj.shunt() # Shunt capture from memory to storage + + class CameraEvent(object): """ A frame-signaller object used by any instances or subclasses of BaseCamera. @@ -87,7 +94,7 @@ class CameraEvent(object): self.events[get_ident()][0].clear() -class BaseCamera(object): +class BaseCamera(metaclass=ABCMeta): """ Base implementation of StreamingCamera. @@ -101,7 +108,6 @@ class BaseCamera(object): stream_timeout (int): Number of inactive seconds before timing out the stream stream_timeout_enabled (bool): Enable or disable timing out the stream state (dict): Dictionary for capture state - config (dict): Dictionary of base camera config paths (dict): Dictionary of capture paths images (list): List of image capture objects videos (list): List of video capture objects @@ -115,12 +121,12 @@ class BaseCamera(object): self.frame = None self.last_access = 0 self.event = CameraEvent() + self.stop = False # Used to indicate that the stream loop should break self.stream_timeout = 20 self.stream_timeout_enabled = False self.state = {} - self.config = {} self.paths = { 'image': BASE_CAPTURE_PATH, 'video': BASE_CAPTURE_PATH, @@ -132,13 +138,15 @@ class BaseCamera(object): self.images = [] self.videos = [] - def apply_config(self, config): + @abstractmethod + def apply_config(self, config: dict): """Update settings from a config dictionary""" - self.config.update(config) + pass - def read_config(self, config): + @abstractmethod + def read_config(self): """Return the current settings as a dictionary""" - return self.config + pass def __enter__(self): """Create camera on context enter.""" @@ -176,7 +184,7 @@ class BaseCamera(object): self.last_access = time.time() self.stop = False - + if not self.state['stream_active']: # start background frame thread self.thread = threading.Thread(target=self._thread) @@ -222,9 +230,10 @@ class BaseCamera(object): return self.frame + @abstractmethod def frames(self): """Create generator that returns frames from the camera.""" - raise RuntimeError('Must be implemented by subclasses.') + pass # RETURNING CAPTURES @@ -248,10 +257,6 @@ class BaseCamera(object): # CREATING NEW CAPTURES - def shunt_captures(self, target_list: list): - for obj in target_list: # For each older capture - obj.shunt() # Shunt capture from memory to storage - def new_image( self, write_to_file: bool = True, @@ -268,6 +273,7 @@ class BaseCamera(object): temporary (bool): Should the data be deleted after session ends. Creating the capture with a content manager sets this to true. filename (str): Name of the stored file. Defaults to timestamp. + folder (str): Name of the folder in which to store the capture. fmt (str): Format of the capture. """ @@ -291,7 +297,7 @@ class BaseCamera(object): filepath=filepath) # Update capture list - self.shunt_captures(self.images) + shunt_captures(self.images) self.images.append(output) return output @@ -312,6 +318,7 @@ class BaseCamera(object): temporary (bool): Should the data be deleted after session ends. Creating the capture with a content manager sets this to true. filename (str): Name of the stored file. Defaults to timestamp. + folder (str): Name of the folder in which to store the capture. fmt (str): Format of the capture. """ @@ -335,7 +342,7 @@ class BaseCamera(object): filepath=filepath) # Update capture list - self.shunt_captures(self.videos) + shunt_captures(self.videos) self.videos.append(output) return output