diff --git a/src/openflexure_microscope_server/things/background_detect.py b/src/openflexure_microscope_server/things/background_detect.py
index 8039605f..c2753a75 100644
--- a/src/openflexure_microscope_server/things/background_detect.py
+++ b/src/openflexure_microscope_server/things/background_detect.py
@@ -13,7 +13,11 @@ from pydantic import BaseModel
import labthings_fastapi as lt
-from openflexure_microscope_server.ui import PropertyControl, property_control_for
+from openflexure_microscope_server.ui import (
+ UI_ELEMENT_RESPONSE,
+ UIElementList,
+ property_control_for,
+)
SettingsType = TypeVar("SettingsType", bound=BaseModel)
BackgroundType = TypeVar("BackgroundType", bound=BaseModel)
@@ -72,9 +76,9 @@ class BackgroundDetectAlgorithm(lt.Thing):
"Each background detect algorithm must implement an set_background method."
)
- @lt.property
- def settings_ui(self) -> list[PropertyControl]:
- """A list of PropertyControl objects to create the settings in the UI."""
+ @lt.endpoint("get", "settings_ui", responses=UI_ELEMENT_RESPONSE)
+ def settings_ui(self) -> UIElementList:
+ """Return the UI for the background detect settings."""
raise NotImplementedError(
"Each background detect algorithm must implement an settings_ui method."
)
@@ -113,15 +117,19 @@ class ColourChannelDetectLUV(BackgroundDetectAlgorithm):
image to be labeled as containing sample.
"""
- @lt.property
- def settings_ui(self) -> list[PropertyControl]:
- """A list of PropertyControl objects to create the settings in the UI."""
- return [
- property_control_for(self, "channel_tolerance", label="Channel Tolerance"),
- property_control_for(
- self, "min_sample_coverage", label="Sample Coverage Required (%)"
- ),
- ]
+ @lt.endpoint("get", "settings_ui", responses=UI_ELEMENT_RESPONSE)
+ def settings_ui(self) -> UIElementList:
+ """Return the UI for the background detect settings."""
+ return UIElementList(
+ [
+ property_control_for(
+ self, "channel_tolerance", label="Channel Tolerance"
+ ),
+ property_control_for(
+ self, "min_sample_coverage", label="Sample Coverage Required (%)"
+ ),
+ ]
+ )
@lt.property
def ready(self) -> bool:
@@ -237,15 +245,19 @@ class ChannelDeviationLUV(BackgroundDetectAlgorithm):
image to be labeled as containing sample.
"""
- @lt.property
- def settings_ui(self) -> list[PropertyControl]:
- """A list of PropertyControl objects to create the settings in the UI."""
- return [
- property_control_for(self, "channel_tolerance", label="Channel Tolerance"),
- property_control_for(
- self, "min_sample_coverage", label="Sample Coverage Required (%)"
- ),
- ]
+ @lt.endpoint("get", "settings_ui", responses=UI_ELEMENT_RESPONSE)
+ def settings_ui(self) -> UIElementList:
+ """Return the UI for the background detect settings."""
+ return UIElementList(
+ [
+ property_control_for(
+ self, "channel_tolerance", label="Channel Tolerance"
+ ),
+ property_control_for(
+ self, "min_sample_coverage", label="Sample Coverage Required (%)"
+ ),
+ ]
+ )
@lt.property
def ready(self) -> bool:
diff --git a/src/openflexure_microscope_server/things/scan_workflows.py b/src/openflexure_microscope_server/things/scan_workflows.py
index df8c6e52..8fa8361e 100644
--- a/src/openflexure_microscope_server/things/scan_workflows.py
+++ b/src/openflexure_microscope_server/things/scan_workflows.py
@@ -566,7 +566,7 @@ class HistoScanWorkflow(RectGridWorkflow[HistoScanSettingsModel]):
[
Accordion(
title="Background Detect",
- children=self._background_detector.settings_ui,
+ children=self._background_detector.settings_ui(),
),
property_control_for(
self, "overlap", label="Image Overlap (0.1-0.7)", step=0.05
@@ -581,7 +581,7 @@ class HistoScanWorkflow(RectGridWorkflow[HistoScanSettingsModel]):
),
property_control_for(
self, "stack_dz", label="Stack dz (steps)", step=5
- ).model_dump(),
+ ),
property_control_for(
self, "autofocus_dz", label="Autofocus Range (steps)", step=200
),
diff --git a/src/openflexure_microscope_server/ui.py b/src/openflexure_microscope_server/ui.py
index eb5051f1..66e12241 100644
--- a/src/openflexure_microscope_server/ui.py
+++ b/src/openflexure_microscope_server/ui.py
@@ -32,7 +32,7 @@ class SafeHTMLParser(HTMLParser):
super().__init__()
self.output = ""
- def handle_starttag(self, tag: str, attrs: dict[str, str]) -> None:
+ def handle_starttag(self, tag: str, attrs: list[tuple[str, Optional[str]]]) -> None:
"""Append a start tag found in the HTML if it is in the allowed list.
This is called by the base class when a start tag is found. We only append
@@ -52,15 +52,16 @@ class SafeHTMLParser(HTMLParser):
if tag == "a":
href = None
for attr, value in attrs:
- if attr == "href":
+ if attr == "href" and value is not None:
href = value.strip()
if href:
safe_href = escape(href, quote=True)
# Always in a new tab
- self.output.append(
+ self.output += (
f''
)
+
else:
self.output += ""
else:
@@ -71,7 +72,9 @@ class SafeHTMLParser(HTMLParser):
if tag in ALLOWED_TAGS:
self.output += f"{tag}>"
- def handle_startendtag(self, tag: str, _attrs: dict[str, str]) -> None:
+ def handle_startendtag(
+ self, tag: str, _attrs: list[tuple[str, Optional[str]]]
+ ) -> None:
"""Append a self-closing tag if it is a new line."""
if tag == "br":
self.output += "
"
@@ -251,7 +254,7 @@ class Accordion(BaseModel):
element_type: Literal["accordion"] = "accordion"
title: str
- children: list["UIElementModels"]
+ children: "UIElementList"
class Container(BaseModel):
@@ -262,7 +265,7 @@ class Container(BaseModel):
element_type: Literal["container"] = "container"
css_class: str
- children: list["UIElementModels"]
+ children: "UIElementList"
UIElementModels = HTMLBlock | PropertyControl | ActionButton | Accordion | Container
diff --git a/webapp/src/components/labThingsComponents/serverSpecifiedInterface.vue b/webapp/src/components/labThingsComponents/serverSpecifiedInterface.vue
index 2afafcfe..0a48c572 100644
--- a/webapp/src/components/labThingsComponents/serverSpecifiedInterface.vue
+++ b/webapp/src/components/labThingsComponents/serverSpecifiedInterface.vue
@@ -1,6 +1,8 @@
diff --git a/webapp/src/components/tabContentComponents/backgroundDetectComponents/paneBackgroundDetect.vue b/webapp/src/components/tabContentComponents/backgroundDetectComponents/paneBackgroundDetect.vue
index fc8a90b8..d71959dd 100644
--- a/webapp/src/components/tabContentComponents/backgroundDetectComponents/paneBackgroundDetect.vue
+++ b/webapp/src/components/tabContentComponents/backgroundDetectComponents/paneBackgroundDetect.vue
@@ -111,10 +111,8 @@ export default {
);
if (this.backgroundDetectorName) {
this.ready = await this.readThingProperty(this.backgroundDetectorName, "ready");
- this.backgroundDetectorSettings = await this.readThingProperty(
- this.backgroundDetectorName,
- "settings_ui",
- );
+ this.backgroundDetectorSettings =
+ (await this.getThingEndpoint(this.backgroundDetectorName, "settings_ui")) || [];
this.backgroundDetectorDisplayName = await this.readThingProperty(
this.backgroundDetectorName,
"display_name",