Rename js directory to webapp

This commit is contained in:
Kaspar Emanuel 2021-08-17 16:14:23 +01:00
parent 375bcf4e73
commit b2eb1e0f05
75 changed files with 5 additions and 5 deletions

View file

@ -0,0 +1,167 @@
<template>
<div
id="CSMSettings"
class="uk-grid uk-grid-divider uk-child-width-expand"
uk-grid
>
<div class="uk-width-large">
<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>
<CSMCalibrationSettings />
</div>
<div id="mini-stream">
<miniStreamDisplay />
</div>
</div>
</template>
<script>
import axios from "axios";
import CSMCalibrationSettings from "./CSMSettingsComponents/CSMCalibrationSettings.vue";
import miniStreamDisplay from "../../genericComponents/miniStreamDisplay.vue";
// Export main app
export default {
name: "CSMSettings",
components: {
CSMCalibrationSettings,
miniStreamDisplay
},
props: {
showExtraSettings: {
type: Boolean,
required: false,
default: true
}
},
data: function() {
return {
settings: null,
recalibrationLinks: {},
isCalibrating: false,
dataAvailable: false
};
},
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: {
updateSettings: function() {
// Update links
axios
.get(this.settingsUri)
.then(response => {
this.settings =
response.data.extensions["org.openflexure.camera_stage_mapping"];
})
.catch(error => {
this.modalError(error); // Let mixin handle error
this.settings = {};
});
},
updateCalibrationDataAvailability: function() {
if ("get_calibration" in this.recalibrationLinks) {
axios
.get(this.recalibrationLinks.get_calibration.href)
.then(response => {
if (Object.keys(response.data).length === 0) {
this.dataAvailable = false;
} else {
this.dataAvailable = true;
}
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
}
},
getCalibrationData: function() {
axios
.get(this.recalibrationLinks.get_calibration.href)
.then(response => {
if (response.data != {}) {
const data = JSON.stringify(response.data);
const url = window.URL.createObjectURL(new Blob([data]));
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "csm_calibration.json");
document.body.appendChild(link);
link.click();
}
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
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.camera_stage_mapping"
);
// if camera-stage mapping extension is enabled
if (foundExtension) {
// Get plugin action link
this.recalibrationLinks = foundExtension.links;
// Update whether calibration data is available
this.updateCalibrationDataAvailability();
} else {
this.recalibrationLinks = {};
}
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
onRecalibrateResponse: function() {
this.modalNotify("Finished stage-to-camera calibration.");
// Update local settings
this.updateSettings();
},
onRecalibrateError: function(error) {
this.modalError(error); // Let mixin handle error
}
}
};
</script>
<style lang="less">
.center-spinner {
margin-left: auto;
margin-right: auto;
}
#mini-stream {
min-width: 300px;
max-width: 600px;
text-align: center;
margin-left: auto;
margin-right: auto;
margin-top: 50px;
}
</style>

View file

@ -0,0 +1,162 @@
<template>
<div id="CSMCalibrationSettings">
<!--Show auto calibrate if default plugin is enabled-->
<div v-if="'calibrate_xy' in recalibrationLinks" class="uk-margin-small">
<taskSubmitter
:button-primary="true"
:can-terminate="false"
:requires-confirmation="true"
:confirmation-message="
'Start recalibration of the stage to the camera? This may take a while, and the microscope will be locked during this time.'
"
:submit-url="recalibrationLinks.calibrate_xy.href"
:submit-label="'Auto-Calibrate using camera'"
@response="onRecalibrateResponse"
@error="modalError"
>
</taskSubmitter>
</div>
<button
v-if="'get_calibration' in recalibrationLinks"
v-show="dataAvailable && showExtraSettings"
type="button"
class="uk-button uk-button-default uk-width-1-1"
@click="getCalibrationData()"
>
Download calibration data
</button>
</div>
</template>
<script>
import axios from "axios";
import taskSubmitter from "../../../genericComponents/taskSubmitter";
// Export main app
export default {
name: "CSMCalibrationSettings",
components: {
taskSubmitter
},
props: {
showExtraSettings: {
type: Boolean,
required: false,
default: true
}
},
data: function() {
return {
settings: null,
recalibrationLinks: {},
isCalibrating: false,
dataAvailable: false
};
},
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: {
updateSettings: function() {
// Update links
axios
.get(this.settingsUri)
.then(response => {
this.settings =
response.data.extensions["org.openflexure.camera_stage_mapping"];
})
.catch(error => {
this.modalError(error); // Let mixin handle error
this.settings = {};
});
},
updateCalibrationDataAvailability: function() {
if ("get_calibration" in this.recalibrationLinks) {
axios
.get(this.recalibrationLinks.get_calibration.href)
.then(response => {
if (Object.keys(response.data).length === 0) {
this.dataAvailable = false;
} else {
this.dataAvailable = true;
}
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
}
},
getCalibrationData: function() {
axios
.get(this.recalibrationLinks.get_calibration.href)
.then(response => {
if (response.data != {}) {
const data = JSON.stringify(response.data);
const url = window.URL.createObjectURL(new Blob([data]));
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "csm_calibration.json");
document.body.appendChild(link);
link.click();
}
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
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.camera_stage_mapping"
);
// if camera-stage mapping extension is enabled
if (foundExtension) {
// Get plugin action link
this.recalibrationLinks = foundExtension.links;
// Update whether calibration data is available
this.updateCalibrationDataAvailability();
} else {
this.recalibrationLinks = {};
}
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
onRecalibrateResponse: function() {
this.modalNotify("Finished stage-to-camera calibration.");
// Update local settings
this.updateSettings();
}
}
};
</script>
<style lang="less">
.center-spinner {
margin-left: auto;
margin-right: auto;
}
</style>

View file

@ -0,0 +1,50 @@
<template>
<div id="appSettings" class="uk-width-large">
<h3>Appearance</h3>
<p>
<label>
Theme
<select v-model="appTheme" class="uk-select">
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="system">System</option>
</select>
</label>
</p>
</div>
</template>
<script>
// Export main app
export default {
name: "AppSettings",
data: function() {
return {};
},
computed: {
appTheme: {
get() {
return this.$store.state.appTheme;
},
set(value) {
this.$store.commit("changeAppTheme", value);
}
}
},
watch: {
appTheme: function() {
this.setLocalStorageObj("appTheme", this.appTheme);
}
},
mounted() {
// Try loading settings from localStorage. If null, don't change.
this.appTheme = this.getLocalStorageObj("appTheme") || this.appTheme;
}
};
</script>
<style lang="less"></style>

View file

@ -0,0 +1,327 @@
<template>
<div id="cameraSettings">
<div class="uk-grid uk-grid-divider uk-child-width-expand" uk-grid>
<div class="uk-width-large">
<h3>Manual camera settings</h3>
<form @submit.prevent="applySettingsRequest">
<div class="uk-margin-small-bottom">
<ul uk-accordion="multiple: true">
<li class="uk-open">
<a class="uk-accordion-title" href="#">Pi Camera Settings</a>
<div class="uk-accordion-content">
<div v-if="picamera.shutter_speed !== undefined">
<label class="uk-form-label" for="form-stacked-text"
>Exposure time</label
>
<div class="uk-form-controls">
<input
v-model="picamera.shutter_speed"
class="uk-input uk-form-small"
type="number"
/>
</div>
</div>
<div v-if="picamera.analog_gain !== undefined">
<label class="uk-form-label" for="form-stacked-text"
>Analogue gain</label
>
<div class="uk-form-controls">
<input
v-model="picamera.analog_gain"
class="uk-input uk-form-small"
type="number"
step="0.0000000000000001"
/>
</div>
</div>
<div v-if="picamera.digital_gain !== undefined">
<label class="uk-form-label" for="form-stacked-text"
>Digital gain</label
>
<div class="uk-form-controls">
<input
v-model="picamera.digital_gain"
class="uk-input uk-form-small"
type="number"
step="0.0000000000000001"
/>
</div>
</div>
<div v-if="picamera.awb_gains !== undefined">
<label class="uk-form-label" for="form-stacked-text">
White Balance gains
</label>
<div class="uk-form-controls">
<label class="uk-form-label">R:</label>
<input
v-model="picamera.awb_gains[0]"
class="uk-input uk-form-small"
type="number"
step="0.0000000000000001"
/>
<label class="uk-form-label">B:</label>
<input
v-model="picamera.awb_gains[1]"
class="uk-input uk-form-small"
type="number"
step="0.0000000000000001"
/>
</div>
</div>
</div>
</li>
<li class="uk-open">
<a class="uk-accordion-title" href="#">Image Quality</a>
<div class="uk-accordion-content">
<div class="uk-child-width-1-2" uk-grid>
<div v-if="jpeg_quality !== undefined">
<label class="uk-form-label" for="form-stacked-text"
>JPEG capture quality (%)</label
>
<div class="uk-form-controls">
<input
v-model="jpeg_quality"
class="uk-input uk-form-small"
type="number"
step="1"
/>
</div>
</div>
<div v-if="stream_resolution !== undefined">
<label class="uk-form-label" for="form-stacked-text"
>Stream resolution</label
>
<select
v-model="stream_resolution"
class="uk-select uk-form-small"
>
<option
v-for="option in resolutionOptions"
:key="option.value[0]"
:value="option.value"
>
{{ option.text }}
</option>
</select>
</div>
</div>
</div>
</li>
<li>
<a class="uk-accordion-title" href="#">Advanced</a>
<div class="uk-accordion-content">
<div v-if="mjpeg_bitrate !== undefined">
<label class="uk-form-label" for="form-stacked-text"
>Camera bitrate</label
>
<select
v-model="mjpeg_bitrate"
class="uk-select uk-form-small"
>
<option
v-for="option in bitrateOptions"
:key="option.value"
:value="option.value"
>
{{ option.text }}
</option>
</select>
<p class="uk-margin-remove">
This option sets the target bitrate for camera
recordings.<br />
<b
>Limiting bitrate may impact fast-autofocus
reliability.</b
>
However is guaranteed to fix the live stream disappearing
for some highly detailed samples.
</p>
</div>
<div
v-if="picamera.framerate !== undefined"
class="uk-margin-top"
>
<label class="uk-form-label" for="form-stacked-text"
>Camera framerate</label
>
<select
v-model="picamera.framerate"
class="uk-select uk-form-small"
>
<option
v-for="option in framerateOptions"
:key="option.value"
:value="option.value"
>
{{ option.text }}
</option>
</select>
<p class="uk-margin-remove">
This option sets the framerate of the Pi Camera video
encoder. The actual stream framerate is determined by
network speed, and is limited by the encoder framerate.<br />
<b
>Lowering framerate may impact fast-autofocus
accuracy.</b
>
</p>
</div>
</div>
</li>
</ul>
</div>
<button
type="submit"
class="uk-button uk-button-primary uk-margin-small uk-width-1-1"
>
Apply Settings
</button>
</form>
<h3>Automatic calibration</h3>
<cameraCalibrationSettings></cameraCalibrationSettings>
</div>
<div id="mini-stream">
<miniStreamDisplay />
</div>
</div>
</div>
</template>
<script>
import axios from "axios";
import cameraCalibrationSettings from "./cameraSettingsComponents/cameraCalibrationSettings.vue";
import miniStreamDisplay from "../../genericComponents/miniStreamDisplay.vue";
// Export main app
export default {
name: "CameraSettings",
components: {
cameraCalibrationSettings,
miniStreamDisplay
},
data: function() {
return {
picamera: {
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 }
]
};
},
computed: {
settingsUri: function() {
return `${this.$store.getters.baseUri}/api/v2/instrument/settings`;
}
},
mounted() {
this.updateSettings();
},
methods: {
updateSettings: function() {
axios
.get(this.settingsUri)
.then(response => {
const cameraSettings = response.data.camera;
// Get base camera settings
this.mjpeg_bitrate = cameraSettings.mjpeg_bitrate;
this.jpeg_quality = cameraSettings.jpeg_quality;
this.stream_resolution = cameraSettings.stream_resolution;
// Get Pi Camera settings if they exist
if (cameraSettings.picamera) {
this.picamera.analog_gain = cameraSettings.picamera.analog_gain;
this.picamera.digital_gain = cameraSettings.picamera.digital_gain;
this.picamera.shutter_speed = cameraSettings.picamera.shutter_speed;
this.picamera.framerate = cameraSettings.picamera.framerate;
this.picamera.awb_gains = cameraSettings.picamera.awb_gains;
}
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
applySettingsRequest: function() {
// We have to use parseInt/parseFloat because JS sometimes seems to
// make the numbers be strings... TypeScript would solve this...
var payload = {
camera: {
mjpeg_bitrate: parseInt(this.mjpeg_bitrate),
jpeg_quality: parseInt(this.jpeg_quality),
stream_resolution: this.stream_resolution,
picamera: {
shutter_speed: parseFloat(this.picamera.shutter_speed),
analog_gain: parseFloat(this.picamera.analog_gain),
digital_gain: parseFloat(this.picamera.digital_gain),
framerate: parseInt(this.picamera.framerate),
awb_gains: [
parseFloat(this.picamera.awb_gains[0]),
parseFloat(this.picamera.awb_gains[1])
]
}
}
};
console.log(payload);
// Send request
axios
.put(this.settingsUri, payload)
.then(() => {
return new Promise(r => setTimeout(r, 500));
}) // why is there no built-in for this??!
.then(() => {
// Update local settings
this.updateSettings();
this.modalNotify("Camera settings applied.");
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
}
}
};
</script>
<style lang="less">
#mini-stream {
min-width: 300px;
max-width: 600px;
text-align: center;
margin-left: auto;
margin-right: auto;
margin-top: 50px;
}
</style>

View file

@ -0,0 +1,179 @@
<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="'Full Auto-Calibrate'"
@response="onRecalibrateResponse"
@error="modalError"
>
</taskSubmitter>
</div>
<div
v-if="'auto_exposure_from_raw' in recalibrationLinks"
class="uk-margin-small"
>
<taskSubmitter
:can-terminate="false"
:requires-confirmation="false"
:submit-url="recalibrationLinks.auto_exposure_from_raw.href"
:submit-label="'Auto gain &amp; shutter speed'"
@response="onRecalibrateResponse"
@error="modalError"
>
</taskSubmitter>
</div>
<div
v-if="'auto_white_balance_from_raw' in recalibrationLinks"
class="uk-margin-small"
>
<taskSubmitter
:can-terminate="false"
:requires-confirmation="false"
:submit-url="recalibrationLinks.auto_white_balance_from_raw.href"
:submit-label="'Auto white balance'"
@response="onRecalibrateResponse"
@error="modalError"
>
</taskSubmitter>
</div>
<div
v-if="'auto_lens_shading_table' in recalibrationLinks"
class="uk-margin-small"
>
<taskSubmitter
:can-terminate="false"
:requires-confirmation="true"
:confirmation-message="
'Is the microscope looking at an evenly illuminated, empty field of view? ' +
'If not, the current image will show through in any images captured afterwards.'
"
:submit-url="recalibrationLinks.auto_lens_shading_table.href"
:submit-label="'Auto flat field correction'"
@response="onRecalibrateResponse"
@error="modalError"
>
</taskSubmitter>
</div>
<div v-show="showExtraSettings" class="uk-child-width-expand">
<button
v-if="'flatten_lens_shading_table' in recalibrationLinks"
class="uk-button uk-button-danger uk-width-1-1"
@click="flattenLensShadingTableRequest"
>
Disable flat field correction
</button>
</div>
<div v-if="LstDownloadEnabled">
<a
class="uk-button uk-button-default uk-width-large uk-margin-small-top uk-align-center"
:href="LstDownloadUri"
download
>Download Lens-Shading Table</a
>
</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,
LstDownloadEnabled: false
};
},
computed: {
pluginsUri: function() {
return `${this.$store.getters.baseUri}/api/v2/extensions`;
},
LstDownloadUri: function() {
return `${this.$store.getters.baseUri}/api/v2/instrument/camera/lst`;
}
},
mounted() {
this.updateRecalibrationLinks();
this.checkLstDownload();
},
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
});
},
checkLstDownload: function() {
axios
.get(this.LstDownloadUri) // Get a list of plugins
.then(response => {
if (response.status === 200) {
this.LstDownloadEnabled = true;
} else {
this.LstDownloadEnabled = false;
}
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
onRecalibrateResponse: function() {
this.modalNotify("Finished recalibration.");
},
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

@ -0,0 +1,115 @@
<template>
<div id="appSettings" class="uk-width-large">
<h3>Additional features</h3>
<p class="uk-margin-small">
<label
><input v-model="IHIEnabled" class="uk-checkbox" type="checkbox" />
Enable IHI Slide Scan</label
>
</p>
<p class="uk-margin-small">
Enabling IHI Slide Scan will add a new tab designed to simplify acquiring
blood smear tile scans. <br />
While this may be useful for other applications, it is primarily designed
for use in clinics acquiring blood smear samples using a 100x
oil-immersion objective, with a Raspberry Pi Camera v2.
</p>
<p class="uk-margin-small" :class="{ 'uk-text-muted': !imjoyPermitted }">
<label :disabled="!imjoyPermitted">
<input
v-model="imjoyEnabled"
class="uk-checkbox"
type="checkbox"
:disabled="!imjoyPermitted"
/>
Enable ImJoy plugin engine
</label>
</p>
<p v-if="imjoyPermitted" class="uk-margin-small">
<a href="https://imjoy.io/">ImJoy</a> enables integration with a wide
variety of microscopy and image analysis applications, including ImageJ.JS
and Kaibu. Support for ImJoy within the OFM software is currently
experimental, and it may slow down loading of the application.
</p>
<p v-if="!imjoyPermitted" class="uk-margin-small uk-text-muted">
ImJoy plugins are disabled in this build of the OpenFlexure software.
</p>
<p class="uk-margin-small">
<label
><input v-model="galleryEnabled" class="uk-checkbox" type="checkbox" />
Enable Gallery</label
>
</p>
<p class="uk-margin-small">
The "gallery" tab can cause performance issues; un-tick the box above to
disable it.
</p>
</div>
</template>
<script>
// Export main app
export default {
name: "FeaturesSettings",
data: function() {
return {};
},
computed: {
IHIEnabled: {
get() {
return this.$store.state.IHIEnabled;
},
set(value) {
this.$store.commit("changeIHIEnabled", value);
}
},
imjoyEnabled: {
get() {
return this.$store.state.imjoyEnabled;
},
set(value) {
this.$store.commit("changeImjoyEnabled", value);
}
},
galleryEnabled: {
get() {
return this.$store.state.galleryEnabled;
},
set(value) {
this.$store.commit("changeGalleryEnabled", value);
}
},
imjoyPermitted: function() {
// ImJoy may be disabled using an environment variable.
// If this function returns false, it is never possible to
// use ImJoy, so we should gray out the option.
return process.env.VUE_APP_ENABLE_IMJOY === "true";
}
},
watch: {
IHIEnabled: function() {
this.setLocalStorageObj("IHIEnabled", this.IHIEnabled);
},
imjoyEnabled: function() {
this.setLocalStorageObj("imjoyEnabled", this.imjoyEnabled);
},
galleryEnabled: function() {
this.setLocalStorageObj("galleryEnabled", this.galleryEnabled);
}
},
mounted() {
// Try loading settings from localStorage. If null, don't change.
this.IHIEnabled = this.getLocalStorageObj("IHIEnabled") || this.IHIEnabled;
this.imjoyEnabled =
this.getLocalStorageObj("imjoyEnabled") || this.imjoyEnabled;
this.galleryEnabled =
this.getLocalStorageObj("galleryEnabled") || this.galleryEnabled;
}
};
</script>
<style lang="less"></style>

View file

@ -0,0 +1,89 @@
<template>
<div v-if="settings" id="microscopeSettings" class="uk-width-large">
<form @submit.prevent="applySettingsRequest">
<div>
<label class="uk-form-label" for="form-stacked-text"
>Microscope name</label
>
<input
v-model="settings.name"
class="uk-input uk-width-1-1 uk-form-small"
name="inputFilename"
placeholder="Leave blank for default"
/>
</div>
<br />
<button
type="submit"
class="uk-button uk-button-primary uk-margin-small uk-width-1-1"
>
Apply Settings
</button>
</form>
</div>
</template>
<script>
import axios from "axios";
// Export main app
export default {
name: "MicroscopeSettings",
data: function() {
return {
settings: null
};
},
computed: {
settingsUri: function() {
return `${this.$store.getters.baseUri}/api/v2/instrument/settings`;
}
},
mounted() {
this.updateSettings();
},
methods: {
updateSettings: function() {
axios
.get(this.settingsUri)
.then(response => {
this.settings = response.data;
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
applySettingsRequest: function() {
var payload = {
name: this.settings.name
};
// Send request to update config
axios
.put(this.settingsUri, payload)
.then(() => {
// Update local settings
this.updateSettings();
this.modalNotify("Microscope settings applied.");
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
}
}
};
</script>
<style lang="less">
.center-spinner {
margin-left: auto;
margin-right: auto;
}
</style>

View file

@ -0,0 +1,195 @@
<template>
<div id="stageSettings" class="uk-width-large">
<div v-if="stageType === 'MissingStage'" class="uk-text-danger">
<b>No stage connected</b>
</div>
<div v-else>
<h3>Stage settings</h3>
<form @submit.prevent="setStageType">
<div class="uk-margin-small-bottom">
<h4>Stage geometry</h4>
<select v-model="stageType" class="uk-select uk-margin-small-top">
<option value="SangaStage">SangaStage (Standard)</option>
<option value="SangaDeltaStage"> SangaStage (Delta)</option>
</select>
<button
type="submit"
class="uk-button uk-button-primary uk-margin-small uk-width-1-1"
>
Change stage geometry
</button>
</div>
</form>
<form @submit.prevent="applySettingsRequest">
<div class="uk-margin-small-bottom">
<h4>Backlash compensation</h4>
<p class="uk-margin-remove">
Backlash compentation causes movements to overshoot by that number
of motor steps, reducing the effect of mechanical backlash.
</p>
<div
class="uk-grid-small uk-child-width-1-3 uk-margin-small-bottom"
uk-grid
>
<div>
<label class="uk-form-label" for="form-stacked-text">x</label>
<div class="uk-form-controls">
<input
v-model="backlash.x"
class="uk-input uk-form-small"
type="number"
/>
</div>
</div>
<div>
<label class="uk-form-label" for="form-stacked-text">y</label>
<div class="uk-form-controls">
<input
v-model="backlash.y"
class="uk-input uk-form-small"
type="number"
/>
</div>
</div>
<div>
<label class="uk-form-label" for="form-stacked-text">z</label>
<div class="uk-form-controls">
<input
v-model="backlash.z"
class="uk-input uk-form-small"
type="number"
/>
</div>
</div>
</div>
<h4>Settle time</h4>
<div v-if="settle_time !== undefined">
<p class="uk-margin-small-bottom">
When moving or scanning, captures will be delayed by the settle
time (in seconds) to reduce motion blur.
</p>
<div class="uk-form-controls">
<input
v-model="settle_time"
class="uk-input uk-form-small"
type="number"
step="0.1"
/>
</div>
</div>
<button
type="submit"
class="uk-button uk-button-primary uk-margin-small uk-width-1-1"
>
Apply Settings
</button>
</div>
</form>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "StageSettings",
components: {},
data: function() {
return {
stageType: "MissingStage",
backlash: {
x: undefined,
y: undefined,
z: undefined
},
settle_time: undefined
};
},
computed: {
settingsUri: function() {
return `${this.$store.getters.baseUri}/api/v2/instrument/settings`;
},
stageTypeUri: function() {
return `${this.$store.getters.baseUri}/api/v2/instrument/stage/type`;
}
},
mounted() {
this.getStageType();
this.updateSettings();
},
methods: {
updateSettings: function() {
axios
.get(this.settingsUri)
.then(response => {
const stageSettings = response.data.stage;
this.backlash.x = stageSettings.backlash.x;
this.backlash.y = stageSettings.backlash.y;
this.backlash.z = stageSettings.backlash.z;
this.settle_time = stageSettings.settle_time;
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
applySettingsRequest: function() {
var payload = {
stage: {
backlash: this.backlash
}
};
// Send request to update settings
axios
.put(this.settingsUri, payload)
.then(() => {
// Update local settings
this.updateSettings();
this.modalNotify("Stage settings applied.");
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
getStageType: function() {
axios
.get(this.stageTypeUri)
.then(response => {
this.stageType = response.data;
})
.catch(error => {
this.modalError(error);
});
},
setStageType: function() {
axios
.put(this.stageTypeUri, this.stageType, {
headers: {
"Content-Type": "application/json"
}
})
.then(response => {
this.stageType = response.data;
this.modalNotify("Stage geometry changed.");
})
.catch(error => {
this.modalError(error);
});
}
}
};
</script>
<style lang="less"></style>

View file

@ -0,0 +1,132 @@
<template>
<div id="streamSettings" class="uk-width-large">
<div>
<h3>Stream settings</h3>
<label
><input v-model="disableStream" class="uk-checkbox" type="checkbox" />
Disable web stream</label
>
<p class="uk-margin-small">
This will disable the embedded web stream of the camera.
</p>
</div>
<br />
<div>
<h3>Microscope display output</h3>
<div uk-grid>
<div>
<label :class="[{ 'uk-disabled': !this.$store.getters.ready }]"
><input
v-model="autoGpuPreview"
class="uk-checkbox"
type="checkbox"
/>
Enable GPU preview</label
>
</div>
<div>
<label :class="[{ 'uk-disabled': !this.$store.getters.ready }]"
><input v-model="trackWindow" class="uk-checkbox" type="checkbox" />
Track window</label
>
</div>
</div>
<p>
Enable GPU preview turns on a low-latency camera preview drawn directly
to the Raspberry Pi display output.
</p>
<p class="uk-margin-small">
<b
>Content, such as your mouse, won't be visible behind this preview.</b
>
Track Window will attempt to resize the GPU preview based on the current
window size, however this is often imperfect and cannot track window
movement.
</p>
</div>
</div>
</template>
<script>
// Export main app
export default {
name: "StreamSettings",
data: function() {
return {};
},
computed: {
disableStream: {
get() {
return this.$store.state.disableStream;
},
set(value) {
this.$store.commit("changeDisableStream", value);
}
},
autoGpuPreview: {
get() {
return this.$store.state.autoGpuPreview;
},
set(value) {
// NB the stream viewer watches the store, and is
// responsible for making the request that switches
// GPU preview on/off
// see streamContent.vue
this.$store.commit("changeAutoGpuPreview", value);
}
},
trackWindow: {
get() {
return this.$store.state.trackWindow;
},
set(value) {
this.$store.commit("changeTrackWindow", value);
}
}
},
watch: {
// Cache the stream settings to local storage for persistence
// (the next 3 functions all relate to this)
disableStream: function(newValue) {
this.setLocalStorageObj("disableStream", newValue);
},
autoGpuPreview: function(newValue) {
this.setLocalStorageObj("autoGpuPreview", newValue);
},
trackWindow: function(newValue) {
this.setLocalStorageObj("trackWindow", newValue);
}
},
created() {
// Apply sensible defaults for stream settings, depending on
// whether we're connecting locally or remotely, respecting
// the settings that were cached previously.
const localMode = ["localhost", "0.0.0.0", "127.0.0.1", "[::1]"].includes(
window.location.hostname
);
const localDefaults = {
disableStream: true,
autoGpuPreview: true,
trackWindow: true
};
for (let k in localDefaults) {
if (localStorage.getItem(k) !== null) {
this[k] = this.getLocalStorageObj(k);
} else if (localMode) {
console.log(`${k} set to default value for a local connection`);
this[k] = localDefaults[k];
}
}
}
};
</script>
<style lang="less"></style>