From ca21ab03c6ec0fccb179ae91026d1dfdcef02e53 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Sun, 8 Mar 2026 11:39:50 +0000 Subject: [PATCH] Tidy up server specified interface --- .../things/background_detect.py | 56 +++++++++++-------- .../things/scan_workflows.py | 4 +- src/openflexure_microscope_server/ui.py | 15 +++-- .../serverSpecifiedInterface.vue | 4 +- .../paneBackgroundDetect.vue | 6 +- 5 files changed, 49 insertions(+), 36 deletions(-) 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"" - 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",