From 274f698fab6571527f5c99efc51c1e288bed4e36 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Tue, 7 Jul 2026 15:11:30 +0100 Subject: [PATCH] Add z-direction calibration action to sangaboard --- .../things/stage/__init__.py | 15 +++---- .../things/stage/sangaboard.py | 34 ++++++++++++++-- .../zMotorDirectionStep.vue | 39 +++++++++++-------- webapp/src/tests/unit/overrides.js | 13 +++++++ 4 files changed, 74 insertions(+), 27 deletions(-) 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: {}, + }, + }), + }, + }, };