This commit is contained in:
Joel Collins 2019-07-16 17:02:57 +01:00
commit 57832f94a8
10 changed files with 130 additions and 112 deletions

View file

@ -1,5 +1,11 @@
#!/usr/bin/env python #!/usr/bin/env python
import time
import atexit
import logging
import sys
import os
from flask import ( from flask import (
Flask, jsonify, send_file) Flask, jsonify, send_file)
@ -13,23 +19,22 @@ from openflexure_microscope.api.utilities import list_routes
from openflexure_microscope import Microscope 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.camera.capture import build_captures_from_exif
from openflexure_microscope.config import USER_CONFIG_DIR from openflexure_microscope.config import USER_CONFIG_DIR
from openflexure_microscope.api.v1 import blueprints from openflexure_microscope.api.v1 import blueprints
import time # Import device modules
import atexit # NB this will eventually be handled by the RC file, so you can choose what device
import logging # class should be attached.
import sys try:
import os 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
# Handle logging # Handle logging
is_gunicorn = "gunicorn" in os.environ.get("SERVER_SOFTWARE", "") is_gunicorn = "gunicorn" in os.environ.get("SERVER_SOFTWARE", "")
@ -96,30 +101,37 @@ def attach_microscope():
global api_microscope, stored_image_list global api_microscope, stored_image_list
logging.debug("First request made. Populating microscope with hardware...") logging.debug("First request made. Populating microscope with hardware...")
# Initialise camera
logging.debug("Creating camera object...") logging.debug("Creating camera object...")
# TODO: Try except finally, like with stage try:
api_camera = StreamingCamera() 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...") logging.debug("Creating stage object...")
# TODO: Tidy this up. api_stage may be referenced before assignment. Use some form of Maybe monad?
try: try:
api_stage = SangaStage() api_stage = SangaStage()
except Exception as e: except Exception as e:
logging.error(e) logging.error(e)
logging.warning("No valid stage hardware found. Falling back to mock stage!") logging.warning("No valid stage hardware found. Falling back to mock stage!")
api_stage = MockStage() api_stage = MockStage()
finally:
logging.debug("Attaching devices to microscope...")
api_microscope.attach(
api_camera,
api_stage
)
logging.debug("Restoring captures...") # Attach devices to microscope
if stored_image_list: logging.debug("Attaching devices to microscope...")
api_microscope.camera.images = stored_image_list api_microscope.attach(
api_camera,
api_stage
)
logging.debug("Microscope successfully attached!") # Restore loaded capture array to camera object
logging.debug("Restoring captures...")
if stored_image_list:
api_microscope.camera.images = stored_image_list
logging.debug("Microscope successfully attached!")
# WEBAPP ROUTES # WEBAPP ROUTES

View file

@ -19,13 +19,13 @@ from .base import BaseCamera, CaptureObject
# MAIN CLASS # MAIN CLASS
class StreamingCamera(BaseCamera): class MockStreamer(BaseCamera):
def __init__(self): def __init__(self):
# Run BaseCamera init # Run BaseCamera init
BaseCamera.__init__(self) BaseCamera.__init__(self)
# Store state of StreamingCamera # Store state of PiCameraStreamer
self.state.update({ self.state.update({
'stream_active': False, 'stream_active': False,
'record_active': False 'record_active': False
@ -49,14 +49,14 @@ class StreamingCamera(BaseCamera):
pass pass
def close(self): def close(self):
"""Close the Raspberry Pi StreamingCamera.""" """Close the Raspberry Pi PiCameraStreamer."""
# Run BaseCamera close method # Run BaseCamera close method
BaseCamera.close(self) BaseCamera.close(self)
# HANDLE SETTINGS # HANDLE SETTINGS
def read_config(self) -> dict: def read_config(self) -> dict:
""" """
Return config dictionary of the StreamingCamera. Return config dictionary of the PiCameraStreamer.
""" """
conf_dict = { conf_dict = {
@ -70,7 +70,7 @@ class StreamingCamera(BaseCamera):
def apply_config(self, config: dict): def apply_config(self, config: dict):
""" """
Write a config dictionary to the StreamingCamera config. Write a config dictionary to the PiCameraStreamer config.
The passed dictionary may contain other parameters not relevant to The passed dictionary may contain other parameters not relevant to
camera config. Eg. Passing a general config file will work fine. camera config. Eg. Passing a general config file will work fine.
@ -81,7 +81,7 @@ class StreamingCamera(BaseCamera):
# TODO: Include timing and batching logic when applying PiCamera settings # TODO: Include timing and batching logic when applying PiCamera settings
paused_stream = False paused_stream = False
logging.debug("StreamingCamera: Applying config:") logging.debug("PiCameraStreamer: Applying config:")
logging.debug(config) logging.debug(config)
with self.lock: with self.lock:
@ -89,7 +89,7 @@ class StreamingCamera(BaseCamera):
# Apply valid config params to Picamera object # Apply valid config params to Picamera object
if not self.state['record_active']: # If not recording a video if not self.state['record_active']: # If not recording a video
# StreamingCamera parameters # PiCameraStreamer parameters
for key, value in config.items(): # For each provided setting for key, value in config.items(): # For each provided setting
if hasattr(self, key): if hasattr(self, key):
setattr(self, key, value) setattr(self, key, value)

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Raspberry Pi camera implementation of the StreamingCamera class. Raspberry Pi camera implementation of the PiCameraStreamer class.
NOTES: NOTES:
Still port used for image capture Still port used for image capture
@ -12,7 +12,7 @@ Video port:
Splitter port 2: Video capture Splitter port 2: Video capture
Splitter port 3: [Currently unused] Splitter port 3: [Currently unused]
StreamingCamera streams at video_resolution PiCameraStreamer streams at video_resolution
Camera capture resolution set to stream_resolution in frames() Camera capture resolution set to stream_resolution in frames()
Video port uses that resolution for everything. If a different resolution Video port uses that resolution for everything. If a different resolution
is specified for video capture, this is handled by the resizer. is specified for video capture, this is handled by the resizer.
@ -41,8 +41,8 @@ from .set_picamera_gain import set_analog_gain, set_digital_gain
# MAIN CLASS # MAIN CLASS
class StreamingCamera(BaseCamera): class PiCameraStreamer(BaseCamera):
"""Raspberry Pi camera implementation of StreamingCamera.""" """Raspberry Pi camera implementation of PiCameraStreamer."""
picamera_settings_keys = [ picamera_settings_keys = [
'exposure_mode', 'exposure_mode',
'analog_gain', 'analog_gain',
@ -61,7 +61,7 @@ class StreamingCamera(BaseCamera):
# Attach to Pi camera # Attach to Pi camera
self.camera = picamera.PiCamera() #: :py:class:`picamera.PiCamera`: Picamera object self.camera = picamera.PiCamera() #: :py:class:`picamera.PiCamera`: Picamera object
# Store state of StreamingCamera # Store state of PiCameraStreamer
self.state.update({ self.state.update({
'stream_active': False, 'stream_active': False,
'record_active': False, 'record_active': False,
@ -87,7 +87,7 @@ class StreamingCamera(BaseCamera):
pass pass
def close(self): def close(self):
"""Close the Raspberry Pi StreamingCamera.""" """Close the Raspberry Pi PiCameraStreamer."""
# Run BaseCamera close method # Run BaseCamera close method
BaseCamera.close(self) BaseCamera.close(self)
# Detach Pi camera # Detach Pi camera
@ -97,7 +97,7 @@ class StreamingCamera(BaseCamera):
# HANDLE SETTINGS # HANDLE SETTINGS
def read_config(self) -> dict: def read_config(self) -> dict:
""" """
Return config dictionary of the StreamingCamera. Return config dictionary of the PiCameraStreamer.
""" """
conf_dict = { conf_dict = {
@ -109,7 +109,7 @@ class StreamingCamera(BaseCamera):
} }
# PiCamera parameters # PiCamera parameters
for key in StreamingCamera.picamera_settings_keys: for key in PiCameraStreamer.picamera_settings_keys:
try: try:
value = getattr(self.camera, key) value = getattr(self.camera, key)
logging.debug("Reading PiCamera().{}: {}".format(key, value)) logging.debug("Reading PiCamera().{}: {}".format(key, value))
@ -121,7 +121,7 @@ class StreamingCamera(BaseCamera):
def apply_config(self, config: dict): def apply_config(self, config: dict):
""" """
Write a config dictionary to the StreamingCamera config. Write a config dictionary to the PiCameraStreamer config.
The passed dictionary may contain other parameters not relevant to The passed dictionary may contain other parameters not relevant to
camera config. Eg. Passing a general config file will work fine. camera config. Eg. Passing a general config file will work fine.
@ -132,7 +132,7 @@ class StreamingCamera(BaseCamera):
# TODO: Include timing and batching logic when applying PiCamera settings # TODO: Include timing and batching logic when applying PiCamera settings
paused_stream = False paused_stream = False
logging.debug("StreamingCamera: Applying config:") logging.debug("PiCameraStreamer: Applying config:")
logging.debug(config) logging.debug(config)
with self.lock: with self.lock:
@ -150,7 +150,7 @@ class StreamingCamera(BaseCamera):
if 'picamera_settings' in config: # If new settings are given if 'picamera_settings' in config: # If new settings are given
self.apply_picamera_settings(config['picamera_settings'], pause_for_effect=True) self.apply_picamera_settings(config['picamera_settings'], pause_for_effect=True)
# StreamingCamera parameters # PiCameraStreamer parameters
for key, value in config.items(): # For each provided setting for key, value in config.items(): # For each provided setting
if (key != 'picamera_settings') and hasattr(self, key): if (key != 'picamera_settings') and hasattr(self, key):
setattr(self, key, value) setattr(self, key, value)

View file

@ -194,13 +194,6 @@ class SangaStage(BaseStage):
"""De-energise the stepper motor coils""" """De-energise the stepper motor coils"""
self.board.release_motors() self.board.release_motors()
def __del__(self):
"""Close the port when the object is deleted
NB if the object is created in a with statement, this will cause
the port to be closed at the end of the with block."""
self.close()
def __enter__(self): def __enter__(self):
"""When we use this in a with statement, remember where we started.""" """When we use this in a with statement, remember where we started."""
self._position_on_enter = self.position self._position_on_enter = self.position

View file

@ -26,8 +26,8 @@ from serial import PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPA
from serial import STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO from serial import STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO
import io import io
import time import time
import warnings
import logging import logging
import warnings
class ExtensibleSerialInstrument(object): class ExtensibleSerialInstrument(object):
""" """
@ -54,7 +54,7 @@ class ExtensibleSerialInstrument(object):
ignore_echo = False ignore_echo = False
port_settings = {} port_settings = {}
def __init__(self, port, **kwargs): def __init__(self, port=None, **kwargs):
""" """
Set up the serial port and so on. Set up the serial port and so on.
""" """
@ -64,7 +64,7 @@ class ExtensibleSerialInstrument(object):
self.open(port, False) # Eventually this shouldn't rely on init... self.open(port, False) # Eventually this shouldn't rely on init...
logging.info("Opened ESI connection to port {}".format(port)) logging.info("Opened ESI connection to port {}".format(port))
def open(self, port, quiet=True): def open(self, port=None, quiet=True):
"""Open communications with the serial port. """Open communications with the serial port.
If no port is specified, it will attempt to autodetect. If quiet=True If no port is specified, it will attempt to autodetect. If quiet=True
@ -74,6 +74,8 @@ class ExtensibleSerialInstrument(object):
if hasattr(self,'_ser') and self._ser.isOpen(): if hasattr(self,'_ser') and self._ser.isOpen():
if not quiet: logging.warning("Attempted to open an already-open port!") if not quiet: logging.warning("Attempted to open an already-open port!")
return return
if port is None:
port=self.find_port()
assert port is not None, "We don't have a serial port to open, meaning you didn't specify a valid port. Are you sure the instrument is connected?" assert port is not None, "We don't have a serial port to open, meaning you didn't specify a valid port. Are you sure the instrument is connected?"
self._ser = serial.Serial(port,**self.port_settings) self._ser = serial.Serial(port,**self.port_settings)
#the block above wraps the serial IO layer with a text IO layer #the block above wraps the serial IO layer with a text IO layer
@ -82,28 +84,33 @@ class ExtensibleSerialInstrument(object):
assert self.test_communications(), "The instrument doesn't seem to be responding. Did you specify the right port?" assert self.test_communications(), "The instrument doesn't seem to be responding. Did you specify the right port?"
def close(self): def close(self):
"""Release the serial port""" """Cleanly close the device. Includes proper logging statements."""
logging.debug("Closing serial connection") logging.debug("Closing serial connection")
with self.communications_lock: with self.communications_lock:
try: try:
self._ser.close() self._ser.close()
except Exception as e: except Exception as e:
logging.warning("The serial port didn't close cleanly:", e) logging.warning("The serial port didn't close cleanly: {}".format(e))
logging.debug("Connection closed") logging.debug("Connection closed")
def __del__(self): def __del__(self):
"""Close the port when the object is deleted """Emergency close the device. Try to avoid having to use this."""
if hasattr(self, '_ser') and self._ser.isOpen():
NB if the object is created in a with statement, this will cause with self.communications_lock:
the port to be closed at the end of the with block.""" print(
self.close() "Closing an open serial communication has been triggered by garbage collection!\n"\
"Please close this device more sensibly in future.")
try:
self._ser.close()
except Exception as e:
print("The serial port didn't close cleanly: {}".format(e))
def __enter__(self): def __enter__(self):
return self return self
def __exit__(self, type, value, traceback): def __exit__(self, type, value, traceback):
"""Close down the instrument. This happens in __del__ though.""" """Cleanly close down the instrument at end of a with block."""
pass self.close()
def write(self,query_string): def write(self,query_string):
"""Write a string to the serial port""" """Write a string to the serial port"""
@ -232,7 +239,7 @@ class ExtensibleSerialInstrument(object):
reply = self.readline().strip() reply = self.readline().strip()
res=re.search(response_regex, reply, flags=re_flags) res=re.search(response_regex, reply, flags=re_flags)
if res is not None: if res is not None:
warnings.warn("Query suceeded after initially receieving unmatched response ('%s') to '%s'. Match pattern /%s/ (generated regex /%s/)"%(original_reply, query_string, response_string, response_regex),RuntimeWarning) warnings.warn("Query suceeded after initially receieving unmatched response ('%s') to '%s'. Match pattern /%s/ (generated regex /%s/)"%(original_reply, query_string, response_string, response_regex), RuntimeWarning)
else: else:
raise ValueError("Stage response to '%s' ('%s') wasn't matched by /%s/ (generated regex /%s/)" % (query_string, original_reply, response_string, response_regex)) raise ValueError("Stage response to '%s' ('%s') wasn't matched by /%s/ (generated regex /%s/)" % (query_string, original_reply, response_string, response_regex))
try: try:
@ -242,9 +249,8 @@ class ExtensibleSerialInstrument(object):
else: else:
return parsed_result return parsed_result
except ValueError: except ValueError:
logging.error("Parsing Error") logging.info("Matched Groups: {}".format(res.groups()))
logging.info("Matched Groups:", res.groups()) logging.info("Parsing Functions {}:".format(parse_function))
logging.info("Parsing Functions:", parse_function)
raise ValueError("Stage response to %s ('%s') couldn't be parsed by the supplied function" % (query_string, reply)) raise ValueError("Stage response to %s ('%s') couldn't be parsed by the supplied function" % (query_string, reply))
def int_query(self, query_string, **kwargs): def int_query(self, query_string, **kwargs):
"""Perform a query and return the result(s) as integer(s) (see parsedQuery)""" """Perform a query and return the result(s) as integer(s) (see parsedQuery)"""
@ -262,6 +268,31 @@ class ExtensibleSerialInstrument(object):
with self.communications_lock: with self.communications_lock:
return True return True
def find_port(self):
"""Iterate through the available serial ports and query them to see
if our instrument is there."""
with self.communications_lock:
success = False
for port_name, _, _ in serial.tools.list_ports.comports(): #loop through serial ports, apparently 256 is the limit?!
try:
logging.info("Trying port {}".format(port_name))
self.open(port_name)
success = True
logging.info("Success!")
except:
pass
finally:
try:
self.close()
except:
pass #we don't care if there's an error closing the port...
if success:
break #again, make sure this happens *after* closing the port
if success:
return port_name
else:
return None
class OptionalModule(object): class OptionalModule(object):
"""This allows a `ExtensibleSerialInstrument` to have optional features. """This allows a `ExtensibleSerialInstrument` to have optional features.

View file

@ -81,13 +81,8 @@ class Sangaboard(ExtensibleSerialInstrument):
it doesn't need to be named. it doesn't need to be named.
""" """
# If no port is specified
if not port:
# Scan all available ports, and check for valid firmware
scanned_port = self.scan_ports()
# Initialise basic serial instrument with specified # Initialise basic serial instrument with specified
ExtensibleSerialInstrument.__init__(self, scanned_port, **kwargs) ExtensibleSerialInstrument.__init__(self, port, **kwargs)
try: try:
# Make absolutely sure that whatever port we're on is valid # Make absolutely sure that whatever port we're on is valid
@ -119,37 +114,11 @@ class Sangaboard(ExtensibleSerialInstrument):
logging.error("You may need to update the firmware running on the Sangaboard.") logging.error("You may need to update the firmware running on the Sangaboard.")
raise e raise e
def scan_ports(self): def test_communications(self):
"""Iterate through the available serial ports and query them to see """
if our instrument is there.""" Overrides superclass, used in self.open(), and port scanning
logging.debug("Running Sangaboard port scanner") """
with self.communications_lock: return self.check_valid_firmware()
success = False
for port_name, _, _ in serial.tools.list_ports.comports(): #loop through serial ports, apparently 256 is the limit?!
try:
logging.info("Trying port {}".format(port_name))
self.open(port_name)
success = True
logging.info("Checking firmware")
fw = self.check_valid_firmware()
if not fw:
success = False
except Exception as e:
logging.warning("Error on port {}".format(port_name))
logging.warning(e)
pass
finally:
try:
self.close()
except:
pass #we don't care if there's an error closing the port...
if success:
break #again, make sure this happens *after* closing the port
if success:
return port_name
else:
return None
def check_valid_firmware(self): def check_valid_firmware(self):
logging.debug("Running firmware checks") logging.debug("Running firmware checks")

12
poetry.lock generated
View file

@ -180,6 +180,7 @@ version = "1.13.1b0"
reference = "b39c2b6e42f5f7f57bb46eafcb5c9e2bbdb5d0cb" reference = "b39c2b6e42f5f7f57bb46eafcb5c9e2bbdb5d0cb"
type = "git" type = "git"
url = "https://github.com/rwb27/picamera.git" url = "https://github.com/rwb27/picamera.git"
[[package]] [[package]]
category = "main" category = "main"
description = "Python Imaging Library (Fork)" description = "Python Imaging Library (Fork)"
@ -242,6 +243,14 @@ chardet = ">=3.0.2,<3.1.0"
idna = ">=2.5,<2.9" idna = ">=2.5,<2.9"
urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
[[package]]
category = "dev"
description = "a python refactoring library..."
name = "rope"
optional = false
python-versions = "*"
version = "0.14.0"
[[package]] [[package]]
category = "main" category = "main"
description = "A module to control Raspberry Pi GPIO channels" description = "A module to control Raspberry Pi GPIO channels"
@ -381,7 +390,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.15.4" version = "0.15.4"
[metadata] [metadata]
content-hash = "33585d30bda3df47511879542dd7c778bb1922947b650a5554c231a0be7c8b4a" content-hash = "c2fee81479442877ae94095d15f05018dc5290eee42edeb7d79d067fdf0ffab6"
python-versions = "^3.5" python-versions = "^3.5"
[metadata.hashes] [metadata.hashes]
@ -411,6 +420,7 @@ pyserial = ["6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627",
pytz = ["303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", "d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141"] pytz = ["303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", "d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141"]
pyyaml = ["3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", "3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", "40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", "558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", "a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", "aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", "bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", "d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", "d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", "e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", "e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"] pyyaml = ["3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", "3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", "40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", "558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", "a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", "aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", "bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", "d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", "d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", "e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", "e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"]
requests = ["11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"] requests = ["11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"]
rope = ["6b728fdc3e98a83446c27a91fc5d56808a004f8beab7a31ab1d7224cecc7d969", "c5c5a6a87f7b1a2095fb311135e2a3d1f194f5ecb96900fdd0a9100881f48aaf", "f0dcf719b63200d492b85535ebe5ea9b29e0d0b8aebeb87fe03fc1a65924fdaf"]
"rpi.gpio" = ["a4210ad63bfe844e43995286de0d3950dfacfa0f3799bb9392770ac54a7d2e47"] "rpi.gpio" = ["a4210ad63bfe844e43995286de0d3950dfacfa0f3799bb9392770ac54a7d2e47"]
scipy = ["03b1e0775edbe6a4c64effb05fff2ce1429b76d29d754aa5ee2d848b60033351", "09d008237baabf52a5d4f5a6fcf9b3c03408f3f61a69c404472a16861a73917e", "10325f0ffac2400b1ec09537b7e403419dcd25d9fee602a44e8a32119af9079e", "1db9f964ed9c52dc5bd6127f0dd90ac89791daa690a5665cc01eae185912e1ba", "409846be9d6bdcbd78b9e5afe2f64b2da5a923dd7c1cd0615ce589489533fdbb", "4907040f62b91c2e170359c3d36c000af783f0fa1516a83d6c1517cde0af5340", "6c0543f2fdd38dee631fb023c0f31c284a532d205590b393d72009c14847f5b1", "826b9f5fbb7f908a13aa1efd4b7321e36992f5868d5d8311c7b40cf9b11ca0e7", "a7695a378c2ce402405ea37b12c7a338a8755e081869bd6b95858893ceb617ae", "a84c31e8409b420c3ca57fd30c7589378d6fdc8d155d866a7f8e6e80dec6fd06", "adadeeae5500de0da2b9e8dd478520d0a9945b577b2198f2462555e68f58e7ef", "b283a76a83fe463c9587a2c88003f800e08c3929dfbeba833b78260f9c209785", "c19a7389ab3cd712058a8c3c9ffd8d27a57f3d84b9c91a931f542682bb3d269d", "c3bb4bd2aca82fb498247deeac12265921fe231502a6bc6edea3ee7fe6c40a7a", "c5ea60ece0c0c1c849025bfc541b60a6751b491b6f11dd9ef37ab5b8c9041921", "db61a640ca20f237317d27bc658c1fc54c7581ff7f6502d112922dc285bdabee"] scipy = ["03b1e0775edbe6a4c64effb05fff2ce1429b76d29d754aa5ee2d848b60033351", "09d008237baabf52a5d4f5a6fcf9b3c03408f3f61a69c404472a16861a73917e", "10325f0ffac2400b1ec09537b7e403419dcd25d9fee602a44e8a32119af9079e", "1db9f964ed9c52dc5bd6127f0dd90ac89791daa690a5665cc01eae185912e1ba", "409846be9d6bdcbd78b9e5afe2f64b2da5a923dd7c1cd0615ce589489533fdbb", "4907040f62b91c2e170359c3d36c000af783f0fa1516a83d6c1517cde0af5340", "6c0543f2fdd38dee631fb023c0f31c284a532d205590b393d72009c14847f5b1", "826b9f5fbb7f908a13aa1efd4b7321e36992f5868d5d8311c7b40cf9b11ca0e7", "a7695a378c2ce402405ea37b12c7a338a8755e081869bd6b95858893ceb617ae", "a84c31e8409b420c3ca57fd30c7589378d6fdc8d155d866a7f8e6e80dec6fd06", "adadeeae5500de0da2b9e8dd478520d0a9945b577b2198f2462555e68f58e7ef", "b283a76a83fe463c9587a2c88003f800e08c3929dfbeba833b78260f9c209785", "c19a7389ab3cd712058a8c3c9ffd8d27a57f3d84b9c91a931f542682bb3d269d", "c3bb4bd2aca82fb498247deeac12265921fe231502a6bc6edea3ee7fe6c40a7a", "c5ea60ece0c0c1c849025bfc541b60a6751b491b6f11dd9ef37ab5b8c9041921", "db61a640ca20f237317d27bc658c1fc54c7581ff7f6502d112922dc285bdabee"]
six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"] six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"]

View file

@ -4,7 +4,7 @@ build-backend = "poetry.masonry.api"
[tool.poetry] [tool.poetry]
name = "openflexure_microscope" name = "openflexure_microscope"
version = "1.1.0" version = "1.1.1"
description = "Python module, and Flask-based web API, to run the OpenFlexure Microscope." description = "Python module, and Flask-based web API, to run the OpenFlexure Microscope."
authors = [ authors = [
@ -37,3 +37,4 @@ picamera = { git = "https://github.com/rwb27/picamera.git", branch = "lens-shadi
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
sphinxcontrib-httpdomain = "^1.7" sphinxcontrib-httpdomain = "^1.7"
rope = "^0.14.0"

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
from openflexure_microscope.camera.pi import StreamingCamera, CaptureObject from openflexure_microscope.camera.pi import PiCameraStreamer, CaptureObject
import os import os
import io import io
@ -279,7 +279,7 @@ class TestThreadStarting(unittest.TestCase):
if __name__ == '__main__': if __name__ == '__main__':
with StreamingCamera() as camera: with PiCameraStreamer() as camera:
suites = [ suites = [
unittest.TestLoader().loadTestsFromTestCase(TestCaptureMethods), unittest.TestLoader().loadTestsFromTestCase(TestCaptureMethods),

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
from openflexure_microscope.camera.pi import StreamingCamera from openflexure_microscope.camera.pi import PiCameraStreamer
from openflexure_stage import OpenFlexureStage from openflexure_stage import OpenFlexureStage
from openflexure_microscope import Microscope, config from openflexure_microscope import Microscope, config
@ -31,7 +31,9 @@ class TestPluginMethods(unittest.TestCase):
if __name__ == '__main__': if __name__ == '__main__':
with Microscope(StreamingCamera(), OpenFlexureStage("/dev/ttyUSB0")) as microscope: with Microscope() as microscope:
microscope.attach(PiCameraStreamer(), OpenFlexureStage())
microscope.plugin.attach("openflexure_microscope.plugins.testing:Plugin") microscope.plugin.attach("openflexure_microscope.plugins.testing:Plugin")