# Area Detectors¶

Area Detector devices require some customization to use. Here is the simplest possible configuration.

from ophyd import AreaDetector, SingleTrigger

pass

prefix = 'XF:23ID1-ES{Tst-Cam:1}'
det = MyDetector(prefix, name="det")


The above should work correctly with any EPICS Area Detector. We test on versions 1.9.1 and 2.2. For preliminary support for AD33 see the nslsii package.

## Callbacks¶

Internally, Area Detector provides a flexible array processing pipeline. The pipeline is a chain of ‘plugins’ which can be re-configured at runtime by setting the .nd_array_port on a downstream plugin to the .port_name of the upstream plugin. Internally the plugins pass data between each other by passing a pointer to an NDArray C++ object (which is an array plus some meta-data). The arrays are allocated out of a shared pool when they are created (typically by the ‘cam’ plugin which wraps the detector driver) and freed when the last plugin is done with them. Each plugin can trigger its children in two ways:

• blocking : The next plugin is called syncronously, blocking the parent plugin until all of the (blocking) children are finished. This is single-threaded.
• non-blocking : The pointer is put on a queue that the child consumes from. This allows multi-threaded processing with each plugin running on its own thread.

This behavior is controlled by the .blocking_callbacks signal on the plugin.

The SingleTrigger sets the acquire bit ‘high’ and then watches for it to go low (indicating that acquisition is complete). If any of the down-stream plugins are in non-blocking mode are likely to have the following sequence of events when using, for example, the Stats plugin and taking one frame

1. detector produces the frame
2. puts the frame on the queue for the stats plugin to consume
3. flips the acquire bit to ‘low’
4. ophyd sees the acquire bit go low and marks the status object as done
5. bluesky continues with the plan and reads the Stats plugin (which still contains old data)
6. the Stats plugin processes the frame (updating the values for the just-collected frame)

Because (6) happens after (5) bluesky reads ‘stale’ data from the stats plugin and produces an event which associates other measurements with the incorrect reading from the camera. This issue has resulted in alignment scans systematically returning the values from the previous point. To avoid this, we ensure in stage() that all plugins are in ‘blocking’ mode. This has the downside of slowing the detector down as we are only using a single thread but has the advantage of giving correct measurements.

Prior to AD3-3, AD did not track if a given frame had fully propagated through the pipeline. We looked into tracking this from the outside and using this to determine when the data acquisition was done. In principle this could be done by watching a combination of queue size and the .uniqueID signal, however this work was abandoned due to the complexity of supporting this for all of the version of AD on the floor.

In AD3-3, the camera now tracks if all of the frames it produces have been processed (added to support ophyd [1] ). There is now a .wait_for_plugins signal that controls the behavior of put-complete on the .acquire signal. If .wait_for_plugins is True, then the put-complete callback on the .acquire signal will not process until all of the frames have been processed by all of the plugins.

This allows us to run with all of the plugins in non-blocking mode and to simplify the trigger logic. Instead of waiting for the acquire bit to change value, we use the a put-completion callback.

To convert an existing area detector sub-class to support the new scheme you must:

1. Change the type of the came to sub-class nslsii.ad33.CamV33Mixin
2. Change the trigger mixin to be nslsii.ad33.SingleTriggerV33
3. Arrange for det.cam.ensure_nonblocking to be called after initializing the ophyd object.

## Ports¶

Each plugin has a read-only out-put port name (.port_name) and a settable in-put port name (.nd_array_port). To connect plugin downstream to plugin upstream set downstream.nd_array_port to upstream.port_name.

The top-level ~base.ADBase class has several helper methods for walking and validating the plugin network.

 ADBase([prefix, kind, read_attrs, …]) The AreaDetector base class visualize_asyn_digraph([ax]) This generates a figure showing the current asyn port layout. get_plugin_by_asyn_port(port_name) Get the plugin which has the given asyn port name get_asyn_port_dictionary() Return port name : component map get_asyn_digraph() Get the directed graph of the ASYN ports validate_asyn_ports() Validate that all components of pipeline are known missing_plugins() Find missing ports

## Filestore Plugins¶

Note

The mixins in this section are to be mixed with the file plugin classes and used as components on a larger device. The siblings of the resulting classes are components representing the various plugins that make up an Area Detector.

Integration of the file writing with filestore is done by mixing sub-classes of FileStorePluginBase into one of the file plugin classes and using the resulting class as a component in your detector.

The base classes (which may be merged in the future)

 FileStoreBase(*args, write_path_template[, …]) Base class for FileStore mixin classes FileStorePluginBase(*args, **kwargs)

provide the basic methods required for integrating AreaDetector file plugins with filestore

 generate_datum(key, timestamp, datum_kwargs) Generate a uid and cache it with its key for later insertion. write_path_template reg_root The ‘root’ put into the Asset Registry fs_root DEPRECATED: The ‘root’ put into the Asset registry, use reg_root read_path_template Returns write_path_template if read_path_template is not set make_filename() Make a filename.

~filestore_mixins.FileStorePluginBase must be sub-classed to match each file plugin and take care of inserting the correct meta-data into FileStore and configuring the file plugin.

 FileStoreTIFF(*args, **kwargs) FileStoreHDF5(*args, **kwargs) FileStoreTIFFSquashing(*args[, …]) Write out ‘squashed’ tiffs

The FileStoreTIFFSquashing also makes use of the processing plugin to ‘squash’ multiple frames together into a single saved image.

To create a functioning class you must also mixin

 FileStoreIterativeWrite(*args, **kwargs) This adds ‘point_number’ to datum_kwargs.

which extends generate_datum() to insert into the FileStore instance as data is taken.

For convenience we provide

 FileStoreHDF5IterativeWrite(*args, **kwargs) FileStoreTIFFIterativeWrite(*args, **kwargs)

## Area Detector Trigger dispatching¶

Note

The mixins in this section are to be mixed with Device to represent the ‘top level’ area detector. The components of the resulting class are the various plugins that make up a full Area Detector.

 DetectorBase([prefix, kind, read_attrs, …]) The base class for the hardware-specific classes that follow. dispatch(key, timestamp) Notify plugins of acquisition being complete. make_data_key()

The translation between the trigger() and triggering the underlying camera is mediated by the trigger mix-ins.

 TriggerBase(*args, **kwargs) Base class for trigger mixin classes SingleTrigger(*args[, image_name]) This trigger mixin class takes one acquisition per trigger. MultiTrigger(*args[, trigger_cycle]) This trigger mixin class can take multiple acquisitions per trigger.

## Plugins¶

 PluginBase(*args, **kwargs) AreaDetector plugin base class ColorConvPlugin(*args, **kwargs) ImagePlugin(*args, **kwargs) OverlayPlugin(*args, **kwargs) Plugin which adds graphics overlays to an NDArray image ProcessPlugin(*args, **kwargs) ROIPlugin(*args, **kwargs) StatsPlugin(*args, **kwargs) TransformPlugin(*args, **kwargs)

## File Plugins¶

 FilePlugin(*args, **kwargs) HDF5Plugin(*args, **kwargs) JPEGPlugin(*args, **kwargs) MagickPlugin(*args, **kwargs) NetCDFPlugin(*args, **kwargs) NexusPlugin(*args, **kwargs) TIFFPlugin(*args, **kwargs)

## Specific Hardware¶

While the above example should work with any Area Detector, ophyd provides specialized classes for specific detectors supported by EPICS Area Detector. These specialized classes generally add components representing fields particular to a given detector, along with device-specific documentation for components.

To use these model-specific classes, swap out AreaDetector like so:

# before
pass

# after
class MyDetector(SingleTrigger, AndorDetector):
pass

 AreaDetector([prefix, kind, read_attrs, …]) AdscDetector([prefix, kind, read_attrs, …]) Andor3Detector([prefix, kind, read_attrs, …]) AndorDetector([prefix, kind, read_attrs, …]) BrukerDetector([prefix, kind, read_attrs, …]) FirewireLinDetector([prefix, kind, …]) FirewireWinDetector([prefix, kind, …]) LightFieldDetector([prefix, kind, …]) Mar345Detector([prefix, kind, read_attrs, …]) MarCCDDetector([prefix, kind, read_attrs, …]) PSLDetector([prefix, kind, read_attrs, …]) PerkinElmerDetector([prefix, kind, …]) PilatusDetector([prefix, kind, read_attrs, …]) PixiradDetector([prefix, kind, read_attrs, …]) PointGreyDetector([prefix, kind, …]) ProsilicaDetector([prefix, kind, …]) PvcamDetector([prefix, kind, read_attrs, …]) RoperDetector([prefix, kind, read_attrs, …]) SimDetector([prefix, kind, read_attrs, …]) URLDetector([prefix, kind, read_attrs, …])

### Cams¶

The vendor specific details are embedded in the cams

 CamBase([prefix, kind, read_attrs, …]) AdscDetectorCam([prefix, kind, read_attrs, …]) Andor3DetectorCam([prefix, kind, …]) AndorDetectorCam([prefix, kind, read_attrs, …]) BrukerDetectorCam([prefix, kind, …]) FirewireLinDetectorCam([prefix, kind, …]) FirewireWinDetectorCam([prefix, kind, …]) LightFieldDetectorCam([prefix, kind, …]) Mar345DetectorCam([prefix, kind, …]) MarCCDDetectorCam([prefix, kind, …]) PSLDetectorCam([prefix, kind, read_attrs, …]) PcoDetectorCam([prefix, kind, read_attrs, …]) PcoDetectorIO([prefix, kind, read_attrs, …]) PcoDetectorSimIO([prefix, kind, read_attrs, …]) PerkinElmerDetectorCam([prefix, kind, …]) PilatusDetectorCam([prefix, kind, …]) PixiradDetectorCam([prefix, kind, …]) PointGreyDetectorCam([prefix, kind, …]) ProsilicaDetectorCam([prefix, kind, …]) PvcamDetectorCam([prefix, kind, read_attrs, …]) RoperDetectorCam([prefix, kind, read_attrs, …]) SimDetectorCam([prefix, kind, read_attrs, …]) URLDetectorCam([prefix, kind, read_attrs, …])

## Custom Devices¶

For custom hardware based on area-detector it may be necesary to add a custom device class (for custom plugins see section below). The new class should inherit from ophyd.areadetector.base.ADbase and should have the following PV structure:

PV = 'Areadetector_device_PV_prefix:(Plugin_suffix or attribute_suffix)'


As an example, for the builtin areadetector ‘stats’ class this looks like:

PV = 'Areadetector_device_PV_prefix:Stats'


And for the builtin areadetector ‘color mode’ attribute it looks like:

PV = 'Areadetector_device_PV_prefix:cam1:ColorMode_RBV'


where 'Areadetector_device_PV_prefix' is the base PV name for the Area detector device, plugin_suffix = 'Stats' is the ‘stats’ Plugin suffix and attribute_suffix = 'ColorMode_RBV' is the ‘color mode’ attribute suffix of the 'cam1' plugin.

In order to create the class then the following code is required (where XXX is the name of the device):

from ophyd.areadetector.base import ad_group, EpicsSignalWithRBV
from ophyd.signal import EpicsSignal, EpicsSignalRO
from ophyd.device import DynamicDeviceComponent as DDCpt, Component as Cpt
from ophyd.detectors import DetectorBase

class XXX(SingleTrigger, DetectorBase):
'''An areadetector device class for ...'''

# ADD ATTRIBUTES AS COMPONENTS HERE USING THE SYNTAX
# where 'Type' is EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV,..
attribute_name = Cpt(Type, attribute_suffix)

# ADD ATTRIBUTE GROUPS AS COMPONENTS USING THE SYNTAX
(attribute_1_name, attribute_1_suffix),
(attribute_2_name, attribute_2_suffix),
...,
(attribute_n_name, attribute_n_suffix))

# ADD ATTRIBUTE PLUGINS AS COMPONENTS USING THE SYNTAX
plugin_name = Cpt(PluginClass, suffix=Plugin_suffix+':')


Note

1. ophyd.areadetector.detectors.DetectorBase can be swapped out for any other Areadetector Device class that inherits from ophyd.areadetector.detectors.DetectorBase.
2. ophyd.areadetector.triggermixins.SingleTrigger is an optional trigger_mixin class and can be swapped out for any other class that inherits from ophyd.areadetector.trigger_mixins.TriggerBase. These classes provide the logic to ‘trigger’ the detector and actually acquire the images.
3. PluginClass can be ophyd.areadetector.plugins.PluginBase, ophyd.areadetector.cam.CamBase or any plugin/cam class that inherits from either of these.
4. In the ophyd source code, you may see ophyd.areadetector.base.ADComponent used. Functionally, this is interchangeable with an ordinary ophyd.device.Component (imported as Cpt above); it just adds extra machinery for generating a docstring based on a scrape of the HTML of the official AreaDetector documentation. For custom extensions such as we are addressing here, it is not generally applicable.

The Areadetector device should then be instantiated using:

ADdevice_name = Some_Areadetector_Device_Class(Areadetector_device_PV_suffix,


## Custom Plugins or Cameras¶

For custom hardware based on area-detector it may be necesary to add a custom plugin or camera class, this section will cover what is required. Both ‘plugins’ and ‘cameras’ act in the same way, but have slightly different ‘base’ attributes, hence they have different ‘base classes’. New Plugin classes should inherit from ophyd.areadetector.base.PluginBase while new Camera classes should inherit from ophyd.areadetector.cam.CamBase. Both should have the following PV structure (replace ‘plugin’ with ‘cam’ for cameras):

PV = 'Areadetector_device_PV_prefix:Plugin_suffix:attribute_suffix'


As an example, for the ‘max value’ component of the built-in areadetector ‘stats’ class this looks like:

PV = 'Areadetector_device_PV_prefix:Stats:max_value'


where Areadetector_device_PV_prefix is the PV name for the Area detector device, plugin_suffix = Stats is the ‘stats’ Plugin suffix and attribute_suffix = max_value is the ‘max value’ attribute suffix.

In order to create the class then the following code is required (where XXX is the name of the plugin):

from ophyd.areadetector.base import ad_group, EpicsSignalWithRBV
from ophyd.signal import EpicsSignal, EpicsSignalRO
from ophyd.device import DynamicDeviceComponent as DDCpt, Component as Cpt

class XXXplugin(PluginBase, FileStoreHDF5):
'''An areadetector plugin class that does ......'''
_suffix_re = 'Plugin_suffix\d:'

# ADD ATTRIBUTES AS COMPONENTS HERE USING THE SYNTAX
attribute_name = Cpt(Type, attribute_suffix)
# where 'Type' is EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV,..

# ADD ATTRIBUTE GROUPS AS COMPONENTS USING THE SYNTAX
(attribute_1_name, attribute_1_suffix),
(attribute_2_name, attribute_2_suffix),
...,
(attribute_n_name, attribute_n_suffix))

# this allows searching for a plugin class via matching _suffix_re for
# classes in the registry against the a PV name and is optional.
register_plugin(XXXplugin)


Note

1. ophyd.areadetector.plugins.PluginBase can be swapped out for ophyd.areadetector.cam.CamBase, ophyd.areadetector.plugins.FilePlugin or any other Areadetector Plugin, cam or FilePlugin class that inherits from these.
2. For FilePlugin plugins the optional filestore_mixin ophyd.areadetector.filestore_mixins.FileStoreHDF5 should also be defined. This can be replaced with any class that inherits from ophyd.areadetector.filestore_mixins.FileStorePluginBase. These mix-in classes provide the logic for generating Asset Registry documents.

Once the class is defined above then it should be added to the Area detector device class as a component using the code:

class Some_Areadetector_Device_Class(Some_Area_Detector_Base_Class):
'The ophyd class for the device that has the custom plugin'

...

xxx = Cpt(XXXplugin, suffix=Plugin_suffix+':')

...


The Areadetector device should then be instantiated using:

ADdevice_name = Some_Areadetector_Device_Class(Areadetector_device_PV_suffix,

 EpicsSignalWithRBV(prefix, **kwargs) ADComponent(cls[, suffix, lazy]) ad_group(cls, attr_suffix, **kwargs) Definition creation for groups of signals in areadetectors