Move background detect settings into SlideScan

This commit is contained in:
Julian Stirling 2026-03-08 16:15:29 +00:00
parent bfef998300
commit 4da05b1869
7 changed files with 59 additions and 171 deletions

View file

@ -44,6 +44,7 @@ from openflexure_microscope_server.ui import (
Accordion,
HTMLBlock,
UIElementList,
action_button_for,
property_control_for,
)
@ -560,6 +561,28 @@ class HistoScanWorkflow(RectGridWorkflow[HistoScanSettingsModel]):
return imaged, focus_height
@lt.action
def check_background(self) -> str:
"""Check if sample is background.
This action is a pre-run check for feeding back to the user.
"""
image_array = self._cam.grab_as_array(stream_name="lores")
is_sample, bg_message = self._background_detector.image_is_sample(image_array)
label = "sample" if is_sample else "background"
return f"Current image is {label} ({bg_message})"
@lt.action
def set_background(self) -> None:
"""Set the background for this background detector.
This sets the background for this workflows background detector as opposed to
the active background detector for the camera.
"""
image_array = self._cam.grab_as_array(stream_name="lores")
self._background_detector.set_background(image_array)
@lt.endpoint("get", "settings_ui", responses=UI_ELEMENT_RESPONSE)
def settings_ui(self) -> UIElementList:
"""Return the UI for the workflow's settings in the scan tab."""
@ -593,13 +616,32 @@ class HistoScanWorkflow(RectGridWorkflow[HistoScanSettingsModel]):
),
]
)
background_ui = self._background_detector.settings_ui()
set_bg_button = action_button_for(
self,
"set_background",
poll_interval=0.1,
submit_label="Set Background",
can_terminate=False,
notify_on_success=True,
success_message="Background image has been updated",
)
check_bg_button = action_button_for(
self,
"check_background",
poll_interval=0.1,
submit_label="Check Current Image",
can_terminate=False,
notify_on_success=True,
response_is_success_message=True,
)
background_ui.root += [set_bg_button, check_bg_button]
return UIElementList(
[
HTMLBlock(html=f"<h4>{self.display_name}</h4><p>{self.ui_blurb}<p>"),
Accordion(
title="Background Detect",
children=self._background_detector.settings_ui(),
),
Accordion(title="Background Detect", children=background_ui),
Accordion(
title="Scan Settings",
children=scan_settings,

View file

@ -140,7 +140,7 @@ class ActionButton(BaseModel):
action: str
"""The name of the action to be triggered."""
poll_interval: int = 1
poll_interval: float = 1
"""The interval for polling in seconds."""
submit_label: str = "Submit"
@ -171,6 +171,12 @@ class ActionButton(BaseModel):
notify_on_success: bool = False
"""Specify whether to a notification on successful completion."""
response_is_success_message: bool = False
"""Set True to use the action response as the success message.
If set ``success_message`` has is never used.
"""
success_message: str = "Success!"
"""The message to show on successful completion."""

View file

@ -82,7 +82,6 @@ import tabContent from "./genericComponents/tabContent.vue";
// Import new content components
import aboutContent from "./tabContentComponents/aboutContent.vue";
import backgroundDetectContent from "./tabContentComponents/backgroundDetectContent.vue";
import controlContent from "./tabContentComponents/controlContent.vue";
import loggingContent from "./tabContentComponents/loggingContent.vue";
import powerContent from "./tabContentComponents/powerContent.vue";
@ -150,15 +149,6 @@ export default {
component: markRaw(controlContent),
requiredThings: [],
},
{
id: "background-detect",
title: "Background Detect",
icon: "background_replace",
component: markRaw(backgroundDetectContent),
// While stage isn't needed; automatic background detect has little function
// for a manual microscope.
requiredThings: ["stage"],
},
{
id: "slide-scan",
title: "Slide Scan",

View file

@ -45,7 +45,11 @@ export default {
*/
actionResponse: function (response) {
if (this.actionData.notify_on_success) {
this.modalNotify(this.actionData.success_message);
if (this.actionData.response_is_success_message) {
this.modalNotify(response.output);
} else {
this.modalNotify(this.actionData.success_message);
}
this.$emit("response", response);
}
},

View file

@ -9,7 +9,7 @@
/>
<server-specified-action-button
v-else-if="element.element_type === 'action_button'"
:property-data="element"
:action-data="element"
/>
<simple-accordion v-else-if="element.element_type === 'accordion'" :title="element.title">
<server-specified-interface :elements="element.children" />

View file

@ -1,129 +0,0 @@
<template>
<div ref="backgroundDetectContent" class="uk-padding-small">
<div>
<ul uk-accordion="multiple: true">
<li>
<a class="uk-accordion-title" href="#">Configure</a>
<div class="uk-accordion-content">
<h4 v-if="backgroundDetectorDisplayName" class="detector-name">
{{ backgroundDetectorDisplayName }}
</h4>
<server-specified-property-control
v-for="(setting, index) in backgroundDetectorSettings"
:key="'detector_setting' + index"
:property-data="setting"
/>
</div>
</li>
</ul>
<div class="uk-margin">
<action-button
thing="camera"
action="set_background"
submit-label="Set Background"
:can-terminate="false"
:poll-interval="0.1"
@response="alertBackgroundSet"
@error="modalError"
/>
</div>
<div class="uk-margin">
<!--Once status is read this should be disabled if not ready..-->
<action-button
thing="camera"
action="image_is_sample"
submit-label="Check Current Image"
:is-disabled="!ready"
:can-terminate="false"
:poll-interval="0.1"
@response="alertImageLabel"
@error="modalError"
/>
</div>
</div>
</div>
</template>
<script>
import ActionButton from "../../labThingsComponents/actionButton.vue";
import ServerSpecifiedPropertyControl from "../../labThingsComponents/serverSpecifiedPropertyControl.vue";
import { useIntersectionObserver } from "@vueuse/core";
export default {
components: {
ActionButton,
ServerSpecifiedPropertyControl,
},
data() {
return {
ready: false,
backgroundDetectorName: undefined,
backgroundDetectorDisplayName: undefined,
backgroundDetectorSettings: [],
animate: false,
};
},
async created() {
await this.readSettings();
},
mounted() {
useIntersectionObserver(
this.$refs.backgroundDetectContent,
([{ isIntersecting }]) => {
this.visibilityChanged(isIntersecting);
},
{
threshold: 0.0,
},
);
},
methods: {
async safeReadSettings() {
if (!this.$store.state.connected) return;
try {
await this.readSettings();
} catch (error) {
console.error("Error reading background detector settings:", error);
}
},
visibilityChanged(isVisible) {
if (isVisible) {
this.safeReadSettings();
}
},
alertBackgroundSet() {
this.modalNotify(`Background image has been updated`);
},
alertImageLabel(r) {
let label = r.output[0] ? "sample" : "background";
this.modalNotify(`Current image is ${label} (${r.output[1]})`);
},
readSettings: async function () {
this.backgroundDetectorName = await this.readThingProperty(
"camera",
"background_detector_name",
);
if (this.backgroundDetectorName) {
this.ready = await this.readThingProperty(this.backgroundDetectorName, "ready");
this.backgroundDetectorSettings =
(await this.getThingEndpoint(this.backgroundDetectorName, "settings_ui")) || [];
this.backgroundDetectorDisplayName = await this.readThingProperty(
this.backgroundDetectorName,
"display_name",
);
}
},
},
};
</script>
<style scoped lang="less">
.detector-name {
margin-bottom: 0.5rem;
}
</style>

View file

@ -1,25 +0,0 @@
<template>
<!-- Grid managing tab content -->
<div uk-grid class="uk-height-1-1 uk-margin-remove uk-padding-remove">
<div class="control-component">
<paneBackgroundDetect />
</div>
<div class="view-component uk-width-expand">
<streamDisplay />
</div>
</div>
</template>
<script>
import paneBackgroundDetect from "./backgroundDetectComponents/paneBackgroundDetect";
import streamDisplay from "./streamContent.vue";
export default {
name: "BackgroundDetectContent",
components: {
paneBackgroundDetect,
streamDisplay,
},
};
</script>