Commit 8d0759e9 authored by Joel Collins's avatar Joel Collins
Browse files

Updated default extensions to subclass structure

parent bacc111a
Loading
Loading
Loading
Loading
Loading
+22 −6
Original line number Diff line number Diff line
@@ -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)
+196 −206
Original line number Diff line number Diff line
@@ -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,7 +139,29 @@ def sharpness_edge(image: np.ndarray):
### Autofocus extension


def measure_sharpness(microscope: Microscope, metric_fn: Callable = sharpness_sum_lap2):
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"
        )

        self.add_view(
            MoveAndMeasureAPI, "/move_and_measure", endpoint="move_and_measure"
        )

    def measure_sharpness(
        self, microscope: Microscope, metric_fn: Callable = sharpness_sum_lap2
    ):
        """Measure the sharpness of the camera's current view."""

        if hasattr(microscope.camera, "array") and callable(
@@ -139,8 +171,8 @@ def measure_sharpness(microscope: Microscope, metric_fn: Callable = sharpness_su
        else:
            raise RuntimeError(f"Object {microscope.camera} has no method `array`")


    def autofocus(
        self,
        microscope: Microscope,
        dz: List[int],
        settle: float = 0.5,
@@ -169,39 +201,26 @@ def autofocus(
                    return
                positions.append(stage.position[2])
                time.sleep(settle)
            sharpnesses.append(measure_sharpness(microscope, metric_fn))
                sharpnesses.append(self.measure_sharpness(microscope, metric_fn))

            newposition = positions[np.argmax(sharpnesses)]
            stage.move_rel((0, 0, newposition - stage.position[2]))

        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):
    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:
            m.focus_rel(dz)
            return m.sharpest_z_on_move(0)


def move_and_measure(microscope: Microscope, dz: int):
    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(microscope: Microscope, dz: int = 2000):
    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:
@@ -222,8 +241,8 @@ def fast_autofocus(microscope: Microscope, dz: int = 2000):
                # Return all focus data
                return m.data_dict()


    def fast_up_down_up_autofocus(
        self,
        microscope: Microscope,
        dz: int = 2000,
        target_z: int = 0,
@@ -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"
)
+40 −47
Original line number Diff line number Diff line
@@ -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
Original line number Diff line number Diff line
from .extension import lst_extension_v2
from .extension import LSTExtension
+38 −37
Original line number Diff line number Diff line
@@ -42,7 +42,27 @@ def pause_stream(scamera: BaseCamera):
                scamera.start_stream()


def recalibrate(microscope: Microscope):
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.",
        )

        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
@@ -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"
)
Loading