Merge branch 'stage-dir-calibrate' into 'v3'
Stage direction calibrate Closes #626 and #647 See merge request openflexure/openflexure-microscope-server!633
This commit is contained in:
commit
6e19c73dd3
10 changed files with 240 additions and 4 deletions
|
|
@ -166,6 +166,15 @@ class BaseStage(lt.Thing):
|
||||||
)
|
)
|
||||||
"""Used to convert coordinates between the program frame and the hardware frame."""
|
"""Used to convert coordinates between the program frame and the hardware frame."""
|
||||||
|
|
||||||
|
@lt.property
|
||||||
|
def calibration_required(self) -> bool:
|
||||||
|
"""Whether the stage requires calibration.
|
||||||
|
|
||||||
|
Currently the only calibration method for the stage is the z-gear rotation
|
||||||
|
direction in the sangaboard stage.
|
||||||
|
"""
|
||||||
|
return False
|
||||||
|
|
||||||
def update_position(self) -> None:
|
def update_position(self) -> None:
|
||||||
"""Update the position property from the stage."""
|
"""Update the position property from the stage."""
|
||||||
# Copy the position before the move.
|
# Copy the position before the move.
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,37 @@ class SangaboardThing(BaseStage):
|
||||||
)
|
)
|
||||||
"""Used to convert coordinates between the program frame and the hardware frame."""
|
"""Used to convert coordinates between the program frame and the hardware frame."""
|
||||||
|
|
||||||
|
z_axis_calibrated: bool = lt.setting(default=False, readonly=True)
|
||||||
|
|
||||||
|
@lt.property
|
||||||
|
def calibration_required(self) -> bool:
|
||||||
|
"""Whether the stage requires calibration.
|
||||||
|
|
||||||
|
Return False only if z-direction has been calibrated.
|
||||||
|
"""
|
||||||
|
return not self.z_axis_calibrated
|
||||||
|
|
||||||
|
@lt.action
|
||||||
|
def calibrate_z_direction(
|
||||||
|
self, positive_motion: Literal["clockwise", "anti-clockwise"]
|
||||||
|
) -> None:
|
||||||
|
"""Calibrate the z motor direction based on the gear rotation direction.
|
||||||
|
|
||||||
|
Positive z motion should turn the exposed gear anti-clockwise. Calling this
|
||||||
|
action marks the z motor as calibrated.
|
||||||
|
|
||||||
|
:param positive_motion: The direction the gear moved during pisitive z motion.
|
||||||
|
"""
|
||||||
|
if positive_motion == "clockwise":
|
||||||
|
# Wrong direction, mark as such.
|
||||||
|
self.invert_axis_direction(axis="z")
|
||||||
|
elif positive_motion != "anti-clockwise":
|
||||||
|
raise ValueError(
|
||||||
|
"Motion should be reported as either 'clockwise' or 'anti-clockwise'."
|
||||||
|
)
|
||||||
|
# Set z_axis_calibrated to true once this function is called.
|
||||||
|
self.z_axis_calibrated = True
|
||||||
|
|
||||||
def _hardware_update_position(self) -> None:
|
def _hardware_update_position(self) -> None:
|
||||||
"""Read position from the stage and set internal attribute _hardware_position.
|
"""Read position from the stage and set internal attribute _hardware_position.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -603,10 +603,13 @@ def test_thing_description_equivalence(dummy_stage, mocker):
|
||||||
dummy_properties = set(dummy_td.properties.keys())
|
dummy_properties = set(dummy_td.properties.keys())
|
||||||
|
|
||||||
sanga_td = create_thing_without_server(SangaboardThing).thing_description()
|
sanga_td = create_thing_without_server(SangaboardThing).thing_description()
|
||||||
# Remove known extra actions
|
|
||||||
sanga_actions = list(sanga_td.actions.keys())
|
# Sangaboard has one extra action for calibrating z-motor direction. This is
|
||||||
sanga_actions = set(sanga_actions)
|
# handled in the webapp calibration wizard, and an extra setting for persisting it
|
||||||
sanga_properties = set(sanga_td.properties.keys())
|
sanga_extra_actions = {"calibrate_z_direction"}
|
||||||
|
sanga_extra_properties = {"z_axis_calibrated"}
|
||||||
|
sanga_actions = set(sanga_td.actions.keys()) - sanga_extra_actions
|
||||||
|
sanga_properties = set(sanga_td.properties.keys()) - sanga_extra_properties
|
||||||
|
|
||||||
assert sanga_actions == dummy_actions == base_actions
|
assert sanga_actions == dummy_actions == base_actions
|
||||||
assert sanga_properties == dummy_properties == base_properties
|
assert sanga_properties == dummy_properties == base_properties
|
||||||
|
|
|
||||||
BIN
webapp/public/direction_AC.png
Normal file
BIN
webapp/public/direction_AC.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
webapp/public/direction_CW.png
Normal file
BIN
webapp/public/direction_CW.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
|
|
@ -28,6 +28,7 @@
|
||||||
<script>
|
<script>
|
||||||
import singleStepTask from "./calibrationWizardComponents/singleStepTask.vue";
|
import singleStepTask from "./calibrationWizardComponents/singleStepTask.vue";
|
||||||
import welcomeStep from "./calibrationWizardComponents/welcomeStep.vue";
|
import welcomeStep from "./calibrationWizardComponents/welcomeStep.vue";
|
||||||
|
import zMotorCalibrationTask from "./calibrationWizardComponents/zMotorCalibrationTask.vue";
|
||||||
import cameraCalibrationTask from "./calibrationWizardComponents/cameraCalibrationTask.vue";
|
import cameraCalibrationTask from "./calibrationWizardComponents/cameraCalibrationTask.vue";
|
||||||
import cameraStageMappingTask from "./calibrationWizardComponents/cameraStageMappingTask.vue";
|
import cameraStageMappingTask from "./calibrationWizardComponents/cameraStageMappingTask.vue";
|
||||||
import finalStep from "./calibrationWizardComponents/finalStep.vue";
|
import finalStep from "./calibrationWizardComponents/finalStep.vue";
|
||||||
|
|
@ -68,6 +69,7 @@ export default {
|
||||||
this.$refs["calibrationModalEl"].addEventListener("hidden", this.onHide);
|
this.$refs["calibrationModalEl"].addEventListener("hidden", this.onHide);
|
||||||
// Check which Things are available on mount.
|
// Check which Things are available on mount.
|
||||||
const allCalibrationTasks = {
|
const allCalibrationTasks = {
|
||||||
|
stage: markRaw(zMotorCalibrationTask),
|
||||||
camera: markRaw(cameraCalibrationTask),
|
camera: markRaw(cameraCalibrationTask),
|
||||||
camera_stage_mapping: markRaw(cameraStageMappingTask),
|
camera_stage_mapping: markRaw(cameraStageMappingTask),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ gets very confusing.
|
||||||
:key="stepIndex"
|
:key="stepIndex"
|
||||||
@awaiting-user="handleAwaitingUser"
|
@awaiting-user="handleAwaitingUser"
|
||||||
@prevent-navigation="handlePreventNavigation"
|
@prevent-navigation="handlePreventNavigation"
|
||||||
|
@advance="nextStep"
|
||||||
/>
|
/>
|
||||||
<p class="uk-text-right">
|
<p class="uk-text-right">
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<template>
|
||||||
|
<singleStepTask
|
||||||
|
title="Stage Calibration"
|
||||||
|
:step-component="zMotorDirectionStep"
|
||||||
|
@next="$emit('next')"
|
||||||
|
@back="$emit('back')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import singleStepTask from "./singleStepTask.vue";
|
||||||
|
import zMotorDirectionStep from "./zMotorDirectionStep.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ZMotorCalibrationTask",
|
||||||
|
|
||||||
|
components: {
|
||||||
|
singleStepTask,
|
||||||
|
},
|
||||||
|
|
||||||
|
emits: ["next", "back"],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
zMotorDirectionStep,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<p>Look at the exposed z gear from above while holding the <b>Turn z motor</b> button.</p>
|
||||||
|
<div class="uk-margin uk-flex uk-flex-center">
|
||||||
|
<button
|
||||||
|
class="uk-button uk-button-primary jog-z-btn"
|
||||||
|
type="button"
|
||||||
|
@pointerdown="jogZ"
|
||||||
|
@pointerup="jogZStop"
|
||||||
|
@pointercancel="jogZStop"
|
||||||
|
>
|
||||||
|
<span>Turn z motor</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p>Which way does the z gear turn?</p>
|
||||||
|
<!-- Direction selection -->
|
||||||
|
<div class="uk-margin uk-flex">
|
||||||
|
<!-- Anti-clockwise column -->
|
||||||
|
<div class="uk-width-1-2 dir-option">
|
||||||
|
<img
|
||||||
|
src="/direction_AC.png"
|
||||||
|
alt="Anti-clockwise"
|
||||||
|
class="dir-image clickable"
|
||||||
|
@click="reportDirection('anti-clockwise')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="uk-button uk-button-default uk-width-1-1 uk-button-primary"
|
||||||
|
type="button"
|
||||||
|
@click="reportDirection('anti-clockwise')"
|
||||||
|
>
|
||||||
|
Anti-clockwise
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Clockwise column -->
|
||||||
|
<div class="uk-width-1-2 dir-option">
|
||||||
|
<img
|
||||||
|
src="/direction_CW.png"
|
||||||
|
alt="Clockwise"
|
||||||
|
class="dir-image clickable"
|
||||||
|
@click="reportDirection('clockwise')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="uk-button uk-button-default uk-width-1-1 uk-button-primary"
|
||||||
|
type="button"
|
||||||
|
@click="reportDirection('clockwise')"
|
||||||
|
>
|
||||||
|
Clockwise
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "ZMotorDirectionStep",
|
||||||
|
|
||||||
|
emits: ["advance", "awaiting-user"],
|
||||||
|
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
jogIntervalId: null,
|
||||||
|
jogDistance: 100,
|
||||||
|
jogTime: 300,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async mounted() {
|
||||||
|
this.checkCalibrationState();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
jogZ(pointerEvent) {
|
||||||
|
if (pointerEvent.button !== 0) return;
|
||||||
|
|
||||||
|
if (this.jogIntervalId) {
|
||||||
|
clearInterval(this.jogIntervalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
pointerEvent.target.setPointerCapture(pointerEvent.pointerId);
|
||||||
|
|
||||||
|
let invokeJog = () => this.invokeAction("stage", "jog", { z: this.jogDistance });
|
||||||
|
invokeJog();
|
||||||
|
this.jogIntervalId = setInterval(invokeJog, this.jogTime);
|
||||||
|
},
|
||||||
|
jogZStop() {
|
||||||
|
if (this.jogIntervalId) {
|
||||||
|
clearInterval(this.jogIntervalId);
|
||||||
|
}
|
||||||
|
this.invokeAction("stage", "jog", { stop: true });
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Report the direction to the microscope then advance to next pane in wizard.
|
||||||
|
*/
|
||||||
|
async reportDirection(direction) {
|
||||||
|
await this.invokeAction("stage", "calibrate_z_direction", { positive_motion: direction });
|
||||||
|
this.$emit("advance");
|
||||||
|
},
|
||||||
|
async checkCalibrationState() {
|
||||||
|
let needsCal = await this.readThingProperty("stage", "calibration_required");
|
||||||
|
if (needsCal) {
|
||||||
|
// If it needs calibration check the relevant calibration action exists.
|
||||||
|
const actions = this.thingDescription("stage").actions;
|
||||||
|
if (!("calibrate_z_direction" in actions)) {
|
||||||
|
console.error(
|
||||||
|
"Stage is requesting calibration but has no 'calibrate_z_direction' action",
|
||||||
|
);
|
||||||
|
needsCal = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$emit("awaiting-user", needsCal);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.jog-z-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 30px;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dir-option {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dir-image {
|
||||||
|
width: 70%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dir-option button {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -133,4 +133,17 @@ export const componentOverrides = {
|
||||||
"cameraCalibrationSettings.vue": {
|
"cameraCalibrationSettings.vue": {
|
||||||
props: { cameraUri: "" },
|
props: { cameraUri: "" },
|
||||||
},
|
},
|
||||||
|
"zMotorDirectionStep.vue": {
|
||||||
|
mocks: {
|
||||||
|
thingDescription: () => ({
|
||||||
|
properties: {
|
||||||
|
test_property_1: {},
|
||||||
|
test_property_2: {},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
calibrate_z_direction: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue