Source code for pytemscript.modules.illumination

from typing import Union, List, Tuple
import math

from .extras import Vector
from ..utils.misc import RequestBody
from ..utils.enums import CondenserLensSystem, CondenserMode, DarkFieldMode, IlluminationMode


[docs] class Illumination: """ Illumination functions. """ __slots__ = ("__client", "__condenser_type", "__id") def __init__(self, client, condenser_type): self.__client = client self.__condenser_type = condenser_type self.__id = "tem.Illumination" @property def __has_3cond(self) -> bool: return self.__condenser_type == CondenserLensSystem.THREE_CONDENSER_LENSES.name @property def spotsize(self) -> int: """ Spotsize number, usually 1 to 11. (read/write) """ body = RequestBody(attr=self.__id + ".SpotsizeIndex", validator=int) return self.__client.call(method="get", body=body) @spotsize.setter def spotsize(self, value: int) -> None: if not (0 < int(value) < 12): raise ValueError("%s is outside of range 1-11" % value) body = RequestBody(attr=self.__id + ".SpotsizeIndex", value=value) self.__client.call(method="set", body=body) @property def intensity(self) -> float: """ Intensity / C2 condenser lens value. (read/write) """ body = RequestBody(attr=self.__id + ".Intensity", validator=float) return self.__client.call(method="get", body=body) @intensity.setter def intensity(self, value: float) -> None: if not (0.0 <= value <= 1.0): raise ValueError("%s is outside of range 0.0-1.0" % value) body = RequestBody(attr=self.__id + ".Intensity", value=value) self.__client.call(method="set", body=body) @property def intensity_zoom(self) -> bool: """ Intensity zoom (AutoZoom on Krios). Set to False to disable. (read/write) """ body = RequestBody(attr=self.__id + ".IntensityZoomEnabled", validator=bool) return self.__client.call(method="get", body=body) @intensity_zoom.setter def intensity_zoom(self, value: bool) -> None: body = RequestBody(attr=self.__id + ".IntensityZoomEnabled", value=bool(value)) self.__client.call(method="set", body=body) @property def intensity_limit(self) -> bool: """ Intensity limit. Set to False to disable. (read/write) """ if self.__has_3cond: raise NotImplementedError("Intensity limit exists only on 2-condenser lens systems.") else: body = RequestBody(attr=self.__id + ".IntensityLimitEnabled", validator=bool) return self.__client.call(method="get", body=body) @intensity_limit.setter def intensity_limit(self, value: bool) -> None: if self.__has_3cond: raise NotImplementedError("Intensity limit exists only on 2-condenser lens systems.") else: body = RequestBody(attr=self.__id + ".IntensityLimitEnabled", value=bool(value)) self.__client.call(method="set", body=body) @property def beam_shift(self) -> Vector: """ Beam shift X and Y in um. (read/write) """ shx = RequestBody(attr=self.__id + ".Shift.X", validator=float) shy = RequestBody(attr=self.__id + ".Shift.Y", validator=float) x = self.__client.call(method="get", body=shx) y = self.__client.call(method="get", body=shy) return Vector(x, y) * 1e6 @beam_shift.setter def beam_shift(self, vector: Union[Vector, List[float], Tuple[float, float]]) -> None: value = Vector.convert_to(vector) * 1e-6 body = RequestBody(attr=self.__id + ".Shift", value=value) self.__client.call(method="set", body=body) @property def rotation_center(self) -> Vector: """ Rotation center X and Y in mrad. (read/write) Depending on the scripting version, the values might need scaling by 6.0 to get mrads. """ rotx = RequestBody(attr=self.__id + ".RotationCenter.X", validator=float) roty = RequestBody(attr=self.__id + ".RotationCenter.Y", validator=float) x = self.__client.call(method="get", body=rotx) y = self.__client.call(method="get", body=roty) return Vector(x, y) * 1e3 @rotation_center.setter def rotation_center(self, vector: Union[Vector, List[float], Tuple[float, float]]) -> None: value = Vector.convert_to(vector) * 1e-3 body = RequestBody(attr=self.__id + ".RotationCenter", value=value) self.__client.call(method="set", body=body) @property def condenser_stigmator(self) -> Vector: """ C2 condenser stigmator X and Y. (read/write) """ stigx = RequestBody(attr=self.__id + ".CondenserStigmator.X", validator=float) stigy = RequestBody(attr=self.__id + ".CondenserStigmator.Y", validator=float) return Vector(self.__client.call(method="get", body=stigx), self.__client.call(method="get", body=stigy)) @condenser_stigmator.setter def condenser_stigmator(self, vector: Union[Vector, List[float], Tuple[float, float]]) -> None: value = Vector.convert_to(vector) value.set_limits(-1.0, 1.0) body = RequestBody(attr=self.__id + ".CondenserStigmator", value=value) self.__client.call(method="set", body=body) @property def illuminated_area(self) -> float: """ Illuminated area in um. Works only on 3-condenser lens systems. (read/write) """ if not self.__has_3cond: raise NotImplementedError("Illuminated area exists only on 3-condenser lens systems.") if self.condenser_mode == CondenserMode.PARALLEL.name: body = RequestBody(attr=self.__id + ".IlluminatedArea", validator=float) return self.__client.call(method="get", body=body) * 1e6 else: raise RuntimeError("Condenser is not in Parallel mode.") @illuminated_area.setter def illuminated_area(self, value: float) -> None: if not self.__has_3cond: raise NotImplementedError("Illuminated area exists only on 3-condenser lens systems.") if self.condenser_mode == CondenserMode.PARALLEL.name: body = RequestBody(attr=self.__id + ".IlluminatedArea", value=value*1e-6) self.__client.call(method="set", body=body) else: raise RuntimeError("Condenser is not in Parallel mode.") @property def probe_defocus(self) -> float: """ Probe defocus. Works only on 3-condenser lens systems in probe mode. """ if not self.__has_3cond: raise NotImplementedError("Probe defocus exists only on 3-condenser lens systems.") if self.condenser_mode == CondenserMode.PROBE.name: body = RequestBody(attr=self.__id + ".ProbeDefocus", validator=float) return self.__client.call(method="get", body=body) else: raise RuntimeError("Condenser is not in Probe mode.") @property def convergence_angle(self) -> float: """ Convergence angle. Works only on 3-condenser lens systems in probe mode. """ if not self.__has_3cond: raise NotImplementedError("Probe defocus exists only on 3-condenser lens systems.") if self.condenser_mode == CondenserMode.PROBE.name: body = RequestBody(attr=self.__id + ".ConvergenceAngle", validator=float) return self.__client.call(method="get", body=body) else: raise RuntimeError("Condenser is not in Probe mode.") @property def C3ImageDistanceParallelOffset(self) -> float: """ C3 image distance parallel offset. Works only on 3-condenser lens systems. (read/write) This value takes the place previously of the Intensity value. The Intensity value changed the focusing of the diffraction pattern at the back-focal plane (MF-Y in Beam Settings control panel) but was rather independent of the illumination optics. As such it changed the size of the illumination but the illuminated area parameter was not influenced. To get rid of this problematic bypass, the C3 image distance offset has been created which effectively does the same focusing but now from within the illumination optics so the illuminated area remains correct. The range is quite small, +/-0.02 """ if not self.__has_3cond: raise NotImplementedError("C3ImageDistanceParallelOffset exists only on 3-condenser lens systems.") if self.condenser_mode == CondenserMode.PARALLEL.name: body = RequestBody(attr=self.__id + ".C3ImageDistanceParallelOffset", validator=float) return self.__client.call(method="get", body=body) else: raise RuntimeError("Condenser is not in Probe mode.") @C3ImageDistanceParallelOffset.setter def C3ImageDistanceParallelOffset(self, value: float) -> None: if not self.__has_3cond: raise NotImplementedError("C3ImageDistanceParallelOffset exists only on 3-condenser lens systems.") if self.condenser_mode == CondenserMode.PARALLEL.name: body = RequestBody(attr=self.__id + ".C3ImageDistanceParallelOffset", value=value) self.__client.call(method="set", body=body) else: raise RuntimeError("Condenser is not in PARALLEL mode.") @property def mode(self) -> str: """ Illumination mode: microprobe or nanoprobe. IlluminationMode enum (read/write) (Nearly) no effect for low magnifications (LM). """ body = RequestBody(attr=self.__id + ".Mode", validator=int) result = self.__client.call(method="get", body=body) return IlluminationMode(result).name @mode.setter def mode(self, value: IlluminationMode) -> None: body = RequestBody(attr=self.__id + ".Mode", value=value) self.__client.call(method="set", body=body) @property def dark_field(self) -> str: """ Dark field mode: cartesian, conical or off. DarkFieldMode enum (read/write) """ body = RequestBody(attr=self.__id + ".DFMode", validator=int) result = self.__client.call(method="get", body=body) return DarkFieldMode(result).name @dark_field.setter def dark_field(self, value: DarkFieldMode) -> None: body = RequestBody(attr=self.__id + ".DFMode", value=value) self.__client.call(method="set", body=body) @property def condenser_mode(self) -> str: """ Mode of the illumination system: parallel or probe. CondenserMode enum (read/write) """ if self.__has_3cond: body = RequestBody(attr=self.__id + ".CondenserMode", validator=int) result = self.__client.call(method="get", body=body) return CondenserMode(result).name else: raise NotImplementedError("Condenser mode exists only on 3-condenser lens systems.") @condenser_mode.setter def condenser_mode(self, value: CondenserMode) -> None: if self.__has_3cond: body = RequestBody(attr=self.__id + ".CondenserMode", value=value) self.__client.call(method="set", body=body) else: raise NotImplementedError("Condenser mode can be changed only on 3-condenser lens systems.") @property def beam_tilt(self) -> Union[Vector, float]: """ Dark field beam tilt relative to the origin stored at alignment time. Only operational if dark field mode is active. Units: mrad, either in Cartesian (x,y) or polar (conical) tilt angles. The accuracy of the beam tilt physical units depends on a calibration of the tilt angles. (read/write) """ dfmode = RequestBody(attr=self.__id + ".DFMode", validator=int) dftiltx = RequestBody(attr=self.__id + ".Tilt.X", validator=float) dftilty = RequestBody(attr=self.__id + ".Tilt.Y", validator=float) mode = self.__client.call(method="get", body=dfmode) tiltx = self.__client.call(method="get", body=dftiltx) # rad tilty = self.__client.call(method="get", body=dftilty) # rad if mode == DarkFieldMode.CONICAL: tilt = tiltx rot = tilty return Vector(tilt * math.cos(rot), tilt * math.sin(rot)) * 1e3 elif mode == DarkFieldMode.CARTESIAN: return Vector(tiltx, tilty) * 1e3 else: # DF is off return Vector(0.0, 0.0) # Microscope might return nonsense if DFMode is OFF @beam_tilt.setter def beam_tilt(self, tilt: Union[Vector, float, List[float], Tuple[float, float]]) -> None: body = RequestBody(attr=self.__id + ".DFMode", validator=int) mode = self.__client.call(method="get", body=body) if isinstance(tilt, float): tilt = Vector(tilt, tilt) tilt = Vector.convert_to(tilt) * 1e-3 # mrad to rad if tilt == (0.0, 0.0): body = RequestBody(attr=self.__id + ".Tilt", value=tilt) self.__client.call(method="set", body=body) body = RequestBody(attr=self.__id + ".DFMode", value=DarkFieldMode.OFF) self.__client.call(method="set", body=body) elif mode == DarkFieldMode.CONICAL: value = Vector(math.sqrt(tilt.x ** 2 + tilt.y ** 2), math.atan2(tilt.y, tilt.x)) body = RequestBody(attr=self.__id + ".Tilt", value=value) self.__client.call(method="set", body=body) elif mode == DarkFieldMode.CARTESIAN: body = RequestBody(attr=self.__id + ".Tilt", value=tilt) self.__client.call(method="set", body=body) else: raise ValueError("Dark field mode is OFF. You cannot set beam tilt.")