Convert Stage to a Protocol
The Stage is now implemented as a runtime-checkable Protocol. Part of this led to the realisation that we can't create a DirectThingClient based on a Protocol, only on a concrete Thing. While it might be a good idea to address that in LabThings in due course, for now we work around it by creating both a Protocol and a stub class. The stub class implements the protocol, and can be used to declare a dependency.
This commit is contained in:
parent
2e2e877d75
commit
a0fbbc3e40
7 changed files with 77 additions and 25 deletions
|
|
@ -4,11 +4,10 @@ import logging
|
||||||
from labthings_fastapi.thing import Thing
|
from labthings_fastapi.thing import Thing
|
||||||
from labthings_fastapi.dependencies.thing import direct_thing_client_dependency
|
from labthings_fastapi.dependencies.thing import direct_thing_client_dependency
|
||||||
from labthings_fastapi.decorators import thing_action
|
from labthings_fastapi.decorators import thing_action
|
||||||
from .stage import Stage
|
from .stage import StageDependency as StageDep
|
||||||
from openflexure_microscope_server.things.autofocus import AutofocusThing
|
from openflexure_microscope_server.things.autofocus import AutofocusThing
|
||||||
from openflexure_microscope_server.things.camera_stage_mapping import CameraStageMapper
|
from openflexure_microscope_server.things.camera_stage_mapping import CameraStageMapper
|
||||||
|
|
||||||
StageDep = direct_thing_client_dependency(Stage, "/stage/")
|
|
||||||
CSMDep = direct_thing_client_dependency(CameraStageMapper, "/camera_stage_mapping/")
|
CSMDep = direct_thing_client_dependency(CameraStageMapper, "/camera_stage_mapping/")
|
||||||
AutofocusDep = direct_thing_client_dependency(AutofocusThing, "/autofocus/")
|
AutofocusDep = direct_thing_client_dependency(AutofocusThing, "/autofocus/")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,10 @@ from labthings_fastapi.decorators import thing_action
|
||||||
from labthings_fastapi.types.numpy import NDArray
|
from labthings_fastapi.types.numpy import NDArray
|
||||||
from .camera import RawCameraDependency as Camera
|
from .camera import RawCameraDependency as Camera
|
||||||
from .camera import CameraDependency as WrappedCamera
|
from .camera import CameraDependency as WrappedCamera
|
||||||
from .stage import Stage as StageThing
|
from .stage import StageDependency as Stage
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
Stage = direct_thing_client_dependency(StageThing, "/stage/")
|
|
||||||
|
|
||||||
### Autofocus utilities
|
### Autofocus utilities
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ from labthings_fastapi.types.numpy import NDArray
|
||||||
from labthings_fastapi.server import ThingServer
|
from labthings_fastapi.server import ThingServer
|
||||||
|
|
||||||
from . import BaseCamera, JPEGBlob
|
from . import BaseCamera, JPEGBlob
|
||||||
from ..stage import Stage
|
from ..stage import StageProtocol as Stage
|
||||||
|
|
||||||
|
|
||||||
class SimulatedCamera(BaseCamera):
|
class SimulatedCamera(BaseCamera):
|
||||||
|
|
|
||||||
|
|
@ -21,17 +21,13 @@ from camera_stage_mapping.camera_stage_calibration_1d import (
|
||||||
calibrate_backlash_1d,
|
calibrate_backlash_1d,
|
||||||
image_to_stage_displacement_from_1d,
|
image_to_stage_displacement_from_1d,
|
||||||
)
|
)
|
||||||
from camera_stage_mapping.camera_stage_tracker import Tracker
|
|
||||||
from .camera import CameraDependency as Camera
|
|
||||||
from .stage import Stage as StageThing
|
|
||||||
|
|
||||||
from labthings_fastapi.dependencies.thing import direct_thing_client_dependency
|
|
||||||
from labthings_fastapi.dependencies.invocation import InvocationCancelledError, InvocationLogger
|
from labthings_fastapi.dependencies.invocation import InvocationCancelledError, InvocationLogger
|
||||||
from labthings_fastapi.types.numpy import NDArray, denumpify, DenumpifyingDict
|
from labthings_fastapi.types.numpy import NDArray, denumpify, DenumpifyingDict
|
||||||
from labthings_fastapi.decorators import thing_action, thing_property
|
from labthings_fastapi.decorators import thing_action, thing_property
|
||||||
from labthings_fastapi.thing import Thing
|
from labthings_fastapi.thing import Thing
|
||||||
|
from camera_stage_mapping.camera_stage_tracker import Tracker
|
||||||
Stage = direct_thing_client_dependency(StageThing, "/stage/")
|
from .camera import CameraDependency as Camera
|
||||||
|
from .stage import StageDependency as Stage
|
||||||
|
|
||||||
CoordinateType = Tuple[float, float, float]
|
CoordinateType = Tuple[float, float, float]
|
||||||
XYCoordinateType = Tuple[float, float]
|
XYCoordinateType = Tuple[float, float]
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,12 @@ from labthings_fastapi.dependencies.invocation import CancelHook, InvocationLogg
|
||||||
from labthings_fastapi.decorators import thing_action, thing_property, fastapi_endpoint
|
from labthings_fastapi.decorators import thing_action, thing_property, fastapi_endpoint
|
||||||
from labthings_fastapi.outputs.blob import blob_type
|
from labthings_fastapi.outputs.blob import blob_type
|
||||||
from .camera import CameraDependency as CamDep
|
from .camera import CameraDependency as CamDep
|
||||||
from .stage import Stage
|
from .stage import StageDependency as StageDep
|
||||||
from openflexure_microscope_server.things.autofocus import AutofocusThing
|
from openflexure_microscope_server.things.autofocus import AutofocusThing
|
||||||
from openflexure_microscope_server.things.camera_stage_mapping import CameraStageMapper
|
from openflexure_microscope_server.things.camera_stage_mapping import CameraStageMapper
|
||||||
from openflexure_microscope_server.things.auto_recentre_stage import RecentringThing
|
from openflexure_microscope_server.things.auto_recentre_stage import RecentringThing
|
||||||
|
|
||||||
|
|
||||||
StageDep = direct_thing_client_dependency(Stage, "/stage/")
|
|
||||||
CSMDep = direct_thing_client_dependency(CameraStageMapper, "/camera_stage_mapping/")
|
CSMDep = direct_thing_client_dependency(CameraStageMapper, "/camera_stage_mapping/")
|
||||||
AutofocusDep = direct_thing_client_dependency(AutofocusThing, "/autofocus/")
|
AutofocusDep = direct_thing_client_dependency(AutofocusThing, "/autofocus/")
|
||||||
RecentreStage = direct_thing_client_dependency(RecentringThing, "/auto_recentre_stage/")
|
RecentreStage = direct_thing_client_dependency(RecentringThing, "/auto_recentre_stage/")
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,63 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
from typing import Any, Protocol, TypeAlias, runtime_checkable
|
||||||
from labthings_fastapi.descriptors.property import PropertyDescriptor
|
from labthings_fastapi.descriptors.property import PropertyDescriptor
|
||||||
from labthings_fastapi.thing import Thing
|
from labthings_fastapi.thing import Thing
|
||||||
from labthings_fastapi.decorators import thing_action, thing_property
|
from labthings_fastapi.decorators import thing_action, thing_property
|
||||||
from labthings_fastapi.dependencies.invocation import CancelHook
|
from labthings_fastapi.dependencies.invocation import CancelHook
|
||||||
|
from labthings_fastapi.dependencies.thing import direct_thing_client_dependency
|
||||||
from collections.abc import Sequence, Mapping
|
from collections.abc import Sequence, Mapping
|
||||||
|
|
||||||
|
@runtime_checkable
|
||||||
|
class StageProtocol(Protocol):
|
||||||
|
"""A protocol for the OpenFlexure translation stage
|
||||||
|
"""
|
||||||
|
_axis_names: Sequence[str]
|
||||||
|
|
||||||
class Stage(Thing):
|
@property
|
||||||
"""A dummy stage for testing purposes
|
def axis_names(self) -> Sequence[str]:
|
||||||
|
"""The names of the stage's axes, in order."""
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def position(self) -> Mapping[str, int]:
|
||||||
|
"""Current position of the stage"""
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def moving() -> bool:
|
||||||
|
"Whether the stage is in motion"
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def thing_state(self) -> Mapping[str, Any]:
|
||||||
|
"""Summary metadata describing the current state of the stage"""
|
||||||
|
...
|
||||||
|
|
||||||
This stage should work similarly to a Sangaboard stage, but without any
|
def move_relative(self, cancel: CancelHook, block_cancellation: bool=False, **kwargs: Mapping[str, int]):
|
||||||
hardware attached.
|
"""Make a relative move. Keyword arguments should be axis names."""
|
||||||
|
...
|
||||||
|
|
||||||
|
def move_absolute(self, cancel: CancelHook, block_cancellation: bool=False, **kwargs: Mapping[str, int]):
|
||||||
|
"""Make an absolute move. Keyword arguments should be axis names."""
|
||||||
|
...
|
||||||
|
|
||||||
|
def set_zero_position(self):
|
||||||
|
"""Make the current position zero in all axes
|
||||||
|
|
||||||
|
This action does not move the stage, but resets the position to zero.
|
||||||
|
It is intended for use after manually or automatically recentring the
|
||||||
|
stage.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class BaseStage(Thing):
|
||||||
|
"""A base stage class for OpenFlexure translation stages
|
||||||
|
|
||||||
|
This can't be used directly but should reduce boilerplate code when
|
||||||
|
implementing new stages. A minimal working stage must implement
|
||||||
|
`move_relative` and `move_absolute` actions, which update the
|
||||||
|
`position` property on completion, and provide `set_zero_position`.
|
||||||
"""
|
"""
|
||||||
_axis_names = ("x", "y", "z")
|
_axis_names = ("x", "y", "z")
|
||||||
|
|
||||||
|
|
@ -41,17 +88,26 @@ class Stage(Thing):
|
||||||
return {
|
return {
|
||||||
"position": self.position
|
"position": self.position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class StageStub(BaseStage):
|
||||||
|
"""A Stub Thing for the OpenFlexure translation stage
|
||||||
|
|
||||||
|
As of LabThings-FastAPI 0.0.7, we can't make a thing client dependency
|
||||||
|
based on a protocol, because the protocol is not a Thing, and its
|
||||||
|
methods/properties are not decorated as Affordances. This stub class
|
||||||
|
is a workaround for that limitation, and should not be used directly.
|
||||||
|
"""
|
||||||
@thing_action
|
@thing_action
|
||||||
def move_relative(self, cancel: CancelHook, block_cancellation: bool=False, **kwargs: Mapping[str, int]):
|
def move_relative(self, cancel: CancelHook, block_cancellation: bool=False, **kwargs: Mapping[str, int]):
|
||||||
"""Make a relative move. Keyword arguments should be axis names."""
|
"""Make a relative move. Keyword arguments should be axis names."""
|
||||||
raise NotImplementedError("Subclasses should implement this method")
|
raise NotImplementedError("StageStub should not be used directly")
|
||||||
|
|
||||||
@thing_action
|
@thing_action
|
||||||
def move_absolute(self, cancel: CancelHook, block_cancellation: bool=False, **kwargs: Mapping[str, int]):
|
def move_absolute(self, cancel: CancelHook, block_cancellation: bool=False, **kwargs: Mapping[str, int]):
|
||||||
"""Make an absolute move. Keyword arguments should be axis names."""
|
"""Make an absolute move. Keyword arguments should be axis names."""
|
||||||
raise NotImplementedError("Subclasses should implement this method")
|
raise NotImplementedError("StageStub should not be used directly")
|
||||||
|
|
||||||
@thing_action
|
@thing_action
|
||||||
def set_zero_position(self):
|
def set_zero_position(self):
|
||||||
"""Make the current position zero in all axes
|
"""Make the current position zero in all axes
|
||||||
|
|
@ -60,4 +116,7 @@ class Stage(Thing):
|
||||||
It is intended for use after manually or automatically recentring the
|
It is intended for use after manually or automatically recentring the
|
||||||
stage.
|
stage.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses should implement this method")
|
raise NotImplementedError("StageStub should not be used directly")
|
||||||
|
|
||||||
|
|
||||||
|
StageDependency: TypeAlias = direct_thing_client_dependency(StageStub, "/stage/")
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ from labthings_fastapi.dependencies.invocation import CancelHook, InvocationCanc
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from . import Stage
|
from . import BaseStage
|
||||||
|
|
||||||
|
|
||||||
class DummyStage(Stage):
|
class DummyStage(BaseStage):
|
||||||
"""A dummy stage for testing purposes
|
"""A dummy stage for testing purposes
|
||||||
|
|
||||||
This stage should work similarly to a Sangaboard stage, but without any
|
This stage should work similarly to a Sangaboard stage, but without any
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue