Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/instamatic/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ def one_cycle(tilt: float = 5, sign=1) -> list:
# self.stage.z = 0 # for testing

zc = self.stage.z
print(f'Current z = {zc:.1f} nm')
print(f'Current z = {zc} nm')

zs = zc + np.linspace(-dz, dz, steps)
shifts = []
Expand Down
17 changes: 14 additions & 3 deletions src/instamatic/microscope/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Tuple
from typing import Optional, Tuple

from instamatic.microscope.utils import StagePositionTuple
from instamatic.typing import float_deg, int_nm


class MicroscopeBase(ABC):
Expand Down Expand Up @@ -90,7 +93,7 @@ def getSpotSize(self) -> int:
pass

@abstractmethod
def getStagePosition(self) -> Tuple[int, int, int, int, int]:
def getStagePosition(self) -> StagePositionTuple:
pass

@abstractmethod
Expand Down Expand Up @@ -178,7 +181,15 @@ def setSpotSize(self, value: int) -> None:
pass

@abstractmethod
def setStagePosition(self, x: int, y: int, z: int, a: int, b: int, wait: bool) -> None:
def setStagePosition(
self,
x: Optional[int_nm],
y: Optional[int_nm],
z: Optional[int_nm],
a: Optional[float_deg],
b: Optional[float_deg],
wait: bool,
) -> None:
pass

@abstractmethod
Expand Down
4 changes: 2 additions & 2 deletions src/instamatic/microscope/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import threading
import time
from functools import wraps
from typing import Any, Callable
from typing import Any, Callable, Dict

from instamatic import config
from instamatic.exceptions import TEMCommunicationError, exception_list
Expand Down Expand Up @@ -92,7 +92,7 @@ def wrapper(*args, **kwargs):

return wrapper

def _eval_dct(self, dct: dict[str, Any]) -> Any:
def _eval_dct(self, dct: Dict[str, Any]) -> Any:
"""Takes approximately 0.2-0.3 ms per call if HOST=='localhost'."""
self.s.send(dumper(dct))

Expand Down
132 changes: 76 additions & 56 deletions src/instamatic/microscope/components/stage.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
from __future__ import annotations

import time
from collections import namedtuple
from contextlib import contextmanager
from typing import Tuple
from typing import Optional, Tuple

import numpy as np

from instamatic.microscope.base import MicroscopeBase

# namedtuples to store results from .get()
StagePositionTuple = namedtuple('StagePositionTuple', ['x', 'y', 'z', 'a', 'b'])
from instamatic.microscope.utils import StagePositionTuple
from instamatic.typing import float_deg, int_nm


class Stage:
Expand All @@ -25,7 +23,7 @@ def __init__(self, tem: MicroscopeBase):

def __repr__(self):
x, y, z, a, b = self.get()
return f'{self.name}(x={x:.1f}, y={y:.1f}, z={z:.1f}, a={a:.1f}, b={b:.1f})'
return f'{self.name}(x={x}, y={y}, z={z}, a={a:.1f}, b={b:.1f})'

@property
def name(self) -> str:
Expand All @@ -34,23 +32,30 @@ def name(self) -> str:

def set(
self,
x: int = None,
y: int = None,
z: int = None,
a: int = None,
b: int = None,
x: Optional[int_nm] = None,
y: Optional[int_nm] = None,
z: Optional[int_nm] = None,
a: Optional[float_deg] = None,
b: Optional[float_deg] = None,
wait: bool = True,
) -> None:
"""Wait: bool, block until stage movement is complete (JEOL only)"""
self._setter(x, y, z, a, b, wait=wait)
self._setter(
round(x) if x is not None else None,
round(y) if y is not None else None,
round(z) if z is not None else None,
float(a) if a is not None else None,
float(b) if b is not None else None,
wait=wait,
)

def set_with_speed(
self,
x: int = None,
y: int = None,
z: int = None,
a: int = None,
b: int = None,
x: Optional[int_nm] = None,
y: Optional[int_nm] = None,
z: Optional[int_nm] = None,
a: Optional[float_deg] = None,
b: Optional[float_deg] = None,
wait: bool = True,
speed: float = 1.0,
) -> None:
Expand All @@ -59,7 +64,15 @@ def set_with_speed(
wait: ignored, but necessary for compatibility with JEOL API
speed: float, set stage rotation with specified speed (FEI only)
"""
self._setter(x, y, z, a, b, wait=wait, speed=speed)
self._setter(
round(x) if x is not None else None,
round(y) if y is not None else None,
round(z) if z is not None else None,
float(a) if a is not None else None,
float(b) if b is not None else None,
wait=wait,
speed=speed,
)

def set_rotation_speed(self, speed=1) -> None:
"""Sets the stage (rotation) movement speed on the TEM."""
Expand Down Expand Up @@ -97,46 +110,45 @@ def rotating_speed(self, speed: int):
else:
yield # if requested speed is the same as current

def get(self) -> Tuple[int, int, int, int, int]:
"""Get stage positions; x, y, z, and status of the rotation axes; a,
b."""
def get(self) -> StagePositionTuple:
"""Get stage positions x, y, z in nm and rotation axes a, b in deg."""
return StagePositionTuple(*self._getter())

@property
def x(self) -> int:
"""X position."""
def x(self) -> int_nm:
"""Stage position X expressed in nm."""
x, y, z, a, b = self.get()
return x

@x.setter
def x(self, value: int):
def x(self, value: int_nm) -> None:
self.set(x=value, wait=self._wait)

@property
def y(self) -> int:
"""Y position."""
def y(self) -> int_nm:
"""Stage position Y expressed in nm."""
x, y, z, a, b = self.get()
return y

@y.setter
def y(self, value: int):
def y(self, value: int_nm) -> None:
self.set(y=value, wait=self._wait)

@property
def xy(self) -> Tuple[int, int]:
"""XY position as a tuple."""
def xy(self) -> Tuple[int_nm, int_nm]:
"""Stage position XY expressed as a tuple in nm."""
x, y, z, a, b = self.get()
return x, y

@xy.setter
def xy(self, values: Tuple[int, int]):
def xy(self, values: Tuple[int_nm, int_nm]) -> None:
x, y = values
self.set(x=x, y=y, wait=self._wait)

def move_in_projection(self, delta_x: int, delta_y: int) -> None:
def move_in_projection(self, delta_x: int_nm, delta_y: int_nm) -> None:
r"""Y and z are always perpendicular to the sample stage. To achieve the
movement in the projection, x and yshould be broken down into the
components z' and y'.
movement in the projection plane instead, x and y should be broken down
into the components z' and y'.

y = y' * cos(a)
z = y' * sin(a)
Expand All @@ -155,7 +167,7 @@ def move_in_projection(self, delta_x: int, delta_y: int) -> None:
z = z - delta_y * np.sin(a)
self.set(x=x, y=y, z=z)

def move_along_optical_axis(self, delta_z: int):
def move_along_optical_axis(self, delta_z: int_nm) -> None:
"""See `Stage.move_in_projection`"""
x, y, z, a, b = self.get()
a = np.radians(a)
Expand All @@ -164,38 +176,38 @@ def move_along_optical_axis(self, delta_z: int):
self.set(y=y, z=z)

@property
def z(self) -> int:
"""Stage height Z."""
def z(self) -> int_nm:
"""Stage height Z expressed in nm."""
x, y, z, a, b = self.get()
return z

@z.setter
def z(self, value: int):
def z(self, value: int_nm) -> None:
self.set(z=value, wait=self._wait)

@property
def a(self) -> int:
"""Rotation angle."""
def a(self) -> float_deg:
"""Primary rotation angle alpha expressed in degrees."""
x, y, z, a, b = self.get()
return a

@a.setter
def a(self, value: int):
def a(self, value: float_deg) -> None:
self.set(a=value, wait=self._wait)

@property
def b(self) -> int:
"""Secondary rotation angle."""
def b(self) -> float_deg:
"""Secondary rotation angle beta expressed in degrees."""
x, y, z, a, b = self.get()
return b

@b.setter
def b(self, value: int):
def b(self, value: float_deg) -> None:
self.set(b=value, wait=self._wait)

def neutral(self) -> None:
"""Reset the position of the stage to the 0-position."""
self.set(x=0, y=0, z=0, a=0, b=0)
self.set(x=0, y=0, z=0, a=0.0, b=0.0)

def is_moving(self) -> bool:
"""Return 'True' if the stage is moving."""
Expand Down Expand Up @@ -224,7 +236,7 @@ def stop(self) -> None:
Stage.set."""
self._tem.stopStage()

def alpha_wobbler(self, delta: float = 5.0, event=None) -> None:
def alpha_wobbler(self, delta: float_deg = 5.0, event=None) -> None:
"""Tilt the stage by plus/minus the value of delta (degrees) If event
is not set, press Ctrl-C to interrupt."""

Expand All @@ -246,15 +258,19 @@ def alpha_wobbler(self, delta: float = 5.0, event=None) -> None:

print(f"Restoring 'alpha': {a_center:.2f}")
self.a = a_center
print(f'Print z={self.z:.2f}')
print(f'Print z={self.z}')

def relax_xy(self, step: int = 100) -> None:
"""Relax the stage by moving it in the opposite direction from the last
movement."""
pass

def set_xy_with_backlash_correction(
self, x: int = None, y: int = None, step: float = 10000, settle_delay: float = 0.200
self,
x: Optional[int_nm] = None,
y: Optional[int_nm] = None,
step: int_nm = 10000,
settle_delay: float = 0.200,
) -> None:
"""Move to new x/y position with backlash correction. This is done by
approaching the target x/y position always from the same direction.
Expand All @@ -277,9 +293,9 @@ def set_xy_with_backlash_correction(

def move_xy_with_backlash_correction(
self,
shift_x: int = None,
shift_y: int = None,
step: float = 5000,
shift_x: Optional[int_nm] = None,
shift_y: Optional[int_nm] = None,
step: int_nm = 5000,
settle_delay: float = 0.200,
wait=True,
) -> None:
Expand All @@ -305,7 +321,7 @@ def move_xy_with_backlash_correction(
target_x = stage.x + shift_x
if target_x > stage.x:
pre_x = stage.x - step
elif target_x < stage.x:
else: # if target_x < stage.x:
pre_x = stage.x + step
else:
pre_x = None
Expand All @@ -315,7 +331,7 @@ def move_xy_with_backlash_correction(
target_y = stage.y + shift_y
if target_y > stage.y:
pre_y = stage.y - step
elif target_y < stage.y:
else: # if target_y < stage.y:
pre_y = stage.y + step
else:
pre_y = None
Expand All @@ -329,12 +345,16 @@ def move_xy_with_backlash_correction(
if settle_delay:
time.sleep(settle_delay)

def eliminate_backlash_xy(self, step: float = 10000, settle_delay: float = 0.200) -> None:
def eliminate_backlash_xy(
self,
step: int_nm = 10000,
settle_delay: float = 0.200,
) -> None:
"""Eliminate backlash by in XY by moving the stage away from the
current position, and approaching it from the common direction. Uses
`set_xy_with_backlash_correction` internally.

step: float,
step: int,
stepsize in nm
settle_delay: float,
delay between movements in seconds to allow the stage to settle
Expand All @@ -346,8 +366,8 @@ def eliminate_backlash_xy(self, step: float = 10000, settle_delay: float = 0.200

def eliminate_backlash_a(
self,
target_angle: float = 0.0,
step: float = 1.0,
target_angle: float_deg = 0.0,
step: float_deg = 1.0,
n_steps: int = 3,
settle_delay: float = 0.200,
) -> None:
Expand Down
Loading