Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fly scan plan stub #293

Merged
merged 34 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
fac624b
Add initial fly plan stub
abbiemery May 8, 2024
aab41fc
Move repeats and periods temporarily
abbiemery May 8, 2024
dc9e179
Remove staging and open/close run
abbiemery May 9, 2024
e1784f3
Make number of frames more explicit
abbiemery May 9, 2024
b5b5544
Change width to exposure
abbiemery May 9, 2024
a4a6605
Change detector_list to detectors
abbiemery May 9, 2024
078427e
Correct trigger info args
abbiemery May 9, 2024
133b53d
Fix args in flyer tests
abbiemery May 9, 2024
deceab6
Change stub name and add to init
abbiemery May 9, 2024
7c4992d
Update import format
abbiemery May 9, 2024
506bc4b
Update fly_and_collect docstring
abbiemery May 9, 2024
e41f509
Move stubs into one file to avoid import issues
abbiemery May 9, 2024
3c3c7cd
Unify naming in tests
abbiemery May 9, 2024
7f4f536
Fix method parameters
abbiemery May 9, 2024
069e56e
Fix trigger parameters in test
abbiemery May 9, 2024
bd53de3
Remove unused import
abbiemery May 10, 2024
65aa21c
StandardDetector: fix collect_asset_docs(index=0) bug
coretl May 17, 2024
4c6852b
Fix error message for ADHDF writer
coretl May 17, 2024
08684fa
Make configuration signals for HardwareTriggeredFlyable optional
coretl May 17, 2024
0f2c200
Use bps.collect_while_completing
coretl May 17, 2024
5c878de
Add h5 suffix in PandA HDF writer
coretl May 17, 2024
3e3d234
Fix enum handling in PAndA
coretl May 17, 2024
fca4a53
Rename planstub folder to plan_stubs to match bluesky naming
abbiemery May 21, 2024
ac50551
Rename fly plan to be more specific
abbiemery May 21, 2024
93d6da9
Make plan_stub test dir and move tests into it
abbiemery May 21, 2024
520d096
Add initial planstub test
abbiemery May 21, 2024
d2cb87b
Fix plan_stub import
abbiemery May 22, 2024
7b30388
Format test imports
abbiemery May 22, 2024
036901e
Remove commented-out lines
abbiemery May 22, 2024
ba5303a
Add fixtures to test fly stub
abbiemery May 22, 2024
fc98389
Fix ruff format
abbiemery May 22, 2024
7795baa
Replace collect while completing with the error catching logic
abbiemery May 22, 2024
e0b6f16
Revert back to old kickoff complete
abbiemery May 22, 2024
4224cc5
Drop the patch
abbiemery May 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading