Skip to content

Commit

Permalink
Add fly scan plan stub (#293)
Browse files Browse the repository at this point in the history
Add a plan stub for a simple fly scan.

Co-authored-by: Tom Cobb <[email protected]>
  • Loading branch information
abbiemery and coretl authored May 23, 2024
1 parent 9f332e3 commit 11fc4cc
Show file tree
Hide file tree
Showing 15 changed files with 317 additions and 98 deletions.
2 changes: 1 addition & 1 deletion .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ coverage:
threshold: 1%
patch:
default:
target: auto
target: 90
threshold: 1%
github_checks:
annotations: false
2 changes: 1 addition & 1 deletion src/ophyd_async/core/detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ async def collect_asset_docs(
# Collect stream datum documents for all indices written.
# The index is optional, and provided for fly scans, however this needs to be
# retrieved for step scans.
if not index:
if index is None:
index = await self.writer.get_indices_written()
async for doc in self.writer.collect_stream_docs(index):
yield doc
Expand Down
2 changes: 1 addition & 1 deletion src/ophyd_async/core/flyer.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class HardwareTriggeredFlyable(
def __init__(
self,
trigger_logic: TriggerLogic[T],
configuration_signals: Sequence[SignalR],
configuration_signals: Sequence[SignalR] = (),
name: str = "",
):
self._trigger_logic = trigger_logic
Expand Down
5 changes: 3 additions & 2 deletions src/ophyd_async/epics/areadetector/writers/hdf_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,13 @@ def __init__(
async def open(self, multiplier: int = 1) -> Dict[str, DataKey]:
self._file = None
info = self._directory_provider()
file_path = str(info.root / info.resource_dir)
await asyncio.gather(
self.hdf.num_extra_dims.set(0),
self.hdf.lazy_open.set(True),
self.hdf.swmr_mode.set(True),
# See https://github.com/bluesky/ophyd-async/issues/122
self.hdf.file_path.set(str(info.root / info.resource_dir)),
self.hdf.file_path.set(file_path),
self.hdf.file_name.set(f"{info.prefix}{self.hdf.name}{info.suffix}"),
self.hdf.file_template.set("%s/%s.h5"),
self.hdf.file_write_mode.set(FileWriteMode.stream),
Expand All @@ -59,7 +60,7 @@ async def open(self, multiplier: int = 1) -> Dict[str, DataKey]:

assert (
await self.hdf.file_path_exists.get_value()
), f"File path {self.hdf.file_path.get_value()} for hdf plugin does not exist"
), f"File path {file_path} for hdf plugin does not exist"

# Overwrite num_capture to go forever
await self.hdf.num_capture.set(0)
Expand Down
8 changes: 4 additions & 4 deletions src/ophyd_async/panda/writers/_hdf_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ async def get_signals_marked_for_capture(
capture_signals.keys(), capture_signals.values(), signal_values
):
signal_path = signal_path.replace("_capture", "")
if (signal_value.value in iter(Capture)) and (signal_value.value != Capture.No):
if (signal_value in iter(Capture)) and (signal_value != Capture.No):
signals_to_capture[signal_path] = CaptureSignalWrapper(
signal_object,
signal_value.value,
signal_value,
)

return signals_to_capture
Expand Down Expand Up @@ -126,7 +126,7 @@ async def open(self, multiplier: int = 1) -> Dict[str, DataKey]:
str(info.root / info.resource_dir)
),
self.panda_device.data.hdf_file_name.set(
f"{info.prefix}{self.panda_device.name}{info.suffix}",
f"{info.prefix}{self.panda_device.name}{info.suffix}.h5",
),
self.panda_device.data.num_capture.set(0),
)
Expand All @@ -149,7 +149,7 @@ async def open(self, multiplier: int = 1) -> Dict[str, DataKey]:
else split_path[-2]
)

for suffix in str(capture_signal.capture_type).split(" "):
for suffix in capture_signal.capture_type.split(" "):
self._datasets.append(
_HDFDataset(
name,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from .ensure_connected import ensure_connected
from .prepare_trigger_and_dets import (
from .fly import (
prepare_static_seq_table_flyer_and_detectors_with_same_trigger,
time_resolved_fly_and_collect_with_static_seq_table,
)

__all__ = [
"time_resolved_fly_and_collect_with_static_seq_table",
"prepare_static_seq_table_flyer_and_detectors_with_same_trigger",
"ensure_connected",
]
130 changes: 130 additions & 0 deletions src/ophyd_async/plan_stubs/fly.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
from typing import List

import bluesky.plan_stubs as bps
from bluesky.utils import short_uid

from ophyd_async.core.detector import DetectorTrigger, StandardDetector, TriggerInfo
from ophyd_async.core.flyer import HardwareTriggeredFlyable
from ophyd_async.core.utils import in_micros
from ophyd_async.panda._table import SeqTable, SeqTableRow, seq_table_from_rows
from ophyd_async.panda._trigger import SeqTableInfo


def time_resolved_fly_and_collect_with_static_seq_table(
stream_name: str,
detectors: List[StandardDetector],
flyer: HardwareTriggeredFlyable[SeqTableInfo],
number_of_frames: int,
exposure: int,
shutter_time: float,
repeats: int = 1,
period: float = 0.0,
):
"""Run a scan wth a flyer and multiple detectors.
The standard basic flow for a flyscan:
- Set up the flyer with a static sequence table and detectors with a trigger
- Declare the stream and kickoff the scan
- Collect while completing
This needs to be used in a plan that instantates detectors and a flyer,
stages/unstages the devices, and opens and closes the run.
"""
# Set up scan and prepare trigger
deadtime = max(det.controller.get_deadtime(exposure) for det in detectors)
yield from prepare_static_seq_table_flyer_and_detectors_with_same_trigger(
flyer,
detectors,
number_of_frames=number_of_frames,
exposure=exposure,
deadtime=deadtime,
shutter_time=shutter_time,
repeats=repeats,
period=period,
)
yield from bps.declare_stream(*detectors, name=stream_name, collect=True)
yield from bps.kickoff(flyer, wait=True)
for detector in detectors:
yield from bps.kickoff(detector)

# collect_while_completing
group = short_uid(label="complete")

yield from bps.complete(flyer, wait=False, group=group)
for detector in detectors:
yield from bps.complete(detector, wait=False, group=group)

done = False
while not done:
try:
yield from bps.wait(group=group, timeout=0.5)
except TimeoutError:
pass
else:
done = True
yield from bps.collect(
*detectors,
return_payload=False,
name=stream_name,
)
yield from bps.wait(group=group)


def prepare_static_seq_table_flyer_and_detectors_with_same_trigger(
flyer: HardwareTriggeredFlyable[SeqTableInfo],
detectors: List[StandardDetector],
number_of_frames: int,
exposure: float,
deadtime: float,
shutter_time: float,
repeats: int = 1,
period: float = 0.0,
):
"""Prepare a hardware triggered flyable and one or more detectors.
Prepare a hardware triggered flyable and one or more detectors with the
same trigger. This method constructs TriggerInfo and a static sequence
table from required parameters. The table is required to prepare the flyer,
and the TriggerInfo is required to prepare the detector(s).
This prepares all supplied detectors with the same trigger.
"""
trigger_info = TriggerInfo(
num=number_of_frames * repeats,
trigger=DetectorTrigger.constant_gate,
deadtime=deadtime,
livetime=exposure,
)

trigger_time = number_of_frames * (exposure + deadtime)
pre_delay = max(period - 2 * shutter_time - trigger_time, 0)

table: SeqTable = seq_table_from_rows(
# Wait for pre-delay then open shutter
SeqTableRow(
time1=in_micros(pre_delay),
time2=in_micros(shutter_time),
outa2=True,
),
# Keeping shutter open, do N triggers
SeqTableRow(
repeats=number_of_frames,
time1=in_micros(exposure),
outa1=True,
outb1=True,
time2=in_micros(deadtime),
outa2=True,
),
# Add the shutter close
SeqTableRow(time2=in_micros(shutter_time)),
)

table_info = SeqTableInfo(table, repeats)

for det in detectors:
yield from bps.prepare(det, trigger_info, wait=False, group="prep")
yield from bps.prepare(flyer, table_info, wait=False, group="prep")
yield from bps.wait(group="prep")
57 changes: 0 additions & 57 deletions src/ophyd_async/planstubs/prepare_trigger_and_dets.py

This file was deleted.

4 changes: 1 addition & 3 deletions tests/core/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@
)
from ophyd_async.core.soft_signal_backend import SoftSignalBackend
from ophyd_async.epics.motion import motor
from ophyd_async.planstubs.ensure_connected import (
ensure_connected,
)
from ophyd_async.plan_stubs.ensure_connected import ensure_connected
from ophyd_async.sim.demo.sim_motor import SimMotor


Expand Down
24 changes: 12 additions & 12 deletions tests/core/test_flyer.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ async def close(self) -> None:


@pytest.fixture
async def detector_list(RE: RunEngine) -> tuple[StandardDetector, StandardDetector]:
async def detectors(RE: RunEngine) -> tuple[StandardDetector, StandardDetector]:
writers = [DummyWriter("testa", (1, 1)), DummyWriter("testb", (1, 1))]
await writers[0].dummy_signal.connect(mock=True)
await writers[1].dummy_signal.connect(mock=True)
Expand Down Expand Up @@ -137,7 +137,7 @@ async def dummy_arm_2(self=None, trigger=None, num=0, exposure=None):


async def test_hardware_triggered_flyable(
RE: RunEngine, detector_list: tuple[StandardDetector]
RE: RunEngine, detectors: tuple[StandardDetector]
):
names = []
docs = []
Expand All @@ -155,39 +155,39 @@ def append_and_print(name, doc):
)

def flying_plan():
yield from bps.stage_all(*detector_list, flyer)
yield from bps.stage_all(*detectors, flyer)
assert flyer._trigger_logic.state == TriggerState.stopping

# move the flyer to the correct place, before fly scanning.
# Prepare the flyer first to get the trigger info for the detectors
yield from bps.prepare(flyer, 1, wait=True)

# prepare detectors second.
for detector in detector_list:
for detector in detectors:
yield from bps.prepare(
detector,
trigger_info,
wait=True,
)

assert flyer._trigger_logic.state == TriggerState.preparing
for detector in detector_list:
for detector in detectors:
detector.controller.disarm.assert_called_once # type: ignore

yield from bps.open_run()
yield from bps.declare_stream(*detector_list, name="main_stream", collect=True)
yield from bps.declare_stream(*detectors, name="main_stream", collect=True)

yield from bps.kickoff(flyer)
for detector in detector_list:
for detector in detectors:
yield from bps.kickoff(detector)

yield from bps.complete(flyer, wait=False, group="complete")
for detector in detector_list:
for detector in detectors:
yield from bps.complete(detector, wait=False, group="complete")
assert flyer._trigger_logic.state == TriggerState.null

# Manually incremenet the index as if a frame was taken
for detector in detector_list:
for detector in detectors:
detector.writer.index += 1

done = False
Expand All @@ -199,15 +199,15 @@ def flying_plan():
else:
done = True
yield from bps.collect(
*detector_list,
*detectors,
return_payload=False,
name="main_stream",
)
yield from bps.wait(group="complete")
yield from bps.close_run()

yield from bps.unstage_all(flyer, *detector_list)
for detector in detector_list:
yield from bps.unstage_all(flyer, *detectors)
for detector in detectors:
assert detector.controller.disarm.called # type: ignore
assert trigger_logic.state == TriggerState.stopping

Expand Down
Loading

0 comments on commit 11fc4cc

Please sign in to comment.