Merge branch 'new-semantics-testing' into 'master'
New metadata semantics See merge request openflexure/openflexure-microscope-server!38
This commit is contained in:
commit
ae589a7063
28 changed files with 536 additions and 861 deletions
|
|
@ -14,7 +14,7 @@ from labthings.server import fields
|
|||
class MicroscopeIdentifySchema(Schema):
|
||||
name = fields.String() # Microscopes name
|
||||
id = fields.UUID() # Microscopes unique ID
|
||||
status = fields.Dict() # Status dictionary
|
||||
state = fields.Dict() # Status dictionary
|
||||
camera = fields.String() # Camera object (represented as a string)
|
||||
stage = fields.String() # Stage object (represented as a string)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ from labthings.server import fields
|
|||
class MicroscopeIdentifySchema(Schema):
|
||||
name = fields.String() # Microscopes name
|
||||
id = fields.UUID() # Microscopes unique ID
|
||||
status = fields.Dict() # Status dictionary
|
||||
state = fields.Dict() # Status dictionary
|
||||
camera = fields.String() # Camera object (represented as a string)
|
||||
stage = fields.String() # Stage object (represented as a string)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import io # Used in our capture action
|
|||
class MicroscopeIdentifySchema(Schema):
|
||||
name = fields.String() # Microscopes name
|
||||
id = fields.UUID() # Microscopes unique ID
|
||||
status = fields.Dict() # Status dictionary
|
||||
state = fields.Dict() # Status dictionary
|
||||
camera = fields.String() # Camera object (represented as a string)
|
||||
stage = fields.String() # Stage object (represented as a string)
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ We start by creating a schema to describe how to serialise a :py:class:`openflex
|
|||
class MicroscopeIdentifySchema(Schema):
|
||||
name = fields.String() # Microscopes name
|
||||
id = fields.UUID() # Microscopes unique ID
|
||||
status = fields.Dict() # Status dictionary
|
||||
state = fields.Dict() # Status dictionary
|
||||
camera = fields.String() # Camera object (represented as a string)
|
||||
stage = fields.String() # Stage object (represented as a string)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ from openflexure_microscope.paths import (
|
|||
OPENFLEXURE_VAR_PATH,
|
||||
OPENFLEXURE_EXTENSIONS_PATH,
|
||||
settings_file_path,
|
||||
logs_file_path
|
||||
logs_file_path,
|
||||
)
|
||||
|
||||
from labthings.server.quick import create_app
|
||||
|
|
@ -92,15 +92,21 @@ labthing.add_root_link(views.CaptureList, "captures")
|
|||
labthing.add_view(views.CaptureView, f"/captures/<id>")
|
||||
labthing.add_view(views.CaptureDownload, f"/captures/<id>/download/<filename>")
|
||||
labthing.add_view(views.CaptureTags, f"/captures/<id>/tags")
|
||||
labthing.add_view(views.CaptureMetadata, f"/captures/<id>/metadata")
|
||||
labthing.add_view(views.CaptureAnnotations, f"/captures/<id>/annotations")
|
||||
|
||||
# Attach settings and state resources
|
||||
labthing.add_view(views.SettingsProperty, f"/instrument/settings")
|
||||
labthing.add_root_link(views.SettingsProperty, "instrumentSettings")
|
||||
labthing.add_view(views.NestedSettingsProperty, "/instrument/settings/<path:route>")
|
||||
labthing.add_view(views.StateProperty, "/instrument/state")
|
||||
labthing.add_view(views.NestedStateProperty, "/instrument/state/<path:route>")
|
||||
labthing.add_root_link(views.StateProperty, "instrumentState")
|
||||
labthing.add_view(views.ConfigurationProperty, "/instrument/configuration")
|
||||
labthing.add_view(
|
||||
views.NestedConfigurationProperty, "/instrument/configuration/<path:route>"
|
||||
)
|
||||
labthing.add_root_link(views.ConfigurationProperty, "instrumentConfiguration")
|
||||
|
||||
# Attach settings and status resources
|
||||
labthing.add_view(views.SettingsProperty, f"/settings")
|
||||
labthing.add_root_link(views.SettingsProperty, "settings")
|
||||
labthing.add_view(views.NestedSettingsProperty, "/settings/<path:route>")
|
||||
labthing.add_view(views.StatusProperty, "/status")
|
||||
labthing.add_view(views.NestedStatusProperty, "/status/<path:route>")
|
||||
labthing.add_root_link(views.StatusProperty, "status")
|
||||
|
||||
# Attach streams resources
|
||||
labthing.add_view(views.MjpegStream, f"/streams/mjpeg")
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ def recalibrate(microscope):
|
|||
"""
|
||||
scamera = microscope.camera
|
||||
with scamera.lock:
|
||||
assert not scamera.status["record_active"], "Can't recalibrate while recording!"
|
||||
streaming = scamera.status["stream_active"]
|
||||
assert not scamera.record_active, "Can't recalibrate while recording!"
|
||||
streaming = scamera.stream_active
|
||||
if streaming:
|
||||
logging.info("Stopping stream before recalibration")
|
||||
scamera.stop_stream_recording(resolution=(640, 480))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import itertools
|
||||
import logging
|
||||
import uuid
|
||||
import datetime
|
||||
from typing import Tuple
|
||||
from functools import reduce
|
||||
|
||||
|
|
@ -71,12 +72,12 @@ def progress():
|
|||
def capture(
|
||||
microscope,
|
||||
basename,
|
||||
scan_id,
|
||||
temporary: bool = False,
|
||||
use_video_port: bool = False,
|
||||
resize: Tuple[int, int] = None,
|
||||
bayer: bool = False,
|
||||
metadata: dict = {},
|
||||
annotations: dict = {},
|
||||
tags: list = [],
|
||||
):
|
||||
|
||||
|
|
@ -94,16 +95,14 @@ def capture(
|
|||
output.file, use_video_port=use_video_port, resize=resize, bayer=bayer
|
||||
)
|
||||
|
||||
# Affix metadata
|
||||
if "scan" not in tags:
|
||||
tags.append("scan")
|
||||
|
||||
# Inject system metadata
|
||||
output.put_metadata(microscope.metadata, system=True)
|
||||
output.put_metadata({"instrument": microscope.metadata})
|
||||
|
||||
# Insert custom metadata
|
||||
output.put_metadata(metadata)
|
||||
|
||||
# Insert custom metadata
|
||||
output.put_annotations(annotations)
|
||||
# Insert custom tags
|
||||
output.put_tags(tags)
|
||||
|
||||
|
|
@ -115,7 +114,7 @@ def tile(
|
|||
microscope,
|
||||
basename: str = None,
|
||||
temporary: bool = False,
|
||||
step_size: int = [2000, 1500, 100],
|
||||
stride_size: int = [2000, 1500, 100],
|
||||
grid: list = [3, 3, 5],
|
||||
style="raster",
|
||||
autofocus_dz: int = 50,
|
||||
|
|
@ -124,13 +123,13 @@ def tile(
|
|||
bayer: bool = False,
|
||||
fast_autofocus=False,
|
||||
metadata: dict = {},
|
||||
annotations: dict = {},
|
||||
tags: list = [],
|
||||
):
|
||||
global _images_to_be_captured
|
||||
global _images_captured_so_far
|
||||
|
||||
# Keep task progress
|
||||
# TODO: Make this line not nasty
|
||||
_images_to_be_captured = reduce((lambda x, y: x * y), grid)
|
||||
_images_captured_so_far = 0
|
||||
|
||||
|
|
@ -138,28 +137,22 @@ def tile(
|
|||
if not basename:
|
||||
basename = generate_basename()
|
||||
|
||||
# Generate a stack ID
|
||||
scan_id = uuid.uuid4()
|
||||
|
||||
# Store initial position
|
||||
initial_position = microscope.stage.position
|
||||
|
||||
# Add scan metadata
|
||||
if "time" not in metadata:
|
||||
metadata["time"] = generate_basename()
|
||||
|
||||
metadata.update(
|
||||
{
|
||||
"scan_id": scan_id,
|
||||
"basename": basename,
|
||||
"scan_parameters": {
|
||||
"step_size": step_size,
|
||||
"grid": grid,
|
||||
"style": style,
|
||||
"autofocus_dz": autofocus_dz,
|
||||
},
|
||||
# Add dataset metadata
|
||||
dataset_d = {
|
||||
"dataset": {
|
||||
"id": uuid.uuid4(),
|
||||
"type": "xyzScan",
|
||||
"name": basename,
|
||||
"acquisitionDate": datetime.datetime.now().isoformat(),
|
||||
"strideSize": stride_size,
|
||||
"grid": grid,
|
||||
"style": style,
|
||||
"autofocusDz": autofocus_dz,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
# Check if autofocus is enabled
|
||||
autofocus_extension = find_extension("org.openflexure.autofocus")
|
||||
|
|
@ -174,11 +167,11 @@ def tile(
|
|||
autofocus_enabled = False
|
||||
|
||||
z_stack_dz = (
|
||||
grid[2] * step_size[2] if grid[2] > 1 else 0
|
||||
grid[2] * stride_size[2] if grid[2] > 1 else 0
|
||||
) # shorthand for Z stack range
|
||||
|
||||
# Construct an x-y grid (worry about z later)
|
||||
x_y_grid = construct_grid(initial_position, step_size[:2], grid[:2], style=style)
|
||||
x_y_grid = construct_grid(initial_position, stride_size[:2], grid[:2], style=style)
|
||||
|
||||
# Keep the initial Z position the same as our current position
|
||||
next_z = initial_position[2]
|
||||
|
|
@ -209,26 +202,25 @@ def tile(
|
|||
target_z=-z_stack_dz / 2.0, # Finish below the focus
|
||||
initial_move_up=False, # We're already at the top of the scan
|
||||
)
|
||||
# TODO: save the focus data for future reference? Use it for diagnostics?
|
||||
else:
|
||||
logging.debug("Running autofocus")
|
||||
autofocus_extension.autofocus(
|
||||
range(-3 * autofocus_dz, 4 * autofocus_dz, autofocus_dz)
|
||||
)
|
||||
logging.debug("Finished autofocus")
|
||||
time.sleep(1) # TODO: Remove
|
||||
time.sleep(1)
|
||||
|
||||
# If we're not doing a z-stack, just capture
|
||||
if grid[2] <= 1:
|
||||
capture(
|
||||
microscope,
|
||||
basename,
|
||||
scan_id,
|
||||
temporary=temporary,
|
||||
use_video_port=use_video_port,
|
||||
resize=resize,
|
||||
bayer=bayer,
|
||||
metadata=metadata,
|
||||
metadata=dataset_d,
|
||||
annotations=annotations,
|
||||
tags=tags,
|
||||
)
|
||||
# Update task progress
|
||||
|
|
@ -240,15 +232,14 @@ def tile(
|
|||
microscope=microscope,
|
||||
basename=basename,
|
||||
temporary=temporary,
|
||||
scan_id=scan_id,
|
||||
step_size=step_size[2],
|
||||
step_size=stride_size[2],
|
||||
steps=grid[2],
|
||||
center=not fast_autofocus, # fast_autofocus does this for us!
|
||||
return_to_start=not fast_autofocus,
|
||||
use_video_port=use_video_port,
|
||||
resize=resize,
|
||||
bayer=bayer,
|
||||
metadata=metadata,
|
||||
metadata=dataset_d,
|
||||
annotations=annotations,
|
||||
tags=tags,
|
||||
)
|
||||
# Make sure we use our current best estimate of focus (i.e. the current position) next point
|
||||
|
|
@ -259,7 +250,7 @@ def tile(
|
|||
) # Fast autofocus requires us to start at the top of the range
|
||||
if grid[2] > 1:
|
||||
next_z -= int(
|
||||
grid[2] / 2.0 * step_size[2]
|
||||
grid[2] / 2.0 * stride_size[2]
|
||||
) # Z stacking means we're higher up to start with
|
||||
|
||||
logging.debug("Returning to {}".format(initial_position))
|
||||
|
|
@ -270,39 +261,25 @@ def stack(
|
|||
microscope,
|
||||
basename: str = None,
|
||||
temporary: bool = False,
|
||||
scan_id: str = None,
|
||||
step_size: int = 100,
|
||||
steps: int = 5,
|
||||
center: bool = True,
|
||||
return_to_start: bool = True,
|
||||
use_video_port: bool = False,
|
||||
resize: Tuple[int, int] = None,
|
||||
bayer: bool = False,
|
||||
metadata: dict = {},
|
||||
annotations: dict = {},
|
||||
tags: list = [],
|
||||
):
|
||||
global _images_captured_so_far
|
||||
|
||||
# Generate a basename if none given
|
||||
if not basename:
|
||||
basename = generate_basename()
|
||||
|
||||
# Generate a stack ID
|
||||
if not scan_id:
|
||||
scan_id = uuid.uuid4()
|
||||
|
||||
# Add scan metadata
|
||||
if not "time" in metadata:
|
||||
metadata["time"] = generate_basename()
|
||||
|
||||
# Store initial position
|
||||
initial_position = microscope.stage.position
|
||||
|
||||
with microscope.lock:
|
||||
# Move to center scan
|
||||
if center:
|
||||
logging.debug("Moving to starting position")
|
||||
microscope.stage.move_rel([0, 0, int((-step_size * steps) / 2)])
|
||||
logging.debug("Moving to starting position")
|
||||
microscope.stage.move_rel([0, 0, int((-step_size * steps) / 2)])
|
||||
|
||||
for i in range(steps):
|
||||
time.sleep(0.1)
|
||||
|
|
@ -310,12 +287,12 @@ def stack(
|
|||
capture(
|
||||
microscope,
|
||||
basename,
|
||||
scan_id,
|
||||
temporary=temporary,
|
||||
use_video_port=use_video_port,
|
||||
resize=resize,
|
||||
bayer=bayer,
|
||||
metadata=metadata,
|
||||
annotations=annotations,
|
||||
tags=tags,
|
||||
)
|
||||
# Update task progress
|
||||
|
|
@ -337,16 +314,18 @@ def stack(
|
|||
class TileScanAPI(View):
|
||||
@use_args(
|
||||
{
|
||||
"filename": fields.String(),
|
||||
"filename": fields.String(missing=None, example=None),
|
||||
"temporary": fields.Boolean(missing=False),
|
||||
"step_size": fields.List(fields.Integer, missing=[2000, 1500, 100]),
|
||||
"grid": fields.List(fields.Integer, missing=[3, 3, 5]),
|
||||
"stride_size": fields.List(
|
||||
fields.Integer, missing=[2000, 1500, 100], example=[2000, 1500, 100]
|
||||
),
|
||||
"grid": fields.List(fields.Integer, missing=[3, 3, 3], example=[3, 3, 3]),
|
||||
"style": fields.String(missing="raster"),
|
||||
"autofocus_dz": fields.Integer(missing=50),
|
||||
"fast_autofocus": fields.Boolean(missing=False),
|
||||
"use_video_port": fields.Boolean(missing=False),
|
||||
"bayer": fields.Boolean(missing=False),
|
||||
"metadata": fields.Dict(missing={}),
|
||||
"annotations": fields.Dict(missing={}, example={"Foo": "Bar"}),
|
||||
"tags": fields.List(fields.String, missing=[]),
|
||||
"resize": fields.Dict(missing=None), # TODO: Validate keys
|
||||
}
|
||||
|
|
@ -373,7 +352,7 @@ class TileScanAPI(View):
|
|||
microscope,
|
||||
basename=args.get("filename"),
|
||||
temporary=args.get("temporary"),
|
||||
step_size=args.get("step_size"),
|
||||
stride_size=args.get("stride_size"),
|
||||
grid=args.get("grid"),
|
||||
style=args.get("style"),
|
||||
autofocus_dz=args.get("autofocus_dz"),
|
||||
|
|
@ -381,7 +360,7 @@ class TileScanAPI(View):
|
|||
resize=resize,
|
||||
bayer=args.get("bayer"),
|
||||
fast_autofocus=args.get("fast_autofocus"),
|
||||
metadata=args.get("metadata"),
|
||||
annotations=args.get("annotations"),
|
||||
tags=args.get("tags"),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -73,17 +73,15 @@ class ZipManager:
|
|||
# Update task progress
|
||||
update_task_progress(int((index / n_files) * 100))
|
||||
|
||||
session_id = uuid.uuid4()
|
||||
session_key = str(session_id)
|
||||
# self.session_zips[session_id] = fp
|
||||
self.session_zips[session_key] = {
|
||||
session_id = str(uuid.uuid4())
|
||||
self.session_zips[session_id] = {
|
||||
"id": session_id,
|
||||
"fp": fp,
|
||||
"data_size": data_size_megabytes,
|
||||
"zip_size": os.path.getsize(fp.name) * 1e-6,
|
||||
}
|
||||
|
||||
return self.session_zips[session_key]
|
||||
return self.session_zips[session_id]
|
||||
|
||||
def zip_from_id(self, session_id):
|
||||
return self.session_zips[session_id]["fp"]
|
||||
|
|
@ -103,13 +101,13 @@ class ZipBuilderAPIView(View):
|
|||
task = taskify(default_zip_manager.build_zip_from_capture_ids)(microscope, ids)
|
||||
|
||||
# Return a handle on the autofocus task
|
||||
return jsonify(task.state), 201
|
||||
return task.state, 201
|
||||
|
||||
|
||||
@ThingProperty
|
||||
class ZipListAPIView(View):
|
||||
def get(self):
|
||||
return jsonify(default_zip_manager.session_zips)
|
||||
return default_zip_manager.session_zips
|
||||
|
||||
|
||||
class ZipGetterAPIView(View):
|
||||
|
|
@ -141,7 +139,7 @@ class ZipGetterAPIView(View):
|
|||
|
||||
del default_zip_manager.session_zips[session_id]
|
||||
|
||||
return jsonify({"return": session_id})
|
||||
return {"return": session_id}
|
||||
|
||||
|
||||
zip_extension_v2 = BaseExtension("org.openflexure.zipbuilder", version="2.0.0-beta.1")
|
||||
|
|
|
|||
|
|
@ -3,38 +3,9 @@ from openflexure_microscope.camera.capture import build_captures_from_exif
|
|||
|
||||
import logging
|
||||
|
||||
# Import device modules
|
||||
# NB this will eventually be handled by the RC file, so you can choose what device
|
||||
# class should be attached.
|
||||
try:
|
||||
from openflexure_microscope.camera.pi import PiCameraStreamer
|
||||
except ImportError:
|
||||
logging.warning("Unable to import PiCameraStreamer")
|
||||
from openflexure_microscope.camera.mock import MockStreamer
|
||||
|
||||
from openflexure_microscope.stage.sanga import SangaStage
|
||||
from openflexure_microscope.stage.mock import MockStage
|
||||
|
||||
default_microscope = Microscope()
|
||||
|
||||
# Initialise camera
|
||||
logging.debug("Creating camera object...")
|
||||
try:
|
||||
api_camera = PiCameraStreamer()
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
logging.warning("No valid camera hardware found. Falling back to mock camera!")
|
||||
api_camera = MockStreamer()
|
||||
|
||||
# Initialise stage
|
||||
logging.debug("Creating stage object...")
|
||||
|
||||
api_stage = MockStage()
|
||||
|
||||
# Attach devices to microscope
|
||||
logging.debug("Attaching devices to microscope...")
|
||||
default_microscope.attach(api_camera, api_stage)
|
||||
|
||||
# Restore loaded capture array to camera object
|
||||
logging.debug("Restoring captures...")
|
||||
default_microscope.camera.images = build_captures_from_exif(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from .actions import enabled_root_actions
|
||||
from .captures import *
|
||||
from .state import *
|
||||
from .instrument import *
|
||||
from .streams import *
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class CaptureAPI(View):
|
|||
"bayer": fields.Boolean(
|
||||
missing=False, description="Store raw bayer data in file"
|
||||
),
|
||||
"metadata": fields.Dict(missing={}, example={"Client": "SwaggerUI"}),
|
||||
"annotations": fields.Dict(missing={}, example={"Client": "SwaggerUI"}),
|
||||
"tags": fields.List(fields.String, missing=[], example=["docs"]),
|
||||
"resize": fields.Dict(
|
||||
missing=None, example={"width": 640, "height": 480}
|
||||
|
|
@ -75,11 +75,10 @@ class CaptureAPI(View):
|
|||
)
|
||||
|
||||
# Inject system metadata
|
||||
output.put_metadata(microscope.metadata, system=True)
|
||||
output.put_metadata({"instrument": microscope.metadata})
|
||||
|
||||
# Insert custom metadata
|
||||
output.put_metadata(args.get("metadata"))
|
||||
|
||||
output.put_annotations(args.get("annotations"))
|
||||
# Insert custom tags
|
||||
output.put_tags(args.get("tags"))
|
||||
|
||||
|
|
@ -164,8 +163,8 @@ class GPUPreviewStartAPI(View):
|
|||
|
||||
microscope.camera.start_preview(fullscreen=fullscreen, window=window)
|
||||
|
||||
# TODO: Make schema for microscope status
|
||||
return jsonify(microscope.status)
|
||||
# TODO: Make schema for microscope state
|
||||
return jsonify(microscope.state)
|
||||
|
||||
|
||||
@ThingAction
|
||||
|
|
@ -176,5 +175,5 @@ class GPUPreviewStopAPI(View):
|
|||
"""
|
||||
microscope = find_component("org.openflexure.microscope")
|
||||
microscope.camera.stop_preview()
|
||||
# TODO: Make schema for microscope status
|
||||
return jsonify(microscope.status)
|
||||
# TODO: Make schema for microscope state
|
||||
return jsonify(microscope.state)
|
||||
|
|
|
|||
|
|
@ -52,8 +52,8 @@ class MoveStageAPI(View):
|
|||
else:
|
||||
logging.warning("Unable to move. No stage found.")
|
||||
|
||||
# TODO: Make schema for microscope status
|
||||
return jsonify(microscope.status["stage"]["position"])
|
||||
# TODO: Make schema for microscope state
|
||||
return jsonify(microscope.state["stage"]["position"])
|
||||
|
||||
|
||||
@ThingAction
|
||||
|
|
@ -66,5 +66,5 @@ class ZeroStageAPI(View):
|
|||
microscope = find_component("org.openflexure.microscope")
|
||||
microscope.stage.zero_position()
|
||||
|
||||
# TODO: Make schema for microscope status
|
||||
return jsonify(microscope.status["stage"])
|
||||
# TODO: Make schema for microscope state
|
||||
return jsonify(microscope.state["stage"])
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class CaptureSchema(Schema):
|
|||
data_key="path", description="Path of file on microscope device"
|
||||
)
|
||||
exists = fields.Bool(data_key="available")
|
||||
filename = fields.String()
|
||||
name = fields.String()
|
||||
metadata = fields.Dict()
|
||||
|
||||
links = fields.Dict()
|
||||
|
|
@ -39,16 +39,18 @@ class CaptureSchema(Schema):
|
|||
"mimetype": "application/json",
|
||||
**description_from_view(CaptureTags),
|
||||
},
|
||||
"metadata": {
|
||||
"href": url_for(CaptureMetadata.endpoint, id=data.id, _external=True),
|
||||
"annotations": {
|
||||
"href": url_for(
|
||||
CaptureAnnotations.endpoint, id=data.id, _external=True
|
||||
),
|
||||
"mimetype": "application/json",
|
||||
**description_from_view(CaptureMetadata),
|
||||
**description_from_view(CaptureAnnotations),
|
||||
},
|
||||
"download": {
|
||||
"href": url_for(
|
||||
CaptureDownload.endpoint,
|
||||
id=data.id,
|
||||
filename=data.filename,
|
||||
filename=data.name,
|
||||
_external=True,
|
||||
),
|
||||
"mimetype": "image/jpeg",
|
||||
|
|
@ -62,6 +64,9 @@ capture_schema = CaptureSchema()
|
|||
capture_list_schema = CaptureSchema(many=True)
|
||||
|
||||
|
||||
from pprint import pprint
|
||||
|
||||
|
||||
@ThingProperty
|
||||
@Tag("captures")
|
||||
class CaptureList(View):
|
||||
|
|
@ -197,10 +202,10 @@ class CaptureTags(View):
|
|||
|
||||
|
||||
@Tag("captures")
|
||||
class CaptureMetadata(View):
|
||||
class CaptureAnnotations(View):
|
||||
def get(self, id):
|
||||
"""
|
||||
Get metadata associated with a single image capture
|
||||
Get annotations associated with a single image capture
|
||||
"""
|
||||
microscope = find_component("org.openflexure.microscope")
|
||||
capture_obj = microscope.camera.image_from_id(id)
|
||||
|
|
@ -208,7 +213,7 @@ class CaptureMetadata(View):
|
|||
if not capture_obj:
|
||||
return abort(404) # 404 Not Found
|
||||
|
||||
return jsonify(capture_obj.metadata)
|
||||
return jsonify(capture_obj.annotations)
|
||||
|
||||
def put(self, id):
|
||||
"""
|
||||
|
|
@ -226,7 +231,6 @@ class CaptureMetadata(View):
|
|||
if type(data_dict) != dict:
|
||||
return abort(400)
|
||||
|
||||
# TODO: Allow putting system metadata maybe?
|
||||
capture_obj.put_metadata(data_dict)
|
||||
capture_obj.put_annotations(data_dict)
|
||||
|
||||
return jsonify(capture_obj.metadata)
|
||||
return jsonify(capture_obj.annotations)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class SettingsProperty(View):
|
|||
logging.debug("Updating settings from PUT request:")
|
||||
logging.debug(payload.json)
|
||||
|
||||
microscope.apply_settings(payload.json)
|
||||
microscope.update_settings(payload.json)
|
||||
microscope.save_settings()
|
||||
|
||||
return self.get()
|
||||
|
|
@ -65,24 +65,24 @@ class NestedSettingsProperty(View):
|
|||
dictionary = create_from_path(keys)
|
||||
set_by_path(dictionary, keys, payload.json)
|
||||
|
||||
microscope.apply_settings(dictionary)
|
||||
microscope.update_settings(dictionary)
|
||||
microscope.save_settings()
|
||||
|
||||
return self.get(route)
|
||||
|
||||
|
||||
@ThingProperty
|
||||
class StatusProperty(View):
|
||||
class StateProperty(View):
|
||||
def get(self):
|
||||
"""
|
||||
Show current read-only state of the microscope
|
||||
"""
|
||||
microscope = find_component("org.openflexure.microscope")
|
||||
return jsonify(microscope.status)
|
||||
return jsonify(microscope.state)
|
||||
|
||||
|
||||
@Tag("properties")
|
||||
class NestedStatusProperty(View):
|
||||
class NestedStateProperty(View):
|
||||
@doc_response(404, description="Status key cannot be found")
|
||||
def get(self, route):
|
||||
"""
|
||||
|
|
@ -92,7 +92,35 @@ class NestedStatusProperty(View):
|
|||
keys = route.split("/")
|
||||
|
||||
try:
|
||||
value = get_by_path(microscope.status, keys)
|
||||
value = get_by_path(microscope.state, keys)
|
||||
except KeyError:
|
||||
return abort(404)
|
||||
|
||||
return jsonify(value)
|
||||
|
||||
|
||||
@ThingProperty
|
||||
class ConfigurationProperty(View):
|
||||
def get(self):
|
||||
"""
|
||||
Show current read-only state of the microscope
|
||||
"""
|
||||
microscope = find_component("org.openflexure.microscope")
|
||||
return jsonify(microscope.configuration)
|
||||
|
||||
|
||||
@Tag("properties")
|
||||
class NestedConfigurationProperty(View):
|
||||
@doc_response(404, description="Configuration key cannot be found")
|
||||
def get(self, route):
|
||||
"""
|
||||
Show a nested section of the current microscope state
|
||||
"""
|
||||
microscope = find_component("org.openflexure.microscope")
|
||||
keys = route.split("/")
|
||||
|
||||
try:
|
||||
value = get_by_path(microscope.configuration, keys)
|
||||
except KeyError:
|
||||
return abort(404)
|
||||
|
||||
|
|
@ -91,20 +91,6 @@ class CameraEvent(object):
|
|||
class BaseCamera(metaclass=ABCMeta):
|
||||
"""
|
||||
Base implementation of StreamingCamera.
|
||||
|
||||
Attributes:
|
||||
thread: Background thread reading frames from camera
|
||||
camera: Camera object
|
||||
lock (:py:class:`labthings.lock.StrictLock`): Strict lock controlling thread
|
||||
access to stage hardware
|
||||
frame (bytes): Current frame is stored here by background thread
|
||||
last_access (time): Time of last client access to the camera
|
||||
stream_timeout (int): Number of inactive seconds before timing out the stream
|
||||
stream_timeout_enabled (bool): Enable or disable timing out the stream
|
||||
status (dict): Dictionary for capture state
|
||||
paths (dict): Dictionary of capture paths
|
||||
images (list): List of image capture objects
|
||||
videos (list): List of video capture objects
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
|
@ -121,7 +107,8 @@ class BaseCamera(metaclass=ABCMeta):
|
|||
self.stream_timeout = 20
|
||||
self.stream_timeout_enabled = False
|
||||
|
||||
self.status = {"board": None}
|
||||
self.stream_active = False
|
||||
self.record_active = False
|
||||
|
||||
self.paths = {"default": BASE_CAPTURE_PATH, "temp": TEMP_CAPTURE_PATH}
|
||||
|
||||
|
|
@ -129,8 +116,24 @@ class BaseCamera(metaclass=ABCMeta):
|
|||
self.images = []
|
||||
self.videos = []
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def apply_settings(self, config: dict):
|
||||
def configuration(self):
|
||||
"""The current camera configuration."""
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def state(self):
|
||||
"""The current read-only camera state."""
|
||||
pass
|
||||
|
||||
@property
|
||||
def settings(self):
|
||||
return self.read_settings()
|
||||
|
||||
@abstractmethod
|
||||
def update_settings(self, config: dict):
|
||||
"""Update settings from a config dictionary"""
|
||||
with self.lock:
|
||||
# Apply valid config params to camera object
|
||||
|
|
@ -187,7 +190,7 @@ class BaseCamera(metaclass=ABCMeta):
|
|||
self.last_access = time.time()
|
||||
self.stop = False
|
||||
|
||||
if not self.status["stream_active"]:
|
||||
if not self.stream_active:
|
||||
# start background frame thread
|
||||
self.thread = threading.Thread(target=self._thread)
|
||||
self.thread.daemon = True
|
||||
|
|
@ -207,12 +210,12 @@ class BaseCamera(metaclass=ABCMeta):
|
|||
logging.debug("Stopping worker thread")
|
||||
timeout_time = time.time() + timeout
|
||||
|
||||
if self.status["stream_active"]:
|
||||
if self.stream_active:
|
||||
self.stop = True
|
||||
self.thread.join() # Wait for stream thread to exit
|
||||
logging.debug("Waiting for stream thread to exit.")
|
||||
|
||||
while self.status["stream_active"]:
|
||||
while self.stream_active:
|
||||
if time.time() > timeout_time:
|
||||
logging.debug("Timeout waiting for worker thread close.")
|
||||
raise TimeoutError("Timeout waiting for worker thread close.")
|
||||
|
|
@ -352,7 +355,7 @@ class BaseCamera(metaclass=ABCMeta):
|
|||
self.frames_iterator = self.frames()
|
||||
logging.debug("Entering worker thread.")
|
||||
|
||||
self.status["stream_active"] = True
|
||||
self.stream_active = True
|
||||
|
||||
for frame in self.frames_iterator:
|
||||
self.frame = frame
|
||||
|
|
@ -365,9 +368,7 @@ class BaseCamera(metaclass=ABCMeta):
|
|||
and ( # If using timeout
|
||||
time.time() - self.last_access > self.stream_timeout
|
||||
)
|
||||
and not self.status[ # And timeout time
|
||||
"preview_active"
|
||||
] # And GPU preview is not active
|
||||
and not self.preview_active # And GPU preview is not active
|
||||
):
|
||||
self.frames_iterator.close()
|
||||
break
|
||||
|
|
@ -383,4 +384,4 @@ class BaseCamera(metaclass=ABCMeta):
|
|||
|
||||
logging.debug("BaseCamera worker thread exiting...")
|
||||
# Set stream_activate state
|
||||
self.status["stream_active"] = False
|
||||
self.stream_active = False
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import yaml
|
|||
import json
|
||||
import logging
|
||||
from PIL import Image
|
||||
import dateutil.parser
|
||||
import atexit
|
||||
|
||||
from openflexure_microscope.camera import piexif
|
||||
|
|
@ -93,18 +94,18 @@ def capture_from_exif(path, exif_dict):
|
|||
# Build file path information
|
||||
capture.split_file_path(capture.file)
|
||||
|
||||
# Populate capture parameters
|
||||
capture.id = exif_dict["id"]
|
||||
capture.timestring = exif_dict["time"]
|
||||
capture.format = exif_dict["format"]
|
||||
# Image metadata
|
||||
image_metadata = exif_dict.pop("image")
|
||||
|
||||
capture.custom_metadata = (
|
||||
exif_dict["custom"] if "custom" in exif_dict.keys() else {}
|
||||
)
|
||||
capture.system_metadata = (
|
||||
exif_dict["system"] if "system" in exif_dict.keys() else {}
|
||||
)
|
||||
capture.tags = exif_dict["tags"]
|
||||
# Populate capture parameters
|
||||
capture.id = image_metadata.get("id")
|
||||
capture.datetime = dateutil.parser.isoparse(image_metadata.get("acquisitionDate"))
|
||||
capture.format = image_metadata.get("format")
|
||||
capture.tags = image_metadata.get("tags")
|
||||
capture.annotations = image_metadata.get("annotations")
|
||||
|
||||
# Since we popped the "image" key, we dump whatever is left in _metadata
|
||||
capture._metadata = exif_dict
|
||||
|
||||
return capture
|
||||
|
||||
|
|
@ -113,16 +114,6 @@ class CaptureObject(object):
|
|||
"""
|
||||
StreamObject used to store and process on-disk capture data, and metadata.
|
||||
Serves to simplify modifying properties of on-disk capture data.
|
||||
|
||||
Attributes:
|
||||
timestring (str): Timestring of capture creation time
|
||||
custom_metadata (dict): Dictionary of custom metadata to be included in metadata file
|
||||
tags (list): List of tags. Essentially just as extra custom metadata field, but useful for quick organisation
|
||||
filefolder (str): Folder in which the capture file will be stored
|
||||
filename (str): Full name of the capture file
|
||||
basename (str): Filename of the capture, without a file extension
|
||||
format (str): Format of the capture data
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, filepath) -> None:
|
||||
|
|
@ -131,17 +122,20 @@ class CaptureObject(object):
|
|||
# Store a nice ID
|
||||
self.id = uuid.uuid4() #: str: Unique capture ID
|
||||
logging.debug("Created StreamObject {}".format(self.id))
|
||||
self.timestring = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||
self.datetime = datetime.datetime.now()
|
||||
|
||||
# Create file name. Default to UUID
|
||||
self.file = filepath
|
||||
self.split_file_path(self.file)
|
||||
|
||||
# Dictionary for storing custom metadata
|
||||
self.custom_metadata = {}
|
||||
# Dictionary for adding top-level metadata (cannmot be accessed through web API)
|
||||
self.system_metadata = {}
|
||||
if not os.path.exists(self.filefolder):
|
||||
os.makedirs(self.filefolder)
|
||||
|
||||
# Dictionary for adding top-level metadata (cannmot be accessed through web API)
|
||||
self._metadata = {}
|
||||
|
||||
# Dictionary for storing custom annotations
|
||||
self.annotations = {}
|
||||
# List for storing tags
|
||||
self.tags = []
|
||||
|
||||
|
|
@ -158,15 +152,11 @@ class CaptureObject(object):
|
|||
Args:
|
||||
filepath (str): String of the full file path, including file format extension
|
||||
"""
|
||||
# Split the full file path into a folder and a filename
|
||||
self.filefolder, self.filename = os.path.split(filepath)
|
||||
# Split the filename out from it's file extension
|
||||
self.basename = os.path.splitext(self.filename)[0]
|
||||
self.format = self.filename.split(".")[-1]
|
||||
|
||||
# Create folder and file
|
||||
if not os.path.exists(self.filefolder):
|
||||
os.makedirs(self.filefolder)
|
||||
# Split the full file path into a folder and a name
|
||||
self.filefolder, self.name = os.path.split(filepath)
|
||||
# Split the name out from it's file extension
|
||||
self.basename = os.path.splitext(self.name)[0]
|
||||
self.format = self.name.split(".")[-1]
|
||||
|
||||
@property
|
||||
def exists(self) -> bool:
|
||||
|
|
@ -204,17 +194,24 @@ class CaptureObject(object):
|
|||
|
||||
# HANDLE METADATA
|
||||
|
||||
def put_metadata(self, data: dict, system: bool = False) -> None:
|
||||
def put_annotations(self, data: dict) -> None:
|
||||
"""
|
||||
Merge metadata from a passed dictionary into the capture metadata, and saves.
|
||||
Merge annotations from a passed dictionary into the capture metadata, and saves.
|
||||
|
||||
Args:
|
||||
data (dict): Dictionary of metadata to be added
|
||||
"""
|
||||
if system:
|
||||
self.system_metadata.update(data)
|
||||
else:
|
||||
self.custom_metadata.update(data)
|
||||
self.annotations.update(data)
|
||||
self.save_metadata()
|
||||
|
||||
def put_metadata(self, data: dict) -> None:
|
||||
"""
|
||||
Merge root metadata from a passed dictionary into the capture metadata, and saves.
|
||||
|
||||
Args:
|
||||
data (dict): Dictionary of metadata to be added
|
||||
"""
|
||||
self._metadata.update(data)
|
||||
self.save_metadata()
|
||||
|
||||
def save_metadata(self) -> None:
|
||||
|
|
@ -244,12 +241,15 @@ class CaptureObject(object):
|
|||
and any added custom metadata and tags.
|
||||
"""
|
||||
d = {
|
||||
"id": self.id,
|
||||
"time": self.timestring,
|
||||
"format": self.format,
|
||||
"tags": self.tags,
|
||||
"custom": self.custom_metadata,
|
||||
"system": self.system_metadata,
|
||||
"image": {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"acquisitionDate": self.datetime.isoformat(),
|
||||
"format": self.format,
|
||||
"tags": self.tags,
|
||||
"annotations": self.annotations,
|
||||
},
|
||||
**self._metadata,
|
||||
}
|
||||
|
||||
# Add custom metadata to dictionary
|
||||
|
|
@ -262,7 +262,7 @@ class CaptureObject(object):
|
|||
"""
|
||||
|
||||
# Create basic state dictionary
|
||||
d = {"path": self.file, "filename": self.filename, "metadata": self.metadata}
|
||||
d = {"path": self.file, "name": self.name, "metadata": self.metadata}
|
||||
|
||||
# Combined availability of data
|
||||
if self.exists:
|
||||
|
|
|
|||
|
|
@ -26,17 +26,13 @@ We override the logging settings in api.app by setting a level for PIL here.
|
|||
pil_logger = logging.getLogger("PIL")
|
||||
pil_logger.setLevel(logging.INFO)
|
||||
|
||||
|
||||
# MAIN CLASS
|
||||
class MockStreamer(BaseCamera):
|
||||
class MissingCamera(BaseCamera):
|
||||
def __init__(self):
|
||||
# Run BaseCamera init
|
||||
BaseCamera.__init__(self)
|
||||
|
||||
# Store state of PiCameraStreamer
|
||||
self.status.update(
|
||||
{"stream_active": False, "record_active": False, "board": None}
|
||||
)
|
||||
|
||||
# Update config properties
|
||||
self.image_resolution = (1312, 976)
|
||||
self.stream_resolution = (640, 480)
|
||||
|
|
@ -71,6 +67,16 @@ class MockStreamer(BaseCamera):
|
|||
|
||||
image.save(self.stream, format="JPEG")
|
||||
|
||||
@property
|
||||
def configuration(self):
|
||||
"""The current camera configuration."""
|
||||
return {}
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""The current read-only camera state."""
|
||||
return {}
|
||||
|
||||
def initialisation(self):
|
||||
"""Run any initialisation code when the frame iterator starts."""
|
||||
pass
|
||||
|
|
@ -101,7 +107,7 @@ class MockStreamer(BaseCamera):
|
|||
|
||||
return conf_dict
|
||||
|
||||
def apply_settings(self, config: dict):
|
||||
def update_settings(self, config: dict):
|
||||
"""
|
||||
Write a config dictionary to the PiCameraStreamer config.
|
||||
|
||||
|
|
@ -117,7 +123,7 @@ class MockStreamer(BaseCamera):
|
|||
with self.lock:
|
||||
|
||||
# Apply valid config params to camera object
|
||||
if not self.status["record_active"]: # If not recording a video
|
||||
if not self.record_active: # If not recording a video
|
||||
|
||||
for key, value in config.items(): # For each provided setting
|
||||
if hasattr(self, key):
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ from .base import BaseCamera, CaptureObject
|
|||
from .set_picamera_gain import set_analog_gain, set_digital_gain
|
||||
|
||||
from openflexure_microscope.paths import settings_file_path
|
||||
from openflexure_microscope.utilities import serialise_array_b64
|
||||
|
||||
|
||||
# MAIN CLASS
|
||||
|
|
@ -84,15 +85,8 @@ class PiCameraStreamer(BaseCamera):
|
|||
picamera.PiCamera()
|
||||
) #: :py:class:`picamera.PiCamera`: Picamera object
|
||||
|
||||
# Store status of PiCameraStreamer
|
||||
self.status.update(
|
||||
{
|
||||
"stream_active": False,
|
||||
"record_active": False,
|
||||
"preview_active": False,
|
||||
"board": f"picamera_{self.camera.revision}",
|
||||
}
|
||||
)
|
||||
# Store state of PiCameraStreamer
|
||||
self.preview_active = False
|
||||
|
||||
# Reset variable states
|
||||
self.set_zoom(1.0)
|
||||
|
|
@ -116,15 +110,22 @@ class PiCameraStreamer(BaseCamera):
|
|||
"picamera_lst.npy"
|
||||
) #: str: Path of .npy lens shading table file
|
||||
|
||||
# Update board identifier
|
||||
self.status.update({})
|
||||
|
||||
# Create an empty stream
|
||||
self.stream = io.BytesIO()
|
||||
|
||||
# Start streaming
|
||||
self.start_worker()
|
||||
|
||||
@property
|
||||
def configuration(self):
|
||||
"""The current camera configuration."""
|
||||
return {"board": self.camera.revision}
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""The current read-only camera state."""
|
||||
return {}
|
||||
|
||||
def initialisation(self):
|
||||
"""Run any initialisation code when the frame iterator starts."""
|
||||
pass
|
||||
|
|
@ -156,7 +157,7 @@ class PiCameraStreamer(BaseCamera):
|
|||
"picamera_lst_path": self.picamera_lst_path
|
||||
if os.path.isfile(self.picamera_lst_path)
|
||||
else None,
|
||||
"picamera_settings": {},
|
||||
"picamera": {},
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -165,7 +166,7 @@ class PiCameraStreamer(BaseCamera):
|
|||
try:
|
||||
value = getattr(self.camera, key)
|
||||
logging.debug("Reading PiCamera().{}: {}".format(key, value))
|
||||
conf_dict["picamera_settings"][key] = value
|
||||
conf_dict["picamera"][key] = value
|
||||
except AttributeError:
|
||||
logging.debug("Unable to read PiCamera attribute {}".format(key))
|
||||
|
||||
|
|
@ -176,7 +177,7 @@ class PiCameraStreamer(BaseCamera):
|
|||
logging.info("Saving picamera_lst to {}".format(self.picamera_lst_path))
|
||||
self.save_lens_shading_table()
|
||||
|
||||
def apply_settings(self, config: dict):
|
||||
def update_settings(self, config: dict):
|
||||
"""
|
||||
Write a config dictionary to the PiCameraStreamer config.
|
||||
|
||||
|
|
@ -194,23 +195,23 @@ class PiCameraStreamer(BaseCamera):
|
|||
with self.lock:
|
||||
|
||||
# Apply valid config params to Picamera object
|
||||
if not self.status["record_active"]: # If not recording a video
|
||||
if not self.record_active: # If not recording a video
|
||||
|
||||
# Pause stream while changing settings
|
||||
if self.status["stream_active"]: # If stream is active
|
||||
if self.stream_active: # If stream is active
|
||||
logging.info("Pausing stream to update config.")
|
||||
self.stop_stream_recording() # Pause stream
|
||||
paused_stream = True # Remember to unpause stream when done
|
||||
|
||||
# PiCamera parameters
|
||||
if "picamera_settings" in config: # If new settings are given
|
||||
if "picamera" in config: # If new settings are given
|
||||
self.apply_picamera_settings(
|
||||
config["picamera_settings"], pause_for_effect=True
|
||||
config["picamera"], pause_for_effect=True
|
||||
)
|
||||
|
||||
# PiCameraStreamer parameters
|
||||
for key, value in config.items(): # For each provided setting
|
||||
if (key != "picamera_settings") and hasattr(self, key):
|
||||
if (key != "picamera") and hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
|
||||
# Handle lens shading if camera supports it
|
||||
|
|
@ -332,13 +333,13 @@ class PiCameraStreamer(BaseCamera):
|
|||
Change the camera zoom, handling re-centering and scaling.
|
||||
"""
|
||||
with self.lock:
|
||||
self.status["zoom_value"] = float(zoom_value)
|
||||
if self.status["zoom_value"] < 1:
|
||||
self.status["zoom_value"] = 1
|
||||
self.zoom_value = float(zoom_value)
|
||||
if self.zoom_value < 1:
|
||||
self.zoom_value = 1
|
||||
# Richard's code for zooming !
|
||||
fov = self.camera.zoom
|
||||
centre = np.array([fov[0] + fov[2] / 2.0, fov[1] + fov[3] / 2.0])
|
||||
size = 1.0 / self.status["zoom_value"]
|
||||
size = 1.0 / self.zoom_value
|
||||
# If the new zoom value would be invalid, move the centre to
|
||||
# keep it within the camera's sensor (this is only relevant
|
||||
# when zooming out, if the FoV is not centred on (0.5, 0.5)
|
||||
|
|
@ -365,7 +366,7 @@ class PiCameraStreamer(BaseCamera):
|
|||
self.camera.preview.window = window
|
||||
if fullscreen:
|
||||
self.camera.preview.fullscreen = fullscreen
|
||||
self.status["preview_active"] = True
|
||||
self.preview_active = True
|
||||
except picamera.exc.PiCameraMMALError as e:
|
||||
logging.error(
|
||||
"Suppressed a MMALError in start_preview. Exception: {}".format(e)
|
||||
|
|
@ -380,7 +381,7 @@ class PiCameraStreamer(BaseCamera):
|
|||
def stop_preview(self):
|
||||
"""Stop the on board GPU camera preview."""
|
||||
self.camera.stop_preview()
|
||||
self.status["preview_active"] = False
|
||||
self.preview_active = False
|
||||
|
||||
def start_recording(self, output, fmt: str = "h264", quality: int = 15):
|
||||
"""Start recording.
|
||||
|
|
@ -398,7 +399,7 @@ class PiCameraStreamer(BaseCamera):
|
|||
"""
|
||||
with self.lock:
|
||||
# Start recording method only if a current recording is not running
|
||||
if not self.status["record_active"]:
|
||||
if not self.record_active:
|
||||
|
||||
# Start the camera video recording on port 2
|
||||
logging.info("Recording to {}".format(output))
|
||||
|
|
@ -411,8 +412,8 @@ class PiCameraStreamer(BaseCamera):
|
|||
quality=quality,
|
||||
)
|
||||
|
||||
# Update status dictionary
|
||||
self.status["record_active"] = True
|
||||
# Update state
|
||||
self.record_active = True
|
||||
|
||||
return output
|
||||
|
||||
|
|
@ -431,8 +432,8 @@ class PiCameraStreamer(BaseCamera):
|
|||
self.camera.stop_recording(splitter_port=2)
|
||||
logging.info("Recording stopped")
|
||||
|
||||
# Update status dictionary
|
||||
self.status["record_active"] = False
|
||||
# Update state
|
||||
self.record_active = False
|
||||
|
||||
def stop_stream_recording(
|
||||
self, splitter_port: int = 1, resolution: Tuple[int, int] = None
|
||||
|
|
@ -500,7 +501,7 @@ class PiCameraStreamer(BaseCamera):
|
|||
self.camera.resolution = resolution
|
||||
|
||||
# If the stream should be active
|
||||
if self.status["stream_active"]:
|
||||
if self.stream_active:
|
||||
try:
|
||||
# Start recording on stream port
|
||||
self.camera.start_recording(
|
||||
|
|
@ -551,7 +552,7 @@ class PiCameraStreamer(BaseCamera):
|
|||
if isinstance(output, CaptureObject):
|
||||
target = output.file
|
||||
else:
|
||||
target = target
|
||||
target = output
|
||||
|
||||
with self.lock:
|
||||
logging.info("Capturing to {}".format(output))
|
||||
|
|
@ -680,7 +681,6 @@ class PiCameraStreamer(BaseCamera):
|
|||
# Start stream recording (and set resolution)
|
||||
self.start_stream_recording()
|
||||
|
||||
# Update status
|
||||
logging.debug("STREAM ACTIVE")
|
||||
|
||||
# While the iterator is not closed
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import json
|
||||
import flask
|
||||
import os
|
||||
import errno
|
||||
import logging
|
||||
|
|
@ -7,7 +8,12 @@ from uuid import UUID
|
|||
import numpy as np
|
||||
from fractions import Fraction
|
||||
|
||||
from .paths import CONFIG_FILE_PATH, DEFAULT_CONFIG_FILE_PATH
|
||||
from .paths import (
|
||||
SETTINGS_FILE_PATH,
|
||||
DEFAULT_SETTINGS_FILE_PATH,
|
||||
CONFIGURATION_FILE_PATH,
|
||||
DEFAULT_CONFIGURATION_FILE_PATH,
|
||||
)
|
||||
|
||||
|
||||
class OpenflexureSettingsFile:
|
||||
|
|
@ -19,21 +25,21 @@ class OpenflexureSettingsFile:
|
|||
expand (bool): Expand paths to valid auxillary config files.
|
||||
"""
|
||||
|
||||
def __init__(self, config_path: str = None):
|
||||
global DEFAULT_CONFIG, CONFIG_FILE_PATH
|
||||
def __init__(self, path: str, defaults: dict = {}):
|
||||
global DEFAULT_SETTINGS
|
||||
|
||||
# Set arguments
|
||||
self.config_path = config_path or CONFIG_FILE_PATH
|
||||
self.path = path
|
||||
|
||||
# Initialise basic config file with defaults if it doesn't exist
|
||||
initialise_file(self.config_path, populate=DEFAULT_CONFIG)
|
||||
initialise_file(self.path, populate=defaults)
|
||||
|
||||
def load(self) -> dict:
|
||||
"""
|
||||
Loads settings from a file on-disk.
|
||||
"""
|
||||
# Unexpanded config dictionary (used at load/save time)
|
||||
loaded_config = load_json_file(self.config_path)
|
||||
loaded_config = load_json_file(self.path)
|
||||
|
||||
logging.debug("Reading settings from disk")
|
||||
return loaded_config
|
||||
|
|
@ -50,11 +56,11 @@ class OpenflexureSettingsFile:
|
|||
save_settings = config
|
||||
|
||||
if backup:
|
||||
if os.path.isfile(self.config_path):
|
||||
shutil.copyfile(self.config_path, self.config_path + ".bk")
|
||||
if os.path.isfile(self.path):
|
||||
shutil.copyfile(self.path, self.path + ".bk")
|
||||
|
||||
logging.debug("Saving settings dictionary to disk")
|
||||
save_json_file(self.config_path, save_settings)
|
||||
save_json_file(self.path, save_settings)
|
||||
|
||||
def merge(self, config: dict) -> dict:
|
||||
"""
|
||||
|
|
@ -71,7 +77,7 @@ class OpenflexureSettingsFile:
|
|||
return settings
|
||||
|
||||
|
||||
class JSONEncoder(json.JSONEncoder):
|
||||
class JSONEncoder(flask.json.JSONEncoder):
|
||||
"""
|
||||
A custom JSON encoder, with type conversions for PiCamera fractions, Numpy integers, and Numpy arrays
|
||||
"""
|
||||
|
|
@ -88,11 +94,14 @@ class JSONEncoder(json.JSONEncoder):
|
|||
# Numpy arrays
|
||||
elif isinstance(o, np.ndarray):
|
||||
return o.tolist()
|
||||
# UUIDs
|
||||
elif isinstance(o, UUID):
|
||||
return str(o)
|
||||
else:
|
||||
# call base class implementation which takes care of
|
||||
# raising exceptions for unsupported types
|
||||
try:
|
||||
return json.JSONEncoder.default(self, o)
|
||||
return flask.json.JSONEncoder.default(self, o)
|
||||
# if it's some mystery object, just return a string representation
|
||||
except TypeError:
|
||||
return str(o)
|
||||
|
|
@ -178,10 +187,22 @@ def initialise_file(config_path, populate: str = "{}\n"):
|
|||
outfile.write(populate)
|
||||
|
||||
|
||||
# Load the default config
|
||||
with open(DEFAULT_CONFIG_FILE_PATH, "r") as default_rc:
|
||||
DEFAULT_CONFIG = default_rc.read()
|
||||
|
||||
# Load the default settings
|
||||
with open(DEFAULT_SETTINGS_FILE_PATH, "r") as default_settings:
|
||||
DEFAULT_SETTINGS = default_settings.read()
|
||||
|
||||
#: Default user settings object
|
||||
user_settings = OpenflexureSettingsFile(config_path=CONFIG_FILE_PATH)
|
||||
user_settings = OpenflexureSettingsFile(
|
||||
path=SETTINGS_FILE_PATH, defaults=DEFAULT_SETTINGS
|
||||
)
|
||||
|
||||
|
||||
# Load the default configuration
|
||||
with open(DEFAULT_CONFIGURATION_FILE_PATH, "r") as default_configuration:
|
||||
DEFAULT_CONFIGURATION = default_configuration.read()
|
||||
|
||||
#: Default user settings object
|
||||
user_configuration = OpenflexureSettingsFile(
|
||||
path=CONFIGURATION_FILE_PATH, defaults=DEFAULT_CONFIGURATION
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,15 +6,21 @@ import logging
|
|||
import pkg_resources
|
||||
import uuid
|
||||
|
||||
from openflexure_microscope.stage.base import BaseStage
|
||||
from openflexure_microscope.stage.mock import MockStage
|
||||
from openflexure_microscope.camera.base import BaseCamera
|
||||
from openflexure_microscope.camera.mock import MockStreamer
|
||||
from openflexure_microscope.stage.mock import MissingStage
|
||||
from openflexure_microscope.camera.mock import MissingCamera
|
||||
from openflexure_microscope.stage.sanga import SangaStage
|
||||
|
||||
try:
|
||||
from openflexure_microscope.camera.pi import PiCameraStreamer
|
||||
except ImportError:
|
||||
logging.warning("Unable to import PiCameraStreamer")
|
||||
from openflexure_microscope.camera.mock import MissingCamera
|
||||
|
||||
from openflexure_microscope.utilities import serialise_array_b64
|
||||
from openflexure_microscope.config import user_settings
|
||||
from openflexure_microscope.config import user_settings, user_configuration
|
||||
|
||||
from labthings.core.lock import CompositeLock
|
||||
from labthings.core.utilities import rupdate
|
||||
|
||||
|
||||
class Microscope:
|
||||
|
|
@ -24,20 +30,26 @@ class Microscope:
|
|||
The camera and stage objects may already be initialised, and can be passed as arguments.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# Initial attributes
|
||||
self.id = uuid.uuid4() #: Microscope UUID
|
||||
self.name = self.id #: Microscope name (modifiable)
|
||||
def __init__(self, settings=user_settings, configuration=user_configuration):
|
||||
self.id = uuid.uuid4()
|
||||
self.name = self.id
|
||||
|
||||
self.fov = [0, 0] #: Microscope field-of-view in stage motor steps
|
||||
self.camera = None #: Currently connected camera object
|
||||
self.stage = None #: Currently connected stage object
|
||||
|
||||
# Store settings and configuration files
|
||||
self.settings_file = settings
|
||||
self.configuration_file = configuration
|
||||
|
||||
# Initialise with an empty composite lock
|
||||
#: :py:class:`labthings.lock.CompositeLock`: Composite lock for locking both camera and stage
|
||||
self.lock = CompositeLock([])
|
||||
|
||||
self.camera = None #: Currently connected camera object
|
||||
self.stage = None #: Currently connected stage object
|
||||
self.setup(self.configuration_file.load()) # Attach components
|
||||
|
||||
# Apply settings loaded from file
|
||||
self.apply_settings(user_settings.load())
|
||||
self.update_settings(self.settings_file.load())
|
||||
|
||||
def __enter__(self):
|
||||
"""Create microscope on context enter."""
|
||||
|
|
@ -56,58 +68,45 @@ class Microscope:
|
|||
self.stage.close()
|
||||
logging.info("Closed {}".format(self))
|
||||
|
||||
def attach(self, camera: BaseCamera, stage: BaseStage):
|
||||
def setup(self, configuration):
|
||||
"""
|
||||
Retroactively attaches a camera and stage to the microscope object.
|
||||
|
||||
Allows the microscope to be created as a "dummy", with hardware communications
|
||||
opened at a later time.
|
||||
|
||||
Args:
|
||||
camera (:py:class:`openflexure_microscope.camera.base.BaseCamera`): camera object
|
||||
stage (:py:class:`openflexure_microscope.stage.base.BaseStage`): stage object
|
||||
Attach microscope components based on initially passed configuration file
|
||||
"""
|
||||
|
||||
settings_full = self.read_settings()
|
||||
### Detector
|
||||
if configuration.get("camera"):
|
||||
camera_type = configuration["camera"].get("type")
|
||||
if camera_type in ("PiCamera", "PiCameraStreamer"):
|
||||
try:
|
||||
self.camera = PiCameraStreamer()
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
logging.warning("No compatible camera hardware found.")
|
||||
|
||||
logging.debug("Attaching camera...")
|
||||
self.camera = (
|
||||
camera
|
||||
) #: :py:class:`openflexure_microscope.camera.base.BaseCamera`: Picamera object
|
||||
### Stage
|
||||
if configuration.get("stage"):
|
||||
stage_type = configuration["stage"].get("type")
|
||||
stage_port = configuration["stage"].get("port")
|
||||
if stage_type in ("SangaBoard", "SangaStage"):
|
||||
self.stage = SangaStage(port=stage_port)
|
||||
|
||||
### Fallbacks
|
||||
if not self.camera:
|
||||
logging.info("No camera attached.")
|
||||
else:
|
||||
logging.info("Attached camera {}".format(camera))
|
||||
|
||||
if hasattr(self.camera, "lock"): # If camera has a lock
|
||||
logging.info("Attaching {} to composite lock.".format(self.camera.lock))
|
||||
# Add the lock to the microscope composite lock
|
||||
self.lock.locks.append(self.camera.lock)
|
||||
|
||||
logging.debug("Attaching stage...")
|
||||
self.stage = (
|
||||
stage
|
||||
) #: :py:class:`openflexure_microscope.stage.base.BaseStage`: OpenFlexure stage object
|
||||
self.camera = MissingCamera()
|
||||
if not self.stage:
|
||||
logging.info("No stage attached.")
|
||||
else:
|
||||
logging.info("Attached stage {}".format(stage))
|
||||
self.stage = MissingStage()
|
||||
|
||||
if hasattr(self.stage, "lock"): # If stage object has a lock
|
||||
logging.info(
|
||||
"Attaching lock {} to composite lock.".format(self.stage.lock)
|
||||
)
|
||||
# Add the lock to the microscope composite lock
|
||||
self.lock.locks.append(self.stage.lock)
|
||||
|
||||
logging.info("Reapplying settings to newly attached devices")
|
||||
self.apply_settings(settings_full)
|
||||
### Locks
|
||||
if hasattr(self.camera, "lock"):
|
||||
self.lock.locks.append(self.camera.lock)
|
||||
if hasattr(self.stage, "lock"):
|
||||
self.lock.locks.append(self.stage.lock)
|
||||
|
||||
def has_real_stage(self) -> bool:
|
||||
"""
|
||||
Check if a real (non-mock) stage is currently attached.
|
||||
"""
|
||||
if hasattr(self, "stage") and not isinstance(self.stage, MockStage):
|
||||
if hasattr(self, "stage") and not isinstance(self.stage, MissingStage):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
|
@ -116,49 +115,45 @@ class Microscope:
|
|||
"""
|
||||
Check if a real (non-mock) camera is currently attached.
|
||||
"""
|
||||
if hasattr(self, "camera") and not isinstance(self.camera, MockStreamer):
|
||||
if hasattr(self, "camera") and not isinstance(self.camera, MissingCamera):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
# Create unified status
|
||||
# Create unified state
|
||||
@property
|
||||
def status(self):
|
||||
"""Dictionary of the basic microscope status.
|
||||
def state(self):
|
||||
"""Dictionary of the basic microscope state.
|
||||
|
||||
Return:
|
||||
dict: Dictionary containing complete microscope status
|
||||
dict: Dictionary containing complete microscope state
|
||||
"""
|
||||
state = {
|
||||
"camera": self.camera.status,
|
||||
"stage": self.stage.status,
|
||||
"version": pkg_resources.get_distribution("openflexure_microscope").version,
|
||||
}
|
||||
state = {"camera": self.camera.state, "stage": self.stage.state}
|
||||
return state
|
||||
|
||||
def apply_settings(self, config: dict):
|
||||
def update_settings(self, settings: dict):
|
||||
"""
|
||||
Applies a settings dictionary to the microscope. Missing parameters will be left untouched.
|
||||
"""
|
||||
logging.debug("Microscope: Applying config: {}".format(config))
|
||||
logging.debug("Microscope: Applying settings: {}".format(settings))
|
||||
|
||||
# If attached to a camera
|
||||
if ("camera_settings" in config) and self.camera:
|
||||
self.camera.apply_settings(config["camera_settings"])
|
||||
if ("camera" in settings) and self.camera:
|
||||
self.camera.update_settings(settings["camera"])
|
||||
|
||||
# If attached to a stage
|
||||
if ("stage_settings" in config) and self.stage:
|
||||
self.stage.apply_settings(config["stage_settings"])
|
||||
if ("stage" in settings) and self.stage:
|
||||
self.stage.update_settings(settings["stage"])
|
||||
|
||||
# Todo: tidy up with some loopy goodness
|
||||
if "id" in config:
|
||||
self.id = config["id"]
|
||||
if "name" in config:
|
||||
self.name = config["name"]
|
||||
if "fov" in config:
|
||||
self.fov = config["fov"]
|
||||
# Microscope settings
|
||||
if "id" in settings:
|
||||
self.id = settings["id"]
|
||||
if "name" in settings:
|
||||
self.name = settings["name"]
|
||||
if "fov" in settings:
|
||||
self.fov = settings["fov"]
|
||||
|
||||
def read_settings(self, json_safe=False):
|
||||
def read_settings(self, full: bool = True):
|
||||
"""
|
||||
Get an updated settings dictionary.
|
||||
|
||||
|
|
@ -174,16 +169,34 @@ class Microscope:
|
|||
# If attached to a camera
|
||||
if self.camera:
|
||||
settings_current_camera = self.camera.read_settings()
|
||||
settings_current["camera_settings"] = settings_current_camera
|
||||
settings_current["camera"] = settings_current_camera
|
||||
|
||||
# Store an encoded copy of the PiCamera lens shading table, if it exists
|
||||
if hasattr(self.camera, "read_lens_shading_table"):
|
||||
# Read LST. Returns None if no LST is active
|
||||
lst_arr = self.camera.read_lens_shading_table()
|
||||
|
||||
if lst_arr:
|
||||
b64_string, dtype, shape = serialise_array_b64(lst_arr)
|
||||
|
||||
settings_current["camera"]["lens_shading_table"] = {
|
||||
"@type": "ndarray",
|
||||
"b64_string": b64_string,
|
||||
"dtype": dtype,
|
||||
"shape": shape,
|
||||
}
|
||||
|
||||
# If attached to a stage
|
||||
if self.stage:
|
||||
settings_current_stage = self.stage.read_settings()
|
||||
settings_current["stage_settings"] = settings_current_stage
|
||||
settings_current["stage"] = settings_current_stage
|
||||
|
||||
settings_full = user_settings.merge(settings_current)
|
||||
settings_full = self.settings_file.merge(settings_current)
|
||||
|
||||
return settings_full
|
||||
if full:
|
||||
return settings_full
|
||||
else:
|
||||
return settings_current
|
||||
|
||||
def save_settings(self):
|
||||
"""
|
||||
|
|
@ -196,7 +209,31 @@ class Microscope:
|
|||
self.camera.save_settings()
|
||||
if self.stage:
|
||||
self.stage.save_settings()
|
||||
user_settings.save(current_config, backup=True)
|
||||
self.settings_file.save(current_config, backup=True)
|
||||
|
||||
@property
|
||||
def configuration(self):
|
||||
initial_configuration = self.configuration_file.load()
|
||||
|
||||
current_configuration = {
|
||||
"application": {
|
||||
"name": "openflexure_microscope",
|
||||
"version": pkg_resources.get_distribution(
|
||||
"openflexure_microscope"
|
||||
).version,
|
||||
},
|
||||
"stage": {
|
||||
"type": self.stage.__class__.__name__,
|
||||
**self.stage.configuration,
|
||||
},
|
||||
"camera": {
|
||||
"type": self.camera.__class__.__name__,
|
||||
**self.camera.configuration,
|
||||
},
|
||||
}
|
||||
|
||||
initial_configuration.update(current_configuration)
|
||||
return initial_configuration
|
||||
|
||||
@property
|
||||
def metadata(self):
|
||||
|
|
@ -204,23 +241,10 @@ class Microscope:
|
|||
Microscope system metadata, to be applied to basically all captures
|
||||
"""
|
||||
system_metadata = {
|
||||
"microscope_settings": self.read_settings(),
|
||||
"microscope_state": self.status,
|
||||
"microscope_id": self.id,
|
||||
"microscope_name": self.name,
|
||||
"id": self.id,
|
||||
"settings": self.read_settings(full=False),
|
||||
"state": self.state,
|
||||
"configuration": self.configuration,
|
||||
}
|
||||
|
||||
# Store an encoded copy of the PiCamera lens shading table, if it exists
|
||||
if self.camera and hasattr(self.camera, "read_lens_shading_table"):
|
||||
# Read LST. Returns None if no LST is active
|
||||
lst_arr = self.camera.read_lens_shading_table()
|
||||
|
||||
b64_string, dtype, shape = serialise_array_b64(lst_arr)
|
||||
|
||||
system_metadata["lens_shading_table"] = {
|
||||
"b64_string": b64_string,
|
||||
"dtype": dtype,
|
||||
"shape": shape,
|
||||
}
|
||||
|
||||
return system_metadata
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"camera": {
|
||||
"type": "PiCamera"
|
||||
},
|
||||
"stage": {
|
||||
"type": "SangaStage",
|
||||
"port": null
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,3 @@
|
|||
{
|
||||
"fov": [
|
||||
4100,
|
||||
3146
|
||||
],
|
||||
"jpeg_quality": 75
|
||||
"jpeg_quality": 100
|
||||
}
|
||||
|
|
@ -32,8 +32,9 @@ def logs_file_path(filename: str):
|
|||
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
#: Path of default (first-run) microscope settings
|
||||
DEFAULT_CONFIG_FILE_PATH = os.path.join(HERE, "microscope_settings.default.json")
|
||||
|
||||
DEFAULT_SETTINGS_FILE_PATH = os.path.join(HERE, "microscope_settings.default.json")
|
||||
#: Path of default (first-run) microscope configuration
|
||||
DEFAULT_CONFIGURATION_FILE_PATH = os.path.join(HERE, "microscope_configuration.default.json")
|
||||
|
||||
# BASE PATHS
|
||||
|
||||
|
|
@ -60,7 +61,9 @@ else:
|
|||
|
||||
# SERVER PATHS
|
||||
|
||||
#: Path of microscope settings directory
|
||||
CONFIG_FILE_PATH = settings_file_path("microscope_settings.json")
|
||||
#: Path of microscope settings file
|
||||
SETTINGS_FILE_PATH = settings_file_path("microscope_settings.json")
|
||||
#: Path of microscope configuration file
|
||||
CONFIGURATION_FILE_PATH = settings_file_path("microscope_configuration.json")
|
||||
#: Path of microscope extensions directory
|
||||
OPENFLEXURE_EXTENSIONS_PATH = extensions_file_path("microscope_extensions")
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class BaseStage(metaclass=ABCMeta):
|
|||
self.lock = StrictLock(timeout=5)
|
||||
|
||||
@abstractmethod
|
||||
def apply_settings(self, config: dict):
|
||||
def update_settings(self, config: dict):
|
||||
"""Update settings from a config dictionary"""
|
||||
pass
|
||||
|
||||
|
|
@ -29,12 +29,14 @@ class BaseStage(metaclass=ABCMeta):
|
|||
|
||||
@property
|
||||
@abstractmethod
|
||||
def status(self):
|
||||
"""The general state dictionary of the board.
|
||||
Should at least contain 'position', and 'board' keys.
|
||||
Note: A None/Null value for 'board' will disable stage
|
||||
movement in the OpenFlexure eV client software,
|
||||
"""
|
||||
def state(self):
|
||||
"""The general state dictionary of the board."""
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def configuration(self):
|
||||
"""The general stage configuration."""
|
||||
pass
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import time
|
|||
import logging
|
||||
|
||||
|
||||
class MockStage(BaseStage):
|
||||
class MissingStage(BaseStage):
|
||||
def __init__(self, port=None, **kwargs):
|
||||
BaseStage.__init__(self)
|
||||
self._position = [0, 0, 0]
|
||||
|
|
@ -17,19 +17,17 @@ class MockStage(BaseStage):
|
|||
self.axis_names = ["x", "y", "z"] # Assume all sangaboards are 3 axis
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""The general status dictionary of the board."""
|
||||
status = {
|
||||
"position": self.position_map,
|
||||
"board": None,
|
||||
"firmware": None,
|
||||
"version": None,
|
||||
}
|
||||
return status
|
||||
def state(self):
|
||||
"""The general state dictionary of the board."""
|
||||
state = {"position": self.position_map}
|
||||
return state
|
||||
|
||||
def apply_settings(self, config: dict):
|
||||
@property
|
||||
def configuration(self):
|
||||
return {}
|
||||
|
||||
def update_settings(self, config: dict):
|
||||
"""Update settings from a config dictionary"""
|
||||
|
||||
# Set backlash. Expects a dictionary with axis labels
|
||||
if "backlash" in config:
|
||||
# Construct backlash array
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class SangaStage(BaseStage):
|
|||
"""Class managing serial communications with the motors for an Openflexure stage"""
|
||||
BaseStage.__init__(self)
|
||||
|
||||
self.port = port
|
||||
self.board = Sangaboard(port, **kwargs)
|
||||
|
||||
self._backlash = (
|
||||
|
|
@ -31,14 +32,17 @@ class SangaStage(BaseStage):
|
|||
self.axis_names = ["x", "y", "z"] # Assume all sangaboards are 3 axis
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""The general status dictionary of the board."""
|
||||
status = {
|
||||
"position": self.position_map,
|
||||
def state(self):
|
||||
"""The general state dictionary of the board."""
|
||||
return {"position": self.position_map}
|
||||
|
||||
@property
|
||||
def configuration(self):
|
||||
return {
|
||||
"port": self.port,
|
||||
"board": self.board.board,
|
||||
"firmware": self.board.firmware,
|
||||
}
|
||||
return status
|
||||
|
||||
@property
|
||||
def n_axes(self):
|
||||
|
|
@ -81,7 +85,7 @@ class SangaStage(BaseStage):
|
|||
else:
|
||||
self._backlash = np.array([int(blsh)] * self.n_axes, dtype=np.int)
|
||||
|
||||
def apply_settings(self, config: dict):
|
||||
def update_settings(self, config: dict):
|
||||
"""Update settings from a config dictionary"""
|
||||
|
||||
# Set backlash. Expects a dictionary with axis labels
|
||||
|
|
|
|||
537
poetry.lock
generated
537
poetry.lock
generated
|
|
@ -14,14 +14,6 @@ optional = false
|
|||
python-versions = ">=3.5"
|
||||
version = "3.2.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["PyYAML (>=3.10)", "prance (>=0.11)", "marshmallow (>=2.19.2)", "pytest", "mock", "flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)", "tox"]
|
||||
docs = ["marshmallow (>=2.19.2)", "pyyaml (5.2)", "sphinx (2.3.0)", "sphinx-issues (1.2.0)", "sphinx-rtd-theme (0.4.3)"]
|
||||
lint = ["flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)"]
|
||||
tests = ["PyYAML (>=3.10)", "prance (>=0.11)", "marshmallow (>=2.19.2)", "pytest", "mock"]
|
||||
validation = ["prance (>=0.11)"]
|
||||
yaml = ["PyYAML (>=3.10)"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
|
|
@ -55,12 +47,6 @@ optional = false
|
|||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "19.3.0"
|
||||
|
||||
[package.extras]
|
||||
azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"]
|
||||
dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"]
|
||||
docs = ["sphinx", "zope.interface"]
|
||||
tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Internationalization utilities"
|
||||
|
|
@ -86,9 +72,6 @@ attrs = ">=17.4.0"
|
|||
click = ">=6.5"
|
||||
toml = ">=0.9.4"
|
||||
|
||||
[package.extras]
|
||||
d = ["aiohttp (>=3.3.2)"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
|
|
@ -144,11 +127,6 @@ Werkzeug = ">=0.15"
|
|||
click = ">=5.1"
|
||||
itsdangerous = ">=0.24"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"]
|
||||
docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"]
|
||||
dotenv = ["python-dotenv"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "A Flask extension adding a decorator for CORS support"
|
||||
|
|
@ -185,12 +163,6 @@ optional = false
|
|||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "4.3.21"
|
||||
|
||||
[package.extras]
|
||||
pipfile = ["pipreqs", "requirementslib"]
|
||||
pyproject = ["toml"]
|
||||
requirements = ["pipreqs", "pip-api"]
|
||||
xdg_home = ["appdirs (>=1.4.0)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Various helpers to pass data to untrusted environments and back."
|
||||
|
|
@ -204,15 +176,12 @@ category = "main"
|
|||
description = "A very fast and expressive template engine."
|
||||
name = "jinja2"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "2.10.3"
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "2.11.0"
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=0.23"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["Babel (>=0.8)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = ""
|
||||
|
|
@ -229,7 +198,7 @@ marshmallow = "^3.3.0"
|
|||
webargs = "^5.5.2"
|
||||
|
||||
[package.source]
|
||||
reference = "23732b17074486367541439a1caa2eb61b9cc22f"
|
||||
reference = "cb1ef6a1f8849f4d58601a7a9617ffea12703f39"
|
||||
type = "git"
|
||||
url = "https://github.com/labthings/python-labthings.git"
|
||||
[[package]]
|
||||
|
|
@ -256,12 +225,6 @@ optional = false
|
|||
python-versions = ">=3.5"
|
||||
version = "3.3.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pytest", "pytz", "simplejson", "mypy (0.750)", "flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)", "tox"]
|
||||
docs = ["sphinx (2.2.2)", "sphinx-issues (1.2.0)", "alabaster (0.7.12)", "sphinx-version-warning (1.1.2)"]
|
||||
lint = ["mypy (0.750)", "flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)"]
|
||||
tests = ["pytest", "pytz", "simplejson"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "McCabe checker, plugin for flake8"
|
||||
|
|
@ -298,11 +261,6 @@ optional = true
|
|||
python-versions = "*"
|
||||
version = "1.13.1b0"
|
||||
|
||||
[package.extras]
|
||||
array = ["numpy"]
|
||||
doc = ["sphinx"]
|
||||
test = ["coverage", "pytest", "mock", "pillow", "numpy"]
|
||||
|
||||
[package.source]
|
||||
reference = "b39c2b6e42f5f7f57bb46eafcb5c9e2bbdb5d0cb"
|
||||
type = "git"
|
||||
|
|
@ -353,6 +311,17 @@ optional = false
|
|||
python-versions = "*"
|
||||
version = "3.4"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Extensions to the standard Python datetime module"
|
||||
name = "python-dateutil"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
version = "2.8.1"
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.5"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "World timezone definitions, modern and historical"
|
||||
|
|
@ -383,10 +352,6 @@ chardet = ">=3.0.2,<3.1.0"
|
|||
idna = ">=2.5,<2.9"
|
||||
urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
|
||||
|
||||
[package.extras]
|
||||
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"]
|
||||
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "a python refactoring library..."
|
||||
|
|
@ -414,6 +379,14 @@ version = "1.3.0"
|
|||
[package.dependencies]
|
||||
numpy = ">=1.13.3"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Simple, fast, extensible JSON encoder/decoder for Python"
|
||||
name = "simplejson"
|
||||
optional = false
|
||||
python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
version = "3.17.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
|
|
@ -457,10 +430,6 @@ sphinxcontrib-jsmath = "*"
|
|||
sphinxcontrib-qthelp = "*"
|
||||
sphinxcontrib-serializinghtml = "*"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinxcontrib-websupport"]
|
||||
test = ["pytest", "pytest-cov", "html5lib", "flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.761)", "docutils-stubs"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = ""
|
||||
|
|
@ -469,9 +438,6 @@ optional = false
|
|||
python-versions = "*"
|
||||
version = "1.0.1"
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest", "flake8", "mypy"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = ""
|
||||
|
|
@ -480,9 +446,6 @@ optional = false
|
|||
python-versions = "*"
|
||||
version = "1.0.1"
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest", "flake8", "mypy"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = ""
|
||||
|
|
@ -491,9 +454,6 @@ optional = false
|
|||
python-versions = "*"
|
||||
version = "1.0.2"
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest", "flake8", "mypy", "html5lib"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Sphinx domain for documenting HTTP APIs"
|
||||
|
|
@ -514,9 +474,6 @@ optional = false
|
|||
python-versions = ">=3.5"
|
||||
version = "1.0.1"
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest", "flake8", "mypy"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = ""
|
||||
|
|
@ -525,9 +482,6 @@ optional = false
|
|||
python-versions = "*"
|
||||
version = "1.0.2"
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest", "flake8", "mypy"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = ""
|
||||
|
|
@ -536,9 +490,6 @@ optional = false
|
|||
python-versions = "*"
|
||||
version = "1.1.3"
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest", "flake8", "mypy"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Python Library for Tom's Obvious, Minimal Language"
|
||||
|
|
@ -564,28 +515,17 @@ optional = false
|
|||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
|
||||
version = "1.25.8"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotlipy (>=0.6.0)"]
|
||||
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
|
||||
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Declarative parsing and validation of HTTP request objects, with built-in support for popular web frameworks, including Flask, Django, Bottle, Tornado, Pyramid, webapp2, Falcon, and aiohttp."
|
||||
name = "webargs"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "5.5.2"
|
||||
version = "5.5.3"
|
||||
|
||||
[package.dependencies]
|
||||
marshmallow = ">=2.15.2"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pytest", "mock", "webtest (2.0.33)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "flake8 (3.7.8)", "pre-commit (>=1.17,<2.0)", "tox", "webtest-aiohttp (2.0.0)", "pytest-aiohttp (>=0.3.0)", "aiohttp (>=3.0.0)", "mypy (0.730)", "flake8-bugbear (19.8.0)"]
|
||||
docs = ["Sphinx (2.2.0)", "sphinx-issues (1.2.0)", "sphinx-typlog-theme (0.7.3)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "aiohttp (>=3.0.0)"]
|
||||
frameworks = ["Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "aiohttp (>=3.0.0)"]
|
||||
lint = ["flake8 (3.7.8)", "pre-commit (>=1.17,<2.0)", "mypy (0.730)", "flake8-bugbear (19.8.0)"]
|
||||
tests = ["pytest", "mock", "webtest (2.0.33)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "webtest-aiohttp (2.0.0)", "pytest-aiohttp (>=0.3.0)", "aiohttp (>=3.0.0)"]
|
||||
simplejson = ">=2.1.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
|
|
@ -593,12 +533,7 @@ description = "The comprehensive WSGI web application library."
|
|||
name = "werkzeug"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "0.16.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"]
|
||||
termcolor = ["termcolor"]
|
||||
watchdog = ["watchdog"]
|
||||
version = "0.16.1"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
|
|
@ -612,373 +547,63 @@ version = "1.11.2"
|
|||
rpi = ["picamera", "RPi.GPIO"]
|
||||
|
||||
[metadata]
|
||||
content-hash = "cd4e5d59e9a63ae053a05c994a61a46be87a29ec6867bcd47f6ef9b39bb785d3"
|
||||
content-hash = "34da76b3341563a0bebfa3e4a3fca90f34b857027e90811fed92b106ad06c2ba"
|
||||
python-versions = "^3.6"
|
||||
|
||||
[metadata.files]
|
||||
alabaster = [
|
||||
{file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
|
||||
{file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
|
||||
]
|
||||
apispec = [
|
||||
{file = "apispec-3.2.0-py2.py3-none-any.whl", hash = "sha256:f2ecb3a6534cfb42cf69b5c1222d2c0e695aab5ec3d7edde31b7354670948f05"},
|
||||
{file = "apispec-3.2.0.tar.gz", hash = "sha256:c1625ae910f699c9adb21928693a3245b9faab6843f1b1c94ab2d505549cd78a"},
|
||||
]
|
||||
appdirs = [
|
||||
{file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"},
|
||||
{file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"},
|
||||
]
|
||||
astroid = [
|
||||
{file = "astroid-2.3.3-py3-none-any.whl", hash = "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"},
|
||||
{file = "astroid-2.3.3.tar.gz", hash = "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a"},
|
||||
]
|
||||
attrs = [
|
||||
{file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
|
||||
{file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
|
||||
]
|
||||
babel = [
|
||||
{file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"},
|
||||
{file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"},
|
||||
]
|
||||
black = [
|
||||
{file = "black-18.9b0-py36-none-any.whl", hash = "sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739"},
|
||||
{file = "black-18.9b0.tar.gz", hash = "sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"},
|
||||
]
|
||||
certifi = [
|
||||
{file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"},
|
||||
{file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"},
|
||||
]
|
||||
chardet = [
|
||||
{file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
|
||||
{file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
|
||||
]
|
||||
click = [
|
||||
{file = "Click-7.0-py2.py3-none-any.whl", hash = "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13"},
|
||||
{file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"},
|
||||
]
|
||||
colorama = [
|
||||
{file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
|
||||
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
|
||||
]
|
||||
docutils = [
|
||||
{file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"},
|
||||
{file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"},
|
||||
]
|
||||
flask = [
|
||||
{file = "Flask-1.1.1-py2.py3-none-any.whl", hash = "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"},
|
||||
{file = "Flask-1.1.1.tar.gz", hash = "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52"},
|
||||
]
|
||||
flask-cors = [
|
||||
{file = "Flask-Cors-3.0.8.tar.gz", hash = "sha256:72170423eb4612f0847318afff8c247b38bd516b7737adfc10d1c2cdbb382d16"},
|
||||
{file = "Flask_Cors-3.0.8-py2.py3-none-any.whl", hash = "sha256:f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"},
|
||||
{file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"},
|
||||
]
|
||||
imagesize = [
|
||||
{file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"},
|
||||
{file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"},
|
||||
]
|
||||
isort = [
|
||||
{file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"},
|
||||
{file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"},
|
||||
]
|
||||
itsdangerous = [
|
||||
{file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"},
|
||||
{file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"},
|
||||
]
|
||||
jinja2 = [
|
||||
{file = "Jinja2-2.10.3-py2.py3-none-any.whl", hash = "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f"},
|
||||
{file = "Jinja2-2.10.3.tar.gz", hash = "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"},
|
||||
]
|
||||
[metadata.hashes]
|
||||
alabaster = ["446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", "a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"]
|
||||
apispec = ["c1625ae910f699c9adb21928693a3245b9faab6843f1b1c94ab2d505549cd78a", "f2ecb3a6534cfb42cf69b5c1222d2c0e695aab5ec3d7edde31b7354670948f05"]
|
||||
appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"]
|
||||
astroid = ["71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a", "840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"]
|
||||
attrs = ["08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", "f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"]
|
||||
babel = ["1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38", "d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"]
|
||||
black = ["817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", "e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"]
|
||||
certifi = ["017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", "25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"]
|
||||
chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"]
|
||||
click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"]
|
||||
colorama = ["7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"]
|
||||
docutils = ["0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", "c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"]
|
||||
flask = ["13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52", "45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"]
|
||||
flask-cors = ["72170423eb4612f0847318afff8c247b38bd516b7737adfc10d1c2cdbb382d16", "f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a"]
|
||||
idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"]
|
||||
imagesize = ["6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1", "b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"]
|
||||
isort = ["54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", "6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"]
|
||||
itsdangerous = ["321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", "b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"]
|
||||
jinja2 = ["6e7a3c2934694d59ad334c93dd1b6c96699cf24c53fdb8ec848ac6b23e685734", "d6609ae5ec3d56212ca7d802eda654eaf2310000816ce815361041465b108be4"]
|
||||
labthings = []
|
||||
lazy-object-proxy = [
|
||||
{file = "lazy-object-proxy-1.4.3.tar.gz", hash = "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp27-cp27m-win32.whl", hash = "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp34-cp34m-win32.whl", hash = "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp34-cp34m-win_amd64.whl", hash = "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp38-cp38-win32.whl", hash = "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239"},
|
||||
]
|
||||
markupsafe = [
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"},
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"},
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"},
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"},
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"},
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"},
|
||||
{file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"},
|
||||
{file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"},
|
||||
{file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"},
|
||||
{file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"},
|
||||
{file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"},
|
||||
{file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"},
|
||||
{file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"},
|
||||
{file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"},
|
||||
{file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
|
||||
{file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"},
|
||||
{file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"},
|
||||
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"},
|
||||
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"},
|
||||
{file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
|
||||
{file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"},
|
||||
{file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"},
|
||||
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"},
|
||||
{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.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
|
||||
]
|
||||
marshmallow = [
|
||||
{file = "marshmallow-3.3.0-py2.py3-none-any.whl", hash = "sha256:3e53dd9e9358977a3929e45cdbe4a671f9eff53a7d6a23f33ed3eab8c1890d8f"},
|
||||
{file = "marshmallow-3.3.0.tar.gz", hash = "sha256:0ba81b6da4ae69eb229b74b3c741ff13fe04fb899824377b1aff5aaa1a9fd46e"},
|
||||
]
|
||||
mccabe = [
|
||||
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
|
||||
{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"},
|
||||
]
|
||||
packaging = [
|
||||
{file = "packaging-20.1-py2.py3-none-any.whl", hash = "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73"},
|
||||
{file = "packaging-20.1.tar.gz", hash = "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"},
|
||||
]
|
||||
lazy-object-proxy = ["0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", "194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", "1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", "4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", "48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", "5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", "59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", "8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", "9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", "9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", "97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", "9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", "a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", "a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", "ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", "cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", "d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", "d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", "eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", "efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", "f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"]
|
||||
markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"]
|
||||
marshmallow = ["0ba81b6da4ae69eb229b74b3c741ff13fe04fb899824377b1aff5aaa1a9fd46e", "3e53dd9e9358977a3929e45cdbe4a671f9eff53a7d6a23f33ed3eab8c1890d8f"]
|
||||
mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"]
|
||||
numpy = ["1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6", "17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e", "20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc", "2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc", "39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a", "56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa", "590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3", "70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121", "77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971", "9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26", "9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd", "ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480", "b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec", "b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77", "b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57", "c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07", "cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572", "d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73", "e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca", "e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474", "f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5"]
|
||||
packaging = ["170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73", "e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"]
|
||||
picamera = []
|
||||
pillow = [
|
||||
{file = "Pillow-5.4.1-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1"},
|
||||
{file = "Pillow-5.4.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7"},
|
||||
{file = "Pillow-5.4.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e"},
|
||||
{file = "Pillow-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c"},
|
||||
{file = "Pillow-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f"},
|
||||
{file = "Pillow-5.4.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c"},
|
||||
{file = "Pillow-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc"},
|
||||
{file = "Pillow-5.4.1-cp34-cp34m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1"},
|
||||
{file = "Pillow-5.4.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8"},
|
||||
{file = "Pillow-5.4.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055"},
|
||||
{file = "Pillow-5.4.1-cp34-cp34m-win32.whl", hash = "sha256:a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6"},
|
||||
{file = "Pillow-5.4.1-cp34-cp34m-win_amd64.whl", hash = "sha256:505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf"},
|
||||
{file = "Pillow-5.4.1-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239"},
|
||||
{file = "Pillow-5.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3"},
|
||||
{file = "Pillow-5.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697"},
|
||||
{file = "Pillow-5.4.1-cp35-cp35m-win32.whl", hash = "sha256:0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7"},
|
||||
{file = "Pillow-5.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe"},
|
||||
{file = "Pillow-5.4.1-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca"},
|
||||
{file = "Pillow-5.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e"},
|
||||
{file = "Pillow-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad"},
|
||||
{file = "Pillow-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a"},
|
||||
{file = "Pillow-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356"},
|
||||
{file = "Pillow-5.4.1-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1"},
|
||||
{file = "Pillow-5.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9"},
|
||||
{file = "Pillow-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3"},
|
||||
{file = "Pillow-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f"},
|
||||
{file = "Pillow-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3"},
|
||||
{file = "Pillow-5.4.1-pp260-pypy_41-win32.whl", hash = "sha256:a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494"},
|
||||
{file = "Pillow-5.4.1-pp360-pp360-win32.whl", hash = "sha256:a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000"},
|
||||
{file = "Pillow-5.4.1-py2.7-macosx-10.13-x86_64.egg", hash = "sha256:634209852cc06c0c1243cc74f8fdc8f7444d866221de51125f7b696d775ec5ca"},
|
||||
{file = "Pillow-5.4.1-py2.7-win-amd64.egg", hash = "sha256:0cf0208500df8d0c3cad6383cd98a2d038b0678fd4f777a8f7e442c5faeee81d"},
|
||||
{file = "Pillow-5.4.1-py2.7-win32.egg", hash = "sha256:f71ff657e63a9b24cac254bb8c9bd3c89c7a1b5e00ee4b3997ca1c18100dac28"},
|
||||
{file = "Pillow-5.4.1-py3.4-win-amd64.egg", hash = "sha256:01a501be4ae05fd714d269cb9c9f145518e58e73faa3f140ddb67fae0c2607b1"},
|
||||
{file = "Pillow-5.4.1-py3.4-win32.egg", hash = "sha256:4baab2d2da57b0d9d544a2ce0f461374dd90ccbcf723fe46689aff906d43a964"},
|
||||
{file = "Pillow-5.4.1-py3.5-win-amd64.egg", hash = "sha256:f62b1aeb5c2ced8babd4fbba9c74cbef9de309f5ed106184b12d9778a3971f15"},
|
||||
{file = "Pillow-5.4.1-py3.5-win32.egg", hash = "sha256:e9f13711780c981d6eadd6042af40e172548c54b06266a1aabda7de192db0838"},
|
||||
{file = "Pillow-5.4.1-py3.6-win-amd64.egg", hash = "sha256:07c35919f983c2c593498edcc126ad3a94154184899297cc9d27a6587672cbaa"},
|
||||
{file = "Pillow-5.4.1-py3.6-win32.egg", hash = "sha256:87fe838f9dac0597f05f2605c0700b1926f9390c95df6af45d83141e0c514bd9"},
|
||||
{file = "Pillow-5.4.1-py3.7-win-amd64.egg", hash = "sha256:c8939dba1a37960a502b1a030a4465c46dd2c2bca7adf05fa3af6bea594e720e"},
|
||||
{file = "Pillow-5.4.1-py3.7-win32.egg", hash = "sha256:5337ac3280312aa065ed0a8ec1e4b6142e9f15c31baed36b5cd964745853243f"},
|
||||
{file = "Pillow-5.4.1.tar.gz", hash = "sha256:5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f"},
|
||||
{file = "Pillow-5.4.1.win-amd64-py2.7.exe", hash = "sha256:e1555d4fda1db8005de72acf2ded1af660febad09b4708430091159e8ae1963e"},
|
||||
{file = "Pillow-5.4.1.win-amd64-py3.4.exe", hash = "sha256:52e2e56fc3706d8791761a157115dc8391319720ad60cc32992350fda74b6be2"},
|
||||
{file = "Pillow-5.4.1.win-amd64-py3.5.exe", hash = "sha256:ba6ef2bd62671c7fb9cdb3277414e87a5cd38b86721039ada1464f7452ad30b2"},
|
||||
{file = "Pillow-5.4.1.win-amd64-py3.6.exe", hash = "sha256:39fbd5d62167197318a0371b2a9c699ce261b6800bb493eadde2ba30d868fe8c"},
|
||||
{file = "Pillow-5.4.1.win-amd64-py3.7.exe", hash = "sha256:5ccd97e0f01f42b7e35907272f0f8ad2c3660a482d799a0c564c7d50e83604d4"},
|
||||
{file = "Pillow-5.4.1.win32-py2.7.exe", hash = "sha256:db418635ea20528f247203bf131b40636f77c8209a045b89fa3badb89e1fcea0"},
|
||||
{file = "Pillow-5.4.1.win32-py3.4.exe", hash = "sha256:ac036b6a6bac7010c58e643d78c234c2f7dc8bb7e591bd8bc3555cf4b1527c28"},
|
||||
{file = "Pillow-5.4.1.win32-py3.5.exe", hash = "sha256:f0e3288b92ca5dbb1649bd00e80ef652a72b657dc94989fa9c348253d179054b"},
|
||||
{file = "Pillow-5.4.1.win32-py3.6.exe", hash = "sha256:4132c78200372045bb348fcad8d52518c8f5cfc077b1089949381ee4a61f1c6d"},
|
||||
{file = "Pillow-5.4.1.win32-py3.7.exe", hash = "sha256:75d1f20bd8072eff92c5f457c266a61619a02d03ece56544195c56d41a1a0522"},
|
||||
]
|
||||
pygments = [
|
||||
{file = "Pygments-2.5.2-py2.py3-none-any.whl", hash = "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b"},
|
||||
{file = "Pygments-2.5.2.tar.gz", hash = "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"},
|
||||
]
|
||||
pylint = [
|
||||
{file = "pylint-2.4.4-py3-none-any.whl", hash = "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"},
|
||||
{file = "pylint-2.4.4.tar.gz", hash = "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"},
|
||||
{file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"},
|
||||
]
|
||||
pyserial = [
|
||||
{file = "pyserial-3.4-py2.py3-none-any.whl", hash = "sha256:e0770fadba80c31013896c7e6ef703f72e7834965954a78e71a3049488d4d7d8"},
|
||||
{file = "pyserial-3.4.tar.gz", hash = "sha256:6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627"},
|
||||
]
|
||||
pytz = [
|
||||
{file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"},
|
||||
{file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"},
|
||||
]
|
||||
pyyaml = [
|
||||
{file = "PyYAML-3.13-cp27-cp27m-win32.whl", hash = "sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f"},
|
||||
{file = "PyYAML-3.13-cp27-cp27m-win_amd64.whl", hash = "sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537"},
|
||||
{file = "PyYAML-3.13-cp34-cp34m-win32.whl", hash = "sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3"},
|
||||
{file = "PyYAML-3.13-cp34-cp34m-win_amd64.whl", hash = "sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04"},
|
||||
{file = "PyYAML-3.13-cp35-cp35m-win32.whl", hash = "sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1"},
|
||||
{file = "PyYAML-3.13-cp35-cp35m-win_amd64.whl", hash = "sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613"},
|
||||
{file = "PyYAML-3.13-cp36-cp36m-win32.whl", hash = "sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a"},
|
||||
{file = "PyYAML-3.13-cp36-cp36m-win_amd64.whl", hash = "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b"},
|
||||
{file = "PyYAML-3.13-cp37-cp37m-win32.whl", hash = "sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"},
|
||||
{file = "PyYAML-3.13-cp37-cp37m-win_amd64.whl", hash = "sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1"},
|
||||
{file = "PyYAML-3.13.tar.gz", hash = "sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf"},
|
||||
]
|
||||
requests = [
|
||||
{file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"},
|
||||
{file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"},
|
||||
]
|
||||
rope = [
|
||||
{file = "rope-0.14.0-py2-none-any.whl", hash = "sha256:6b728fdc3e98a83446c27a91fc5d56808a004f8beab7a31ab1d7224cecc7d969"},
|
||||
{file = "rope-0.14.0-py3-none-any.whl", hash = "sha256:f0dcf719b63200d492b85535ebe5ea9b29e0d0b8aebeb87fe03fc1a65924fdaf"},
|
||||
{file = "rope-0.14.0.tar.gz", hash = "sha256:c5c5a6a87f7b1a2095fb311135e2a3d1f194f5ecb96900fdd0a9100881f48aaf"},
|
||||
]
|
||||
"rpi.gpio" = [
|
||||
{file = "RPi.GPIO-0.6.5.tar.gz", hash = "sha256:a4210ad63bfe844e43995286de0d3950dfacfa0f3799bb9392770ac54a7d2e47"},
|
||||
]
|
||||
scipy = [
|
||||
{file = "scipy-1.3.0-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:4907040f62b91c2e170359c3d36c000af783f0fa1516a83d6c1517cde0af5340"},
|
||||
{file = "scipy-1.3.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1db9f964ed9c52dc5bd6127f0dd90ac89791daa690a5665cc01eae185912e1ba"},
|
||||
{file = "scipy-1.3.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:adadeeae5500de0da2b9e8dd478520d0a9945b577b2198f2462555e68f58e7ef"},
|
||||
{file = "scipy-1.3.0-cp35-cp35m-win32.whl", hash = "sha256:03b1e0775edbe6a4c64effb05fff2ce1429b76d29d754aa5ee2d848b60033351"},
|
||||
{file = "scipy-1.3.0-cp35-cp35m-win_amd64.whl", hash = "sha256:a7695a378c2ce402405ea37b12c7a338a8755e081869bd6b95858893ceb617ae"},
|
||||
{file = "scipy-1.3.0-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:826b9f5fbb7f908a13aa1efd4b7321e36992f5868d5d8311c7b40cf9b11ca0e7"},
|
||||
{file = "scipy-1.3.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b283a76a83fe463c9587a2c88003f800e08c3929dfbeba833b78260f9c209785"},
|
||||
{file = "scipy-1.3.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:db61a640ca20f237317d27bc658c1fc54c7581ff7f6502d112922dc285bdabee"},
|
||||
{file = "scipy-1.3.0-cp36-cp36m-win32.whl", hash = "sha256:409846be9d6bdcbd78b9e5afe2f64b2da5a923dd7c1cd0615ce589489533fdbb"},
|
||||
{file = "scipy-1.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c19a7389ab3cd712058a8c3c9ffd8d27a57f3d84b9c91a931f542682bb3d269d"},
|
||||
{file = "scipy-1.3.0-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:09d008237baabf52a5d4f5a6fcf9b3c03408f3f61a69c404472a16861a73917e"},
|
||||
{file = "scipy-1.3.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a84c31e8409b420c3ca57fd30c7589378d6fdc8d155d866a7f8e6e80dec6fd06"},
|
||||
{file = "scipy-1.3.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c5ea60ece0c0c1c849025bfc541b60a6751b491b6f11dd9ef37ab5b8c9041921"},
|
||||
{file = "scipy-1.3.0-cp37-cp37m-win32.whl", hash = "sha256:6c0543f2fdd38dee631fb023c0f31c284a532d205590b393d72009c14847f5b1"},
|
||||
{file = "scipy-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:10325f0ffac2400b1ec09537b7e403419dcd25d9fee602a44e8a32119af9079e"},
|
||||
{file = "scipy-1.3.0.tar.gz", hash = "sha256:c3bb4bd2aca82fb498247deeac12265921fe231502a6bc6edea3ee7fe6c40a7a"},
|
||||
]
|
||||
six = [
|
||||
{file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"},
|
||||
{file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"},
|
||||
]
|
||||
snowballstemmer = [
|
||||
{file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"},
|
||||
{file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"},
|
||||
]
|
||||
sphinx = [
|
||||
{file = "Sphinx-2.3.1-py3-none-any.whl", hash = "sha256:298537cb3234578b2d954ff18c5608468229e116a9757af3b831c2b2b4819159"},
|
||||
{file = "Sphinx-2.3.1.tar.gz", hash = "sha256:e6e766b74f85f37a5f3e0773a1e1be8db3fcb799deb58ca6d18b70b0b44542a5"},
|
||||
]
|
||||
sphinxcontrib-applehelp = [
|
||||
{file = "sphinxcontrib-applehelp-1.0.1.tar.gz", hash = "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897"},
|
||||
{file = "sphinxcontrib_applehelp-1.0.1-py2.py3-none-any.whl", hash = "sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"},
|
||||
]
|
||||
sphinxcontrib-devhelp = [
|
||||
{file = "sphinxcontrib-devhelp-1.0.1.tar.gz", hash = "sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34"},
|
||||
{file = "sphinxcontrib_devhelp-1.0.1-py2.py3-none-any.whl", hash = "sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"},
|
||||
]
|
||||
sphinxcontrib-htmlhelp = [
|
||||
{file = "sphinxcontrib-htmlhelp-1.0.2.tar.gz", hash = "sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422"},
|
||||
{file = "sphinxcontrib_htmlhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"},
|
||||
]
|
||||
sphinxcontrib-httpdomain = [
|
||||
{file = "sphinxcontrib-httpdomain-1.7.0.tar.gz", hash = "sha256:ac40b4fba58c76b073b03931c7b8ead611066a6aebccafb34dc19694f4eb6335"},
|
||||
{file = "sphinxcontrib_httpdomain-1.7.0-py2.py3-none-any.whl", hash = "sha256:1fb5375007d70bf180cdd1c79e741082be7aa2d37ba99efe561e1c2e3f38191e"},
|
||||
]
|
||||
sphinxcontrib-jsmath = [
|
||||
{file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
|
||||
{file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
|
||||
]
|
||||
sphinxcontrib-qthelp = [
|
||||
{file = "sphinxcontrib-qthelp-1.0.2.tar.gz", hash = "sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"},
|
||||
{file = "sphinxcontrib_qthelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20"},
|
||||
]
|
||||
sphinxcontrib-serializinghtml = [
|
||||
{file = "sphinxcontrib-serializinghtml-1.1.3.tar.gz", hash = "sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227"},
|
||||
{file = "sphinxcontrib_serializinghtml-1.1.3-py2.py3-none-any.whl", hash = "sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"},
|
||||
]
|
||||
toml = [
|
||||
{file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"},
|
||||
{file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"},
|
||||
{file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"},
|
||||
]
|
||||
typed-ast = [
|
||||
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
|
||||
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"},
|
||||
{file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"},
|
||||
{file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"},
|
||||
{file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"},
|
||||
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"},
|
||||
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"},
|
||||
{file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"},
|
||||
{file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"},
|
||||
{file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"},
|
||||
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"},
|
||||
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"},
|
||||
{file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"},
|
||||
{file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"},
|
||||
{file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"},
|
||||
{file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"},
|
||||
{file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"},
|
||||
{file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
|
||||
{file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
|
||||
{file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
|
||||
{file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
|
||||
]
|
||||
urllib3 = [
|
||||
{file = "urllib3-1.25.8-py2.py3-none-any.whl", hash = "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc"},
|
||||
{file = "urllib3-1.25.8.tar.gz", hash = "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"},
|
||||
]
|
||||
webargs = [
|
||||
{file = "webargs-5.5.2-py2-none-any.whl", hash = "sha256:3f9dc15de183d356c9a0acc159c100ea0506c0c240c1e6f1d8b308c5fed4dbbd"},
|
||||
{file = "webargs-5.5.2-py3-none-any.whl", hash = "sha256:fa4ad3ad9b38bedd26c619264fdc50d7ae014b49186736bca851e5b5228f2a1b"},
|
||||
{file = "webargs-5.5.2.tar.gz", hash = "sha256:3beca296598067cec24a0b6f91c0afcc19b6e3c4d84ab026b931669628bb47b4"},
|
||||
]
|
||||
werkzeug = [
|
||||
{file = "Werkzeug-0.16.0-py2.py3-none-any.whl", hash = "sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4"},
|
||||
{file = "Werkzeug-0.16.0.tar.gz", hash = "sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7"},
|
||||
]
|
||||
wrapt = [
|
||||
{file = "wrapt-1.11.2.tar.gz", hash = "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"},
|
||||
]
|
||||
pillow = ["01a501be4ae05fd714d269cb9c9f145518e58e73faa3f140ddb67fae0c2607b1", "051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e", "07c35919f983c2c593498edcc126ad3a94154184899297cc9d27a6587672cbaa", "0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7", "0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a", "0cf0208500df8d0c3cad6383cd98a2d038b0678fd4f777a8f7e442c5faeee81d", "163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3", "18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1", "24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1", "267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7", "3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1", "39fbd5d62167197318a0371b2a9c699ce261b6800bb493eadde2ba30d868fe8c", "4132c78200372045bb348fcad8d52518c8f5cfc077b1089949381ee4a61f1c6d", "4baab2d2da57b0d9d544a2ce0f461374dd90ccbcf723fe46689aff906d43a964", "4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3", "4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055", "505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf", "5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f", "52e2e56fc3706d8791761a157115dc8391319720ad60cc32992350fda74b6be2", "5337ac3280312aa065ed0a8ec1e4b6142e9f15c31baed36b5cd964745853243f", "5ccd97e0f01f42b7e35907272f0f8ad2c3660a482d799a0c564c7d50e83604d4", "5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f", "634209852cc06c0c1243cc74f8fdc8f7444d866221de51125f7b696d775ec5ca", "75d1f20bd8072eff92c5f457c266a61619a02d03ece56544195c56d41a1a0522", "7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239", "801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe", "825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c", "87fe838f9dac0597f05f2605c0700b1926f9390c95df6af45d83141e0c514bd9", "9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697", "a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494", "a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356", "a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6", "a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000", "ac036b6a6bac7010c58e643d78c234c2f7dc8bb7e591bd8bc3555cf4b1527c28", "b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f", "ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c", "ba6ef2bd62671c7fb9cdb3277414e87a5cd38b86721039ada1464f7452ad30b2", "c8939dba1a37960a502b1a030a4465c46dd2c2bca7adf05fa3af6bea594e720e", "cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca", "cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8", "d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3", "d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad", "db418635ea20528f247203bf131b40636f77c8209a045b89fa3badb89e1fcea0", "e1555d4fda1db8005de72acf2ded1af660febad09b4708430091159e8ae1963e", "e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9", "e9f13711780c981d6eadd6042af40e172548c54b06266a1aabda7de192db0838", "f0e3288b92ca5dbb1649bd00e80ef652a72b657dc94989fa9c348253d179054b", "f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc", "f62b1aeb5c2ced8babd4fbba9c74cbef9de309f5ed106184b12d9778a3971f15", "f71ff657e63a9b24cac254bb8c9bd3c89c7a1b5e00ee4b3997ca1c18100dac28", "fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e"]
|
||||
pygments = ["2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", "98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"]
|
||||
pylint = ["3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd", "886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"]
|
||||
pyparsing = ["4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", "c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"]
|
||||
pyserial = ["6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627", "e0770fadba80c31013896c7e6ef703f72e7834965954a78e71a3049488d4d7d8"]
|
||||
python-dateutil = ["73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", "75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"]
|
||||
pytz = ["1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", "b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"]
|
||||
pyyaml = ["3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", "3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", "40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", "558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", "a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", "aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", "bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", "d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", "d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", "e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", "e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"]
|
||||
requests = ["11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"]
|
||||
rope = ["6b728fdc3e98a83446c27a91fc5d56808a004f8beab7a31ab1d7224cecc7d969", "c5c5a6a87f7b1a2095fb311135e2a3d1f194f5ecb96900fdd0a9100881f48aaf", "f0dcf719b63200d492b85535ebe5ea9b29e0d0b8aebeb87fe03fc1a65924fdaf"]
|
||||
"rpi.gpio" = ["a4210ad63bfe844e43995286de0d3950dfacfa0f3799bb9392770ac54a7d2e47"]
|
||||
scipy = ["03b1e0775edbe6a4c64effb05fff2ce1429b76d29d754aa5ee2d848b60033351", "09d008237baabf52a5d4f5a6fcf9b3c03408f3f61a69c404472a16861a73917e", "10325f0ffac2400b1ec09537b7e403419dcd25d9fee602a44e8a32119af9079e", "1db9f964ed9c52dc5bd6127f0dd90ac89791daa690a5665cc01eae185912e1ba", "409846be9d6bdcbd78b9e5afe2f64b2da5a923dd7c1cd0615ce589489533fdbb", "4907040f62b91c2e170359c3d36c000af783f0fa1516a83d6c1517cde0af5340", "6c0543f2fdd38dee631fb023c0f31c284a532d205590b393d72009c14847f5b1", "826b9f5fbb7f908a13aa1efd4b7321e36992f5868d5d8311c7b40cf9b11ca0e7", "a7695a378c2ce402405ea37b12c7a338a8755e081869bd6b95858893ceb617ae", "a84c31e8409b420c3ca57fd30c7589378d6fdc8d155d866a7f8e6e80dec6fd06", "adadeeae5500de0da2b9e8dd478520d0a9945b577b2198f2462555e68f58e7ef", "b283a76a83fe463c9587a2c88003f800e08c3929dfbeba833b78260f9c209785", "c19a7389ab3cd712058a8c3c9ffd8d27a57f3d84b9c91a931f542682bb3d269d", "c3bb4bd2aca82fb498247deeac12265921fe231502a6bc6edea3ee7fe6c40a7a", "c5ea60ece0c0c1c849025bfc541b60a6751b491b6f11dd9ef37ab5b8c9041921", "db61a640ca20f237317d27bc658c1fc54c7581ff7f6502d112922dc285bdabee"]
|
||||
simplejson = ["0fe3994207485efb63d8f10a833ff31236ed27e3b23dadd0bf51c9900313f8f2", "17163e643dbf125bb552de17c826b0161c68c970335d270e174363d19e7ea882", "1d1e929cdd15151f3c0b2efe953b3281b2fd5ad5f234f77aca725f28486466f6", "1d346c2c1d7dd79c118f0cc7ec5a1c4127e0c8ffc83e7b13fc5709ff78c9bb84", "1ea59f570b9d4916ae5540a9181f9c978e16863383738b69a70363bc5e63c4cb", "1fbba86098bbfc1f85c5b69dc9a6d009055104354e0d9880bb00b692e30e0078", "229edb079d5dd81bf12da952d4d825bd68d1241381b37d3acf961b384c9934de", "22a7acb81968a7c64eba7526af2cf566e7e2ded1cb5c83f0906b17ff1540f866", "2b4b2b738b3b99819a17feaf118265d0753d5536049ea570b3c43b51c4701e81", "4cf91aab51b02b3327c9d51897960c554f00891f9b31abd8a2f50fd4a0071ce8", "4fd5f79590694ebff8dc980708e1c182d41ce1fda599a12189f0ca96bf41ad70", "5cfd495527f8b85ce21db806567de52d98f5078a8e9427b18e251c68bd573a26", "60aad424e47c5803276e332b2a861ed7a0d46560e8af53790c4c4fb3420c26c2", "7739940d68b200877a15a5ff5149e1599737d6dd55e302625650629350466418", "7cce4bac7e0d66f3a080b80212c2238e063211fe327f98d764c6acbc214497fc", "8027bd5f1e633eb61b8239994e6fc3aba0346e76294beac22a892eb8faa92ba1", "86afc5b5cbd42d706efd33f280fec7bd7e2772ef54e3f34cf6b30777cd19a614", "87d349517b572964350cc1adc5a31b493bbcee284505e81637d0174b2758ba17", "8de378d589eccbc75941e480b4d5b4db66f22e4232f87543b136b1f093fff342", "926bcbef9eb60e798eabda9cd0bbcb0fca70d2779aa0aa56845749d973eb7ad5", "9a126c3a91df5b1403e965ba63b304a50b53d8efc908a8c71545ed72535374a3", "ad8dd3454d0c65c0f92945ac86f7b9efb67fa2040ba1b0189540e984df904378", "d140e9376e7f73c1f9e0a8e3836caf5eec57bbafd99259d56979da05a6356388", "da00675e5e483ead345429d4f1374ab8b949fba4429d60e71ee9d030ced64037", "daaf4d11db982791be74b23ff4729af2c7da79316de0bebf880fa2d60bcc8c5a", "f4b64a1031acf33e281fd9052336d6dad4d35eee3404c95431c8c6bc7a9c0588", "fc046afda0ed8f5295212068266c92991ab1f4a50c6a7144b69364bdee4a0159", "fc9051d249dd5512e541f20330a74592f7a65b2d62e18122ca89bf71f94db748"]
|
||||
six = ["236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", "8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"]
|
||||
snowballstemmer = ["209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", "df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"]
|
||||
sphinx = ["298537cb3234578b2d954ff18c5608468229e116a9757af3b831c2b2b4819159", "e6e766b74f85f37a5f3e0773a1e1be8db3fcb799deb58ca6d18b70b0b44542a5"]
|
||||
sphinxcontrib-applehelp = ["edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", "fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"]
|
||||
sphinxcontrib-devhelp = ["6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", "9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"]
|
||||
sphinxcontrib-htmlhelp = ["4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422", "d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"]
|
||||
sphinxcontrib-httpdomain = ["1fb5375007d70bf180cdd1c79e741082be7aa2d37ba99efe561e1c2e3f38191e", "ac40b4fba58c76b073b03931c7b8ead611066a6aebccafb34dc19694f4eb6335"]
|
||||
sphinxcontrib-jsmath = ["2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", "a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"]
|
||||
sphinxcontrib-qthelp = ["513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20", "79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"]
|
||||
sphinxcontrib-serializinghtml = ["c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227", "db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"]
|
||||
toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"]
|
||||
typed-ast = ["0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", "0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", "249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", "24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", "269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", "4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", "498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", "4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", "6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", "715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", "73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", "8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", "8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", "aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", "bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", "c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", "d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", "d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", "d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", "fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", "fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"]
|
||||
urllib3 = ["2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", "87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"]
|
||||
webargs = ["4f04918864c7602886335d8099f9b8960ee698b6b914f022736ed50be6b71235", "871642a2e0c62f21d5b78f357750ac7a87e6bc734c972f633aa5fb6204fbf29a", "fc81c9f9d391acfbce406a319217319fd8b2fd862f7fdb5319ad06944f36ed25"]
|
||||
werkzeug = ["1e0dedc2acb1f46827daa2e399c1485c8fa17c0d8e70b6b875b4e7f54bf408d2", "b353856d37dec59d6511359f97f6a4b2468442e454bd1c98298ddce53cac1f04"]
|
||||
wrapt = ["565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"]
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ picamera = { git = "https://github.com/rwb27/picamera.git", branch = "lens-shadi
|
|||
pyserial = "^3.4" # Used for sangaboard (basic_serial_instrument) until we move to sangaboard pip library
|
||||
|
||||
labthings = { git = "https://github.com/labthings/python-labthings.git", branch = "master"} # TODO: Pin to a fixed version before 2.0.0 stable
|
||||
python-dateutil = "^2.8"
|
||||
|
||||
[tool.poetry.extras]
|
||||
rpi = ["picamera", "RPi.GPIO"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue