From 0794d4777f240fd2b898d07345f954134d63e885 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Sun, 27 Jul 2025 15:07:35 +0100 Subject: [PATCH 01/13] Camera calibration buttons specified by server. Simulation has option to remove sample --- .../things/camera/__init__.py | 11 ++ .../things/camera/picamera.py | 54 +++++++++ .../things/camera/simulation.py | 37 +++++- src/openflexure_microscope_server/ui.py | 41 +++++++ .../cameraCalibrationSettings.vue | 111 ++++++------------ 5 files changed, 175 insertions(+), 79 deletions(-) create mode 100644 src/openflexure_microscope_server/ui.py diff --git a/src/openflexure_microscope_server/things/camera/__init__.py b/src/openflexure_microscope_server/things/camera/__init__.py index 4225b43d..465ab97f 100644 --- a/src/openflexure_microscope_server/things/camera/__init__.py +++ b/src/openflexure_microscope_server/things/camera/__init__.py @@ -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 diff --git a/src/openflexure_microscope_server/things/camera/picamera.py b/src/openflexure_microscope_server/things/camera/picamera.py index 6ee9e200..70d93799 100644 --- a/src/openflexure_microscope_server/things/camera/picamera.py +++ b/src/openflexure_microscope_server/things/camera/picamera.py @@ -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). diff --git a/src/openflexure_microscope_server/things/camera/simulation.py b/src/openflexure_microscope_server/things/camera/simulation.py index 76d11eb9..4aa790e4 100644 --- a/src/openflexure_microscope_server/things/camera/simulation.py +++ b/src/openflexure_microscope_server/things/camera/simulation.py @@ -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"), + ] diff --git a/src/openflexure_microscope_server/ui.py b/src/openflexure_microscope_server/ui.py new file mode 100644 index 00000000..9b3163a8 --- /dev/null +++ b/src/openflexure_microscope_server/ui.py @@ -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.""" diff --git a/webapp/src/components/tabContentComponents/settingsComponents/cameraSettingsComponents/cameraCalibrationSettings.vue b/webapp/src/components/tabContentComponents/settingsComponents/cameraSettingsComponents/cameraCalibrationSettings.vue index 348a9227..d449d024 100644 --- a/webapp/src/components/tabContentComponents/settingsComponents/cameraSettingsComponents/cameraCalibrationSettings.vue +++ b/webapp/src/components/tabContentComponents/settingsComponents/cameraSettingsComponents/cameraCalibrationSettings.vue @@ -1,87 +1,38 @@ @@ -97,6 +48,13 @@ export default { ActionButton }, + data() { + return { + primaryCalibrationActions: [], + secondaryCalibrationActions: [] + }; + }, + props: { showExtraSettings: { type: Boolean, @@ -115,6 +73,11 @@ export default { } }, + async created() { + this.primaryCalibrationActions = await this.readThingProperty("camera", "primary_calibration_actions"); + this.secondaryCalibrationActions = await this.readThingProperty("camera", "secondary_calibration_actions"); + }, + methods: { onRecalibrateResponse: function() { this.modalNotify("Finished recalibration."); From 36510134407e8e3349e0a13648b48e958f9f8676 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Sun, 27 Jul 2025 15:39:38 +0100 Subject: [PATCH 02/13] Generalise server specified action buttons. --- .../things/camera/picamera.py | 2 + src/openflexure_microscope_server/ui.py | 14 ++++ .../serverSpecifiedActionButton.vue | 46 +++++++++++++ .../cameraCalibrationSettings.vue | 65 +++++++------------ 4 files changed, 86 insertions(+), 41 deletions(-) create mode 100644 webapp/src/components/labThingsComponents/serverSpecifiedActionButton.vue diff --git a/src/openflexure_microscope_server/things/camera/picamera.py b/src/openflexure_microscope_server/things/camera/picamera.py index 70d93799..349ad4bd 100644 --- a/src/openflexure_microscope_server/things/camera/picamera.py +++ b/src/openflexure_microscope_server/things/camera/picamera.py @@ -791,6 +791,8 @@ class StreamingPiCamera2(BaseCamera): "Start recalibration? This may take a while, and the microscope " "will be locked during this time." ), + notify_on_success=True, + success_message="Finished recalibration.", ), action_button_for( self.auto_expose_from_minimum, diff --git a/src/openflexure_microscope_server/ui.py b/src/openflexure_microscope_server/ui.py index 9b3163a8..7fad40d3 100644 --- a/src/openflexure_microscope_server/ui.py +++ b/src/openflexure_microscope_server/ui.py @@ -23,19 +23,33 @@ class ActionButton(BaseModel): 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.""" + + notify_on_success: bool = False + """Specify whether to a notification on successful completion.""" + + success_message: str = "Success!" + """The message to show on successful completion.""" diff --git a/webapp/src/components/labThingsComponents/serverSpecifiedActionButton.vue b/webapp/src/components/labThingsComponents/serverSpecifiedActionButton.vue new file mode 100644 index 00000000..86e1b76b --- /dev/null +++ b/webapp/src/components/labThingsComponents/serverSpecifiedActionButton.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/webapp/src/components/tabContentComponents/settingsComponents/cameraSettingsComponents/cameraCalibrationSettings.vue b/webapp/src/components/tabContentComponents/settingsComponents/cameraSettingsComponents/cameraCalibrationSettings.vue index d449d024..2c15d2a9 100644 --- a/webapp/src/components/tabContentComponents/settingsComponents/cameraSettingsComponents/cameraCalibrationSettings.vue +++ b/webapp/src/components/tabContentComponents/settingsComponents/cameraSettingsComponents/cameraCalibrationSettings.vue @@ -1,58 +1,34 @@ From e7e3a08210f26c266951fec1c87b065dc716c5d9 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Sun, 27 Jul 2025 16:54:02 +0100 Subject: [PATCH 03/13] Camera settings exposed in UI defined by server. --- .../things/camera/__init__.py | 7 +- .../things/camera/picamera.py | 34 +++++++- .../things/camera/simulation.py | 16 +++- src/openflexure_microscope_server/ui.py | 63 ++++++++++++--- .../labThingsComponents/propertyControl.vue | 11 +-- .../serverSpecifiedPropertyControl.vue | 31 ++++++++ .../settingsComponents/cameraSettings.vue | 77 +++++-------------- 7 files changed, 159 insertions(+), 80 deletions(-) create mode 100644 webapp/src/components/labThingsComponents/serverSpecifiedPropertyControl.vue diff --git a/src/openflexure_microscope_server/things/camera/__init__.py b/src/openflexure_microscope_server/things/camera/__init__.py index 465ab97f..fcccaae8 100644 --- a/src/openflexure_microscope_server/things/camera/__init__.py +++ b/src/openflexure_microscope_server/things/camera/__init__.py @@ -20,7 +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.ui import ActionButton, PropertyControl from openflexure_microscope_server.background_detect import ( ColourChannelDetectLUV, BackgroundDetectAlgorithm, @@ -487,6 +487,11 @@ class BaseCamera(lt.Thing): """The calibration actions that appear only in settings panel.""" return [] + @lt.thing_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 diff --git a/src/openflexure_microscope_server/things/camera/picamera.py b/src/openflexure_microscope_server/things/camera/picamera.py index 349ad4bd..1f67741f 100644 --- a/src/openflexure_microscope_server/things/camera/picamera.py +++ b/src/openflexure_microscope_server/things/camera/picamera.py @@ -35,7 +35,12 @@ 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 openflexure_microscope_server.ui import ( + ActionButton, + PropertyControl, + action_button_for, + property_control_for, +) from . import picamera_recalibrate_utils as recalibrate_utils from . import BaseCamera, JPEGBlob, ArrayModel @@ -833,6 +838,33 @@ class StreamingPiCamera2(BaseCamera): ), ] + @lt.thing_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, + "exposure_time", + label="Exposure Time (0-33251)", + read_back=True, + read_back_delay=1000, + ), + property_control_for( + self, + "analogue_gain", + label="Analogue Gain", + read_back=True, + read_back_delay=1000, + ), + property_control_for( + self, + "colour_gains", + label="Colour Gains", + read_back=True, + read_back_delay=1000, + ), + ] + @lt.thing_property def lens_shading_tables(self) -> Optional[LensShading]: """The current lens shading (i.e. flat-field correction). diff --git a/src/openflexure_microscope_server/things/camera/simulation.py b/src/openflexure_microscope_server/things/camera/simulation.py index 4aa790e4..5903891b 100644 --- a/src/openflexure_microscope_server/things/camera/simulation.py +++ b/src/openflexure_microscope_server/things/camera/simulation.py @@ -21,7 +21,12 @@ from scipy.ndimage import gaussian_filter import labthings_fastapi as lt -from openflexure_microscope_server.ui import ActionButton, action_button_for +from openflexure_microscope_server.ui import ( + ActionButton, + PropertyControl, + action_button_for, + property_control_for, +) from . import BaseCamera, JPEGBlob, ArrayModel from ..stage import BaseStage @@ -166,7 +171,7 @@ class SimulatedCamera(BaseCamera): ) # Add noise and convert to uint8 - image += RNG.normal(scale=2, size=self.shape).astype("int16") + image += RNG.normal(scale=self.noise_level, size=self.shape).astype("int16") image[image < 0] = 0 image[image > 255] = 255 return image.astype("uint8") @@ -221,6 +226,8 @@ class SimulatedCamera(BaseCamera): return self._capture_thread.is_alive() return False + noise_level = lt.ThingProperty(float, 2.0) + def _capture_frames(self): portal = lt.get_blocking_portal(self) while self._capture_enabled: @@ -306,3 +313,8 @@ class SimulatedCamera(BaseCamera): action_button_for(self.load_sample, submit_label="Load Sample"), action_button_for(self.remove_sample, submit_label="Remove Sample"), ] + + @lt.thing_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")] diff --git a/src/openflexure_microscope_server/ui.py b/src/openflexure_microscope_server/ui.py index 7fad40d3..be2a5c12 100644 --- a/src/openflexure_microscope_server/ui.py +++ b/src/openflexure_microscope_server/ui.py @@ -3,18 +3,6 @@ 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. @@ -53,3 +41,54 @@ class ActionButton(BaseModel): success_message: str = "Success!" """The message to show on successful completion.""" + + +def action_button_for(action, **kwargs) -> ActionButton: + """Create a ActionButton data 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 PropertyControl(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.""" + + property_name: str + """The name of the property (or setting).""" + + label: str + """The label to show in the UI""" + + read_back: bool = False + """Whether or not to read back the property after setting. + + This is useful for hardware settings that may be coerced to the closest value. + """ + + read_back_delay: int = 1000 + """The delay in ms before reading back the property.""" + + +def property_control_for(thing, property_name, **kwargs) -> PropertyControl: + """Create an PropertyControl data for the specified Thing Property. + + :param thing: The instance of the thing that has the property to be controlled. + :param property_name: The name of the property to create a control for. + :param kwargs: Any attribute of `PropertyControl` except for ``thing`` or + ``property_name``. If label is not set here it will be the property name. + """ + thing_path = thing.path.strip("/") + if "label" not in kwargs: + kwargs["label"] = property_name + return PropertyControl(thing=thing_path, property_name=property_name, **kwargs) diff --git a/webapp/src/components/labThingsComponents/propertyControl.vue b/webapp/src/components/labThingsComponents/propertyControl.vue index 27f9ab55..e3ce0cbb 100644 --- a/webapp/src/components/labThingsComponents/propertyControl.vue +++ b/webapp/src/components/labThingsComponents/propertyControl.vue @@ -98,9 +98,14 @@ export default { type: String, required: true }, + readBack: { + type: Boolean, + required: false, + default: false + }, readBackDelay: { type: Number, - default: undefined, + default: 1000, required: false } }, @@ -124,9 +129,6 @@ export default { return 1; } }, - readBack: function() { - return this.readBackDelay !== undefined; - }, propertyDescription: function() { try { return this.thingDescription(this.thingName).properties[ @@ -202,7 +204,6 @@ export default { writeProperty: async function() { try { let requestedValue = this.value; - console.log("writing", requestedValue); await this.writeThingProperty( this.thingName, this.propertyName, diff --git a/webapp/src/components/labThingsComponents/serverSpecifiedPropertyControl.vue b/webapp/src/components/labThingsComponents/serverSpecifiedPropertyControl.vue new file mode 100644 index 00000000..a0f81ab5 --- /dev/null +++ b/webapp/src/components/labThingsComponents/serverSpecifiedPropertyControl.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/webapp/src/components/tabContentComponents/settingsComponents/cameraSettings.vue b/webapp/src/components/tabContentComponents/settingsComponents/cameraSettings.vue index ddcef921..7d56cb8c 100644 --- a/webapp/src/components/tabContentComponents/settingsComponents/cameraSettings.vue +++ b/webapp/src/components/tabContentComponents/settingsComponents/cameraSettings.vue @@ -4,37 +4,14 @@

Automatic calibration

-

Manual camera settings

-
-
- -
-
+
+ +
@@ -47,7 +24,7 @@ From 7f4b4ed1f3a37a3bd666ab3d8252365d275718ea Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Sun, 27 Jul 2025 18:30:32 +0100 Subject: [PATCH 04/13] Splitting logic for property control from logic to create UI from schema --- .../labThingsComponents/inputFromSchema.vue | 216 ++++++++++++++++++ .../labThingsComponents/propertyControl.vue | 194 ++-------------- .../serverSpecifiedPropertyControl.vue | 1 + 3 files changed, 236 insertions(+), 175 deletions(-) create mode 100644 webapp/src/components/labThingsComponents/inputFromSchema.vue diff --git a/webapp/src/components/labThingsComponents/inputFromSchema.vue b/webapp/src/components/labThingsComponents/inputFromSchema.vue new file mode 100644 index 00000000..e1463507 --- /dev/null +++ b/webapp/src/components/labThingsComponents/inputFromSchema.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/webapp/src/components/labThingsComponents/propertyControl.vue b/webapp/src/components/labThingsComponents/propertyControl.vue index e3ce0cbb..2b3e09f0 100644 --- a/webapp/src/components/labThingsComponents/propertyControl.vue +++ b/webapp/src/components/labThingsComponents/propertyControl.vue @@ -1,90 +1,23 @@ - + diff --git a/webapp/src/components/labThingsComponents/serverSpecifiedPropertyControl.vue b/webapp/src/components/labThingsComponents/serverSpecifiedPropertyControl.vue index a0f81ab5..89a24fe3 100644 --- a/webapp/src/components/labThingsComponents/serverSpecifiedPropertyControl.vue +++ b/webapp/src/components/labThingsComponents/serverSpecifiedPropertyControl.vue @@ -3,6 +3,7 @@ :thing="propertyData.thing" :property-name="propertyData.property_name" :thing-name="propertyData.thing" + :label="propertyData.label" :read-back="propertyData.read_back" :read-back-delay="propertyData.read_back_delay" /> From d376eee5d5d734e14e3c0891b031853538ce8be1 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Sun, 27 Jul 2025 20:46:37 +0100 Subject: [PATCH 05/13] Remove custom error for background. Do not allow check if background is not set. --- .../labThingsComponents/actionButton.vue | 8 ++++++- .../paneBackgroundDetect.vue | 24 ++++++++++++------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/webapp/src/components/labThingsComponents/actionButton.vue b/webapp/src/components/labThingsComponents/actionButton.vue index 73ca9537..b33233a2 100644 --- a/webapp/src/components/labThingsComponents/actionButton.vue +++ b/webapp/src/components/labThingsComponents/actionButton.vue @@ -18,6 +18,7 @@
@@ -25,10 +26,11 @@ thing="camera" action="image_is_sample" submit-label="Check Current Image" + :isDisabled="!backgroundDetectorStatus.ready" :can-terminate="false" :poll-interval="0.1" @response="alertImageLabel" - @error="backgroundDetectError" + @error="modalError" />
@@ -40,7 +42,13 @@ import ActionButton from "../../labThingsComponents/actionButton.vue"; export default { components: { - ActionButton + ActionButton, + }, + + data() { + return { + backgroundDetectorStatus: undefined, + }; }, methods: { @@ -50,13 +58,13 @@ export default { alertImageLabel(r) { let label = r.output[0] ? "sample" : "background"; this.modalNotify(`Current image is ${label} (${r.output[1]})`); - }, - backgroundDetectError() { - this.modalError( - "Background detection failed, most likely you need to set a background image." + - " There may be more information in the log." - ); } + }, + async created() { + this.backgroundDetectorStatus = await this.readThingProperty( + "camera", + "background_detector_status" + ); } }; From 92d15d921bad8cf2c121c48dd401aade059f7064 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Sun, 27 Jul 2025 21:30:16 +0100 Subject: [PATCH 06/13] Display background detector settings built automatically from schema --- .../background_detect.py | 7 ++++--- .../labThingsComponents/propertyControl.vue | 1 - .../paneBackgroundDetect.vue | 18 ++++++++++++++++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/openflexure_microscope_server/background_detect.py b/src/openflexure_microscope_server/background_detect.py index 6a1b417f..7773eb93 100644 --- a/src/openflexure_microscope_server/background_detect.py +++ b/src/openflexure_microscope_server/background_detect.py @@ -32,8 +32,9 @@ class BackgroundDetectorStatus(BaseModel): ``ready`` is used in case more complex methods are added in the future, which need different initialisation. """ - settings: BaseModel - """The settings for this this background detect Algorithm""" + settings: dict[str, Any] + """The settings for this this background detect Algorithm. These are a dictionary + dumped from the base model.""" # Setting schema is a dict until LabThings FastAPI issue #154 is fixed and # DataSchema can be used directly. For now `model_dump()` must be used to dump schema @@ -63,7 +64,7 @@ class BackgroundDetectAlgorithm: """The status information needed for the GUI. Read only.""" return BackgroundDetectorStatus( ready=self.background_data is not None, - settings=self.settings, + settings=self.settings.model_dump(), # Dump model with `model_dump()` for reason explained when defining # BackgroundDetectorStatus settings_schema=type_to_dataschema(self.settings_data_model).model_dump(), diff --git a/webapp/src/components/labThingsComponents/propertyControl.vue b/webapp/src/components/labThingsComponents/propertyControl.vue index 2b3e09f0..1ed1b477 100644 --- a/webapp/src/components/labThingsComponents/propertyControl.vue +++ b/webapp/src/components/labThingsComponents/propertyControl.vue @@ -88,7 +88,6 @@ export default { }, writeProperty: async function(requestedValue) { try { - console.log(requestedValue); this.value=requestedValue; await this.writeThingProperty( this.thingName, diff --git a/webapp/src/components/tabContentComponents/backgroundDetectComponents/paneBackgroundDetect.vue b/webapp/src/components/tabContentComponents/backgroundDetectComponents/paneBackgroundDetect.vue index 6c09d02c..1a9b4df5 100644 --- a/webapp/src/components/tabContentComponents/backgroundDetectComponents/paneBackgroundDetect.vue +++ b/webapp/src/components/tabContentComponents/backgroundDetectComponents/paneBackgroundDetect.vue @@ -5,7 +5,12 @@
  • Settings
    -

    TODO!

    +
  • @@ -26,7 +31,7 @@ thing="camera" action="image_is_sample" submit-label="Check Current Image" - :isDisabled="!backgroundDetectorStatus.ready" + :isDisabled="!ready" :can-terminate="false" :poll-interval="0.1" @response="alertImageLabel" @@ -39,10 +44,12 @@