ADPilatus Area Detector with default HDF5 File Name#

Objective

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

EPICS Area Detector IOC#

The Pilatus detector computer collects images to its own file system. These files are read by the ADPilatus driver in the IOC. There is a feature in the HDF5 plugin to select if the driver file file should be deleted after the HDF5 file is written by the IOC. This is a good idea to avoid filling up the Pilatus computer’s file system with old files.

Both the IOC and Bluesky should be able to see the HDF5 files. Whatever the method to accomplish this goal (shared file system, such as NFS, or file transfer from Pilatus computer to Bluesky computer), the file path on the IOC (AD_IOC_MOUNT_PATH) will likely be different than on the Bluesky (local) workstation (BLUESKY_MOUNT_PATH). Following the setup in the File-Directories section of the basic example:

import pathlib

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

AD_IOC_MOUNT_PATH = pathlib.Path("/mnt/fileserver/data")  # TODO: use yours
BLUESKY_MOUNT_PATH = pathlib.Path("/export/raid5/fileshare/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 ADPilatus driver is supported by ophyd.areadetector’s PilatusDetectorCam 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 Pilatus here as an example) between PilatusDetector and PilatusDetectorCam. The PilatusDetector includes PilatusDetectorCam as a component to build a useful detector. We must customize the PilatusDetectorCam class with updates, so we can’t use the stock PilatusDetector.

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

class PilatusDetectorCam_V34(CamMixin_V34, PilatusDetectorCam):
    """Update PilatusDetectorCam 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 PilatusDetector_V34(SingleTrigger_V34, DetectorBase):
    """
    ADSimDetector

    SingleTrigger:

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

    cam = ADComponent(PilatusDetectorCam_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:

pilatus = PilatusDetector_V34(IOC, name="pilatus")
pilatus.wait_for_connection(timeout=15)

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

# override default settings from ophyd
# Plugins will tell the camera driver when acquisition is finished.
# RunEngine will wait until `pilatus:cam1:AcquireBusy_RBV` PV goes to zero.
pilatus.cam.stage_sigs["wait_for_plugins"] = "Yes"
pilatus.hdf1.stage_sigs["blocking_callbacks"] = "No"
pilatus.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
pilatus.hdf1.create_directory.put(-5)

NUM_FRAMES = 5
pilatus.cam.stage_sigs["acquire_period"] = 0.1
pilatus.cam.stage_sigs["acquire_time"] = 0.01
pilatus.cam.stage_sigs["num_images"] = 5
pilatus.hdf1.stage_sigs["num_capture"] = 0  # capture ALL frames received
pilatus.hdf1.stage_sigs["compression"] = "zlib"  # LZ4
# pilatus.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([pilatus]))

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