"""Support for the ADPilatus areaDetector driver.
https://github.com/areaDetector/ADPilatus
"""
from collections.abc import Sequence
from enum import Enum
from typing import Annotated as A
from ophyd_async.core import (
DetectorTriggerLogic,
PathProvider,
SignalDict,
SignalR,
SignalRW,
StrictEnum,
)
from .adcore import (
ADArmLogic,
ADBaseIO,
ADWriterType,
AreaDetector,
NDPluginBaseIO,
prepare_exposures,
)
from .core import PvSuffix
__all__ = [
"PilatusDetector",
"PilatusDriverIO",
"PilatusTriggerLogic",
"PilatusTriggerMode",
"PilatusReadoutTime",
]
_MAX_NUM_IMAGE = 999_999
[docs]
class PilatusTriggerMode(StrictEnum):
"""Trigger modes for ADPilatus detector."""
INTERNAL = "Internal"
EXT_ENABLE = "Ext. Enable"
EXT_TRIGGER = "Ext. Trigger"
MULT_TRIGGER = "Mult. Trigger"
ALIGNMENT = "Alignment"
[docs]
class PilatusDriverIO(ADBaseIO):
"""Driver for the Pilatus pixel array detectors."""
"""This mirrors the interface provided by ADPilatus/db/pilatus.template."""
"""See HTML docs at https://areadetector.github.io/areaDetector/ADPilatus/pilatusDoc.html"""
trigger_mode: A[SignalRW[PilatusTriggerMode], PvSuffix.rbv("TriggerMode")]
armed: A[SignalR[bool], PvSuffix("Armed")]
[docs]
class PilatusReadoutTime(float, Enum):
"""Pilatus readout time per model in ms."""
# Cite: https://media.dectris.com/User_Manual-PILATUS2-V1_4.pdf
PILATUS2 = 2.28e-3
# Cite: https://media.dectris.com/user-manual-pilatus3-2020.pdf
PILATUS3 = 0.95e-3
[docs]
class PilatusTriggerLogic(DetectorTriggerLogic):
"""Trigger logic for ADPilatus detectors."""
def __init__(
self,
driver: PilatusDriverIO,
readout_time: PilatusReadoutTime,
):
self.driver = driver
self.readout_time = readout_time
[docs]
def get_deadtime(self, config_values: SignalDict) -> float:
return self.readout_time
[docs]
async def prepare_internal(self, num: int, livetime: float, deadtime: float):
await self.driver.trigger_mode.set(PilatusTriggerMode.INTERNAL)
await prepare_exposures(self.driver, num or _MAX_NUM_IMAGE, livetime, deadtime)
[docs]
async def prepare_edge(self, num: int, livetime: float):
await self.driver.trigger_mode.set(PilatusTriggerMode.EXT_TRIGGER)
await prepare_exposures(self.driver, num or _MAX_NUM_IMAGE, livetime)
[docs]
async def prepare_level(self, num: int):
await self.driver.trigger_mode.set(PilatusTriggerMode.EXT_ENABLE)
await prepare_exposures(self.driver, num or _MAX_NUM_IMAGE)
[docs]
class PilatusDetector(AreaDetector[PilatusDriverIO]):
"""Create an ADPilatus AreaDetector instance.
:param prefix: EPICS PV prefix for the detector
:param path_provider: Provider for file paths during acquisition
:param readout_time: Readout time for the specific Pilatus model
:param driver_suffix: Suffix for the driver PV, defaults to "cam1:"
:param writer_type: Type of file writer (HDF or TIFF)
:param writer_suffix: Suffix for the writer PV
:param plugins: Additional areaDetector plugins to include
:param config_sigs: Additional signals to include in configuration
:param name: Name for the detector device
"""
def __init__(
self,
prefix: str,
path_provider: PathProvider | None = None,
readout_time: PilatusReadoutTime = PilatusReadoutTime.PILATUS3,
driver_suffix="cam1:",
writer_type: ADWriterType | None = ADWriterType.HDF,
writer_suffix: str | None = None,
plugins: dict[str, NDPluginBaseIO] | None = None,
config_sigs: Sequence[SignalR] = (),
name: str = "",
) -> None:
driver = PilatusDriverIO(prefix + driver_suffix)
super().__init__(
prefix=prefix,
driver=driver,
arm_logic=ADArmLogic(driver, driver_armed_signal=driver.armed),
trigger_logic=PilatusTriggerLogic(driver, readout_time),
path_provider=path_provider,
writer_type=writer_type,
writer_suffix=writer_suffix,
plugins=plugins,
config_sigs=config_sigs,
name=name,
)