Added initial calibration modal wizard

This commit is contained in:
Joel Collins 2020-05-19 15:24:08 +01:00
parent 1a2822f709
commit 31b84e8477
8 changed files with 514 additions and 101 deletions

View file

@ -4,6 +4,11 @@
class="uk-margin-remove uk-padding-remove uk-height-1-1"
uk-grid
>
<!-- Initialisation modals -->
<calibrationModal
ref="calibrationModal"
:available-plugins="plugins"
></calibrationModal>
<!-- Vertical tab bar -->
<div
id="switcher-left"
@ -167,9 +172,12 @@ import settingsContent from "./tabContentComponents/settingsContent.vue";
import galleryContent from "./tabContentComponents/galleryContent.vue";
import extensionContent from "./tabContentComponents/extensionContent.vue";
// Import modal components for device initialisation
import calibrationModal from "./modalComponents/calibrationModal.vue";
// Export main app
export default {
name: "PanelLeft",
name: "AppContent",
components: {
tabIcon,
@ -180,7 +188,8 @@ export default {
viewContent,
settingsContent,
galleryContent,
extensionContent
extensionContent,
calibrationModal
},
data: function() {
@ -220,7 +229,11 @@ export default {
},
ready => {
// Update plugins
this.updatePlugins();
this.updatePlugins().then(() => {
// Start initialisation modals
this.startModals();
});
if (ready) {
console.log("Left panel now ready");
} else {
@ -244,7 +257,7 @@ export default {
methods: {
updatePlugins: function() {
console.log("Updating plugin forms");
axios
return axios
.get(this.pluginsUri)
.then(response => {
console.log(response);
@ -258,6 +271,9 @@ export default {
if (!(this.currentTab == tab)) {
this.currentTab = tab;
}
},
startModals: function() {
this.$refs["calibrationModal"].show();
}
}
};

View file

@ -0,0 +1,270 @@
<template>
<div id="modal-example" ref="calibrationModalEl" uk-modal>
<div v-if="ready" class="uk-modal-dialog uk-modal-body">
<h2 class="uk-modal-title">Microscope Calibration</h2>
<div v-show="stepValue == 0">
<p>
<b
>Some important microscope calibration data is currently missing.</b
>
</p>
<p>
Your microscope will still function, however some functionality will
be limited, and image quality will likely suffer.
</p>
<p>
<b>Click Next to begin microscope calibration.</b>
</p>
</div>
<div v-show="stepValue == 1">
<h3>Lens-shading</h3>
<div v-if="isLSTCalibrated">
<p>
<b
>Your lens-shading table has already been calibrated. Click Next
to move on.</b
>
</p>
</div>
<div v-else>
<p>
<b
>Follow the important steps below before starting lens-shading
calibration!</b
>
</p>
<ul class="uk-list uk-list-bullet">
<li>Remove any samples from your microscope</li>
<li>Ensure your illumination is on and properly fixed in place</li>
</ul>
<miniStreamDisplay
v-if="stepValue == 1"
class="mini-preview"
></miniStreamDisplay>
<p>Once you're ready, click auto-calibrate.</p>
<cameraCalibrationSettings
:show-extra-settings="false"
></cameraCalibrationSettings>
</div>
</div>
<div v-show="stepValue == 2">
<h3>Camera-stage mapping</h3>
<div v-if="isCSMCalibrated">
<p>
<b
>Your camera-stage mapping has already been calibrated. Click Next
to move on.</b
>
</p>
</div>
<div v-else>
<p>
<b
>Follow the important steps below before starting camera-stage
mapping calibration!</b
>
</p>
<ul class="uk-list uk-list-bullet">
<li>Insert a clearly visible sample to the microscope</li>
<li>
Ensure the sample is reasonably well centered on the microscope
camera
</li>
</ul>
<miniStreamDisplay
v-if="stepValue == 2"
class="mini-preview"
></miniStreamDisplay>
<p>Once you're ready, click auto-calibrate.</p>
<cameraStageMappingSettings
:show-extra-settings="false"
></cameraStageMappingSettings>
</div>
</div>
<div v-show="stepValue == 3">
<p>
<b>Calibration complete</b>
</p>
<p>
Click Finish to return to your microscope, or Restart to re-run the
calibration routine
</p>
</div>
<p class="uk-text-right">
<button
v-show="stepValue == 0"
class="uk-button uk-button-default uk-modal-close"
type="button"
>
Cancel
</button>
<button
v-show="stepValue == 3"
class="uk-button uk-button-default"
type="button"
@click="stepValue = 0"
>
Restart
</button>
<button
v-show="stepValue < 3"
class="uk-button uk-button-primary uk-margin-left"
type="button"
@click="increment()"
>
Next
</button>
<button
v-show="stepValue == 3"
class="uk-button uk-button-primary uk-margin-left"
type="button"
@click="hide()"
>
Finish
</button>
</p>
</div>
</div>
</template>
<script>
import axios from "axios";
import cameraCalibrationSettings from "../viewComponents/settingsComponents/cameraCalibrationSettings.vue";
import cameraStageMappingSettings from "../viewComponents/settingsComponents/cameraStageMappingSettings.vue";
import miniStreamDisplay from "../viewComponents/miniStreamDisplay.vue";
export default {
name: "CalibrationModal",
components: {
cameraCalibrationSettings,
cameraStageMappingSettings,
miniStreamDisplay
},
props: {
availablePlugins: {
type: Array,
required: true
}
},
data: function() {
return {
ready: false,
stepValue: 0,
settings: {}
};
},
computed: {
settingsUri: function() {
return `${this.$store.getters.baseUri}/api/v2/instrument/settings`;
},
availablePluginTitles: function() {
return this.availablePlugins.map(a => a.title);
},
isCSMCalibrated: function() {
if (this.settings.extensions["org.openflexure.camera_stage_mapping"]) {
return true;
} else {
return false;
}
},
isLSTCalibrated: function() {
if (
this.settings.camera.picamera &&
this.settings.camera.picamera.lens_shading_table
) {
return true;
} else {
return false;
}
},
canCSMCalibrated: function() {
return this.availablePluginTitles.includes(
"org.openflexure.camera_stage_mapping"
);
},
canLSTCalibrated: function() {
return this.availablePluginTitles.includes(
"org.openflexure.calibration.picamera"
);
},
isUseful: function() {
var CSMUseful = this.canCSMCalibrated && !this.isCSMCalibrated;
var LSTUseful = this.canLSTCalibrated && !this.isLSTCalibrated;
return CSMUseful || LSTUseful;
}
},
methods: {
show: function() {
// Get current settings
this.getSettings().then(() => {
// Check if this calibration wizard can actually do anything useful
console.log(this.isUseful);
if (this.isUseful) {
this.ready = true;
this.stepValue = 0;
// Show the modal
var el = this.$refs["calibrationModalEl"];
this.showModalElement(el); // Calls the mixin
}
});
},
hide: function() {
// Show the modal
var el = this.$refs["calibrationModalEl"];
this.hideModalElement(el); // Calls the mixin
this.ready = false;
},
getSettings: function() {
return axios
.get(this.settingsUri)
.then(response => {
this.settings = response.data;
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
decrement: function() {
if (this.stepValue > 0) {
this.stepValue = this.stepValue - 1;
}
},
increment: function() {
// Upper bound on section number
if (this.stepValue < 3) {
this.stepValue = this.stepValue + 1;
return true;
}
}
}
};
</script>
<style scoped>
.mini-preview {
width: 75%;
margin-left: auto;
margin-right: auto;
border: 1px solid #555;
}
</style>

View file

@ -0,0 +1,69 @@
<template>
<div
id="stream-display"
ref="streamDisplay"
class="stream-display uk-width-1-1 uk-height-1-1 scrollTarget"
>
<img
ref="click-frame"
class="uk-align-center uk-margin-remove-bottom"
:hidden="!streamEnabled"
:src="streamImgUri"
alt="Stream"
/>
<div v-if="!streamEnabled" class="uk-height-1-1">
<div v-if="$store.state.waiting" class="uk-position-center">
<div uk-spinner="ratio: 4.5"></div>
</div>
<div v-else class="uk-position-center position-relative text-center">
No active connection
</div>
</div>
</div>
</template>
<script>
// Export main app
export default {
name: "MiniStreamDisplay",
data: function() {
return {};
},
computed: {
streamEnabled: function() {
return (
this.$store.getters.ready &&
!this.$store.state.globalSettings.disableStream
);
},
streamImgUri: function() {
return `${this.$store.getters.baseUri}/api/v2/streams/mjpeg`;
}
},
methods: {}
};
</script>
<style scoped lang="less">
.stream-display img {
text-align: center;
object-fit: contain;
}
.stream-display {
width: 100%;
height: 100%;
}
.position-relative {
position: relative !important;
}
.text-center {
text-align: center;
}
</style>

View file

@ -0,0 +1,120 @@
<template>
<div>
<!--Show auto calibrate if default plugin is enabled-->
<div v-if="'recalibrate' in recalibrationLinks" class="uk-margin-small">
<taskSubmitter
:can-terminate="false"
:requires-confirmation="true"
:confirmation-message="
'Start recalibration? This may take a while, and the microscope will be locked during this time.'
"
:submit-url="recalibrationLinks.recalibrate.href"
:submit-label="'Auto-Calibrate'"
@response="onRecalibrateResponse"
@error="onRecalibrateError"
>
</taskSubmitter>
</div>
<div v-show="showExtraSettings" class="uk-child-width-expand" uk-grid>
<div v-if="'flatten_lens_shading_table' in recalibrationLinks">
<button
class="uk-button uk-button-danger uk-width-1-1"
@click="flattenLensShadingTableRequest"
>
Disable flat-field correction
</button>
</div>
<div v-if="'delete_lens_shading_table' in recalibrationLinks">
<button
class="uk-button uk-button-danger uk-width-1-1"
@click="deleteLensShadingTableRequest"
>
Adaptive flat-field correction
</button>
</div>
</div>
</div>
</template>
<script>
import axios from "axios";
import taskSubmitter from "../../genericComponents/taskSubmitter";
// Export main app
export default {
name: "CameraCalibrationSettings",
components: {
taskSubmitter
},
props: {
showExtraSettings: {
type: Boolean,
required: false,
default: true
}
},
data: function() {
return {
recalibrationLinks: {},
isCalibrating: false
};
},
computed: {
pluginsUri: function() {
return `${this.$store.getters.baseUri}/api/v2/extensions`;
}
},
mounted() {
this.updateRecalibrationLinks();
},
methods: {
updateRecalibrationLinks: function() {
axios
.get(this.pluginsUri) // Get a list of plugins
.then(response => {
var plugins = response.data;
var foundExtension = plugins.find(
e => e.title === "org.openflexure.calibration.picamera"
);
// if AutocalibrationPlugin is enabled
if (foundExtension) {
// Get plugin action link
this.recalibrationLinks = foundExtension.links;
} else {
this.recalibrationLinks = {};
}
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
onRecalibrateResponse: function() {
this.modalNotify("Finished recalibration.");
// Update local settings
this.updateSettings();
},
onRecalibrateError: function(error) {
this.modalError(error); // Let mixin handle error
},
flattenLensShadingTableRequest: function() {
axios.post(this.recalibrationLinks.flatten_lens_shading_table.href);
},
deleteLensShadingTableRequest: function() {
axios.post(this.recalibrationLinks.delete_lens_shading_table.href);
}
}
};
</script>
<style lang="less"></style>

View file

@ -57,76 +57,37 @@
</div>
<!--Show auto calibrate if default plugin is enabled-->
<div v-if="'recalibrate' in recalibrationLinks" class="uk-margin-small">
<h3>Automatic calibration</h3>
<taskSubmitter
:can-terminate="false"
:requires-confirmation="true"
:confirmation-message="
'Start recalibration? This may take a while, and the microscope will be locked during this time.'
"
:submit-url="recalibrationLinks.recalibrate.href"
:submit-label="'Auto-Calibrate'"
@response="onRecalibrateResponse"
@error="onRecalibrateError"
>
</taskSubmitter>
</div>
<div class="uk-child-width-expand" uk-grid>
<div v-if="'flatten_lens_shading_table' in recalibrationLinks">
<button
class="uk-button uk-button-danger uk-width-1-1"
@click="flattenLensShadingTableRequest"
>
Disable flat-field correction
</button>
</div>
<div v-if="'delete_lens_shading_table' in recalibrationLinks">
<button
class="uk-button uk-button-danger uk-width-1-1"
@click="deleteLensShadingTableRequest"
>
Adaptive flat-field correction
</button>
</div>
</div>
<h3>Automatic calibration</h3>
<cameraCalibrationSettings></cameraCalibrationSettings>
</div>
</template>
<script>
import axios from "axios";
import taskSubmitter from "../../genericComponents/taskSubmitter";
import cameraCalibrationSettings from "./cameraCalibrationSettings.vue";
// Export main app
export default {
name: "CameraSettings",
components: {
taskSubmitter
cameraCalibrationSettings
},
data: function() {
return {
settings: {},
recalibrationLinks: {},
isCalibrating: false
settings: {}
};
},
computed: {
settingsUri: function() {
return `${this.$store.getters.baseUri}/api/v2/instrument/settings`;
},
pluginsUri: function() {
return `${this.$store.getters.baseUri}/api/v2/extensions`;
}
},
mounted() {
this.updateSettings();
this.updateRecalibrationLinks();
},
methods: {
@ -141,27 +102,6 @@ export default {
});
},
updateRecalibrationLinks: function() {
axios
.get(this.pluginsUri) // Get a list of plugins
.then(response => {
var plugins = response.data;
var foundExtension = plugins.find(
e => e.title === "org.openflexure.calibration.picamera"
);
// if AutocalibrationPlugin is enabled
if (foundExtension) {
// Get plugin action link
this.recalibrationLinks = foundExtension.links;
} else {
this.recalibrationLinks = {};
}
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
applyConfigRequest: function() {
console.log("Applying config to the microscope");
var payload = {
@ -188,31 +128,9 @@ export default {
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
onRecalibrateResponse: function() {
this.modalNotify("Finished recalibration.");
// Update local settings
this.updateSettings();
},
onRecalibrateError: function(error) {
this.modalError(error); // Let mixin handle error
},
flattenLensShadingTableRequest: function() {
axios.post(this.recalibrationLinks.flatten_lens_shading_table.href);
},
deleteLensShadingTableRequest: function() {
axios.post(this.recalibrationLinks.delete_lens_shading_table.href);
}
}
};
</script>
<style lang="less">
.center-spinner {
margin-left: auto;
margin-right: auto;
}
</style>
<style lang="less"></style>

View file

@ -1,10 +1,5 @@
<template>
<div id="cameraStageMappingSettings">
<h3>Camera/stage mapping</h3>
<p>
Camera/stage mapping allows the stage to move relative to the camera view.
This enables functions like click-to-move, and more precise tile scans.
</p>
<form @submit.prevent="applyConfigRequest">
<!--Show auto calibrate if default plugin is enabled-->
<div v-if="'calibrate_xy' in recalibrationLinks" class="uk-margin-small">
@ -24,7 +19,7 @@
</div>
<button
v-if="'get_calibration' in recalibrationLinks"
v-show="dataAvailable"
v-show="dataAvailable && showExtraSettings"
type="button"
class="uk-button uk-button-default uk-width-1-1"
@click="getCalibrationData()"
@ -47,6 +42,14 @@ export default {
taskSubmitter
},
props: {
showExtraSettings: {
type: Boolean,
required: false,
default: true
}
},
data: function() {
return {
settings: null,

View file

@ -110,6 +110,12 @@
:current-tab="currentTab"
>
<div class="settings-pane uk-padding-small">
<h3>Camera/stage mapping</h3>
<p>
Camera/stage mapping allows the stage to move relative to the camera
view. This enables functions like click-to-move, and more precise
tile scans.
</p>
<cameraStageMappingSettings />
</div>
</tabContent>

View file

@ -32,7 +32,7 @@ Vue.mixin({
var showModal = function(resolve, reject) {
UIkit.modal
.confirm(modalText)
.confirm(modalText, { stack: true })
.then(
function() {
resolve();
@ -61,7 +61,8 @@ Vue.mixin({
},
modalDialog: function(title, message) {
UIkit.modal.dialog(`
UIkit.modal.dialog(
`
<button class="uk-modal-close-default" type="button" uk-close></button>
<div class="uk-modal-header">
<h2 class="uk-modal-title">${title}</h2>
@ -69,7 +70,9 @@ Vue.mixin({
<div class="uk-modal-body">
<p>${message}</p>
</div>
`);
`,
{ stack: true }
);
},
modalError: function(error) {
@ -104,6 +107,14 @@ Vue.mixin({
});
},
showModalElement: function(element) {
UIkit.modal(element).show();
},
hideModalElement: function(element) {
UIkit.modal(element).hide();
},
getLocalStorageObj: function(keyName) {
if (localStorage.getItem(keyName)) {
try {