Get UI working for background detector things
This commit is contained in:
parent
44860bf777
commit
3e8874188e
5 changed files with 70 additions and 38 deletions
|
|
@ -13,6 +13,8 @@ from pydantic import BaseModel
|
||||||
|
|
||||||
import labthings_fastapi as lt
|
import labthings_fastapi as lt
|
||||||
|
|
||||||
|
from openflexure_microscope_server.ui import PropertyControl, property_control_for
|
||||||
|
|
||||||
SettingsType = TypeVar("SettingsType", bound=BaseModel)
|
SettingsType = TypeVar("SettingsType", bound=BaseModel)
|
||||||
BackgroundType = TypeVar("BackgroundType", bound=BaseModel)
|
BackgroundType = TypeVar("BackgroundType", bound=BaseModel)
|
||||||
|
|
||||||
|
|
@ -32,6 +34,8 @@ class ChannelBlankError(RuntimeError):
|
||||||
class BackgroundDetectAlgorithm(lt.Thing):
|
class BackgroundDetectAlgorithm(lt.Thing):
|
||||||
"""The base class for defining background detect algorithms."""
|
"""The base class for defining background detect algorithms."""
|
||||||
|
|
||||||
|
display_name: str = lt.property(default="Base Detector", readonly=True)
|
||||||
|
|
||||||
@lt.property
|
@lt.property
|
||||||
def ready(self) -> bool:
|
def ready(self) -> bool:
|
||||||
"""Whether the background detector is ready."""
|
"""Whether the background detector is ready."""
|
||||||
|
|
@ -59,6 +63,13 @@ class BackgroundDetectAlgorithm(lt.Thing):
|
||||||
"Each background detect algorithm must implement an set_background method."
|
"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."""
|
||||||
|
raise NotImplementedError(
|
||||||
|
"Each background detect algorithm must implement an settings_ui method."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ColourChannelDetectLUV(BackgroundDetectAlgorithm):
|
class ColourChannelDetectLUV(BackgroundDetectAlgorithm):
|
||||||
"""Compare images with a known background in LUV colourspace.
|
"""Compare images with a known background in LUV colourspace.
|
||||||
|
|
@ -68,6 +79,8 @@ class ColourChannelDetectLUV(BackgroundDetectAlgorithm):
|
||||||
intuitive way.
|
intuitive way.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
display_name: str = lt.property(default="Colour Channel (LUV)", readonly=True)
|
||||||
|
|
||||||
background_means: Optional[list[float]] = lt.setting(default=None, readonly=True)
|
background_means: Optional[list[float]] = lt.setting(default=None, readonly=True)
|
||||||
"""The mean of each channel in the colourspace."""
|
"""The mean of each channel in the colourspace."""
|
||||||
background_stds: Optional[list[float]] = lt.setting(default=None, readonly=True)
|
background_stds: Optional[list[float]] = lt.setting(default=None, readonly=True)
|
||||||
|
|
@ -91,10 +104,20 @@ class ColourChannelDetectLUV(BackgroundDetectAlgorithm):
|
||||||
image to be labeled as containing sample.
|
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.property
|
@lt.property
|
||||||
def ready(self) -> bool:
|
def ready(self) -> bool:
|
||||||
"""Whether the background detector is ready."""
|
"""Whether the background detector is ready."""
|
||||||
return not (self.background_means is None or self.background_stds is None)
|
return self.background_means is not None and self.background_stds is not None
|
||||||
|
|
||||||
def background_mask(self, image: np.ndarray) -> np.ndarray:
|
def background_mask(self, image: np.ndarray) -> np.ndarray:
|
||||||
"""Calculate a binary image, showing whether each pixel is background.
|
"""Calculate a binary image, showing whether each pixel is background.
|
||||||
|
|
@ -180,6 +203,8 @@ class ChannelDeviationLUV(BackgroundDetectAlgorithm):
|
||||||
to the median standard deviation for a grid of background images.
|
to the median standard deviation for a grid of background images.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
display_name: str = lt.property(default="Channel Deviation (LUV)", readonly=True)
|
||||||
|
|
||||||
background_stds: Optional[list[float]] = lt.setting(default=None, readonly=True)
|
background_stds: Optional[list[float]] = lt.setting(default=None, readonly=True)
|
||||||
"""The standard deviation of each channel in the colourspace."""
|
"""The standard deviation of each channel in the colourspace."""
|
||||||
|
|
||||||
|
|
@ -203,6 +228,21 @@ class ChannelDeviationLUV(BackgroundDetectAlgorithm):
|
||||||
image to be labeled as containing sample.
|
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.property
|
||||||
|
def ready(self) -> bool:
|
||||||
|
"""Whether the background detector is ready."""
|
||||||
|
return self.background_stds is not None
|
||||||
|
|
||||||
def get_sample_coverage(self, image: np.ndarray) -> float:
|
def get_sample_coverage(self, image: np.ndarray) -> float:
|
||||||
"""Return the percentage of the input image that is background.
|
"""Return the percentage of the input image that is background.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,10 @@ class BaseCamera(lt.Thing):
|
||||||
dictionary in this function. Configuration will be added at a later date.
|
dictionary in this function. Configuration will be added at a later date.
|
||||||
"""
|
"""
|
||||||
super().__init__(thing_server_interface)
|
super().__init__(thing_server_interface)
|
||||||
|
# Default is never updated but is used if the value set from settings is
|
||||||
|
# incorrect. In the future a better way to set defaults for thing slot mappings
|
||||||
|
# would be ideal.
|
||||||
|
self._default_detector = "bg_channel_deviations_luv"
|
||||||
self._detector_name = "bg_channel_deviations_luv"
|
self._detector_name = "bg_channel_deviations_luv"
|
||||||
|
|
||||||
def __enter__(self) -> Self:
|
def __enter__(self) -> Self:
|
||||||
|
|
@ -591,6 +595,9 @@ class BaseCamera(lt.Thing):
|
||||||
@property
|
@property
|
||||||
def active_detector(self) -> BackgroundDetectAlgorithm:
|
def active_detector(self) -> BackgroundDetectAlgorithm:
|
||||||
"""The active background detector instance."""
|
"""The active background detector instance."""
|
||||||
|
if self.detector_name not in self.background_detectors:
|
||||||
|
self.logger.warning(f"No detector named {self.detector_name}")
|
||||||
|
self.detector_name = self._default_detector
|
||||||
return self.background_detectors[self.detector_name]
|
return self.background_detectors[self.detector_name]
|
||||||
|
|
||||||
@lt.action
|
@lt.action
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ class SimulatedCamera(BaseCamera):
|
||||||
@lt.property
|
@lt.property
|
||||||
def calibration_required(self) -> bool:
|
def calibration_required(self) -> bool:
|
||||||
"""Whether the camera needs calibrating."""
|
"""Whether the camera needs calibrating."""
|
||||||
return not self.background_detector_status.ready
|
return not self.active_detector.ready
|
||||||
|
|
||||||
def generate_sprites(self) -> None:
|
def generate_sprites(self) -> None:
|
||||||
"""Generate sprites to populate the image."""
|
"""Generate sprites to populate the image."""
|
||||||
|
|
|
||||||
|
|
@ -234,7 +234,7 @@ class SmartScanThing(lt.Thing):
|
||||||
self._csm.assert_calibration()
|
self._csm.assert_calibration()
|
||||||
|
|
||||||
if self.skip_background:
|
if self.skip_background:
|
||||||
if not self._cam.background_detector_status.ready:
|
if not self._cam.active_detector.ready:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Background is not set: you need to calibrate background detection."
|
"Background is not set: you need to calibrate background detection."
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,13 @@
|
||||||
<li>
|
<li>
|
||||||
<a class="uk-accordion-title" href="#">Configure</a>
|
<a class="uk-accordion-title" href="#">Configure</a>
|
||||||
<div class="uk-accordion-content">
|
<div class="uk-accordion-content">
|
||||||
<h4 v-if="backgroundDetectorName" class="detector-name">
|
<h4 v-if="backgroundDetectorDisplayName" class="detector-name">
|
||||||
{{ backgroundDetectorName }}
|
{{ backgroundDetectorDisplayName }}
|
||||||
</h4>
|
</h4>
|
||||||
<input-from-schema
|
<server-specified-property-control
|
||||||
v-if="backgroundDetectorStatus"
|
v-for="(setting, index) in backgroundDetectorSettings"
|
||||||
v-model="backgroundDetectorStatus.settings"
|
:key="'detector_setting' + index"
|
||||||
:data-schema="backgroundDetectorStatus.settings_schema"
|
:property-data="setting"
|
||||||
label=""
|
|
||||||
:animate="animate"
|
|
||||||
@requestUpdate="readSettings"
|
|
||||||
@sendValue="writeSettings"
|
|
||||||
@animationShown="resetAnimate"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -51,33 +46,26 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ActionButton from "../../labThingsComponents/actionButton.vue";
|
import ActionButton from "../../labThingsComponents/actionButton.vue";
|
||||||
import InputFromSchema from "../../labThingsComponents/inputFromSchema.vue";
|
import ServerSpecifiedPropertyControl from "../../labThingsComponents/serverSpecifiedPropertyControl.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ActionButton,
|
ActionButton,
|
||||||
InputFromSchema,
|
ServerSpecifiedPropertyControl,
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
backgroundDetectorStatus: undefined,
|
ready: false,
|
||||||
backgroundDetectorName: undefined,
|
backgroundDetectorName: undefined,
|
||||||
|
backgroundDetectorDisplayName: undefined,
|
||||||
|
backgroundDetectorSettings: [],
|
||||||
animate: false,
|
animate: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
|
||||||
ready() {
|
|
||||||
const status = this.backgroundDetectorStatus;
|
|
||||||
return status && status.ready === true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
async created() {
|
async created() {
|
||||||
this.backgroundDetectorStatus = await this.readThingProperty(
|
this.readSettings();
|
||||||
"camera",
|
|
||||||
"background_detector_status",
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -95,18 +83,15 @@ export default {
|
||||||
},
|
},
|
||||||
readSettings: async function () {
|
readSettings: async function () {
|
||||||
this.backgroundDetectorName = await this.readThingProperty("camera", "detector_name");
|
this.backgroundDetectorName = await this.readThingProperty("camera", "detector_name");
|
||||||
this.backgroundDetectorStatus = await this.readThingProperty(
|
this.ready = await this.readThingProperty(this.backgroundDetectorName, "ready");
|
||||||
"camera",
|
this.backgroundDetectorSettings = await this.readThingProperty(
|
||||||
"background_detector_status",
|
this.backgroundDetectorName,
|
||||||
|
"settings_ui",
|
||||||
|
);
|
||||||
|
this.backgroundDetectorDisplayName = await this.readThingProperty(
|
||||||
|
this.backgroundDetectorName,
|
||||||
|
"display_name",
|
||||||
);
|
);
|
||||||
},
|
|
||||||
writeSettings: async function (requestedValue) {
|
|
||||||
await this.invokeAction("camera", "update_detector_settings", { data: requestedValue });
|
|
||||||
this.animate = true;
|
|
||||||
this.readSettings();
|
|
||||||
},
|
|
||||||
resetAnimate: function () {
|
|
||||||
this.animate = false;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue