Started updating documentation

This commit is contained in:
Joel Collins 2020-01-15 15:03:58 +00:00
parent be74b92bd7
commit 802f5ba0c2
17 changed files with 248 additions and 368 deletions

View file

@ -1,25 +1,15 @@
HTTP API
========
Summary
-------
.. qrefflask:: openflexure_microscope.api.app:app
:undoc-endpoints: index
:undoc-static:
:endpoints:
Live documentation
------------------
Details
-------
Full, interactive Swagger documentation for your microscopes web API is available from the microscope itself. From any browser, go to ``http://{your microscope IP address}/api/v2/swagger-ui``.
Default views
-------------
.. autoflask:: openflexure_microscope.api.app:app
:undoc-endpoints: index
:undoc-static:
:endpoints:
:order: path
Example Requests
----------------
.. toctree::
:maxdepth: 2
./apirequests.rst

View file

@ -1,108 +0,0 @@
Introduction
------------
In these examples, our microscope is located at the IP ``192.168.1.126``, running on port 5000.
New Image Capture
-----------------
Temprary capture, using video port.
CURL
++++
.. code-block:: none
curl -X POST -H "Content-Type: application/json" -d \\
'{"temporary": true, "use_video_port": true}' http://192.168.1.126:5000/api/v1/camera/capture
HTTPie
++++++
.. code-block:: none
http POST http://192.168.1.126:5000/api/v1/camera/capture temporary:=true use_video_port:=true
Python Requests
+++++++++++++++
.. code-block:: python
json_payload = {
"keep_on_disk": keep_on_disk,
"use_video_port": use_video_port
}
requests.post('http://192.168.1.126:5000/api/v1/camera/capture', json=json_payload)
Update Config
-------------
Example, changing picamera shutter speed to 2000, and the JPEG quality to 90.
CURL
++++
.. code-block:: none
curl -X POST -H "Content-Type: application/json" -d \\
'{"camera_settings": {"picamera_settings": {"shutter_speed": 2000}, "jpeg_quality": 90}}' http://192.168.1.126:5000/api/v1/config
HTTPie
++++++
.. code-block:: none
http POST http://192.168.1.126:5000/api/v1/config camera_settings:='{"jpeg_quality": 90, "picamera_settings": {"shutter_speed": 2000}}'
Python Requests
+++++++++++++++
.. code-block:: python
json_payload = {
camera_settings: {
"jpeg_quality": 90,
"picamera_settings": {"shutter_speed": 2000}
}
}
requests.post('http://192.168.1.126:5000/api/v1/config', json=json_payload)
Read Config
-----------
CURL
++++
.. code-block:: none
curl http://192.168.1.126:5000/api/v1/config
HTTPie
++++++
.. code-block:: none
http GET http://192.168.1.126:5000/api/v1/config
Python Requests
+++++++++++++++
.. code-block:: python
requests.get('http://192.168.1.126:5000/api/v1/config')
Recalibrate Picamera
--------------------
Starts the automatic recalibration plugin
CURL
++++
.. code-block:: none
curl -X POST -H "Content-Type: application/json" \\
http://192.168.1.126:5000/api/v1/plugin/default/camera_calibration/recalibrate
HTTPie
++++++
.. code-block:: none
http POST http://192.168.1.126:5000/api/v1/plugin/default/camera_calibration/recalibrate
Python Requests
+++++++++++++++
.. code-block:: python
requests.post('http://192.168.1.126:5000/api/v1/plugin/default/camera_calibration/recalibrate')

View file

@ -1,9 +1,35 @@
Camera Class
=======================================================
============
.. toctree::
:maxdepth: 2
picamera.rst
basecamera.rst
capture.rst
capture.rst
Capturing from a camera object
------------------------------
In the cases of both a Raspberry Pi Streaming Camera, and a Mock Camera (attached if no real camera can be found), the camera's ``capture`` method takes as it's first positional argument either a string describing a file path to save to, or any Python file-like object.
The :class:`openflexure_microscope.camera.capture.CaptureObject` class works by providing a file path string, but adds additional functionality around storing and retreiving EXIF metadata in compatible files.
If, for your application, you do not require this functionality, you can pass a simple string or file-like object. For example, to take an image that will be stored in-memory, processed rapidly, and then discarded, you could use a BytesIO stream:
.. code-block:: python
import io
from PIL import Image
...
with microscope.camera.lock, io.BytesIO() as stream:
microscope.camera.capture(
stream,
use_video_port=True,
bayer=False,
)
stream.seek(0)
image = Image.open(stream)

View file

@ -3,7 +3,7 @@ Capture Object
By default, all image and video capture data are stored to instances of :py:class:`openflexure_microscope.camera.capture.CaptureObject`. This class mostly wraps up complexity associated with moving data between disk and memory.
The class also includes some convenience features such as automatically cleaning up captures marked as temporary, handling metadata tags and file names, and generating image thumbnails. Often, the most recent capture wants to be stored in memory, for applications that involve immediately processing the capture data in some way. Rather than manually keep track of the location of the capture data, you can use the CaptureObject ``data`` attribute, which automatically returns capture data from the fastest available location. Additionally, the class handles storing capture metadata to Exif tags in supported formats.
The class also includes some convenience features such as handling metadata tags and file names, and generating image thumbnails. Additionally, the class handles storing capture metadata to Exif tags in supported formats.
Below are details of available methods and attributes.

View file

@ -1,12 +0,0 @@
Common utilities
=======================================================
Tasks module
------------
.. automodule:: openflexure_microscope.common.tasks
:members:
Lock module
-----------
.. automodule:: openflexure_microscope.common.lock
:members:

View file

@ -1,5 +1,5 @@
Microscope configuration
========================
Microscope settings
===================
.. toctree::
:maxdepth: 2
@ -7,65 +7,15 @@ Microscope configuration
.. _MicroscopeRC:
Microscope RC file
------------------
Microscope settings file
------------------------
Microscope configuration is made persistent via a microscope runtime-config (RC) file. By default, this
file exists at ``~/.openflexure/microscope_settings.json``, and contains basic parameters to set up the microscope.
Microscope settings are made persistent via a microscope settings file. By default, this
file exists at ``~/.openflexure/microscope_settings.json``.
Additionally, by default, configurations for specific pieces of hardware (i.e. the stage and camera) are located
in separate "auxillary" config files, and are linked together by adding the config file's path to your main
microscope RC file.
The class :class:`openflexure_microscope.config.OpenflexureSettingsFile` provides functionality for loading a JSON-format settings file as a Python dictionary, and merging changed settings back into the file.
The default settings are loaded by the :attr:`openflexure_microscope.config.user_settings`, which can be imported anywhere in the microscope server application to allow reading and writing of persistent settings.
Loading the runtime-config
++++++++++++++++++++++++++
By default, a microscope object will load the a runtime-config from the default location.
This RC can then be passed to any hardware attached to the microscope. This can be particularly
useful when creating a dummy microscope object before attaching hardware. For example:
.. code-block:: python
from openflexure_microscope import Microscope
from openflexure_microscope.camera.pi import StreamingCamera
from openflexure_stage import OpenFlexureStage
# Create an empty microscope
api_microscope = Microscope(None, None)
# Create a picamera StreamingCamera, using our config
camera_obj = StreamingCamera(config=api_microscope.rc.read())
# Create an OpenFlexure Stage attached to /dev/ttyUSB0
stage_obj = OpenFlexureStage("/dev/ttyUSB0")
# Attach devices to the Microscope object
api_microscope.attach(camera_obj, stage_obj)
Alternatively, a config can be loaded prior to creating a microscope, allowing the microscope
hardware to be created and attached in a single line. For example:
.. code-block:: python
from openflexure_microscope.config import OpenflexureConfig
from openflexure_microscope import Microscope
from openflexure_microscope.camera.pi import StreamingCamera
from openflexure_stage import OpenFlexureStage
# Create a runtime-config from the default location
rc = OpenflexureConfig(expand=True)
# Create a populated microscope
api_microscope = Microscope(
StreamingCamera(config=rc),
OpenFlexureStage("/dev/ttyUSB0"),
config=rc
)
Config module and OpenflexureConfig class
-----------------------------------------
.. automodule:: openflexure_microscope.config
:members:

View file

@ -9,7 +9,6 @@ Welcome to OpenFlexure Microscope Software's documentation!
config.rst
microscope.rst
camera.rst
common.rst
plugins.rst
api.rst

View file

@ -1,6 +1,8 @@
Microscope class
=======================================================
The main microscope class handles microscope settings, passing these between the settings file and their appropriate components (camera, stage), basic metadata about the current device status, and interfacing with the separate camera and stage components.
.. toctree::
:maxdepth: 2
:caption: Contents:

View file

@ -1,5 +1,5 @@
Developing Plugins
=======================================================
Developing API Extensions
=========================
.. toctree::
:maxdepth: 2

View file

@ -1,42 +1,21 @@
Introduction
============
Plugins allow functionality to be added to the OpenFlexure Microscope without having to modify the base code.
Extensions allow functionality to be added to the OpenFlexure Microscope web API without having to modify the base code.
They have full access to the :py:class:`openflexure_microscope.Microscope` object,
including direct access to the attached :py:class:`openflexure_microscope.camera.pi.StreamingCamera` and :py:class:`openflexure_stage.stage.OpenFlexureStage` objects.
This also allows direct access to the :py:class:`picamera.PiCamera` object.
including direct access to any attached :py:class:`openflexure_microscope.camera.base.BaseCamera` and :py:class:`openflexure_stage.stage.OpenFlexureStage` objects.
This also allows access to the :py:class:`picamera.PiCamera` object.
All microscope plugins must subclass :py:class:`openflexure_microscope.plugins.MicroscopePlugin` in order to be loaded by the microscopes :py:class:`openflexure_microscope.plugins.PluginMount`.
Extensions can either be loaded from a single Python file, or as a Python package installed to the environment being used.
Once attached, all methods defined within the plugin class will be accessible from ``<microscope_object>.plugin.<plugin_namespace>.<plugin_method(args)>``.
Here, ``<microscope_object>`` is an instance of :py:class:`openflexure_microscope.Microscope`. The ``<plugin_method>`` is the method defined within your plugin class. Finally, ``<plugin_namespace>`` is determined differently depending on the type of plugin.
Plugins can either be loaded as a single Python file located anywhere on disk, or as a Python package installed to the environment being used. If loaded from a single file, the namespace is set to the file name (excluding .py extension) of the plugin file. If loaded from a package, the namespace is set to the name of the top-level module in the package. For example, if your plugin class definition resides within ``my_openflexure_plugins.microscope.mypluginpackage``, the namespace will be set to ``mypluginpackage``. Where possible, try to use descriptive, unique package names for this reason. For example, rather than name your plugin package ``autofocus``, which would like cause namespace clashes, instead name it ``yourname_autofocus``, or similar.
Module (single-file) plugins
Single-file extensions
----------------------------
For adding simple functionality, such as a few basic functions and API routes, a single Python file can be loaded as a plugin.
For adding simple functionality, such as a few basic functions and API routes, a single Python file can be loaded as a extension. This Python file must contain all of your extension objects, and be located in the applications extensions directory (by default ``~/.openflexure/microscope_extensions``).
This Python file must contain all of your plugin classes. Relative imports will not work. External modules and packages can be used with absolute imports, however for more complex plugins, it is often worth instead making use of an installable package plugin.
Package plugins
Package extensions
---------------
Generally, for adding anything other than very simple functionality, plugins should be written as `package distributions <https://packaging.python.org/tutorials/packaging-projects/>`_. This has the advantage of allowing relative imports, so functionality can be easily split over several files. For example, class definitions associated with API routes can be separated from class definitions associated with the microscope plugin.
Generally, for adding anything other than very simple functionality, extensions should be written as `package distributions <https://packaging.python.org/tutorials/packaging-projects/>`_. This has the advantage of allowing relative imports, so functionality can be easily split over several files. For example, class definitions associated with API routes can be separated from class definitions associated with the microscope extension.
The main restriction is that the plugin package must be importable using an absolute import from within the Python environment being used to load your microscope.
The main restriction is that the extension package must be importable using an absolute import from within the Python environment being used to load your microscope.
Loading plugins with microscope_settings.json
---------------------------------------------
Both types of plugin are loaded by specifying the plugin class in your :ref:`MicroscopeRC`. In the case of a single-file plugin, specify the path to the plugin file, followed by the name of your :py:class:`openflexure_microscope.plugins.MicroscopePlugin` child class, separated by a colon. For packaged plugins, specify the absolute module name in place of the path.
For example, the plugins section of your microscope_settings.json file may look like:
.. code-block:: json
...
"plugins": [
"openflexure_microscope.plugins.default:Plugin",
"~/my_plugins/my_plugin_file.py:MyPluginClass",
"my_openflexure_plugins.microscope.mypluginpackage:MyPluginClass"
]
...
In order to enable your packaged extension, create a file in the applications extensions directory (by default ``~/.openflexure/microscope_extensions``) which imports your extension object(s) from your module.

View file

@ -1,5 +1,5 @@
Adding web API routes
=====================
Adding web API views
====================
.. toctree::
:maxdepth: 2
@ -7,37 +7,38 @@ Adding web API routes
Introduction
------------
Plugins can automatically create routes to expose plugin functionality via the web API. Creating API routes for your plugin is strongly recommended, as this is the primary way we encourage interaction with the microscope device.
Extensions can create views to expose extension functionality via the web API. Creating API views for your extension is strongly recommended, as this is the primary way we encourage interaction with the microscope device.
To create API routes, add a dictionary to your plugin class, named ``api_views``. Within this dictionary, each key should be a string defining the route URL, whose value is a class, subclassing :py:class:`openflexure_microscope.api.v1.views.MicroscopeViewPlugin`.
To create API views, add a dictionary to your extension class, named ``api_views``. Within this dictionary, each key should be a string defining the route URL, whose value is a class, subclassing :py:class:`openflexure_microscope.api.v1.views.MicroscopeViewExtension`.
For example, your ``api_views`` dictionary may look like:
.. code-block:: python
class MyPluginClass(MicroscopePlugin):
class MyExtensionClass(MicroscopeExtension):
api_views = {
'/myplugin': MyRouteAPI,
'/myextension': MyRouteAPI,
}
Here, ``MyRouteAPI`` is a web API plugin class, subclassing :py:class:`openflexure_microscope.api.v1.views.MicroscopeViewPlugin`. If your plugin package were named ``myplugins.package``, an API route would be automatically added at ``/api/v1/plugin/myplugins/package/myplugin``.
Here, ``MyRouteAPI`` is a web API extension class, subclassing :py:class:`openflexure_microscope.api.v1.views.MicroscopeViewExtension`. If your extension package were named ``myextensions.package``, an API route would be automatically added at ``/api/v1/extension/myextensions/package/myextension``.
The full URL that your plugin will attach to is essentially identical to it's full module path. That is, if your plugin is loaded from ``my_microscope_plugins.mypluginpackage:MyPluginClass``, then your plugin routes will appear at ``<microscope_url>/plugin/my_microscope_plugins/mypluginpackage/<route>``. While this means that plugin routes can get long very quickly, they will generally only ever be accessed by client applications, and so this generally should not be a problem.
The full URL that your extension will attach to is essentially identical to it's full module path. That is, if your extension is loaded from ``my_microscope_extensions.myextensionpackage:MyExtensionClass``, then your extension views will appear at ``<microscope_url>/extension/my_microscope_extensions/myextensionpackage/<route>``. While this means that extension views can get long very quickly, they will generally only ever be accessed by client applications, and so this generally should not be a problem.
The MicroscopeViewPlugin class
The MicroscopeViewExtension class
------------------------------
All API plugin classes must subclass :py:class:`openflexure_microscope.api.v1.views.MicroscopeViewPlugin`, which is itself a subclass of `Flask's MethodView <http://flask.pocoo.org/docs/1.0/api/#flask.views.MethodView>`_. This greatly simplifies defining different functionality associated with different HTTP methods at a single URL route.
It is best practice to clearly separate out types of functionality by HTTP method. For example, a GET request should never change the state of the microscope. For this, POST or PUT requests are acceptable. Parameters should be passed to POST and PUT requests as JSON payloads, and your methods should include fallback code for cases where parameters are not passed, or are passed in an invalid format. The DELETE method should only be used in situations where your plugin creates additional URL routes for newly created objects, and should serve only to delete these objects and routes.
All API extension classes must subclass :py:class:`openflexure_microscope.api.v1.views.MicroscopeViewExtension`, which is itself a subclass of `Flask's MethodView <http://flask.pocoo.org/docs/1.0/api/#flask.views.MethodView>`_. This greatly simplifies defining different functionality associated with different HTTP methods at a single URL route.
It is best practice to clearly separate out types of functionality by HTTP method. For example, a GET request should never change the state of the microscope. For this, POST or PUT requests are acceptable. Parameters should be passed to POST and PUT requests as JSON payloads, and your methods should include fallback code for cases where parameters are not passed, or are passed in an invalid format. The DELETE method should only be used in situations where your extension creates additional URL views for newly created objects, and should serve only to delete these objects and views.
Each HTTP method maps to a function with the same name, in lowercase. For example, your :py:class:`openflexure_microscope.api.v1.views.MicroscopeViewPlugin` may look like:
Each HTTP method maps to a function with the same name, in lowercase. For example, your :py:class:`openflexure_microscope.api.v1.views.MicroscopeViewExtension` may look like:
.. code-block:: python
from flask import jsonify
...
class MyRouteAPI(MicroscopeViewPlugin):
class MyRouteAPI(MicroscopeViewExtension):
def get(self):
# Retrieve some information, without changing the state of the microscope
@ -47,39 +48,39 @@ Each HTTP method maps to a function with the same name, in lowercase. For exampl
# Change the state of the microscope based on passed parameters
...
Sometimes you will need to create variable API routes. For example, the built-in routes for managing capture data use capture IDs in the request URL to specify which capture data should be returned. Plugins can also access this functionality. This is done using `Flask variable rules <http://flask.pocoo.org/docs/1.0/quickstart/#variable-rules>`_. Here, variables are added to the URL route string by marking them with ``<variable_name>``, which then passes ``variable_name`` to your request function as a keyword argument.
Sometimes you will need to create variable API views. For example, the built-in views for managing capture data use capture IDs in the request URL to specify which capture data should be returned. Extensions can also access this functionality. This is done using `Flask variable rules <http://flask.pocoo.org/docs/1.0/quickstart/#variable-rules>`_. Here, variables are added to the URL route string by marking them with ``<variable_name>``, which then passes ``variable_name`` to your request function as a keyword argument.
For example:
.. code-block:: python
class MyPluginClass(MicroscopePlugin):
class MyExtensionClass(MicroscopeExtension):
api_views = {
'/myplugin/<object_id>': MyRouteAPI,
'/myextension/<object_id>': MyRouteAPI,
}
...
class MyRouteAPI(MicroscopeViewPlugin):
class MyRouteAPI(MicroscopeViewExtension):
def get(self, object_id):
# Retrieve some information about object_id
this_object = object_dictionary[object_id]
...
Calling plugin methods from routes
Calling extension methods from views
++++++++++++++++++++++++++++++++++
Instances of MicroscopeViewPlugin have direct access to their associated microscope plugin methods, without needing to know the plugin namespace in advance. As described earlier in this section, all plugins get attached to the microscope in their own namespace, based on the plugins name. This means there are two equivalent ways to access your plugin methods from a web API plugin:
Instances of MicroscopeViewExtension have direct access to their associated microscope extension methods, without needing to know the extension namespace in advance. As described earlier in this section, all extensions get attached to the microscope in their own namespace, based on the extensions name. This means there are two equivalent ways to access your extension methods from a web API extension:
.. code-block:: python
...
# Call a method from our plugin, using the MicroscopeViewPlugin.plugin shortcut
self.plugin.my_plugin_method()
# Call a method from our extension, using the MicroscopeViewExtension.extension shortcut
self.extension.my_extension_method()
# Call a method from our plugin, using the full route
self.microscope.my_plugin_name.my_plugin_method()
# Call a method from our extension, using the full route
self.microscope.my_extension_name.my_extension_method()
...
Building responses
@ -98,14 +99,14 @@ An example web route with simple responses may look like:
from flask import jsonify
...
class MyPluginClass(MicroscopePlugin):
class MyExtensionClass(MicroscopeExtension):
api_views = {
'/myplugin/<object_id>': MyRouteAPI,
'/myextension/<object_id>': MyRouteAPI,
}
...
class MyRouteAPI(MicroscopeViewPlugin):
class MyRouteAPI(MicroscopeViewExtension):
def get(self, object_id):
@ -135,7 +136,7 @@ To ease obtaining values from a JSON payload attached to an HTTP POST request, y
.. code-block:: python
...
class MyRouteAPI(MicroscopeViewPlugin):
class MyRouteAPI(MicroscopeViewExtension):
def post(self):
# Get payload JSON from request
@ -144,7 +145,7 @@ To ease obtaining values from a JSON payload attached to an HTTP POST request, y
# Try to find value associated with 'my_string' key.
# If that key doesn't exist in the payload, return '' instead.
# If a value does exist, convert it to a string, regardless of its original type.
new_plugin_string = payload.param('my_string', default='', convert=str)
new_extension_string = payload.param('my_string', default='', convert=str)
...
.. autoclass:: openflexure_microscope.api.utilities.JsonResponse

View file

@ -1,33 +1,58 @@
Basic plugin structure
===========================
Basic extension structure
=========================
As described earlier, a plugin must subclass :py:class:`openflexure_microscope.plugins.MicroscopePlugin`. Each plugin in described by a single class, containing any number of methods. The methods defined within your plugin class will be attached to the microscope. By subclassing :py:class:`openflexure_microscope.plugins.MicroscopePlugin`, your methods automatically get access to the microscope object your plugin is attached to, through ``self.microscope``. This is an instance of :py:class:`openflexure_microscope.Microscope`, and gives unrestricted access to the attached camera and stage hardware.
An extension starts as a simple instance of :py:class:`openflexure_microscope.common.flask_labthings.extensions.BaseExtension`.
Each extension is described by a single ``BaseExtension`` instance, containing any number of methods, API views, and additional hardware components.
For example, a simple plugin file named ``myplugin.py``, may look like:
In order to access the currently running microscope object, use the :py:func:`openflexure_microscope.common.flask_labthings.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.
A simple extension file, with no API views but application-available methods may look like:
.. code-block:: python
from openflexure_microscope.plugins import MicroscopePlugin
from openflexure_microscope.common.flask_labthings.extensions import BaseExtension
from openflexure_microscope.common.flask_labthings.find import find_component
class MyPlugin(MicroscopePlugin):
def identify(self):
"""
Demonstrate access to Microscope.camera, and Microscope.stage
"""
def identify():
"""
Demonstrate access to Microscope.camera, and Microscope.stage
"""
microscope = find_component("org.openflexure.microscope")
parent_camera = self.microscope.camera
parent_stage = self.microscope.stage
parent_camera = microscope.camera
parent_stage = microscope.stage
response = "My parent camera is {}, and my parent stage is {}.".format(parent_camera,
parent_stage)
return response
response = "My parent camera is {}, and my parent stage is {}.".format(parent_camera,
parent_stage)
return response
def hello_world(self):
"""
Demonstrate passive method
"""
def hello_world():
"""
Demonstrate passive method
"""
return "Hello world!"
return "Hello world!"
When this plugin is loaded and attached to a microscope object named ``microscope``, the plugin methods will be available at ``microscope.plugin.myplugin.identify()`` and ``microscope.plugin.myplugin.hello_world()``.
# Create your extension object
my_extension = BaseExtension("com.myname.myextension", version="0.0.0")
# Add methods to your extension
my_extension.add_method(identify, "identify")
my_extension.add_method(hello_world, "hello_world")
Once this extension is loaded, any other extensions will have access to your methods:
.. code-block:: python
from openflexure_microscope.common.flask_labthings.find import find_extension
def test_extension_method():
# Find your extension. Returns None if it hasn't been found.
my_found_extension = find_extension("com.myname.myextension")
# Call a function from your extension
if my_found_extension:
my_found_extension.identify()

View file

@ -6,21 +6,14 @@ Install
Stable installation
+++++++++++++++++++
For most users, this is the reccommended installation method.
For most users, the OpenFlexure Microscope software should be installed using our `pre-built Raspbian SD card image. <https://openflexure.org/projects/microscope/install>`_
- Run ``curl -LSs get.openflexure.org/microscope |sudo bash``
- See the `GitLab repo <https://gitlab.com/openflexure/openflexure-microscope-cli>`_ for details.
- Follow on-screen prompts
Developer and non-interactive installation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The installer script can pull the latest development package from our git repository, and use install into a developer environment using Poetry.
Options also exist to run the installer without any user prompts.
See the `installer script GitLab repo <https://gitlab.com/openflexure/openflexure-microscope-cli>`_ for details.
Manual installation
+++++++++++++++++++
For offline (i.e. no real microscope connected) development, a basic development server can run on any system.
- (Recommended) create and activate a virtual environment
- Install non-python dependencies with ``sudo apt-get install libatlas-base-dev libjasper-dev libjpeg-dev``
- Install `Poetry <https://github.com/sdispater/poetry>`_, clone this repo, and ``poetry install`` from inside the repo.
@ -28,6 +21,6 @@ Manual installation
Managing the server
-------------------
Managing the server through the installer script's CLI is documented `on our website <https://openflexure.gitlab.io/projects/microscope/#managing-the-microscope-server>`_.
Managing the server through the installer script's CLI is documented `on our website <https://openflexure.org/projects/microscope/install/#managing-the-microscope-server>`_.
This includes starting the server as a background service, as well as starting a development server with real-time debug logging.