Camera calibration buttons specified by server. Simulation has option to remove sample
This commit is contained in:
parent
1e89ef0ff7
commit
0794d4777f
5 changed files with 175 additions and 79 deletions
|
|
@ -20,6 +20,7 @@ import piexif
|
|||
import labthings_fastapi as lt
|
||||
from labthings_fastapi.types.numpy import NDArray
|
||||
|
||||
from openflexure_microscope_server.ui import ActionButton
|
||||
from openflexure_microscope_server.background_detect import (
|
||||
ColourChannelDetectLUV,
|
||||
BackgroundDetectAlgorithm,
|
||||
|
|
@ -476,6 +477,16 @@ class BaseCamera(lt.Thing):
|
|||
time.sleep(self.settling_time)
|
||||
self.discard_frames()
|
||||
|
||||
@lt.thing_property
|
||||
def primary_calibration_actions(self) -> list[ActionButton]:
|
||||
"""The calibration actions for both calibration wizard and settings panel."""
|
||||
return []
|
||||
|
||||
@lt.thing_property
|
||||
def secondary_calibration_actions(self) -> list[ActionButton]:
|
||||
"""The calibration actions that appear only in 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
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ from picamera2.outputs import Output
|
|||
import labthings_fastapi as lt
|
||||
from labthings_fastapi.exceptions import NotConnectedToServerError
|
||||
|
||||
from openflexure_microscope_server.ui import ActionButton, action_button_for
|
||||
from . import picamera_recalibrate_utils as recalibrate_utils
|
||||
from . import BaseCamera, JPEGBlob, ArrayModel
|
||||
|
||||
|
|
@ -777,6 +778,59 @@ class StreamingPiCamera2(BaseCamera):
|
|||
)
|
||||
self._initialise_picamera()
|
||||
|
||||
@lt.thing_property
|
||||
def primary_calibration_actions(self) -> list[ActionButton]:
|
||||
"""The calibration actions for both calibration wizard and settings panel."""
|
||||
return [
|
||||
action_button_for(
|
||||
self.full_auto_calibrate,
|
||||
submit_label="Full Auto-Calibrate",
|
||||
can_terminate=False,
|
||||
requires_confirmation=True,
|
||||
confirmation_message=(
|
||||
"Start recalibration? This may take a while, and the microscope "
|
||||
"will be locked during this time."
|
||||
),
|
||||
),
|
||||
action_button_for(
|
||||
self.auto_expose_from_minimum,
|
||||
submit_label="Auto Gain & Shutter Speed",
|
||||
can_terminate=False,
|
||||
),
|
||||
action_button_for(
|
||||
self.calibrate_white_balance,
|
||||
submit_label="Auto White Balance",
|
||||
can_terminate=False,
|
||||
),
|
||||
action_button_for(
|
||||
self.calibrate_lens_shading,
|
||||
submit_label="Auto Flat Field Correction",
|
||||
can_terminate=False,
|
||||
requires_confirmation=True,
|
||||
confirmation_message=(
|
||||
"Is the microscope looking at an evenly illuminated, empty field "
|
||||
"of view? If not, the current image will show through in any "
|
||||
"images captured afterwards."
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@lt.thing_property
|
||||
def secondary_calibration_actions(self) -> list[ActionButton]:
|
||||
"""The calibration actions that appear only in settings panel."""
|
||||
return [
|
||||
action_button_for(
|
||||
self.flat_lens_shading,
|
||||
submit_label="Disable Flat Field Correction",
|
||||
can_terminate=False,
|
||||
),
|
||||
action_button_for(
|
||||
self.reset_lens_shading,
|
||||
submit_label="Reset Flat Field Correction",
|
||||
can_terminate=False,
|
||||
),
|
||||
]
|
||||
|
||||
@lt.thing_property
|
||||
def lens_shading_tables(self) -> Optional[LensShading]:
|
||||
"""The current lens shading (i.e. flat-field correction).
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ from scipy.ndimage import gaussian_filter
|
|||
|
||||
import labthings_fastapi as lt
|
||||
|
||||
from openflexure_microscope_server.ui import ActionButton, action_button_for
|
||||
|
||||
from . import BaseCamera, JPEGBlob, ArrayModel
|
||||
from ..stage import BaseStage
|
||||
|
||||
|
|
@ -40,6 +42,7 @@ class SimulatedCamera(BaseCamera):
|
|||
|
||||
_stage: Optional[BaseStage] = None
|
||||
_server: Optional[lt.ThingServer] = None
|
||||
_show_sample: bool = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -124,10 +127,11 @@ class SimulatedCamera(BaseCamera):
|
|||
Canvas is int16 so that random noise can be added to simulation image before
|
||||
changing to unit8 to stop wrapping.
|
||||
"""
|
||||
self.canvas = np.ones(self.canvas_shape, dtype=np.int16)
|
||||
self.canvas[:, :, 0] *= BG_COLOR[0]
|
||||
self.canvas[:, :, 1] *= BG_COLOR[1]
|
||||
self.canvas[:, :, 2] *= BG_COLOR[2]
|
||||
self.blank_canvas = np.ones(self.canvas_shape, dtype=np.int16)
|
||||
self.blank_canvas[:, :, 0] *= BG_COLOR[0]
|
||||
self.blank_canvas[:, :, 1] *= BG_COLOR[1]
|
||||
self.blank_canvas[:, :, 2] *= BG_COLOR[2]
|
||||
self.canvas = self.blank_canvas.copy()
|
||||
w, h, _ = self.glyph_shape
|
||||
for x, y, sprite_size_index in self.blobs:
|
||||
self.canvas[
|
||||
|
|
@ -149,7 +153,8 @@ class SimulatedCamera(BaseCamera):
|
|||
x_slice = slice(top_left[0], top_left[0] + self.shape[0])
|
||||
y_slice = slice(top_left[1], top_left[1] + self.shape[1])
|
||||
z_slice = slice(None)
|
||||
focused_image = self.canvas[(x_slice, y_slice, z_slice)]
|
||||
canvas = self.canvas if self._show_sample else self.blank_canvas
|
||||
focused_image = canvas[(x_slice, y_slice, z_slice)]
|
||||
image = gaussian_filter(
|
||||
focused_image,
|
||||
sigma=np.abs(pos[2]) / 5,
|
||||
|
|
@ -279,3 +284,25 @@ class SimulatedCamera(BaseCamera):
|
|||
output = io.BytesIO()
|
||||
piexif.insert(piexif.dump(exif_dict), jpeg, output)
|
||||
return JPEGBlob.from_bytes(output.getvalue())
|
||||
|
||||
@lt.thing_action
|
||||
def remove_sample(self):
|
||||
"""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
|
||||
def load_sample(self):
|
||||
"""Show the simulated sample."""
|
||||
if self._show_sample:
|
||||
raise RuntimeError("Sample is already in place.")
|
||||
self._show_sample = True
|
||||
|
||||
@lt.thing_property
|
||||
def secondary_calibration_actions(self) -> list[ActionButton]:
|
||||
"""The calibration actions that appear only in settings panel."""
|
||||
return [
|
||||
action_button_for(self.load_sample, submit_label="Load Sample"),
|
||||
action_button_for(self.remove_sample, submit_label="Remove Sample"),
|
||||
]
|
||||
|
|
|
|||
41
src/openflexure_microscope_server/ui.py
Normal file
41
src/openflexure_microscope_server/ui.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
"""Functionality for communicating the required user interface for a thing."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
def action_button_for(action, **kwargs):
|
||||
"""Create an action button for the specified Thing Action.
|
||||
|
||||
:param action: The thing action to create a button for.
|
||||
:param kwargs: Any attribute of `ActionButton` except for ``thing`` or ``action``.
|
||||
"""
|
||||
thing = action.args[0]
|
||||
thing_path = thing.path.strip("/")
|
||||
action_name = action.func.__name__
|
||||
return ActionButton(thing=thing_path, action=action_name, **kwargs)
|
||||
|
||||
|
||||
class ActionButton(BaseModel):
|
||||
"""The data required for creating an actionButton in Vue.
|
||||
|
||||
Currently this cannot be used to submitData, or to submitOnEvent.
|
||||
"""
|
||||
|
||||
thing: str
|
||||
"""The Thing "path" for the Thing instance."""
|
||||
action: str
|
||||
"""The name of the action to be triggered."""
|
||||
poll_interval: int = 1
|
||||
"""The interval for polling in seconds."""
|
||||
submit_label: str = "Submit"
|
||||
"""The label for the action button."""
|
||||
can_terminate: bool = True
|
||||
"""Specify whether the action can be terminated."""
|
||||
requires_confirmation: bool = False
|
||||
"""Specify whether a confirmation modal is needed."""
|
||||
confirmation_message: str = "Start task?"
|
||||
"""The message for the confirmation modal."""
|
||||
button_primary: bool = True
|
||||
"""Specify whether the button is styled as a primary button."""
|
||||
modal_progress: bool = False
|
||||
"""Specify whether to show a progress modal."""
|
||||
Loading…
Add table
Add a link
Reference in a new issue