Merge branch 'master' of https://gitlab.com/openflexure/openflexure-microscope-server
This commit is contained in:
commit
57832f94a8
10 changed files with 130 additions and 112 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)"""
|
||||||
|
|
@ -261,7 +267,32 @@ class ExtensibleSerialInstrument(object):
|
||||||
Usually this function sends a command and checks for a known reply."""
|
Usually this function sends a command and checks for a known reply."""
|
||||||
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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
12
poetry.lock
generated
|
|
@ -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"]
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue