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

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)


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.


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


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.


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,

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')

    def forward(self, pseudo_pos):
        '''Run a forward (pseudo -> real) calculation'''
        return self.RealPosition(rx=-pseudo_pos.px,

    def inverse(self, real_pos):
        '''Run an inverse (real -> pseudo) calculation'''
        return self.PseudoPosition(px=-real_pos.rx,

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.

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


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.


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.


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.

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')