Loading openflexure_microscope/api/app.py +7 −4 Original line number Diff line number Diff line Loading @@ -12,14 +12,17 @@ from openflexure_microscope.api.exceptions import JSONExceptionHandler from openflexure_microscope.api.utilities import list_routes from openflexure_microscope import Microscope try: from openflexure_microscope.camera.pi import StreamingCamera except ImportError: from openflexure_microscope.camera.mock import StreamingCamera from openflexure_microscope.stage.sanga import SangaStage from openflexure_microscope.stage.mock import MockStage from openflexure_microscope.camera.capture import build_captures_from_exif from openflexure_microscope.config import USER_CONFIG_DIR from openflexure_microscope.api.v1 import blueprints import time Loading Loading @@ -101,7 +104,7 @@ def attach_microscope(): # TODO: Tidy this up. api_stage may be referenced before assignment. Use some form of Maybe monad? try: api_stage = SangaStage() except (SerialException, OSError) as e: except Exception as e: logging.error(e) logging.warning("No valid stage hardware found. Falling back to mock stage!") api_stage = MockStage() Loading openflexure_microscope/camera/__init__.py +0 −3 Original line number Diff line number Diff line __all__ = ['pi', 'base'] from . import pi, base openflexure_microscope/camera/base.py +0 −9 Original line number Diff line number Diff line Loading @@ -167,15 +167,6 @@ class BaseCamera(metaclass=ABCMeta): self.stop_worker() logging.info("Closed {}".format(self)) def wait_for_camera(self, timeout=5): """Wait for camera object, with 5 second timeout.""" timeout_time = time.time() + timeout while not self.camera: if time.time() > timeout_time: raise TimeoutError("Timeout waiting for camera") else: pass # START AND STOP WORKER THREAD def start_worker(self, timeout: int = 5) -> bool: Loading openflexure_microscope/camera/mock.py 0 → 100644 +217 −0 Original line number Diff line number Diff line # -*- coding: utf-8 -*- """ """ from __future__ import division import io import time import numpy as np from PIL import Image import logging # Type hinting from typing import Tuple from .base import BaseCamera, CaptureObject # MAIN CLASS class StreamingCamera(BaseCamera): def __init__(self): # Run BaseCamera init BaseCamera.__init__(self) # Store state of StreamingCamera self.state.update({ 'stream_active': False, 'record_active': False }) # Update config properties self.image_resolution = (1312, 976) self.stream_resolution = (832, 624) self.numpy_resolution = (1312, 976) self.jpeg_quality = 75 self.framerate = 10 # Create an empty stream self.stream = io.BytesIO() # Start streaming self.start_worker() def initialisation(self): """Run any initialisation code when the frame iterator starts.""" pass def close(self): """Close the Raspberry Pi StreamingCamera.""" # Run BaseCamera close method BaseCamera.close(self) # HANDLE SETTINGS def read_config(self) -> dict: """ Return config dictionary of the StreamingCamera. """ conf_dict = { 'stream_resolution': self.stream_resolution, 'image_resolution': self.image_resolution, 'numpy_resolution': self.numpy_resolution, 'jpeg_quality': self.jpeg_quality } return conf_dict def apply_config(self, config: dict): """ Write a config dictionary to the StreamingCamera config. The passed dictionary may contain other parameters not relevant to camera config. Eg. Passing a general config file will work fine. Args: config (dict): Dictionary of config parameters. """ # TODO: Include timing and batching logic when applying PiCamera settings paused_stream = False logging.debug("StreamingCamera: Applying config:") logging.debug(config) with self.lock: # Apply valid config params to Picamera object if not self.state['record_active']: # If not recording a video # StreamingCamera parameters for key, value in config.items(): # For each provided setting if hasattr(self, key): setattr(self, key, value) # If stream was paused to update config, unpause if paused_stream: logging.info("Resuming stream.") self.start_stream_recording() else: raise Exception( "Cannot update camera config while recording is active.") def set_zoom(self, zoom_value: float = 1.) -> None: """ Change the camera zoom, handling re-centering and scaling. """ logging.warning("Zoom not implemented in mock camera") # LAUNCH ACTIONS def start_preview(self, fullscreen=True, window=None): """Start the on board GPU camera preview.""" logging.warning("GPU preview not implemented in mock camera") def stop_preview(self): """Stop the on board GPU camera preview.""" logging.warning("GPU preview not implemented in mock camera") def start_recording( self, output, fmt: str = 'h264', quality: int = 15): """Start recording. Start a new video recording, writing to a output object. Args: output (CaptureObject/str): Output object to write data bytes to. fmt (str): Format of the capture. quality (int): Video recording quality. Returns: output_object (str/BytesIO): Target object. """ with self.lock: # Start recording method only if a current recording is not running logging.warning("Recording not implemented in mock camera") def stop_recording(self): """Stop the last started video recording on splitter port 2.""" with self.lock: logging.warning("Recording not implemented in mock camera") def capture( self, output, fmt: str = 'jpeg', use_video_port: bool = False, resize: Tuple[int, int] = None, bayer: bool = True): """ Capture a still image to a StreamObject. Defaults to JPEG format. Target object can be overridden for development purposes. Args: output (CaptureObject/str): Output object to write data bytes to. use_video_port (bool): Capture from the video port used for streaming. Lower resolution, faster. fmt (str): Format of the capture. resize ((int, int)): Resize the captured image. bayer (bool): Store raw bayer data in capture """ with self.lock: logging.warning("Capture not implemented in mock camera") def gen_img(self): imarray = np.random.rand(self.stream_resolution[1], self.stream_resolution[0], 3) * 255 im = Image.fromarray(imarray.astype('uint8')).convert('L') im.save(self.stream, format="JPEG") # HANDLE STREAM FRAMES def frames(self): """ Create generator that returns frames from the camera. Records video from port 1 to a byte stream, and iterates sequential frames. """ # Run this initialisation method self.initialisation() # Update state logging.debug("STREAM ACTIVE") # While the iterator is not closed try: while True: # reset stream for next frame self.stream.seek(0) self.stream.truncate() # to stream, read the new frame time.sleep(1 / self.framerate * 0.1) # yield the result to be read self.gen_img() frame = self.stream.getvalue() # ensure the size of package is right if len(frame) == 0: pass else: yield frame # When GeneratorExit or StopIteration raised, run cleanup code finally: logging.debug("FRAME ITERATOR END") openflexure_microscope/camera/pi.py +9 −0 Original line number Diff line number Diff line Loading @@ -529,6 +529,15 @@ class StreamingCamera(BaseCamera): # HANDLE STREAM FRAMES def wait_for_camera(self, timeout=5): """Wait for camera object, with 5 second timeout.""" timeout_time = time.time() + timeout while not self.camera: if time.time() > timeout_time: raise TimeoutError("Timeout waiting for camera") else: pass def frames(self): """ Create generator that returns frames from the camera. Loading Loading
openflexure_microscope/api/app.py +7 −4 Original line number Diff line number Diff line Loading @@ -12,14 +12,17 @@ from openflexure_microscope.api.exceptions import JSONExceptionHandler from openflexure_microscope.api.utilities import list_routes from openflexure_microscope import Microscope try: from openflexure_microscope.camera.pi import StreamingCamera except ImportError: from openflexure_microscope.camera.mock import StreamingCamera from openflexure_microscope.stage.sanga import SangaStage from openflexure_microscope.stage.mock import MockStage from openflexure_microscope.camera.capture import build_captures_from_exif from openflexure_microscope.config import USER_CONFIG_DIR from openflexure_microscope.api.v1 import blueprints import time Loading Loading @@ -101,7 +104,7 @@ def attach_microscope(): # TODO: Tidy this up. api_stage may be referenced before assignment. Use some form of Maybe monad? try: api_stage = SangaStage() except (SerialException, OSError) as e: except Exception as e: logging.error(e) logging.warning("No valid stage hardware found. Falling back to mock stage!") api_stage = MockStage() Loading
openflexure_microscope/camera/__init__.py +0 −3 Original line number Diff line number Diff line __all__ = ['pi', 'base'] from . import pi, base
openflexure_microscope/camera/base.py +0 −9 Original line number Diff line number Diff line Loading @@ -167,15 +167,6 @@ class BaseCamera(metaclass=ABCMeta): self.stop_worker() logging.info("Closed {}".format(self)) def wait_for_camera(self, timeout=5): """Wait for camera object, with 5 second timeout.""" timeout_time = time.time() + timeout while not self.camera: if time.time() > timeout_time: raise TimeoutError("Timeout waiting for camera") else: pass # START AND STOP WORKER THREAD def start_worker(self, timeout: int = 5) -> bool: Loading
openflexure_microscope/camera/mock.py 0 → 100644 +217 −0 Original line number Diff line number Diff line # -*- coding: utf-8 -*- """ """ from __future__ import division import io import time import numpy as np from PIL import Image import logging # Type hinting from typing import Tuple from .base import BaseCamera, CaptureObject # MAIN CLASS class StreamingCamera(BaseCamera): def __init__(self): # Run BaseCamera init BaseCamera.__init__(self) # Store state of StreamingCamera self.state.update({ 'stream_active': False, 'record_active': False }) # Update config properties self.image_resolution = (1312, 976) self.stream_resolution = (832, 624) self.numpy_resolution = (1312, 976) self.jpeg_quality = 75 self.framerate = 10 # Create an empty stream self.stream = io.BytesIO() # Start streaming self.start_worker() def initialisation(self): """Run any initialisation code when the frame iterator starts.""" pass def close(self): """Close the Raspberry Pi StreamingCamera.""" # Run BaseCamera close method BaseCamera.close(self) # HANDLE SETTINGS def read_config(self) -> dict: """ Return config dictionary of the StreamingCamera. """ conf_dict = { 'stream_resolution': self.stream_resolution, 'image_resolution': self.image_resolution, 'numpy_resolution': self.numpy_resolution, 'jpeg_quality': self.jpeg_quality } return conf_dict def apply_config(self, config: dict): """ Write a config dictionary to the StreamingCamera config. The passed dictionary may contain other parameters not relevant to camera config. Eg. Passing a general config file will work fine. Args: config (dict): Dictionary of config parameters. """ # TODO: Include timing and batching logic when applying PiCamera settings paused_stream = False logging.debug("StreamingCamera: Applying config:") logging.debug(config) with self.lock: # Apply valid config params to Picamera object if not self.state['record_active']: # If not recording a video # StreamingCamera parameters for key, value in config.items(): # For each provided setting if hasattr(self, key): setattr(self, key, value) # If stream was paused to update config, unpause if paused_stream: logging.info("Resuming stream.") self.start_stream_recording() else: raise Exception( "Cannot update camera config while recording is active.") def set_zoom(self, zoom_value: float = 1.) -> None: """ Change the camera zoom, handling re-centering and scaling. """ logging.warning("Zoom not implemented in mock camera") # LAUNCH ACTIONS def start_preview(self, fullscreen=True, window=None): """Start the on board GPU camera preview.""" logging.warning("GPU preview not implemented in mock camera") def stop_preview(self): """Stop the on board GPU camera preview.""" logging.warning("GPU preview not implemented in mock camera") def start_recording( self, output, fmt: str = 'h264', quality: int = 15): """Start recording. Start a new video recording, writing to a output object. Args: output (CaptureObject/str): Output object to write data bytes to. fmt (str): Format of the capture. quality (int): Video recording quality. Returns: output_object (str/BytesIO): Target object. """ with self.lock: # Start recording method only if a current recording is not running logging.warning("Recording not implemented in mock camera") def stop_recording(self): """Stop the last started video recording on splitter port 2.""" with self.lock: logging.warning("Recording not implemented in mock camera") def capture( self, output, fmt: str = 'jpeg', use_video_port: bool = False, resize: Tuple[int, int] = None, bayer: bool = True): """ Capture a still image to a StreamObject. Defaults to JPEG format. Target object can be overridden for development purposes. Args: output (CaptureObject/str): Output object to write data bytes to. use_video_port (bool): Capture from the video port used for streaming. Lower resolution, faster. fmt (str): Format of the capture. resize ((int, int)): Resize the captured image. bayer (bool): Store raw bayer data in capture """ with self.lock: logging.warning("Capture not implemented in mock camera") def gen_img(self): imarray = np.random.rand(self.stream_resolution[1], self.stream_resolution[0], 3) * 255 im = Image.fromarray(imarray.astype('uint8')).convert('L') im.save(self.stream, format="JPEG") # HANDLE STREAM FRAMES def frames(self): """ Create generator that returns frames from the camera. Records video from port 1 to a byte stream, and iterates sequential frames. """ # Run this initialisation method self.initialisation() # Update state logging.debug("STREAM ACTIVE") # While the iterator is not closed try: while True: # reset stream for next frame self.stream.seek(0) self.stream.truncate() # to stream, read the new frame time.sleep(1 / self.framerate * 0.1) # yield the result to be read self.gen_img() frame = self.stream.getvalue() # ensure the size of package is right if len(frame) == 0: pass else: yield frame # When GeneratorExit or StopIteration raised, run cleanup code finally: logging.debug("FRAME ITERATOR END")
openflexure_microscope/camera/pi.py +9 −0 Original line number Diff line number Diff line Loading @@ -529,6 +529,15 @@ class StreamingCamera(BaseCamera): # HANDLE STREAM FRAMES def wait_for_camera(self, timeout=5): """Wait for camera object, with 5 second timeout.""" timeout_time = time.time() + timeout while not self.camera: if time.time() > timeout_time: raise TimeoutError("Timeout waiting for camera") else: pass def frames(self): """ Create generator that returns frames from the camera. Loading