From ea10cf1ce9e99602688390a4e2d23397a86b5156 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Fri, 16 Nov 2018 15:46:19 +0000 Subject: [PATCH] Created initial general documentation --- doc/Makefile | 19 +++ doc/make.bat | 35 ++++ doc/requirements.txt | 3 + doc/source/api.rst | 24 +++ doc/source/basecamera.rst | 5 + doc/source/camera.rst | 10 ++ doc/source/capture.rst | 5 + doc/source/conf.py | 202 +++++++++++++++++++++++ doc/source/index.rst | 18 ++ doc/source/microscope.rst | 9 + doc/source/picamera.rst | 5 + openflexure_microscope/api/v1.py | 197 ++++++++++++++++++++-- openflexure_microscope/camera/base.py | 31 ++-- openflexure_microscope/camera/capture.py | 3 + openflexure_microscope/camera/pi.py | 91 +++++----- openflexure_microscope/microscope.py | 23 ++- 16 files changed, 607 insertions(+), 73 deletions(-) create mode 100644 doc/Makefile create mode 100644 doc/make.bat create mode 100644 doc/requirements.txt create mode 100644 doc/source/api.rst create mode 100644 doc/source/basecamera.rst create mode 100644 doc/source/camera.rst create mode 100644 doc/source/capture.rst create mode 100644 doc/source/conf.py create mode 100644 doc/source/index.rst create mode 100644 doc/source/microscope.rst create mode 100644 doc/source/picamera.rst diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 00000000..69fe55ec --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,19 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 00000000..4d9eb83d --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 00000000..45ac0217 --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,3 @@ +Sphinx +sphinxcontrib-httpdomain +sphinx_rtd_theme \ No newline at end of file diff --git a/doc/source/api.rst b/doc/source/api.rst new file mode 100644 index 00000000..c27f6f60 --- /dev/null +++ b/doc/source/api.rst @@ -0,0 +1,24 @@ +REST API +====================================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + +API Documentation +================= + +Summary +------- +.. qrefflask:: openflexure_microscope.api.v1:app + :undoc-endpoints: index + :undoc-static: + :endpoints: + +Details +----------- +.. autoflask:: openflexure_microscope.api.v1:app + :undoc-endpoints: index + :undoc-static: + :endpoints: + :order: path \ No newline at end of file diff --git a/doc/source/basecamera.rst b/doc/source/basecamera.rst new file mode 100644 index 00000000..a29e74a7 --- /dev/null +++ b/doc/source/basecamera.rst @@ -0,0 +1,5 @@ +Base Streaming Camera +======================================================= + +.. automodule:: openflexure_microscope.camera.base + :members: \ No newline at end of file diff --git a/doc/source/camera.rst b/doc/source/camera.rst new file mode 100644 index 00000000..c14dd770 --- /dev/null +++ b/doc/source/camera.rst @@ -0,0 +1,10 @@ +Camera Functionality +======================================================= + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + picamera.rst + basecamera.rst + capture.rst \ No newline at end of file diff --git a/doc/source/capture.rst b/doc/source/capture.rst new file mode 100644 index 00000000..f1dc035a --- /dev/null +++ b/doc/source/capture.rst @@ -0,0 +1,5 @@ +Stream Object +======================================================= + +.. automodule:: openflexure_microscope.camera.capture + :members: \ No newline at end of file diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 00000000..580a1c32 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,202 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'OpenFlexure Microscope Software' +copyright = '2018, Bath Open Instrumentation Group' +author = 'Bath Open Instrumentation Group' + +# The short X.Y version +version = '' +# The full version, including alpha/beta/rc tags +release = '' + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.viewcode', + 'sphinx.ext.githubpages', + 'sphinx.ext.ifconfig', + 'sphinxcontrib.httpdomain', + 'sphinxcontrib.autohttp.flask', + 'sphinxcontrib.autohttp.flaskqref', +] + +# Override ordering +autodoc_member_order = 'bysource' + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = None + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinx_rtd_theme" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'OpenFlexureMicroscopeSoftwaredoc' + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'OpenFlexureMicroscopeSoftware.tex', 'OpenFlexure Microscope Software Documentation', + 'Bath Open Instrumentation Group', 'manual'), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'openflexuremicroscopesoftware', 'OpenFlexure Microscope Software Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'OpenFlexureMicroscopeSoftware', 'OpenFlexure Microscope Software Documentation', + author, 'OpenFlexureMicroscopeSoftware', 'One line description of project.', + 'Miscellaneous'), +] + + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + + +# -- Extension configuration ------------------------------------------------- + +# -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = { + 'openflexure_stage': ('https://openflexure-stage.readthedocs.io/en/latest/', None), + 'picamera': ('https://picamera.readthedocs.io/en/release-1.13/', None) + } + +# -- Options for todo extension ---------------------------------------------- + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True \ No newline at end of file diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 00000000..fc003f62 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,18 @@ +Welcome to OpenFlexure Microscope Software's documentation! +=========================================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + microscope.rst + camera.rst + api.rst + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/doc/source/microscope.rst b/doc/source/microscope.rst new file mode 100644 index 00000000..13cd25ab --- /dev/null +++ b/doc/source/microscope.rst @@ -0,0 +1,9 @@ +Microscope class +======================================================= + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + +.. automodule:: openflexure_microscope.microscope + :members: \ No newline at end of file diff --git a/doc/source/picamera.rst b/doc/source/picamera.rst new file mode 100644 index 00000000..4f7d8cdc --- /dev/null +++ b/doc/source/picamera.rst @@ -0,0 +1,5 @@ +Raspberry Pi Streaming Camera +======================================================= + +.. automodule:: openflexure_microscope.camera.pi + :members: \ No newline at end of file diff --git a/openflexure_microscope/api/v1.py b/openflexure_microscope/api/v1.py index f006b1b3..486f1f67 100644 --- a/openflexure_microscope/api/v1.py +++ b/openflexure_microscope/api/v1.py @@ -81,7 +81,13 @@ class StreamAPI(MicroscopeView): def get(self): """ - Video streaming route. Put this in the src attribute of an img tag. + Real-time MJPEG stream from the microscope camera + + .. :quickref: State; Camera stream + + :>header Accept: image/jpeg + :>header Content-Type: image/jpeg + :status 200: stream active """ # Restart stream worker thread self.microscope.camera.start_worker() @@ -99,7 +105,39 @@ class StateAPI(MicroscopeView): def get(self): """ - Return JSONified microscope state. + JSON representation of the microscope object. + + .. :quickref: State; Microscope state + + **Example request**: + + .. sourcecode:: http + + GET /state/ HTTP/1.1 + Accept: application/json + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Vary: Accept + Content-Type: application/json + + { + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "preview_active": false, + "record_active": false, + "stream_active": true + } + + :>header Accept: application/json + :>header Content-Type: application/json + :status 200: state available """ return jsonify(self.microscope.state) @@ -115,17 +153,42 @@ class PositionAPI(MicroscopeView): def get(self): """ Return current x, y and z positions of the stage. - + .. :quickref: Position; Get current position + + **Example request**: + + .. sourcecode:: http + + GET /position/ HTTP/1.1 + Accept: application/json + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Vary: Accept + Content-Type: application/json + + { + "x": 0, + "y": 0, + "z": 0 + } + + :>json int x: x steps + :>json int y: y steps + :>json int z: z steps """ return jsonify(self.microscope.state['position']) def post(self): """ Set x, y and z positions of the stage. - + .. :quickref: Position; Update current position - + :reqheader Accept: application/json :header Accept: application/json + :query include_unavailable: return json representations of captures that have been completely deleted + + :>jsonarr boolean available: availability of capture data + :>jsonarr string filename: filename of capture + :>jsonarr string id: unique id of the capture object + :>jsonarr boolean keep_on_disk: keep the capture file on microscope after closing + :>jsonarr boolean locked: file locked for modifications (mostly used for video recording) + :>jsonarr string path: path on pi storage to the capture file, if available + :>jsonarr boolean stream: capture stored in-memory as a BytesIO stream + :>jsonarr json uri: - **download** *(string)*: api uri to the capture file download + - **metadata** *(string)*: api uri to the capture json representation + + :>header Content-Type: application/json + :status 200: capture found + :status 404: no capture found with that id """ include_unavailable = get_bool(request.args.get('include_unavailable')) @@ -197,8 +277,8 @@ class CaptureListAPI(MicroscopeView): def delete(self): """ - Delete all captures. - + Delete all captures (not yet implemented) + .. :quickref: Capture collection; Delete all captures """ return jsonify({"error": "not yet implemented"}) @@ -206,16 +286,47 @@ class CaptureListAPI(MicroscopeView): def post(self): """ Create a new image capture. - + .. :quickref: Capture collection; New capture - - :reqheader Accept: application/json + + **Example request**: + + .. sourcecode:: http + + POST /position/ HTTP/1.1 + Accept: application/json + + { + "filename": "myfirstcapture", + "keep_on_disk": true, + "use_video_port": true, + "size": { + "x": 640, + "y": 480 + } + } + + :>header Accept: application/json + :json boolean available: availability of capture data + :>json string filename: filename of capture + :>json string id: unique id of the capture object + :>json boolean keep_on_disk: keep the capture file on microscope after closing + :>json boolean locked: file locked for modifications (mostly used for video recording) + :>json string path: path on pi storage to the capture file, if available + :>json boolean stream: capture stored in-memory as a BytesIO stream + :>json json uri: - **download** *(string)*: api uri to the capture file download + - **metadata** *(string)*: api uri to the capture json representation + + :
json boolean available: availability of capture data + :>json string filename: filename of capture + :>json string id: unique id of the capture object + :>json boolean keep_on_disk: keep the capture file on microscope after closing + :>json boolean locked: file locked for modifications (mostly used for video recording) + :>json string path: path on pi storage to the capture file, if available + :>json boolean stream: capture stored in-memory as a BytesIO stream + :>json json uri: - **download** *(string)*: api uri to the capture file download + - **metadata** *(string)*: api uri to the capture json representation + """ capture_obj = self.microscope.camera.image_from_id(capture_id) @@ -290,7 +441,7 @@ class CaptureAPI(MicroscopeView): def delete(self, capture_id): """ - Delete a capture + Delete a capture (not yet implemented) .. :quickref: Capture; Delete capture """ @@ -298,7 +449,7 @@ class CaptureAPI(MicroscopeView): def put(self, capture_id): """ - Modify the metadata of a capture + Modify the metadata of a capture (not yet implemented) .. :quickref: Capture; Update capture metadata """ @@ -315,6 +466,20 @@ class CaptureDownloadAPI(MicroscopeView): Return image data for a capture. .. :quickref: Capture; Download capture file + + **Example request**: + + .. sourcecode:: http + + GET /capture/d0b2067abbb946f19351e075c5e7cd5b/download?thumbnail=true HTTP/1.1 + Accept: image/jpeg + + :>header Accept: image/jpeg + :query thumbnail: return an image thumbnail e.g. ?thumbnail=true + + :>header Content-Type: image/jpeg + :status 200: capture data found + :status 404: no capture found with that id """ print(capture_id) capture_obj = self.microscope.camera.image_from_id(capture_id) diff --git a/openflexure_microscope/camera/base.py b/openflexure_microscope/camera/base.py index 0649cb71..76f828cd 100644 --- a/openflexure_microscope/camera/base.py +++ b/openflexure_microscope/camera/base.py @@ -34,13 +34,12 @@ def entry_by_id(id: str, object_list: list): class CameraEvent(object): - def __init__(self): - """ - Create a frame-signaller object for StreamingCamera. + """ + A frame-signaller object used by any instances or subclasses of BaseCamera. - An event-like class that signals all active clients - when a new frame is available. - """ + An event-like class that signals all active clients when a new frame is available. + """ + def __init__(self): self.events = {} def wait(self, timeout: int=5): @@ -79,21 +78,23 @@ class CameraEvent(object): class BaseCamera(object): + """ + Base implementation of StreamingCamera. + """ def __init__(self): - """Base implementation of StreamingCamera.""" - self.thread = None # Background thread that reads frames from camera - self.camera = None # Camera object, for direct access to camera + self.thread = None #: Background thread reading frames from camera + self.camera = None #: Camera object - self.frame = None # Current frame is stored here by background thread - self.last_access = 0 # Time of last client access to the camera + self.frame = None #: bytes: Current frame is stored here by background thread + self.last_access = 0 #: time: Time of last client access to the camera self.event = CameraEvent() - self.state = {} # Create dict for capture state - self.settings = {} # Create dict to store settings + self.state = {} #: dict: Dictionary for capture state + self.settings = {} #: dict: Dictionary of camera settings # Capture data - self.images = [] - self.videos = [] + self.images = [] #: list: List of image capture objects + self.videos = [] #: list: List of video recording objects def __enter__(self): """Create camera on context enter.""" diff --git a/openflexure_microscope/camera/capture.py b/openflexure_microscope/camera/capture.py index 7094967b..f245a60f 100644 --- a/openflexure_microscope/camera/capture.py +++ b/openflexure_microscope/camera/capture.py @@ -11,6 +11,9 @@ thumbnail_size = (60, 60) class StreamObject(object): + """ + StreamObject used to store and process capture data, and metadata. + """ def __init__( self, write_to_file: bool=None, diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index 3cd0bd21..a11805ae 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -55,12 +55,12 @@ DEFAULT_CONFIG = os.path.join(HERE, 'config_picamera.yaml') class StreamingCamera(BaseCamera): + """Raspberry Pi camera implementation of StreamingCamera.""" def __init__(self): - """Raspberry Pi camera implementation of StreamingCamera.""" # Run BaseCamera init BaseCamera.__init__(self) # Attach to Pi camera - self.camera = picamera.PiCamera() + self.camera = picamera.PiCamera() #: :py:class:`picamera.PiCamera`: Picamera object # Camera settings self.settings.update({ @@ -93,13 +93,21 @@ class StreamingCamera(BaseCamera): # HANDLE SETTINGS - # TODO: Handle exceptions - # TODO: Have this take a dictionary (not a config file) - # TODO: Web API entry point to send settings as JSON to this method - # TODO: Separate method to store current settings back to YAML file - # TODO: API entry point to get settings to JSON def update_settings(self, config_path: str=None) -> None: - """Open config_picamera.yaml file and write to camera.""" + """ + Open config_picamera.yaml file and write to camera. + + Todo: + TODO: Handle exceptions + + TODO: Have this take a dictionary (not a config file) + + TODO: Web API entry point to send settings as JSON to this method + + TODO: Separate method to store current settings back to YAML file + + TODO: API entry point to get settings to JSON + """ global DEFAULT_CONFIG paused_stream = False @@ -164,7 +172,12 @@ class StreamingCamera(BaseCamera): "Cannot update camera settings while recording is active.") def change_zoom(self, zoom_value: int=1) -> None: - """Change the camera zoom, handling recentering and scaling.""" + """Change the camera zoom, handling recentering and scaling. + + Todo: + TODO: Needs to be re-implemented + + """ zoom_value = float(zoom_value) if zoom_value < 1: zoom_value = 1 @@ -204,17 +217,20 @@ class StreamingCamera(BaseCamera): folder: str='record', fmt: str='h264', quality: int=15): - """Start a new video recording, writing to a target object. + """Start recording. + + Start a new video recording, writing to a target object. + + Args: + target (str/BytesIO): Target object to write bytes to. + write_to_file (bool/NoneType): Should the StreamObject write to a file? + filename (str): Name of the stored file. Defaults to timestamp. + folder (str): Relative directory to store data file in. + fmt (str): Format of the capture. + + Returns: + target_object (str/BytesIO): Target object. - target (str/BytesIO): Target object to write bytes to. - (default StreamObject) - write_to_file (bool/NoneType): Should the StreamObject write to a file? - (default True for video capture) - filename (str): Name of the stored file. - (defaults to timestamp) - folder (str): Relative directory to store data file in. - fmt (str): Format of the capture. - (default 'h264') """ # Start recording method only if a current recording is not running if not self.state['record_active']: @@ -274,9 +290,9 @@ class StreamingCamera(BaseCamera): """ Pause capture on a splitter port. - splitter_port (int): Splitter port to stop recording on - resolution ((int, int)): Resolution to set the camera to, - after stopping recording. + Args: + splitter_port (int): Splitter port to stop recording on + resolution ((int, int)): Resolution to set the camera to, after stopping recording. """ logging.debug("Pausing stream") # If no resolution is specified, default to image_resolution @@ -296,9 +312,9 @@ class StreamingCamera(BaseCamera): """ Resume capture on a splitter port. - splitter_port (int): Splitter port to start recording on - resolution ((int, int)): Resolution to set the camera to, - before starting recording. + Args: + splitter_port (int): Splitter port to start recording on + resolution ((int, int)): Resolution to set the camera to, before starting recording. """ logging.debug("Unpausing stream") if not resolution: @@ -330,17 +346,14 @@ class StreamingCamera(BaseCamera): Defaults to JPEG format. Target object can be overridden for development purposes. - target (str/BytesIO): Target object to write data bytes to. - write_to_file (bool): Should the StreamObject write to a file, - instead of BytesIO stream? - use_video_port (bool): Capture from the video port used for streaming. - (lower resolution, faster) - filename (str): Name of the stored file. - (defaults to timestamp) - folder (str): Relative directory to store data file in. - fmt (str): Format of the capture. - (default 'h264') - resize ((int, int)): Resize the captured image. + Args: + target (str/BytesIO): Target object to write data bytes to. + write_to_file (bool): Should the StreamObject write to a file, instead of BytesIO stream? + use_video_port (bool): Capture from the video port used for streaming. Lower resolution, faster. + filename (str): Name of the stored file. Defaults to timestamp. + folder (str): Relative directory to store data file in. + fmt (str): Format of the capture. + resize ((int, int)): Resize the captured image. """ # If no target is specified, store to StreamingCamera if not target: @@ -391,9 +404,9 @@ class StreamingCamera(BaseCamera): resize: Tuple[int, int]=None) -> np.ndarray: """Capture an uncompressed still YUV image to a Numpy array. - use_video_port (bool): Capture from the video port used for streaming. - (lower resolution, faster) - resize ((int, int)): Resize the captured image. + Args: + use_video_port (bool): Capture from the video port used for streaming. Lower resolution, faster. + resize ((int, int)): Resize the captured image. """ if use_video_port: resolution = self.settings['video_resolution'] diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index 672f1670..1758df3b 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -1,4 +1,8 @@ # -*- coding: utf-8 -*- +""" +Defines a microscope object, binding a camera and stage with basic functionality. +""" + import numpy as np from openflexure_stage import OpenFlexureStage @@ -6,12 +10,20 @@ from .camera.pi import StreamingCamera class Microscope(object): + """ + A basic microscope object. + + The camera and stage should already be initialised, and passed as arguments. + + Args: + camera (:py:class:`openflexure_microscope.camera.pi.StreamingCamera`): camera object + microscope (:py:class:`openflexure_stage.stage.OpenFlexureStage`): stage object + """ def __init__(self, camera: StreamingCamera, stage: OpenFlexureStage): - """Create the microscope object. The camera and stage should already be initialised.""" print("Assigning camera") - self.camera = camera + self.camera = camera #: :py:class:`openflexure_microscope.camera.pi.StreamingCamera`: Picamera object print("Assigning stage") - self.stage = stage + self.stage = stage #: :py:class:`openflexure_stage.stage.OpenFlexureStage`: OpenFlexure stage object self.stage.backlash = np.zeros(3, dtype=np.int) def __enter__(self): @@ -30,6 +42,11 @@ class Microscope(object): # Create unified state @property def state(self): + """Dictionary of the basic microscope state. + + Return: + dict: Dictionary containing position data, and :py:attr:`openflexure_microscope.camera.base.BaseCamera.state` + """ state = {} # Add stage position