Camera settings exposed in UI defined by server.
This commit is contained in:
parent
3651013440
commit
e7e3a08210
7 changed files with 159 additions and 80 deletions
|
|
@ -20,7 +20,7 @@ import piexif
|
||||||
import labthings_fastapi as lt
|
import labthings_fastapi as lt
|
||||||
from labthings_fastapi.types.numpy import NDArray
|
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 (
|
from openflexure_microscope_server.background_detect import (
|
||||||
ColourChannelDetectLUV,
|
ColourChannelDetectLUV,
|
||||||
BackgroundDetectAlgorithm,
|
BackgroundDetectAlgorithm,
|
||||||
|
|
@ -487,6 +487,11 @@ class BaseCamera(lt.Thing):
|
||||||
"""The calibration actions that appear only in settings panel."""
|
"""The calibration actions that appear only in settings panel."""
|
||||||
return []
|
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
|
# Note that the default detector name is set at init. This is over written if
|
||||||
# setting is loaded from disk.
|
# setting is loaded from disk.
|
||||||
@lt.thing_setting
|
@lt.thing_setting
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,12 @@ from picamera2.outputs import Output
|
||||||
import labthings_fastapi as lt
|
import labthings_fastapi as lt
|
||||||
from labthings_fastapi.exceptions import NotConnectedToServerError
|
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 picamera_recalibrate_utils as recalibrate_utils
|
||||||
from . import BaseCamera, JPEGBlob, ArrayModel
|
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
|
@lt.thing_property
|
||||||
def lens_shading_tables(self) -> Optional[LensShading]:
|
def lens_shading_tables(self) -> Optional[LensShading]:
|
||||||
"""The current lens shading (i.e. flat-field correction).
|
"""The current lens shading (i.e. flat-field correction).
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,12 @@ from scipy.ndimage import gaussian_filter
|
||||||
|
|
||||||
import labthings_fastapi as lt
|
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 . import BaseCamera, JPEGBlob, ArrayModel
|
||||||
from ..stage import BaseStage
|
from ..stage import BaseStage
|
||||||
|
|
@ -166,7 +171,7 @@ class SimulatedCamera(BaseCamera):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add noise and convert to uint8
|
# 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 < 0] = 0
|
||||||
image[image > 255] = 255
|
image[image > 255] = 255
|
||||||
return image.astype("uint8")
|
return image.astype("uint8")
|
||||||
|
|
@ -221,6 +226,8 @@ class SimulatedCamera(BaseCamera):
|
||||||
return self._capture_thread.is_alive()
|
return self._capture_thread.is_alive()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
noise_level = lt.ThingProperty(float, 2.0)
|
||||||
|
|
||||||
def _capture_frames(self):
|
def _capture_frames(self):
|
||||||
portal = lt.get_blocking_portal(self)
|
portal = lt.get_blocking_portal(self)
|
||||||
while self._capture_enabled:
|
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.load_sample, submit_label="Load Sample"),
|
||||||
action_button_for(self.remove_sample, submit_label="Remove 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")]
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,6 @@
|
||||||
from pydantic import BaseModel
|
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):
|
class ActionButton(BaseModel):
|
||||||
"""The data required for creating an actionButton in Vue.
|
"""The data required for creating an actionButton in Vue.
|
||||||
|
|
||||||
|
|
@ -53,3 +41,54 @@ class ActionButton(BaseModel):
|
||||||
|
|
||||||
success_message: str = "Success!"
|
success_message: str = "Success!"
|
||||||
"""The message to show on successful completion."""
|
"""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)
|
||||||
|
|
|
||||||
|
|
@ -98,9 +98,14 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
readBack: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
readBackDelay: {
|
readBackDelay: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: undefined,
|
default: 1000,
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -124,9 +129,6 @@ export default {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
readBack: function() {
|
|
||||||
return this.readBackDelay !== undefined;
|
|
||||||
},
|
|
||||||
propertyDescription: function() {
|
propertyDescription: function() {
|
||||||
try {
|
try {
|
||||||
return this.thingDescription(this.thingName).properties[
|
return this.thingDescription(this.thingName).properties[
|
||||||
|
|
@ -202,7 +204,6 @@ export default {
|
||||||
writeProperty: async function() {
|
writeProperty: async function() {
|
||||||
try {
|
try {
|
||||||
let requestedValue = this.value;
|
let requestedValue = this.value;
|
||||||
console.log("writing", requestedValue);
|
|
||||||
await this.writeThingProperty(
|
await this.writeThingProperty(
|
||||||
this.thingName,
|
this.thingName,
|
||||||
this.propertyName,
|
this.propertyName,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<template>
|
||||||
|
<property-control
|
||||||
|
:thing="propertyData.thing"
|
||||||
|
:property-name="propertyData.property_name"
|
||||||
|
:thing-name="propertyData.thing"
|
||||||
|
:read-back="propertyData.read_back"
|
||||||
|
:read-back-delay="propertyData.read_back_delay"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import PropertyControl from "./propertyControl.vue";
|
||||||
|
|
||||||
|
// Export main app
|
||||||
|
export default {
|
||||||
|
name: "ServerSpecifiedPropertyControl",
|
||||||
|
|
||||||
|
components: {
|
||||||
|
PropertyControl
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
propertyData: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less"></style>
|
||||||
|
|
@ -4,37 +4,14 @@
|
||||||
<div class="uk-width-large">
|
<div class="uk-width-large">
|
||||||
<h3>Automatic calibration</h3>
|
<h3>Automatic calibration</h3>
|
||||||
<cameraCalibrationSettings :camera-uri="cameraUri" />
|
<cameraCalibrationSettings :camera-uri="cameraUri" />
|
||||||
|
|
||||||
<h3>Manual camera settings</h3>
|
<h3>Manual camera settings</h3>
|
||||||
<form @submit.prevent="applySettingsRequest">
|
<div class="uk-margin-small-bottom">
|
||||||
<div class="uk-margin-small-bottom">
|
<server-specified-property-control
|
||||||
<ul uk-accordion="multiple: true">
|
v-for="(setting, index) in manualCameraSettings"
|
||||||
<li class="uk-open">
|
:key="'cam_setting' + index"
|
||||||
<a class="uk-accordion-title" href="#">Pi Camera Settings</a>
|
:property-data="setting"
|
||||||
<div class="uk-accordion-content">
|
/>
|
||||||
<PropertyControl
|
</div>
|
||||||
label="Exposure Time (0-33251)"
|
|
||||||
property-name="exposure_time"
|
|
||||||
thing-name="camera"
|
|
||||||
:read-back-delay="1000"
|
|
||||||
/>
|
|
||||||
<PropertyControl
|
|
||||||
label="Analogue Gain"
|
|
||||||
property-name="analogue_gain"
|
|
||||||
thing-name="camera"
|
|
||||||
:read-back-delay="1000"
|
|
||||||
/>
|
|
||||||
<PropertyControl
|
|
||||||
label="Colour Gains"
|
|
||||||
property-name="colour_gains"
|
|
||||||
thing-name="camera"
|
|
||||||
:read-back-delay="1000"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="mini-stream">
|
<div id="mini-stream">
|
||||||
|
|
@ -47,7 +24,7 @@
|
||||||
<script>
|
<script>
|
||||||
import cameraCalibrationSettings from "./cameraSettingsComponents/cameraCalibrationSettings.vue";
|
import cameraCalibrationSettings from "./cameraSettingsComponents/cameraCalibrationSettings.vue";
|
||||||
import miniStreamDisplay from "../../genericComponents/miniStreamDisplay.vue";
|
import miniStreamDisplay from "../../genericComponents/miniStreamDisplay.vue";
|
||||||
import PropertyControl from "../../labThingsComponents/propertyControl.vue";
|
import ServerSpecifiedPropertyControl from "../../labThingsComponents/serverSpecifiedPropertyControl.vue";
|
||||||
|
|
||||||
// Export main app
|
// Export main app
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -56,37 +33,12 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
cameraCalibrationSettings,
|
cameraCalibrationSettings,
|
||||||
miniStreamDisplay,
|
miniStreamDisplay,
|
||||||
PropertyControl
|
ServerSpecifiedPropertyControl
|
||||||
},
|
},
|
||||||
|
|
||||||
data: function() {
|
data() {
|
||||||
return {
|
return {
|
||||||
picamera: {
|
manualCameraSettings: [],
|
||||||
shutter_speed: undefined,
|
|
||||||
analog_gain: undefined,
|
|
||||||
digital_gain: undefined,
|
|
||||||
framerate: undefined,
|
|
||||||
awb_gains: undefined
|
|
||||||
},
|
|
||||||
mjpeg_bitrate: undefined,
|
|
||||||
stream_resolution: undefined,
|
|
||||||
jpeg_quality: undefined,
|
|
||||||
bitrateOptions: [
|
|
||||||
{ text: "Maximum (unlimited)", value: -1 },
|
|
||||||
{ text: "High (25Mbps)", value: 25000000 },
|
|
||||||
{ text: "Normal (17Mbps)", value: 17000000 },
|
|
||||||
{ text: "Low (5Mbps)", value: 5000000 },
|
|
||||||
{ text: "Very low (2.5Mbps)", value: 2500000 }
|
|
||||||
],
|
|
||||||
resolutionOptions: [
|
|
||||||
{ text: "Higher (832, 624)", value: [832, 624] },
|
|
||||||
{ text: "Normal (640, 480)", value: [640, 480] }
|
|
||||||
],
|
|
||||||
framerateOptions: [
|
|
||||||
{ text: "Normal (30fps)", value: 30 },
|
|
||||||
{ text: "Low (15fps)", value: 15 },
|
|
||||||
{ text: "Very low (10fps)", value: 10 }
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -94,6 +46,13 @@ export default {
|
||||||
cameraUri: function() {
|
cameraUri: function() {
|
||||||
return `${this.$store.getters.baseUri}/camera/`;
|
return `${this.$store.getters.baseUri}/camera/`;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async created() {
|
||||||
|
this.manualCameraSettings = await this.readThingProperty(
|
||||||
|
"camera",
|
||||||
|
"manual_camera_settings"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue