diff --git a/src/openflexure_microscope_server/things/stage/__init__.py b/src/openflexure_microscope_server/things/stage/__init__.py
index a42190c6..f70eabfe 100644
--- a/src/openflexure_microscope_server/things/stage/__init__.py
+++ b/src/openflexure_microscope_server/things/stage/__init__.py
@@ -166,6 +166,15 @@ class BaseStage(lt.Thing):
)
"""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:
"""Update the position property from the stage."""
# Copy the position before the move.
diff --git a/src/openflexure_microscope_server/things/stage/sangaboard.py b/src/openflexure_microscope_server/things/stage/sangaboard.py
index b8ad994c..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.
diff --git a/tests/unit_tests/test_stage.py b/tests/unit_tests/test_stage.py
index f922ef65..84c82305 100644
--- a/tests/unit_tests/test_stage.py
+++ b/tests/unit_tests/test_stage.py
@@ -603,10 +603,13 @@ def test_thing_description_equivalence(dummy_stage, mocker):
dummy_properties = set(dummy_td.properties.keys())
sanga_td = create_thing_without_server(SangaboardThing).thing_description()
- # Remove known extra actions
- sanga_actions = list(sanga_td.actions.keys())
- sanga_actions = set(sanga_actions)
- sanga_properties = set(sanga_td.properties.keys())
+
+ # Sangaboard has one extra action for calibrating z-motor direction. This is
+ # handled in the webapp calibration wizard, and an extra setting for persisting it
+ 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_properties == dummy_properties == base_properties
diff --git a/webapp/public/direction_AC.png b/webapp/public/direction_AC.png
new file mode 100644
index 00000000..214c0efd
Binary files /dev/null and b/webapp/public/direction_AC.png differ
diff --git a/webapp/public/direction_CW.png b/webapp/public/direction_CW.png
new file mode 100644
index 00000000..3758dc4b
Binary files /dev/null and b/webapp/public/direction_CW.png differ
diff --git a/webapp/src/components/modalComponents/calibrationWizard.vue b/webapp/src/components/modalComponents/calibrationWizard.vue
index 1e54ec6b..cc5993f0 100644
--- a/webapp/src/components/modalComponents/calibrationWizard.vue
+++ b/webapp/src/components/modalComponents/calibrationWizard.vue
@@ -28,6 +28,7 @@
diff --git a/webapp/src/components/modalComponents/calibrationWizardComponents/zMotorDirectionStep.vue b/webapp/src/components/modalComponents/calibrationWizardComponents/zMotorDirectionStep.vue
new file mode 100644
index 00000000..b0d97e4f
--- /dev/null
+++ b/webapp/src/components/modalComponents/calibrationWizardComponents/zMotorDirectionStep.vue
@@ -0,0 +1,148 @@
+
+
+
Look at the exposed z gear from above while holding the Turn z motor button.