Source code for sm_bluesky.electron_analyser.plan_stubs.analyser_per_step

from collections.abc import Mapping, Sequence
from typing import Any, TypeVar

from bluesky.plan_stubs import TakeReading, move_per_step, mv, trigger_and_read
from bluesky.plans import PerShot, PerStepND
from bluesky.protocols import Movable, Readable
from bluesky.utils import MsgGenerator, plan
from dodal.devices.electron_analyser.base import (
    BaseRegion,
    BaseSequence,
    ElectronAnalyserDetector,
)
from dodal.devices.fast_shutter import GenericFastShutter
from dodal.log import LOGGER

T = TypeVar("T")


def _validate_args(
    analyser: ElectronAnalyserDetector,
    sequence: BaseSequence,
    detectors: Sequence[Readable],
    close_shutter_per_region: bool,
    shutter: GenericFastShutter | None,
) -> None:
    if len(sequence.get_enabled_regions()) == 0:
        raise ValueError("Sequence has zero enabled regions.")

    if close_shutter_per_region and shutter is None:
        raise ValueError(
            "close_shutter_per_region=True requires a shutter to be provided."
        )

    if analyser in detectors:
        raise ValueError(
            f"{analyser.name} is provided as analyser argument and also in the "
            "detectors list argument. Please remove it from detector list."
        )


def _analyser_regions(
    analyser: ElectronAnalyserDetector,
    sequence: BaseSequence[BaseRegion],
    readables: Sequence[Readable],
    shutter: GenericFastShutter | None,
    close_shutter_per_region: bool,
) -> MsgGenerator:
    """
    Configure and collect data from each enabled electron analyser region.

    For each region, the analyser is configured, the optional shutter is opened,
    and the provided readables are triggered and read. The shutter is optionally
    closed after each region.

    Parameters
    ----------
    analyser:
        Electron analyser to configure for each region.
    sequence:
        Sequence containing the analyser regions to collect.
    readables:
        Devices to trigger and read for each region.
    shutter:
        Optional shutter to open before collecting each region.
    close_shutter_per_region:
        Whether to close the shutter after collecting each region.
    """

    for region in sequence.get_enabled_regions():
        LOGGER.info(f"Scanning region {region.name}.")
        yield from mv(analyser, region)

        if shutter is not None:
            yield from mv(shutter.open, True)

        yield from trigger_and_read(readables, name=region.name)

        if close_shutter_per_region and shutter is not None:
            yield from mv(shutter.open, False)


[docs] def make_analyser_per_shot( analyser: ElectronAnalyserDetector, sequence: BaseSequence, detectors: Sequence[Readable], close_shutter_per_region: bool, shutter: GenericFastShutter | None, ) -> PerShot: """ Create a custom per-shot callback for acquisitions involving an electron analyser. The callback iterates over each enabled analyser region, configuring the analyser and optionally opening and closing a shutter for each region before triggering and reading the detectors. Parameters ---------- analyser: Electron analyser to configure for each region and measure with. sequence: Sequence containing the analyser regions to collect. close_shutter_per_region: Whether to close the shutter after collecting each region. shutter: Optional shutter to open before collecting each region. """ _validate_args(analyser, sequence, detectors, close_shutter_per_region, shutter) @plan def analyser_shot( detectors: Sequence[Readable], take_reading: TakeReading | None = None ) -> MsgGenerator: yield from _analyser_regions( analyser, sequence, detectors, shutter, close_shutter_per_region, ) return analyser_shot
[docs] def make_analyser_per_step( analyser: ElectronAnalyserDetector, sequence: BaseSequence, detectors: Sequence[Readable], close_shutter_per_region: bool, shutter: GenericFastShutter | None, ) -> PerStepND: """ Create a custom per-step callback for scans involving an electron analyser. The callback moves the scan motors, then iterates over each enabled analyser region, configuring the analyser and optionally opening and closing a shutter for each region before triggering and reading the detectors and scan motors. Parameters ---------- analyser: Electron analyser to configure for each region and measure with. sequence: Sequence containing the analyser regions to collect. close_shutter_per_region: Whether to close the shutter after collecting each region. shutter: Optional shutter to open before collecting each region. """ _validate_args(analyser, sequence, detectors, close_shutter_per_region, shutter) def analyser_nd_step( detectors: Sequence[Readable], step: Mapping[Movable, Any], pos_cache: dict[Movable, Any], take_reading: TakeReading | None = None, ) -> MsgGenerator: yield from move_per_step(step, pos_cache) # Get any readables from the movables to measure with. readables: list[Readable] = [s for s in step.keys() if isinstance(s, Readable)] yield from _analyser_regions( analyser, sequence, [*detectors, *readables], shutter, close_shutter_per_region, ) return analyser_nd_step
@plan def close_shutter(shutter: GenericFastShutter | None = None) -> MsgGenerator: if shutter is None: return yield from mv(shutter.open, False)