diff --git a/src/openflexure_microscope_server/things/stage/__init__.py b/src/openflexure_microscope_server/things/stage/__init__.py
index 11c22097..f70eabfe 100644
--- a/src/openflexure_microscope_server/things/stage/__init__.py
+++ b/src/openflexure_microscope_server/things/stage/__init__.py
@@ -166,8 +166,14 @@ class BaseStage(lt.Thing):
)
"""Used to convert coordinates between the program frame and the hardware frame."""
- calibration_required: bool = lt.setting(default=False)
- """Whether calibration of the stage z axis is required."""
+ @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:
"""Update the position property from the stage."""
@@ -538,8 +544,3 @@ class BaseStage(lt.Thing):
This method provides the interface expected by the camera_stage_mapping.
"""
self.move_absolute(x=xyz_pos[0], y=xyz_pos[1], z=xyz_pos[2])
-
- @lt.action
- def mark_calibration_complete(self) -> None:
- """Mark stage calibration as complete."""
- self.calibration_required = False
diff --git a/src/openflexure_microscope_server/things/stage/sangaboard.py b/src/openflexure_microscope_server/things/stage/sangaboard.py
index ed34b41d..7ab3103f 100644
--- a/src/openflexure_microscope_server/things/stage/sangaboard.py
+++ b/src/openflexure_microscope_server/things/stage/sangaboard.py
@@ -80,6 +80,37 @@ class SangaboardThing(BaseStage):
)
"""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:
"""Read position from the stage and set internal attribute _hardware_position.
@@ -236,6 +267,3 @@ class SangaboardThing(BaseStage):
self._sangaboard.query(f"{led_command} {on_brightness}")
else:
self._sangaboard.query(f"{led_command} 0")
-
- calibration_required: bool = lt.setting(default=True)
- """By default, Sangaboard stage will require calibration of z direction."""
diff --git a/webapp/src/components/modalComponents/calibrationWizardComponents/zMotorDirectionStep.vue b/webapp/src/components/modalComponents/calibrationWizardComponents/zMotorDirectionStep.vue
index 23d73660..b0d97e4f 100644
--- a/webapp/src/components/modalComponents/calibrationWizardComponents/zMotorDirectionStep.vue
+++ b/webapp/src/components/modalComponents/calibrationWizardComponents/zMotorDirectionStep.vue
@@ -21,13 +21,13 @@
src="/direction_AC.png"
alt="Anti-clockwise"
class="dir-image clickable"
- @click="selectDir"
+ @click="reportDirection('anti-clockwise')"
/>
@@ -39,13 +39,13 @@
src="/direction_CW.png"
alt="Clockwise"
class="dir-image clickable"
- @click="triggerClockwise"
+ @click="reportDirection('clockwise')"
/>
@@ -68,7 +68,7 @@ export default {
};
},
- mounted() {
+ async mounted() {
this.checkCalibrationState();
},
@@ -92,20 +92,25 @@ export default {
}
this.invokeAction("stage", "jog", { stop: true });
},
- // Once a direction is selected, advance
- selectDir() {
- this.invokeAction("stage", "mark_calibration_complete").then(() => {
- this.$emit("advance");
- });
- },
- // If motor moves clockwise, invert z direction, then progress to next step
- triggerClockwise() {
- this.invokeAction("stage", "invert_axis_direction", { axis: "z" }).then(() => {
- this.selectDir();
- });
+ /**
+ * 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() {
- const needsCal = await this.readThingProperty("stage", "calibration_required");
+ 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);
},
},
diff --git a/webapp/src/tests/unit/overrides.js b/webapp/src/tests/unit/overrides.js
index 53797e78..90ebdcaa 100644
--- a/webapp/src/tests/unit/overrides.js
+++ b/webapp/src/tests/unit/overrides.js
@@ -133,4 +133,17 @@ export const componentOverrides = {
"cameraCalibrationSettings.vue": {
props: { cameraUri: "" },
},
+ "zMotorDirectionStep.vue": {
+ mocks: {
+ thingDescription: () => ({
+ properties: {
+ test_property_1: {},
+ test_property_2: {},
+ },
+ actions: {
+ calibrate_z_direction: {},
+ },
+ }),
+ },
+ },
};