Commit dc794d4d authored by Joel Collins's avatar Joel Collins
Browse files

Microscope capture objects now just handle on-disk data

parent a832dfb2
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -59,15 +59,15 @@ class MyPluginClass(MicroscopePlugin):
            for _ in range(n_images):

                # Create a data stream to capture to
                capture_data = self.microscope.camera.new_image(
                    write_to_file=True, temporary=False
                output = self.microscope.camera.new_image(
                    temporary=False
                )

                # Capture a still image from the Pi camera, into the data stream
                self.microscope.camera.capture(capture_data, use_video_port=True)
                self.microscope.camera.capture(output.file, use_video_port=True)

                # Append the capture data to our list
                capture_array.append(capture_data)
                capture_array.append(output)

                # Wait for 1 minute
                time.sleep(60)
+3 −4
Original line number Diff line number Diff line
@@ -103,17 +103,16 @@ For example, a timelapse plugin may look like:
                for _ in range(n_images):

                    # Create a data stream to capture to
                    capture_data = self.microscope.camera.new_image(
                        write_to_file=True,
                    output = self.microscope.camera.new_image(
                        temporary=False)

                    # Capture a still image from the Pi camera, into the data stream
                    self.microscope.camera.capture(
                        capture_data,
                        output.file,
                        use_video_port=True)
                    
                    # Append the capture data to our list
                    capture_array.append(capture_data)
                    capture_array.append(output)

                    # Wait for 1 minute
                    time.sleep(60)  
+27 −44
Original line number Diff line number Diff line
@@ -43,7 +43,7 @@ DEFAULT_LOGFILE = os.path.join(USER_CONFIG_DIR, "openflexure_microscope.log")
if (__name__ == "__main__") or (not is_gunicorn):
    # If imported, but not by gunicorn
    print("Letting sys handle logs")
    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
    logging.basicConfig(level=logging.DEBUG)
else:
    # Direct standard Python logging to file and console
    root = logging.getLogger()
@@ -66,37 +66,6 @@ else:
# Create a dummy microscope object, with no hardware attachments
api_microscope = Microscope()

# Rebuild the capture list
# TODO: Offload to a thread?
stored_image_list = build_captures_from_exif()


# Generate API URI based on version from filename
def uri(suffix, api_version, base=None):
    if not base:
        base = "/api/{}".format(api_version)
    return_uri = base + suffix
    logging.debug("Created app route: {}".format(return_uri))
    return return_uri


# Create flask app
app = Flask(__name__)
app.url_map.strict_slashes = False

CORS(app, resources=r"/api/*")

# Make errors more API friendly
handler = JSONExceptionHandler(app)


# After app starts, but before first request, attach hardware to global microscope
@app.before_first_request
def attach_microscope():
    # Create the microscope object globally (common to all spawned server threads)
    global api_microscope, stored_image_list
    logging.debug("First request made. Populating microscope with hardware...")

# Initialise camera
logging.debug("Creating camera object...")
try:
@@ -121,15 +90,29 @@ def attach_microscope():

# Restore loaded capture array to camera object
logging.debug("Restoring captures...")
    if stored_image_list:
        api_microscope.camera.images = stored_image_list
api_microscope.camera.images = build_captures_from_exif(api_microscope.camera.paths["default"])

logging.debug("Microscope successfully attached!")

# Generate API URI based on version from filename
def uri(suffix, api_version, base=None):
    if not base:
        base = "/api/{}".format(api_version)
    return_uri = base + suffix
    logging.debug("Created app route: {}".format(return_uri))
    return return_uri

# WEBAPP ROUTES

# API ROUTES
# Create flask app
app = Flask(__name__)
app.url_map.strict_slashes = False

CORS(app, resources=r"/api/*")

# Make errors more API friendly
handler = JSONExceptionHandler(app)

# WEBAPP ROUTES

# Base routes
base_blueprint = blueprints.base.construct_blueprint(api_microscope)
+2 −2
Original line number Diff line number Diff line
@@ -123,11 +123,11 @@ class ListAPI(MicroscopeView):
        # Explicitally acquire lock (prevents empty files being created if lock is unavailable)
        with self.microscope.camera.lock:
            output = self.microscope.camera.new_image(
                write_to_file=True, temporary=temporary, filename=filename
                temporary=temporary, filename=filename
            )

            self.microscope.camera.capture(
                output, use_video_port=use_video_port, resize=resize, bayer=bayer
                output.file, use_video_port=use_video_port, resize=resize, bayer=bayer
            )

            metadata.update(
+28 −19
Original line number Diff line number Diff line
# -*- coding: utf-8 -*-
import time
import os
import shutil
import threading
import datetime
import logging

from abc import ABCMeta, abstractmethod

from .capture import CaptureObject, BASE_CAPTURE_PATH, TEMP_CAPTURE_PATH
from .capture import CaptureObject
from openflexure_microscope.utilities import entry_by_id
from openflexure_microscope.lock import StrictLock


BASE_CAPTURE_PATH = os.path.join(os.path.expanduser("~"), "micrographs")
TEMP_CAPTURE_PATH = os.path.join(BASE_CAPTURE_PATH, "tmp")


def last_entry(object_list: list):
    """Return the last entry of a list, if the list contains items."""
    if object_list:  # If any images have been captured
@@ -121,11 +126,11 @@ class BaseCamera(metaclass=ABCMeta):
        self.stream_timeout_enabled = False

        self.state = {}

        # TODO: Load/save these to config
        self.paths = {
            "image": BASE_CAPTURE_PATH,
            "video": BASE_CAPTURE_PATH,
            "image_tmp": TEMP_CAPTURE_PATH,
            "video_tpm": TEMP_CAPTURE_PATH,
            "default": BASE_CAPTURE_PATH,
            "temp": TEMP_CAPTURE_PATH
        }

        # Capture data
@@ -157,10 +162,22 @@ class BaseCamera(metaclass=ABCMeta):
        for capture_list in [self.images, self.videos]:
            for stream_object in capture_list:
                stream_object.close()
        # Empty temp directory
        self.clear_tmp()
        # Stop worker thread
        self.stop_worker()
        logging.info("Closed {}".format(self))

    def clear_tmp(self):
        """
        Removes all files in the temporary capture directories
        """

        if os.path.isdir(self.paths["temp"]):
            logging.info("Clearing {}...".format(self.paths["temp"]))
            shutil.rmtree(self.paths["temp"])
            logging.debug("Cleared {}.".format(self.paths["temp"]))

    # START AND STOP WORKER THREAD

    def start_worker(self, timeout: int = 5) -> bool:
@@ -244,7 +261,6 @@ class BaseCamera(metaclass=ABCMeta):

    def new_image(
        self,
        write_to_file: bool = True,
        temporary: bool = True,
        filename: str = None,
        folder: str = "",
@@ -252,10 +268,9 @@ class BaseCamera(metaclass=ABCMeta):
    ):

        """
        Create a new image capture object. Adds to the image list, and shunt all others.
        Create a new image capture object.

        Args:
            write_to_file (bool): Should the StreamObject write to a file, or an in-memory byte stream.
            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.
@@ -270,16 +285,14 @@ class BaseCamera(metaclass=ABCMeta):
        filename = "{}.{}".format(filename, fmt)

        # Generate folder
        base_folder = self.paths["image_tmp"] if temporary else self.paths["image"]
        base_folder = self.paths["temp"] if temporary else self.paths["default"]
        folder = os.path.join(base_folder, folder)

        # Generate file path
        filepath = os.path.join(folder, filename)

        # Create capture object
        output = CaptureObject(
            write_to_file=write_to_file, temporary=temporary, filepath=filepath
        )
        output = CaptureObject(filepath=filepath)

        # Update capture list
        shunt_captures(self.images)
@@ -289,7 +302,6 @@ class BaseCamera(metaclass=ABCMeta):

    def new_video(
        self,
        write_to_file: bool = True,
        temporary: bool = False,
        filename: str = None,
        folder: str = "",
@@ -297,10 +309,9 @@ class BaseCamera(metaclass=ABCMeta):
    ):

        """
        Create a new video capture object. Adds to the image list, and shunt all others.
        Create a new video capture object.

        Args:
            write_to_file (bool): Should the StreamObject write to a file, or an in-memory byte stream.
            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.
@@ -315,16 +326,14 @@ class BaseCamera(metaclass=ABCMeta):
        filename = "{}.{}".format(filename, fmt)

        # Generate folder
        base_folder = self.paths["video_tmp"] if temporary else self.paths["video"]
        base_folder = self.paths["temp"] if temporary else self.paths["default"]
        folder = os.path.join(base_folder, folder)

        # Generate file path
        filepath = os.path.join(folder, filename)

        # Create capture object
        output = CaptureObject(
            write_to_file=write_to_file, temporary=temporary, filepath=filepath
        )
        output = CaptureObject(filepath=filepath)

        # Update capture list
        shunt_captures(self.videos)
Loading