diff --git a/openflexure_microscope/api/app.py b/openflexure_microscope/api/app.py index b603b666..7868a714 100644 --- a/openflexure_microscope/api/app.py +++ b/openflexure_microscope/api/app.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -from gevent import monkey +from labthings.server import monkey monkey.patch_all() diff --git a/openflexure_microscope/api/default_extensions/scan.py b/openflexure_microscope/api/default_extensions/scan.py index c58d282f..eace367e 100644 --- a/openflexure_microscope/api/default_extensions/scan.py +++ b/openflexure_microscope/api/default_extensions/scan.py @@ -83,6 +83,7 @@ def capture( annotations=annotations, tags=tags, metadata=metadata, + cache_key=folder ) diff --git a/openflexure_microscope/camera/mock.py b/openflexure_microscope/camera/mock.py index ec61bc7e..0b858cda 100644 --- a/openflexure_microscope/camera/mock.py +++ b/openflexure_microscope/camera/mock.py @@ -10,7 +10,6 @@ import time import numpy as np from PIL import Image, ImageFont, ImageDraw from datetime import datetime -import gevent import logging @@ -221,7 +220,7 @@ class MissingCamera(BaseCamera): # While the iterator is not closed try: while True: - gevent.sleep(1) # Only serve frames at 1fps + time.sleep(1) # Only serve frames at 1fps # Reset stream self.stream.seek(0) self.stream.truncate() diff --git a/openflexure_microscope/captures/capture.py b/openflexure_microscope/captures/capture.py index 57db09d5..00777442 100644 --- a/openflexure_microscope/captures/capture.py +++ b/openflexure_microscope/captures/capture.py @@ -10,9 +10,6 @@ from PIL import Image import dateutil.parser import atexit -from gevent.fileobject import FileObjectThread -import gevent - from collections import OrderedDict from openflexure_microscope.camera import piexif @@ -129,10 +126,6 @@ class CaptureObject(object): """Create a new StreamObject, to manage capture data.""" # Stream for buffering capture data 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 self.id = uuid.uuid4() #: str: Unique capture ID @@ -160,19 +153,12 @@ class CaptureObject(object): def write(self, 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): - logging.debug(f"Flushing {self.file}") - gevent.spawn(self._stream_to_file) - logging.debug(f"Returning flushing {self.file}") + logging.info(f"Writing to disk {self.file}") + with open(self.file, "wb") as outfile: + outfile.write(self.stream.getbuffer()) + self.stream.close() + logging.info(f"Finished writing to disk {self.file}") def open(self, mode): return open(self.file, mode) @@ -275,20 +261,18 @@ class CaptureObject(object): global EXIF_FORMATS if self.format.upper() in EXIF_FORMATS and self.exists: - self.file_ready.wait() - with self.lock: - logging.debug("Writing exif data to capture file") - # Extract current Exif data - exif_dict = piexif.load(self.file) - # Serialize metadata - metadata_string = json.dumps(self.metadata, cls=JSONEncoder) - logging.debug(f"Saving metadata string to file: {metadata_string}") - # Insert metadata into exif_dict - exif_dict["Exif"][piexif.ExifIFD.UserComment] = metadata_string.encode() - # Convert new exif dict to exif bytes - exif_bytes = piexif.dump(exif_dict) - # Insert exif into file - piexif.insert(exif_bytes, self.file) + logging.debug("Writing exif data to capture file") + # Extract current Exif data + exif_dict = piexif.load(self.file) + # Serialize metadata + metadata_string = json.dumps(self.metadata, cls=JSONEncoder) + logging.debug(f"Saving metadata string to file: {metadata_string}") + # Insert metadata into exif_dict + exif_dict["Exif"][piexif.ExifIFD.UserComment] = metadata_string.encode() + # Convert new exif dict to exif bytes + exif_bytes = piexif.dump(exif_dict) + # Insert exif into file + piexif.insert(exif_bytes, self.file) logging.info(f"Finished saving metadata to {self.file}") @property diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index eaa019bb..1a0abd8a 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -6,8 +6,7 @@ import logging import pkg_resources import uuid from typing import Tuple - -import gevent +from expiringdict import ExpiringDict from openflexure_microscope.captures import CaptureManager @@ -61,6 +60,10 @@ class Microscope: # Apply settings loaded from file 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): """Create microscope on context enter.""" return self @@ -237,8 +240,7 @@ class Microscope: # Save config to file self.settings_file.save(current_config, backup=True) - @property - def configuration(self): + def force_get_configuration(self): with self.lock: initial_configuration = self.configuration_file.load() @@ -262,37 +264,62 @@ class Microscope: initial_configuration.update(current_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 - 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 = { "id": self.id, - "settings": settings, - "state": state, - "configuration": configuration, + "settings": self.read_settings(full=False), + "configuration": self.get_configuration(), } return system_metadata - def add_metadata_to_capture(self, output, metadata, annotations, tags): - logging.debug(f"Waiting for {output.file}") - # Wait for the file to be written to disk - with Timer("Waiting for file:"): - output.file_ready.wait() + def get_metadata(self, cache_key=None): + """ + Read microscope metadata, with partial caching + """ + 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"): - full_metadata = {"instrument": self.metadata, **metadata} - with Timer("Writing metadata to file"): - output.put_and_save(tags, annotations, full_metadata) - logging.info(f"Finished injecting EXIF data into {output.file}") + # Keys that should never be cached + metadata.update({ + "state": self.state, + }) + + return metadata + + @property + def metadata(self): + return self.get_metadata() def capture( self, @@ -306,6 +333,7 @@ class Microscope: annotations: dict = None, tags: list = None, metadata: dict = None, + cache_key: str = None ): logging.debug(f"Microscope capturing to {filename}") if not annotations: @@ -315,6 +343,10 @@ class Microscope: if not tags: tags = [] + # Read metadata for capture + full_metadata = {"instrument": self.get_metadata(cache_key), **metadata} + + # Do capture with self.camera.lock: # Create output object output = self.captures.new_image( @@ -322,7 +354,7 @@ class Microscope: ) # Capture to output object - logging.info("Starting microscope capture...") + logging.info(f"Starting microscope capture {filename}") self.camera.capture( output, use_video_port=use_video_port, @@ -331,11 +363,7 @@ class Microscope: fmt=fmt, ) - # Gether metadata from hardware in a greenlet - #gevent.get_hub().threadpool.spawn(self.add_metadata_to_capture, output, metadata, annotations, tags) - 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}") + output.put_and_save(tags, annotations, full_metadata) + logging.debug(f"Finished capture to {output.file}") return output diff --git a/poetry.lock b/poetry.lock index 072950bd..5487a171 100644 --- a/poetry.lock +++ b/poetry.lock @@ -91,25 +91,19 @@ d = ["aiohttp (>=3.3.2)"] [[package]] category = "main" -description = "" +description = "Calibration and mapping between stage and camera coordinates in a microscope" name = "camera-stage-mapping" optional = false -python-versions = "^3.6" -version = "0.1.1" +python-versions = ">=3.6,<4.0" +version = "0.1.2" [package.dependencies] -numpy = "^1.17" +numpy = ">=1.17,<2.0" [package.extras] -all = ["opencv-python-headless (^4.1)", "scipy (^1.4)"] -correlation = ["opencv-python-headless (^4.1)", "scipy (^1.4)"] -ipython = [] -ofmclient = [] +all = ["opencv-python-headless (>=4.1,<5.0)", "scipy (>=1.4,<2.0)"] +correlation = ["opencv-python-headless (>=4.1,<5.0)", "scipy (>=1.4,<2.0)"] -[package.source] -reference = "f5d0d8977a2511b20b47061ac203551f97b8b4ab" -type = "git" -url = "https://gitlab.com/openflexure/microscope-extensions/camera-stage-mapping.git" [[package]] category = "main" 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.*" 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]] category = "main" description = "A simple framework for building complex web applications." @@ -219,7 +224,7 @@ description = "Coroutine-based network library" name = "gevent" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -version = "20.6.0" +version = "20.6.2" [package.dependencies] cffi = ">=1.12.2" @@ -231,9 +236,9 @@ setuptools = "*" [package.extras] dnspython = ["dnspython (>=1.16.0)", "idna"] docs = ["repoze.sphinx.autointerface", "sphinxcontrib-programoutput"] -monitor = ["psutil (>=5.6.1)", "psutil (5.6.3)"] -recommended = ["dnspython (>=1.16.0)", "idna", "cffi (>=1.12.2)", "psutil (>=5.6.1)", "psutil (5.6.3)", "selectors2", "backports.socketpair"] -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)"] +monitor = ["psutil (>=5.7.0)"] +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)", "selectors2", "futures", "mock", "backports.socketpair", "contextvars (2.4)", "coverage (<5.0)", "coveralls (>=1.7.0)", "psutil (>=5.7.0)"] [[package]] category = "main" @@ -841,7 +846,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] rpi = ["RPi.GPIO"] [metadata] -content-hash = "0607e2b39e77ac530c84a4c2e1dfc14b373ca96322b634e52001cb0591b4b3e7" +content-hash = "94e35da71817800c617a54e54dc937df62953fa95caa2e2e05f69b1163582c4c" python-versions = "^3.6" [metadata.files] @@ -873,7 +878,10 @@ black = [ {file = "black-18.9b0-py36-none-any.whl", hash = "sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739"}, {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 = [ {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.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, ] +expiringdict = [ + {file = "expiringdict-1.2.1.tar.gz", hash = "sha256:fe2ba427220425c3c8a3d29f6d2e2985bcee323f8bcd4021e68ebefbd90d8250"}, +] flask = [ {file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"}, {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"}, ] gevent = [ - {file = "gevent-20.6.0-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:ccac571d5bb7a85be736c0c5e241a5263a36904b358edc2dcceaa3b54a18eb25"}, - {file = "gevent-20.6.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:eb74efa2486b8c2576eaf2ef6a3a782b1a38fe414390a4a3ab411efca91e54f5"}, - {file = "gevent-20.6.0-cp27-cp27m-win32.whl", hash = "sha256:2701ac60993fb74f1f548348641c78160215aa9707b08e37caf10fe63712f5d9"}, - {file = "gevent-20.6.0-cp27-cp27m-win_amd64.whl", hash = "sha256:1a449ab1390fd48cc509d846a62d867e7c548f8f9d5632ca012af2741c97e6d6"}, - {file = "gevent-20.6.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:e963584dd93b8268764297b13d6d99e24d9f1d63d921d5260286b3cde92f2851"}, - {file = "gevent-20.6.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:56f2949b068e922bce3695e80bbf484115536901ad77e833b0e1ac7e4d996654"}, - {file = "gevent-20.6.0-cp35-cp35m-win32.whl", hash = "sha256:d4376e9d8f6ecf68de182fed99eb7d7f255b15170edb0bc7d06720552f541c6f"}, - {file = "gevent-20.6.0-cp35-cp35m-win_amd64.whl", hash = "sha256:6d172744705aa401b0014142343e866f98668bd86fc5ba070ace22f4e98f76dd"}, - {file = "gevent-20.6.0-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:664f061993e1104f901801dc7b305824389f5c05936e7daeabee56bf76837726"}, - {file = "gevent-20.6.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f9521f4721f50f7e1ade81bf61c17387d2cd901de65701038e74c5e87e2785cc"}, - {file = "gevent-20.6.0-cp36-cp36m-win32.whl", hash = "sha256:779ab592efc30550879546bfe83ee6cb42538080fcd495817868ed7b7b17c3ff"}, - {file = "gevent-20.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:94241c8e962b5eab49d5459b319454161bdb1a0c53ffe883a94a9952ab916345"}, - {file = "gevent-20.6.0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:77d9237d17a64054e1a03883d3265d27fe98e49f41cf1dd22b42ee3206f99b53"}, - {file = "gevent-20.6.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:5520e72522906483559bd12eacabf72f130972dabe8e05d159e227eca97cbcf9"}, - {file = "gevent-20.6.0-cp37-cp37m-win32.whl", hash = "sha256:8625256ec3acc9b095050d15c256ad9ce3f8bac833982bd712af14a83991b067"}, - {file = "gevent-20.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e5e1f2024fbf753549703cde287f6cc940248d860f617d657c3b3ea0638035f0"}, - {file = "gevent-20.6.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:3165510ed37f8ea38345a8a2464d5c700f7cc8828194612889f6c583e2889ff1"}, - {file = "gevent-20.6.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b5f73ef6442e72774359afcf892c47cbdcb0811a4577fc1b4b11191aa362e353"}, - {file = "gevent-20.6.0-cp38-cp38-win32.whl", hash = "sha256:6e232e1870dfd2e32aaa26083488c116492e9378d288dffca0e38c1521ba502d"}, - {file = "gevent-20.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:dffc2d2bb815a6ab0370e3e478094e0c43f610a3466e241bc150af21293763db"}, - {file = "gevent-20.6.0-pp27-pypy_73-win32.whl", hash = "sha256:ea79e58f7927939ac60d3c73aa9e5d6186c3fea77e4414d768d658e1f58ef7a3"}, - {file = "gevent-20.6.0.tar.gz", hash = "sha256:51cab8f792961a75da39452586bb3474eaa1607ab2c83398745ef1b9ba09cb92"}, + {file = "gevent-20.6.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:b03890bbddbae5667f5baad517417056496ff5e92c3c7945b27cc08f55a9fcb2"}, + {file = "gevent-20.6.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1ea0d34cb78cdf37870be3bfb9330ebda89197bed9e048c14f4a90dec19a33e0"}, + {file = "gevent-20.6.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:73eb4cf3114fbb5dd801bd0b93941adfa2fa6d99e91976c20a121ea14b8b39b9"}, + {file = "gevent-20.6.2-cp27-cp27m-win32.whl", hash = "sha256:f41cc8e853ac2252bc58f6feabd74b8aae613e2d19097c5373463122f4dc08f0"}, + {file = "gevent-20.6.2-cp27-cp27m-win_amd64.whl", hash = "sha256:d3baff87d935a5eeffb0e4f7cd5ffe258d2430cd62aeee2e5396f85da07df435"}, + {file = "gevent-20.6.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:7d8408854ce892f987305a0e9bf5c051f4ea29453665454396d6afb620c719b6"}, + {file = "gevent-20.6.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:ea2e4584950186b71d648bde6af40dae4d4c6f43db25a732ec056b27a7a83afe"}, + {file = "gevent-20.6.2-cp35-cp35m-win32.whl", hash = "sha256:c0f4340e40e0f9dfe93a52a12ddf5b1eeda9bbc89b99bf3b9b23acab0dfae0a4"}, + {file = "gevent-20.6.2-cp35-cp35m-win_amd64.whl", hash = "sha256:13c74d6784ef5ada2666abf2bb310d27a1d14291f7cac46148f336b19f714d40"}, + {file = "gevent-20.6.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:78bd94f6f2ac366155169df3507068f6381f2ad77625633189ce183f86a57597"}, + {file = "gevent-20.6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0b16dd85eddaf6acdad373ce90ed4da09ef466cbc5e0ee5932d13f099929e844"}, + {file = "gevent-20.6.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:a47556cac07e31b3cef8fd701599b3b1365961fe3736471f41807ffa27c5c848"}, + {file = "gevent-20.6.2-cp36-cp36m-win32.whl", hash = "sha256:bef18b8bd3b728240b9bbd699737216b793d6c97b482431f69dcbe328ad73692"}, + {file = "gevent-20.6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d0a67a20ce325f6a2068e0bd9fbf83db8a5f5ced972ed8ac5c20079a7d98c7d1"}, + {file = "gevent-20.6.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:b17915b65b49a425115ddc3087484c81b1e47ce38c931d18bb14e453753e4d06"}, + {file = "gevent-20.6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ebb8a545112110e3a6edf905ae1556b0538fc148c743aa7d8cfaebbbc23de31d"}, + {file = "gevent-20.6.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6c864b5604166ac8351e3128a1135b883b9e978fd24afbd75a249dcb42bc8ab5"}, + {file = "gevent-20.6.2-cp37-cp37m-win32.whl", hash = "sha256:e5ca5ee80a9d9e697c9fc22b4bbce9ad06870f83fc8e7774e5504892ef702476"}, + {file = "gevent-20.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f2a02d9004ccb18edd9eaf6f25da9a7763de41a69754d5e4d872a8cbf8bd0b72"}, + {file = "gevent-20.6.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:354f932c284fa45826b32f42927d892096cce05671b50b3ff59528230217ad47"}, + {file = "gevent-20.6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:67776cb33b638a3c61a0351d9d1e8f33a46b47de619e249de1159892f9ff035c"}, + {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 = [ {file = "gevent-websocket-0.10.1.tar.gz", hash = "sha256:7eaef32968290c9121f7c35b973e2cc302ffb076d018c9068d2f5ca8b2d85fb0"}, diff --git a/pyproject.toml b/pyproject.toml index a2754a00..b4474f84 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.masonry.api" [tool.poetry] 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." authors = [ @@ -44,9 +44,9 @@ opencv-python-headless = [ ] labthings = {git = "https://github.com/labthings/python-labthings.git", rev = "simpler-spec"} 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" -gevent = "20.6.0" # Pinned version to guarantee piwheels wheel +expiringdict = "^1.2.1" +camera-stage-mapping = "^0.1.2" [tool.poetry.extras] rpi = ["RPi.GPIO"]