From 18a4a9e560a25a240ab65656745e7c6b4c80ba5d Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 6 Aug 2019 18:00:23 +0100 Subject: [PATCH 1/5] Added method to zero positions --- openflexure_microscope/stage/sangaboard/sangaboard.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openflexure_microscope/stage/sangaboard/sangaboard.py b/openflexure_microscope/stage/sangaboard/sangaboard.py index b755d44b..33c8f3bd 100644 --- a/openflexure_microscope/stage/sangaboard/sangaboard.py +++ b/openflexure_microscope/stage/sangaboard/sangaboard.py @@ -174,6 +174,10 @@ class Sangaboard(ExtensibleSerialInstrument): """De-energise the stepper motor coils""" self.query("release") + def zero(self): + """Zero the stored motor positions""" + self.query("zero") + def move_abs(self, final, **kwargs): """Make an absolute move to a position From d12f0a53629300eb2be84ef30c7209395f593d70 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 6 Aug 2019 18:00:37 +0100 Subject: [PATCH 2/5] First draft of delta stage --- openflexure_microscope/api/app.py | 4 +- openflexure_microscope/stage/sanga.py | 82 ++++++++++++++++++++++++--- 2 files changed, 76 insertions(+), 10 deletions(-) diff --git a/openflexure_microscope/api/app.py b/openflexure_microscope/api/app.py index cbdfeb71..6f964dc5 100644 --- a/openflexure_microscope/api/app.py +++ b/openflexure_microscope/api/app.py @@ -32,7 +32,7 @@ except ImportError: logging.warning("Unable to import PiCameraStreamer") from openflexure_microscope.camera.mock import MockStreamer -from openflexure_microscope.stage.sanga import SangaStage +from openflexure_microscope.stage.sanga import SangaStage, SangaDeltaStage from openflexure_microscope.stage.mock import MockStage @@ -113,7 +113,7 @@ def attach_microscope(): # Initialise stage logging.debug("Creating stage object...") try: - api_stage = SangaStage() + api_stage = SangaDeltaStage() except Exception as e: logging.error(e) logging.warning("No valid stage hardware found. Falling back to mock stage!") diff --git a/openflexure_microscope/stage/sanga.py b/openflexure_microscope/stage/sanga.py index 9e4c25ec..cf1419cb 100644 --- a/openflexure_microscope/stage/sanga.py +++ b/openflexure_microscope/stage/sanga.py @@ -104,24 +104,26 @@ class SangaStage(BaseStage): return config + def steps_to_array(self, displacement, axis): + assert axis in self.axis_names, "axis must be one of {}".format(self.axis_names) + move = np.zeros(self.n_axes, dtype=np.int) + move[np.argmax(np.array(self.axis_names) == axis)] = int(displacement) + return move + def move_rel(self, displacement, axis=None, backlash=True): """Make a relative move, optionally correcting for backlash. displacement: integer or array/list of 3 integers axis: None (for 3-axis moves) or one of 'x','y','z' backlash: (default: True) whether to correct for backlash. """ + + if axis is not None: + displacement = self.steps_to_array(displacement, axis) + with self.lock: if not backlash or self.backlash is None: return self.board.move_rel(displacement, axis=axis) - if axis is not None: - # backlash correction is easier if we're always in 3D - # so this code just converts single-axis moves into all-axis moves. - assert axis in self.axis_names, "axis must be one of {}".format(self.axis_names) - move = np.zeros(self.n_axes, dtype=np.int) - move[np.argmax(np.array(self.axis_names) == axis)] = int(displacement) - displacement = move - initial_move = np.array(displacement, dtype=np.int) # Backlash Correction # This backlash correction strategy ensures we're always approaching the @@ -214,3 +216,67 @@ class SangaStage(BaseStage): print("A further exception occurred when resetting position: {}".format(e)) print("Move completed, raising exception...") raise value # Propagate the exception + + +class SangaDeltaStage(SangaStage): + def __init__(self, port=None, flex_h=80, flex_a=48, camera_angle=37.5, scale=10, **kwargs): + self.flex_h = flex_h + self.flex_a = flex_a + + # Set up camera rotation relative to stage + camera_theta = (camera_angle/180)*np.pi + self.R_camera = np.array([ + [np.cos(camera_theta), -np.sin(camera_theta), 0], + [np.sin(camera_theta), np.cos(camera_theta), 0], + [0, 0, 1] + ]) + + # Transformation matrix converting delta into cartesian + x_fac = np.divide(np.divide(2, np.sqrt(3)), self.flex_h) + y_fac = np.divide(1, self.flex_h) + z_fac = np.divide(1, self.flex_a) + + self.Tvd = np.array([ + [x_fac, -x_fac, 0], + [0.5 * y_fac, 0.5 * y_fac, -y_fac], + [z_fac, z_fac, z_fac] + ]) * scale + + self.Tdv = np.linalg.inv(self.Tvd) + + SangaStage.__init__(self, port=port, **kwargs) + + @property + def position(self): + # TODO: Account for camera rotation + position = np.dot(self.Tvd, self.board.position) + + position = np.dot(np.linalg.inv(self.R_camera), position) + return position.tolist() + + def move_rel(self, displacement, axis=None, backlash=True): + if axis is not None: + displacement = self.steps_to_array(displacement, axis) + + # Transform into camera coordinates + displacement = np.dot(self.R_camera, displacement) + + # Transform into delta coordinates + displacement = np.dot(self.Tdv, displacement) + + logging.debug("Delta displacement: {}".format(displacement)) + + # Do the move + SangaStage.move_rel(self, displacement, axis=None, backlash=backlash) + + def move_abs(self, final, **kwargs): + # Transform into camera coordinates + final = np.dot(self.R_camera, final) + + # Transform into delta coordinates + final = np.dot(self.Tdv, final) + + logging.debug("Delta final: {}".format(final)) + + # Do the move + SangaStage.move_abs(self, final, **kwargs) \ No newline at end of file From e1df36e8c52afd7b29ed2b5d03bc2ba1ee5d5900 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 7 Aug 2019 17:18:37 +0100 Subject: [PATCH 3/5] Improved step scaling --- openflexure_microscope/stage/sanga.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/openflexure_microscope/stage/sanga.py b/openflexure_microscope/stage/sanga.py index cf1419cb..90f11877 100644 --- a/openflexure_microscope/stage/sanga.py +++ b/openflexure_microscope/stage/sanga.py @@ -219,9 +219,10 @@ class SangaStage(BaseStage): class SangaDeltaStage(SangaStage): - def __init__(self, port=None, flex_h=80, flex_a=48, camera_angle=37.5, scale=10, **kwargs): + def __init__(self, port=None, flex_h=80, flex_a=50, flex_b=38, camera_angle=45, **kwargs): self.flex_h = flex_h self.flex_a = flex_a + self.flex_b = flex_b # Set up camera rotation relative to stage camera_theta = (camera_angle/180)*np.pi @@ -232,15 +233,15 @@ class SangaDeltaStage(SangaStage): ]) # Transformation matrix converting delta into cartesian - x_fac = np.divide(np.divide(2, np.sqrt(3)), self.flex_h) - y_fac = np.divide(1, self.flex_h) - z_fac = np.divide(1, self.flex_a) + x_fac = -1 * np.multiply(np.divide(2, np.sqrt(3)), np.divide(self.flex_b, self.flex_h)) + y_fac = -1 * np.divide(self.flex_b, self.flex_h) + z_fac = np.divide(self.flex_b, self.flex_a) self.Tvd = np.array([ [x_fac, -x_fac, 0], [0.5 * y_fac, 0.5 * y_fac, -y_fac], [z_fac, z_fac, z_fac] - ]) * scale + ]) self.Tdv = np.linalg.inv(self.Tvd) @@ -252,7 +253,7 @@ class SangaDeltaStage(SangaStage): position = np.dot(self.Tvd, self.board.position) position = np.dot(np.linalg.inv(self.R_camera), position) - return position.tolist() + return [int(p) for p in position] def move_rel(self, displacement, axis=None, backlash=True): if axis is not None: From 5e7e2abc62f9de0805a312b923ca4e9813052e7d Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 7 Aug 2019 17:38:08 +0100 Subject: [PATCH 4/5] Fixed broken z scaling --- openflexure_microscope/stage/sanga.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openflexure_microscope/stage/sanga.py b/openflexure_microscope/stage/sanga.py index 90f11877..01de2651 100644 --- a/openflexure_microscope/stage/sanga.py +++ b/openflexure_microscope/stage/sanga.py @@ -219,7 +219,7 @@ class SangaStage(BaseStage): class SangaDeltaStage(SangaStage): - def __init__(self, port=None, flex_h=80, flex_a=50, flex_b=38, camera_angle=45, **kwargs): + def __init__(self, port=None, flex_h=80, flex_a=50, flex_b=50, camera_angle=0, **kwargs): self.flex_h = flex_h self.flex_a = flex_a self.flex_b = flex_b @@ -235,15 +235,17 @@ class SangaDeltaStage(SangaStage): # Transformation matrix converting delta into cartesian x_fac = -1 * np.multiply(np.divide(2, np.sqrt(3)), np.divide(self.flex_b, self.flex_h)) y_fac = -1 * np.divide(self.flex_b, self.flex_h) - z_fac = np.divide(self.flex_b, self.flex_a) + z_fac = np.multiply(np.divide(1, 3), np.divide(self.flex_b, self.flex_a)) self.Tvd = np.array([ [x_fac, -x_fac, 0], [0.5 * y_fac, 0.5 * y_fac, -y_fac], [z_fac, z_fac, z_fac] ]) + logging.debug(self.Tvd) self.Tdv = np.linalg.inv(self.Tvd) + logging.debug(self.Tdv) SangaStage.__init__(self, port=port, **kwargs) From 6dcd6601c417989cf69ce857d0ceac0330718f47 Mon Sep 17 00:00:00 2001 From: Samuel McDermott Date: Sun, 6 Sep 2020 17:38:57 +0100 Subject: [PATCH 5/5] Include option for delta stage in microscope initialisation --- openflexure_microscope/microscope.py | 10 +++++++++- openflexure_microscope/stage/sanga.py | 11 +++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index 637d518c..d935afbf 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -12,7 +12,7 @@ from openflexure_microscope.captures import CaptureManager from openflexure_microscope.stage.mock import MissingStage from openflexure_microscope.camera.mock import MissingCamera -from openflexure_microscope.stage.sanga import SangaStage +from openflexure_microscope.stage.sanga import SangaStage, SangaDeltaStage try: from openflexure_microscope.camera.pi import PiCameraStreamer @@ -111,10 +111,18 @@ class Microscope: stage_port = configuration["stage"].get("port") if stage_type in ("SangaBoard", "SangaStage"): try: + logging.info("Trying SangaStage") self.stage = SangaStage(port=stage_port) except Exception as e: logging.error(e) logging.warning("No compatible Sangaboard hardware found.") + elif stage_type in ("SangaDeltaStage"): + try: + logging.info("Trying SangaDeltaStage") + self.stage = SangaDeltaStage(port=stage_port) + except Exception as e: + logging.error(e) + logging.warning("No compatible Sangaboard hardware found.") logging.info("Handling fallbacks") ### Fallbacks diff --git a/openflexure_microscope/stage/sanga.py b/openflexure_microscope/stage/sanga.py index 9cd8cf6b..cf5b063f 100644 --- a/openflexure_microscope/stage/sanga.py +++ b/openflexure_microscope/stage/sanga.py @@ -108,10 +108,6 @@ class SangaStage(BaseStage): axis: None (for 3-axis moves) or one of 'x','y','z' backlash: (default: True) whether to correct for backlash. """ - - if axis is not None: - displacement = self.steps_to_array(displacement, axis) - with self.lock: logging.debug(f"Moving sangaboard by {displacement}") if not backlash or self.backlash is None: @@ -193,7 +189,6 @@ class SangaStage(BaseStage): print("Move completed, raising exception...") raise value # Propagate the exception - class SangaDeltaStage(SangaStage): def __init__(self, port=None, flex_h=80, flex_a=50, flex_b=50, camera_angle=0, **kwargs): self.flex_h = flex_h @@ -208,13 +203,15 @@ class SangaDeltaStage(SangaStage): [0, 0, 1] ]) + logging.debug(self.R_camera) + # Transformation matrix converting delta into cartesian x_fac = -1 * np.multiply(np.divide(2, np.sqrt(3)), np.divide(self.flex_b, self.flex_h)) y_fac = -1 * np.divide(self.flex_b, self.flex_h) z_fac = np.multiply(np.divide(1, 3), np.divide(self.flex_b, self.flex_a)) self.Tvd = np.array([ - [x_fac, -x_fac, 0], + [-x_fac, x_fac, 0], [0.5 * y_fac, 0.5 * y_fac, -y_fac], [z_fac, z_fac, z_fac] ]) @@ -234,8 +231,6 @@ class SangaDeltaStage(SangaStage): return [int(p) for p in position] def move_rel(self, displacement, axis=None, backlash=True): - if axis is not None: - displacement = self.steps_to_array(displacement, axis) # Transform into camera coordinates displacement = np.dot(self.R_camera, displacement)