Skip to content

hardware

Hardware

AutoGator has a number of optional hardware components that can be used to control the motion of the PIC chip and other aspects of the system.

This module serves the following purposes:

  • defines device interfaces for different types of hardware, such as lasers, motors, and other devices
  • defines concrete implementations of interfaces for specific hardware
  • tracks the "concept of motion" of the system as a whole
  • centralizes control of an AutoGator instance's available resources

CameraBase (HardwareDevice)

Base class for camera devices.

This class is used to control a camera.

Parameters:

Name Type Description Default
name str

The name of the camera.

required
Source code in autogator/hardware.py
class CameraBase(HardwareDevice):
    """
    Base class for camera devices.

    This class is used to control a camera.

    Parameters
    ----------
    name : str
        The name of the camera.
    """
    def __init__(self, name: str) -> None:
        self.name = name

    def get_frame(self) -> Any:
        """
        Take a picture.

        This method should take a picture from the camera.
        """
        log.info(f"Taking picture from {self.name}")

get_frame(self)

Take a picture.

This method should take a picture from the camera.

Source code in autogator/hardware.py
def get_frame(self) -> Any:
    """
    Take a picture.

    This method should take a picture from the camera.
    """
    log.info(f"Taking picture from {self.name}")

DataAcquisitionUnitBase (HardwareDevice)

A data acquisition unit.

This class is used to control a data acquisition unit.

Parameters:

Name Type Description Default
name str

The name of the data acquisition unit.

required
Source code in autogator/hardware.py
class DataAcquisitionUnitBase(HardwareDevice):
    """
    A data acquisition unit.

    This class is used to control a data acquisition unit.

    Parameters
    ----------
    name : str
        The name of the data acquisition unit.
    """
    def __init__(self, name: str) -> None:
        self.name = name

    def measure(self) -> Any:
        """
        Takes a single-shot measurement.

        Returns
        -------
        Any
            The measured data.
        """
        log.info(f"Measuring {self.name}")

    def acquire(self) -> None:
        """
        Asynchronous command to begin acquiring data.

        This method should acquire data from the data acquisition unit.
        """
        log.info(f"Acquiring data from {self.name}")

    def get_data(self) -> Any:
        """
        Gets the acquired data.

        Returns
        -------
        Any
            The acquired data.
        """
        log.info(f"Getting data from {self.name}")

acquire(self)

Asynchronous command to begin acquiring data.

This method should acquire data from the data acquisition unit.

Source code in autogator/hardware.py
def acquire(self) -> None:
    """
    Asynchronous command to begin acquiring data.

    This method should acquire data from the data acquisition unit.
    """
    log.info(f"Acquiring data from {self.name}")

get_data(self)

Gets the acquired data.

Returns:

Type Description
Any

The acquired data.

Source code in autogator/hardware.py
def get_data(self) -> Any:
    """
    Gets the acquired data.

    Returns
    -------
    Any
        The acquired data.
    """
    log.info(f"Getting data from {self.name}")

measure(self)

Takes a single-shot measurement.

Returns:

Type Description
Any

The measured data.

Source code in autogator/hardware.py
def measure(self) -> Any:
    """
    Takes a single-shot measurement.

    Returns
    -------
    Any
        The measured data.
    """
    log.info(f"Measuring {self.name}")

GenericPyroLabDevice (HardwareDevice)

A class that passes all function calls on to the PyroLab driver.

This class is used to pass all function calls and attribute requests on to a nonstandard PyroLab driver. This is useful for devices that are not implemented or described by the standard AutoGator API, such as custom, homebuilt hardware.

Parameters:

Name Type Description Default
pyroname str

The name of the PyroLab object as registered with the nameserver.

''
ns_host str

The hostname of the PyroLab nameserver (default "localhost").

'localhost'
ns_port int

The port of the PyroLab nameserver (default "9090").

9090
Source code in autogator/hardware.py
class GenericPyroLabDevice(HardwareDevice):
    """
    A class that passes all function calls on to the PyroLab driver.

    This class is used to pass all function calls and attribute requests on to
    a nonstandard PyroLab driver. This is useful for devices that are not
    implemented or described by the standard AutoGator API, such as custom,
    homebuilt hardware.

    Parameters
    ----------
    pyroname : str
        The name of the PyroLab object as registered with the nameserver.
    ns_host : str, optional
        The hostname of the PyroLab nameserver (default "localhost").
    ns_port : int, optional
        The port of the PyroLab nameserver (default "9090").
    """
    def __init__(self, pyroname: str = "", ns_host: str = "localhost", ns_port: int = 9090) -> None:
        # super().__init__(pyroname)
        with locate_ns(host=ns_host, port=ns_port) as ns:
            self.driver = Proxy(ns.lookup(pyroname))
            self.driver.autoconnect()

    def __getattr__(self, __name: str) -> Any:
        return self.driver.__getattr__(__name)

HardwareConfiguration (BaseSettings) pydantic-model

A class for storing the hardware configurations of an arbitrary driver.

Attributes:

Name Type Description
module str

The name of the module containing the driver (default "autogator.hardware"). AutoGator supports external drivers that implement AutoGator's HardwareDevice interface.

classname str

The name of the class implementing the driver, to be dynamically loaded from the module.

parameters dict

A dictionary of initialization parameters to pass to the driver. It is therefore best for drivers to have keyword-only initialization parameters.

Source code in autogator/hardware.py
class HardwareConfiguration(BaseSettings):
    """
    A class for storing the hardware configurations of an arbitrary driver.

    Attributes
    ----------
    module : str, optional
        The name of the module containing the driver (default
        "autogator.hardware"). AutoGator supports external drivers that 
        implement AutoGator's HardwareDevice interface.
    classname : str
        The name of the class implementing the driver, to be dynamically
        loaded from the module.
    parameters : dict, optional
        A dictionary of initialization parameters to pass to the driver.
        It is therefore best for drivers to have keyword-only initialization
        parameters.
    """
    module: str = "autogator.hardware"
    classname: str = ""
    parameters: Dict[str, Any] = {}

    def get_object(self) -> Type[HardwareDevice]:
        """
        Constructs and returns the instantiated driver object.

        Returns
        -------
        Type[HardwareDevice]
            The driver object.
        """
        log.debug(f"Attempting to load '{self.module}.{self.classname}'")
        try:
            mod = importlib.import_module(self.module)
            log.debug("Module found...")
            obj: HardwareDevice = getattr(mod, self.classname)
            log.debug("Class found...")
        except Exception as e:
            log.critical(e)
            raise e

        return obj(**self.parameters)

get_object(self)

Constructs and returns the instantiated driver object.

Returns:

Type Description
Type[autogator.hardware.HardwareDevice]

The driver object.

Source code in autogator/hardware.py
def get_object(self) -> Type[HardwareDevice]:
    """
    Constructs and returns the instantiated driver object.

    Returns
    -------
    Type[HardwareDevice]
        The driver object.
    """
    log.debug(f"Attempting to load '{self.module}.{self.classname}'")
    try:
        mod = importlib.import_module(self.module)
        log.debug("Module found...")
        obj: HardwareDevice = getattr(mod, self.classname)
        log.debug("Class found...")
    except Exception as e:
        log.critical(e)
        raise e

    return obj(**self.parameters)

HardwareDevice

Abstract base class for hardware devices.

This class is used to define the lowest level interface for hardware devices. It is not intended to be used directly, but rather to be subclassed by specific hardware devices.

AutoGator hardware devices mainly interact with drivers provided by external libraries. AutoGator does not aim to provide any of the low-level interfacing with hardware devices, but rather implements a higher-level interface that abstracts the underlying hardware drivers. In this way, the same AutoGator motion and control routines can be used to control different hardware with different APIs, simply because they must implement the same AutoGator interface.

AutoGator hardware devices keep a reference to the underlying hardware driver. While no AutoGator routine will make use of custom functionality, user-implemented Experiment classes which access the Stage object may want to call functions on the driver directly.

Attributes:

Name Type Description
driver object

A placeholder for a driver object. All objects provide the ability to directly access the underlying driver object.

Source code in autogator/hardware.py
class HardwareDevice:
    """
    Abstract base class for hardware devices.

    This class is used to define the lowest level interface for hardware
    devices. It is not intended to be used directly, but rather to be
    subclassed by specific hardware devices.

    AutoGator hardware devices mainly interact with drivers provided by
    external libraries. AutoGator does not aim to provide any of the low-level
    interfacing with hardware devices, but rather implements a higher-level
    interface that abstracts the underlying hardware drivers. In this way, the
    same AutoGator motion and control routines can be used to control different
    hardware with different APIs, simply because they must implement the same
    AutoGator interface.

    AutoGator hardware devices keep a reference to the underlying hardware
    driver. While no AutoGator routine will make use of custom functionality,
    user-implemented [`Experiment`][autogator.experiments.Experiment] classes
    which access the Stage object may want to call functions on the driver
    directly.

    Attributes
    ----------
    driver : object
        A placeholder for a driver object. All objects provide the ability to
        directly access the underlying driver object.
    """
    driver = None

    def __init__(self, *args, **kwargs) -> None:
        pass

KMTS25ELinearStage (LinearStageBase)

A linear motor.

Parameters:

Name Type Description Default
pyroname str

The name of the PyroLab object as registered with the nameserver.

''
ns_host str

The hostname of the PyroLab nameserver (default "localhost").

'localhost'
ns_port int

The port of the PyroLab nameserver (default "9090").

9090
Source code in autogator/hardware.py
class KMTS25ELinearStage(LinearStageBase):
    """
    A linear motor.

    Parameters
    ----------
    pyroname : str
        The name of the PyroLab object as registered with the nameserver.
    ns_host : str, optional
        The hostname of the PyroLab nameserver (default "localhost").
    ns_port : int, optional
        The port of the PyroLab nameserver (default "9090").
    """
    def __init__(self, pyroname: str = "", ns_host: str = "localhost", ns_port: int = 9090) -> None:
        super().__init__(pyroname)
        with locate_ns(host=ns_host, port=ns_port) as ns:
            self.driver = Proxy(ns.lookup(pyroname))
            self.driver.autoconnect()
        self._step_size = None

    @property
    def step_size(self) -> float:
        """The jog step size in mm."""
        return self._step_size

    @step_size.setter
    def step_size(self, step_size: float) -> None:
        if step_size != self._step_size:
            self.driver._pyroClaimOwnership()
            self.driver.jog_step_size = step_size
            self._step_size = step_size

    def move_to(self, position: float) -> None:
        """
        Moves to a new position.

        This motor adjusts for backlash; a given position will always be
        approached from the "negative" direction. That may require overshooting
        the commanded position in order to always approach it again from a
        consistent direction.

        If stepping in short steps, it is therefore most efficient to step from
        negative to positive values to avoid backlash adjustments on each step.

        Parameters
        ----------
        position : float
            The new position to move to.
        """
        self.driver._pyroClaimOwnership()
        if self._requires_backlash_adjustment(position):
            self.driver.move_to(position - (self.driver.backlash * 1.5))
        self.driver.move_to(position)

    def move_by(self, distance: float) -> None:
        """
        Jogs the motor by a fixed distance.

        Parameters
        ----------
        distance : float
            The distance to move the motor. A positive value will move the
            motor forward, and a negative value will move the motor backwards.
        """
        self.driver._pyroClaimOwnership()
        if np.abs(distance) != self.step_size:
            self.step_size = np.abs(distance)
        if distance > 0:
            self.driver.jog("forward")
        else:
            self.driver.jog("backward")

    def move_cont(self, direction: str) -> None:
        """
        Starts a continuous move in the specified direction.

        Parameters
        ----------
        direction : str
            The direction to move the motor, either "forward" or "backward".
        """
        self.driver._pyroClaimOwnership()
        self.driver.move_continuous(direction)

    def _requires_backlash_adjustment(self, position: float) -> bool:
        """
        Determine if the new position command needs to compensate for backlash.

        The ThorLabs linear stages have a small backlash distance. To ensure
        as accurate a reposition as possible when moving to the same location
        multiple times, the motor will always approach the position from the 
        same direction. This function determines whether that requires 
        overshooting the current position before reapproaching.

        Parameters
        ----------
        position : float
            The position to move to.

        Returns
        -------
        bool
            Whether backlash compensation is required.
        """
        if position < self.get_position():
            return True
        return False

    def stop(self) -> None:
        """
        Stop all motion.
        """
        self.driver._pyroClaimOwnership()
        self.driver.stop()

    def get_position(self) -> float:
        """
        Get the current position in millimeters.
        """
        self.driver._pyroClaimOwnership()
        return self.driver.get_position()

    def home(self) -> None:
        """
        Home the motor.
        """
        self.driver._pyroClaimOwnership()
        self.driver.go_home()

    def status(self) -> int:
        """
        Returns a nonzero value if the motor is busy.
        """
        pass

    def close(self) -> None:
        """
        This should close the connection to the cube

        Returns
        -------
        None
        """
        log.info(f"Closing LinearStageBase {self.name}")
        self.driver._pyroClaimOwnership()
        self.driver.close()

step_size: float property writable

The jog step size in mm.

close(self)

This should close the connection to the cube

Source code in autogator/hardware.py
def close(self) -> None:
    """
    This should close the connection to the cube

    Returns
    -------
    None
    """
    log.info(f"Closing LinearStageBase {self.name}")
    self.driver._pyroClaimOwnership()
    self.driver.close()

get_position(self)

Get the current position in millimeters.

Source code in autogator/hardware.py
def get_position(self) -> float:
    """
    Get the current position in millimeters.
    """
    self.driver._pyroClaimOwnership()
    return self.driver.get_position()

home(self)

Home the motor.

Source code in autogator/hardware.py
def home(self) -> None:
    """
    Home the motor.
    """
    self.driver._pyroClaimOwnership()
    self.driver.go_home()

move_by(self, distance)

Jogs the motor by a fixed distance.

Parameters:

Name Type Description Default
distance float

The distance to move the motor. A positive value will move the motor forward, and a negative value will move the motor backwards.

required
Source code in autogator/hardware.py
def move_by(self, distance: float) -> None:
    """
    Jogs the motor by a fixed distance.

    Parameters
    ----------
    distance : float
        The distance to move the motor. A positive value will move the
        motor forward, and a negative value will move the motor backwards.
    """
    self.driver._pyroClaimOwnership()
    if np.abs(distance) != self.step_size:
        self.step_size = np.abs(distance)
    if distance > 0:
        self.driver.jog("forward")
    else:
        self.driver.jog("backward")

move_cont(self, direction)

Starts a continuous move in the specified direction.

Parameters:

Name Type Description Default
direction str

The direction to move the motor, either "forward" or "backward".

required
Source code in autogator/hardware.py
def move_cont(self, direction: str) -> None:
    """
    Starts a continuous move in the specified direction.

    Parameters
    ----------
    direction : str
        The direction to move the motor, either "forward" or "backward".
    """
    self.driver._pyroClaimOwnership()
    self.driver.move_continuous(direction)

move_to(self, position)

Moves to a new position.

This motor adjusts for backlash; a given position will always be approached from the "negative" direction. That may require overshooting the commanded position in order to always approach it again from a consistent direction.

If stepping in short steps, it is therefore most efficient to step from negative to positive values to avoid backlash adjustments on each step.

Parameters:

Name Type Description Default
position float

The new position to move to.

required
Source code in autogator/hardware.py
def move_to(self, position: float) -> None:
    """
    Moves to a new position.

    This motor adjusts for backlash; a given position will always be
    approached from the "negative" direction. That may require overshooting
    the commanded position in order to always approach it again from a
    consistent direction.

    If stepping in short steps, it is therefore most efficient to step from
    negative to positive values to avoid backlash adjustments on each step.

    Parameters
    ----------
    position : float
        The new position to move to.
    """
    self.driver._pyroClaimOwnership()
    if self._requires_backlash_adjustment(position):
        self.driver.move_to(position - (self.driver.backlash * 1.5))
    self.driver.move_to(position)

status(self)

Returns a nonzero value if the motor is busy.

Source code in autogator/hardware.py
def status(self) -> int:
    """
    Returns a nonzero value if the motor is busy.
    """
    pass

stop(self)

Stop all motion.

Source code in autogator/hardware.py
def stop(self) -> None:
    """
    Stop all motion.
    """
    self.driver._pyroClaimOwnership()
    self.driver.stop()

LaserBase (HardwareDevice)

Base class for laser devices.

This class is used to control a laser.

Parameters:

Name Type Description Default
name str

The name of the laser.

required
Source code in autogator/hardware.py
class LaserBase(HardwareDevice):
    """
    Base class for laser devices.

    This class is used to control a laser.

    Parameters
    ----------
    name : str
        The name of the laser.
    """
    def __init__(self, name: str) -> None:
        self.name = name

    def on(self) -> None:
        """
        Turn the laser on.
        """
        log.info(f"Turning {self.name} on")

    def off(self) -> None:
        """
        Turn the laser off.
        """
        log.info(f"Turning {self.name} off")

    def power(self, power: float) -> None:
        """
        Set the power of the laser.

        Parameters
        ----------
        power : float
            The power of the laser.
        """
        log.info(f"Setting power of {self.name} to {power}")

    def wavelength(self, wavelength: float) -> None:
        """
        Set the wavelength of the laser.

        Parameters
        ----------
        wavelength : float
            The wavelength of the laser.
        """
        log.info(f"Setting wavelength of {self.name} to {wavelength}")

    def sweep(self) -> None:
        """
        Sweep the laser.
        """
        log.info(f"Sweeping {self.name}")

off(self)

Turn the laser off.

Source code in autogator/hardware.py
def off(self) -> None:
    """
    Turn the laser off.
    """
    log.info(f"Turning {self.name} off")

on(self)

Turn the laser on.

Source code in autogator/hardware.py
def on(self) -> None:
    """
    Turn the laser on.
    """
    log.info(f"Turning {self.name} on")

power(self, power)

Set the power of the laser.

Parameters:

Name Type Description Default
power float

The power of the laser.

required
Source code in autogator/hardware.py
def power(self, power: float) -> None:
    """
    Set the power of the laser.

    Parameters
    ----------
    power : float
        The power of the laser.
    """
    log.info(f"Setting power of {self.name} to {power}")

sweep(self)

Sweep the laser.

Source code in autogator/hardware.py
def sweep(self) -> None:
    """
    Sweep the laser.
    """
    log.info(f"Sweeping {self.name}")

wavelength(self, wavelength)

Set the wavelength of the laser.

Parameters:

Name Type Description Default
wavelength float

The wavelength of the laser.

required
Source code in autogator/hardware.py
def wavelength(self, wavelength: float) -> None:
    """
    Set the wavelength of the laser.

    Parameters
    ----------
    wavelength : float
        The wavelength of the laser.
    """
    log.info(f"Setting wavelength of {self.name} to {wavelength}")

LinearStageBase (HardwareDevice)

A linear motor.

This class is used to control a linear motor.

Parameters:

Name Type Description Default
name str

The name of the motor.

required
axis Tuple[float, float, float]

The axis of the motor as a 3-vector.

typing.Tuple[float, float, float]
Source code in autogator/hardware.py
class LinearStageBase(HardwareDevice):
    """
    A linear motor.

    This class is used to control a linear motor.

    Parameters
    ----------
    name : str
        The name of the motor.
    axis : Tuple[float, float, float]
        The axis of the motor as a 3-vector.
    """
    def __init__(self, name: str, axis=Tuple[float, float, float]) -> None:
        self.name = name
        self.axis = axis

    def move_to(self, position: float) -> None:
        """
        Move the motor to a position.

        Parameters
        ----------
        position : float
            The position to move the motor to.
        """
        log.info(f"Moving {self.name} to {position}")

    def move_by(self, distance: float) -> None:
        """
        Move the motor by a distance.

        Some motors have the capability for more accurate "jogs." This function
        exists to support that capability. Motors that don't have this
        capability can imitate its effects by simply using
        [`move_to()`][autogator.hardware.LinearStageBase.move_to].

        Parameters
        ----------
        distance : float
            The distance to move the motor. A positive value will move the
            motor forward, and a negative value will move the motor backwards.
        """
        log.info(f"Moving {self.name} by {distance}")

    def move_cont(self, direction: float) -> None:
        """
        Move the motor continuously in the specified direction.

        A positive value for direction will move the motor in its forward
        sense. A negative value will move the motor in its reverse sense. The
        magnitude of direction can optionally correlate with the speed of the
        move, if the motor supports it.

        Parameters
        ----------
        direction : float
            The direction and velocity with which to start moving the motor.
        """
        log.info(f"Moving {self.name} continuously in {direction}")

    def stop(self) -> None:
        """
        Stops all current motion.
        """
        log.info(f"Stopping {self.name}")

    def get_position(self) -> float:
        """
        Get the current position of the motor.

        Returns
        -------
        float
            The current position of the motor.
        """
        log.info(f"Getting position of {self.name}")
        return 0.0

    def home(self) -> None:
        """
        Move the motor to its home position, if supported.
        """
        log.info(f"Homing {self.name}")

    def status(self) -> int:
        """
        Whether the motor is doing something. Codes are device dependent.

        A status of 0 should always mean that the motor is idle and waiting
        for a command.

        Returns
        -------
        int
            The current status of the motor.
        """
        log.info(f"Getting status of {self.name}")
        return 0

    def close(self) -> None:
        """
        This should close the connection to the cube

        Returns
        -------
        None
        """
        log.info(f"Closing LinearStageBase {self.name}")

close(self)

This should close the connection to the cube

Source code in autogator/hardware.py
def close(self) -> None:
    """
    This should close the connection to the cube

    Returns
    -------
    None
    """
    log.info(f"Closing LinearStageBase {self.name}")

get_position(self)

Get the current position of the motor.

Returns:

Type Description
float

The current position of the motor.

Source code in autogator/hardware.py
def get_position(self) -> float:
    """
    Get the current position of the motor.

    Returns
    -------
    float
        The current position of the motor.
    """
    log.info(f"Getting position of {self.name}")
    return 0.0

home(self)

Move the motor to its home position, if supported.

Source code in autogator/hardware.py
def home(self) -> None:
    """
    Move the motor to its home position, if supported.
    """
    log.info(f"Homing {self.name}")

move_by(self, distance)

Move the motor by a distance.

Some motors have the capability for more accurate "jogs." This function exists to support that capability. Motors that don't have this capability can imitate its effects by simply using move_to().

Parameters:

Name Type Description Default
distance float

The distance to move the motor. A positive value will move the motor forward, and a negative value will move the motor backwards.

required
Source code in autogator/hardware.py
def move_by(self, distance: float) -> None:
    """
    Move the motor by a distance.

    Some motors have the capability for more accurate "jogs." This function
    exists to support that capability. Motors that don't have this
    capability can imitate its effects by simply using
    [`move_to()`][autogator.hardware.LinearStageBase.move_to].

    Parameters
    ----------
    distance : float
        The distance to move the motor. A positive value will move the
        motor forward, and a negative value will move the motor backwards.
    """
    log.info(f"Moving {self.name} by {distance}")

move_cont(self, direction)

Move the motor continuously in the specified direction.

A positive value for direction will move the motor in its forward sense. A negative value will move the motor in its reverse sense. The magnitude of direction can optionally correlate with the speed of the move, if the motor supports it.

Parameters:

Name Type Description Default
direction float

The direction and velocity with which to start moving the motor.

required
Source code in autogator/hardware.py
def move_cont(self, direction: float) -> None:
    """
    Move the motor continuously in the specified direction.

    A positive value for direction will move the motor in its forward
    sense. A negative value will move the motor in its reverse sense. The
    magnitude of direction can optionally correlate with the speed of the
    move, if the motor supports it.

    Parameters
    ----------
    direction : float
        The direction and velocity with which to start moving the motor.
    """
    log.info(f"Moving {self.name} continuously in {direction}")

move_to(self, position)

Move the motor to a position.

Parameters:

Name Type Description Default
position float

The position to move the motor to.

required
Source code in autogator/hardware.py
def move_to(self, position: float) -> None:
    """
    Move the motor to a position.

    Parameters
    ----------
    position : float
        The position to move the motor to.
    """
    log.info(f"Moving {self.name} to {position}")

status(self)

Whether the motor is doing something. Codes are device dependent.

A status of 0 should always mean that the motor is idle and waiting for a command.

Returns:

Type Description
int

The current status of the motor.

Source code in autogator/hardware.py
def status(self) -> int:
    """
    Whether the motor is doing something. Codes are device dependent.

    A status of 0 should always mean that the motor is idle and waiting
    for a command.

    Returns
    -------
    int
        The current status of the motor.
    """
    log.info(f"Getting status of {self.name}")
    return 0

stop(self)

Stops all current motion.

Source code in autogator/hardware.py
def stop(self) -> None:
    """
    Stops all current motion.
    """
    log.info(f"Stopping {self.name}")

PRM1Z8RotationalStage (RotationalStageBase)

A rotational motor.

Parameters:

Name Type Description Default
pyroname str

The name of the PyroLab object as registered with the nameserver.

''
ns_host str

The hostname of the PyroLab nameserver (default "localhost").

'localhost'
ns_port int

The port of the PyroLab nameserver (default "9090").

9090
Source code in autogator/hardware.py
class PRM1Z8RotationalStage(RotationalStageBase):
    """
    A rotational motor.

    Parameters
    ----------
    pyroname : str
        The name of the PyroLab object as registered with the nameserver.
    ns_host : str, optional
        The hostname of the PyroLab nameserver (default "localhost").
    ns_port : int, optional
        The port of the PyroLab nameserver (default "9090").
    """
    def __init__(self, pyroname: str = "", ns_host: str = "localhost", ns_port: int = 9090) -> None:
        super().__init__(pyroname)
        with locate_ns(host=ns_host, port=ns_port) as ns:
            self.driver = Proxy(ns.lookup(pyroname))
            self.driver.autoconnect()
        self._step_size = None

    @property
    def step_size(self) -> float:
        """The jog step size in mm."""
        return self._step_size

    @step_size.setter
    def step_size(self, step_size: float) -> None:
        if step_size != self._step_size:
            self.driver._pyroClaimOwnership()
            self.driver.jog_step_size = step_size
            self._step_size = step_size

    def move_to(self, position: float) -> None:
        """
        Moves to a new position.

        This motor adjusts for backlash; a given position will always be 
        approached from the same direction. That may require overshooting the
        commanded position in order to approach it again from a consistent
        direction.

        Parameters
        ----------
        position : float
            The new position to move to.
        """
        self.driver._pyroClaimOwnership()
        if self._requires_backlash_adjustment(position):
            self.driver.move_to(position - (self.driver.backlash * 1.5))
        self.driver.move_to(position)

    def move_by(self, distance: float) -> None:
        """
        Jogs the motor by a fixed distance.

        Parameters
        ----------
        distance : float, optional
            The distance to move the motor. A positive value will move the
            motor forward, and a negative value will move the motor backwards.
        """
        self.driver._pyroClaimOwnership()
        if np.abs(distance) != self.step_size:
            self.step_size = np.abs(distance)
        if distance > 0:
            self.driver.jog("forward")
        else:
            self.driver.jog("backward")

    def move_cont(self, direction: str) -> None:
        """
        Starts a continuous move in the specified direction.

        Parameters
        ----------
        direction : str
            The direction to move the motor, either "forward" or "backward".
        """
        self.driver._pyroClaimOwnership()
        self.driver.move_continuous(direction)

    def _requires_backlash_adjustment(self, position: float) -> bool:
        """
        Determine if the new position command needs to compensate for backlash.

        The ThorLabs linear stages have a small backlash distance. To ensure
        as accurate a reposition as possible when moving to the same location
        multiple times, the motor will always approach the position from the 
        same direction. This function determines whether that requires 
        overshooting the current position before reapproaching.

        Parameters
        ----------
        position : float
            The position to move to.

        Returns
        -------
        bool
            Whether backlash compensation is required.
        """
        if position < self.get_position():
            return True
        return False

    def stop(self) -> None:
        """
        Stop all motion.
        """
        self.driver._pyroClaimOwnership()
        self.driver.stop()

    def get_position(self) -> float:
        """
        Get the current position in millimeters.
        """
        self.driver._pyroClaimOwnership()
        return self.driver.get_position()

    def home(self) -> None:
        """
        Home the motor.
        """
        self.driver._pyroClaimOwnership()
        self.driver.go_home()

    def status(self) -> int:
        """
        Returns a nonzero value if the motor is busy.
        """
        pass

    def close(self) -> None:
        """
        This should close the connection to the cube

        Returns
        -------
        None
        """
        log.info(f"Closing RotationalStageBase {self.name}")
        self.driver._pyroClaimOwnership()
        self.driver.close()

step_size: float property writable

The jog step size in mm.

close(self)

This should close the connection to the cube

Source code in autogator/hardware.py
def close(self) -> None:
    """
    This should close the connection to the cube

    Returns
    -------
    None
    """
    log.info(f"Closing RotationalStageBase {self.name}")
    self.driver._pyroClaimOwnership()
    self.driver.close()

get_position(self)

Get the current position in millimeters.

Source code in autogator/hardware.py
def get_position(self) -> float:
    """
    Get the current position in millimeters.
    """
    self.driver._pyroClaimOwnership()
    return self.driver.get_position()

home(self)

Home the motor.

Source code in autogator/hardware.py
def home(self) -> None:
    """
    Home the motor.
    """
    self.driver._pyroClaimOwnership()
    self.driver.go_home()

move_by(self, distance)

Jogs the motor by a fixed distance.

Parameters:

Name Type Description Default
distance float

The distance to move the motor. A positive value will move the motor forward, and a negative value will move the motor backwards.

required
Source code in autogator/hardware.py
def move_by(self, distance: float) -> None:
    """
    Jogs the motor by a fixed distance.

    Parameters
    ----------
    distance : float, optional
        The distance to move the motor. A positive value will move the
        motor forward, and a negative value will move the motor backwards.
    """
    self.driver._pyroClaimOwnership()
    if np.abs(distance) != self.step_size:
        self.step_size = np.abs(distance)
    if distance > 0:
        self.driver.jog("forward")
    else:
        self.driver.jog("backward")

move_cont(self, direction)

Starts a continuous move in the specified direction.

Parameters:

Name Type Description Default
direction str

The direction to move the motor, either "forward" or "backward".

required
Source code in autogator/hardware.py
def move_cont(self, direction: str) -> None:
    """
    Starts a continuous move in the specified direction.

    Parameters
    ----------
    direction : str
        The direction to move the motor, either "forward" or "backward".
    """
    self.driver._pyroClaimOwnership()
    self.driver.move_continuous(direction)

move_to(self, position)

Moves to a new position.

This motor adjusts for backlash; a given position will always be approached from the same direction. That may require overshooting the commanded position in order to approach it again from a consistent direction.

Parameters:

Name Type Description Default
position float

The new position to move to.

required
Source code in autogator/hardware.py
def move_to(self, position: float) -> None:
    """
    Moves to a new position.

    This motor adjusts for backlash; a given position will always be 
    approached from the same direction. That may require overshooting the
    commanded position in order to approach it again from a consistent
    direction.

    Parameters
    ----------
    position : float
        The new position to move to.
    """
    self.driver._pyroClaimOwnership()
    if self._requires_backlash_adjustment(position):
        self.driver.move_to(position - (self.driver.backlash * 1.5))
    self.driver.move_to(position)

status(self)

Returns a nonzero value if the motor is busy.

Source code in autogator/hardware.py
def status(self) -> int:
    """
    Returns a nonzero value if the motor is busy.
    """
    pass

stop(self)

Stop all motion.

Source code in autogator/hardware.py
def stop(self) -> None:
    """
    Stop all motion.
    """
    self.driver._pyroClaimOwnership()
    self.driver.stop()

RohdeSchwarzOscilloscope (DataAcquisitionUnitBase)

A Rohde-Schwarz oscilloscope simplified interface.

Parameters:

Name Type Description Default
name str

An name for the oscilloscope.

required
address str

The IP address (or other valid identifier, see PyroLab's documentation) of the oscilloscope.

required
hislip bool

Whether to use the HiSLIP protocol or not (default False) (not supported unless you are using the NI VISA driver).

False
timeout float

The timeout for the connection in milliseconds (default 30000.0).

30000.0
Source code in autogator/hardware.py
class RohdeSchwarzOscilloscope(DataAcquisitionUnitBase):
    """
    A Rohde-Schwarz oscilloscope simplified interface.

    Parameters
    ----------
    name : str
        An name for the oscilloscope.
    address : str
        The IP address (or other valid identifier, see PyroLab's documentation)
        of the oscilloscope.
    hislip : str, optional
        Whether to use the HiSLIP protocol or not (default False) (not
        supported unless you are using the NI VISA driver).
    timeout : float, optional
        The timeout for the connection in milliseconds (default 30000.0).
    """
    def __init__(self, name: str, address: str, hislip: bool = False, timeout: float = 30000.0):
        super().__init__(name)
        self.driver = RTO()
        self.driver.connect(address, hislip=hislip, timeout=timeout)

    def measure(self) -> float:
        """
        Performs a single-shot measurement, if configured.

        See PyroLab's documentation for more information. Requires first 
        configuring the measurement with ``set_auto_measurement()``.

        Returns
        -------
        float
            The measured value.
        """
        return self.driver.measure()

    def acquire(self, timeout: float = -1) -> float:
        """
        Asynchronous command that starts acquisition.

        Parameters
        ----------
        timeout : float, optional
            The timeout for the acquisition in milliseconds (default -1). If
            not modified, default timeout is used. Can be changed for this
            single function call, after which it is reset to the original
            value.
        """
        self.driver.acquire(timeout=timeout)

    def get_data(self, channel: int) -> List[float]:
        """
        Gets the data from the specified channel.

        Parameters
        ----------
        channel : int
            The channel to retrieve the data from.
        """
        # TODO: Convert this to binary instead of ascii, which is faster?
        # TODO: Verify return type.
        return self.driver.get_data(channel)

    def wait_for_device(self) -> None:
        """
        Waits for the device to be ready (finished with all previous commands).

        This is a blocking call.
        """
        self.driver.wait_for_device()

    def set_channel(self, channel: int, range: float = 0.5, coupling: str = "DCLimit", position: float = 0.0) -> None:
        """
        Sets the channel parameters.

        Parameters
        ----------
        channel : int
            The channel to set.
        range : float
            Sets the voltage range across the 10 vertical divisions of the
            diagram in V/div. Default is 0.5.
        coupling : str
            Selects the connection of the indicated channel signal. Valid
            values are “DC” (direct connection with 50 ohm termination),
            “DCLimit” (direct connection with 1M ohm termination), or “AC”
            (connection through DC capacitor). Default is "DCLimit".
        position : float, optional
            Sets the vertical position of the indicated channel as a graphical
            value. Valid range is [-5, 5] in increments of 0.01 (units is
            “divisions”). Default is 0.
        """
        self.driver.set_channel(channel, range=range, coupling=coupling, position=position)

    def set_channel_for_auto_measurement(
        self,
        channel: int,
        range: float = 0.5,
        coupling: str = "DCLimit",
        position: float = 0,
        timescale: float = 10e-9,
    ) -> None:
        """
        Sets the channel parameters for auto measurement.

        Parameters
        ----------
        channel : int
            The channel to set.
        range : float
            Sets the voltage range across the 10 vertical divisions of the
            diagram in V/div. Default is 0.5.
        coupling : str
            Selects the connection of the indicated channel signal. Valid
            values are “DC” (direct connection with 50 ohm termination),
            “DCLimit” (direct connection with 1M ohm termination), or “AC”
            (connection through DC capacitor). Default is "DCLimit".
        position : float
            Sets the vertical position of the indicated channel as a graphical
            value. Valid range is [-5, 5] in increments of 0.01 (units is
            “divisions”). Default is 0.
        timescale : float
            The time (in seconds) per division. Valid range is from 25e-12 to
            10000 (RTO) | 5000 (RTE) in increments of 1e-12. Default is 10e-9.
        """
        self.driver.set_timescale(timescale)
        self.set_channel(channel, range=range, coupling=coupling, position=position)
        self.driver.set_auto_measurement(source="C" + str(channel) + "W1")
        self.wait_for_device()

    def set_acquisition_settings(self, sample_rate: float, duration: float) -> None:
        """
        Sets the acquisition settings.

        Parameters
        ----------
        sample_rate : float
            Sample rate of device in Sa/s. Range is 2 to 20e+12 in increments
            of 1.
        duration : float
            Length of acquisition in seconds.
        """
        self.driver.acquisition_settings(sample_rate=sample_rate, duration=duration)

    def set_edge_trigger(self, trigger_channel: int, trigger_level: int) -> None:
        """
        Sets a trigger channel and level.

        Parameters
        ----------
        trigger_channel : int
            The channel to trigger on.
        trigger_level : int
            Voltage threshold for positive slope edge trigger.
        """
        self.driver.edge_trigger(trigger_channel, trigger_level)

acquire(self, timeout=-1)

Asynchronous command that starts acquisition.

Parameters:

Name Type Description Default
timeout float

The timeout for the acquisition in milliseconds (default -1). If not modified, default timeout is used. Can be changed for this single function call, after which it is reset to the original value.

-1
Source code in autogator/hardware.py
def acquire(self, timeout: float = -1) -> float:
    """
    Asynchronous command that starts acquisition.

    Parameters
    ----------
    timeout : float, optional
        The timeout for the acquisition in milliseconds (default -1). If
        not modified, default timeout is used. Can be changed for this
        single function call, after which it is reset to the original
        value.
    """
    self.driver.acquire(timeout=timeout)

get_data(self, channel)

Gets the data from the specified channel.

Parameters:

Name Type Description Default
channel int

The channel to retrieve the data from.

required
Source code in autogator/hardware.py
def get_data(self, channel: int) -> List[float]:
    """
    Gets the data from the specified channel.

    Parameters
    ----------
    channel : int
        The channel to retrieve the data from.
    """
    # TODO: Convert this to binary instead of ascii, which is faster?
    # TODO: Verify return type.
    return self.driver.get_data(channel)

measure(self)

Performs a single-shot measurement, if configured.

See PyroLab's documentation for more information. Requires first configuring the measurement with set_auto_measurement().

Returns:

Type Description
float

The measured value.

Source code in autogator/hardware.py
def measure(self) -> float:
    """
    Performs a single-shot measurement, if configured.

    See PyroLab's documentation for more information. Requires first 
    configuring the measurement with ``set_auto_measurement()``.

    Returns
    -------
    float
        The measured value.
    """
    return self.driver.measure()

set_acquisition_settings(self, sample_rate, duration)

Sets the acquisition settings.

Parameters:

Name Type Description Default
sample_rate float

Sample rate of device in Sa/s. Range is 2 to 20e+12 in increments of 1.

required
duration float

Length of acquisition in seconds.

required
Source code in autogator/hardware.py
def set_acquisition_settings(self, sample_rate: float, duration: float) -> None:
    """
    Sets the acquisition settings.

    Parameters
    ----------
    sample_rate : float
        Sample rate of device in Sa/s. Range is 2 to 20e+12 in increments
        of 1.
    duration : float
        Length of acquisition in seconds.
    """
    self.driver.acquisition_settings(sample_rate=sample_rate, duration=duration)

set_channel(self, channel, range=0.5, coupling='DCLimit', position=0.0)

Sets the channel parameters.

Parameters:

Name Type Description Default
channel int

The channel to set.

required
range float

Sets the voltage range across the 10 vertical divisions of the diagram in V/div. Default is 0.5.

0.5
coupling str

Selects the connection of the indicated channel signal. Valid values are “DC” (direct connection with 50 ohm termination), “DCLimit” (direct connection with 1M ohm termination), or “AC” (connection through DC capacitor). Default is "DCLimit".

'DCLimit'
position float

Sets the vertical position of the indicated channel as a graphical value. Valid range is [-5, 5] in increments of 0.01 (units is “divisions”). Default is 0.

0.0
Source code in autogator/hardware.py
def set_channel(self, channel: int, range: float = 0.5, coupling: str = "DCLimit", position: float = 0.0) -> None:
    """
    Sets the channel parameters.

    Parameters
    ----------
    channel : int
        The channel to set.
    range : float
        Sets the voltage range across the 10 vertical divisions of the
        diagram in V/div. Default is 0.5.
    coupling : str
        Selects the connection of the indicated channel signal. Valid
        values are “DC” (direct connection with 50 ohm termination),
        “DCLimit” (direct connection with 1M ohm termination), or “AC”
        (connection through DC capacitor). Default is "DCLimit".
    position : float, optional
        Sets the vertical position of the indicated channel as a graphical
        value. Valid range is [-5, 5] in increments of 0.01 (units is
        “divisions”). Default is 0.
    """
    self.driver.set_channel(channel, range=range, coupling=coupling, position=position)

set_channel_for_auto_measurement(self, channel, range=0.5, coupling='DCLimit', position=0, timescale=1e-08)

Sets the channel parameters for auto measurement.

Parameters:

Name Type Description Default
channel int

The channel to set.

required
range float

Sets the voltage range across the 10 vertical divisions of the diagram in V/div. Default is 0.5.

0.5
coupling str

Selects the connection of the indicated channel signal. Valid values are “DC” (direct connection with 50 ohm termination), “DCLimit” (direct connection with 1M ohm termination), or “AC” (connection through DC capacitor). Default is "DCLimit".

'DCLimit'
position float

Sets the vertical position of the indicated channel as a graphical value. Valid range is [-5, 5] in increments of 0.01 (units is “divisions”). Default is 0.

0
timescale float

The time (in seconds) per division. Valid range is from 25e-12 to 10000 (RTO) | 5000 (RTE) in increments of 1e-12. Default is 10e-9.

1e-08
Source code in autogator/hardware.py
def set_channel_for_auto_measurement(
    self,
    channel: int,
    range: float = 0.5,
    coupling: str = "DCLimit",
    position: float = 0,
    timescale: float = 10e-9,
) -> None:
    """
    Sets the channel parameters for auto measurement.

    Parameters
    ----------
    channel : int
        The channel to set.
    range : float
        Sets the voltage range across the 10 vertical divisions of the
        diagram in V/div. Default is 0.5.
    coupling : str
        Selects the connection of the indicated channel signal. Valid
        values are “DC” (direct connection with 50 ohm termination),
        “DCLimit” (direct connection with 1M ohm termination), or “AC”
        (connection through DC capacitor). Default is "DCLimit".
    position : float
        Sets the vertical position of the indicated channel as a graphical
        value. Valid range is [-5, 5] in increments of 0.01 (units is
        “divisions”). Default is 0.
    timescale : float
        The time (in seconds) per division. Valid range is from 25e-12 to
        10000 (RTO) | 5000 (RTE) in increments of 1e-12. Default is 10e-9.
    """
    self.driver.set_timescale(timescale)
    self.set_channel(channel, range=range, coupling=coupling, position=position)
    self.driver.set_auto_measurement(source="C" + str(channel) + "W1")
    self.wait_for_device()

set_edge_trigger(self, trigger_channel, trigger_level)

Sets a trigger channel and level.

Parameters:

Name Type Description Default
trigger_channel int

The channel to trigger on.

required
trigger_level int

Voltage threshold for positive slope edge trigger.

required
Source code in autogator/hardware.py
def set_edge_trigger(self, trigger_channel: int, trigger_level: int) -> None:
    """
    Sets a trigger channel and level.

    Parameters
    ----------
    trigger_channel : int
        The channel to trigger on.
    trigger_level : int
        Voltage threshold for positive slope edge trigger.
    """
    self.driver.edge_trigger(trigger_channel, trigger_level)

wait_for_device(self)

Waits for the device to be ready (finished with all previous commands).

This is a blocking call.

Source code in autogator/hardware.py
def wait_for_device(self) -> None:
    """
    Waits for the device to be ready (finished with all previous commands).

    This is a blocking call.
    """
    self.driver.wait_for_device()

RotationalStageBase (HardwareDevice)

A rotational motor.

This class is used to control a rotational motor.

Parameters:

Name Type Description Default
name str

The name of the motor.

required
axis Tuple[float, float, float]

The axis of the motor as a 3-vector.

typing.Tuple[float, float, float]
Source code in autogator/hardware.py
class RotationalStageBase(HardwareDevice):
    """
    A rotational motor.

    This class is used to control a rotational motor.

    Parameters
    ----------
    name : str
        The name of the motor.
    axis : Tuple[float, float, float]
        The axis of the motor as a 3-vector.
    """
    def __init__(self, name: str, axis=Tuple[float, float, float]) -> None:
        self.name = name
        self.axis = axis

    def move_to(self, position: float) -> None:
        """
        Move the motor to a position.

        Parameters
        ----------
        position : float
            The position to move the motor to.
        """
        log.info(f"Moving {self.name} to {position}")

    def move_by(self, distance: float) -> None:
        """
        Move the motor by a distance.

        Some motors have the capability for more accurate "jogs." This function
        exists to support that capability. Motors that don't have this
        capability can imitate its effects by simply using
        [`move_to()`][autogator.hardware.RotationalStageBase.move_to].

        Parameters
        ----------
        distance : float
            The distance to move the motor.
        """
        log.info(f"Moving {self.name} by {distance}")

    def move_cont(self, direction: float) -> None:
        """
        Move the motor continuously in the specified direction.

        A positive value for direction will move the motor in its forward
        sense. A negative value will move the motor in its reverse sense. The
        magnitude of direction can optionally correlate with the speed of the
        move, if the motor supports it.

        Parameters
        ----------
        direction : float
            The direction and velocity with which to start moving the motor.
        """
        log.info(f"Moving {self.name} continuously in {direction}")

    def stop(self) -> None:
        """
        Stops all current motion.
        """
        log.info(f"Stopping {self.name}")

    def get_position(self) -> float:
        """
        Get the current position of the motor.

        Returns
        -------
        float
            The current position of the motor.
        """
        log.info(f"Getting position of {self.name}")
        return 0.0

    def home(self) -> None:
        """
        Move the motor to its home position, if supported.
        """
        log.info(f"Homing {self.name}")

    def status(self) -> int:
        """
        Whether the motor is doing something. Codes are device dependent.

        A status of 0 should always mean that the motor is idle and waiting
        for a command.

        Returns
        -------
        int
            The current status of the motor.
        """
        log.info(f"Getting status of {self.name}")
        return 0

    def close(self) -> None:
        """
        This should close the connection to the cube

        Returns
        -------
        None
        """
        log.info(f"Closing RotationalStageBase {self.name}")

close(self)

This should close the connection to the cube

Source code in autogator/hardware.py
def close(self) -> None:
    """
    This should close the connection to the cube

    Returns
    -------
    None
    """
    log.info(f"Closing RotationalStageBase {self.name}")

get_position(self)

Get the current position of the motor.

Returns:

Type Description
float

The current position of the motor.

Source code in autogator/hardware.py
def get_position(self) -> float:
    """
    Get the current position of the motor.

    Returns
    -------
    float
        The current position of the motor.
    """
    log.info(f"Getting position of {self.name}")
    return 0.0

home(self)

Move the motor to its home position, if supported.

Source code in autogator/hardware.py
def home(self) -> None:
    """
    Move the motor to its home position, if supported.
    """
    log.info(f"Homing {self.name}")

move_by(self, distance)

Move the motor by a distance.

Some motors have the capability for more accurate "jogs." This function exists to support that capability. Motors that don't have this capability can imitate its effects by simply using move_to().

Parameters:

Name Type Description Default
distance float

The distance to move the motor.

required
Source code in autogator/hardware.py
def move_by(self, distance: float) -> None:
    """
    Move the motor by a distance.

    Some motors have the capability for more accurate "jogs." This function
    exists to support that capability. Motors that don't have this
    capability can imitate its effects by simply using
    [`move_to()`][autogator.hardware.RotationalStageBase.move_to].

    Parameters
    ----------
    distance : float
        The distance to move the motor.
    """
    log.info(f"Moving {self.name} by {distance}")

move_cont(self, direction)

Move the motor continuously in the specified direction.

A positive value for direction will move the motor in its forward sense. A negative value will move the motor in its reverse sense. The magnitude of direction can optionally correlate with the speed of the move, if the motor supports it.

Parameters:

Name Type Description Default
direction float

The direction and velocity with which to start moving the motor.

required
Source code in autogator/hardware.py
def move_cont(self, direction: float) -> None:
    """
    Move the motor continuously in the specified direction.

    A positive value for direction will move the motor in its forward
    sense. A negative value will move the motor in its reverse sense. The
    magnitude of direction can optionally correlate with the speed of the
    move, if the motor supports it.

    Parameters
    ----------
    direction : float
        The direction and velocity with which to start moving the motor.
    """
    log.info(f"Moving {self.name} continuously in {direction}")

move_to(self, position)

Move the motor to a position.

Parameters:

Name Type Description Default
position float

The position to move the motor to.

required
Source code in autogator/hardware.py
def move_to(self, position: float) -> None:
    """
    Move the motor to a position.

    Parameters
    ----------
    position : float
        The position to move the motor to.
    """
    log.info(f"Moving {self.name} to {position}")

status(self)

Whether the motor is doing something. Codes are device dependent.

A status of 0 should always mean that the motor is idle and waiting for a command.

Returns:

Type Description
int

The current status of the motor.

Source code in autogator/hardware.py
def status(self) -> int:
    """
    Whether the motor is doing something. Codes are device dependent.

    A status of 0 should always mean that the motor is idle and waiting
    for a command.

    Returns
    -------
    int
        The current status of the motor.
    """
    log.info(f"Getting status of {self.name}")
    return 0

stop(self)

Stops all current motion.

Source code in autogator/hardware.py
def stop(self) -> None:
    """
    Stops all current motion.
    """
    log.info(f"Stopping {self.name}")

Stage

Singleton-like class that centralizes access to all hardware devices.

If you know a certain stage axis supports more complex functions, you can access the object directly using the driver attribute, e.g.:

x_motor = stage.x.driver
x_motor.unexposed_function()

Additionally, any "auxiliary" devices can also be directly accessed as attributes, e.g.:

daq = stage.daq
daq.measure()
daq.driver.unexposed_function()

Parameters:

Name Type Description Default
x LinearStageBase

The x-axis stage.

None
y LinearStageBase

The y-axis stage.

None
z LinearStageBase

The z-axis stage.

None
theta RotationalStageBase

The theta-axis stage.

None
phi RotationalStageBase

The phi-axis stage.

None
psi RotationalStageBase

The psi-axis stage.

None
calibration_matrix ndarray

The calibration matrix for the stage. Converts from motor coordinates to GDS coordinates. If not provided, some stage functionality will be unavailable.

None
loaded_position List[float]

A default "goto" position for positioning the stage under the fiber array and camera.

[None, None, None, None, None, None]
unloaded_position List[float]

A default "goto" position for pulling the stage away from the fiber array and camera, enabling easier access to the sample for switching chips, etc.

[None, None, None, None, None, None]
**auxiliaries HardwareDevice

Any other devices that are not part of the standard stage. Keyword will be used as device attribute, and can be retrieved from the stage using that same name. Argument is the device object. Useful for specifying more instruments, such as a scope, DAQ, or microscope lamp controller.

{}
Source code in autogator/hardware.py
class Stage:
    """
    Singleton-like class that centralizes access to all hardware devices. 

    If you know a certain stage axis supports more complex functions, you can
    access the object directly using the ``driver`` attribute, e.g.:

    ``` python
    x_motor = stage.x.driver
    x_motor.unexposed_function()
    ```

    Additionally, any "auxiliary" devices can also be directly accessed as 
    attributes, e.g.:

    ``` python
    daq = stage.daq
    daq.measure()
    daq.driver.unexposed_function()
    ```

    Parameters
    ----------
    x : LinearStageBase
        The x-axis stage.
    y : LinearStageBase
        The y-axis stage.
    z : LinearStageBase
        The z-axis stage.
    theta : RotationalStageBase
        The theta-axis stage.
    phi : RotationalStageBase
        The phi-axis stage.
    psi : RotationalStageBase
        The psi-axis stage.
    calibration_matrix : np.ndarray, optional
        The calibration matrix for the stage. Converts from motor coordinates
        to GDS coordinates. If not provided, some stage functionality
        will be unavailable.
    loaded_position : np.ndarray, optional
        A default "goto" position for positioning the stage under the fiber
        array and camera.
    unloaded_position : np.ndarray, optional
        A default "goto" position for pulling the stage away from the fiber
        array and camera, enabling easier access to the sample for switching
        chips, etc.
    **auxiliaries : HardwareDevice, optional
        Any other devices that are not part of the standard stage. Keyword will
        be used as device attribute, and can be retrieved from the stage
        using that same name. Argument is the device object. Useful for 
        specifying more instruments, such as a scope, DAQ, or microscope lamp
        controller.
    """    
    def __init__(
        self, 
        x: LinearStageBase = None, 
        y: LinearStageBase = None, 
        z: LinearStageBase = None, 
        theta: RotationalStageBase = None, 
        phi: RotationalStageBase = None, 
        psi: RotationalStageBase = None, 
        calibration_matrix: np.ndarray = None, 
        loaded_position: List[float] = [None, None, None, None, None, None],
        unloaded_position: List[float] = [None, None, None, None, None, None],
        **auxiliaries: HardwareDevice,
    ) -> None:
        self.x = x
        self.y = y
        self.z = z
        self.theta = theta
        self.phi = phi
        self.psi = psi

        self.calibration_matrix = calibration_matrix
        self.loaded_position = loaded_position
        self.unloaded_position = unloaded_position
        self.auxiliaries = auxiliaries

        # TODO: Implement?
        # self._last_calibrated = None
        # self._last_homed = None

    def __getattr__(self, name) -> Any:
        if name in self.auxiliaries:
            return self.auxiliaries[name]
        else:
            raise AttributeError(f"'Stage' object has no attribute '{name}'")

    def load_calibration_matrix(self, filename: Union[str, Path]) -> None:
        """
        Load a conversion matrix from a file.

        Parameters
        ----------
        filename : str
            The path to the file containing the conversion matrix.

        Raises
        ------
        FileNotFoundError
            If the file does not exist.
        """
        if isinstance(filename, str):
            filename = Path(filename)
        if not filename.exists():
            raise FileNotFoundError(f"File {filename} does not exist.")
        self.calibration_matrix = np.loadtxt(filename)

    def save_calibration_matrix(self, filename: str) -> None:
        """
        Saves a conversion matrix to a file.

        Parameters
        ----------
        filename : str
            The path to the file to save the conversion matrix to.
        """
        np.savetxt(filename, self.calibration_matrix)

    @property
    def motors(self) -> list:
        """
        Returns a list of all motors, ordered [x, y, z, theta, phi, psi].
        """
        return [self.x, self.y, self.z, self.theta, self.phi, self.psi]

    def set_position(self, *, pos: List[float] = [], x: float = None, y: float = None, z: float = None, theta: float = None, phi: float = None, psi: float = None) -> None:
        """
        Set the position of the stage in real world units.

        Any unspecified parameters won't be moved. All parameters are
        keyword-only (no positional parameters accepted).

        Parameters
        ----------
        pos : list, optional
            The position to move to as a 6-list of floats, matching the format
            of ``Stage.get_position``. If specified, all other parameters are
            ignored.
        x : float, optional
            The x position.
        y : float, optional
            The y position.
        z : float, optional
            The z position.
        theta : float, optional
            The theta position.
        phi : float, optional
            The phi position.
        psi : float, optional
            The psi position.
        """
        ZLIFTSIZE = 0.1
        if not pos:
            pos = [x, y, z, theta, phi, psi]
        commands = [cmd for cmd in zip(self.motors, pos) if cmd[1] is not None]

        if z is None:
            self.jog_position(z=ZLIFTSIZE)

        with concurrent.futures.ThreadPoolExecutor(max_workers=len(commands)) as executor:
            # Start the load operations and mark each future with its URL
            future_to_cmd = {}
            for motor, pos in commands:
                future_to_cmd[executor.submit(motor.move_to, pos)] = motor
                time.sleep(0.01) # Space out simultaneous calls to potentially shared DLLs
            for future in concurrent.futures.as_completed(future_to_cmd):
                motor = future_to_cmd[future]
                try:
                    driver = future.result()
                except Exception as exc:
                    print(f'{motor} generated an exception: {exc}')
                    log.exception(exc)

        if z is None:
            self.jog_position(z=-ZLIFTSIZE)

    def jog_position(self, *, pos: List[float] = [], x=None, y=None, z=None, theta=None, phi=None, psi=None) -> None:
        """
        Set the position of the stage in real world units.

        Any unspecified parameters won't be moved. All parameters are
        keyword-only (no positional parameters accepted).

        Parameters
        ----------
        pos : list, optional
            The position to move to as a 6-list of floats, matching the format
            of ``Stage.get_position``. If specified, all other parameters are
            ignored.
        x : float, optional
            The x jog step.
        y : float, optional
            The y jog step.
        z : float, optional
            The z jog step.
        theta : float, optional
            The theta jog step.
        phi : float, optional
            The phi jog step.
        psi : float, optional
            The psi jog step.
        """
        ZLIFTSIZE = 0.1
        if z is None:
            self.jog_position(z=ZLIFTSIZE)
        if not pos:
            pos = [x, y, z, theta, phi, psi]
        commands = [cmd for cmd in zip(self.motors, pos) if cmd[1] is not None]
        for motor, pos in commands:
            if motor:
                motor.move_by(pos)
        if z is None:
            self.jog_position(z=-ZLIFTSIZE)

    def set_position_gds(self, x: float, y: float) -> None:
        """
        Set the position of the stage in GDS coordinates.

        Only supports 2D commands, i.e. (x, y) coordinates.

        Parameters
        ----------
        x : float
            The x coordinate.
        y : float
            The y coordinate.

        Raises
        ------
        UncalibratedStageError
            If the stage is not calibrated. GDS position cannot be set without
            first calibrating the stage.
        """
        if self.calibration_matrix is None:
            raise UncalibratedStageError("Stage is not calibrated (no conversion matrix set), cannot set position in GDS coordinates")

        gds_pos = np.array([[x], [y], [1]])
        stage_pos = self.calibration_matrix @ gds_pos # @ is matrix multiplication

        self.set_position(x=stage_pos[0, 0], y=stage_pos[1, 0])
        actual = self.get_position()
        log.info(f"CMD: ({stage_pos[0,0], stage_pos[1,0]}), ACT: ({actual[0], actual[1]}), ERR: ({stage_pos[0,0] - actual[0], stage_pos[1,0] - actual[1]})")

    def get_position(self) -> List[float]:
        """
        Returns the current position of the stage.

        Returns
        -------
        List[float]
            A list of the current position of each motor as a six-vector of
            floats. The order is [x, y, z, theta, phi, psi].
        """
        return [motor.get_position() if motor else None for motor in self.motors]

    def stop_all(self) -> None:
        """
        Stops motor motion is it is moving conintuously and marks motor as not moving.

        This function should not block! In the case of multiple motors, 
        be careful that calling stop on each may wait until completion before
        returning! In the case of an emergency stop, you want all motors to 
        stop essentially instantaneously. Perhaps spawn them all in a thread?
        """
        for motor in self.motors:
            if motor:
                motor.stop()
                # if motor.status() != 0:
                #     motor.stop()

    def load(self):
        """
        Load the stage.

        This method places the stage in the loaded position.
        """
        log.info("Loading stage")
        self.set_position(*self.loaded_position)

    def unload(self):
        """
        Unload the stage.

        This method places the stage in the unloaded position.
        """
        log.info("Unloading stage")
        self.set_position(*self.unloaded_position)

motors: list property readonly

Returns a list of all motors, ordered [x, y, z, theta, phi, psi].

get_position(self)

Returns the current position of the stage.

Returns:

Type Description
List[float]

A list of the current position of each motor as a six-vector of floats. The order is [x, y, z, theta, phi, psi].

Source code in autogator/hardware.py
def get_position(self) -> List[float]:
    """
    Returns the current position of the stage.

    Returns
    -------
    List[float]
        A list of the current position of each motor as a six-vector of
        floats. The order is [x, y, z, theta, phi, psi].
    """
    return [motor.get_position() if motor else None for motor in self.motors]

jog_position(self, *, pos=[], x=None, y=None, z=None, theta=None, phi=None, psi=None)

Set the position of the stage in real world units.

Any unspecified parameters won't be moved. All parameters are keyword-only (no positional parameters accepted).

Parameters:

Name Type Description Default
pos List[float]

The position to move to as a 6-list of floats, matching the format of Stage.get_position. If specified, all other parameters are ignored.

[]
x float

The x jog step.

None
y float

The y jog step.

None
z float

The z jog step.

None
theta float

The theta jog step.

None
phi float

The phi jog step.

None
psi float

The psi jog step.

None
Source code in autogator/hardware.py
def jog_position(self, *, pos: List[float] = [], x=None, y=None, z=None, theta=None, phi=None, psi=None) -> None:
    """
    Set the position of the stage in real world units.

    Any unspecified parameters won't be moved. All parameters are
    keyword-only (no positional parameters accepted).

    Parameters
    ----------
    pos : list, optional
        The position to move to as a 6-list of floats, matching the format
        of ``Stage.get_position``. If specified, all other parameters are
        ignored.
    x : float, optional
        The x jog step.
    y : float, optional
        The y jog step.
    z : float, optional
        The z jog step.
    theta : float, optional
        The theta jog step.
    phi : float, optional
        The phi jog step.
    psi : float, optional
        The psi jog step.
    """
    ZLIFTSIZE = 0.1
    if z is None:
        self.jog_position(z=ZLIFTSIZE)
    if not pos:
        pos = [x, y, z, theta, phi, psi]
    commands = [cmd for cmd in zip(self.motors, pos) if cmd[1] is not None]
    for motor, pos in commands:
        if motor:
            motor.move_by(pos)
    if z is None:
        self.jog_position(z=-ZLIFTSIZE)

load(self)

Load the stage.

This method places the stage in the loaded position.

Source code in autogator/hardware.py
def load(self):
    """
    Load the stage.

    This method places the stage in the loaded position.
    """
    log.info("Loading stage")
    self.set_position(*self.loaded_position)

load_calibration_matrix(self, filename)

Load a conversion matrix from a file.

Parameters:

Name Type Description Default
filename Union[str, pathlib.Path]

The path to the file containing the conversion matrix.

required

Exceptions:

Type Description
FileNotFoundError

If the file does not exist.

Source code in autogator/hardware.py
def load_calibration_matrix(self, filename: Union[str, Path]) -> None:
    """
    Load a conversion matrix from a file.

    Parameters
    ----------
    filename : str
        The path to the file containing the conversion matrix.

    Raises
    ------
    FileNotFoundError
        If the file does not exist.
    """
    if isinstance(filename, str):
        filename = Path(filename)
    if not filename.exists():
        raise FileNotFoundError(f"File {filename} does not exist.")
    self.calibration_matrix = np.loadtxt(filename)

save_calibration_matrix(self, filename)

Saves a conversion matrix to a file.

Parameters:

Name Type Description Default
filename str

The path to the file to save the conversion matrix to.

required
Source code in autogator/hardware.py
def save_calibration_matrix(self, filename: str) -> None:
    """
    Saves a conversion matrix to a file.

    Parameters
    ----------
    filename : str
        The path to the file to save the conversion matrix to.
    """
    np.savetxt(filename, self.calibration_matrix)

set_position(self, *, pos=[], x=None, y=None, z=None, theta=None, phi=None, psi=None)

Set the position of the stage in real world units.

Any unspecified parameters won't be moved. All parameters are keyword-only (no positional parameters accepted).

Parameters:

Name Type Description Default
pos List[float]

The position to move to as a 6-list of floats, matching the format of Stage.get_position. If specified, all other parameters are ignored.

[]
x float

The x position.

None
y float

The y position.

None
z float

The z position.

None
theta float

The theta position.

None
phi float

The phi position.

None
psi float

The psi position.

None
Source code in autogator/hardware.py
def set_position(self, *, pos: List[float] = [], x: float = None, y: float = None, z: float = None, theta: float = None, phi: float = None, psi: float = None) -> None:
    """
    Set the position of the stage in real world units.

    Any unspecified parameters won't be moved. All parameters are
    keyword-only (no positional parameters accepted).

    Parameters
    ----------
    pos : list, optional
        The position to move to as a 6-list of floats, matching the format
        of ``Stage.get_position``. If specified, all other parameters are
        ignored.
    x : float, optional
        The x position.
    y : float, optional
        The y position.
    z : float, optional
        The z position.
    theta : float, optional
        The theta position.
    phi : float, optional
        The phi position.
    psi : float, optional
        The psi position.
    """
    ZLIFTSIZE = 0.1
    if not pos:
        pos = [x, y, z, theta, phi, psi]
    commands = [cmd for cmd in zip(self.motors, pos) if cmd[1] is not None]

    if z is None:
        self.jog_position(z=ZLIFTSIZE)

    with concurrent.futures.ThreadPoolExecutor(max_workers=len(commands)) as executor:
        # Start the load operations and mark each future with its URL
        future_to_cmd = {}
        for motor, pos in commands:
            future_to_cmd[executor.submit(motor.move_to, pos)] = motor
            time.sleep(0.01) # Space out simultaneous calls to potentially shared DLLs
        for future in concurrent.futures.as_completed(future_to_cmd):
            motor = future_to_cmd[future]
            try:
                driver = future.result()
            except Exception as exc:
                print(f'{motor} generated an exception: {exc}')
                log.exception(exc)

    if z is None:
        self.jog_position(z=-ZLIFTSIZE)

set_position_gds(self, x, y)

Set the position of the stage in GDS coordinates.

Only supports 2D commands, i.e. (x, y) coordinates.

Parameters:

Name Type Description Default
x float

The x coordinate.

required
y float

The y coordinate.

required

Exceptions:

Type Description
UncalibratedStageError

If the stage is not calibrated. GDS position cannot be set without first calibrating the stage.

Source code in autogator/hardware.py
def set_position_gds(self, x: float, y: float) -> None:
    """
    Set the position of the stage in GDS coordinates.

    Only supports 2D commands, i.e. (x, y) coordinates.

    Parameters
    ----------
    x : float
        The x coordinate.
    y : float
        The y coordinate.

    Raises
    ------
    UncalibratedStageError
        If the stage is not calibrated. GDS position cannot be set without
        first calibrating the stage.
    """
    if self.calibration_matrix is None:
        raise UncalibratedStageError("Stage is not calibrated (no conversion matrix set), cannot set position in GDS coordinates")

    gds_pos = np.array([[x], [y], [1]])
    stage_pos = self.calibration_matrix @ gds_pos # @ is matrix multiplication

    self.set_position(x=stage_pos[0, 0], y=stage_pos[1, 0])
    actual = self.get_position()
    log.info(f"CMD: ({stage_pos[0,0], stage_pos[1,0]}), ACT: ({actual[0], actual[1]}), ERR: ({stage_pos[0,0] - actual[0], stage_pos[1,0] - actual[1]})")

stop_all(self)

Stops motor motion is it is moving conintuously and marks motor as not moving.

This function should not block! In the case of multiple motors, be careful that calling stop on each may wait until completion before returning! In the case of an emergency stop, you want all motors to stop essentially instantaneously. Perhaps spawn them all in a thread?

Source code in autogator/hardware.py
def stop_all(self) -> None:
    """
    Stops motor motion is it is moving conintuously and marks motor as not moving.

    This function should not block! In the case of multiple motors, 
    be careful that calling stop on each may wait until completion before
    returning! In the case of an emergency stop, you want all motors to 
    stop essentially instantaneously. Perhaps spawn them all in a thread?
    """
    for motor in self.motors:
        if motor:
            motor.stop()
            # if motor.status() != 0:
            #     motor.stop()

unload(self)

Unload the stage.

This method places the stage in the unloaded position.

Source code in autogator/hardware.py
def unload(self):
    """
    Unload the stage.

    This method places the stage in the unloaded position.
    """
    log.info("Unloading stage")
    self.set_position(*self.unloaded_position)

StageConfiguration (BaseSettings) pydantic-model

The persisted stage configuration. Typically stored as a JSON file.

Attributes:

Name Type Description
x HardwareConfiguration

The configuration for the x-axis stage.

y HardwareConfiguration

The configuration for the y-axis stage.

z HardwareConfiguration

The configuration for the z-axis stage.

theta HardwareConfiguration

The configuration for the rotational stage in the x-z plane.

phi HardwareConfiguration

The configuration for the rotational stage in the y-z plane.

psi HardwareConfiguration

The configuration for the rotational stage in the x-y plane.

calibration_matrix str

Path to the conversion matrix text file.

loaded_position List[float]

List (length 6) of motor positions when the stage is loaded.

unloaded_position List[float]

List (length 6) of motor positions when the stage is unloaded.

auxiliaries Dict[str, HardwareConfiguration]

A dictionary of auxiliary hardware devices.

Source code in autogator/hardware.py
class StageConfiguration(BaseSettings):
    """
    The persisted stage configuration. Typically stored as a JSON file.

    Attributes
    ----------
    x : HardwareConfiguration
        The configuration for the x-axis stage.
    y : HardwareConfiguration
        The configuration for the y-axis stage.
    z : HardwareConfiguration
        The configuration for the z-axis stage.
    theta : HardwareConfiguration
        The configuration for the rotational stage in the x-z plane.
    phi : HardwareConfiguration
        The configuration for the rotational stage in the y-z plane.
    psi : HardwareConfiguration
        The configuration for the rotational stage in the x-y plane.
    calibration_matrix : str
        Path to the conversion matrix text file.
    loaded_position : List[float]
        List (length 6) of motor positions when the stage is loaded.
    unloaded_position : List[float]
        List (length 6) of motor positions when the stage is unloaded.
    auxiliaries : Dict[str, HardwareConfiguration]
        A dictionary of auxiliary hardware devices.
    """
    x: HardwareConfiguration = None
    y: HardwareConfiguration = None
    z: HardwareConfiguration = None
    theta: HardwareConfiguration = None
    phi: HardwareConfiguration = None
    psi: HardwareConfiguration = None
    calibration_matrix: str = ""
    loaded_position: List[Any] = [None, None, None, None, None, None]
    unloaded_position: List[Any] = [None, None, None, None, None, None]
    auxiliaries: Dict[str, HardwareConfiguration] = {}

    def get_stage(self) -> Stage:
        """
        Constructs and returns the instantiated stage object.

        Returns
        -------
        Stage
            The stage object.
        """
        cmatfile = Path(self.calibration_matrix)
        calibration_matrix = np.loadtxt(cmatfile) if cmatfile.is_file() else None

        log.info("Loading stage objects...")
        names = ["x", "y", "z", "theta", "phi", "psi"]
        configs = [self.x, self.y, self.z, self.theta, self.phi, self.psi]
        to_load = {name: config for name, config in zip(names, configs) if config}
        to_load.update(self.auxiliaries)
        loaded = {}

        ###################################
        for name, config in to_load.items():
            log.info(f"Loading {name} stage...")
            max_retries = 10
            count = 0
            status = "failed"
            while count < max_retries:
                try:
                    driver = config.get_object()
                    if (count > 8):
                        time.sleep(2)           
                    status = "success"
                    break
                except Exception as e:
                    #log.info("Failed to connect to", name, "trying again")
                    count += 1
                    print(count)

            # Space out simultaneous calls to potentially shared DLLs
            if (status == "success"):
                loaded[name] = driver
            else:
                print(f"failed to connect after {max_retries} attempts")
        ###################################


        # # Laod all stage objects in parallel, in case they are slow
        # # Code adapted from https://docs.python.org/3/library/concurrent.futures.html#threadpoolexecutor-example
        # with concurrent.futures.ThreadPoolExecutor(max_workers=len(configs)) as executor:
        #     # Start the load operations and mark each future with its URL
        #     future_to_config = {}
        #     for name, config in to_load.items():
        #         log.info(f"Loading {name} stage...")
        #         future_to_config[executor.submit(config.get_object)] = name
        #         # Space out simultaneous calls to potentially shared DLLs
        #         time.sleep(3)
        #     for future in concurrent.futures.as_completed(future_to_config):
        #         name = future_to_config[future]
        #         try:
        #             driver = future.result()
        #         except Exception as exc:
        #             print(f'{name} generated an exception: {exc}')
        #             log.exception(exc)
        #         else:
        #             loaded[name] = driver

        log.info("Stage objects loaded.")
        return Stage(
            **loaded,
            calibration_matrix = calibration_matrix,
            loaded_position = self.loaded_position,
            unloaded_position = self.unloaded_position,
        )

get_stage(self)

Constructs and returns the instantiated stage object.

Returns:

Type Description
Stage

The stage object.

Source code in autogator/hardware.py
def get_stage(self) -> Stage:
    """
    Constructs and returns the instantiated stage object.

    Returns
    -------
    Stage
        The stage object.
    """
    cmatfile = Path(self.calibration_matrix)
    calibration_matrix = np.loadtxt(cmatfile) if cmatfile.is_file() else None

    log.info("Loading stage objects...")
    names = ["x", "y", "z", "theta", "phi", "psi"]
    configs = [self.x, self.y, self.z, self.theta, self.phi, self.psi]
    to_load = {name: config for name, config in zip(names, configs) if config}
    to_load.update(self.auxiliaries)
    loaded = {}

    ###################################
    for name, config in to_load.items():
        log.info(f"Loading {name} stage...")
        max_retries = 10
        count = 0
        status = "failed"
        while count < max_retries:
            try:
                driver = config.get_object()
                if (count > 8):
                    time.sleep(2)           
                status = "success"
                break
            except Exception as e:
                #log.info("Failed to connect to", name, "trying again")
                count += 1
                print(count)

        # Space out simultaneous calls to potentially shared DLLs
        if (status == "success"):
            loaded[name] = driver
        else:
            print(f"failed to connect after {max_retries} attempts")
    ###################################


    # # Laod all stage objects in parallel, in case they are slow
    # # Code adapted from https://docs.python.org/3/library/concurrent.futures.html#threadpoolexecutor-example
    # with concurrent.futures.ThreadPoolExecutor(max_workers=len(configs)) as executor:
    #     # Start the load operations and mark each future with its URL
    #     future_to_config = {}
    #     for name, config in to_load.items():
    #         log.info(f"Loading {name} stage...")
    #         future_to_config[executor.submit(config.get_object)] = name
    #         # Space out simultaneous calls to potentially shared DLLs
    #         time.sleep(3)
    #     for future in concurrent.futures.as_completed(future_to_config):
    #         name = future_to_config[future]
    #         try:
    #             driver = future.result()
    #         except Exception as exc:
    #             print(f'{name} generated an exception: {exc}')
    #             log.exception(exc)
    #         else:
    #             loaded[name] = driver

    log.info("Stage objects loaded.")
    return Stage(
        **loaded,
        calibration_matrix = calibration_matrix,
        loaded_position = self.loaded_position,
        unloaded_position = self.unloaded_position,
    )

TSL550Laser (LaserBase)

A laser.

Parameters:

Name Type Description Default
pyroname str

The name of the PyroLab object as registered with the nameserver.

''
ns_host str

The hostname of the PyroLab nameserver (default "localhost").

'localhost'
ns_port int

The port of the PyroLab nameserver (default "9090").

9090
Source code in autogator/hardware.py
class TSL550Laser(LaserBase):
    """
    A laser.

    Parameters
    ----------
    pyroname : str
        The name of the PyroLab object as registered with the nameserver.
    ns_host : str, optional
        The hostname of the PyroLab nameserver (default "localhost").
    ns_port : int, optional
        The port of the PyroLab nameserver (default "9090").
    """
    def __init__(self, pyroname: str = "", ns_host: str = "localhost", ns_port: int = 9090):
        super().__init__(pyroname)
        with locate_ns(host=ns_host, port=ns_port) as ns:
            self.driver = Proxy(ns.lookup(pyroname))
            self.driver.autoconnect()

    def on(self, block=False) -> None:
        """
        Turn on the laser.

        If the laser diode is off, there is a warm-up time before the laser
        diode is ready. If block is True, this function will block until the
        warm-up time is complete.

        Parameters
        ----------
        block : bool, optional
            Whether to block until the warm-up time is complete (default False).
        """
        self.driver._pyroClaimOwnership()
        if self.driver.status()[0] != '-':
            self.driver.on()
            if block:
                while self.driver.status()[0] != '-':
                    time.sleep(5.0)    
        self.driver.open_shutter()

    def off(self, diode: bool = True) -> None:
        """
        Turns off laser output by closing the shutter and optionally turning off the diode.

        Parameters
        ----------
        diode : bool, optional
            Whether to turn off the diode. If False, the laser diode will be
            turned off. There is a warm-up period to turn the laser back on if
            the diode has been turned off. If True, the laser diode will be
            left on but the shutter will be closed.
        """
        self.driver._pyroClaimOwnership()
        self.driver.close_shutter()
        if not diode:
            self.driver.off()

    def power(self, power: float) -> None:
        """
        Sets the laser power in dBm.

        Parameters
        ----------
        power : float
            The power to set the laser to.
        """
        self.driver._pyroClaimOwnership()
        self.driver.power_dBm(power)

    def wavelength(self, wavelength: float) -> None:
        """
        Sets the laser wavelength in nm.

        Parameters
        ----------
        wavelength : float
            The wavelength to set the laser to.
        """
        self.driver._pyroClaimOwnership()
        self.driver.wavelength(wavelength)

    def sweep(self, num: int = 1) -> None:
        """
        Starts the configured wavelength sweep.

        Parameters
        ----------
        num : int, optional
            The number of times to run the wavelength sweep (default 1).
        """
        self.driver._pyroClaimOwnership()
        self.driver.sweep_start(num)

    def sweep_wavelength(self, wl_start: float = 1500, wl_stop: float = 1630, duration: float = 2, number: int = 1):
        """
        Convenience function to run a continuous wavelength sweep.

        Parameters
        ----------
        wl_start : float, optional
            The starting wavelength (default 1500).
        wl_stop : float, optional
            The ending wavelength (default 1630).
        duration : float, optional
            The duration of the sweep (default 2).
        number : int, optional
            The number of times to run the sweep (default 1).
        """
        self.driver._pyroClaimOwnership()
        self.driver.sweep_wavelength(start=wl_start, stop=wl_stop, duration=duration, number=number)

    def sweep_set_mode(
        self, continuous: bool = True, twoway: bool = True, trigger: bool = False, const_freq_step: bool = False
    ) -> None:
        """
        Sets the sweep mode.

        Parameters
        ----------
        continuous : bool
            Continuous (``True``, default) or stepwise (``False``).
        twoway : bool
            Two-way (``True``, default) or one-directional with reset
            (``False``).
        trigger : bool
            Start on external trigger (defaults to ``False``).
        const_freq_step : bool
            Constant frequency interval, requires stepwise mode (defaults to
            ``False``).
        """
        self.driver._pyroClaimOwnership()
        self.driver.sweep_set_mode(
            continuous=continuous,
            twoway=twoway,
            trigger=trigger,
            const_freq_step=const_freq_step,
        )

    def set_trigger(self, mode: str, step: float) -> None:
        """
        Enables trigger output.

        The output trigger can be set to fire at the start of a wavelength
        sweep, at the end of a sweep, or at a fixed step. Valid step range is
        0.004 - 160 nm with a minimum step of 0.0001 nm.

        Parameters
        ----------
        mode : str
            The trigger mode. One of: “None”, “Stop”, “Start”, “Step”.
        step : float
            The trigger step size, in nanometers.
        """
        self.driver._pyroClaimOwnership()
        self.driver.trigger_enable_output()
        triggerMode = self.driver.trigger_set_mode(mode)
        triggerStep = self.driver.trigger_step(step)
        return triggerMode, triggerStep

    def wavelength_logging(self) -> None:
        """
        Downloads the wavelength log.

        Returns
        -------
        list
            The last wavelength log.
        """
        self.driver._pyroClaimOwnership()
        return self.driver.wavelength_logging()

off(self, diode=True)

Turns off laser output by closing the shutter and optionally turning off the diode.

Parameters:

Name Type Description Default
diode bool

Whether to turn off the diode. If False, the laser diode will be turned off. There is a warm-up period to turn the laser back on if the diode has been turned off. If True, the laser diode will be left on but the shutter will be closed.

True
Source code in autogator/hardware.py
def off(self, diode: bool = True) -> None:
    """
    Turns off laser output by closing the shutter and optionally turning off the diode.

    Parameters
    ----------
    diode : bool, optional
        Whether to turn off the diode. If False, the laser diode will be
        turned off. There is a warm-up period to turn the laser back on if
        the diode has been turned off. If True, the laser diode will be
        left on but the shutter will be closed.
    """
    self.driver._pyroClaimOwnership()
    self.driver.close_shutter()
    if not diode:
        self.driver.off()

on(self, block=False)

Turn on the laser.

If the laser diode is off, there is a warm-up time before the laser diode is ready. If block is True, this function will block until the warm-up time is complete.

Parameters:

Name Type Description Default
block bool

Whether to block until the warm-up time is complete (default False).

False
Source code in autogator/hardware.py
def on(self, block=False) -> None:
    """
    Turn on the laser.

    If the laser diode is off, there is a warm-up time before the laser
    diode is ready. If block is True, this function will block until the
    warm-up time is complete.

    Parameters
    ----------
    block : bool, optional
        Whether to block until the warm-up time is complete (default False).
    """
    self.driver._pyroClaimOwnership()
    if self.driver.status()[0] != '-':
        self.driver.on()
        if block:
            while self.driver.status()[0] != '-':
                time.sleep(5.0)    
    self.driver.open_shutter()

power(self, power)

Sets the laser power in dBm.

Parameters:

Name Type Description Default
power float

The power to set the laser to.

required
Source code in autogator/hardware.py
def power(self, power: float) -> None:
    """
    Sets the laser power in dBm.

    Parameters
    ----------
    power : float
        The power to set the laser to.
    """
    self.driver._pyroClaimOwnership()
    self.driver.power_dBm(power)

set_trigger(self, mode, step)

Enables trigger output.

The output trigger can be set to fire at the start of a wavelength sweep, at the end of a sweep, or at a fixed step. Valid step range is 0.004 - 160 nm with a minimum step of 0.0001 nm.

Parameters:

Name Type Description Default
mode str

The trigger mode. One of: “None”, “Stop”, “Start”, “Step”.

required
step float

The trigger step size, in nanometers.

required
Source code in autogator/hardware.py
def set_trigger(self, mode: str, step: float) -> None:
    """
    Enables trigger output.

    The output trigger can be set to fire at the start of a wavelength
    sweep, at the end of a sweep, or at a fixed step. Valid step range is
    0.004 - 160 nm with a minimum step of 0.0001 nm.

    Parameters
    ----------
    mode : str
        The trigger mode. One of: “None”, “Stop”, “Start”, “Step”.
    step : float
        The trigger step size, in nanometers.
    """
    self.driver._pyroClaimOwnership()
    self.driver.trigger_enable_output()
    triggerMode = self.driver.trigger_set_mode(mode)
    triggerStep = self.driver.trigger_step(step)
    return triggerMode, triggerStep

sweep(self, num=1)

Starts the configured wavelength sweep.

Parameters:

Name Type Description Default
num int

The number of times to run the wavelength sweep (default 1).

1
Source code in autogator/hardware.py
def sweep(self, num: int = 1) -> None:
    """
    Starts the configured wavelength sweep.

    Parameters
    ----------
    num : int, optional
        The number of times to run the wavelength sweep (default 1).
    """
    self.driver._pyroClaimOwnership()
    self.driver.sweep_start(num)

sweep_set_mode(self, continuous=True, twoway=True, trigger=False, const_freq_step=False)

Sets the sweep mode.

Parameters:

Name Type Description Default
continuous bool

Continuous (True, default) or stepwise (False).

True
twoway bool

Two-way (True, default) or one-directional with reset (False).

True
trigger bool

Start on external trigger (defaults to False).

False
const_freq_step bool

Constant frequency interval, requires stepwise mode (defaults to False).

False
Source code in autogator/hardware.py
def sweep_set_mode(
    self, continuous: bool = True, twoway: bool = True, trigger: bool = False, const_freq_step: bool = False
) -> None:
    """
    Sets the sweep mode.

    Parameters
    ----------
    continuous : bool
        Continuous (``True``, default) or stepwise (``False``).
    twoway : bool
        Two-way (``True``, default) or one-directional with reset
        (``False``).
    trigger : bool
        Start on external trigger (defaults to ``False``).
    const_freq_step : bool
        Constant frequency interval, requires stepwise mode (defaults to
        ``False``).
    """
    self.driver._pyroClaimOwnership()
    self.driver.sweep_set_mode(
        continuous=continuous,
        twoway=twoway,
        trigger=trigger,
        const_freq_step=const_freq_step,
    )

sweep_wavelength(self, wl_start=1500, wl_stop=1630, duration=2, number=1)

Convenience function to run a continuous wavelength sweep.

Parameters:

Name Type Description Default
wl_start float

The starting wavelength (default 1500).

1500
wl_stop float

The ending wavelength (default 1630).

1630
duration float

The duration of the sweep (default 2).

2
number int

The number of times to run the sweep (default 1).

1
Source code in autogator/hardware.py
def sweep_wavelength(self, wl_start: float = 1500, wl_stop: float = 1630, duration: float = 2, number: int = 1):
    """
    Convenience function to run a continuous wavelength sweep.

    Parameters
    ----------
    wl_start : float, optional
        The starting wavelength (default 1500).
    wl_stop : float, optional
        The ending wavelength (default 1630).
    duration : float, optional
        The duration of the sweep (default 2).
    number : int, optional
        The number of times to run the sweep (default 1).
    """
    self.driver._pyroClaimOwnership()
    self.driver.sweep_wavelength(start=wl_start, stop=wl_stop, duration=duration, number=number)

wavelength(self, wavelength)

Sets the laser wavelength in nm.

Parameters:

Name Type Description Default
wavelength float

The wavelength to set the laser to.

required
Source code in autogator/hardware.py
def wavelength(self, wavelength: float) -> None:
    """
    Sets the laser wavelength in nm.

    Parameters
    ----------
    wavelength : float
        The wavelength to set the laser to.
    """
    self.driver._pyroClaimOwnership()
    self.driver.wavelength(wavelength)

wavelength_logging(self)

Downloads the wavelength log.

Returns:

Type Description

The last wavelength log.

Source code in autogator/hardware.py
def wavelength_logging(self) -> None:
    """
    Downloads the wavelength log.

    Returns
    -------
    list
        The last wavelength log.
    """
    self.driver._pyroClaimOwnership()
    return self.driver.wavelength_logging()

Z825BLinearStage (LinearStageBase)

A linear motor.

Parameters:

Name Type Description Default
pyroname str

The name of the PyroLab object as registered with the nameserver.

''
ns_host str

The hostname of the PyroLab nameserver (default "localhost").

'localhost'
ns_port int

The port of the PyroLab nameserver (default "9090").

9090
Source code in autogator/hardware.py
class Z825BLinearStage(LinearStageBase):
    """
    A linear motor.

    Parameters
    ----------
    pyroname : str
        The name of the PyroLab object as registered with the nameserver.
    ns_host : str, optional
        The hostname of the PyroLab nameserver (default "localhost").
    ns_port : int, optional
        The port of the PyroLab nameserver (default "9090").
    """
    def __init__(self, pyroname: str = "", ns_host: str = "localhost", ns_port: int = 9090) -> None:
        super().__init__(pyroname)
        with locate_ns(host=ns_host, port=ns_port) as ns:
            self.driver = Proxy(ns.lookup(pyroname))
            self.driver.autoconnect()
        self._step_size = None

    @property
    def step_size(self) -> float:
        """The jog step size in mm."""
        return self._step_size

    @step_size.setter
    def step_size(self, step_size: float) -> None:
        if step_size != self._step_size:
            self.driver._pyroClaimOwnership()
            self.driver.jog_step_size = step_size
            self._step_size = step_size

    def move_to(self, position: float) -> None:
        """
        Moves to a new position.

        This motor adjusts for backlash; a given position will always be
        approached from the "negative" direction. That may require overshooting
        the commanded position in order to always approach it again from a
        consistent direction.

        If stepping in short steps, it is therefore most efficient to step from
        negative to positive values to avoid backlash adjustments on each step.

        Parameters
        ----------
        position : float
            The new position to move to.
        """
        self.driver._pyroClaimOwnership()
        if self._requires_backlash_adjustment(position):
            self.driver.move_to(position - (self.driver.backlash * 1.5))
        self.driver.move_to(position)

    def move_by(self, distance: float) -> None:
        """
        Jogs the motor by a fixed distance.

        Parameters
        ----------
        distance : float
            The distance to move the motor. A positive value will move the
            motor forward, and a negative value will move the motor backwards.
        """
        self.driver._pyroClaimOwnership()
        if np.abs(distance) != self.step_size:
            self.step_size = np.abs(distance)
        if distance > 0:
            self.driver.jog("forward")
        else:
            self.driver.jog("backward")

    def move_cont(self, direction: str) -> None:
        """
        Starts a continuous move in the specified direction.

        Parameters
        ----------
        direction : str
            The direction to move the motor, either "forward" or "backward".
        """
        self.driver._pyroClaimOwnership()
        self.driver.move_continuous(direction)

    def _requires_backlash_adjustment(self, position: float) -> bool:
        """
        Determine if the new position command needs to compensate for backlash.

        The ThorLabs linear stages have a small backlash distance. To ensure
        as accurate a reposition as possible when moving to the same location
        multiple times, the motor will always approach the position from the 
        same direction. This function determines whether that requires 
        overshooting the current position before reapproaching.

        Parameters
        ----------
        position : float
            The position to move to.

        Returns
        -------
        bool
            Whether backlash compensation is required.
        """
        if position < self.get_position():
            return True
        return False

    def stop(self) -> None:
        """
        Stop all motion.
        """
        self.driver._pyroClaimOwnership()
        self.driver.stop()

    def get_position(self) -> float:
        """
        Get the current position in millimeters.
        """
        self.driver._pyroClaimOwnership()
        return self.driver.get_position()

    def home(self) -> None:
        """
        Home the motor.
        """
        self.driver._pyroClaimOwnership()
        self.driver.go_home()

    def status(self) -> int:
        """
        Returns a nonzero value if the motor is busy.
        """
        pass

    def close(self) -> None:
        """
        This should close the connection to the cube

        Returns
        -------
        None
        """
        log.info(f"Closing LinearStageBase {self.name}")
        self.driver._pyroClaimOwnership()
        self.driver.close()

step_size: float property writable

The jog step size in mm.

close(self)

This should close the connection to the cube

Source code in autogator/hardware.py
def close(self) -> None:
    """
    This should close the connection to the cube

    Returns
    -------
    None
    """
    log.info(f"Closing LinearStageBase {self.name}")
    self.driver._pyroClaimOwnership()
    self.driver.close()

get_position(self)

Get the current position in millimeters.

Source code in autogator/hardware.py
def get_position(self) -> float:
    """
    Get the current position in millimeters.
    """
    self.driver._pyroClaimOwnership()
    return self.driver.get_position()

home(self)

Home the motor.

Source code in autogator/hardware.py
def home(self) -> None:
    """
    Home the motor.
    """
    self.driver._pyroClaimOwnership()
    self.driver.go_home()

move_by(self, distance)

Jogs the motor by a fixed distance.

Parameters:

Name Type Description Default
distance float

The distance to move the motor. A positive value will move the motor forward, and a negative value will move the motor backwards.

required
Source code in autogator/hardware.py
def move_by(self, distance: float) -> None:
    """
    Jogs the motor by a fixed distance.

    Parameters
    ----------
    distance : float
        The distance to move the motor. A positive value will move the
        motor forward, and a negative value will move the motor backwards.
    """
    self.driver._pyroClaimOwnership()
    if np.abs(distance) != self.step_size:
        self.step_size = np.abs(distance)
    if distance > 0:
        self.driver.jog("forward")
    else:
        self.driver.jog("backward")

move_cont(self, direction)

Starts a continuous move in the specified direction.

Parameters:

Name Type Description Default
direction str

The direction to move the motor, either "forward" or "backward".

required
Source code in autogator/hardware.py
def move_cont(self, direction: str) -> None:
    """
    Starts a continuous move in the specified direction.

    Parameters
    ----------
    direction : str
        The direction to move the motor, either "forward" or "backward".
    """
    self.driver._pyroClaimOwnership()
    self.driver.move_continuous(direction)

move_to(self, position)

Moves to a new position.

This motor adjusts for backlash; a given position will always be approached from the "negative" direction. That may require overshooting the commanded position in order to always approach it again from a consistent direction.

If stepping in short steps, it is therefore most efficient to step from negative to positive values to avoid backlash adjustments on each step.

Parameters:

Name Type Description Default
position float

The new position to move to.

required
Source code in autogator/hardware.py
def move_to(self, position: float) -> None:
    """
    Moves to a new position.

    This motor adjusts for backlash; a given position will always be
    approached from the "negative" direction. That may require overshooting
    the commanded position in order to always approach it again from a
    consistent direction.

    If stepping in short steps, it is therefore most efficient to step from
    negative to positive values to avoid backlash adjustments on each step.

    Parameters
    ----------
    position : float
        The new position to move to.
    """
    self.driver._pyroClaimOwnership()
    if self._requires_backlash_adjustment(position):
        self.driver.move_to(position - (self.driver.backlash * 1.5))
    self.driver.move_to(position)

status(self)

Returns a nonzero value if the motor is busy.

Source code in autogator/hardware.py
def status(self) -> int:
    """
    Returns a nonzero value if the motor is busy.
    """
    pass

stop(self)

Stop all motion.

Source code in autogator/hardware.py
def stop(self) -> None:
    """
    Stop all motion.
    """
    self.driver._pyroClaimOwnership()
    self.driver.stop()
handler: python
rendering:
  show_root_heading: false
  show_source: false
Back to top