Merge remote-tracking branch 'origin/master' into labthings-070

This commit is contained in:
Joel Collins 2020-07-03 18:13:20 +01:00
commit ab3c4b3745
7 changed files with 140 additions and 111 deletions

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
from gevent import monkey from labthings.server import monkey
monkey.patch_all() monkey.patch_all()

View file

@ -83,6 +83,7 @@ def capture(
annotations=annotations, annotations=annotations,
tags=tags, tags=tags,
metadata=metadata, metadata=metadata,
cache_key=folder
) )

View file

@ -10,7 +10,6 @@ import time
import numpy as np import numpy as np
from PIL import Image, ImageFont, ImageDraw from PIL import Image, ImageFont, ImageDraw
from datetime import datetime from datetime import datetime
import gevent
import logging import logging
@ -221,7 +220,7 @@ class MissingCamera(BaseCamera):
# While the iterator is not closed # While the iterator is not closed
try: try:
while True: while True:
gevent.sleep(1) # Only serve frames at 1fps time.sleep(1) # Only serve frames at 1fps
# Reset stream # Reset stream
self.stream.seek(0) self.stream.seek(0)
self.stream.truncate() self.stream.truncate()

View file

@ -10,9 +10,6 @@ from PIL import Image
import dateutil.parser import dateutil.parser
import atexit import atexit
from gevent.fileobject import FileObjectThread
import gevent
from collections import OrderedDict from collections import OrderedDict
from openflexure_microscope.camera import piexif from openflexure_microscope.camera import piexif
@ -129,10 +126,6 @@ class CaptureObject(object):
"""Create a new StreamObject, to manage capture data.""" """Create a new StreamObject, to manage capture data."""
# Stream for buffering capture data # Stream for buffering capture data
self.stream = io.BytesIO() self.stream = io.BytesIO()
# Event to notify when the stream has finished writing to disk
self.file_ready = gevent.event.Event()
# Access lock for the disk file
self.lock = gevent.lock.BoundedSemaphore()
# Store a nice ID # Store a nice ID
self.id = uuid.uuid4() #: str: Unique capture ID self.id = uuid.uuid4() #: str: Unique capture ID
@ -160,19 +153,12 @@ class CaptureObject(object):
def write(self, s): def write(self, s):
self.stream.write(s) self.stream.write(s)
def _stream_to_file(self):
with self.lock:
logging.info(f"Writing to disk {self.file}")
with FileObjectThread(open(self.file, "wb"), 'wb') as outfile:
outfile.write(self.stream.getbuffer())
self.stream.close()
self.file_ready.set()
logging.info(f"Finished writing to disk {self.file}")
def flush(self): def flush(self):
logging.debug(f"Flushing {self.file}") logging.info(f"Writing to disk {self.file}")
gevent.spawn(self._stream_to_file) with open(self.file, "wb") as outfile:
logging.debug(f"Returning flushing {self.file}") outfile.write(self.stream.getbuffer())
self.stream.close()
logging.info(f"Finished writing to disk {self.file}")
def open(self, mode): def open(self, mode):
return open(self.file, mode) return open(self.file, mode)
@ -275,20 +261,18 @@ class CaptureObject(object):
global EXIF_FORMATS global EXIF_FORMATS
if self.format.upper() in EXIF_FORMATS and self.exists: if self.format.upper() in EXIF_FORMATS and self.exists:
self.file_ready.wait() logging.debug("Writing exif data to capture file")
with self.lock: # Extract current Exif data
logging.debug("Writing exif data to capture file") exif_dict = piexif.load(self.file)
# Extract current Exif data # Serialize metadata
exif_dict = piexif.load(self.file) metadata_string = json.dumps(self.metadata, cls=JSONEncoder)
# Serialize metadata logging.debug(f"Saving metadata string to file: {metadata_string}")
metadata_string = json.dumps(self.metadata, cls=JSONEncoder) # Insert metadata into exif_dict
logging.debug(f"Saving metadata string to file: {metadata_string}") exif_dict["Exif"][piexif.ExifIFD.UserComment] = metadata_string.encode()
# Insert metadata into exif_dict # Convert new exif dict to exif bytes
exif_dict["Exif"][piexif.ExifIFD.UserComment] = metadata_string.encode() exif_bytes = piexif.dump(exif_dict)
# Convert new exif dict to exif bytes # Insert exif into file
exif_bytes = piexif.dump(exif_dict) piexif.insert(exif_bytes, self.file)
# Insert exif into file
piexif.insert(exif_bytes, self.file)
logging.info(f"Finished saving metadata to {self.file}") logging.info(f"Finished saving metadata to {self.file}")
@property @property

View file

@ -6,8 +6,7 @@ import logging
import pkg_resources import pkg_resources
import uuid import uuid
from typing import Tuple from typing import Tuple
from expiringdict import ExpiringDict
import gevent
from openflexure_microscope.captures import CaptureManager from openflexure_microscope.captures import CaptureManager
@ -61,6 +60,10 @@ class Microscope:
# Apply settings loaded from file # Apply settings loaded from file
self.update_settings(self.settings_file.load()) self.update_settings(self.settings_file.load())
# Data cache
self.configuration_cache = ExpiringDict(max_len=100, max_age_seconds=3600)
self.metadata_cache = ExpiringDict(max_len=100, max_age_seconds=3600)
def __enter__(self): def __enter__(self):
"""Create microscope on context enter.""" """Create microscope on context enter."""
return self return self
@ -237,8 +240,7 @@ class Microscope:
# Save config to file # Save config to file
self.settings_file.save(current_config, backup=True) self.settings_file.save(current_config, backup=True)
@property def force_get_configuration(self):
def configuration(self):
with self.lock: with self.lock:
initial_configuration = self.configuration_file.load() initial_configuration = self.configuration_file.load()
@ -262,37 +264,62 @@ class Microscope:
initial_configuration.update(current_configuration) initial_configuration.update(current_configuration)
return initial_configuration return initial_configuration
def get_configuration(self, cache_key=None):
if cache_key:
cached_config = self.configuration_cache.get(cache_key, None)
if cached_config:
return cached_config
else:
full_config = self.force_get_configuration()
self.configuration_cache[cache_key] = full_config
return full_config
return self.force_get_configuration()
@property @property
def metadata(self): def configuration(self):
return self.get_configuration()
def force_get_metadata(self):
""" """
Microscope system metadata, to be applied to basically all captures Read cachable bits of microscope metadata.
Currently ID, settings, and configuration can be cached
""" """
with Timer("Reading settings:"):
settings = self.read_settings(full=False)
with Timer("Reading state:"):
state = self.state
with Timer("Reading configuration:"):
configuration = self.configuration
system_metadata = { system_metadata = {
"id": self.id, "id": self.id,
"settings": settings, "settings": self.read_settings(full=False),
"state": state, "configuration": self.get_configuration(),
"configuration": configuration,
} }
return system_metadata return system_metadata
def add_metadata_to_capture(self, output, metadata, annotations, tags): def get_metadata(self, cache_key=None):
logging.debug(f"Waiting for {output.file}") """
# Wait for the file to be written to disk Read microscope metadata, with partial caching
with Timer("Waiting for file:"): """
output.file_ready.wait() metadata = {}
# Load cached bits of metadata
if cache_key:
logging.debug(f"Reading cached microscope metadata: {cache_key}")
metadata = self.metadata_cache.get(cache_key, None)
if not metadata:
logging.debug(f"Building and caching microscope metadata: {cache_key}")
metadata = self.force_get_metadata()
self.metadata_cache[cache_key] = metadata
else:
logging.debug(f"Building microscope metadata: {cache_key}")
metadata = self.force_get_metadata()
with Timer("Building metadata"): # Keys that should never be cached
full_metadata = {"instrument": self.metadata, **metadata} metadata.update({
with Timer("Writing metadata to file"): "state": self.state,
output.put_and_save(tags, annotations, full_metadata) })
logging.info(f"Finished injecting EXIF data into {output.file}")
return metadata
@property
def metadata(self):
return self.get_metadata()
def capture( def capture(
self, self,
@ -306,6 +333,7 @@ class Microscope:
annotations: dict = None, annotations: dict = None,
tags: list = None, tags: list = None,
metadata: dict = None, metadata: dict = None,
cache_key: str = None
): ):
logging.debug(f"Microscope capturing to {filename}") logging.debug(f"Microscope capturing to {filename}")
if not annotations: if not annotations:
@ -315,6 +343,10 @@ class Microscope:
if not tags: if not tags:
tags = [] tags = []
# Read metadata for capture
full_metadata = {"instrument": self.get_metadata(cache_key), **metadata}
# Do capture
with self.camera.lock: with self.camera.lock:
# Create output object # Create output object
output = self.captures.new_image( output = self.captures.new_image(
@ -322,7 +354,7 @@ class Microscope:
) )
# Capture to output object # Capture to output object
logging.info("Starting microscope capture...") logging.info(f"Starting microscope capture {filename}")
self.camera.capture( self.camera.capture(
output, output,
use_video_port=use_video_port, use_video_port=use_video_port,
@ -331,11 +363,7 @@ class Microscope:
fmt=fmt, fmt=fmt,
) )
# Gether metadata from hardware in a greenlet output.put_and_save(tags, annotations, full_metadata)
#gevent.get_hub().threadpool.spawn(self.add_metadata_to_capture, output, metadata, annotations, tags) logging.debug(f"Finished capture to {output.file}")
gevent.spawn(self.add_metadata_to_capture, output, metadata, annotations, tags)
#self.add_metadata_to_capture(output, metadata, annotations, tags)
logging.info(f"Finished capture to {output.file}")
return output return output

97
poetry.lock generated
View file

@ -91,25 +91,19 @@ d = ["aiohttp (>=3.3.2)"]
[[package]] [[package]]
category = "main" category = "main"
description = "" description = "Calibration and mapping between stage and camera coordinates in a microscope"
name = "camera-stage-mapping" name = "camera-stage-mapping"
optional = false optional = false
python-versions = "^3.6" python-versions = ">=3.6,<4.0"
version = "0.1.1" version = "0.1.2"
[package.dependencies] [package.dependencies]
numpy = "^1.17" numpy = ">=1.17,<2.0"
[package.extras] [package.extras]
all = ["opencv-python-headless (^4.1)", "scipy (^1.4)"] all = ["opencv-python-headless (>=4.1,<5.0)", "scipy (>=1.4,<2.0)"]
correlation = ["opencv-python-headless (^4.1)", "scipy (^1.4)"] correlation = ["opencv-python-headless (>=4.1,<5.0)", "scipy (>=1.4,<2.0)"]
ipython = []
ofmclient = []
[package.source]
reference = "f5d0d8977a2511b20b47061ac203551f97b8b4ab"
type = "git"
url = "https://gitlab.com/openflexure/microscope-extensions/camera-stage-mapping.git"
[[package]] [[package]]
category = "main" category = "main"
description = "Pure Python CBOR (de)serializer with extensive tag support" description = "Pure Python CBOR (de)serializer with extensive tag support"
@ -174,6 +168,17 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "0.16" version = "0.16"
[[package]]
category = "main"
description = "Dictionary with auto-expiring values for caching purposes"
name = "expiringdict"
optional = false
python-versions = "*"
version = "1.2.1"
[package.extras]
tests = ["dill", "coverage", "coveralls", "mock", "nose"]
[[package]] [[package]]
category = "main" category = "main"
description = "A simple framework for building complex web applications." description = "A simple framework for building complex web applications."
@ -219,7 +224,7 @@ description = "Coroutine-based network library"
name = "gevent" name = "gevent"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
version = "20.6.0" version = "20.6.2"
[package.dependencies] [package.dependencies]
cffi = ">=1.12.2" cffi = ">=1.12.2"
@ -231,9 +236,9 @@ setuptools = "*"
[package.extras] [package.extras]
dnspython = ["dnspython (>=1.16.0)", "idna"] dnspython = ["dnspython (>=1.16.0)", "idna"]
docs = ["repoze.sphinx.autointerface", "sphinxcontrib-programoutput"] docs = ["repoze.sphinx.autointerface", "sphinxcontrib-programoutput"]
monitor = ["psutil (>=5.6.1)", "psutil (5.6.3)"] monitor = ["psutil (>=5.7.0)"]
recommended = ["dnspython (>=1.16.0)", "idna", "cffi (>=1.12.2)", "psutil (>=5.6.1)", "psutil (5.6.3)", "selectors2", "backports.socketpair"] recommended = ["dnspython (>=1.16.0)", "idna", "cffi (>=1.12.2)", "selectors2", "backports.socketpair", "psutil (>=5.7.0)"]
test = ["dnspython (>=1.16.0)", "idna", "requests", "objgraph", "cffi (>=1.12.2)", "psutil (>=5.6.1)", "psutil (5.6.3)", "selectors2", "futures", "mock", "backports.socketpair", "contextvars (2.4)", "coverage (<5.0)", "coveralls (>=1.7.0)"] test = ["dnspython (>=1.16.0)", "idna", "requests", "objgraph", "cffi (>=1.12.2)", "selectors2", "futures", "mock", "backports.socketpair", "contextvars (2.4)", "coverage (<5.0)", "coveralls (>=1.7.0)", "psutil (>=5.7.0)"]
[[package]] [[package]]
category = "main" category = "main"
@ -841,7 +846,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
rpi = ["RPi.GPIO"] rpi = ["RPi.GPIO"]
[metadata] [metadata]
content-hash = "0607e2b39e77ac530c84a4c2e1dfc14b373ca96322b634e52001cb0591b4b3e7" content-hash = "94e35da71817800c617a54e54dc937df62953fa95caa2e2e05f69b1163582c4c"
python-versions = "^3.6" python-versions = "^3.6"
[metadata.files] [metadata.files]
@ -873,7 +878,10 @@ black = [
{file = "black-18.9b0-py36-none-any.whl", hash = "sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739"}, {file = "black-18.9b0-py36-none-any.whl", hash = "sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739"},
{file = "black-18.9b0.tar.gz", hash = "sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"}, {file = "black-18.9b0.tar.gz", hash = "sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"},
] ]
camera-stage-mapping = [] camera-stage-mapping = [
{file = "camera-stage-mapping-0.1.2.tar.gz", hash = "sha256:0d1a6f93b3a60054bc8057359464adbb40488b4daeb93c672b90e4fe525e7b69"},
{file = "camera_stage_mapping-0.1.2-py3-none-any.whl", hash = "sha256:3f3da3e95792e811e6808173c368be67ebb00e6f7d7e37d68cb36fa765d13da9"},
]
cbor2 = [ cbor2 = [
{file = "cbor2-5.1.0.tar.gz", hash = "sha256:43ce11e8c2fe4971d386d1a60cf83bfa0a4a667b97668ba76acbf5e6398821aa"}, {file = "cbor2-5.1.0.tar.gz", hash = "sha256:43ce11e8c2fe4971d386d1a60cf83bfa0a4a667b97668ba76acbf5e6398821aa"},
] ]
@ -927,6 +935,9 @@ docutils = [
{file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"},
{file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"},
] ]
expiringdict = [
{file = "expiringdict-1.2.1.tar.gz", hash = "sha256:fe2ba427220425c3c8a3d29f6d2e2985bcee323f8bcd4021e68ebefbd90d8250"},
]
flask = [ flask = [
{file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"}, {file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"},
{file = "Flask-1.1.2.tar.gz", hash = "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060"}, {file = "Flask-1.1.2.tar.gz", hash = "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060"},
@ -939,28 +950,34 @@ future = [
{file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
] ]
gevent = [ gevent = [
{file = "gevent-20.6.0-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:ccac571d5bb7a85be736c0c5e241a5263a36904b358edc2dcceaa3b54a18eb25"}, {file = "gevent-20.6.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:b03890bbddbae5667f5baad517417056496ff5e92c3c7945b27cc08f55a9fcb2"},
{file = "gevent-20.6.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:eb74efa2486b8c2576eaf2ef6a3a782b1a38fe414390a4a3ab411efca91e54f5"}, {file = "gevent-20.6.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1ea0d34cb78cdf37870be3bfb9330ebda89197bed9e048c14f4a90dec19a33e0"},
{file = "gevent-20.6.0-cp27-cp27m-win32.whl", hash = "sha256:2701ac60993fb74f1f548348641c78160215aa9707b08e37caf10fe63712f5d9"}, {file = "gevent-20.6.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:73eb4cf3114fbb5dd801bd0b93941adfa2fa6d99e91976c20a121ea14b8b39b9"},
{file = "gevent-20.6.0-cp27-cp27m-win_amd64.whl", hash = "sha256:1a449ab1390fd48cc509d846a62d867e7c548f8f9d5632ca012af2741c97e6d6"}, {file = "gevent-20.6.2-cp27-cp27m-win32.whl", hash = "sha256:f41cc8e853ac2252bc58f6feabd74b8aae613e2d19097c5373463122f4dc08f0"},
{file = "gevent-20.6.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:e963584dd93b8268764297b13d6d99e24d9f1d63d921d5260286b3cde92f2851"}, {file = "gevent-20.6.2-cp27-cp27m-win_amd64.whl", hash = "sha256:d3baff87d935a5eeffb0e4f7cd5ffe258d2430cd62aeee2e5396f85da07df435"},
{file = "gevent-20.6.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:56f2949b068e922bce3695e80bbf484115536901ad77e833b0e1ac7e4d996654"}, {file = "gevent-20.6.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:7d8408854ce892f987305a0e9bf5c051f4ea29453665454396d6afb620c719b6"},
{file = "gevent-20.6.0-cp35-cp35m-win32.whl", hash = "sha256:d4376e9d8f6ecf68de182fed99eb7d7f255b15170edb0bc7d06720552f541c6f"}, {file = "gevent-20.6.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:ea2e4584950186b71d648bde6af40dae4d4c6f43db25a732ec056b27a7a83afe"},
{file = "gevent-20.6.0-cp35-cp35m-win_amd64.whl", hash = "sha256:6d172744705aa401b0014142343e866f98668bd86fc5ba070ace22f4e98f76dd"}, {file = "gevent-20.6.2-cp35-cp35m-win32.whl", hash = "sha256:c0f4340e40e0f9dfe93a52a12ddf5b1eeda9bbc89b99bf3b9b23acab0dfae0a4"},
{file = "gevent-20.6.0-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:664f061993e1104f901801dc7b305824389f5c05936e7daeabee56bf76837726"}, {file = "gevent-20.6.2-cp35-cp35m-win_amd64.whl", hash = "sha256:13c74d6784ef5ada2666abf2bb310d27a1d14291f7cac46148f336b19f714d40"},
{file = "gevent-20.6.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f9521f4721f50f7e1ade81bf61c17387d2cd901de65701038e74c5e87e2785cc"}, {file = "gevent-20.6.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:78bd94f6f2ac366155169df3507068f6381f2ad77625633189ce183f86a57597"},
{file = "gevent-20.6.0-cp36-cp36m-win32.whl", hash = "sha256:779ab592efc30550879546bfe83ee6cb42538080fcd495817868ed7b7b17c3ff"}, {file = "gevent-20.6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0b16dd85eddaf6acdad373ce90ed4da09ef466cbc5e0ee5932d13f099929e844"},
{file = "gevent-20.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:94241c8e962b5eab49d5459b319454161bdb1a0c53ffe883a94a9952ab916345"}, {file = "gevent-20.6.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:a47556cac07e31b3cef8fd701599b3b1365961fe3736471f41807ffa27c5c848"},
{file = "gevent-20.6.0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:77d9237d17a64054e1a03883d3265d27fe98e49f41cf1dd22b42ee3206f99b53"}, {file = "gevent-20.6.2-cp36-cp36m-win32.whl", hash = "sha256:bef18b8bd3b728240b9bbd699737216b793d6c97b482431f69dcbe328ad73692"},
{file = "gevent-20.6.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:5520e72522906483559bd12eacabf72f130972dabe8e05d159e227eca97cbcf9"}, {file = "gevent-20.6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d0a67a20ce325f6a2068e0bd9fbf83db8a5f5ced972ed8ac5c20079a7d98c7d1"},
{file = "gevent-20.6.0-cp37-cp37m-win32.whl", hash = "sha256:8625256ec3acc9b095050d15c256ad9ce3f8bac833982bd712af14a83991b067"}, {file = "gevent-20.6.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:b17915b65b49a425115ddc3087484c81b1e47ce38c931d18bb14e453753e4d06"},
{file = "gevent-20.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e5e1f2024fbf753549703cde287f6cc940248d860f617d657c3b3ea0638035f0"}, {file = "gevent-20.6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ebb8a545112110e3a6edf905ae1556b0538fc148c743aa7d8cfaebbbc23de31d"},
{file = "gevent-20.6.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:3165510ed37f8ea38345a8a2464d5c700f7cc8828194612889f6c583e2889ff1"}, {file = "gevent-20.6.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6c864b5604166ac8351e3128a1135b883b9e978fd24afbd75a249dcb42bc8ab5"},
{file = "gevent-20.6.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b5f73ef6442e72774359afcf892c47cbdcb0811a4577fc1b4b11191aa362e353"}, {file = "gevent-20.6.2-cp37-cp37m-win32.whl", hash = "sha256:e5ca5ee80a9d9e697c9fc22b4bbce9ad06870f83fc8e7774e5504892ef702476"},
{file = "gevent-20.6.0-cp38-cp38-win32.whl", hash = "sha256:6e232e1870dfd2e32aaa26083488c116492e9378d288dffca0e38c1521ba502d"}, {file = "gevent-20.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f2a02d9004ccb18edd9eaf6f25da9a7763de41a69754d5e4d872a8cbf8bd0b72"},
{file = "gevent-20.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:dffc2d2bb815a6ab0370e3e478094e0c43f610a3466e241bc150af21293763db"}, {file = "gevent-20.6.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:354f932c284fa45826b32f42927d892096cce05671b50b3ff59528230217ad47"},
{file = "gevent-20.6.0-pp27-pypy_73-win32.whl", hash = "sha256:ea79e58f7927939ac60d3c73aa9e5d6186c3fea77e4414d768d658e1f58ef7a3"}, {file = "gevent-20.6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:67776cb33b638a3c61a0351d9d1e8f33a46b47de619e249de1159892f9ff035c"},
{file = "gevent-20.6.0.tar.gz", hash = "sha256:51cab8f792961a75da39452586bb3474eaa1607ab2c83398745ef1b9ba09cb92"}, {file = "gevent-20.6.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:68764aca061bbbbade43727e797f9c28042f6d90cca5fb6514ef726d43ab00ca"},
{file = "gevent-20.6.2-cp38-cp38-win32.whl", hash = "sha256:0f3fbb1703b10609856e5dffb0e358bf5edf57e52dc7cd7226e3f8674fdc0a0f"},
{file = "gevent-20.6.2-cp38-cp38-win_amd64.whl", hash = "sha256:a18d8dd9bfa994a22f30adfa0563d80f0809140045c34f85535f422813d25855"},
{file = "gevent-20.6.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:9527087984f1659be899b3300d5d61c7c5b01d8beae106aff5160316da8bc56f"},
{file = "gevent-20.6.2-pp27-pypy_73-macosx_10_7_x86_64.whl", hash = "sha256:76ef4c6e3332e6f7278142d791b28695adfce39735900fccef2a0f1d894f6b36"},
{file = "gevent-20.6.2-pp27-pypy_73-win32.whl", hash = "sha256:3cb2f6978615d52e4e4e667b035c11a7272bb68b14d119faf1b138164b2f354f"},
{file = "gevent-20.6.2.tar.gz", hash = "sha256:a23c2abf08e851c988723f6a2996d495f513a2c0dc70f9956af03af8debdb5d1"},
] ]
gevent-websocket = [ gevent-websocket = [
{file = "gevent-websocket-0.10.1.tar.gz", hash = "sha256:7eaef32968290c9121f7c35b973e2cc302ffb076d018c9068d2f5ca8b2d85fb0"}, {file = "gevent-websocket-0.10.1.tar.gz", hash = "sha256:7eaef32968290c9121f7c35b973e2cc302ffb076d018c9068d2f5ca8b2d85fb0"},

View file

@ -4,7 +4,7 @@ build-backend = "poetry.masonry.api"
[tool.poetry] [tool.poetry]
name = "openflexure-microscope-server" name = "openflexure-microscope-server"
version = "2.2.3" version = "2.2.5"
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 = [
@ -44,9 +44,9 @@ opencv-python-headless = [
] ]
labthings = {git = "https://github.com/labthings/python-labthings.git", rev = "simpler-spec"} labthings = {git = "https://github.com/labthings/python-labthings.git", rev = "simpler-spec"}
pynpm = "^0.1.2" pynpm = "^0.1.2"
camera-stage-mapping = {git = "https://gitlab.com/openflexure/microscope-extensions/camera-stage-mapping.git", rev = "labthings-0.7"}
sangaboard = "^0.2" sangaboard = "^0.2"
gevent = "20.6.0" # Pinned version to guarantee piwheels wheel expiringdict = "^1.2.1"
camera-stage-mapping = "^0.1.2"
[tool.poetry.extras] [tool.poetry.extras]
rpi = ["RPi.GPIO"] rpi = ["RPi.GPIO"]