Stage move API now works

The test plugin seems to have problems
calling setPosition more than once.
We don't understand this...

Co-authored-by: Kaspar Emanuel <kaspar@monostable.co.uk>
This commit is contained in:
Richard 2021-04-27 12:23:37 +01:00
parent 757ee216cf
commit 297908fbc8
4 changed files with 298 additions and 39 deletions

View file

@ -0,0 +1,131 @@
<config lang="json">
{
"name": "Script Editor",
"type": "web-worker",
"tags": [],
"ui": "",
"version": "0.1.0",
"cover": "",
"description": "A Script Editor for Controlling OpenFlexure Microscope with ImJoy",
"icon": "extension",
"inputs": null,
"outputs": null,
"api_version": "0.1.8",
"env": "",
"permissions": [],
"requirements": [],
"dependencies": []
}
</config>
<script lang="javascript">
class ImJoyPlugin {
async setup() {
api.log('initialized')
}
async run(ctx) {
let pluginInEditor, stopped, editorWindow;
const config = {lang: 'javascript'}
config.templates = [
{
name: "New",
url: null,
lang: 'javascript',
},
{
name: "Snap Image Template",
url: "./OpenFlexureSnapImageTemplate.imjoy.html",
lang: "html",
}
]
config.ui_elements = {
save: {
_rintf: true,
type: 'button',
label: "Save",
visible: false,
icon: "content-save",
callback(content) {
console.log(content)
}
},
run: {
_rintf: true,
type: 'button',
label: "Run",
icon: "play",
visible: true,
shortcut: 'Shift-Enter',
async callback(content) {
try {
editorWindow.setLoader(true);
editorWindow.updateUIElement('stop', {
visible: true
})
api.showProgress(0);
pluginInEditor = await api.getPlugin({src: content, hot_reloading: true})
if (stopped) {
pluginInEditor = null;
return;
}
if (pluginInEditor && pluginInEditor.run) {
return await pluginInEditor.run({
config: {},
data: {}
});
}
if (stopped) {
pluginInEditor = null;
return;
}
} catch (e) {
api.showMessage("Failed to load plugin, error: " + e.toString());
} finally {
editorWindow.updateUIElement('stop', {
visible: false
})
editorWindow.setLoader(false);
api.showProgress(100);
}
}
},
stop: {
_rintf: true,
type: 'button',
label: "Stop",
style: "color: #ff0080cf;",
icon: "stop",
visible: false,
async callback() {
stopped = true;
await editorWindow.setLoader(false);
await editorWindow.updateUIElement('stop', {
visible: false
})
}
},
export: {
_rintf: true,
type: 'button',
label: "Export",
icon: "file-download-outline",
visible: true,
async callback(content) {
const fileName = (pluginInEditor && pluginInEditor.config.name && pluginInEditor.config.name + '.imjoy.html') || config.name + '.imjoy.html' || "myPlugin.imjoy.html";
await api.exportFile(content, fileName);
}
}
}
editorWindow = await api.createWindow({
src: 'https://if.imjoy.io',
name: (ctx && ctx.data && ctx.data.name) ||'OpenFlexure Script Editor',
config,
data: {code: ctx && ctx.data && ctx.data.code}
})
}
}
api.export(new ImJoyPlugin())
</script>

View file

@ -0,0 +1,43 @@
<docs>
[TODO: write documentation for this plugin.]
</docs>
<config lang="json">
{
"name": "OpenFlexureSnapImageTemplate",
"type": "web-worker",
"tags": [],
"ui": "",
"version": "0.1.0",
"cover": "",
"description": "[TODO: describe this plugin with one sentence.]",
"icon": "extension",
"inputs": null,
"outputs": null,
"api_version": "0.1.8",
"env": "",
"permissions": [],
"requirements": [],
"dependencies": []
}
</config>
<script lang="javascript">
class ImJoyPlugin {
async setup() {
api.log('initialized')
}
async run(ctx) {
// get a list of service
const services = await api.getServices({name: "OpenFlexure"});
// get the first service
const ofm = services[0];
// snap an image
await ofm.snapImage();
console.log("snapped an image");
}
}
api.export(new ImJoyPlugin())
</script>

View file

@ -0,0 +1,48 @@
<docs>
[TODO: write documentation for this plugin.]
</docs>
<config lang="json">
{
"name": "Test OFM Stage",
"type": "web-worker",
"tags": [],
"ui": "",
"version": "0.1.0",
"cover": "",
"description": "Move the OFM stage to test the API works",
"icon": "extension",
"inputs": null,
"outputs": null,
"api_version": "0.1.8",
"env": "",
"permissions": [],
"requirements": [],
"dependencies": []
}
</config>
<script lang="javascript">
class ImJoyPlugin {
async setup() {
api.log('initialized')
}
async run(ctx) {
// get a list of service
const services = await api.getServices({name: "OpenFlexure"});
// get the first service
const ofm = services[0];
// snap an image
const start = await ofm.getXYZPosition();
console.log(`Starting position: ${start}`);
// move the Z axis up and down
await ofm.setPosition(start[2] + 100);
console.log(`Moved Z to ${await ofm.getPosition()}`);
await ofm.setPosition(start[2]);
console.log(`Moved Z to ${await ofm.getPosition()}`);
}
}
api.export(new ImJoyPlugin())
</script>

View file

@ -98,6 +98,21 @@ import * as imjoyCore from "imjoy-core";
import axios from "axios";
import UIkit from "uikit";
// TODO: move this to a module or something...
async function pollAction(response) {
// Poll an action until its status is completed
// TODO: raise an error if it fails or is cancelled
// For ease, this accepts and returns a response object from
// axios.get
// FIXME: this could be an infinite loop if the task is cancelled/errored
let r = response;
while ((r.data.status == "running") | (r.data.status == "pending")) {
await new Promise(resolve => setTimeout(resolve, 100));
r = await axios.get(r.data.href);
}
return r;
}
export default {
name: "ImJoyContent",
@ -123,6 +138,9 @@ export default {
},
moveActionUri: function() {
return `${this.$store.getters.baseUri}/api/v2/actions/stage/move`;
},
baseUri: function() {
return this.$store.getters.baseUri;
}
},
mounted() {
@ -168,7 +186,7 @@ export default {
}
});
imjoy.start({ workspace: "default" }).then(() => {
imjoy.start({ workspace: "default" }).then(async () => {
console.log("ImJoy started");
imjoy.event_bus.on("add_window", w => {
this.addWindow(w);
@ -176,8 +194,16 @@ export default {
this.setupMicroscopeAPI();
this.setupViewers();
// load the script editor for openflexure
this.loadPlugin(
"https://gist.githubusercontent.com/oeway/7a9dcb49c51b1f99b2c3619f470fe35c/raw/OpenFlexureScriptEditor.imjoy.html"
await this.loadPlugin(
`${window.location.origin}/OpenFlexureScriptEditor.imjoy.html`
);
// Load the snap image template
await this.loadPlugin(
`${window.location.origin}/OpenFlexureSnapImageTemplate.imjoy.html`
);
// Load the snap image template
await this.loadPlugin(
`${window.location.origin}/OpenFlexureTestMoveStage.imjoy.html`
);
this.setupPluginEngine();
});
@ -232,22 +258,10 @@ export default {
// See here: https://valelab4.ucsf.edu/~MM/doc/MMCore/html/class_c_m_m_core.html
const snapshotUri = this.snapshotStreamUri;
const captureActionUri = this.captureActionUri;
const getPositionUri = this.getPositionUri;
const moveActionUri = this.moveActionUri;
const modalError = this.modalError;
async function pollUntilComplete(response) {
// Poll an action until its status is completed
// TODO: raise an error if it fails or is cancelled
// For ease, this accepts and returns a response object from
// axios.get
// FIXME: this could be an infinite loop if the task is cancelled/errored
let r = response;
while (r.data.status == "running" | r.data.status == "pending") {
await new Promise(resolve => setTimeout(resolve, 100));
r = await axios.get(r.data.href);
}
return r;
}
const getPositionAsObject = this.getStagePosition;
const setPositionAsObject = this.setStagePosition;
const service = {
type: "_microscope-control",
name: "OpenFlexure",
@ -276,9 +290,7 @@ export default {
async getImage() {
// FIXME: handle nicely getImage() being called before snapImage()
// Wait until the capture has completed and we can retrieve it
service.lastSnapResponse = await pollUntilComplete(
service.lastSnapResponse
);
service.lastSnapResponse = await pollAction(service.lastSnapResponse);
let capture = service.lastSnapResponse.data.output;
// TODO: check links.download.href exists and is JPEG
let jpeg = await axios
@ -299,35 +311,37 @@ export default {
setPosition(z) {
// The C api suggests this defaults to just Z, and can accept a "label" to
// select the stage.
return service.setXYZPosition({
"z": z,
"absolute": true,
return setPositionAsObject({
z: z,
absolute: true
});
},
async getPosition() {
const pos = await service.getXYZPosition();
return pos.z;
const {z} = await getPositionAsObject();
return z;
},
async setXYPosition(x, y) {
return service.setXYZPosition({
"x": x,
"y": y,
"absolute": true,
return setPositionAsObject({
x: x,
y: y,
absolute: true
});
},
async getXYPosition() {
const pos = await service.getXYZPosition();
return [pos.x, pos.y];
const {x, y} = await getPositionAsObject();
return [x, y];
},
async getXYZPosition() {
return axios
.get(getPositionUri)
.then(r => r.data)
const {x, y, z} = await getPositionAsObject();
return [x, y, z];
},
async setXYZPosition(payload) {
return axios
.post(moveActionUri, payload)
.then(pollUntilComplete)
async setXYZPosition(x, y, z) {
return setPositionAsObject({
x: x,
y: y,
z: z,
absolute: true
});
}
};
this.imjoy.pm.registerService({ name: "openflexure" }, service);
@ -463,6 +477,29 @@ export default {
if (uri) {
this.loadPlugin(uri);
}
} /*
async getPosition() {
const pos = await service.getXYZPosition();
return pos.z;
},
async setXYPosition(x, y) {
return service.setXYZPosition({
"x": x,
"y": y,
"absolute": true,
});
},
async getXYPosition() {
const pos = await service.getXYZPosition();
return [pos.x, pos.y];
},*/,
async getStagePosition() {
return axios.get(this.getPositionUri).then(r => r.data);
},
async setStagePosition(payload) {
return axios
.post(this.moveActionUri, payload)
.then(pollAction)
}
}
};