Commit 19af9873 authored by Joel Collins's avatar Joel Collins
Browse files

Draft of new semantics

parent b39a0653
Loading
Loading
Loading
Loading
+0 −29
Original line number Diff line number Diff line
@@ -3,38 +3,9 @@ from openflexure_microscope.camera.capture import build_captures_from_exif

import logging

# Import device modules
# NB this will eventually be handled by the RC file, so you can choose what device
# class should be attached.
try:
    from openflexure_microscope.camera.pi import PiCameraStreamer
except ImportError:
    logging.warning("Unable to import PiCameraStreamer")
from openflexure_microscope.camera.mock import MockStreamer

from openflexure_microscope.stage.sanga import SangaStage
from openflexure_microscope.stage.mock import MockStage

default_microscope = Microscope()

# Initialise camera
logging.debug("Creating camera object...")
try:
    api_camera = PiCameraStreamer()
except Exception as e:
    logging.error(e)
    logging.warning("No valid camera hardware found. Falling back to mock camera!")
    api_camera = MockStreamer()

# Initialise stage
logging.debug("Creating stage object...")

api_stage = MockStage()

# Attach devices to microscope
logging.debug("Attaching devices to microscope...")
default_microscope.attach(api_camera, api_stage)

# Restore loaded capture array to camera object
logging.debug("Restoring captures...")
default_microscope.camera.images = build_captures_from_exif(
+2 −2
Original line number Diff line number Diff line
@@ -30,7 +30,7 @@ class SettingsProperty(View):
        logging.debug("Updating settings from PUT request:")
        logging.debug(payload.json)

        microscope.apply_settings(payload.json)
        microscope.update_settings(payload.json)
        microscope.save_settings()

        return self.get()
@@ -65,7 +65,7 @@ class NestedSettingsProperty(View):
        dictionary = create_from_path(keys)
        set_by_path(dictionary, keys, payload.json)

        microscope.apply_settings(dictionary)
        microscope.update_settings(dictionary)
        microscope.save_settings()

        return self.get(route)
+25 −10
Original line number Diff line number Diff line
@@ -121,7 +121,8 @@ class BaseCamera(metaclass=ABCMeta):
        self.stream_timeout = 20
        self.stream_timeout_enabled = False

        self.status = {"board": None}
        self.stream_active = False
        self.record_active = False

        self.paths = {"default": BASE_CAPTURE_PATH, "temp": TEMP_CAPTURE_PATH}

@@ -129,8 +130,24 @@ class BaseCamera(metaclass=ABCMeta):
        self.images = []
        self.videos = []

    @property
    @abstractmethod
    def configuration(self):
        """The current camera configuration."""
        pass

    @property
    @abstractmethod
    def state(self):
        """The current read-only camera state."""
        pass

    @property
    def settings(self):
        return self.read_settings()

    @abstractmethod
    def apply_settings(self, config: dict):
    def update_settings(self, config: dict):
        """Update settings from a config dictionary"""
        with self.lock:
            # Apply valid config params to camera object
@@ -187,7 +204,7 @@ class BaseCamera(metaclass=ABCMeta):
        self.last_access = time.time()
        self.stop = False

        if not self.status["stream_active"]:
        if not self.stream_active:
            # start background frame thread
            self.thread = threading.Thread(target=self._thread)
            self.thread.daemon = True
@@ -207,12 +224,12 @@ class BaseCamera(metaclass=ABCMeta):
        logging.debug("Stopping worker thread")
        timeout_time = time.time() + timeout

        if self.status["stream_active"]:
        if self.stream_active:
            self.stop = True
            self.thread.join()  # Wait for stream thread to exit
            logging.debug("Waiting for stream thread to exit.")

        while self.status["stream_active"]:
        while self.stream_active:
            if time.time() > timeout_time:
                logging.debug("Timeout waiting for worker thread close.")
                raise TimeoutError("Timeout waiting for worker thread close.")
@@ -352,7 +369,7 @@ class BaseCamera(metaclass=ABCMeta):
        self.frames_iterator = self.frames()
        logging.debug("Entering worker thread.")

        self.status["stream_active"] = True
        self.stream_active = True

        for frame in self.frames_iterator:
            self.frame = frame
@@ -365,9 +382,7 @@ class BaseCamera(metaclass=ABCMeta):
                and (  # If using timeout
                    time.time() - self.last_access > self.stream_timeout
                )
                and not self.status[  # And timeout time
                    "preview_active"
                ]  # And GPU preview is not active
                and not self.preview_active  # And GPU preview is not active
            ):
                self.frames_iterator.close()
                break
@@ -383,4 +398,4 @@ class BaseCamera(metaclass=ABCMeta):

        logging.debug("BaseCamera worker thread exiting...")
        # Set stream_activate state
        self.status["stream_active"] = False
        self.stream_active = False
+14 −8
Original line number Diff line number Diff line
@@ -26,17 +26,13 @@ We override the logging settings in api.app by setting a level for PIL here.
pil_logger = logging.getLogger("PIL")
pil_logger.setLevel(logging.INFO)


# MAIN CLASS
class MockStreamer(BaseCamera):
class MissingCamera(BaseCamera):
    def __init__(self):
        # Run BaseCamera init
        BaseCamera.__init__(self)

        # Store state of PiCameraStreamer
        self.status.update(
            {"stream_active": False, "record_active": False, "board": None}
        )

        # Update config properties
        self.image_resolution = (1312, 976)
        self.stream_resolution = (640, 480)
@@ -71,6 +67,16 @@ class MockStreamer(BaseCamera):

        image.save(self.stream, format="JPEG")

    @property
    def configuration(self):
        """The current camera configuration."""
        return {}

    @property
    def state(self):
        """The current read-only camera state."""
        return {}

    def initialisation(self):
        """Run any initialisation code when the frame iterator starts."""
        pass
@@ -101,7 +107,7 @@ class MockStreamer(BaseCamera):

        return conf_dict

    def apply_settings(self, config: dict):
    def update_settings(self, config: dict):
        """
        Write a config dictionary to the PiCameraStreamer config.

@@ -117,7 +123,7 @@ class MockStreamer(BaseCamera):
        with self.lock:

            # Apply valid config params to camera object
            if not self.status["record_active"]:  # If not recording a video
            if not self.record_active:  # If not recording a video

                for key, value in config.items():  # For each provided setting
                    if hasattr(self, key):
+36 −20
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ from .base import BaseCamera, CaptureObject
from .set_picamera_gain import set_analog_gain, set_digital_gain

from openflexure_microscope.paths import settings_file_path
from openflexure_microscope.utilities import serialise_array_b64


# MAIN CLASS
@@ -85,14 +86,7 @@ class PiCameraStreamer(BaseCamera):
        )  #: :py:class:`picamera.PiCamera`: Picamera object

        # Store status of PiCameraStreamer
        self.status.update(
            {
                "stream_active": False,
                "record_active": False,
                "preview_active": False,
                "board": f"picamera_{self.camera.revision}",
            }
        )
        self.preview_active = False

        # Reset variable states
        self.set_zoom(1.0)
@@ -116,15 +110,37 @@ class PiCameraStreamer(BaseCamera):
            "picamera_lst.npy"
        )  #: str: Path of .npy lens shading table file

        # Update board identifier
        self.status.update({})

        # Create an empty stream
        self.stream = io.BytesIO()

        # Start streaming
        self.start_worker()

    @property
    def configuration(self):
        """The current camera configuration."""
        config = {
            "board": self.camera.revision,
        }

        if self.read_lens_shading_table():
            b64_string, dtype, shape = serialise_array_b64(self.read_lens_shading_table())

            config.update({
                "lens_shading_table": {
                    "b64_string": b64_string,
                    "dtype": dtype,
                    "shape": shape,
                }
            })

        return config

    @property
    def state(self):
        """The current read-only camera state."""
        return {}

    def initialisation(self):
        """Run any initialisation code when the frame iterator starts."""
        pass
@@ -176,7 +192,7 @@ class PiCameraStreamer(BaseCamera):
        logging.info("Saving picamera_lst to {}".format(self.picamera_lst_path))
        self.save_lens_shading_table()

    def apply_settings(self, config: dict):
    def update_settings(self, config: dict):
        """
        Write a config dictionary to the PiCameraStreamer config.

@@ -194,10 +210,10 @@ class PiCameraStreamer(BaseCamera):
        with self.lock:

            # Apply valid config params to Picamera object
            if not self.status["record_active"]:  # If not recording a video
            if not self.record_active:  # If not recording a video

                # Pause stream while changing settings
                if self.status["stream_active"]:  # If stream is active
                if self.stream_active:  # If stream is active
                    logging.info("Pausing stream to update config.")
                    self.stop_stream_recording()  # Pause stream
                    paused_stream = True  # Remember to unpause stream when done
@@ -365,7 +381,7 @@ class PiCameraStreamer(BaseCamera):
                    self.camera.preview.window = window
                if fullscreen:
                    self.camera.preview.fullscreen = fullscreen
            self.status["preview_active"] = True
            self.preview_active = True
        except picamera.exc.PiCameraMMALError as e:
            logging.error(
                "Suppressed a MMALError in start_preview. Exception: {}".format(e)
@@ -380,7 +396,7 @@ class PiCameraStreamer(BaseCamera):
    def stop_preview(self):
        """Stop the on board GPU camera preview."""
        self.camera.stop_preview()
        self.status["preview_active"] = False
        self.preview_active = False

    def start_recording(self, output, fmt: str = "h264", quality: int = 15):
        """Start recording.
@@ -398,7 +414,7 @@ class PiCameraStreamer(BaseCamera):
        """
        with self.lock:
            # Start recording method only if a current recording is not running
            if not self.status["record_active"]:
            if not self.record_active:

                # Start the camera video recording on port 2
                logging.info("Recording to {}".format(output))
@@ -412,7 +428,7 @@ class PiCameraStreamer(BaseCamera):
                )

                # Update status dictionary
                self.status["record_active"] = True
                self.record_active = True

                return output

@@ -432,7 +448,7 @@ class PiCameraStreamer(BaseCamera):
            logging.info("Recording stopped")

            # Update status dictionary
            self.status["record_active"] = False
            self.record_active = False

    def stop_stream_recording(
        self, splitter_port: int = 1, resolution: Tuple[int, int] = None
@@ -500,7 +516,7 @@ class PiCameraStreamer(BaseCamera):
                self.camera.resolution = resolution

            # If the stream should be active
            if self.status["stream_active"]:
            if self.stream_active:
                try:
                    # Start recording on stream port
                    self.camera.start_recording(
Loading