update action, property, and setting syntax for labthings-fastapi 0.0.12
This commit is contained in:
parent
bbbfaf8602
commit
5eea78cac7
14 changed files with 139 additions and 192 deletions
|
|
@ -161,7 +161,7 @@ ignore = [
|
|||
[tool.ruff.lint.pydocstyle]
|
||||
# This lets the D401 checker understand that decorated thing properties and thing
|
||||
# settings act like properties so should be documented as such.
|
||||
property-decorators = ["labthings_fastapi.thing_property", "labthings_fastapi.thing_setting"]
|
||||
property-decorators = ["labthings_fastapi.property", "labthings_fastapi.setting"]
|
||||
|
||||
[tool.ruff.lint.pylint]
|
||||
max-args = 7
|
||||
|
|
|
|||
|
|
@ -350,7 +350,7 @@ class AutofocusThing(lt.Thing):
|
|||
field of view to assess focus (autofocus and testing the success of a z-stack)
|
||||
"""
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def fast_autofocus(
|
||||
self,
|
||||
sharpness_monitor: SharpnessMonitorDep,
|
||||
|
|
@ -381,7 +381,7 @@ class AutofocusThing(lt.Thing):
|
|||
# Return all focus data
|
||||
return sharpness_monitor.data_dict()
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def z_move_and_measure_sharpness(
|
||||
self,
|
||||
sharpness_monitor: SharpnessMonitorDep,
|
||||
|
|
@ -407,7 +407,7 @@ class AutofocusThing(lt.Thing):
|
|||
sharpness_monitor.focus_rel(current_dz)
|
||||
return sharpness_monitor.data_dict()
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def looping_autofocus(
|
||||
self,
|
||||
stage: Stage,
|
||||
|
|
@ -455,19 +455,13 @@ class AutofocusThing(lt.Thing):
|
|||
"Looping autofocus couldn't converge on a focus location."
|
||||
)
|
||||
|
||||
stack_images_to_save = lt.ThingSetting(
|
||||
initial_value=1,
|
||||
model=int,
|
||||
)
|
||||
stack_images_to_save: int = lt.setting(default=1)
|
||||
"""The number of images to save in a stack.
|
||||
|
||||
Defaults to 1 unless you need to see either side of focus
|
||||
"""
|
||||
|
||||
stack_min_images_to_test = lt.ThingSetting(
|
||||
initial_value=9,
|
||||
model=int,
|
||||
)
|
||||
stack_min_images_to_test: int = lt.setting(default=9)
|
||||
"""The minimum number of images to capture in a stack.
|
||||
|
||||
This many images are captures and tested for focus, if the focus is not central
|
||||
|
|
@ -477,7 +471,7 @@ class AutofocusThing(lt.Thing):
|
|||
Defaults to 9 which balances reliability and speed.
|
||||
"""
|
||||
|
||||
stack_dz = lt.ThingSetting(initial_value=50, model=int)
|
||||
stack_dz: int = lt.setting(default=50)
|
||||
"""Distance in steps between images in a z-stack.
|
||||
|
||||
Suggested values:
|
||||
|
|
@ -487,7 +481,7 @@ class AutofocusThing(lt.Thing):
|
|||
* 200 for 20x
|
||||
"""
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def create_stack_params(
|
||||
self,
|
||||
images_dir: str,
|
||||
|
|
@ -560,7 +554,7 @@ class AutofocusThing(lt.Thing):
|
|||
save_resolution=save_resolution,
|
||||
)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def run_smart_stack(
|
||||
self,
|
||||
cam: CameraClient,
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ class BaseCamera(lt.Thing):
|
|||
"""Close hardware connection when the Thing context manager is closed."""
|
||||
raise NotImplementedError("CameraThings must define their own __exit__ method")
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def calibration_required(self) -> bool:
|
||||
"""Whether the camera needs calibrating.
|
||||
|
||||
|
|
@ -212,7 +212,7 @@ class BaseCamera(lt.Thing):
|
|||
"""
|
||||
return False
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def start_streaming(
|
||||
self, main_resolution: tuple[int, int], buffer_count: int
|
||||
) -> None:
|
||||
|
|
@ -239,21 +239,21 @@ class BaseCamera(lt.Thing):
|
|||
self.mjpeg_stream._streaming = False
|
||||
self.lores_mjpeg_stream._streaming = False
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def stream_active(self) -> bool:
|
||||
"""Whether the MJPEG stream is active."""
|
||||
raise NotImplementedError(
|
||||
"CameraThings must define their own stream_active method"
|
||||
)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def discard_frames(self) -> None:
|
||||
"""Discard frames so that the next frame captured is fresh."""
|
||||
raise NotImplementedError(
|
||||
"CameraThings must define their own discard_frames method"
|
||||
)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def capture_array(
|
||||
self,
|
||||
stream_name: Literal["main", "lores", "raw", "full"] = "main",
|
||||
|
|
@ -264,10 +264,10 @@ class BaseCamera(lt.Thing):
|
|||
"CameraThings must define their own capture_array method"
|
||||
)
|
||||
|
||||
downsampled_array_factor = lt.ThingProperty(int, 2)
|
||||
downsampled_array_factor: int = lt.property(default=2)
|
||||
"""The downsampling factor when calling capture_downsampled_array."""
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def capture_downsampled_array(self) -> ArrayModel:
|
||||
"""Acquire one image from the camera, downsample, and return as an array.
|
||||
|
||||
|
|
@ -279,7 +279,7 @@ class BaseCamera(lt.Thing):
|
|||
img = self.capture_array()
|
||||
return downsample(self.downsampled_array_factor, img)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def capture_jpeg(
|
||||
self,
|
||||
metadata_getter: lt.deps.GetThingStates,
|
||||
|
|
@ -316,7 +316,7 @@ class BaseCamera(lt.Thing):
|
|||
|
||||
return JPEGBlob.from_temporary_directory(directory, fname)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def grab_jpeg(
|
||||
self,
|
||||
portal: lt.deps.BlockingPortal,
|
||||
|
|
@ -340,7 +340,7 @@ class BaseCamera(lt.Thing):
|
|||
frame = portal.call(stream.grab_frame)
|
||||
return JPEGBlob.from_bytes(frame)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def grab_as_array(
|
||||
self,
|
||||
portal: lt.deps.BlockingPortal,
|
||||
|
|
@ -367,7 +367,7 @@ class BaseCamera(lt.Thing):
|
|||
tries += 1
|
||||
raise OSError("Could not open frames from MJPEG stream.")
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def grab_jpeg_size(
|
||||
self,
|
||||
portal: lt.deps.BlockingPortal,
|
||||
|
|
@ -389,7 +389,7 @@ class BaseCamera(lt.Thing):
|
|||
"CameraThings must define their own capture_image method"
|
||||
)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def capture_and_save(
|
||||
self,
|
||||
jpeg_path: str,
|
||||
|
|
@ -420,7 +420,7 @@ class BaseCamera(lt.Thing):
|
|||
save_resolution,
|
||||
)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def capture_to_memory(
|
||||
self,
|
||||
logger: lt.deps.InvocationLogger,
|
||||
|
|
@ -444,7 +444,7 @@ class BaseCamera(lt.Thing):
|
|||
image, metadata = self._robust_image_capture(metadata_getter, logger)
|
||||
return self._memory_buffer.add_image(image, metadata, buffer_max=buffer_max)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def save_from_memory(
|
||||
self,
|
||||
jpeg_path: str,
|
||||
|
|
@ -473,7 +473,7 @@ class BaseCamera(lt.Thing):
|
|||
save_resolution=save_resolution,
|
||||
)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def clear_buffers(self) -> None:
|
||||
"""Clear all images in memory."""
|
||||
self._memory_buffer.clear()
|
||||
|
|
@ -600,10 +600,10 @@ class BaseCamera(lt.Thing):
|
|||
except Exception as e:
|
||||
raise IOError(f"An error occurred while saving {jpeg_path}") from e
|
||||
|
||||
settling_time = lt.ThingSetting(float, 0.2)
|
||||
settling_time: float = lt.setting(default=0.2)
|
||||
"""The settling time when calling the ``settle()`` method."""
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def settle(self) -> None:
|
||||
"""Sleep for the settling time, ready to provide a fresh frame.
|
||||
|
||||
|
|
@ -616,24 +616,24 @@ class BaseCamera(lt.Thing):
|
|||
time.sleep(self.settling_time)
|
||||
self.discard_frames()
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def primary_calibration_actions(self) -> list[ActionButton]:
|
||||
"""The calibration actions for both calibration wizard and settings panel."""
|
||||
return []
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def secondary_calibration_actions(self) -> list[ActionButton]:
|
||||
"""The calibration actions that appear only in settings panel."""
|
||||
return []
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def manual_camera_settings(self) -> list[PropertyControl]:
|
||||
"""The camera settings to expose as property controls in the settings panel."""
|
||||
return []
|
||||
|
||||
# Note that the default detector name is set at init. This is over written if
|
||||
# setting is loaded from disk.
|
||||
@lt.thing_setting
|
||||
@lt.setting
|
||||
def detector_name(self) -> str:
|
||||
"""The name of the active background selector."""
|
||||
return self._detector_name
|
||||
|
|
@ -650,12 +650,12 @@ class BaseCamera(lt.Thing):
|
|||
"""The active background detector instance."""
|
||||
return self.background_detectors[self.detector_name]
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def background_detector_status(self) -> BackgroundDetectorStatus:
|
||||
"""The status of the active detector for the UI."""
|
||||
return self.active_detector.status
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def update_detector_settings(self, data: dict[str, Any]) -> None:
|
||||
"""Update the settings of the current detector.
|
||||
|
||||
|
|
@ -671,7 +671,7 @@ class BaseCamera(lt.Thing):
|
|||
# Manually save settings as the setter is not called.
|
||||
self.save_settings()
|
||||
|
||||
@lt.thing_setting
|
||||
@lt.setting
|
||||
def background_detector_data(self) -> dict:
|
||||
"""The data for each background detector, used to save to disk."""
|
||||
data = {}
|
||||
|
|
@ -704,13 +704,13 @@ class BaseCamera(lt.Thing):
|
|||
f"No background detector named {name}, settings will be discarded."
|
||||
)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def image_is_sample(self, portal: lt.deps.BlockingPortal) -> tuple[bool, str]:
|
||||
"""Label the current image as either background or sample."""
|
||||
current_image = self.grab_as_array(portal, stream_name="lores")
|
||||
return self.active_detector.image_is_sample(current_image)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def set_background(self, portal: lt.deps.BlockingPortal) -> None:
|
||||
"""Grab an image, and use its statistics to set the background.
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class OpenCVCamera(BaseCamera):
|
|||
self._capture_thread.join()
|
||||
self.cap.release()
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def stream_active(self) -> bool:
|
||||
"""Whether the MJPEG stream is active."""
|
||||
if self._capture_enabled and self._capture_thread:
|
||||
|
|
@ -81,12 +81,12 @@ class OpenCVCamera(BaseCamera):
|
|||
].tobytes()
|
||||
self.lores_mjpeg_stream.add_frame(jpeg_lores, portal)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def discard_frames(self) -> None:
|
||||
"""Discard frames so that the next frame captured is fresh."""
|
||||
self.capture_array()
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def capture_array(
|
||||
self,
|
||||
stream_name: Literal["main", "full"] = "full",
|
||||
|
|
|
|||
|
|
@ -169,30 +169,19 @@ class StreamingPiCamera2(BaseCamera):
|
|||
# trigger a NotConnectedToServerError
|
||||
self._colour_gains = tf_utils.get_colour_gains_from_lst(self.tuning)
|
||||
|
||||
stream_resolution = lt.ThingProperty(
|
||||
tuple[int, int],
|
||||
initial_value=(820, 616),
|
||||
)
|
||||
stream_resolution: tuple[int, int] = lt.property(default=(820, 616))
|
||||
"""Resolution to use for the MJPEG stream."""
|
||||
|
||||
mjpeg_bitrate = lt.ThingProperty(
|
||||
Optional[int],
|
||||
initial_value=100000000,
|
||||
)
|
||||
mjpeg_bitrate: Optional[int] = lt.property(default=100000000)
|
||||
"""Bitrate for MJPEG stream (None for default)."""
|
||||
|
||||
stream_active = lt.ThingProperty(
|
||||
bool,
|
||||
initial_value=False,
|
||||
observable=True,
|
||||
readonly=True,
|
||||
)
|
||||
stream_active: bool = lt.property(default=False, observable=True, readonly=True)
|
||||
"""Whether the MJPEG stream is active."""
|
||||
|
||||
def save_settings(self) -> None:
|
||||
"""Override save_settings to ensure that camera properties don't recurse.
|
||||
|
||||
This method is run by any Thing when a ThingSetting is saved. However, the
|
||||
This method is run by any Thing when a setting is saved. However, the
|
||||
method reads the thing_setting. As reading the thing setting talks to the
|
||||
camera and calls save_settings if the value is not as expected, this could
|
||||
cause recursion. Also this means that saving one setting causes all others
|
||||
|
|
@ -204,7 +193,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
finally:
|
||||
self._setting_save_in_progress = False
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def calibration_required(self) -> bool:
|
||||
"""Whether the camera needs calibrating."""
|
||||
# Check if the lens shading table is calibrated.
|
||||
|
|
@ -214,7 +203,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
|
||||
_analogue_gain: float = 1.0
|
||||
|
||||
@lt.thing_setting
|
||||
@lt.setting
|
||||
def analogue_gain(self) -> float:
|
||||
"""The Analogue gain applied by the camera sensor."""
|
||||
if not self._setting_save_in_progress and self.streaming:
|
||||
|
|
@ -234,7 +223,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
|
||||
_colour_gains: tuple[float, float] = (1.0, 1.0)
|
||||
|
||||
@lt.thing_setting
|
||||
@lt.setting
|
||||
def colour_gains(self) -> tuple[float, float]:
|
||||
"""The red and blue colour gains, must be between 0.0 and 32.0."""
|
||||
if not self._setting_save_in_progress and self.streaming:
|
||||
|
|
@ -254,7 +243,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
|
||||
_exposure_time: int = 500
|
||||
|
||||
@lt.thing_setting
|
||||
@lt.setting
|
||||
def exposure_time(self) -> int:
|
||||
"""The camera exposure time in microseconds.
|
||||
|
||||
|
|
@ -296,7 +285,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
|
||||
_sensor_modes = None
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def sensor_modes(self) -> list[SensorMode]:
|
||||
"""All the available modes the current sensor supports."""
|
||||
if not self._sensor_modes:
|
||||
|
|
@ -306,7 +295,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
|
||||
_sensor_mode: Optional[dict] = None
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def sensor_mode(self) -> Optional[SensorModeSelector]:
|
||||
"""The intended sensor mode of the camera."""
|
||||
if self._sensor_mode is None:
|
||||
|
|
@ -329,13 +318,13 @@ class StreamingPiCamera2(BaseCamera):
|
|||
with self._streaming_picamera(pause_stream=True):
|
||||
pass
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def sensor_resolution(self) -> Optional[tuple[int, int]]:
|
||||
"""The native resolution of the camera's sensor."""
|
||||
with self._streaming_picamera() as cam:
|
||||
return cam.sensor_resolution
|
||||
|
||||
tuning = lt.ThingSetting(Optional[dict], None, readonly=True)
|
||||
tuning: Optional[dict] = lt.setting(default=None, readonly=True)
|
||||
"""The Raspberry PiCamera Tuning File JSON."""
|
||||
|
||||
def _initialise_picamera(self, check_sensor_model: bool = False) -> None:
|
||||
|
|
@ -437,7 +426,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
cam.close()
|
||||
del self._picamera
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def start_streaming(
|
||||
self, main_resolution: tuple[int, int] = (820, 616), buffer_count: int = 6
|
||||
) -> None:
|
||||
|
|
@ -509,7 +498,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
"Started MJPEG stream at %s on port %s", self.stream_resolution, 1
|
||||
)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def stop_streaming(self, stop_web_stream: bool = True) -> None:
|
||||
"""Stop the MJPEG stream."""
|
||||
with self._streaming_picamera() as picam:
|
||||
|
|
@ -529,7 +518,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
# Adding a sleep to prevent camera getting confused by rapid commands
|
||||
time.sleep(self._sensor_info.short_pause)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def discard_frames(self) -> None:
|
||||
"""Discard frames so that the next frame captured is fresh."""
|
||||
with self._streaming_picamera() as cam:
|
||||
|
|
@ -587,7 +576,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
else:
|
||||
raise ValueError(f'Unknown stream name "{stream_name}"')
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def capture_array(
|
||||
self,
|
||||
stream_name: Literal["main", "lores", "raw", "full"] = "main",
|
||||
|
|
@ -620,7 +609,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
# as array
|
||||
return np.array(self.capture_image(stream_name, wait))
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def camera_configuration(self) -> Mapping:
|
||||
"""The "configuration" dictionary of the picamera2 object.
|
||||
|
||||
|
|
@ -635,13 +624,13 @@ class StreamingPiCamera2(BaseCamera):
|
|||
with self._streaming_picamera() as cam:
|
||||
return cam.camera_configuration()
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def capture_metadata(self) -> dict:
|
||||
"""Return the metadata from the camera."""
|
||||
with self._streaming_picamera() as cam:
|
||||
return cam.capture_metadata()
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def auto_expose_from_minimum(
|
||||
self,
|
||||
target_white_level: Optional[int] = None,
|
||||
|
|
@ -671,7 +660,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
percentile=percentile,
|
||||
)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def calibrate_lens_shading(self) -> None:
|
||||
"""Take an image and use it for flat-field correction.
|
||||
|
||||
|
|
@ -699,7 +688,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
|
||||
self.colour_gains = tf_utils.get_colour_gains_from_lst(self.tuning)
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def colour_correction_matrix(
|
||||
self,
|
||||
) -> tuple[float, float, float, float, float, float, float, float, float]:
|
||||
|
|
@ -724,7 +713,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
with self._streaming_picamera(pause_stream=True):
|
||||
self._initialise_picamera()
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def reset_ccm(self) -> None:
|
||||
"""Overwrite the colour correction matrix in camera tuning with default values."""
|
||||
self.tuning = tf_utils.copy_algo_from_other_tuning(
|
||||
|
|
@ -733,7 +722,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
copy_from=self.default_tuning,
|
||||
)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def set_static_green_equalisation(self, offset: int = 65535) -> None:
|
||||
"""Set the green equalisation to a static value.
|
||||
|
||||
|
|
@ -748,7 +737,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
self.tuning = tf_utils.set_static_geq(self.tuning, offset)
|
||||
self._initialise_picamera()
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def set_ce_enable_to_off(self) -> None:
|
||||
"""Set the contrast enhancement to disabled.
|
||||
|
||||
|
|
@ -759,7 +748,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
self.tuning = tf_utils.set_ce_to_disabled(self.tuning)
|
||||
self._initialise_picamera()
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def full_auto_calibrate(self, portal: lt.deps.BlockingPortal) -> None:
|
||||
"""Perform a full auto-calibration.
|
||||
|
||||
|
|
@ -787,7 +776,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
pass
|
||||
raise RuntimeError("Couldn't set background")
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def primary_calibration_actions(self) -> list[ActionButton]:
|
||||
"""The calibration actions for both calibration wizard and settings panel."""
|
||||
return [
|
||||
|
|
@ -805,7 +794,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
),
|
||||
]
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def secondary_calibration_actions(self) -> list[ActionButton]:
|
||||
"""The calibration actions that appear only in settings panel."""
|
||||
return [
|
||||
|
|
@ -847,7 +836,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
),
|
||||
]
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def manual_camera_settings(self) -> list[PropertyControl]:
|
||||
"""The camera settings to expose as property controls in the settings panel."""
|
||||
return [
|
||||
|
|
@ -874,7 +863,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
),
|
||||
]
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def lens_shading_tables(self) -> Optional[tf_utils.LensShading]:
|
||||
"""The current lens shading (i.e. flat-field correction).
|
||||
|
||||
|
|
@ -901,7 +890,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
)
|
||||
self._initialise_picamera()
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def flat_lens_shading(self) -> None:
|
||||
"""Disable flat-field correction.
|
||||
|
||||
|
|
@ -916,7 +905,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
self.tuning = tf_utils.flatten_lst(self.tuning)
|
||||
self._initialise_picamera()
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def flat_lens_shading_chrominance(self) -> None:
|
||||
"""Disable flat-field correction for colour only.
|
||||
|
||||
|
|
@ -928,7 +917,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
self.tuning = tf_utils.flatten_lst(self.tuning, keep_luminance=True)
|
||||
self._initialise_picamera()
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def reset_lens_shading(self) -> None:
|
||||
"""Revert to default lens shading settings.
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ class SimulatedCamera(BaseCamera):
|
|||
self.generate_blobs()
|
||||
self.generate_canvas()
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def calibration_required(self) -> bool:
|
||||
"""Whether the camera needs calibrating."""
|
||||
return not self.background_detector_status.ready
|
||||
|
|
@ -274,7 +274,7 @@ class SimulatedCamera(BaseCamera):
|
|||
self._capture_enabled = False
|
||||
self._capture_thread.join()
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def start_streaming(
|
||||
self, main_resolution: tuple[int, int] = (820, 616), buffer_count: int = 1
|
||||
) -> None:
|
||||
|
|
@ -300,14 +300,14 @@ class SimulatedCamera(BaseCamera):
|
|||
self._capture_thread = Thread(target=self._capture_frames)
|
||||
self._capture_thread.start()
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def stream_active(self) -> bool:
|
||||
"""Whether the MJPEG stream is active."""
|
||||
if self._capture_enabled and self._capture_thread:
|
||||
return self._capture_thread.is_alive()
|
||||
return False
|
||||
|
||||
noise_level = lt.ThingProperty(float, 2.0)
|
||||
noise_level: float = lt.property(default=2.0)
|
||||
|
||||
def _capture_frames(self) -> None:
|
||||
portal = lt.get_blocking_portal(self)
|
||||
|
|
@ -326,14 +326,14 @@ class SimulatedCamera(BaseCamera):
|
|||
except Exception as e:
|
||||
LOGGER.exception(f"Failed to capture frame: {e}, retrying...")
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def discard_frames(self) -> None:
|
||||
"""Discard frames so that the next frame captured is fresh.
|
||||
|
||||
There is nothing to do as this is a simulation!
|
||||
"""
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def capture_array(
|
||||
self,
|
||||
stream_name: Literal["main", "full"] = "full",
|
||||
|
|
@ -370,7 +370,7 @@ class SimulatedCamera(BaseCamera):
|
|||
LOGGER.warning(f"Simulation camera camera doesn't respect {stream_name=}")
|
||||
return self.generate_frame()
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def full_auto_calibrate(self, portal: lt.deps.BlockingPortal) -> None:
|
||||
"""Perform a full auto-calibration.
|
||||
|
||||
|
|
@ -386,21 +386,21 @@ class SimulatedCamera(BaseCamera):
|
|||
time.sleep(0.2)
|
||||
self.load_sample()
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def remove_sample(self) -> None:
|
||||
"""Show the simulated background with no sample."""
|
||||
if not self._show_sample:
|
||||
raise RuntimeError("Sample is already removed.")
|
||||
self._show_sample = False
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def load_sample(self) -> None:
|
||||
"""Show the simulated sample."""
|
||||
if self._show_sample:
|
||||
raise RuntimeError("Sample is already in place.")
|
||||
self._show_sample = True
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def primary_calibration_actions(self) -> list[ActionButton]:
|
||||
"""The calibration actions for both calibration wizard and settings panel."""
|
||||
return [
|
||||
|
|
@ -409,7 +409,7 @@ class SimulatedCamera(BaseCamera):
|
|||
),
|
||||
]
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def secondary_calibration_actions(self) -> list[ActionButton]:
|
||||
"""The calibration actions that appear only in settings panel."""
|
||||
return [
|
||||
|
|
@ -417,7 +417,7 @@ class SimulatedCamera(BaseCamera):
|
|||
action_button_for(self.remove_sample, submit_label="Remove Sample"),
|
||||
]
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def manual_camera_settings(self) -> list[PropertyControl]:
|
||||
"""The camera settings to expose as property controls in the settings panel."""
|
||||
return [property_control_for(self, "noise_level", label="Noise Level")]
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ class CameraStageMapper(lt.Thing):
|
|||
override the ``get_xyz_position()`` and ``move_to_xyz_position()`` methods.
|
||||
"""
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def calibrate_1d(
|
||||
self,
|
||||
camera: CameraClient,
|
||||
|
|
@ -154,7 +154,7 @@ class CameraStageMapper(lt.Thing):
|
|||
result["image_resolution"] = camera.capture_downsampled_array().shape[:2]
|
||||
return result
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def calibrate_xy(
|
||||
self, camera: CameraClient, stage: Stage, logger: lt.deps.InvocationLogger
|
||||
) -> DenumpifyingDict:
|
||||
|
|
@ -200,7 +200,7 @@ class CameraStageMapper(lt.Thing):
|
|||
|
||||
return data
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def image_to_stage_displacement_matrix(
|
||||
self,
|
||||
) -> Optional[List[List[float]]]: # 2x2 integer array
|
||||
|
|
@ -230,19 +230,17 @@ class CameraStageMapper(lt.Thing):
|
|||
]
|
||||
return np.array(displacement_matrix).tolist()
|
||||
|
||||
last_calibration = lt.ThingSetting(
|
||||
initial_value=None, model=Optional[dict], readonly=True
|
||||
)
|
||||
last_calibration: Optional[dict] = lt.setting(default=None, readonly=True)
|
||||
"""The most recent CSM calibration."""
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def image_resolution(self) -> Optional[Tuple[float, float]]:
|
||||
"""The image size used to calibrate the image_to_stage_displacement_matrix."""
|
||||
if self.last_calibration is None:
|
||||
return None
|
||||
return self.last_calibration["image_resolution"]
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def calibration_required(self) -> bool:
|
||||
"""Whether the camera stage mapper needs calibrating."""
|
||||
return self.image_to_stage_displacement_matrix is None
|
||||
|
|
@ -254,7 +252,7 @@ class CameraStageMapper(lt.Thing):
|
|||
# added by CSMUncalibratedError
|
||||
raise CSMUncalibratedError() # noqa: RSE102
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def move_in_image_coordinates(
|
||||
self,
|
||||
stage: Stage,
|
||||
|
|
@ -275,7 +273,7 @@ class CameraStageMapper(lt.Thing):
|
|||
self.assert_calibrated()
|
||||
stage.move_relative(**self.convert_image_to_stage_coordinates(x=x, y=y))
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def convert_image_to_stage_coordinates(
|
||||
self, x: float, y: float, **_kwargs: float
|
||||
) -> Mapping[str, int]:
|
||||
|
|
@ -283,7 +281,7 @@ class CameraStageMapper(lt.Thing):
|
|||
self.assert_calibrated()
|
||||
return csm_img_to_stage(self.image_to_stage_displacement_matrix, x=x, y=y)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def convert_stage_to_image_coordinates(
|
||||
self, x: int, y: int, **_kwargs: int
|
||||
) -> Mapping[str, float]:
|
||||
|
|
@ -291,7 +289,7 @@ class CameraStageMapper(lt.Thing):
|
|||
self.assert_calibrated()
|
||||
return csm_stage_to_img(self.image_to_stage_displacement_matrix, x=x, y=y)
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def thing_state(self) -> Mapping[str, Any]:
|
||||
"""Summary metadata describing the current state of the Thing."""
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ class SmartScanThing(lt.Thing):
|
|||
self._latest_scan_name: Optional[str] = None
|
||||
|
||||
# Scan logger is the invocation logger labthings-fastapi creates
|
||||
# when the `sample_scan` lt.thing_action is called. It is saved as
|
||||
# when the `sample_scan` lt.action is called. It is saved as
|
||||
# private class variable along with many others here.
|
||||
# Access to these variables requires a scan to be running,
|
||||
# any method that calls these should be decorated with
|
||||
|
|
@ -133,7 +133,7 @@ class SmartScanThing(lt.Thing):
|
|||
self._scan_data: Optional[scan_directories.ScanData] = None
|
||||
self._preview_stitcher: Optional[stitching.PreviewStitcher] = None
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def sample_scan(
|
||||
self,
|
||||
cancel: lt.deps.CancelHook,
|
||||
|
|
@ -231,7 +231,7 @@ class SmartScanThing(lt.Thing):
|
|||
"of motion."
|
||||
)
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def latest_scan_name(self) -> Optional[str]:
|
||||
"""The name of the last scan to be started."""
|
||||
return self._latest_scan_name
|
||||
|
|
@ -546,48 +546,27 @@ class SmartScanThing(lt.Thing):
|
|||
raise HTTPException(404, "File not found")
|
||||
return FileResponse(preview_path)
|
||||
|
||||
save_resolution = lt.ThingSetting(
|
||||
initial_value=(1640, 1232),
|
||||
model=tuple[int, int],
|
||||
)
|
||||
save_resolution: tuple[int, int] = lt.setting(default=(1640, 1232))
|
||||
"""A tuple of the image resolution to capture."""
|
||||
|
||||
max_range = lt.ThingSetting(
|
||||
initial_value=45000,
|
||||
model=int,
|
||||
)
|
||||
max_range: int = lt.setting(default=45000)
|
||||
"""The maximum distance in steps from the centre of the scan."""
|
||||
|
||||
stitch_tiff = lt.ThingSetting(
|
||||
initial_value=False,
|
||||
model=bool,
|
||||
)
|
||||
stitch_tiff: bool = lt.setting(default=False)
|
||||
"""Whether or not to also produce a pyramidal tiff at the end of a scan."""
|
||||
|
||||
skip_background = lt.ThingSetting(
|
||||
initial_value=True,
|
||||
model=bool,
|
||||
)
|
||||
skip_background: bool = lt.setting(default=True)
|
||||
"""Whether to detect and skip empty fields of view.
|
||||
|
||||
This uses the settings from the ``BackgroundDetectThing``."""
|
||||
|
||||
autofocus_dz = lt.ThingSetting(
|
||||
initial_value=1000,
|
||||
model=int,
|
||||
)
|
||||
autofocus_dz: int = lt.setting(default=1000)
|
||||
"""The z distance to perform an autofocus in steps."""
|
||||
|
||||
overlap = lt.ThingSetting(
|
||||
initial_value=0.45,
|
||||
model=float,
|
||||
)
|
||||
overlap: float = lt.setting(default=0.45)
|
||||
"""The fraction (0-1) that adjacent images should overlap in x or y."""
|
||||
|
||||
stitch_automatically = lt.ThingSetting(
|
||||
initial_value=True,
|
||||
model=bool,
|
||||
)
|
||||
stitch_automatically: bool = lt.setting(default=True)
|
||||
"""Whether to run a final stitch at the end of a successful scan."""
|
||||
|
||||
def _get_all_scan_info(self) -> list[scan_directories.ScanInfo]:
|
||||
|
|
@ -600,7 +579,7 @@ class SmartScanThing(lt.Thing):
|
|||
ongoing_name = None if self._ongoing_scan is None else self._ongoing_scan.name
|
||||
return self._scan_dir_manager.all_scans_info(ongoing=ongoing_name)
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def scans(self) -> ScanListInfo:
|
||||
"""All the available scans.
|
||||
|
||||
|
|
@ -675,7 +654,7 @@ class SmartScanThing(lt.Thing):
|
|||
for scan_name in self._scan_dir_manager.all_scans:
|
||||
self._delete_scan(scan_name, logger)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def purge_empty_scans(self, logger: lt.deps.InvocationLogger) -> None:
|
||||
"""Delete all scan folders containing no images at the top level."""
|
||||
# JSON is ignored as it's created before any images are captured
|
||||
|
|
@ -712,7 +691,7 @@ class SmartScanThing(lt.Thing):
|
|||
scan_name=self.latest_scan_name, filename="preview.jpg", check_exists=True
|
||||
)
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def latest_preview_stitch_time(self) -> Optional[float]:
|
||||
"""The modification time of the latest preview image, to allow live updating.
|
||||
|
||||
|
|
@ -746,7 +725,7 @@ class SmartScanThing(lt.Thing):
|
|||
raise HTTPException(404, "File not found")
|
||||
return FileResponse(preview_path)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def stitch_scan(
|
||||
self,
|
||||
logger: lt.deps.InvocationLogger,
|
||||
|
|
@ -757,7 +736,7 @@ class SmartScanThing(lt.Thing):
|
|||
) -> None:
|
||||
"""Generate a stitched image based on stage position metadata.
|
||||
|
||||
Note that as this is a lt.thing_action it needs the logger passed as
|
||||
Note that as this is a lt.action it needs the logger passed as
|
||||
a variable if called from another thing action
|
||||
"""
|
||||
scan_data_dict = self._scan_dir_manager.get_scan_data_dict(scan_name)
|
||||
|
|
@ -780,7 +759,7 @@ class SmartScanThing(lt.Thing):
|
|||
except SubprocessError as e:
|
||||
self._scan_logger.error(f"Stitching failed: {e}", exc_info=e)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def download_zip(
|
||||
self,
|
||||
scan_name: str,
|
||||
|
|
@ -792,7 +771,7 @@ class SmartScanThing(lt.Thing):
|
|||
zip_fname = self._scan_dir_manager.zip_scan(scan_name, final_version=True)
|
||||
return ZipBlob.from_file(zip_fname)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def stitch_all_scans(
|
||||
self, logger: lt.deps.InvocationLogger, cancel: lt.deps.CancelHook
|
||||
) -> None:
|
||||
|
|
|
|||
|
|
@ -68,28 +68,21 @@ class BaseStage(lt.Thing):
|
|||
"_hardware_move_relative and/or _hardware_move_absolute instead."
|
||||
)
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def axis_names(self) -> Sequence[str]:
|
||||
"""The names of the stage's axes, in order."""
|
||||
return self._axis_names
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def position(self) -> Mapping[str, int]:
|
||||
"""Current position of the stage."""
|
||||
return self._apply_axis_direction(self._hardware_position)
|
||||
|
||||
moving = lt.ThingProperty(
|
||||
bool,
|
||||
False,
|
||||
readonly=True,
|
||||
observable=True,
|
||||
)
|
||||
moving: bool = lt.property(bool, default=False, readonly=True, observable=True)
|
||||
"""Whether the stage is in motion."""
|
||||
|
||||
axis_inverted = lt.ThingSetting(
|
||||
initial_value={"x": False, "y": False, "z": False},
|
||||
model=Mapping[str, bool],
|
||||
readonly=True,
|
||||
axis_inverted: Mapping[str, bool] = lt.setting(
|
||||
default={"x": False, "y": False, "z": False}, readonly=True
|
||||
)
|
||||
"""Used to convert coordinates between the program frame and the hardware frame."""
|
||||
|
||||
|
|
@ -124,7 +117,7 @@ class BaseStage(lt.Thing):
|
|||
"""Summary metadata describing the current state of the stage."""
|
||||
return {"position": self.position}
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def invert_axis_direction(self, axis: Literal["x", "y", "z"]) -> None:
|
||||
"""Invert the direction setting of the given axis.
|
||||
|
||||
|
|
@ -138,7 +131,7 @@ class BaseStage(lt.Thing):
|
|||
raise KeyError(f"The axis {axis} is not defined.") from e
|
||||
self.axis_inverted = direction
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def move_relative(
|
||||
self,
|
||||
cancel: lt.deps.CancelHook,
|
||||
|
|
@ -166,7 +159,7 @@ class BaseStage(lt.Thing):
|
|||
"StageThings must define their own _hardware_move_relative method"
|
||||
)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def move_absolute(
|
||||
self,
|
||||
cancel: lt.deps.CancelHook,
|
||||
|
|
@ -194,7 +187,7 @@ class BaseStage(lt.Thing):
|
|||
"StageThings must define their own move_absolute method"
|
||||
)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def set_zero_position(self) -> None:
|
||||
"""Make the current position zero in all axes.
|
||||
|
||||
|
|
@ -206,7 +199,7 @@ class BaseStage(lt.Thing):
|
|||
"StageThings must define their own set_zero_position method"
|
||||
)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def get_xyz_position(self) -> tuple[int, int, int]:
|
||||
"""Return a tuple containing (x, y, z) position.
|
||||
|
||||
|
|
@ -217,7 +210,7 @@ class BaseStage(lt.Thing):
|
|||
position_dict = self.position
|
||||
return (position_dict["x"], position_dict["y"], position_dict["z"])
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def move_to_xyz_position(
|
||||
self, cancel: lt.deps.CancelHook, xyz_pos: tuple[int, int, int]
|
||||
) -> None:
|
||||
|
|
|
|||
|
|
@ -43,10 +43,8 @@ class DummyStage(BaseStage):
|
|||
) -> None:
|
||||
"""Nothing to do when the Thing context manager is closed."""
|
||||
|
||||
axis_inverted = lt.ThingSetting(
|
||||
initial_value={"x": True, "y": False, "z": False},
|
||||
model=Mapping[str, bool],
|
||||
readonly=True,
|
||||
axis_inverted: Mapping[str, bool] = lt.setting(
|
||||
default={"x": True, "y": False, "z": False}, readonly=True
|
||||
)
|
||||
"""Used to convert coordinates between the program frame and the hardware frame."""
|
||||
|
||||
|
|
@ -104,7 +102,7 @@ class DummyStage(BaseStage):
|
|||
cancel, block_cancellation=block_cancellation, **displacement
|
||||
)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def set_zero_position(self) -> None:
|
||||
"""Make the current position zero in all axes.
|
||||
|
||||
|
|
|
|||
|
|
@ -78,10 +78,8 @@ class SangaboardThing(BaseStage):
|
|||
with self._sangaboard_lock:
|
||||
yield self._sangaboard
|
||||
|
||||
axis_inverted = lt.ThingSetting(
|
||||
initial_value={"x": True, "y": False, "z": True},
|
||||
model=Mapping[str, bool],
|
||||
readonly=True,
|
||||
axis_inverted: Mapping[str, bool] = lt.setting(
|
||||
default={"x": True, "y": False, "z": True}, readonly=True
|
||||
)
|
||||
"""Used to convert coordinates between the program frame and the hardware frame."""
|
||||
|
||||
|
|
@ -164,7 +162,7 @@ class SangaboardThing(BaseStage):
|
|||
cancel, block_cancellation=block_cancellation, **displacement
|
||||
)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def set_zero_position(self) -> None:
|
||||
"""Make the current position zero in all axes.
|
||||
|
||||
|
|
@ -176,7 +174,7 @@ class SangaboardThing(BaseStage):
|
|||
sb.zero_position()
|
||||
self.update_position()
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def flash_led(
|
||||
self,
|
||||
number_of_flashes: int = 10,
|
||||
|
|
|
|||
|
|
@ -136,9 +136,7 @@ class ParasiticMotionError(Exception):
|
|||
class RangeofMotionThing(lt.Thing):
|
||||
"""A class used to measure the range of motion of the stage in X and Y."""
|
||||
|
||||
calibrated_range = lt.ThingSetting(
|
||||
initial_value=None, model=Optional[list[int, int]], readonly=True
|
||||
)
|
||||
calibrated_range: int = lt.setting(default=None, readonly=True)
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialise and create the lock."""
|
||||
|
|
@ -147,7 +145,7 @@ class RangeofMotionThing(lt.Thing):
|
|||
self._stream_resolution: Optional[tuple[int, int]] = None
|
||||
self._rom_data = RomDataTracker()
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def perform_rom_test(
|
||||
self,
|
||||
autofocus: AutofocusDep,
|
||||
|
|
@ -214,7 +212,7 @@ class RangeofMotionThing(lt.Thing):
|
|||
"Step Range": step_range,
|
||||
}
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def perform_recentre(
|
||||
self,
|
||||
autofocus: AutofocusDep,
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class OpenFlexureSystem(lt.Thing):
|
|||
|
||||
_microscope_id: Optional[str] = None
|
||||
|
||||
@lt.thing_setting
|
||||
@lt.setting
|
||||
def microscope_id(self) -> UUID:
|
||||
"""A unique identifier for this microscope."""
|
||||
if self._microscope_id is None:
|
||||
|
|
@ -54,14 +54,14 @@ class OpenFlexureSystem(lt.Thing):
|
|||
def microscope_id(self, uuid: UUID) -> None:
|
||||
self._microscope_id = uuid
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def hostname(self) -> str:
|
||||
"""The hostname of the microscope, as reported by its operating system."""
|
||||
return socket.gethostname()
|
||||
|
||||
_version_data: Optional[VersionData] = None
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def version_data(self) -> VersionData:
|
||||
"""The version string and version source for the server.
|
||||
|
||||
|
|
@ -76,12 +76,12 @@ class OpenFlexureSystem(lt.Thing):
|
|||
self._version_data = robust_version_strings()
|
||||
return self._version_data
|
||||
|
||||
@lt.thing_property
|
||||
@lt.property
|
||||
def is_raspberrypi(self) -> bool:
|
||||
"""Return True if running on a Raspberry Pi."""
|
||||
return os.path.exists("/usr/bin/raspi-config")
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def shutdown(self) -> CommandOutput:
|
||||
"""Attempt to shutdown the device."""
|
||||
if not self.is_raspberrypi:
|
||||
|
|
@ -103,7 +103,7 @@ class OpenFlexureSystem(lt.Thing):
|
|||
out, err = p.communicate()
|
||||
return CommandOutput(output=out, error=err)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def reboot(self) -> CommandOutput:
|
||||
"""Attempt to reboot the device."""
|
||||
if not self.is_raspberrypi:
|
||||
|
|
@ -120,7 +120,7 @@ class OpenFlexureSystem(lt.Thing):
|
|||
out, err = p.communicate()
|
||||
return CommandOutput(output=out, error=err)
|
||||
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def get_things_state(self, metadata_getter: lt.deps.GetThingStates) -> Mapping:
|
||||
"""Metadata summarising the current state of all Things in the server."""
|
||||
return metadata_getter()
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ def test_override_base_movement():
|
|||
"""
|
||||
|
||||
class BadStage1(BaseStage):
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def move_relative(
|
||||
self,
|
||||
cancel: lt.deps.CancelHook,
|
||||
|
|
@ -76,7 +76,7 @@ def test_override_base_movement():
|
|||
BadStage1()
|
||||
|
||||
class BadStage2(BaseStage):
|
||||
@lt.thing_action
|
||||
@lt.action
|
||||
def move_absolute(
|
||||
self,
|
||||
cancel: lt.deps.CancelHook,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue