ADPerkinElmer Area Detector with default HDF5 File Name#

Demonstrate the setup of an ADPerkinElmer camera driver (part of EPICS area detector) to acquire an image with bluesky and write it to an HDF5 file.

EPICS Area Detector IOC#

This Perkin-Elmer detector IOC shares its file system with the Bluesky workstation. Thus, the file path is the same on both the IOC (AD_IOC_MOUNT_PATH) and the Bluesky (local) workstation (BLUESKY_MOUNT_PATH). Following the setup in the File-Directories section of the basic example:

import pathlib

IOC = "PE1:"  # TODO: replace with your IOC's prefix

AD_IOC_MOUNT_PATH = pathlib.Path("/local/data")  # TODO: use yours
BLUESKY_MOUNT_PATH = pathlib.Path("/local/data")  # TODO: use yours
IMAGE_DIR = "example/%Y/%m/%d"

# MUST end with a `/`, pathlib will NOT provide it
WRITE_PATH_TEMPLATE = f"{AD_IOC_MOUNT_PATH / IMAGE_DIR}/"
READ_PATH_TEMPLATE = f"{BLUESKY_MOUNT_PATH / IMAGE_DIR}/"

ophyd#

The ADPerkinElmer driver is supported by ophyd.areadetector’s PerkinElmerDetectorCam class. We’ll use that class here since we want to update that class for changes in area detector’s ADCore.

Note that ophyd makes a distinction (using the Perkin-Elmer here as an example) between PerkinElmerDetector and PerkinElmerDetectorCam. The PerkinElmerDetector includes PerkinElmerDetectorCam as a component to build a useful detector. We must customize the PerkinElmerDetectorCam class with updates, so we can’t use the stock PerkinElmerDetector.

from apstools.devices import CamMixin_V34
from ophyd.areadetector import PerkinElmerDetectorCam

class PerkinElmerDetectorCam_V34(CamMixin_V34, PerkinElmerDetectorCam):
    """Update PerkinElmerDetectorCam to ADCore 3.4+."""

Build a class which configures the HDF5 plugin with the data acquisition methods we need:

from ophyd.areadetector.filestore_mixins import FileStoreHDF5IterativeWrite
from ophyd.areadetector.plugins import HDF5Plugin_V34 as HDF5Plugin

class MyHDF5Plugin(FileStoreHDF5IterativeWrite, HDF5Plugin):
    """
    Add data acquisition methods to HDF5Plugin.

    * ``stage()`` - prepare device PVs befor data acquisition
    * ``unstage()`` - restore device PVs after data acquisition
    * ``generate_datum()`` - coordinate image storage metadata
    """

    def stage(self):
        self.stage_sigs.move_to_end("capture", last=True)
        super().stage()

NOTE: If you want to control the HDF5 file naming with EPICS PVs (in area detector), then replace the MyHDF5Plugin class with AD_EpicsFileNameHDF5Plugin when creating the detector class below and follow the additional instructions and cautions in the HDF5: AD_EpicsFileNameHDF5Plugin section of the custom HDF5 image file names example. You’ll need this import:

from apstools.devices import AD_EpicsFileNameHDF5Plugin

Then, build the detector class which puts it all together:

from apstools.devices import SingleTrigger_V34
from ophyd import ADComponent
from ophyd.areadetector import DetectorBase

class PerkinElmerDetector_V34(SingleTrigger_V34, DetectorBase):
    """
    ADSimDetector

    SingleTrigger:

    * stop any current acquisition
    * sets image_mode to 'Multiple'
    """

    cam = ADComponent(PerkinElmerDetectorCam_V34, "cam1:")
    hdf1 = ADComponent(
        MyHDF5Plugin,
        "HDF1:",
        write_path_template=WRITE_PATH_TEMPLATE,
        read_path_template=READ_PATH_TEMPLATE,
    )
    image = ADComponent(ImagePlugin, "image1:")

Create the Python object for the detector:

det_pe = PerkinElmerDetector_V34(IOC, name="det_pe")
det_pe.wait_for_connection(timeout=15)

det_pe.missing_plugins()  # confirm all plugins are defined
det_pe.read_attrs.append("hdf1")  # include `hdf1` plugin with 'det_pe.read()'

# override default settings from ophyd
# Plugins will tell the camera driver when acquisition is finished.
# RunEngine will wait until `PE1:cam1:AcquireBusy_RBV` PV goes to zero.
det_pe.cam.stage_sigs["wait_for_plugins"] = "Yes"
det_pe.hdf1.stage_sigs["blocking_callbacks"] = "No"
det_pe.image.stage_sigs["blocking_callbacks"] = "No"

Consider staging any customizations you wish for data acquisition:

# IOC may create up to 5 new subdirectories, as needed
det_pe.hdf1.create_directory.put(-5)

NUM_FRAMES = 5
det_pe.cam.stage_sigs["acquire_period"] = 0.1
det_pe.cam.stage_sigs["acquire_time"] = 0.01
det_pe.cam.stage_sigs["num_images"] = 5
det_pe.hdf1.stage_sigs["num_capture"] = 0  # capture ALL frames received
det_pe.hdf1.stage_sigs["compression"] = "zlib"  # LZ4
# det_pe.hdf1.stage_sigs["queue_size"] = 20

bluesky#

Use with the bluesky RunEngine RE like any other detector in a plan, such as:

RE(bp.count([det_pe]))

See the bluesky section of the basic example for more information.