Updated default extensions to subclass structure
This commit is contained in:
parent
bacc111a87
commit
8d0759e9e0
8 changed files with 300 additions and 305 deletions
|
|
@ -2,6 +2,8 @@ import logging
|
|||
import traceback
|
||||
from contextlib import contextmanager
|
||||
|
||||
LABTHINGS_EXTENSIONS = []
|
||||
|
||||
|
||||
@contextmanager
|
||||
def handle_extension_error(extension_name):
|
||||
|
|
@ -17,14 +19,28 @@ def handle_extension_error(extension_name):
|
|||
|
||||
|
||||
with handle_extension_error("autofocus"):
|
||||
from .autofocus import autofocus_extension_v2
|
||||
from .autofocus import AutofocusExtension
|
||||
|
||||
LABTHINGS_EXTENSIONS.append(AutofocusExtension)
|
||||
with handle_extension_error("scan"):
|
||||
from .scan import scan_extension_v2
|
||||
from .scan import ScanExtension
|
||||
|
||||
LABTHINGS_EXTENSIONS.append(ScanExtension)
|
||||
with handle_extension_error("zip builder"):
|
||||
from .zip_builder import zip_extension_v2
|
||||
from .zip_builder import ZipBuilderExtension
|
||||
|
||||
LABTHINGS_EXTENSIONS.append(ZipBuilderExtension)
|
||||
with handle_extension_error("autostorage"):
|
||||
from .autostorage import autostorage_extension_v2
|
||||
from .autostorage import AutostorageExtension
|
||||
|
||||
LABTHINGS_EXTENSIONS.append(AutostorageExtension)
|
||||
|
||||
with handle_extension_error("lens shading calibration"):
|
||||
from .picamera_autocalibrate import LSTExtension
|
||||
|
||||
LABTHINGS_EXTENSIONS.append(LSTExtension)
|
||||
|
||||
with handle_extension_error("camera stage mapping"):
|
||||
from camera_stage_mapping.ofm_extension import csm_extension
|
||||
with handle_extension_error("lens shading calibration"):
|
||||
from .picamera_autocalibrate import lst_extension_v2
|
||||
|
||||
LABTHINGS_EXTENSIONS.append(csm_extension)
|
||||
|
|
|
|||
|
|
@ -109,6 +109,16 @@ class JPEGSharpnessMonitor:
|
|||
return data
|
||||
|
||||
|
||||
@contextmanager
|
||||
def monitor_sharpness(microscope: Microscope):
|
||||
m = JPEGSharpnessMonitor(microscope)
|
||||
m.start()
|
||||
try:
|
||||
yield m
|
||||
finally:
|
||||
m.stop()
|
||||
|
||||
|
||||
def sharpness_sum_lap2(rgb_image: np.ndarray):
|
||||
"""Return an image sharpness metric: sum(laplacian(image)**")"""
|
||||
image_bw = np.mean(rgb_image, 2)
|
||||
|
|
@ -129,184 +139,193 @@ def sharpness_edge(image: np.ndarray):
|
|||
### Autofocus extension
|
||||
|
||||
|
||||
def measure_sharpness(microscope: Microscope, metric_fn: Callable = sharpness_sum_lap2):
|
||||
"""Measure the sharpness of the camera's current view."""
|
||||
class AutofocusExtension(BaseExtension):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
"org.openflexure.autofocus",
|
||||
version="2.0.0",
|
||||
description="Actions to move the microscope in Z and pick the point with the sharpest image.",
|
||||
)
|
||||
self.add_view(
|
||||
MeasureSharpnessAPI, "/measure_sharpness", endpoint="measure_sharpness"
|
||||
)
|
||||
self.add_view(AutofocusAPI, "/autofocus", endpoint="autofocus")
|
||||
self.add_view(FastAutofocusAPI, "/fast_autofocus", endpoint="fast_autofocus")
|
||||
self.add_view(
|
||||
UpDownUpAutofocusAPI, "/updownup_autofocus", endpoint="updownup_autofocus"
|
||||
)
|
||||
|
||||
if hasattr(microscope.camera, "array") and callable(
|
||||
getattr(microscope.camera, "array")
|
||||
self.add_view(
|
||||
MoveAndMeasureAPI, "/move_and_measure", endpoint="move_and_measure"
|
||||
)
|
||||
|
||||
def measure_sharpness(
|
||||
self, microscope: Microscope, metric_fn: Callable = sharpness_sum_lap2
|
||||
):
|
||||
return metric_fn(getattr(microscope.camera, "array")(use_video_port=True))
|
||||
else:
|
||||
raise RuntimeError(f"Object {microscope.camera} has no method `array`")
|
||||
"""Measure the sharpness of the camera's current view."""
|
||||
|
||||
if hasattr(microscope.camera, "array") and callable(
|
||||
getattr(microscope.camera, "array")
|
||||
):
|
||||
return metric_fn(getattr(microscope.camera, "array")(use_video_port=True))
|
||||
else:
|
||||
raise RuntimeError(f"Object {microscope.camera} has no method `array`")
|
||||
|
||||
def autofocus(
|
||||
microscope: Microscope,
|
||||
dz: List[int],
|
||||
settle: float = 0.5,
|
||||
metric_fn: Callable = sharpness_sum_lap2,
|
||||
):
|
||||
"""Perform a simple autofocus routine.
|
||||
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
|
||||
evaulated. We then move back to the position where the sharpness was
|
||||
highest. No interpolation is performed.
|
||||
dz is assumed to be in ascending order (starting at -ve values)
|
||||
"""
|
||||
camera = microscope.camera
|
||||
stage = microscope.stage
|
||||
def autofocus(
|
||||
self,
|
||||
microscope: Microscope,
|
||||
dz: List[int],
|
||||
settle: float = 0.5,
|
||||
metric_fn: Callable = sharpness_sum_lap2,
|
||||
):
|
||||
"""Perform a simple autofocus routine.
|
||||
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
|
||||
evaulated. We then move back to the position where the sharpness was
|
||||
highest. No interpolation is performed.
|
||||
dz is assumed to be in ascending order (starting at -ve values)
|
||||
"""
|
||||
camera = microscope.camera
|
||||
stage = microscope.stage
|
||||
|
||||
with set_properties(stage, backlash=256), stage.lock, camera.lock:
|
||||
sharpnesses = []
|
||||
positions = []
|
||||
with set_properties(stage, backlash=256), stage.lock, camera.lock:
|
||||
sharpnesses = []
|
||||
positions = []
|
||||
|
||||
# Some cameras may not have annotate_text. Reset if it does
|
||||
if getattr(camera, "annotate_text"):
|
||||
setattr(camera, "annotate_text", "")
|
||||
# Some cameras may not have annotate_text. Reset if it does
|
||||
if getattr(camera, "annotate_text"):
|
||||
setattr(camera, "annotate_text", "")
|
||||
|
||||
for _ in stage.scan_z(dz, return_to_start=False):
|
||||
if current_action() and current_action().stopped:
|
||||
return
|
||||
positions.append(stage.position[2])
|
||||
time.sleep(settle)
|
||||
sharpnesses.append(measure_sharpness(microscope, metric_fn))
|
||||
for _ in stage.scan_z(dz, return_to_start=False):
|
||||
if current_action() and current_action().stopped:
|
||||
return
|
||||
positions.append(stage.position[2])
|
||||
time.sleep(settle)
|
||||
sharpnesses.append(self.measure_sharpness(microscope, metric_fn))
|
||||
|
||||
newposition = positions[np.argmax(sharpnesses)]
|
||||
stage.move_rel((0, 0, newposition - stage.position[2]))
|
||||
newposition = positions[np.argmax(sharpnesses)]
|
||||
stage.move_rel((0, 0, newposition - stage.position[2]))
|
||||
|
||||
return positions, sharpnesses
|
||||
return positions, sharpnesses
|
||||
|
||||
|
||||
@contextmanager
|
||||
def monitor_sharpness(microscope: Microscope):
|
||||
m = JPEGSharpnessMonitor(microscope)
|
||||
m.start()
|
||||
try:
|
||||
yield m
|
||||
finally:
|
||||
m.stop()
|
||||
|
||||
|
||||
def move_and_find_focus(microscope: Microscope, dz: int):
|
||||
"""Make a relative Z move and return the peak sharpness position"""
|
||||
with monitor_sharpness(microscope) as m:
|
||||
m.focus_rel(dz)
|
||||
return m.sharpest_z_on_move(0)
|
||||
|
||||
|
||||
def move_and_measure(microscope: Microscope, dz: int):
|
||||
"""Make a relative Z move and return the sharpness data"""
|
||||
with monitor_sharpness(microscope) as m:
|
||||
m.focus_rel(dz)
|
||||
return m.data_dict()
|
||||
|
||||
|
||||
def fast_autofocus(microscope: Microscope, dz: int = 2000):
|
||||
"""Perform a down-up-down-up autofocus"""
|
||||
with microscope.camera.lock, microscope.stage.lock:
|
||||
def move_and_find_focus(self, microscope: Microscope, dz: int):
|
||||
"""Make a relative Z move and return the peak sharpness position"""
|
||||
with monitor_sharpness(microscope) as m:
|
||||
# Move to (-dz / 2)
|
||||
m.focus_rel(-dz / 2)
|
||||
# Move to dz while monitoring sharpness
|
||||
# i: Sharpness monitor index for this move
|
||||
# z: Final z position after move
|
||||
i, z = m.focus_rel(dz)
|
||||
# Get the z position with highest sharpness from the previous move (index i)
|
||||
fz = m.sharpest_z_on_move(i)
|
||||
# Move all the way to the start so it's consistent
|
||||
# Store final absolute z position from this return move
|
||||
i, z = m.focus_rel(-dz)
|
||||
# Move to the target position fz
|
||||
# Can't do absolute move here yet so move by (fz - z)
|
||||
m.focus_rel(fz - z)
|
||||
# Return all focus data
|
||||
m.focus_rel(dz)
|
||||
return m.sharpest_z_on_move(0)
|
||||
|
||||
def move_and_measure(self, microscope: Microscope, dz: int):
|
||||
"""Make a relative Z move and return the sharpness data"""
|
||||
with monitor_sharpness(microscope) as m:
|
||||
m.focus_rel(dz)
|
||||
return m.data_dict()
|
||||
|
||||
def fast_autofocus(self, microscope: Microscope, dz: int = 2000):
|
||||
"""Perform a down-up-down-up autofocus"""
|
||||
with microscope.camera.lock, microscope.stage.lock:
|
||||
with monitor_sharpness(microscope) as m:
|
||||
# Move to (-dz / 2)
|
||||
m.focus_rel(-dz / 2)
|
||||
# Move to dz while monitoring sharpness
|
||||
# i: Sharpness monitor index for this move
|
||||
# z: Final z position after move
|
||||
i, z = m.focus_rel(dz)
|
||||
# Get the z position with highest sharpness from the previous move (index i)
|
||||
fz = m.sharpest_z_on_move(i)
|
||||
# Move all the way to the start so it's consistent
|
||||
# Store final absolute z position from this return move
|
||||
i, z = m.focus_rel(-dz)
|
||||
# Move to the target position fz
|
||||
# Can't do absolute move here yet so move by (fz - z)
|
||||
m.focus_rel(fz - z)
|
||||
# Return all focus data
|
||||
return m.data_dict()
|
||||
|
||||
def fast_up_down_up_autofocus(
|
||||
microscope: Microscope,
|
||||
dz: int = 2000,
|
||||
target_z: int = 0,
|
||||
initial_move_up: bool = True,
|
||||
mini_backlash: int = 25,
|
||||
):
|
||||
"""Autofocus by measuring on the way down, and moving back up with feedback.
|
||||
def fast_up_down_up_autofocus(
|
||||
self,
|
||||
microscope: Microscope,
|
||||
dz: int = 2000,
|
||||
target_z: int = 0,
|
||||
initial_move_up: bool = True,
|
||||
mini_backlash: int = 25,
|
||||
):
|
||||
"""Autofocus by measuring on the way down, and moving back up with feedback.
|
||||
|
||||
This autofocus method is very efficient, as it only passes the peak once.
|
||||
The sequence of moves it performs is:
|
||||
This autofocus method is very efficient, as it only passes the peak once.
|
||||
The sequence of moves it performs is:
|
||||
|
||||
1. Move to the top of the range `dz/2` (can be disabled)
|
||||
1. Move to the top of the range `dz/2` (can be disabled)
|
||||
|
||||
2. Move down by `dz` while monitoring JPEG size to find the focus.
|
||||
2. Move down by `dz` while monitoring JPEG size to find the focus.
|
||||
|
||||
3. Move back up to the `target_z` position, relative to the sharpest image.
|
||||
3. Move back up to the `target_z` position, relative to the sharpest image.
|
||||
|
||||
4. Measure the sharpness, and compare against the curve recorded in (2) to \\
|
||||
estimate how much further we need to go. Make this move, to reach our \\
|
||||
target position.
|
||||
4. Measure the sharpness, and compare against the curve recorded in (2) to \\
|
||||
estimate how much further we need to go. Make this move, to reach our \\
|
||||
target position.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
Parameters:
|
||||
dz: number of steps over which to scan (optional, default 2000)
|
||||
Parameters:
|
||||
dz: number of steps over which to scan (optional, default 2000)
|
||||
|
||||
target_z: we aim to finish at this position, relative to focus. This may
|
||||
be useful if, for example, you want to acquire a stack of images in Z.
|
||||
It is optional, and the default value of 0 will finish at the focus.
|
||||
target_z: we aim to finish at this position, relative to focus. This may
|
||||
be useful if, for example, you want to acquire a stack of images in Z.
|
||||
It is optional, and the default value of 0 will finish at the focus.
|
||||
|
||||
initial_move_up: (optional, default True) set this to `False` to move down
|
||||
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.
|
||||
initial_move_up: (optional, default True) set this to `False` to move down
|
||||
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.
|
||||
|
||||
mini_backlash: (optional, default 25) is a small extra move made in step
|
||||
3 to help counteract backlash. It should be small enough that you
|
||||
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
|
||||
may cause you to overshoot, which is a problem.
|
||||
"""
|
||||
with microscope.camera.lock, microscope.stage.lock:
|
||||
with monitor_sharpness(microscope) as m:
|
||||
# Ensure the MJPEG stream has started
|
||||
microscope.camera.start_stream()
|
||||
mini_backlash: (optional, default 25) is a small extra move made in step
|
||||
3 to help counteract backlash. It should be small enough that you
|
||||
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
|
||||
may cause you to overshoot, which is a problem.
|
||||
"""
|
||||
with microscope.camera.lock, microscope.stage.lock:
|
||||
with monitor_sharpness(microscope) as m:
|
||||
# Ensure the MJPEG stream has started
|
||||
microscope.camera.start_stream()
|
||||
|
||||
logging.debug("Initial move")
|
||||
if initial_move_up:
|
||||
m.focus_rel(dz / 2)
|
||||
# move down
|
||||
logging.debug("Move down")
|
||||
i, z = m.focus_rel(-dz)
|
||||
# now inspect where the sharpest point is, and estimate the sharpness
|
||||
# (JPEG size) that we should find at the start of the Z stack
|
||||
_, jz, js = m.move_data(i)
|
||||
best_z = jz[np.argmax(js)]
|
||||
logging.debug("Initial move")
|
||||
if initial_move_up:
|
||||
m.focus_rel(dz / 2)
|
||||
# move down
|
||||
logging.debug("Move down")
|
||||
i, z = m.focus_rel(-dz)
|
||||
# now inspect where the sharpest point is, and estimate the sharpness
|
||||
# (JPEG size) that we should find at the start of the Z stack
|
||||
_, jz, js = m.move_data(i)
|
||||
best_z = jz[np.argmax(js)]
|
||||
|
||||
# now move to the start of the z stack
|
||||
logging.debug("Move to the start of the z stack")
|
||||
i, z = m.focus_rel(
|
||||
best_z + target_z - z + mini_backlash
|
||||
) # takes us to the start of the stack
|
||||
# now move to the start of the z stack
|
||||
logging.debug("Move to the start of the z stack")
|
||||
i, z = m.focus_rel(
|
||||
best_z + target_z - z + mini_backlash
|
||||
) # takes us to the start of the stack
|
||||
|
||||
# We've deliberately undershot - figure out how much further we should move based on the curve
|
||||
logging.debug("Calculate remining movement")
|
||||
current_js = m.camera.stream.last.size
|
||||
imax = np.argmax(js) # we want to crop out just the bit below the peak
|
||||
js = js[imax:] # NB z is in DECREASING order
|
||||
jz = jz[imax:]
|
||||
inow = np.argmax(
|
||||
js < current_js
|
||||
) # use the curve we recorded to estimate our position
|
||||
# We've deliberately undershot - figure out how much further we should move based on the curve
|
||||
logging.debug("Calculate remining movement")
|
||||
current_js = m.camera.stream.last.size
|
||||
imax = np.argmax(js) # we want to crop out just the bit below the peak
|
||||
js = js[imax:] # NB z is in DECREASING order
|
||||
jz = jz[imax:]
|
||||
inow = np.argmax(
|
||||
js < current_js
|
||||
) # use the curve we recorded to estimate our position
|
||||
|
||||
# So, the Z position corresponding to our current sharpness value is zs[inow]
|
||||
# That means we should move forwards, by best_z - zs[inow]
|
||||
logging.debug("Correction move")
|
||||
correction_move = best_z + target_z - jz[inow]
|
||||
logging.debug(
|
||||
"Fast autofocus scan: correcting backlash by moving %s steps",
|
||||
(correction_move),
|
||||
)
|
||||
m.focus_rel(correction_move)
|
||||
return m.data_dict()
|
||||
# So, the Z position corresponding to our current sharpness value is zs[inow]
|
||||
# That means we should move forwards, by best_z - zs[inow]
|
||||
logging.debug("Correction move")
|
||||
correction_move = best_z + target_z - jz[inow]
|
||||
logging.debug(
|
||||
"Fast autofocus scan: correcting backlash by moving %s steps",
|
||||
(correction_move),
|
||||
)
|
||||
m.focus_rel(correction_move)
|
||||
return m.data_dict()
|
||||
|
||||
|
||||
class MeasureSharpnessAPI(View):
|
||||
|
|
@ -316,7 +335,7 @@ class MeasureSharpnessAPI(View):
|
|||
if not microscope:
|
||||
abort(503, "No microscope connected. Unable to measure sharpness.")
|
||||
|
||||
return {"sharpness": measure_sharpness(microscope)}
|
||||
return {"sharpness": self.extension.measure_sharpness(microscope)}
|
||||
|
||||
|
||||
class MoveAndMeasureAPI(ActionView):
|
||||
|
|
@ -335,7 +354,7 @@ class MoveAndMeasureAPI(ActionView):
|
|||
if microscope.has_real_stage():
|
||||
# Acquire microscope lock with 1s timeout
|
||||
with microscope.camera.lock, microscope.stage.lock:
|
||||
return move_and_measure(microscope, dz=args.get("dz"))
|
||||
return self.extension.move_and_measure(microscope, dz=args.get("dz"))
|
||||
|
||||
else:
|
||||
abort(503, "No stage connected. Unable to autofocus.")
|
||||
|
|
@ -361,7 +380,7 @@ class AutofocusAPI(ActionView):
|
|||
logging.debug("Running autofocus...")
|
||||
|
||||
# return a handle on the autofocus task
|
||||
return autofocus(microscope, dz)
|
||||
return self.extension.autofocus(microscope, dz)
|
||||
|
||||
else:
|
||||
abort(503, "No stage connected. Unable to autofocus.")
|
||||
|
|
@ -385,7 +404,7 @@ class FastAutofocusAPI(ActionView):
|
|||
# Acquire microscope lock with 1s timeout
|
||||
with microscope.lock(timeout=1):
|
||||
# Run fast_autofocus
|
||||
return fast_autofocus(microscope, dz=args.get("dz"))
|
||||
return self.extension.fast_autofocus(microscope, dz=args.get("dz"))
|
||||
|
||||
else:
|
||||
abort(503, "No stage connected. Unable to autofocus.")
|
||||
|
|
@ -417,38 +436,9 @@ class UpDownUpAutofocusAPI(ActionView):
|
|||
# Acquire microscope lock with 1s timeout
|
||||
with microscope.lock(timeout=1):
|
||||
# Run fast_up_down_up_autofocus
|
||||
return 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.")
|
||||
|
||||
|
||||
autofocus_extension_v2 = BaseExtension(
|
||||
"org.openflexure.autofocus",
|
||||
version="2.0.0",
|
||||
description="Actions to move the microscope in Z and pick the point with the sharpest image.",
|
||||
)
|
||||
|
||||
autofocus_extension_v2.add_method(fast_autofocus, "fast_autofocus")
|
||||
autofocus_extension_v2.add_method(
|
||||
fast_up_down_up_autofocus, "fast_up_down_up_autofocus"
|
||||
)
|
||||
autofocus_extension_v2.add_method(autofocus, "autofocus")
|
||||
autofocus_extension_v2.add_method(move_and_measure, "move_and_measure")
|
||||
|
||||
autofocus_extension_v2.add_view(
|
||||
MeasureSharpnessAPI, "/measure_sharpness", endpoint="measure_sharpness"
|
||||
)
|
||||
autofocus_extension_v2.add_view(AutofocusAPI, "/autofocus", endpoint="autofocus")
|
||||
autofocus_extension_v2.add_view(
|
||||
FastAutofocusAPI, "/fast_autofocus", endpoint="fast_autofocus"
|
||||
)
|
||||
autofocus_extension_v2.add_view(
|
||||
UpDownUpAutofocusAPI, "/updownup_autofocus", endpoint="updownup_autofocus"
|
||||
)
|
||||
|
||||
autofocus_extension_v2.add_view(
|
||||
MoveAndMeasureAPI, "/move_and_measure", endpoint="move_and_measure"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -93,12 +93,43 @@ class AutostorageExtension(BaseExtension):
|
|||
|
||||
# We'll store a reference to a CaptureManager object, who's capture paths will be modified
|
||||
self.capture_manager: Optional[CaptureManager] = None
|
||||
|
||||
self.initial_location: str = get_default_location()
|
||||
|
||||
# Register the on_microscope function to run when the microscope is attached
|
||||
self.on_component("org.openflexure.microscope", self.on_microscope)
|
||||
|
||||
self.add_view(GetLocationsView, "/list-locations")
|
||||
self.add_view(PreferredLocationView, "/location")
|
||||
self.add_view(PreferredLocationGUIView, "/location-from-title")
|
||||
self.add_meta("gui", build_gui(self.dynamic_form, self))
|
||||
|
||||
def dynamic_form(self):
|
||||
self.check_location()
|
||||
return {
|
||||
"icon": "sd_storage",
|
||||
"title": "Storage",
|
||||
"viewPanel": "gallery",
|
||||
"forms": [
|
||||
{
|
||||
"name": "Autostorage",
|
||||
"isCollapsible": False,
|
||||
"isTask": False,
|
||||
"route": "/location-from-title",
|
||||
"emitOnResponse": "globalUpdateCaptures",
|
||||
"submitLabel": "Set path",
|
||||
"schema": [
|
||||
{
|
||||
"fieldType": "selectList",
|
||||
"name": "new_path_title",
|
||||
"label": "Capture storage path",
|
||||
"options": self.get_titles(),
|
||||
"value": self.get_preferred_title(),
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
def on_microscope(self, microscope_obj: Microscope):
|
||||
"""Function to automatically call when the parent LabThing has a microscope attached."""
|
||||
logging.debug("Autostorage extension found microscope %s", microscope_obj)
|
||||
|
|
@ -204,12 +235,9 @@ class AutostorageExtension(BaseExtension):
|
|||
return self.key_to_title(self.get_preferred_key())
|
||||
|
||||
|
||||
autostorage_extension_v2 = AutostorageExtension()
|
||||
|
||||
|
||||
class GetLocationsView(PropertyView):
|
||||
def get(self):
|
||||
autostorage_extension_v2.check_location()
|
||||
self.extension.check_location()
|
||||
return autostorage_extension_v2.get_locations()
|
||||
|
||||
|
||||
|
|
@ -217,8 +245,8 @@ class PreferredLocationView(PropertyView):
|
|||
schema = fields.String(required=True, example="Default")
|
||||
|
||||
def get(self):
|
||||
autostorage_extension_v2.check_location()
|
||||
return autostorage_extension_v2.get_preferred_key()
|
||||
self.extension.check_location()
|
||||
return self.extension.get_preferred_key()
|
||||
|
||||
def post(self, new_path_key):
|
||||
microscope = find_component("org.openflexure.microscope")
|
||||
|
|
@ -226,8 +254,8 @@ class PreferredLocationView(PropertyView):
|
|||
if not microscope:
|
||||
abort(503, "No microscope connected. Unable to autofocus.")
|
||||
|
||||
autostorage_extension_v2.check_location()
|
||||
autostorage_extension_v2.set_preferred_key(new_path_key)
|
||||
self.extension.check_location()
|
||||
self.extension.set_preferred_key(new_path_key)
|
||||
microscope.save_settings()
|
||||
|
||||
|
||||
|
|
@ -237,46 +265,11 @@ class PreferredLocationGUIView(View):
|
|||
new_path_title = args.get("new_path_title")
|
||||
logging.debug(new_path_title)
|
||||
|
||||
new_path_key = autostorage_extension_v2.title_to_key(new_path_title)
|
||||
new_path_key = self.extension.title_to_key(new_path_title)
|
||||
logging.debug(new_path_key)
|
||||
|
||||
autostorage_extension_v2.check_location()
|
||||
autostorage_extension_v2.set_preferred_key(new_path_key)
|
||||
self.extension.check_location()
|
||||
self.extension.set_preferred_key(new_path_key)
|
||||
|
||||
return new_path_title
|
||||
|
||||
|
||||
def dynamic_form():
|
||||
autostorage_extension_v2.check_location()
|
||||
return {
|
||||
"icon": "sd_storage",
|
||||
"title": "Storage",
|
||||
"viewPanel": "gallery",
|
||||
"forms": [
|
||||
{
|
||||
"name": "Autostorage",
|
||||
"isCollapsible": False,
|
||||
"isTask": False,
|
||||
"route": "/location-from-title",
|
||||
"emitOnResponse": "globalUpdateCaptures",
|
||||
"submitLabel": "Set path",
|
||||
"schema": [
|
||||
{
|
||||
"fieldType": "selectList",
|
||||
"name": "new_path_title",
|
||||
"label": "Capture storage path",
|
||||
"options": autostorage_extension_v2.get_titles(),
|
||||
"value": autostorage_extension_v2.get_preferred_title(),
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
autostorage_extension_v2.add_view(GetLocationsView, "/list-locations")
|
||||
autostorage_extension_v2.add_view(PreferredLocationView, "/location")
|
||||
autostorage_extension_v2.add_view(PreferredLocationGUIView, "/location-from-title")
|
||||
autostorage_extension_v2.add_meta(
|
||||
"gui", build_gui(dynamic_form, autostorage_extension_v2)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
from .extension import lst_extension_v2
|
||||
from .extension import LSTExtension
|
||||
|
|
|
|||
|
|
@ -42,23 +42,43 @@ def pause_stream(scamera: BaseCamera):
|
|||
scamera.start_stream()
|
||||
|
||||
|
||||
def recalibrate(microscope: Microscope):
|
||||
"""Reset the camera's settings.
|
||||
class LSTExtension(BaseExtension):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
"org.openflexure.calibration.picamera",
|
||||
version="2.0.0-beta.1",
|
||||
description="Routines to perform flat-field correction on the camera.",
|
||||
)
|
||||
|
||||
This generates new gains, exposure time, and lens shading
|
||||
table such that the background is as uniform as possible
|
||||
with a gray level of 230. It takes a little while to run.
|
||||
"""
|
||||
with pause_stream(microscope.camera) as scamera:
|
||||
if hasattr(scamera, "picamera"):
|
||||
picamera_obj: picamerax.PiCamera = getattr(scamera, "picamera")
|
||||
auto_expose_and_freeze_settings(picamera_obj)
|
||||
recalibrate_camera(picamera_obj)
|
||||
microscope.save_settings()
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"Recalibrate can only be used with a Raspberry Pi camera"
|
||||
)
|
||||
self.add_view(RecalibrateView, "/recalibrate", endpoint="recalibrate")
|
||||
self.add_view(
|
||||
FlattenLSTView,
|
||||
"/flatten_lens_shading_table",
|
||||
endpoint="flatten_lens_shading_table",
|
||||
)
|
||||
self.add_view(
|
||||
DeleteLSTView,
|
||||
"/delete_lens_shading_table",
|
||||
endpoint="delete_lens_shading_table",
|
||||
)
|
||||
|
||||
def recalibrate(self, microscope: Microscope):
|
||||
"""Reset the camera's settings.
|
||||
|
||||
This generates new gains, exposure time, and lens shading
|
||||
table such that the background is as uniform as possible
|
||||
with a gray level of 230. It takes a little while to run.
|
||||
"""
|
||||
with pause_stream(microscope.camera) as scamera:
|
||||
if hasattr(scamera, "picamera"):
|
||||
picamera_obj: picamerax.PiCamera = getattr(scamera, "picamera")
|
||||
auto_expose_and_freeze_settings(picamera_obj)
|
||||
recalibrate_camera(picamera_obj)
|
||||
microscope.save_settings()
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"Recalibrate can only be used with a Raspberry Pi camera"
|
||||
)
|
||||
|
||||
|
||||
class RecalibrateView(ActionView):
|
||||
|
|
@ -70,7 +90,7 @@ class RecalibrateView(ActionView):
|
|||
|
||||
logging.info("Starting microscope recalibration...")
|
||||
|
||||
return recalibrate(microscope)
|
||||
return self.extension.recalibrate(microscope)
|
||||
|
||||
|
||||
class FlattenLSTView(ActionView):
|
||||
|
|
@ -102,22 +122,3 @@ class DeleteLSTView(ActionView):
|
|||
with pause_stream(microscope.camera) as scamera:
|
||||
scamera.camera.lens_shading_table = None
|
||||
microscope.save_settings()
|
||||
|
||||
|
||||
lst_extension_v2 = BaseExtension(
|
||||
"org.openflexure.calibration.picamera",
|
||||
version="2.0.0-beta.1",
|
||||
description="Routines to perform flat-field correction on the camera.",
|
||||
)
|
||||
|
||||
lst_extension_v2.add_method(
|
||||
recalibrate, "org.openflexure.calibration.picamera.recalibrate"
|
||||
)
|
||||
|
||||
lst_extension_v2.add_view(RecalibrateView, "/recalibrate", endpoint="recalibrate")
|
||||
lst_extension_v2.add_view(
|
||||
FlattenLSTView, "/flatten_lens_shading_table", endpoint="flatten_lens_shading_table"
|
||||
)
|
||||
lst_extension_v2.add_view(
|
||||
DeleteLSTView, "/delete_lens_shading_table", endpoint="delete_lens_shading_table"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -99,9 +99,12 @@ def construct_grid(
|
|||
|
||||
class ScanExtension(BaseExtension):
|
||||
def __init__(self):
|
||||
BaseExtension.__init__(self, "org.openflexure.scan", version="2.0.0")
|
||||
|
||||
self._images_to_be_captured: int = 1
|
||||
self._images_captured_so_far: int = 0
|
||||
BaseExtension.__init__(self, "org.openflexure.scan", version="2.0.0")
|
||||
|
||||
self.add_view(TileScanAPI, "/tile", endpoint="tile")
|
||||
|
||||
def capture(
|
||||
self,
|
||||
|
|
@ -356,9 +359,6 @@ class ScanExtension(BaseExtension):
|
|||
microscope.stage.move_abs(initial_position)
|
||||
|
||||
|
||||
scan_extension_v2 = ScanExtension()
|
||||
|
||||
|
||||
class TileScanArgs(FullCaptureArgs):
|
||||
namemode = fields.String(missing="coordinates", example="coordinates")
|
||||
grid = fields.List(fields.Integer, missing=[3, 3, 3], example=[3, 3, 3])
|
||||
|
|
@ -398,7 +398,7 @@ class TileScanAPI(ActionView):
|
|||
# Acquire microscope lock with 1s timeout
|
||||
with microscope.lock(timeout=1):
|
||||
# Run scan_extension_v2
|
||||
return scan_extension_v2.tile(
|
||||
return self.extension.scan_extension_v2.tile(
|
||||
microscope,
|
||||
basename=args.get("filename"),
|
||||
namemode=args.get("namemode"),
|
||||
|
|
@ -414,6 +414,3 @@ class TileScanAPI(ActionView):
|
|||
annotations=args.get("annotations"),
|
||||
tags=args.get("tags"),
|
||||
)
|
||||
|
||||
|
||||
scan_extension_v2.add_view(TileScanAPI, "/tile", endpoint="tile")
|
||||
|
|
|
|||
|
|
@ -131,8 +131,18 @@ class ZipManager:
|
|||
zd.close()
|
||||
|
||||
|
||||
# Create a global ZIP manager
|
||||
default_zip_manager = ZipManager()
|
||||
class ZipBuilderExtension(BaseExtension):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
"org.openflexure.zipbuilder",
|
||||
version="2.0.0",
|
||||
description="Build and download capture collections as ZIP files",
|
||||
)
|
||||
self.manager = ZipManager()
|
||||
|
||||
self.add_view(ZipGetterAPIView, "/get/<string:session_id>", endpoint="get_id")
|
||||
self.add_view(ZipListAPIView, "/get", endpoint="get")
|
||||
self.add_view(ZipBuilderAPIView, "/build", endpoint="build")
|
||||
|
||||
|
||||
class ZipBuilderAPIView(ActionView):
|
||||
|
|
@ -142,7 +152,7 @@ class ZipBuilderAPIView(ActionView):
|
|||
microscope = find_component("org.openflexure.microscope")
|
||||
|
||||
# Return a handle on the autofocus task
|
||||
return default_zip_manager.marshaled_build_zip_from_capture_ids(
|
||||
return self.extension.manager.marshaled_build_zip_from_capture_ids(
|
||||
microscope, args
|
||||
)
|
||||
|
||||
|
|
@ -151,7 +161,7 @@ class ZipListAPIView(PropertyView):
|
|||
schema = ZipObjectSchema(many=True)
|
||||
|
||||
def get(self):
|
||||
return default_zip_manager.session_zips.values()
|
||||
return self.extension.manager.session_zips.values()
|
||||
|
||||
|
||||
class ZipGetterAPIView(View):
|
||||
|
|
@ -163,13 +173,13 @@ class ZipGetterAPIView(View):
|
|||
"""
|
||||
Download a particular capture collection ZIP file
|
||||
"""
|
||||
if not session_id in default_zip_manager.session_zips:
|
||||
if not session_id in self.extension.manager.session_zips:
|
||||
return abort(404) # 404 Not Found
|
||||
|
||||
logging.info("Session ID: %s", session_id)
|
||||
|
||||
return send_file(
|
||||
default_zip_manager.zip_fp_from_id(session_id).name,
|
||||
self.extension.manager.zip_fp_from_id(session_id).name,
|
||||
mimetype="application/zip",
|
||||
as_attachment=True,
|
||||
attachment_filename=f"{session_id}.zip",
|
||||
|
|
@ -179,26 +189,12 @@ class ZipGetterAPIView(View):
|
|||
"""
|
||||
Close and delete a particular capture collection ZIP file
|
||||
"""
|
||||
if not session_id in default_zip_manager.session_zips:
|
||||
if not session_id in self.extension.manager.session_zips:
|
||||
return abort(404) # 404 Not Found
|
||||
|
||||
# Close the file
|
||||
default_zip_manager.session_zips[session_id].close()
|
||||
self.extension.manager.session_zips[session_id].close()
|
||||
# Delete the file reference
|
||||
del default_zip_manager.session_zips[session_id]
|
||||
del self.extension.manager.session_zips[session_id]
|
||||
|
||||
return {"return": session_id}
|
||||
|
||||
|
||||
zip_extension_v2 = BaseExtension(
|
||||
"org.openflexure.zipbuilder",
|
||||
version="2.0.0",
|
||||
description="Build and download capture collections as ZIP files",
|
||||
)
|
||||
|
||||
zip_extension_v2.add_view(
|
||||
ZipGetterAPIView, "/get/<string:session_id>", endpoint="get_id"
|
||||
)
|
||||
zip_extension_v2.add_view(ZipListAPIView, "/get", endpoint="get")
|
||||
|
||||
zip_extension_v2.add_view(ZipBuilderAPIView, "/build", endpoint="build")
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
from .tools import devtools_extension_v2
|
||||
|
||||
LABTHINGS_EXTENSIONS = [devtools_extension_v2]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue