Source code for pytemscript.modules.stage

from functools import lru_cache
from typing import Dict, Optional
import math
import time
import logging

from ..utils.misc import RequestBody
from ..utils.enums import MeasurementUnitType, StageStatus, StageHolderType, StageAxes
from .extras import StageObj


[docs] class Stage: """ Stage functions. """ __slots__ = ("__client", "__id", "__err_msg") def __init__(self, client): self.__client = client self.__id = "tem.Stage" self.__err_msg = "Timeout. Stage is not ready" @property def _beta_available(self) -> bool: return self.limits['b']['unit'] != MeasurementUnitType.UNKNOWN.name def _wait_for_stage(self, tries: int = 10) -> None: """ Wait for stage to become ready. """ attempt = 0 while attempt < tries: body = RequestBody(attr=self.__id + ".Status", validator=int) if self.__client.call(method="get", body=body) != StageStatus.READY: logging.info("Stage is not ready, waiting..") tries += 1 time.sleep(1) else: break else: raise RuntimeError(self.__err_msg) def _change_position(self, direct: bool = False, relative: bool = False, speed: Optional[float] = None, **kwargs) -> None: """ Execute stage move to a new position. :param bool direct: use Goto instead of MoveTo :param bool relative: use relative coordinates :param float speed: Goto speed :param kwargs: new coordinates """ self._wait_for_stage(tries=5) if relative: current_pos = self.position for axis in kwargs: kwargs[axis] += current_pos[axis] # convert units to meters and radians new_coords = dict() for axis in 'xyz': if kwargs.get(axis) is not None: new_coords.update({axis: kwargs[axis] * 1e-6}) for axis in 'ab': if kwargs.get(axis) is not None: new_coords.update({axis: math.radians(kwargs[axis])}) if speed is not None and not (0.0 <= speed <= 1.0): raise ValueError("Speed must be within 0.0-1.0 range") if 'b' in new_coords and not self._beta_available: raise KeyError("B-axis is not available") limits = self.limits axes = 0 for key, value in new_coords.items(): if key not in 'xyzab': raise ValueError("Unexpected axis: %s" % key) if value < limits[key]['min'] or value > limits[key]['max']: raise ValueError('Stage position %s=%s is out of range' % (value, key)) axes |= getattr(StageAxes, key.upper()) # X and Y - 1000 to + 1000(micrometers) # Z - 375 to 375(micrometers) # a - 80 to + 80(degrees) # b - 29.7 to + 29.7(degrees) if not direct: body = RequestBody(attr=self.__id, obj_cls=StageObj, obj_method="set", axes=axes, method="MoveTo", **new_coords) self.__client.call(method="exec_special", body=body) else: if speed is not None: body = RequestBody(attr=self.__id, obj_cls=StageObj, obj_method="set", axes=axes, speed=speed, method="GoToWithSpeed", **new_coords) self.__client.call(method="exec_special", body=body) else: body = RequestBody(attr=self.__id, obj_cls=StageObj, obj_method="set", axes=axes, method="GoTo", **new_coords) self.__client.call(method="exec_special", body=body) self._wait_for_stage(tries=10) @property def status(self) -> str: """ The current state of the stage. StageStatus enum. """ body = RequestBody(attr=self.__id + ".Status", validator=int) result = self.__client.call(method="get", body=body) return StageStatus(result).name @property def holder(self) -> str: """ The current specimen holder type. StageHolderType enum. """ body = RequestBody(attr=self.__id + ".Holder", validator=int) result = self.__client.call(method="get", body=body) return StageHolderType(result).name @property def position(self) -> Dict: """ The current position of the stage (x,y,z in um and a,b in degrees). """ body = RequestBody(attr=self.__id + ".Position", validator=dict, obj_cls=StageObj, obj_method="get", a=True, b=self._beta_available) return self.__client.call(method="exec_special", body=body)
[docs] def go_to(self, relative: bool = False, speed: Optional[float] = None, **kwargs) -> None: """ Makes the holder directly go to the new position by moving all axes simultaneously. Keyword args can be x,y,z,a or b. (x,y,z in um and a,b in degrees) :param bool relative: Use relative move instead of absolute position. :param float speed: fraction of the standard speed setting (max 1.0) """ self._change_position(direct=True, relative=relative, speed=speed, **kwargs)
[docs] def move_to(self, relative: bool = False, **kwargs) -> None: """ Makes the holder safely move to the new position. Keyword args can be x,y,z,a or b (x,y,z in um and a,b in degrees). :param bool relative: Use relative move instead of absolute position. """ self._change_position(relative=relative, **kwargs)
[docs] def reset_holder(self) -> None: """ Reset holder to zero position for all axis. """ self.go_to(x=0, y=0, z=0, a=0)
@property @lru_cache(maxsize=1) def limits(self) -> Dict: """ Returns a dict with stage move limits. """ body = RequestBody(attr=self.__id, validator=dict, obj_cls=StageObj, obj_method="limits") return self.__client.call(method="exec_special", body=body)