Positioners#

PVPositioner#

For example, this code defines a CS700 temperature controller. A temperature controller is a kind of positioner, from ophyd’s point of view, where the “position” is the temperature.

from ophyd import PVPositioner, EpicsSignal, EpicsSignalRO
from ophyd import Component as Cpt

# Define a new kind of device.

class CS700TemperatureController(PVPositioner):
    setpoint = Cpt(EpicsSignal, 'T-SP')
    readback = Cpt(EpicsSignalRO, 'T-I')
    done = Cpt(EpicsSignalRO, 'Cmd-Busy')
    stop_signal = Cpt(EpicsSignal, 'Cmd-Cmd')

# Create an instance of this new kind of device.

prefix = 'XF:28IDC-ES:1{Env:01}'
cs700 = CS700TemperatureController(prefix, name='cs700')

# When the cs700 has reached the set-point temperature, the 'done' signal
# flips to 0.
cs700.done_value = 0
class ophyd.pv_positioner.PVPositioner(prefix='', *, limits=None, name=None, read_attrs=None, configuration_attrs=None, parent=None, egu='', **kwargs)#

A Positioner which is controlled using multiple user-defined signals

Keyword arguments are passed through to the base class, Positioner

Parameters:
prefixstr, optional

The device prefix used for all sub-positioners. This is optional as it may be desirable to specify full PV names for PVPositioners.

limits2-element sequence, optional

(low_limit, high_limit)

namestr

The device name

egustr, optional

The engineering units (EGU) for the position

settle_timefloat, optional

The amount of time to wait after moves to report status completion

timeoutfloat, optional

The default timeout to use for motion requests, in seconds.

Attributes:
setpointSignal

The setpoint (request) signal

readbackSignal or None

The readback PV (e.g., encoder position PV)

actuateSignal or None

The actuation PV to set when movement is requested

actuate_valueany, optional

The actuation value, sent to the actuate signal when motion is requested

stop_signalSignal or None

The stop PV to set when motion should be stopped

stop_valueany, optional

The value sent to stop_signal when a stop is requested

doneSignal

A readback value indicating whether motion is finished

done_valueany, optional

The value that the done pv should be when motion has completed

put_completebool, optional

If set, the specified PV should allow for asynchronous put completion to indicate motion has finished. If actuate is specified, it will be used for put completion. Otherwise, the setpoint will be used. See the -c option from caput for more information.

PVPositionerComparator#

As an example, take a shutter that has a setpoint PV and a readback PV. The setpoint PV takes only CLOSE_VALVE and OPEN_VALVE commands. The readback PV goes through the states OPEN -> MOVING -> CLOSED -> MOVING and back to OPEN. The done_comparator() method knows that the movement is done when the readback argument is not the MOVING state.

From Bluesky, this device can be moved like any other motor.

from ophyd.pv_positioner import PVPositionerComparator
from ophyd import Component, EpicsSignal, EpicsSignalRO

from enum import IntEnum, unique

from typing import Any

@unique
class SetpointCmd(IntEnum):
  CLOSE_VALVE = 1
  OPEN_VALVE = 2

@unique
class ReadbackStatus(IntEnum):
  UNKNOWN = 0
  INVALID = 1
  CLOSED = 2
  OPEN = 3
  MOVING = 4

class Shutter(PVPositionerComparator):
  setpoint = Component(EpicsSignal, ":OPEN_CLOSE_CMD")
  readback = Component(EpicsSignalRO, ":OPEN_CLOSE_STATUS")

  def __init__(self, prefix: str, *, name: str, **kwargs):
    kwargs.update({"limits": (SetpointCmd.CLOSE_VALVE,
                              SetpointCmd.OPEN_VALVE)})
    super().__init__(prefix, name=name, **kwargs)

  def done_comparator(self, readback:Any, setpoint:Any) -> bool:
    return readback != ReadbackStatus.MOVING
class ophyd.pv_positioner.PVPositionerComparator(prefix: str, *, name: str, **kwargs)#

PV Positioner with a software done signal.

The done state is set by a comparison function defined in the class body. The comparison function takes two arguments, readback and setpoint, returning True if we are considered done or False if we are not.

This class is intended to support PVPositionerIsClose, but exists to allow some flexibility if we want to use other metrics for deciding if the PVPositioner is done.

Internally, this will subscribe to both the setpoint and readback signals, updating done as appropriate.

Parameters:
prefixstr, optional

The device prefix used for all sub-positioners. This is optional as it may be desirable to specify full PV names for PVPositioners.

limits2-element sequence, optional

(low_limit, high_limit)

namestr

The device name

egustr, optional

The engineering units (EGU) for the position

settle_timefloat, optional

The amount of time to wait after moves to report status completion

timeoutfloat, optional

The default timeout to use for motion requests, in seconds.

Attributes:
setpointSignal

The setpoint (request) signal

readbackSignal or None

The readback PV (e.g., encoder position PV)

actuateSignal or None

The actuation PV to set when movement is requested

actuate_valueany, optional

The actuation value, sent to the actuate signal when motion is requested

stop_signalSignal or None

The stop PV to set when motion should be stopped

stop_valueany, optional

The value sent to stop_signal when a stop is requested

put_completebool, optional

If set, the specified PV should allow for asynchronous put completion to indicate motion has finished. If actuate is specified, it will be used for put completion. Otherwise, the setpoint will be used. See the -c option from caput for more information.

class ophyd.pv_positioner.PVPositionerIsClose(prefix: str, *, name: str, atol: float | None = None, rtol: float | None = None, **kwargs)#

PV Positioner that updates done state based on np.isclose.

Effectively, this will treat our move as complete if the readback is sufficiently close to the setpoint. This is generically helpful for PV positioners that don’t have a done signal built into the hardware.

The arguments atol and rtol can be set as class attributes or passed as initialization arguments.

atol is a measure of absolute tolerance. If atol is 0.1, then you’d be able to be up to 0.1 units away and still count as done. This is typically the most useful parameter for calibrating done tolerance.

rtol is a measure of relative tolerance. If rtol is 0.1, then you’d be able to deviate from the goal position by up to 10% of its value. This is useful for small quantities. For example, defining an atol for a positioner that ranges from 1e-8 to 2e-8 could be somewhat awkward.

Parameters:
prefixstr, optional

The device prefix used for all sub-positioners. This is optional as it may be desirable to specify full PV names for PVPositioners.

limits2-element sequence, optional

(low_limit, high_limit)

namestr

The device name

egustr, optional

The engineering units (EGU) for the position

settle_timefloat, optional

The amount of time to wait after moves to report status completion

timeoutfloat, optional

The default timeout to use for motion requests, in seconds.

atolfloat, optional

A measure of absolute tolerance. If atol is 0.1, then you’d be able to be up to 0.1 units away and still count as done.

rtolfloat, optional

A measure of relative tolerance. If rtol is 0.1, then you’d be able to deviate from the goal position by up to 10% of its value

Attributes:
setpointSignal

The setpoint (request) signal

readbackSignal or None

The readback PV (e.g., encoder position PV)

actuateSignal or None

The actuation PV to set when movement is requested

actuate_valueany, optional

The actuation value, sent to the actuate signal when motion is requested

stop_signalSignal or None

The stop PV to set when motion should be stopped

stop_valueany, optional

The value sent to stop_signal when a stop is requested

put_completebool, optional

If set, the specified PV should allow for asynchronous put completion to indicate motion has finished. If actuate is specified, it will be used for put completion. Otherwise, the setpoint will be used. See the -c option from caput for more information.

atolfloat, optional

A measure of absolute tolerance. If atol is 0.1, then you’d be able to be up to 0.1 units away and still count as done.

rtolfloat, optional

A measure of relative tolerance. If rtol is 0.1, then you’d be able to deviate from the goal position by up to 10% of its value

PseudoPositioner#

An ophyd PseudoPositioner relates one or more pseudo (virtual) axes to one or more real (physical) axes via forward and inverse calculations. To define such a PseudoPositioner, one must subclass from PseudoPositioner:

from ophyd import (PseudoPositioner, PseudoSingle, EpicsMotor)
from ophyd import (Component as Cpt)
from ophyd.pseudopos import (pseudo_position_argument,
                             real_position_argument)


class Pseudo3x3(PseudoPositioner):
    # The pseudo positioner axes:
    px = Cpt(PseudoSingle, limits=(-10, 10))
    py = Cpt(PseudoSingle, limits=(-10, 10))
    pz = Cpt(PseudoSingle)

    # The real (or physical) positioners:
    rx = Cpt(EpicsMotor, 'XF:31IDA-OP{Tbl-Ax:X1}Mtr')
    ry = Cpt(EpicsMotor, 'XF:31IDA-OP{Tbl-Ax:X2}Mtr')
    rz = Cpt(EpicsMotor, 'XF:31IDA-OP{Tbl-Ax:X3}Mtr')

    @pseudo_position_argument
    def forward(self, pseudo_pos):
        '''Run a forward (pseudo -> real) calculation'''
        return self.RealPosition(rx=-pseudo_pos.px,
                                 ry=-pseudo_pos.py,
                                 rz=-pseudo_pos.pz)

    @real_position_argument
    def inverse(self, real_pos):
        '''Run an inverse (real -> pseudo) calculation'''
        return self.PseudoPosition(px=-real_pos.rx,
                                   py=-real_pos.ry,
                                   pz=-real_pos.rz)

Pseudo3x3 above is a pseudo positioner with 3 pseudo axes and 3 real axes. The pseudo axes are defined in order as (px, py, pz). Similarly, the real positioners are (rx, ry, rz).

There is no restriction that the real axes must be tied to physical hardware. A physical axis could just as well be a SoftPositioner, or any subclass of PositionerBase (with the sole exception of PseudoSingle).

The forward calculation says that, for any given pseudo position, the real motors should move to the opposite position. For example, for a pseudo position of (px=1, py=2, pz=3), the corresponding real position would be (rx=-1, ry=-2, rz=-3). The inverse calculation is similar, in going from a real position to a pseudo position.

The two decorators @real_position_argument and @pseudo_position_argument are used here for convenience so that one can call these functions in a variety of ways, all of which generate a correct PseudoPosition tuple as the first argument to your calculation method. Positions can be specified in the following ways:

  • As positional arguments:

pseudo.forward(px, py, pz)
  • As a sequence or PseudoPosition/RealPosition:

pseudo.forward((px, py, pz))
pseudo.forward(pseudo.PseudoPosition(px, py, pz))
  • As kwargs:

pseudo.forward(px=1, py=2, pz=3)

move is decorated like this on PseudoPositioner, meaning you can also call it with this syntax.

class ophyd.pseudopos.PseudoSingle(prefix='', *, limits=None, egu='', parent=None, name=None, source='computed', target_initial_position=False, **kwargs)#

A single axis of a PseudoPositioner

This should not be instantiated on its own, but rather used as a Component in a PseudoPositioner subclass.

Parameters:
prefixstr, optional

The PV prefix, for compatibility with the Device hierarchy

limits(low_limit, high_limit)

User-defined limits for this pseudo axis.

egustr, optional

The engineering units (EGU) for the position

parentPseudoPositioner instance

The instance of the parent PseudoPositioner

namestr, optional

The name of the positioner

sourcestr, optional

Metadata indicating the source of this positioner’s position. Defaults to ‘computed’

target_initial_positionbool, optional

Use initial inverse-calculated position as the default setpoint/target for this axis

settle_timefloat, optional

The amount of time to wait after moves to report status completion

timeoutfloat, optional

The default timeout to use for motion requests, in seconds.

class ophyd.pseudopos.PseudoPositioner(prefix='', *, concurrent=True, read_attrs=None, configuration_attrs=None, name, egu='', auto_target=True, **kwargs)#

A pseudo positioner which can be comprised of multiple positioners

Parameters:
prefixstr

The PV prefix for all components of the device

concurrentbool, optional

If set, all real motors will be moved concurrently. If not, they will be moved in order of how they were defined initially

read_attrssequence of attribute names

the components to include in a normal reading (i.e., in read())

configuration_attrssequence of attribute names

the components to be read less often (i.e., in read_configuration()) and to adjust via configure()

namestr, optional

The name of the device

egustr, optional

The user-defined engineering units for the whole PseudoPositioner

auto_targetbool, optional

Automatically set the target position of PseudoSingle devices when moving to a single PseudoPosition

parentinstance or None

The instance of the parent device, if applicable

settle_timefloat, optional

The amount of time to wait after moves to report status completion

timeoutfloat, optional

The default timeout to use for motion requests, in seconds.

SoftPositioner#

A SoftPositioner is a positioner which has no corresponding physical motor. On its own, it is most useful for debugging scanning logic when moving physical motors is either undesirable or not possible.

Used as-is, a SoftPositioner will “move” to the requested position immediately.

PseudoSingle and PseudoPositioner, for example, are implemented as heavily customized SoftPositioner subclasses.

class ophyd.positioner.PositionerBase(*, name=None, parent=None, settle_time=0.0, timeout=None, **kwargs)#

The positioner base class

Subclass from this to implement your own positioners.

Note

Subclasses should add an additional ‘wait’ keyword argument on the move method. The MoveStatus object returned from PositionerBase can then be waited on after the subclass finishes the motion configuration.

class ophyd.positioner.SoftPositioner(*, egu='', limits=None, source='computed', init_pos=None, **kwargs)#

A positioner which does not communicate with any hardware

SoftPositioner ‘moves’ immediately to the target position when commanded to do so.

Parameters:
limits(low_limit, high_limit)

Soft limits to use

egustr, optional

Engineering units (EGU) for a position

sourcestr, optional

Metadata indicating the source of this positioner’s position. Defaults to ‘computed’

init_posfloat, optional

Create the positioner with this starting position. Defaults to None.

from ophyd import SoftPositioner
my_positioner = SoftPositioner(name='my_positioner')