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.dependencies.thing import direct_thing_client_dependency
|
||||
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.camera_stage_mapping import CameraStageMapper
|
||||
|
||||
StageDep = direct_thing_client_dependency(Stage, "/stage/")
|
||||
CSMDep = direct_thing_client_dependency(CameraStageMapper, "/camera_stage_mapping/")
|
||||
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 .camera import RawCameraDependency as Camera
|
||||
from .camera import CameraDependency as WrappedCamera
|
||||
from .stage import Stage as StageThing
|
||||
from .stage import StageDependency as Stage
|
||||
import numpy as np
|
||||
from pydantic import BaseModel
|
||||
|
||||
Stage = direct_thing_client_dependency(StageThing, "/stage/")
|
||||
|
||||
### Autofocus utilities
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ from labthings_fastapi.types.numpy import NDArray
|
|||
from labthings_fastapi.server import ThingServer
|
||||
|
||||
from . import BaseCamera, JPEGBlob
|
||||
from ..stage import Stage
|
||||
from ..stage import StageProtocol as Stage
|
||||
|
||||
|
||||
class SimulatedCamera(BaseCamera):
|
||||
|
|
|
|||
|
|
@ -21,17 +21,13 @@ from camera_stage_mapping.camera_stage_calibration_1d import (
|
|||
calibrate_backlash_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.types.numpy import NDArray, denumpify, DenumpifyingDict
|
||||
from labthings_fastapi.decorators import thing_action, thing_property
|
||||
from labthings_fastapi.thing import Thing
|
||||
|
||||
Stage = direct_thing_client_dependency(StageThing, "/stage/")
|
||||
from camera_stage_mapping.camera_stage_tracker import Tracker
|
||||
from .camera import CameraDependency as Camera
|
||||
from .stage import StageDependency as Stage
|
||||
|
||||
CoordinateType = Tuple[float, 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.outputs.blob import blob_type
|
||||
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.camera_stage_mapping import CameraStageMapper
|
||||
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/")
|
||||
AutofocusDep = direct_thing_client_dependency(AutofocusThing, "/autofocus/")
|
||||
RecentreStage = direct_thing_client_dependency(RecentringThing, "/auto_recentre_stage/")
|
||||
|
|
|
|||
|
|
@ -1,16 +1,63 @@
|
|||
from __future__ import annotations
|
||||
from typing import Any, Protocol, TypeAlias, runtime_checkable
|
||||
from labthings_fastapi.descriptors.property import PropertyDescriptor
|
||||
from labthings_fastapi.thing import Thing
|
||||
from labthings_fastapi.decorators import thing_action, thing_property
|
||||
from labthings_fastapi.dependencies.invocation import CancelHook
|
||||
from labthings_fastapi.dependencies.thing import direct_thing_client_dependency
|
||||
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):
|
||||
"""A dummy stage for testing purposes
|
||||
@property
|
||||
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
|
||||
hardware attached.
|
||||
def move_relative(self, cancel: CancelHook, block_cancellation: bool=False, **kwargs: Mapping[str, int]):
|
||||
"""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")
|
||||
|
||||
|
|
@ -41,17 +88,26 @@ class Stage(Thing):
|
|||
return {
|
||||
"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
|
||||
def move_relative(self, cancel: CancelHook, block_cancellation: bool=False, **kwargs: Mapping[str, int]):
|
||||
"""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
|
||||
def move_absolute(self, cancel: CancelHook, block_cancellation: bool=False, **kwargs: Mapping[str, int]):
|
||||
"""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
|
||||
def set_zero_position(self):
|
||||
"""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
|
||||
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
|
||||
import time
|
||||
|
||||
from . import Stage
|
||||
from . import BaseStage
|
||||
|
||||
|
||||
class DummyStage(Stage):
|
||||
class DummyStage(BaseStage):
|
||||
"""A dummy stage for testing purposes
|
||||
|
||||
This stage should work similarly to a Sangaboard stage, but without any
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue