Loading docs/source/plugins/example/plugin.py +4 −4 Original line number Diff line number Diff line Loading @@ -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) Loading docs/source/plugins/hardware.rst +3 −4 Original line number Diff line number Diff line Loading @@ -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) Loading openflexure_microscope/api/app.py +27 −44 Original line number Diff line number Diff line Loading @@ -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() Loading @@ -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: Loading @@ -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) Loading openflexure_microscope/api/v1/blueprints/camera/capture.py +2 −2 Original line number Diff line number Diff line Loading @@ -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( Loading openflexure_microscope/camera/base.py +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 Loading Loading @@ -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 Loading Loading @@ -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: Loading Loading @@ -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 = "", Loading @@ -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. Loading @@ -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) Loading @@ -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 = "", Loading @@ -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. Loading @@ -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 Loading
docs/source/plugins/example/plugin.py +4 −4 Original line number Diff line number Diff line Loading @@ -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) Loading
docs/source/plugins/hardware.rst +3 −4 Original line number Diff line number Diff line Loading @@ -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) Loading
openflexure_microscope/api/app.py +27 −44 Original line number Diff line number Diff line Loading @@ -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() Loading @@ -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: Loading @@ -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) Loading
openflexure_microscope/api/v1/blueprints/camera/capture.py +2 −2 Original line number Diff line number Diff line Loading @@ -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( Loading
openflexure_microscope/camera/base.py +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 Loading Loading @@ -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 Loading Loading @@ -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: Loading Loading @@ -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 = "", Loading @@ -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. Loading @@ -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) Loading @@ -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 = "", Loading @@ -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. Loading @@ -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