Illumination Things for sangaboard and simulator

This commit is contained in:
jaknapper 2026-02-10 18:42:59 +00:00
parent 8435f6ae98
commit 5682b4d28d
3 changed files with 65 additions and 27 deletions

View file

@ -140,6 +140,7 @@ class SimulatedCamera(BaseCamera):
self._capture_thread: Optional[Thread] = None self._capture_thread: Optional[Thread] = None
self._capture_enabled = False self._capture_enabled = False
self.generate_sprites() self.generate_sprites()
self.mult = 1
repeating: bool = lt.property(default=False) repeating: bool = lt.property(default=False)
@ -339,14 +340,20 @@ class SimulatedCamera(BaseCamera):
pl_img = Image.fromarray(np_img.astype("uint8")) pl_img = Image.fromarray(np_img.astype("uint8"))
return pl_img.resize((self.shape[1], self.shape[0]), Image.Resampling.BILINEAR) return pl_img.resize((self.shape[1], self.shape[0]), Image.Resampling.BILINEAR)
@lt.action
def set_led(self, led_on: bool = True, led_channel=None):
if led_on:
self.mult = 1
else:
self.mult = 0
def generate_frame(self) -> Image.Image: def generate_frame(self) -> Image.Image:
"""Generate a frame with blobs based on the stage coordinates.""" """Generate a frame with blobs based on the stage coordinates."""
pos = self._stage.instantaneous_position pos = self._stage.instantaneous_position
frame = self.generate_image((pos["y"], pos["x"], pos["z"])) frame = self.generate_image((pos["y"], pos["x"], pos["z"]))
# Simulate LED turning off by setting all channels to 0 # Simulate LED turning off by setting all channels to 0
if not self._stage.led_on: return frame * self.mult
frame = np.full((self.shape[0], self.shape[1], 3), 0, dtype=np.uint8)
return frame
def __enter__(self) -> Self: def __enter__(self) -> Self:
"""Start the capture thread when the Thing context manager is opened.""" """Start the capture thread when the Thing context manager is opened."""

View file

@ -3,19 +3,12 @@ from typing import Literal
import labthings_fastapi as lt import labthings_fastapi as lt
from .stage import BaseStage
from .camera import BaseCam
class Illumination(lt.Thing): class Illumination(lt.Thing):
"""Abstract illumination controller.""" """Abstract illumination controller."""
brightness: float = lt.property(
default=0.0,
description="Illumination brightness (0-1)",
)
@lt.action
def set_brightness(self, brightness: float) -> None:
self.brightness = brightness
@lt.action @lt.action
def flash( def flash(
self, self,
@ -23,22 +16,14 @@ class Illumination(lt.Thing):
dt: float = 0.5, dt: float = 0.5,
) -> None: ) -> None:
"""Flash the illumination source.""" """Flash the illumination source."""
for _ in range(number_of_flashes): raise NotImplementedError(
self.set_brightness(1.0) 'Flashing the LED can only be done from the simulator or sangaboard'
time.sleep(dt) )
self.set_brightness(0.0)
time.sleep(dt)
class SangaIllumination(Illumination): class SangaIllumination(Illumination):
"""Illumination driven by a Sangaboard.""" """Illumination driven by a Sangaboard."""
sangaboard = lt.thing_slot(description="Sangaboard providing LED control") _stage: BaseStage = lt.thing_slot()
@lt.action
def set_brightness(self, brightness: float) -> None:
self.sangaboard.set_led_brightness(brightness)
self.brightness = brightness
@lt.action @lt.action
def flash( def flash(
@ -48,7 +33,25 @@ class SangaIllumination(Illumination):
led_channel: Literal["cc"] = "cc", led_channel: Literal["cc"] = "cc",
) -> None: ) -> None:
for _ in range(number_of_flashes): for _ in range(number_of_flashes):
self.sangaboard.set_led(True, led_channel) self._stage.set_led(False, led_channel)
time.sleep(dt) time.sleep(dt)
self.sangaboard.set_led(False, led_channel) self._stage.set_led(True, led_channel)
time.sleep(dt)
class SimulatorIllumination(Illumination):
"""Illumination control in the simulator."""
_cam: BaseCam = lt.thing_slot()
@lt.action
def flash(
self,
number_of_flashes: int = 10,
dt: float = 0.5,
led_channel: Literal["cc"] = "cc",
) -> None:
for _ in range(number_of_flashes):
self._cam.set_led(False, led_channel)
time.sleep(dt)
self._cam.set_led(True, led_channel)
time.sleep(dt) time.sleep(dt)

View file

@ -6,7 +6,7 @@ import threading
from contextlib import contextmanager from contextlib import contextmanager
from copy import copy from copy import copy
from types import TracebackType from types import TracebackType
from typing import Any, Iterator, Optional, Self from typing import Any, Iterator, Literal, Optional, Self
import semver import semver
@ -172,3 +172,31 @@ class SangaboardThing(BaseStage):
with self.sangaboard() as sb: with self.sangaboard() as sb:
sb.zero_position() sb.zero_position()
self.update_position() self.update_position()
@lt.action
def set_led(
self,
led_on: bool = True,
led_channel: Literal["cc"] = "cc",
) -> None:
"""Flash the LED to identify the board.
This is intended to be useful in situations where there are multiple
Sangaboards in use, and it is necessary to identify which one is
being addressed.
"""
led_command = f"led_{led_channel}"
with self.sangaboard() as sb:
return_value = sb.query(f"{led_command}?")
if not return_value.startswith("CC LED:"):
raise IOError("The sangaboard does not support LED control")
# Reading and setting LED brightness suffers from repeated reads and writes
# decreasing the value. Rather than use the value the code warns that the value
# cannot be used.
if led_on:
on_brightness = 0.32
sb.query(f"{led_command} {on_brightness}")
else:
sb.query(f"{led_command} 0")