Better API docs for autofocus extension
This commit is contained in:
parent
202d942c90
commit
c81c4eb64d
1 changed files with 249 additions and 131 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
@ -14,6 +15,7 @@ from openflexure_microscope.devel import abort
|
||||||
from openflexure_microscope.microscope import Microscope
|
from openflexure_microscope.microscope import Microscope
|
||||||
from openflexure_microscope.stage.base import BaseStage
|
from openflexure_microscope.stage.base import BaseStage
|
||||||
from openflexure_microscope.utilities import set_properties
|
from openflexure_microscope.utilities import set_properties
|
||||||
|
from labthings.utilities import get_docstring, get_summary
|
||||||
|
|
||||||
### Autofocus utilities
|
### Autofocus utilities
|
||||||
|
|
||||||
|
|
@ -144,6 +146,117 @@ def sharpness_edge(image: np.ndarray) -> float:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def find_microscope() -> dict:
|
||||||
|
"""Find the microscope component or raise an exception.
|
||||||
|
|
||||||
|
This function will fail with HTTPError extensions if it can't
|
||||||
|
find the appropriate hardware.
|
||||||
|
|
||||||
|
It is returned as a dictionary with one key, "microscope".
|
||||||
|
This means it can be easily merged into an arguments dictionary.
|
||||||
|
"""
|
||||||
|
microscope = find_component("org.openflexure.microscope")
|
||||||
|
|
||||||
|
if not microscope:
|
||||||
|
abort(503, "No microscope connected. Unable to autofocus.")
|
||||||
|
|
||||||
|
return {"microscope": microscope}
|
||||||
|
|
||||||
|
def find_microscope_with_real_stage() -> dict:
|
||||||
|
"""Find the microscope and ensure it has a real stage.
|
||||||
|
|
||||||
|
This function wraps `find_microscope()` and additionally asserts
|
||||||
|
that there is a real stage, raising a `503` code if not.
|
||||||
|
"""
|
||||||
|
args = find_microscope()
|
||||||
|
|
||||||
|
if not args["microscope"].has_real_stage():
|
||||||
|
abort(503, "No stage connected. Unable to autofocus.")
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def extension_action(args=None, extra_args_functions=None):
|
||||||
|
"""A decorator to auto-create an Action endpoint for a method
|
||||||
|
|
||||||
|
Use this decorator on any method of an extension (`BaseExtension` subclass)
|
||||||
|
and it will automatically be added to the API. At present it is deliberately
|
||||||
|
basic, and the plan is to expand the options in the future.
|
||||||
|
|
||||||
|
Currently, you may specify `args` (which is a Marshmallow-format schema
|
||||||
|
or dictionary, determining the datatype of the arguments, which will be
|
||||||
|
taken from the JSON payload of the POST request initiating the action).
|
||||||
|
|
||||||
|
It is also possible to specify a list of functions, which will be executed
|
||||||
|
in order, and their results will be added to the arguments. This allows,
|
||||||
|
for example, hardware components to be injected into the arguments. This
|
||||||
|
feature is experimental and should not be relied upon.
|
||||||
|
|
||||||
|
The parsed arguments dictionary is expanded as the function arguments,
|
||||||
|
i.e. we call `decorated_method(self, **kwargs). This means that any
|
||||||
|
unused arguments will cause an error, which is probably good practice...
|
||||||
|
|
||||||
|
NB this decorator does **not** replace the function with a `View` or
|
||||||
|
register it with the parent `Extension`. It adds the created `View` as
|
||||||
|
a property of the function, `flask_view`. The parent `Extension` is
|
||||||
|
responsible for collating and adding the views in its `__init__` method.
|
||||||
|
"""
|
||||||
|
supplied_args = args
|
||||||
|
def decorator(func):
|
||||||
|
class_docstring = f"""Manage actions for {func.__name__}.
|
||||||
|
|
||||||
|
This `View` class will return a list of `Action` objects representing
|
||||||
|
each time {func.__name__} has been run in response to a `GET` request,
|
||||||
|
and will start a new `Action` in response to a `POST` request.
|
||||||
|
"""
|
||||||
|
class ActionViewWrapper(ActionView):
|
||||||
|
__doc__ = class_docstring
|
||||||
|
args = supplied_args
|
||||||
|
|
||||||
|
def post(self, args):
|
||||||
|
# Inject arguments, if functions are supplied (TODO: I don't love this...)
|
||||||
|
if extra_args_functions:
|
||||||
|
for f in extra_args_functions:
|
||||||
|
args.update(f())
|
||||||
|
# Run the action
|
||||||
|
return func(**args)
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
# Explicitly wrap the `get` method to allow us to add a docstring
|
||||||
|
return super().get(*args, **kwargs)
|
||||||
|
|
||||||
|
# Create a nice docstring. NB because the source function and this docstring
|
||||||
|
# aren't guaranteed to have the same leading whitespace, we just make sure
|
||||||
|
# both docstrings are stripped of leading indent, using `inspect.cleandoc()`
|
||||||
|
ActionViewWrapper.post.description = (
|
||||||
|
get_docstring(func, remove_newlines=False) +
|
||||||
|
"\n\n" +
|
||||||
|
inspect.cleandoc(
|
||||||
|
"""
|
||||||
|
This `POST` request starts an Action, i.e. the hardware will do something
|
||||||
|
that may continue after the HTTP request has been responded to. The
|
||||||
|
response will always be an Action object, that details the current
|
||||||
|
status of the action and provides an interface to poll for completion.
|
||||||
|
|
||||||
|
If the action completes within a specified timeout, we will return
|
||||||
|
an HTTP status code of `200` and the return value will include any
|
||||||
|
output from the action. If it does not complete, we will return a
|
||||||
|
`201` response code, and the action's endpoint may be polled to follow
|
||||||
|
its progress.
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ActionViewWrapper.post.summary = get_summary(func)
|
||||||
|
ActionViewWrapper.post.__doc__ = ActionViewWrapper.post.description
|
||||||
|
ActionViewWrapper.__name__ = func.__name__
|
||||||
|
ActionViewWrapper.get.summary = f"List running and completed `{func.__name__}` actions."
|
||||||
|
|
||||||
|
func.flask_view = ActionViewWrapper
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Autofocus extension
|
### Autofocus extension
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -157,21 +270,30 @@ class AutofocusExtension(BaseExtension):
|
||||||
self.add_view(
|
self.add_view(
|
||||||
MeasureSharpnessAPI, "/measure_sharpness", endpoint="measure_sharpness"
|
MeasureSharpnessAPI, "/measure_sharpness", endpoint="measure_sharpness"
|
||||||
)
|
)
|
||||||
self.add_view(AutofocusAPI, "/autofocus", endpoint="autofocus")
|
self.add_decorated_method_views()
|
||||||
self.add_view(FastAutofocusAPI, "/fast_autofocus", endpoint="fast_autofocus")
|
|
||||||
self.add_view(
|
|
||||||
UpDownUpAutofocusAPI, "/updownup_autofocus", endpoint="updownup_autofocus"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.add_view(
|
def add_decorated_method_views(self):
|
||||||
MoveAndMeasureAPI, "/move_and_measure", endpoint="move_and_measure"
|
"""Add views from any methods that have been decorated
|
||||||
)
|
|
||||||
|
Using the decorators `@extension_action()` et al will add a
|
||||||
|
property to the decorated method, `method_view`. If this is
|
||||||
|
present, this function will add the views to the extension.
|
||||||
|
"""
|
||||||
|
for k in dir(self):
|
||||||
|
obj = getattr(self, k)
|
||||||
|
if hasattr(obj, "flask_view"):
|
||||||
|
name = obj.__name__
|
||||||
|
self.add_view(obj.flask_view, f"/{name}", name)
|
||||||
|
|
||||||
def measure_sharpness(
|
def measure_sharpness(
|
||||||
self, microscope: Microscope, metric_fn: Callable = sharpness_sum_lap2
|
self, microscope: Microscope, metric_fn: Callable = sharpness_sum_lap2
|
||||||
) -> float:
|
) -> float:
|
||||||
"""Measure the sharpness of the camera's current view."""
|
"""Measure the sharpness from the MJPEG stream
|
||||||
|
|
||||||
|
Take a JPEG snapshot from the camera (extracted from the live preview stream)
|
||||||
|
and return its size. This is the sharpness metric used by the fast autofocus
|
||||||
|
method.
|
||||||
|
"""
|
||||||
if hasattr(microscope.camera, "array") and callable(
|
if hasattr(microscope.camera, "array") and callable(
|
||||||
getattr(microscope.camera, "array")
|
getattr(microscope.camera, "array")
|
||||||
):
|
):
|
||||||
|
|
@ -179,14 +301,19 @@ class AutofocusExtension(BaseExtension):
|
||||||
else:
|
else:
|
||||||
raise RuntimeError(f"Object {microscope.camera} has no method `array`")
|
raise RuntimeError(f"Object {microscope.camera} has no method `array`")
|
||||||
|
|
||||||
|
@extension_action(
|
||||||
|
args = {"dz": fields.List(fields.Int())},
|
||||||
|
extra_args_functions = [find_microscope_with_real_stage],
|
||||||
|
)
|
||||||
def autofocus(
|
def autofocus(
|
||||||
self,
|
self,
|
||||||
microscope: Microscope,
|
microscope: Microscope,
|
||||||
dz: List[int],
|
dz: Optional[List[int]] = None,
|
||||||
settle: float = 0.5,
|
settle: float = 0.5,
|
||||||
metric_fn: Callable = sharpness_sum_lap2,
|
metric_fn: Callable = sharpness_sum_lap2,
|
||||||
) -> Tuple[List[int], List[float]]:
|
) -> Tuple[List[int], List[float]]:
|
||||||
"""Perform a simple autofocus routine.
|
"""Perform a simple autofocus routine.
|
||||||
|
|
||||||
The stage is moved to z positions (relative to current position) in dz,
|
The stage is moved to z positions (relative to current position) in dz,
|
||||||
and at each position an image is captured and the sharpness function
|
and at each position an image is captured and the sharpness function
|
||||||
evaulated. We then move back to the position where the sharpness was
|
evaulated. We then move back to the position where the sharpness was
|
||||||
|
|
@ -195,6 +322,8 @@ class AutofocusExtension(BaseExtension):
|
||||||
"""
|
"""
|
||||||
camera: BaseCamera = microscope.camera
|
camera: BaseCamera = microscope.camera
|
||||||
stage: BaseStage = microscope.stage
|
stage: BaseStage = microscope.stage
|
||||||
|
if not dz:
|
||||||
|
dz = np.linspace(-300, 300, 7)
|
||||||
|
|
||||||
with set_properties(stage, backlash=256), stage.lock, camera.lock:
|
with set_properties(stage, backlash=256), stage.lock, camera.lock:
|
||||||
sharpnesses: List[float] = []
|
sharpnesses: List[float] = []
|
||||||
|
|
@ -222,19 +351,95 @@ class AutofocusExtension(BaseExtension):
|
||||||
m.focus_rel(dz)
|
m.focus_rel(dz)
|
||||||
return m.sharpest_z_on_move(0)
|
return m.sharpest_z_on_move(0)
|
||||||
|
|
||||||
|
@extension_action(
|
||||||
|
args={"dz": fields.Int(required=True)},
|
||||||
|
extra_args_functions=[find_microscope_with_real_stage],
|
||||||
|
)
|
||||||
def move_and_measure(
|
def move_and_measure(
|
||||||
self, microscope: Microscope, dz: int
|
self, microscope: Microscope, dz: int
|
||||||
) -> Dict[str, np.ndarray]:
|
) -> Dict[str, np.ndarray]:
|
||||||
"""Make a relative Z move and return the sharpness data"""
|
"""Make a relative move in Z and measure dynamic sharpness
|
||||||
|
|
||||||
|
This accesses the underlying method used by the fast autofocus routines, to
|
||||||
|
move the stage while monitoring the sharpness, as reported by the size of
|
||||||
|
each JPEG frame in the preview stream. It returns a dictionary with
|
||||||
|
stage position vs time and image size (i.e. sharpness) vs time.
|
||||||
|
"""
|
||||||
with monitor_sharpness(microscope) as m:
|
with monitor_sharpness(microscope) as m:
|
||||||
m.focus_rel(dz)
|
m.focus_rel(dz)
|
||||||
return m.data_dict()
|
return m.data_dict()
|
||||||
|
|
||||||
|
@extension_action(
|
||||||
|
args={"dz": fields.Int(missing=2000, example=2000)},
|
||||||
|
extra_args_functions=[find_microscope_with_real_stage],
|
||||||
|
)
|
||||||
def fast_autofocus(
|
def fast_autofocus(
|
||||||
self, microscope: Microscope, dz: int = 2000
|
self, microscope: Microscope, dz: int = 2000
|
||||||
) -> Dict[str, np.ndarray]:
|
) -> Dict[str, np.ndarray]:
|
||||||
"""Perform a down-up-down-up autofocus"""
|
"""Perform a fast down-up-down-up autofocus
|
||||||
with microscope.camera.lock, microscope.stage.lock:
|
|
||||||
|
This "fast" autofocus method moves the stage continuously in Z, while
|
||||||
|
following the sharpness using the MJPEG stream. This version is the
|
||||||
|
simplest "fast" autofocus method, and performs the following sequence
|
||||||
|
of moves:
|
||||||
|
|
||||||
|
1. Move to `-dz/2`, i.e. the bottom of the range
|
||||||
|
2. Move up by `dz`, i.e. to the top of the range, while recording the
|
||||||
|
sharpness of the image as a function of time. Record the estimated
|
||||||
|
position of the stage when the sharpness was maximised, `fz`.
|
||||||
|
3. Move back to the bottom (by `-dz`)
|
||||||
|
4. Move up to the position where it was sharpest.
|
||||||
|
|
||||||
|
## Backlash correction
|
||||||
|
This routine should cancel out backlash: the stage is moving upwards as
|
||||||
|
we record the sharpnes vs z data, and it is also moving upwards when
|
||||||
|
we make the final move to the sharpest point. Mechanical backlash should
|
||||||
|
therefore be the same in both cases.
|
||||||
|
|
||||||
|
This does not account for lag between the sharpness measurements and the
|
||||||
|
stage's motion; that has been tested for and seems not to be a big issue
|
||||||
|
most of the time, but may need to be accounted for in the future, if
|
||||||
|
hardware or software changes increase the latency.
|
||||||
|
|
||||||
|
## Sharpness metric
|
||||||
|
This method uses the MJPEG preview stream to estimate the sharpness of
|
||||||
|
the image. MJPEG streams consist of a series of independent JPEG images,
|
||||||
|
so each frame can be looked at in isolation (though see later for an
|
||||||
|
important caveat). JPEG images are compressed lossily, by taking the
|
||||||
|
discrete cosine transform (DCT) of each 8x8 block in the image. A very
|
||||||
|
rough precis of how this works is that after the DCT, cosine components
|
||||||
|
that are deemed unimportant (i.e. smaller than a threshold) are discarded.
|
||||||
|
The effect is that images with lots of high-frequency information have a
|
||||||
|
larger file size.
|
||||||
|
|
||||||
|
We look only at the size of each JPEG frame in the stream, so we get a
|
||||||
|
remarkably robust estimate of image sharpness without even opening the
|
||||||
|
images! That's what lets us analyse 30 images/second even on the very
|
||||||
|
limited processing power available to the Raspberry Pi 3.
|
||||||
|
|
||||||
|
> *Warning*
|
||||||
|
> We assume that JPEG frames are independent. This is only true if the
|
||||||
|
> MJPEG stream is encoded at *constant quality* without any additional
|
||||||
|
> bit rate control. By default, many streams will reduce the quality
|
||||||
|
> factor if they exceed a target bit rate, which badly affects this
|
||||||
|
> method. We turn off bit rate limiting for the Raspberry Pi camera,
|
||||||
|
> which fixes the problem, at the expense of sometimes failing if
|
||||||
|
> particularly sharp images appear in the stream, as there is a fairly
|
||||||
|
> small maximum size for each JPEG frame beyond which empty images are
|
||||||
|
> returned.
|
||||||
|
|
||||||
|
## Estimation of sharpness vs z
|
||||||
|
What we record during an autofocus is two time series, from two parallel
|
||||||
|
threads. One thread monitors the camera, and records the size of each
|
||||||
|
JPEG frame as a function of time. NB this is time from `time.time()`
|
||||||
|
in Python, so will not be microsecond-accurate. The other thread is
|
||||||
|
responsible for moving the stage, and records its current Z position
|
||||||
|
before and after each move. Interpolating between these `(t, z)` points
|
||||||
|
gives us a `z` value for each JPEG size, and so we can estimate the
|
||||||
|
JPEG size as a function of `z` and hence determine the `z` value at
|
||||||
|
which sharpness is maximised.
|
||||||
|
"""
|
||||||
|
with microscope.lock(timeout=1), microscope.camera.lock, microscope.stage.lock:
|
||||||
with monitor_sharpness(microscope) as m:
|
with monitor_sharpness(microscope) as m:
|
||||||
# Move to (-dz / 2)
|
# Move to (-dz / 2)
|
||||||
m.focus_rel(-dz / 2)
|
m.focus_rel(-dz / 2)
|
||||||
|
|
@ -253,15 +458,28 @@ class AutofocusExtension(BaseExtension):
|
||||||
# Return all focus data
|
# Return all focus data
|
||||||
return m.data_dict()
|
return m.data_dict()
|
||||||
|
|
||||||
|
@extension_action(
|
||||||
|
args = {
|
||||||
|
"dz": fields.Int(missing=2000, example=2000),
|
||||||
|
"backlash": fields.Int(missing=25, minimum=0),
|
||||||
|
},
|
||||||
|
extra_args_functions=[find_microscope_with_real_stage],
|
||||||
|
)
|
||||||
def fast_up_down_up_autofocus(
|
def fast_up_down_up_autofocus(
|
||||||
self,
|
self,
|
||||||
microscope: Microscope,
|
microscope: Microscope,
|
||||||
dz: int = 2000,
|
dz: int = 2000,
|
||||||
target_z: int = 0,
|
target_z: int = 0,
|
||||||
initial_move_up: bool = True,
|
initial_move_up: bool = True,
|
||||||
mini_backlash: int = 25,
|
backlash: Optional[int] = None,
|
||||||
|
mini_backlash: Optional[int] = None,
|
||||||
) -> Dict[str, np.ndarray]:
|
) -> Dict[str, np.ndarray]:
|
||||||
"""Autofocus by measuring on the way down, and moving back up with feedback.
|
"""Perform a fast up-down-up autofocus, with feedback
|
||||||
|
|
||||||
|
Autofocus by measuring on the way down, and moving back up with feedback.
|
||||||
|
This is a "fast" autofocus method, i.e. it moves the stage continuously
|
||||||
|
while monitoring the sharpness using the MJPEG stream. See `fast_autofocus`
|
||||||
|
for more details.
|
||||||
|
|
||||||
This autofocus method is very efficient, as it only passes the peak once.
|
This autofocus method is very efficient, as it only passes the peak once.
|
||||||
The sequence of moves it performs is:
|
The sequence of moves it performs is:
|
||||||
|
|
@ -277,7 +495,10 @@ class AutofocusExtension(BaseExtension):
|
||||||
target position.
|
target position.
|
||||||
|
|
||||||
Moving back to the target position in two steps allows us to correct for
|
Moving back to the target position in two steps allows us to correct for
|
||||||
backlash, by using the sharpness-vs-z curve as a rough encoder for Z.
|
backlash, by using the sharpness-vs-z curve as a rough encoder for Z. The
|
||||||
|
main source of error is that the curves on the way up and the way down are
|
||||||
|
not always identical, largely due to small lateral shifts as the Z axis is
|
||||||
|
moved.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
dz: number of steps over which to scan (optional, default 2000)
|
dz: number of steps over which to scan (optional, default 2000)
|
||||||
|
|
@ -290,13 +511,18 @@ class AutofocusExtension(BaseExtension):
|
||||||
from the starting position. Mostly useful if you're able to combine
|
from the starting position. Mostly useful if you're able to combine
|
||||||
the initial move with something else, e.g. moving to the next scan point.
|
the initial move with something else, e.g. moving to the next scan point.
|
||||||
|
|
||||||
mini_backlash: (optional, default 25) is a small extra move made in step
|
backlash: (optional, default 25) is a small extra move made in step
|
||||||
3 to help counteract backlash. It should be small enough that you
|
3 to help counteract backlash. It should be small enough that you
|
||||||
would always expect there to be greater backlash than this. Too small
|
would always expect there to be greater backlash than this. Too small
|
||||||
might slightly hurt accuracy, but is unlikely to be a big issue. Too big
|
might slightly hurt accuracy, but is unlikely to be a big issue. Too big
|
||||||
may cause you to overshoot, which is a problem.
|
may cause you to overshoot, which is a problem.
|
||||||
|
|
||||||
|
mini_backlash: (optional, default 25) is an alias for `backlash` and will be
|
||||||
|
removed in due course.
|
||||||
"""
|
"""
|
||||||
with microscope.camera.lock, microscope.stage.lock:
|
if not mini_backlash: # I renamed the argument,
|
||||||
|
mini_backlash = backlash or 25
|
||||||
|
with microscope.lock(timeout=1), microscope.camera.lock, microscope.stage.lock:
|
||||||
with monitor_sharpness(microscope) as m:
|
with monitor_sharpness(microscope) as m:
|
||||||
# Ensure the MJPEG stream has started
|
# Ensure the MJPEG stream has started
|
||||||
microscope.camera.start_stream()
|
microscope.camera.start_stream()
|
||||||
|
|
@ -347,116 +573,8 @@ class AutofocusExtension(BaseExtension):
|
||||||
|
|
||||||
|
|
||||||
class MeasureSharpnessAPI(View):
|
class MeasureSharpnessAPI(View):
|
||||||
|
__doc__ = AutofocusExtension.measure_sharpness.__doc__
|
||||||
def post(self):
|
def post(self):
|
||||||
microscope = find_component("org.openflexure.microscope")
|
microscope = find_microscope()["microscope"]
|
||||||
|
|
||||||
if not microscope:
|
|
||||||
abort(503, "No microscope connected. Unable to measure sharpness.")
|
|
||||||
|
|
||||||
return {"sharpness": self.extension.measure_sharpness(microscope)}
|
return {"sharpness": self.extension.measure_sharpness(microscope)}
|
||||||
|
|
||||||
|
|
||||||
class MoveAndMeasureAPI(ActionView):
|
|
||||||
"""
|
|
||||||
Move the stage and measure dynamic sharpness
|
|
||||||
"""
|
|
||||||
|
|
||||||
args = {"dz": fields.Int(required=True)}
|
|
||||||
|
|
||||||
def post(self, args):
|
|
||||||
microscope = find_component("org.openflexure.microscope")
|
|
||||||
|
|
||||||
if not microscope:
|
|
||||||
abort(503, "No microscope connected. Unable to autofocus.")
|
|
||||||
|
|
||||||
if microscope.has_real_stage():
|
|
||||||
# Acquire microscope lock with 1s timeout
|
|
||||||
with microscope.camera.lock, microscope.stage.lock:
|
|
||||||
return self.extension.move_and_measure(microscope, dz=args.get("dz"))
|
|
||||||
|
|
||||||
else:
|
|
||||||
abort(503, "No stage connected. Unable to autofocus.")
|
|
||||||
|
|
||||||
|
|
||||||
class AutofocusAPI(ActionView):
|
|
||||||
"""
|
|
||||||
Run a standard autofocus
|
|
||||||
"""
|
|
||||||
|
|
||||||
args = {"dz": fields.List(fields.Int())}
|
|
||||||
|
|
||||||
def post(self, args):
|
|
||||||
microscope = find_component("org.openflexure.microscope")
|
|
||||||
|
|
||||||
if not microscope:
|
|
||||||
abort(503, "No microscope connected. Unable to autofocus.")
|
|
||||||
|
|
||||||
# Figure out the range of z values to use
|
|
||||||
dz = np.array(args.get("dz", np.linspace(-300, 300, 7)))
|
|
||||||
|
|
||||||
if microscope.has_real_stage():
|
|
||||||
logging.debug("Running autofocus...")
|
|
||||||
|
|
||||||
# return a handle on the autofocus task
|
|
||||||
return self.extension.autofocus(microscope, dz)
|
|
||||||
|
|
||||||
else:
|
|
||||||
abort(503, "No stage connected. Unable to autofocus.")
|
|
||||||
|
|
||||||
|
|
||||||
class FastAutofocusAPI(ActionView):
|
|
||||||
"""
|
|
||||||
Run a fast autofocus
|
|
||||||
"""
|
|
||||||
|
|
||||||
args = {"dz": fields.Int(missing=2000, example=2000)}
|
|
||||||
|
|
||||||
def post(self, args):
|
|
||||||
microscope = find_component("org.openflexure.microscope")
|
|
||||||
|
|
||||||
if not microscope:
|
|
||||||
abort(503, "No microscope connected. Unable to autofocus.")
|
|
||||||
|
|
||||||
if microscope.has_real_stage():
|
|
||||||
logging.debug("Running autofocus...")
|
|
||||||
# Acquire microscope lock with 1s timeout
|
|
||||||
with microscope.lock(timeout=1):
|
|
||||||
# Run fast_autofocus
|
|
||||||
return self.extension.fast_autofocus(microscope, dz=args.get("dz"))
|
|
||||||
|
|
||||||
else:
|
|
||||||
abort(503, "No stage connected. Unable to autofocus.")
|
|
||||||
|
|
||||||
|
|
||||||
class UpDownUpAutofocusAPI(ActionView):
|
|
||||||
"""
|
|
||||||
Run a fast up-down-up autofocus
|
|
||||||
"""
|
|
||||||
|
|
||||||
args = {
|
|
||||||
"dz": fields.Int(missing=2000, example=2000),
|
|
||||||
"backlash": fields.Int(missing=25, minimum=0),
|
|
||||||
}
|
|
||||||
|
|
||||||
def post(self, args):
|
|
||||||
microscope = find_component("org.openflexure.microscope")
|
|
||||||
|
|
||||||
if not microscope:
|
|
||||||
abort(503, "No microscope connected. Unable to autofocus.")
|
|
||||||
|
|
||||||
# Figure out the parameters to use
|
|
||||||
dz = args.get("dz")
|
|
||||||
backlash = args.get("backlash")
|
|
||||||
|
|
||||||
if microscope.has_real_stage():
|
|
||||||
logging.debug("Running autofocus...")
|
|
||||||
|
|
||||||
# Acquire microscope lock with 1s timeout
|
|
||||||
with microscope.lock(timeout=1):
|
|
||||||
# Run fast_up_down_up_autofocus
|
|
||||||
return self.extension.fast_up_down_up_autofocus(
|
|
||||||
microscope, dz=dz, mini_backlash=backlash
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
abort(503, "No stage connected. Unable to autofocus.")
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue