Updated documentation to subclass-based extensions
This commit is contained in:
parent
7a849c725a
commit
bacc111a87
10 changed files with 249 additions and 318 deletions
|
|
@ -2,35 +2,35 @@ from labthings import find_component
|
||||||
from labthings.extensions import BaseExtension
|
from labthings.extensions import BaseExtension
|
||||||
|
|
||||||
|
|
||||||
def identify():
|
# Create the extension class
|
||||||
"""
|
class MyExtension(BaseExtension):
|
||||||
Demonstrate access to Microscope.camera, and Microscope.stage
|
def __init__(self):
|
||||||
"""
|
# Superclass init function
|
||||||
microscope = find_component("org.openflexure.microscope")
|
super().__init__("com.myname.myextension", version="0.0.0")
|
||||||
|
|
||||||
response = (
|
def identify(self):
|
||||||
f"My name is {microscope.name}. "
|
"""
|
||||||
f"My parent camera is {microscope.camera}, "
|
Demonstrate access to Microscope.camera, and Microscope.stage
|
||||||
f"and my parent stage is {microscope.stage}."
|
"""
|
||||||
)
|
microscope = find_component("org.openflexure.microscope")
|
||||||
|
|
||||||
return response
|
response = (
|
||||||
|
f"My name is {microscope.name}. "
|
||||||
|
f"My parent camera is {microscope.camera}, "
|
||||||
|
f"and my parent stage is {microscope.stage}."
|
||||||
|
)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def rename(self, new_name):
|
||||||
|
"""
|
||||||
|
Rename the microscope
|
||||||
|
"""
|
||||||
|
|
||||||
|
microscope = find_component("org.openflexure.microscope")
|
||||||
|
|
||||||
|
microscope.name = new_name
|
||||||
|
microscope.save_settings()
|
||||||
|
|
||||||
|
|
||||||
def rename(new_name):
|
LABTHINGS_EXTENSIONS = (MyExtension,)
|
||||||
"""
|
|
||||||
Rename the microscope
|
|
||||||
"""
|
|
||||||
|
|
||||||
microscope = find_component("org.openflexure.microscope")
|
|
||||||
|
|
||||||
microscope.name = new_name
|
|
||||||
microscope.save_settings()
|
|
||||||
|
|
||||||
|
|
||||||
# Create your extension object
|
|
||||||
my_extension = BaseExtension("com.myname.myextension", version="0.0.0")
|
|
||||||
|
|
||||||
# Add methods to your extension
|
|
||||||
my_extension.add_method(identify, "identify")
|
|
||||||
my_extension.add_method(rename, "rename")
|
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
from labthings import find_component
|
|
||||||
from labthings.extensions import BaseExtension
|
|
||||||
|
|
||||||
|
|
||||||
# Create the extension class
|
|
||||||
class MyExtension(BaseExtension):
|
|
||||||
def __init__(self):
|
|
||||||
|
|
||||||
# Create some instance variable
|
|
||||||
self.state_variable = "An example of a persistant instance variable"
|
|
||||||
|
|
||||||
# Superclass init function
|
|
||||||
super().__init__("com.myname.myextension", version="0.0.0")
|
|
||||||
|
|
||||||
def identify(self):
|
|
||||||
"""
|
|
||||||
Demonstrate access to Microscope.camera, and Microscope.stage
|
|
||||||
"""
|
|
||||||
microscope = find_component("org.openflexure.microscope")
|
|
||||||
|
|
||||||
response = (
|
|
||||||
f"My name is {microscope.name}. "
|
|
||||||
f"My parent camera is {microscope.camera}, "
|
|
||||||
f"and my parent stage is {microscope.stage}."
|
|
||||||
)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def rename(self, new_name):
|
|
||||||
"""
|
|
||||||
Rename the microscope
|
|
||||||
"""
|
|
||||||
|
|
||||||
microscope = find_component("org.openflexure.microscope")
|
|
||||||
|
|
||||||
microscope.name = new_name
|
|
||||||
microscope.save_settings()
|
|
||||||
|
|
||||||
|
|
||||||
# Create your extension object
|
|
||||||
my_extension = MyExtension()
|
|
||||||
|
|
@ -2,42 +2,44 @@ from labthings import fields, find_component
|
||||||
from labthings.extensions import BaseExtension
|
from labthings.extensions import BaseExtension
|
||||||
from labthings.views import View
|
from labthings.views import View
|
||||||
|
|
||||||
## Extension methods
|
# Create the extension class
|
||||||
|
class MyExtension(BaseExtension):
|
||||||
|
def __init__(self):
|
||||||
|
# Superclass init function
|
||||||
|
super().__init__("com.myname.myextension", version="0.0.0")
|
||||||
|
|
||||||
|
# Add our API Views (defined below MyExtension)
|
||||||
|
self.add_view(ExampleIdentifyView, "/identify")
|
||||||
|
self.add_view(ExampleRenameView, "/rename")
|
||||||
|
|
||||||
def identify(microscope):
|
def identify(self, microscope):
|
||||||
"""
|
"""
|
||||||
Demonstrate access to Microscope.camera, and Microscope.stage
|
Demonstrate access to Microscope.camera, and Microscope.stage
|
||||||
"""
|
"""
|
||||||
|
response = (
|
||||||
|
f"My name is {microscope.name}. "
|
||||||
|
f"My parent camera is {microscope.camera}, "
|
||||||
|
f"and my parent stage is {microscope.stage}."
|
||||||
|
)
|
||||||
|
|
||||||
response = (
|
return response
|
||||||
f"My name is {microscope.name}. "
|
|
||||||
f"My parent camera is {microscope.camera}, "
|
|
||||||
f"and my parent stage is {microscope.stage}."
|
|
||||||
)
|
|
||||||
|
|
||||||
return response
|
def rename(self, microscope, new_name):
|
||||||
|
"""
|
||||||
|
Rename the microscope
|
||||||
def rename(microscope, new_name):
|
"""
|
||||||
"""
|
microscope.name = new_name
|
||||||
Rename the microscope
|
microscope.save_settings()
|
||||||
"""
|
|
||||||
|
|
||||||
microscope.name = new_name
|
|
||||||
microscope.save_settings()
|
|
||||||
|
|
||||||
|
|
||||||
## Extension views
|
## Extension views
|
||||||
|
|
||||||
|
|
||||||
class ExampleIdentifyView(View):
|
class ExampleIdentifyView(View):
|
||||||
def get(self):
|
def get(self):
|
||||||
# Find our microscope component
|
# Find our microscope component
|
||||||
microscope = find_component("org.openflexure.microscope")
|
microscope = find_component("org.openflexure.microscope")
|
||||||
|
|
||||||
# Return our identify function's output
|
# Return our identify function's output
|
||||||
return identify(microscope)
|
return self.extension.identify(microscope)
|
||||||
|
|
||||||
|
|
||||||
class ExampleRenameView(View):
|
class ExampleRenameView(View):
|
||||||
|
|
@ -53,21 +55,10 @@ class ExampleRenameView(View):
|
||||||
microscope = find_component("org.openflexure.microscope")
|
microscope = find_component("org.openflexure.microscope")
|
||||||
|
|
||||||
# Pass microscope and new name to our rename function
|
# Pass microscope and new name to our rename function
|
||||||
rename(microscope, new_name)
|
self.extension.rename(microscope, new_name)
|
||||||
|
|
||||||
# Return our identify function's output
|
# Return our identify function's output
|
||||||
return identify(microscope)
|
return self.extension.identify(microscope)
|
||||||
|
|
||||||
|
|
||||||
## Create extension
|
LABTHINGS_EXTENSIONS = (MyExtension,)
|
||||||
|
|
||||||
# Create your extension object
|
|
||||||
my_extension = BaseExtension("com.myname.myextension", version="0.0.0")
|
|
||||||
|
|
||||||
# Add methods to your extension
|
|
||||||
my_extension.add_method(identify, "identify")
|
|
||||||
my_extension.add_method(rename, "rename")
|
|
||||||
|
|
||||||
# Add API views to your extension
|
|
||||||
my_extension.add_view(ExampleIdentifyView, "/identify")
|
|
||||||
my_extension.add_view(ExampleRenameView, "/rename")
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,22 @@ from labthings import Schema, fields, find_component
|
||||||
from labthings.extensions import BaseExtension
|
from labthings.extensions import BaseExtension
|
||||||
from labthings.views import View
|
from labthings.views import View
|
||||||
|
|
||||||
## Extension methods
|
# Create the extension class
|
||||||
|
class MyExtension(BaseExtension):
|
||||||
|
def __init__(self):
|
||||||
|
# Superclass init function
|
||||||
|
super().__init__("com.myname.myextension", version="0.0.0")
|
||||||
|
|
||||||
|
# Add our API Views (defined below MyExtension)
|
||||||
|
self.add_view(ExampleIdentifyView, "/identify")
|
||||||
|
self.add_view(ExampleRenameView, "/rename")
|
||||||
|
|
||||||
|
def rename(self, microscope, new_name):
|
||||||
|
"""
|
||||||
|
Rename the microscope
|
||||||
|
"""
|
||||||
|
microscope.name = new_name
|
||||||
|
microscope.save_settings()
|
||||||
|
|
||||||
|
|
||||||
# Define which properties of a Microscope object we care about,
|
# Define which properties of a Microscope object we care about,
|
||||||
|
|
@ -15,18 +30,7 @@ class MicroscopeIdentifySchema(Schema):
|
||||||
stage = fields.String() # Stage object (represented as a string)
|
stage = fields.String() # Stage object (represented as a string)
|
||||||
|
|
||||||
|
|
||||||
def rename(microscope, new_name):
|
|
||||||
"""
|
|
||||||
Rename the microscope
|
|
||||||
"""
|
|
||||||
|
|
||||||
microscope.name = new_name
|
|
||||||
microscope.save_settings()
|
|
||||||
|
|
||||||
|
|
||||||
## Extension views
|
## Extension views
|
||||||
|
|
||||||
|
|
||||||
class ExampleIdentifyView(View):
|
class ExampleIdentifyView(View):
|
||||||
# Format our returned object using MicroscopeIdentifySchema
|
# Format our returned object using MicroscopeIdentifySchema
|
||||||
schema = MicroscopeIdentifySchema()
|
schema = MicroscopeIdentifySchema()
|
||||||
|
|
@ -54,21 +58,11 @@ class ExampleRenameView(View):
|
||||||
microscope = find_component("org.openflexure.microscope")
|
microscope = find_component("org.openflexure.microscope")
|
||||||
|
|
||||||
# Pass microscope and new name to our rename function
|
# Pass microscope and new name to our rename function
|
||||||
rename(microscope, new_name)
|
self.extension.rename(microscope, new_name)
|
||||||
|
|
||||||
# Return our microscope object,
|
# Return our microscope object,
|
||||||
# let schema handle formatting the output
|
# let schema handle formatting the output
|
||||||
return microscope
|
return microscope
|
||||||
|
|
||||||
|
|
||||||
## Create extension
|
LABTHINGS_EXTENSIONS = (MyExtension,)
|
||||||
|
|
||||||
# Create your extension object
|
|
||||||
my_extension = BaseExtension("com.myname.myextension", version="0.0.0")
|
|
||||||
|
|
||||||
# Add methods to your extension
|
|
||||||
my_extension.add_method(rename, "rename")
|
|
||||||
|
|
||||||
# Add API views to your extension
|
|
||||||
my_extension.add_view(ExampleIdentifyView, "/identify")
|
|
||||||
my_extension.add_view(ExampleRenameView, "/rename")
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,22 @@ from labthings import Schema, fields, find_component
|
||||||
from labthings.extensions import BaseExtension
|
from labthings.extensions import BaseExtension
|
||||||
from labthings.views import PropertyView
|
from labthings.views import PropertyView
|
||||||
|
|
||||||
## Extension methods
|
# Create the extension class
|
||||||
|
class MyExtension(BaseExtension):
|
||||||
|
def __init__(self):
|
||||||
|
# Superclass init function
|
||||||
|
super().__init__("com.myname.myextension", version="0.0.0")
|
||||||
|
|
||||||
|
# Add our API Views (defined below MyExtension)
|
||||||
|
self.add_view(ExampleIdentifyView, "/identify")
|
||||||
|
self.add_view(ExampleRenameView, "/rename")
|
||||||
|
|
||||||
|
def rename(self, microscope, new_name):
|
||||||
|
"""
|
||||||
|
Rename the microscope
|
||||||
|
"""
|
||||||
|
microscope.name = new_name
|
||||||
|
microscope.save_settings()
|
||||||
|
|
||||||
|
|
||||||
# Define which properties of a Microscope object we care about,
|
# Define which properties of a Microscope object we care about,
|
||||||
|
|
@ -15,16 +30,7 @@ class MicroscopeIdentifySchema(Schema):
|
||||||
stage = fields.String() # Stage object (represented as a string)
|
stage = fields.String() # Stage object (represented as a string)
|
||||||
|
|
||||||
|
|
||||||
def rename(microscope, new_name):
|
## Extension viewss
|
||||||
"""
|
|
||||||
Rename the microscope
|
|
||||||
"""
|
|
||||||
|
|
||||||
microscope.name = new_name
|
|
||||||
microscope.save_settings()
|
|
||||||
|
|
||||||
|
|
||||||
## Extension views
|
|
||||||
|
|
||||||
# Since we only have a GET method here, it'll register as a read-only property
|
# Since we only have a GET method here, it'll register as a read-only property
|
||||||
class ExampleIdentifyView(PropertyView):
|
class ExampleIdentifyView(PropertyView):
|
||||||
|
|
@ -68,21 +74,11 @@ class ExampleRenameView(PropertyView):
|
||||||
microscope = find_component("org.openflexure.microscope")
|
microscope = find_component("org.openflexure.microscope")
|
||||||
|
|
||||||
# Pass microscope and new name to our rename function
|
# Pass microscope and new name to our rename function
|
||||||
rename(microscope, new_name)
|
self.extension.rename(microscope, new_name)
|
||||||
|
|
||||||
# Return our microscope object,
|
# Return our microscope object,
|
||||||
# let schema handle formatting the output
|
# let schema handle formatting the output
|
||||||
return microscope
|
return microscope
|
||||||
|
|
||||||
|
|
||||||
## Create extension
|
LABTHINGS_EXTENSIONS = (MyExtension,)
|
||||||
|
|
||||||
# Create your extension object
|
|
||||||
my_extension = BaseExtension("com.myname.myextension", version="0.0.0")
|
|
||||||
|
|
||||||
# Add methods to your extension
|
|
||||||
my_extension.add_method(rename, "rename")
|
|
||||||
|
|
||||||
# Add API views to your extension
|
|
||||||
my_extension.add_view(ExampleIdentifyView, "/identify")
|
|
||||||
my_extension.add_view(ExampleRenameView, "/rename")
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,22 @@ from labthings import Schema, fields, find_component
|
||||||
from labthings.extensions import BaseExtension
|
from labthings.extensions import BaseExtension
|
||||||
from labthings.views import ActionView, PropertyView
|
from labthings.views import ActionView, PropertyView
|
||||||
|
|
||||||
## Extension methods
|
# Create the extension class
|
||||||
|
class MyExtension(BaseExtension):
|
||||||
|
def __init__(self):
|
||||||
|
# Superclass init function
|
||||||
|
super().__init__("com.myname.myextension", version="0.0.0")
|
||||||
|
|
||||||
|
# Add our API Views (defined below MyExtension)
|
||||||
|
self.add_view(ExampleIdentifyView, "/identify")
|
||||||
|
self.add_view(ExampleRenameView, "/rename")
|
||||||
|
|
||||||
|
def rename(self, microscope, new_name):
|
||||||
|
"""
|
||||||
|
Rename the microscope
|
||||||
|
"""
|
||||||
|
microscope.name = new_name
|
||||||
|
microscope.save_settings()
|
||||||
|
|
||||||
|
|
||||||
# Define which properties of a Microscope object we care about,
|
# Define which properties of a Microscope object we care about,
|
||||||
|
|
@ -18,15 +33,6 @@ class MicroscopeIdentifySchema(Schema):
|
||||||
stage = fields.String() # Stage object (represented as a string)
|
stage = fields.String() # Stage object (represented as a string)
|
||||||
|
|
||||||
|
|
||||||
def rename(microscope, new_name):
|
|
||||||
"""
|
|
||||||
Rename the microscope
|
|
||||||
"""
|
|
||||||
|
|
||||||
microscope.name = new_name
|
|
||||||
microscope.save_settings()
|
|
||||||
|
|
||||||
|
|
||||||
## Extension views
|
## Extension views
|
||||||
|
|
||||||
# Since we only have a GET method here, it'll register as a read-only property
|
# Since we only have a GET method here, it'll register as a read-only property
|
||||||
|
|
@ -71,7 +77,7 @@ class ExampleRenameView(PropertyView):
|
||||||
microscope = find_component("org.openflexure.microscope")
|
microscope = find_component("org.openflexure.microscope")
|
||||||
|
|
||||||
# Pass microscope and new name to our rename function
|
# Pass microscope and new name to our rename function
|
||||||
rename(microscope, new_name)
|
self.extension.rename(microscope, new_name)
|
||||||
|
|
||||||
# Return our microscope object,
|
# Return our microscope object,
|
||||||
# let schema handle formatting the output
|
# let schema handle formatting the output
|
||||||
|
|
@ -109,15 +115,4 @@ class QuickCaptureAPI(ActionView):
|
||||||
return send_file(io.BytesIO(stream.read()), mimetype="image/jpeg")
|
return send_file(io.BytesIO(stream.read()), mimetype="image/jpeg")
|
||||||
|
|
||||||
|
|
||||||
## Create extension
|
LABTHINGS_EXTENSIONS = (MyExtension,)
|
||||||
|
|
||||||
# Create your extension object
|
|
||||||
my_extension = BaseExtension("com.myname.myextension", version="0.0.0")
|
|
||||||
|
|
||||||
# Add methods to your extension
|
|
||||||
my_extension.add_method(rename, "rename")
|
|
||||||
|
|
||||||
# Add API views to your extension
|
|
||||||
my_extension.add_view(ExampleIdentifyView, "/identify")
|
|
||||||
my_extension.add_view(ExampleRenameView, "/rename")
|
|
||||||
my_extension.add_view(QuickCaptureAPI, "/quick-capture")
|
|
||||||
|
|
|
||||||
|
|
@ -7,52 +7,59 @@ from labthings.views import ActionView
|
||||||
# Used in our timelapse function
|
# Used in our timelapse function
|
||||||
from openflexure_microscope.captures.capture_manager import generate_basename
|
from openflexure_microscope.captures.capture_manager import generate_basename
|
||||||
|
|
||||||
## Extension methods
|
|
||||||
|
|
||||||
|
# Create the extension class
|
||||||
|
class TimelapseExtension(BaseExtension):
|
||||||
|
def __init__(self):
|
||||||
|
# Superclass init function
|
||||||
|
super().__init__("org.openflexure.timelapse-extension", version="0.0.0")
|
||||||
|
|
||||||
def timelapse(microscope, n_images, t_between):
|
# Add our API views
|
||||||
"""
|
self.add_view(TimelapseAPIView, "/timelapse")
|
||||||
Save a set of images in a timelapse
|
|
||||||
|
|
||||||
Args:
|
def timelapse(self, microscope, n_images, t_between):
|
||||||
microscope: Microscope object
|
"""
|
||||||
n_images (int): Number of images to take
|
Save a set of images in a timelapse
|
||||||
t_between (int/float): Time, in seconds, between sequential captures
|
|
||||||
"""
|
|
||||||
base_file_name = generate_basename()
|
|
||||||
folder = "TIMELAPSE_{}".format(base_file_name)
|
|
||||||
|
|
||||||
# Take exclusive control over both the camera and stage
|
Args:
|
||||||
with microscope.camera.lock, microscope.stage.lock:
|
microscope: Microscope object
|
||||||
for n in range(n_images):
|
n_images (int): Number of images to take
|
||||||
# Elegantly handle action cancellation
|
t_between (int/float): Time, in seconds, between sequential captures
|
||||||
if current_action() and current_action().stopped:
|
"""
|
||||||
return
|
base_file_name = generate_basename()
|
||||||
# Generate a filename
|
folder = "TIMELAPSE_{}".format(base_file_name)
|
||||||
filename = f"{base_file_name}_image{n}"
|
|
||||||
# Create a file to save the image to
|
|
||||||
output = microscope.camera.new_image(
|
|
||||||
filename=filename, folder=folder, temporary=False
|
|
||||||
)
|
|
||||||
|
|
||||||
# Capture
|
# Take exclusive control over both the camera and stage
|
||||||
microscope.camera.capture(output)
|
with microscope.camera.lock, microscope.stage.lock:
|
||||||
|
for n in range(n_images):
|
||||||
|
# Elegantly handle action cancellation
|
||||||
|
if current_action() and current_action().stopped:
|
||||||
|
return
|
||||||
|
# Generate a filename
|
||||||
|
filename = f"{base_file_name}_image{n}"
|
||||||
|
# Create a file to save the image to
|
||||||
|
output = microscope.camera.new_image(
|
||||||
|
filename=filename, folder=folder, temporary=False
|
||||||
|
)
|
||||||
|
|
||||||
# Add system metadata
|
# Capture
|
||||||
output.put_metadata(microscope.metadata, system=True)
|
microscope.camera.capture(output)
|
||||||
|
|
||||||
# Update task progress (only does anyting if the function is running in a LabThings task)
|
# Add system metadata
|
||||||
progress_pct = ((n + 1) / n_images) * 100 # Progress, in percent
|
output.put_metadata(microscope.metadata, system=True)
|
||||||
update_action_progress(progress_pct)
|
|
||||||
|
|
||||||
# Wait for the specified time
|
# Update task progress (only does anyting if the function is running in a LabThings task)
|
||||||
time.sleep(t_between)
|
progress_pct = ((n + 1) / n_images) * 100 # Progress, in percent
|
||||||
|
update_action_progress(progress_pct)
|
||||||
|
|
||||||
|
# Wait for the specified time
|
||||||
|
time.sleep(t_between)
|
||||||
|
|
||||||
|
|
||||||
## Extension views
|
## Extension views
|
||||||
|
|
||||||
|
|
||||||
class TimelapseAPI(ActionView):
|
class TimelapseAPIView(ActionView):
|
||||||
"""
|
"""
|
||||||
Take a series of images in a timelapse
|
Take a series of images in a timelapse
|
||||||
"""
|
"""
|
||||||
|
|
@ -71,16 +78,9 @@ class TimelapseAPI(ActionView):
|
||||||
microscope = find_component("org.openflexure.microscope")
|
microscope = find_component("org.openflexure.microscope")
|
||||||
|
|
||||||
# Start "timelapse"
|
# Start "timelapse"
|
||||||
return timelapse(microscope, args.get("n_images"), args.get("t_between"))
|
return self.extension.timelapse(
|
||||||
|
microscope, args.get("n_images"), args.get("t_between")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
## Create extension
|
LABTHINGS_EXTENSIONS = (TimelapseExtension,)
|
||||||
|
|
||||||
# Create your extension object
|
|
||||||
my_extension = BaseExtension("com.myname.timelapse-extension", version="0.0.0")
|
|
||||||
|
|
||||||
# Add methods to your extension
|
|
||||||
my_extension.add_method(timelapse, "timelapse")
|
|
||||||
|
|
||||||
# Add API views to your extension
|
|
||||||
my_extension.add_view(TimelapseAPI, "/timelapse")
|
|
||||||
|
|
|
||||||
|
|
@ -4,60 +4,97 @@ from labthings import current_action, fields, find_component, update_action_prog
|
||||||
from labthings.extensions import BaseExtension
|
from labthings.extensions import BaseExtension
|
||||||
from labthings.views import ActionView
|
from labthings.views import ActionView
|
||||||
|
|
||||||
# Used to convert our GUI dictionary into a complete eV extension GUI
|
|
||||||
from openflexure_microscope.api.utilities.gui import build_gui
|
|
||||||
|
|
||||||
# Used in our timelapse function
|
# Used in our timelapse function
|
||||||
from openflexure_microscope.captures.capture_manager import generate_basename
|
from openflexure_microscope.captures.capture_manager import generate_basename
|
||||||
|
|
||||||
## Extension methods
|
# Used to convert our GUI dictionary into a complete eV extension GUI
|
||||||
|
from openflexure_microscope.api.utilities.gui import build_gui
|
||||||
|
|
||||||
|
|
||||||
def timelapse(microscope, n_images, t_between):
|
# Create the extension class
|
||||||
"""
|
class TimelapseExtension(BaseExtension):
|
||||||
Save a set of images in a timelapse
|
def __init__(self):
|
||||||
|
# Superclass init function
|
||||||
|
super().__init__("org.openflexure.timelapse-extension", version="0.0.0")
|
||||||
|
|
||||||
Args:
|
# Add our API views
|
||||||
microscope: Microscope object
|
self.add_view(TimelapseAPIView, "/timelapse")
|
||||||
n_images (int): Number of images to take
|
|
||||||
t_between (int/float): Time, in seconds, between sequential captures
|
|
||||||
"""
|
|
||||||
base_file_name = generate_basename()
|
|
||||||
folder = "TIMELAPSE_{}".format(base_file_name)
|
|
||||||
|
|
||||||
# Take exclusive control over both the camera and stage
|
# Add our GUI description
|
||||||
with microscope.camera.lock, microscope.stage.lock:
|
gui_description = {
|
||||||
for n in range(n_images):
|
"icon": "timelapse", # Name of an icon from https://material.io/resources/icons/
|
||||||
# Elegantly handle action cancellation
|
"forms": [ # List of forms. Each form is a collapsible accordion panel
|
||||||
if current_action() and current_action().stopped:
|
{
|
||||||
return
|
"name": "Start a timelapse", # Form title
|
||||||
# Generate a filename
|
"route": "/timelapse", # The URL rule (as given by "add_view") of your submission view
|
||||||
filename = f"{base_file_name}_image{n}"
|
"isTask": True, # This forms submission starts a background task
|
||||||
# Create a file to save the image to
|
"isCollapsible": False, # This form cannot be collapsed into an accordion
|
||||||
output = microscope.camera.new_image(
|
"submitLabel": "Start", # Label for the form submit button
|
||||||
filename=filename, folder=folder, temporary=False
|
"schema": [ # List of dictionaries. Each element is a form component.
|
||||||
)
|
{
|
||||||
|
"fieldType": "numberInput",
|
||||||
|
"name": "n_images", # Name of the view arg this value corresponds to
|
||||||
|
"label": "Number of images",
|
||||||
|
"min": 1, # HTML number input attribute
|
||||||
|
"default": 5, # HTML number input attribute
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldType": "numberInput",
|
||||||
|
"name": "t_between",
|
||||||
|
"label": "Time (seconds) between images",
|
||||||
|
"min": 0.1, # HTML number input attribute
|
||||||
|
"step": 0.1, # HTML number input attribute
|
||||||
|
"default": 1, # HTML number input attribute
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
self.add_meta("gui", build_gui(gui_description, self))
|
||||||
|
|
||||||
# Capture
|
def timelapse(self, microscope, n_images, t_between):
|
||||||
microscope.camera.capture(output)
|
"""
|
||||||
|
Save a set of images in a timelapse
|
||||||
|
|
||||||
# Add system metadata
|
Args:
|
||||||
output.put_metadata(microscope.metadata, system=True)
|
microscope: Microscope object
|
||||||
|
n_images (int): Number of images to take
|
||||||
|
t_between (int/float): Time, in seconds, between sequential captures
|
||||||
|
"""
|
||||||
|
base_file_name = generate_basename()
|
||||||
|
folder = "TIMELAPSE_{}".format(base_file_name)
|
||||||
|
|
||||||
# Update task progress (only does anyting if the function is running in a LabThings task)
|
# Take exclusive control over both the camera and stage
|
||||||
progress_pct = ((n + 1) / n_images) * 100 # Progress, in percent
|
with microscope.camera.lock, microscope.stage.lock:
|
||||||
update_action_progress(progress_pct)
|
for n in range(n_images):
|
||||||
|
# Elegantly handle action cancellation
|
||||||
|
if current_action() and current_action().stopped:
|
||||||
|
return
|
||||||
|
# Generate a filename
|
||||||
|
filename = f"{base_file_name}_image{n}"
|
||||||
|
# Create a file to save the image to
|
||||||
|
output = microscope.camera.new_image(
|
||||||
|
filename=filename, folder=folder, temporary=False
|
||||||
|
)
|
||||||
|
|
||||||
# Wait for the specified time
|
# Capture
|
||||||
time.sleep(t_between)
|
microscope.camera.capture(output)
|
||||||
|
|
||||||
|
# Add system metadata
|
||||||
|
output.put_metadata(microscope.metadata, system=True)
|
||||||
|
|
||||||
|
# Update task progress (only does anyting if the function is running in a LabThings task)
|
||||||
|
progress_pct = ((n + 1) / n_images) * 100 # Progress, in percent
|
||||||
|
update_action_progress(progress_pct)
|
||||||
|
|
||||||
|
# Wait for the specified time
|
||||||
|
time.sleep(t_between)
|
||||||
|
|
||||||
|
|
||||||
## Extension views
|
## Extension views
|
||||||
|
class TimelapseAPIView(ActionView):
|
||||||
|
|
||||||
class TimelapseAPI(ActionView):
|
|
||||||
"""
|
"""
|
||||||
Take a series of images in a timelapse, running as a background task
|
Take a series of images in a timelapse
|
||||||
"""
|
"""
|
||||||
|
|
||||||
args = {
|
args = {
|
||||||
|
|
@ -73,53 +110,10 @@ class TimelapseAPI(ActionView):
|
||||||
# Find our microscope component
|
# Find our microscope component
|
||||||
microscope = find_component("org.openflexure.microscope")
|
microscope = find_component("org.openflexure.microscope")
|
||||||
|
|
||||||
# Create and start "timelapse", running in a background task
|
# Start "timelapse"
|
||||||
return timelapse(microscope, args.get("n_images"), args.get("t_between"))
|
return self.extension.timelapse(
|
||||||
|
microscope, args.get("n_images"), args.get("t_between")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
## Extension GUI (OpenFlexure eV)
|
LABTHINGS_EXTENSIONS = (TimelapseExtension,)
|
||||||
# Alternate form without any dynamic parts
|
|
||||||
extension_gui = {
|
|
||||||
"icon": "timelapse", # Name of an icon from https://material.io/resources/icons/
|
|
||||||
"forms": [ # List of forms. Each form is a collapsible accordion panel
|
|
||||||
{
|
|
||||||
"name": "Start a timelapse", # Form title
|
|
||||||
"route": "/timelapse", # The URL rule (as given by "add_view") of your submission view
|
|
||||||
"isTask": True, # This forms submission starts a background task
|
|
||||||
"isCollapsible": False, # This form cannot be collapsed into an accordion
|
|
||||||
"submitLabel": "Start", # Label for the form submit button
|
|
||||||
"schema": [ # List of dictionaries. Each element is a form component.
|
|
||||||
{
|
|
||||||
"fieldType": "numberInput",
|
|
||||||
"name": "n_images", # Name of the view arg this value corresponds to
|
|
||||||
"label": "Number of images",
|
|
||||||
"min": 1, # HTML number input attribute
|
|
||||||
"default": 5, # HTML number input attribute
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldType": "numberInput",
|
|
||||||
"name": "t_between",
|
|
||||||
"label": "Time (seconds) between images",
|
|
||||||
"min": 0.1, # HTML number input attribute
|
|
||||||
"step": 0.1, # HTML number input attribute
|
|
||||||
"default": 1, # HTML number input attribute
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
## Create extension
|
|
||||||
|
|
||||||
# Create your extension object
|
|
||||||
my_extension = BaseExtension("com.myname.timelapse-extension", version="0.0.0")
|
|
||||||
|
|
||||||
# Add methods to your extension
|
|
||||||
my_extension.add_method(timelapse, "timelapse")
|
|
||||||
|
|
||||||
# Add API views to your extension
|
|
||||||
my_extension.add_view(TimelapseAPI, "/timelapse")
|
|
||||||
|
|
||||||
# Add OpenFlexure eV GUI to your extension
|
|
||||||
my_extension.add_meta("gui", build_gui(extension_gui, my_extension))
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
Basic extension structure
|
Basic extension structure
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
An extension starts as a simple instance of :py:class:`labthings.extensions.BaseExtension`.
|
An extension starts as a subclass of :py:class:`labthings.extensions.BaseExtension`.
|
||||||
Each extension is described by a single ``BaseExtension`` instance, containing any number of methods, API views, and additional hardware components.
|
Each extension is described by a single ``BaseExtension`` instance, containing any number of methods, API views, and additional hardware components.
|
||||||
|
|
||||||
|
You will build your extension by subclassing :py:class:`labthings.extensions.BaseExtension`, and adding the class to a top-level `LABTHINGS_EXTENSIONS` list.
|
||||||
|
|
||||||
In order to access the currently running microscope object, use the :py:func:`labthings.find_component` function, with the argument ``"org.openflexure.microscope"``. Likewise, any new components attached by other extensions can be found using their full name, as above.
|
In order to access the currently running microscope object, use the :py:func:`labthings.find_component` function, with the argument ``"org.openflexure.microscope"``. Likewise, any new components attached by other extensions can be found using their full name, as above.
|
||||||
|
|
||||||
A simple extension file, with no API views but application-available methods may look like:
|
A simple extension file, with no API views but application-available methods may look like:
|
||||||
|
|
@ -24,13 +26,3 @@ Once this extension is loaded, any other extensions will have access to your met
|
||||||
# Call a function from your extension
|
# Call a function from your extension
|
||||||
if my_found_extension:
|
if my_found_extension:
|
||||||
my_found_extension.identify()
|
my_found_extension.identify()
|
||||||
|
|
||||||
|
|
||||||
Subclassing ``BaseExtension``
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
The syntax used above allows novice programmers to easily start building extensions, without having to deal with subclassing. However, for more complex extensions which require persistent state, subclassing :py:class:`labthings.extensions.BaseExtension` is recommended.
|
|
||||||
|
|
||||||
The same simple extension as seen above can be written using subclassing:
|
|
||||||
|
|
||||||
.. literalinclude:: ./example_extension/01b_basic_structure_subclass.py
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,14 @@
|
||||||
Adding web API views
|
Adding web API views
|
||||||
====================
|
====================
|
||||||
|
|
||||||
|
Key terminology
|
||||||
|
---------------
|
||||||
|
|
||||||
|
API View (or View)
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
*"A view function is the code you write to respond to requests to your application [...] For RESTful APIs it’s especially helpful to execute a different function for each HTTP method. With the [View class] you can easily do that. Each HTTP method maps to a function with the same name (just in lowercase)"* - `Flask documentation <https://flask.palletsprojects.com/en/1.1.x/views/>`_
|
||||||
|
|
||||||
Introduction
|
Introduction
|
||||||
------------
|
------------
|
||||||
Extensions can create views to expose extension functionality via the web API. Creating API views for your extension is strongly recommended, as this is the primary way we encourage interaction with the microscope device.
|
Extensions can create views to expose extension functionality via the web API. Creating API views for your extension is strongly recommended, as this is the primary way we encourage interaction with the microscope device.
|
||||||
|
|
@ -13,6 +21,8 @@ Continuing our example on the previous page, and discussed below, adding API vie
|
||||||
|
|
||||||
Note that we are now passing our microscope object as an argument to our API methods. Finding the microscope component is performed by the API view at request-time, and passed onto the functions.
|
Note that we are now passing our microscope object as an argument to our API methods. Finding the microscope component is performed by the API view at request-time, and passed onto the functions.
|
||||||
|
|
||||||
|
Your extension functions can be accessed from within an API View by using ``self.extension``. Once your view has been added to your extension, this will point to the extension object, allowing your API views to use your extension functionality.
|
||||||
|
|
||||||
In this case, our extension will have two new API views at `/identify` and `/rename`. The `/identify` view only accepts GET requests, and the `/rename` view only accepts POST requests.
|
In this case, our extension will have two new API views at `/identify` and `/rename`. The `/identify` view only accepts GET requests, and the `/rename` view only accepts POST requests.
|
||||||
|
|
||||||
Request arguments
|
Request arguments
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue