Loading openflexure_microscope/api/microscope.py +0 −29 Original line number Diff line number Diff line Loading @@ -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( Loading openflexure_microscope/api/v2/views/state.py +2 −2 Original line number Diff line number Diff line Loading @@ -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() Loading Loading @@ -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) Loading openflexure_microscope/camera/base.py +25 −10 Original line number Diff line number Diff line Loading @@ -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} Loading @@ -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 Loading Loading @@ -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 Loading @@ -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.") Loading Loading @@ -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 Loading @@ -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 Loading @@ -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 openflexure_microscope/camera/mock.py +14 −8 Original line number Diff line number Diff line Loading @@ -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) Loading Loading @@ -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 Loading Loading @@ -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. Loading @@ -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): Loading openflexure_microscope/camera/pi.py +36 −20 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading @@ -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 Loading Loading @@ -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. Loading @@ -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 Loading Loading @@ -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) Loading @@ -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. Loading @@ -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)) Loading @@ -412,7 +428,7 @@ class PiCameraStreamer(BaseCamera): ) # Update status dictionary self.status["record_active"] = True self.record_active = True return output Loading @@ -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 Loading Loading @@ -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 Loading
openflexure_microscope/api/microscope.py +0 −29 Original line number Diff line number Diff line Loading @@ -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( Loading
openflexure_microscope/api/v2/views/state.py +2 −2 Original line number Diff line number Diff line Loading @@ -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() Loading Loading @@ -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) Loading
openflexure_microscope/camera/base.py +25 −10 Original line number Diff line number Diff line Loading @@ -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} Loading @@ -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 Loading Loading @@ -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 Loading @@ -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.") Loading Loading @@ -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 Loading @@ -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 Loading @@ -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
openflexure_microscope/camera/mock.py +14 −8 Original line number Diff line number Diff line Loading @@ -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) Loading Loading @@ -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 Loading Loading @@ -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. Loading @@ -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): Loading
openflexure_microscope/camera/pi.py +36 −20 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading @@ -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 Loading Loading @@ -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. Loading @@ -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 Loading Loading @@ -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) Loading @@ -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. Loading @@ -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)) Loading @@ -412,7 +428,7 @@ class PiCameraStreamer(BaseCamera): ) # Update status dictionary self.status["record_active"] = True self.record_active = True return output Loading @@ -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 Loading Loading @@ -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