diff --git a/docs/source/extensions/example_extension/01b_basic_structure_subclass.py b/docs/source/extensions/example_extension/01b_basic_structure_subclass.py new file mode 100644 index 00000000..6177a2a9 --- /dev/null +++ b/docs/source/extensions/example_extension/01b_basic_structure_subclass.py @@ -0,0 +1,41 @@ +from labthings.server.extensions import BaseExtension +from labthings.server.find import find_component + + +# Create the extension class +class MyExtension(BaseExtension): + def __init__(self): + + # Create some instance variable + self.state_variable = "An example of a persistant instance variable" + + # Superclass init function + super().__init__("com.myname.myextension", version="0.0.0") + + def identify(self): + """ + Demonstrate access to Microscope.camera, and Microscope.stage + """ + microscope = find_component("org.openflexure.microscope") + + response = ( + f"My name is {microscope.name}. " + f"My parent camera is {microscope.camera}, " + f"and my parent stage is {microscope.stage}." + ) + + return response + + def rename(self, new_name): + """ + Rename the microscope + """ + + microscope = find_component("org.openflexure.microscope") + + microscope.name = new_name + microscope.save_settings() + + +# Create your extension object +my_extension = MyExtension() diff --git a/docs/source/extensions/lifecycle_hooks.rst b/docs/source/extensions/lifecycle_hooks.rst new file mode 100644 index 00000000..451618a4 --- /dev/null +++ b/docs/source/extensions/lifecycle_hooks.rst @@ -0,0 +1,53 @@ +Lifecycle Hooks +=============== + +Introduction +------------ +In some cases it is useful to have functions triggered by events in an extensions lifecycle. Currently two such lifecycle events can be used, ``on_register``, and ``on_component``. + +``on_register`` +--------------- + +The ``on_register`` method can be used to have a function call as soon as the extension has been successfully registered to the microscope. For example: + +.. code-block:: python + + class MyExtension(BaseExtension): + def __init__(self): + + # Track if the extension has been registered + self.registered = False + + # Add lifecycle hooks + self.on_register(self.on_register_handler, args=(), kwargs={}) + + # Superclass init function + super().__init__("com.myname.myextension", version="0.0.0") + + def on_register_handler(self, *args, **kwargs): + self.registered = True + print("Extension has been registered!") + + +``on_component`` +---------------- + +The ``on_component`` method can be used to have a function call as soon as a particular LabThings component has been added. This can be used, for example, to get information about the microscope instance as soon as it is available. For example: + +.. code-block:: python + + class MyExtension(BaseExtension): + def __init__(self): + + # Hold a reference to the microscope object as soon as it is available + self.microscope = None + + # Add lifecycle hooks + self.on_component("com.myname.myextension", self.on_microscope_handler) + + # Superclass init function + super().__init__("org.openflexure.microscope", version="0.0.0") + + def on_microscope_handler(self, microscope_object): + print("Microscope object has been found!") + self.microscope = microscope_object \ No newline at end of file diff --git a/docs/source/extensions/structure.rst b/docs/source/extensions/structure.rst index ee2bc478..053d4a4a 100644 --- a/docs/source/extensions/structure.rst +++ b/docs/source/extensions/structure.rst @@ -4,7 +4,7 @@ Basic extension structure An extension starts as a simple instance of :py:class:`labthings.server.extensions.BaseExtension`. Each extension is described by a single ``BaseExtension`` instance, containing any number of methods, API views, and additional hardware components. -In order to access the currently running microscope object, use the :py:func:`labthings.server.find` function, with the argument ``"org.openflexure.microscope"``. Likewise, any new components attached by other extensions can be found using their full name, as above. +In order to access the currently running microscope object, use the :py:func:`labthings.server.find.find_component` function, with the argument ``"org.openflexure.microscope"``. Likewise, any new components attached by other extensions can be found using their full name, as above. A simple extension file, with no API views but application-available methods may look like: @@ -23,4 +23,14 @@ Once this extension is loaded, any other extensions will have access to your met # Call a function from your extension if my_found_extension: - my_found_extension.identify() \ No newline at end of file + my_found_extension.identify() + + +Subclassing ``BaseExtension`` +------------------------------- + +The syntax used above allows novice programmers to easily start building extensions, without having to deal with subclassing. However, for more complex extensions which require persistent state, subclassing :py:class:`labthings.server.extensions.BaseExtension` is recommended. + +The same simple extension as seen above can be written using subclassing: + +.. literalinclude:: ./example_extension/01b_basic_structure_subclass.py diff --git a/docs/source/extensions/views.rst b/docs/source/extensions/views.rst index 7d40b82b..ea35ddd2 100644 --- a/docs/source/extensions/views.rst +++ b/docs/source/extensions/views.rst @@ -20,15 +20,15 @@ Request arguments For POST and PUT requests, data usually needs to be provided to the view in order to perform its function. In this example, our ``rename`` view requires a new microscope name to be passed. We make use of the ``@use_body`` decorator to provide this functionality. -``@use_body`` defines the type of data expected in the request body. In this example, we ``String`` type data. The arguments of ``fields.String`` allow us to provide additional information, such as the parameter being required, and example values to appear in API documentation. +``@use_body`` defines the type of data expected in the request body. In this example, we use ``String`` type data. The arguments of ``fields.String`` allow us to provide additional information, such as the parameter being required, and example values to appear in API documentation. Adding additional fields, and the meaning of the field types, will be discussed further in the next section. -When a POST request is made to our API view, the request body is converted to processed by ``@use_body``, and passed as a positional argument to our ``post`` function. +When a POST request is made to our API view, the ``@use_body`` converts the body of the request into a ``String``, and passes it as a positional argument to our ``post`` function. Swagger documentation +++++++++++++++++++++ -At this point, it is useful to introduce the automatically generated Swagger documentation. From any web browser, go to ``http://microscope.local/api/v2/swagger-ui`` (or replace ``microscope.local`` with your microscopes IP address on incompatible systems). +At this point, it is useful to introduce the automatically generated Swagger documentation. From any web browser, go to ``http://microscope.local/api/v2/swagger-ui`` (or replace ``microscope.local`` with your microscope's IP address if ``microscope.local`` doesn't work for your system). -This page uses `SwaggerUI `_ to provide visual, interactive API documentation. Find your extensions URL in the documentation under the ``extensions`` group. Basic documentation about the parameters required for your POST method should be visible, as well as an interactive example filled out with the example request given in ``@use_body``. \ No newline at end of file +This page uses `SwaggerUI `_ to provide visual, interactive API documentation. Find your extensions URL in the documentation under the ``extensions`` group. Basic documentation about the parameters required for your POST method should be visible, as well as an interactive example filled out with the example request given in ``@use_body``. diff --git a/docs/source/plugins.rst b/docs/source/plugins.rst index ad28657e..bb865f26 100644 --- a/docs/source/plugins.rst +++ b/docs/source/plugins.rst @@ -11,4 +11,5 @@ Developing API Extensions ./extensions/properties.rst ./extensions/actions.rst ./extensions/tasks_locks.rst - ./extensions/ev_gui.rst \ No newline at end of file + ./extensions/ev_gui.rst + ./extensions/lifecycle_hooks.rst \ No newline at end of file diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst index 767fdf19..abeaaf45 100644 --- a/docs/source/quickstart.rst +++ b/docs/source/quickstart.rst @@ -21,6 +21,6 @@ For offline (i.e. no real microscope connected) development, a basic development Managing the server ------------------- -Managing the server through the installer script's CLI is documented `on our website `_. +Managing the server through the installer script's CLI is documented `on our website `_. This includes starting the server as a background service, as well as starting a development server with real-time debug logging. \ No newline at end of file diff --git a/openflexure_microscope/api/default_extensions/autofocus.py b/openflexure_microscope/api/default_extensions/autofocus.py index 5fbc9f7c..1da9960b 100644 --- a/openflexure_microscope/api/default_extensions/autofocus.py +++ b/openflexure_microscope/api/default_extensions/autofocus.py @@ -359,7 +359,7 @@ class FastAutofocusAPI(View): autofocus_extension_v2 = BaseExtension( - "org.openflexure.autofocus", version="2.0.0-beta.1" + "org.openflexure.autofocus", version="2.0.0" ) autofocus_extension_v2.add_method(fast_autofocus, "fast_autofocus") diff --git a/openflexure_microscope/api/default_extensions/autostorage.py b/openflexure_microscope/api/default_extensions/autostorage.py index 4ce6c28c..345ca73d 100644 --- a/openflexure_microscope/api/default_extensions/autostorage.py +++ b/openflexure_microscope/api/default_extensions/autostorage.py @@ -88,7 +88,7 @@ class AutostorageExtension(BaseExtension): BaseExtension.__init__( self, "org.openflexure.autostorage", - version="2.0.0-beta.1", + version="2.0.0", description="Handle switching capture storage devices", ) diff --git a/openflexure_microscope/api/default_extensions/scan.py b/openflexure_microscope/api/default_extensions/scan.py index 83ba4e35..a5f82f52 100644 --- a/openflexure_microscope/api/default_extensions/scan.py +++ b/openflexure_microscope/api/default_extensions/scan.py @@ -354,6 +354,6 @@ class TileScanAPI(View): return task -scan_extension_v2 = BaseExtension("org.openflexure.scan", version="2.0.0-beta.1") +scan_extension_v2 = BaseExtension("org.openflexure.scan", version="2.0.0") scan_extension_v2.add_view(TileScanAPI, "/tile") diff --git a/openflexure_microscope/api/default_extensions/zip_builder.py b/openflexure_microscope/api/default_extensions/zip_builder.py index 22375a1e..b32eb995 100644 --- a/openflexure_microscope/api/default_extensions/zip_builder.py +++ b/openflexure_microscope/api/default_extensions/zip_builder.py @@ -202,7 +202,7 @@ class ZipGetterAPIView(View): zip_extension_v2 = BaseExtension( "org.openflexure.zipbuilder", - version="2.0.0-beta.1", + version="2.0.0", description="Build and download capture collections as ZIP files", ) diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index 990767d0..29ab938b 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -531,6 +531,7 @@ class PiCameraStreamer(BaseCamera): # Set resolution and stop stream recording if necessary if not use_video_port: self.stop_stream_recording() + time.sleep(0.1) self.camera.capture( target, @@ -540,6 +541,7 @@ class PiCameraStreamer(BaseCamera): bayer=(not use_video_port) and bayer, use_video_port=use_video_port, ) + time.sleep(0.1) # Set resolution and start stream recording if necessary if not use_video_port: diff --git a/poetry.lock b/poetry.lock index c1f800e8..d177e8f5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -324,7 +324,7 @@ description = "NumPy is the fundamental package for array computing with Python. name = "numpy" optional = false python-versions = ">=3.5" -version = "1.18.1" +version = "1.18.2" [[package]] category = "dev" @@ -355,7 +355,6 @@ test = ["coverage", "pytest", "mock", "pillow", "numpy"] reference = "79177fa7f28d6d5c6eab5ba814b420b1785b6b24" type = "git" url = "https://github.com/rwb27/picamera.git" - [[package]] category = "main" description = "Python Imaging Library (Fork)" @@ -915,11 +914,6 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] marshmallow = [ @@ -931,27 +925,27 @@ mccabe = [ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] numpy = [ - {file = "numpy-1.18.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc"}, - {file = "numpy-1.18.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121"}, - {file = "numpy-1.18.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e"}, - {file = "numpy-1.18.1-cp35-cp35m-win32.whl", hash = "sha256:f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5"}, - {file = "numpy-1.18.1-cp35-cp35m-win_amd64.whl", hash = "sha256:1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6"}, - {file = "numpy-1.18.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480"}, - {file = "numpy-1.18.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572"}, - {file = "numpy-1.18.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57"}, - {file = "numpy-1.18.1-cp36-cp36m-win32.whl", hash = "sha256:2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc"}, - {file = "numpy-1.18.1-cp36-cp36m-win_amd64.whl", hash = "sha256:9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd"}, - {file = "numpy-1.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa"}, - {file = "numpy-1.18.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca"}, - {file = "numpy-1.18.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec"}, - {file = "numpy-1.18.1-cp37-cp37m-win32.whl", hash = "sha256:d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73"}, - {file = "numpy-1.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971"}, - {file = "numpy-1.18.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07"}, - {file = "numpy-1.18.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26"}, - {file = "numpy-1.18.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474"}, - {file = "numpy-1.18.1-cp38-cp38-win32.whl", hash = "sha256:590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3"}, - {file = "numpy-1.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a"}, - {file = "numpy-1.18.1.zip", hash = "sha256:b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77"}, + {file = "numpy-1.18.2-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a1baa1dc8ecd88fb2d2a651671a84b9938461e8a8eed13e2f0a812a94084d1fa"}, + {file = "numpy-1.18.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a244f7af80dacf21054386539699ce29bcc64796ed9850c99a34b41305630286"}, + {file = "numpy-1.18.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6fcc5a3990e269f86d388f165a089259893851437b904f422d301cdce4ff25c8"}, + {file = "numpy-1.18.2-cp35-cp35m-win32.whl", hash = "sha256:b5ad0adb51b2dee7d0ee75a69e9871e2ddfb061c73ea8bc439376298141f77f5"}, + {file = "numpy-1.18.2-cp35-cp35m-win_amd64.whl", hash = "sha256:87902e5c03355335fc5992a74ba0247a70d937f326d852fc613b7f53516c0963"}, + {file = "numpy-1.18.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9ab21d1cb156a620d3999dd92f7d1c86824c622873841d6b080ca5495fa10fef"}, + {file = "numpy-1.18.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:cdb3a70285e8220875e4d2bc394e49b4988bdb1298ffa4e0bd81b2f613be397c"}, + {file = "numpy-1.18.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6d205249a0293e62bbb3898c4c2e1ff8a22f98375a34775a259a0523111a8f6c"}, + {file = "numpy-1.18.2-cp36-cp36m-win32.whl", hash = "sha256:a35af656a7ba1d3decdd4fae5322b87277de8ac98b7d9da657d9e212ece76a61"}, + {file = "numpy-1.18.2-cp36-cp36m-win_amd64.whl", hash = "sha256:1598a6de323508cfeed6b7cd6c4efb43324f4692e20d1f76e1feec7f59013448"}, + {file = "numpy-1.18.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:deb529c40c3f1e38d53d5ae6cd077c21f1d49e13afc7936f7f868455e16b64a0"}, + {file = "numpy-1.18.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:cd77d58fb2acf57c1d1ee2835567cd70e6f1835e32090538f17f8a3a99e5e34b"}, + {file = "numpy-1.18.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b1fe1a6f3a6f355f6c29789b5927f8bd4f134a4bd9a781099a7c4f66af8850f5"}, + {file = "numpy-1.18.2-cp37-cp37m-win32.whl", hash = "sha256:2e40be731ad618cb4974d5ba60d373cdf4f1b8dcbf1dcf4d9dff5e212baf69c5"}, + {file = "numpy-1.18.2-cp37-cp37m-win_amd64.whl", hash = "sha256:4ba59db1fcc27ea31368af524dcf874d9277f21fd2e1f7f1e2e0c75ee61419ed"}, + {file = "numpy-1.18.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:59ca9c6592da581a03d42cc4e270732552243dc45e87248aa8d636d53812f6a5"}, + {file = "numpy-1.18.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1b0ece94018ae21163d1f651b527156e1f03943b986188dd81bc7e066eae9d1c"}, + {file = "numpy-1.18.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:82847f2765835c8e5308f136bc34018d09b49037ec23ecc42b246424c767056b"}, + {file = "numpy-1.18.2-cp38-cp38-win32.whl", hash = "sha256:5e0feb76849ca3e83dd396254e47c7dba65b3fa9ed3df67c2556293ae3e16de3"}, + {file = "numpy-1.18.2-cp38-cp38-win_amd64.whl", hash = "sha256:ba3c7a2814ec8a176bb71f91478293d633c08582119e713a0c5351c0f77698da"}, + {file = "numpy-1.18.2.zip", hash = "sha256:e7894793e6e8540dbeac77c87b489e331947813511108ae097f1715c018b8f3d"}, ] packaging = [ {file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"}, diff --git a/pyproject.toml b/pyproject.toml index 975b03bc..c5080ad0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.masonry.api" [tool.poetry] name = "openflexure_microscope" -version = "2.0.0-beta.6" +version = "2.0.0" description = "Python module, and Flask-based web API, to run the OpenFlexure Microscope." authors = [