Added support for a fully mocked device

This commit is contained in:
jtc42 2019-06-28 12:52:55 +01:00
parent eaa37f70e0
commit 4733b250ed
12 changed files with 283 additions and 41 deletions

View file

@ -12,14 +12,17 @@ from openflexure_microscope.api.exceptions import JSONExceptionHandler
from openflexure_microscope.api.utilities import list_routes from openflexure_microscope.api.utilities import list_routes
from openflexure_microscope import Microscope from openflexure_microscope import Microscope
from openflexure_microscope.camera.pi import StreamingCamera
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.sanga import SangaStage
from openflexure_microscope.stage.mock import MockStage 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 time
@ -101,7 +104,7 @@ def attach_microscope():
# TODO: Tidy this up. api_stage may be referenced before assignment. Use some form of Maybe monad? # 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 (SerialException, OSError) 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()

View file

@ -1,3 +0,0 @@
__all__ = ['pi', 'base']
from . import pi, base

View file

@ -167,15 +167,6 @@ class BaseCamera(metaclass=ABCMeta):
self.stop_worker() self.stop_worker()
logging.info("Closed {}".format(self)) 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 # START AND STOP WORKER THREAD
def start_worker(self, timeout: int = 5) -> bool: def start_worker(self, timeout: int = 5) -> bool:

View file

@ -0,0 +1,217 @@
# -*- 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")

View file

@ -529,6 +529,15 @@ class StreamingCamera(BaseCamera):
# HANDLE STREAM FRAMES # 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): def frames(self):
""" """
Create generator that returns frames from the camera. Create generator that returns frames from the camera.

View file

@ -1,8 +1,8 @@
import numpy as np import numpy as np
from picamera import PiCamera
from picamera.array import PiRGBArray, PiBayerArray
import time import time
from picamera import PiCamera
from picamera.array import PiRGBArray, PiBayerArray
def rgb_image(camera, resize=None, **kwargs): def rgb_image(camera, resize=None, **kwargs):
"""Capture an image and return an RGB numpy array""" """Capture an image and return an RGB numpy array"""

View file

@ -78,8 +78,13 @@ def load_plugin_module(plugin_path):
# If the loader was found (i.e. plugin probably exists) # If the loader was found (i.e. plugin probably exists)
if check_module(plugin_path): if check_module(plugin_path):
plugin_module = importlib.import_module(plugin_path) try:
plugin_name = name_from_module(plugin_path) plugin_module = importlib.import_module(plugin_path)
plugin_name = name_from_module(plugin_path)
except Exception as e:
logging.error("Error loading plugin.")
logging.error(e)
return None, None
# If no loader was found, try finding a file from path # If no loader was found, try finding a file from path
else: else:
@ -156,24 +161,28 @@ class PluginMount(object):
""" """
plugin_class, plugin_name = class_from_map(plugin_map) plugin_class, plugin_name = class_from_map(plugin_map)
pythonsafe_plugin_name = plugin_name.replace("/", "_") if plugin_class is not None:
if plugin_class and plugin_name: pythonsafe_plugin_name = plugin_name.replace("/", "_")
plugin_object = plugin_class()
if hasattr(self, plugin_name): # If a plugin with the same name is already attached. if plugin_class and plugin_name:
logging.warning(ConColors.WARNING + "A plugin named {} has already been loaded. Skipping {}.".format(plugin_name, plugin_map) + ConColors.ENDC) plugin_object = plugin_class()
elif isinstance(plugin_object, MicroscopePlugin): # If plugin_object is an instance of MicroscopePlugin if hasattr(self, plugin_name): # If a plugin with the same name is already attached.
# Attach plugin_object to the plugin mount logging.warning(ConColors.WARNING + "A plugin named {} has already been loaded. Skipping {}.".format(plugin_name, plugin_map) + ConColors.ENDC)
setattr(self, pythonsafe_plugin_name, plugin_object)
self.plugins.append((plugin_name, plugin_object))
# Grant plugin access to the hardware elif isinstance(plugin_object, MicroscopePlugin): # If plugin_object is an instance of MicroscopePlugin
plugin_object.microscope = self.parent # Attach plugin_object to the plugin mount
setattr(self, pythonsafe_plugin_name, plugin_object)
self.plugins.append((plugin_name, plugin_object))
logging.info(ConColors.OKGREEN + "Plugin {} loaded as {}.".format(plugin_map, plugin_name) + ConColors.ENDC) # Grant plugin access to the hardware
plugin_object.microscope = self.parent
logging.info(ConColors.OKGREEN + "Plugin {} loaded as {}.".format(plugin_map, plugin_name) + ConColors.ENDC)
else:
logging.error("Error loading plugin. Moving on.")
class MicroscopePlugin: class MicroscopePlugin:
""" """

View file

@ -28,6 +28,7 @@ from serial import STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO
import io import io
import time import time
import warnings import warnings
import logging
class ExtensibleSerialInstrument(object): class ExtensibleSerialInstrument(object):
""" """
@ -58,8 +59,11 @@ class ExtensibleSerialInstrument(object):
""" """
Set up the serial port and so on. Set up the serial port and so on.
""" """
logging.info("Updating BSI port settings")
self.port_settings.update(kwargs) self.port_settings.update(kwargs)
logging.info("Opening BSI connection to port {}".format(port))
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 BSI connection to port {}".format(port))
def open(self, port=None, quiet=True): def open(self, port=None, quiet=True):
"""Open communications with the serial port. """Open communications with the serial port.
@ -81,11 +85,13 @@ class ExtensibleSerialInstrument(object):
def close(self): def close(self):
"""Release the serial port""" """Release the serial port"""
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:
print("The serial port didn't close cleanly:", e) print("The serial port didn't close cleanly:", e)
logging.debug("Connection closed")
def __del__(self): def __del__(self):
"""Close the port when the object is deleted """Close the port when the object is deleted

View file

@ -12,6 +12,7 @@ import time
from .extensible_serial_instrument import ExtensibleSerialInstrument, OptionalModule, QueriedProperty, EIGHTBITS, PARITY_NONE, STOPBITS_ONE from .extensible_serial_instrument import ExtensibleSerialInstrument, OptionalModule, QueriedProperty, EIGHTBITS, PARITY_NONE, STOPBITS_ONE
import re import re
import warnings import warnings
import logging
class Sangaboard(ExtensibleSerialInstrument): class Sangaboard(ExtensibleSerialInstrument):
"""Class managing serial communications with a Sangaboard """Class managing serial communications with a Sangaboard
@ -68,6 +69,7 @@ class Sangaboard(ExtensibleSerialInstrument):
""" """
super(Sangaboard, self).__init__(port, **kwargs) super(Sangaboard, self).__init__(port, **kwargs)
try: try:
logging.debug("Running firmware checks")
# Request firmware version from the board # Request firmware version from the board
self.firmware = self.query("version",timeout=2).rstrip() self.firmware = self.query("version",timeout=2).rstrip()
# The slightly complicated regexp below will match the version string, # The slightly complicated regexp below will match the version string,

16
poetry.lock generated
View file

@ -255,11 +255,11 @@ category = "main"
description = "SciPy: Scientific Library for Python" description = "SciPy: Scientific Library for Python"
name = "scipy" name = "scipy"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" python-versions = ">=3.5"
version = "1.2.1" version = "1.3.0"
[package.dependencies] [package.dependencies]
numpy = ">=1.8.2" numpy = ">=1.13.3"
[[package]] [[package]]
category = "main" category = "main"
@ -271,11 +271,11 @@ version = "1.12.0"
[[package]] [[package]]
category = "dev" category = "dev"
description = "This package provides 16 stemmer algorithms (15 + Poerter English stemmer) generated from Snowball algorithms." description = "This package provides 23 stemmers for 22 languages generated from Snowball algorithms."
name = "snowballstemmer" name = "snowballstemmer"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "1.2.1" version = "1.9.0"
[[package]] [[package]]
category = "dev" category = "dev"
@ -381,7 +381,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 = "14a52bee3012c7880e4c87e940639606eb88ad7320e262d5228566b6df1ef601" content-hash = "ec0d5aa30422b8f44fb92e8e818120d4f0e1b084773594ce2531e31db5af26f4"
python-versions = "^3.5" python-versions = "^3.5"
[metadata.hashes] [metadata.hashes]
@ -412,9 +412,9 @@ pytz = ["303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", "d74
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"]
"rpi.gpio" = ["a4210ad63bfe844e43995286de0d3950dfacfa0f3799bb9392770ac54a7d2e47"] "rpi.gpio" = ["a4210ad63bfe844e43995286de0d3950dfacfa0f3799bb9392770ac54a7d2e47"]
scipy = ["014cb900c003b5ac81a53f2403294e8ecf37aedc315b59a6b9370dce0aa7627a", "281a34da34a5e0de42d26aed692ab710141cad9d5d218b20643a9cb538ace976", "588f9cc4bfab04c45fbd19c1354b5ade377a8124d6151d511c83730a9b6b2338", "5a10661accd36b6e2e8855addcf3d675d6222006a15795420a39c040362def66", "628f60be272512ca1123524969649a8cb5ae8b31cca349f7c6f8903daf9034d7", "6dcc43a88e25b815c2dea1c6fac7339779fc988f5df8396e1de01610604a7c38", "70e37cec0ac0fe95c85b74ca4e0620169590fd5d3f44765f3c3a532cedb0e5fd", "7274735fb6fb5d67d3789ddec2cd53ed6362539b41aa6cc0d33a06c003aaa390", "78e12972e144da47326958ac40c2bd1c1cca908edc8b01c26a36f9ffd3dce466", "790cbd3c8d09f3a6d9c47c4558841e25bac34eb7a0864a9def8f26be0b8706af", "79792c8fe8e9d06ebc50fe23266522c8c89f20aa94ac8e80472917ecdce1e5ba", "865afedf35aaef6df6344bee0de391ee5e99d6e802950a237f9fb9b13e441f91", "870fd401ec7b64a895cff8e206ee16569158db00254b2f7157b4c9a5db72c722", "963815c226b29b0176d5e3d37fc9de46e2778ce4636a5a7af11a48122ef2577c", "9726791484f08e394af0b59eb80489ad94d0a53bbb58ab1837dcad4d58489863", "9de84a71bb7979aa8c089c4fb0ea0e2ed3917df3fb2a287a41aaea54bbad7f5d", "b2c324ddc5d6dbd3f13680ad16a29425841876a84a1de23a984236d1afff4fa6", "b86ae13c597fca087cb8c193870507c8916cefb21e52e1897da320b5a35075e5", "ba0488d4dbba2af5bf9596b849873102d612e49a118c512d9d302ceafa36e01a", "d78702af4102a3a4e23bb7372cec283e78f32f5573d92091aa6aaba870370fe1", "def0e5d681dd3eb562b059d355ae8bebe27f5cc455ab7c2b6655586b63d3a8ea", "e085d1babcb419bbe58e2e805ac61924dac4ca45a07c9fa081144739e500aa3c", "e2cfcbab37c082a5087aba5ff00209999053260441caadd4f0e8f4c2d6b72088", "e742f1f5dcaf222e8471c37ee3d1fd561568a16bb52e031c25674ff1cf9702d5", "f06819b028b8ef9010281e74c59cb35483933583043091ed6b261bb1540f11cc", "f15f2d60a11c306de7700ee9f65df7e9e463848dbea9c8051e293b704038da60", "f31338ee269d201abe76083a990905473987371ff6f3fdb76a3f9073a361cf37", "f6b88c8d302c3dac8dff7766955e38d670c82e0d79edfc7eae47d6bb2c186594"] scipy = ["03b1e0775edbe6a4c64effb05fff2ce1429b76d29d754aa5ee2d848b60033351", "09d008237baabf52a5d4f5a6fcf9b3c03408f3f61a69c404472a16861a73917e", "10325f0ffac2400b1ec09537b7e403419dcd25d9fee602a44e8a32119af9079e", "1db9f964ed9c52dc5bd6127f0dd90ac89791daa690a5665cc01eae185912e1ba", "409846be9d6bdcbd78b9e5afe2f64b2da5a923dd7c1cd0615ce589489533fdbb", "4907040f62b91c2e170359c3d36c000af783f0fa1516a83d6c1517cde0af5340", "6c0543f2fdd38dee631fb023c0f31c284a532d205590b393d72009c14847f5b1", "826b9f5fbb7f908a13aa1efd4b7321e36992f5868d5d8311c7b40cf9b11ca0e7", "a7695a378c2ce402405ea37b12c7a338a8755e081869bd6b95858893ceb617ae", "a84c31e8409b420c3ca57fd30c7589378d6fdc8d155d866a7f8e6e80dec6fd06", "adadeeae5500de0da2b9e8dd478520d0a9945b577b2198f2462555e68f58e7ef", "b283a76a83fe463c9587a2c88003f800e08c3929dfbeba833b78260f9c209785", "c19a7389ab3cd712058a8c3c9ffd8d27a57f3d84b9c91a931f542682bb3d269d", "c3bb4bd2aca82fb498247deeac12265921fe231502a6bc6edea3ee7fe6c40a7a", "c5ea60ece0c0c1c849025bfc541b60a6751b491b6f11dd9ef37ab5b8c9041921", "db61a640ca20f237317d27bc658c1fc54c7581ff7f6502d112922dc285bdabee"]
six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"] six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"]
snowballstemmer = ["919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128", "9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89"] snowballstemmer = ["9f3b9ffe0809d174f7047e121431acf99c89a7040f0ca84f94ba53a498e6d0c9"]
sphinx = ["22538e1bbe62b407cf5a8aabe1bb15848aa66bb79559f42f5202bbce6b757a69", "f9a79e746b87921cabc3baa375199c6076d1270cee53915dbd24fdbeaaacc427"] sphinx = ["22538e1bbe62b407cf5a8aabe1bb15848aa66bb79559f42f5202bbce6b757a69", "f9a79e746b87921cabc3baa375199c6076d1270cee53915dbd24fdbeaaacc427"]
sphinxcontrib-applehelp = ["edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", "fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"] sphinxcontrib-applehelp = ["edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", "fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"]
sphinxcontrib-devhelp = ["6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", "9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"] sphinxcontrib-devhelp = ["6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", "9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"]

View file

@ -28,12 +28,12 @@ Flask = "^1.0"
pyyaml = "^3.13" pyyaml = "^3.13"
numpy = "1.16.4" # Exact version until we can guarantee a wheel numpy = "1.16.4" # Exact version until we can guarantee a wheel
Pillow = "^5.4" Pillow = "^5.4"
"RPi.GPIO" = "^0.6.5"
openflexure-stage = "^0.2.1" openflexure-stage = "^0.2.1"
flask-cors = "^3.0" flask-cors = "^3.0"
scipy = "1.3.0" # Exact version until we can guarantee a wheel scipy = "1.3.0" # Exact version until we can guarantee a wheel
picamera = { git = "https://github.com/rwb27/picamera.git", branch = "lens-shading" } # Lens shading requires >1.14 or RWB fork, lens-shading branch. picamera = { git = "https://github.com/rwb27/picamera.git", branch = "lens-shading" } # Lens shading requires >1.14 or RWB fork, lens-shading branch.
"RPi.GPIO" = { version = "^0.6.5", optional = true }
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
sphinxcontrib-httpdomain = "^1.7" sphinxcontrib-httpdomain = "^1.7"

8
test1.py Normal file
View file

@ -0,0 +1,8 @@
from openflexure_microscope.stage.sangaboard import Sangaboard
import logging
import sys
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
stage = Sangaboard()