Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 6 additions & 1 deletion src/ophyd_async/core/_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ class TriggerInfo(ConfinedModel):
number_of_events: NonNegativeInt = Field(default=1)
"""Number of bluesky events that will be emitted, (0 means infinite)."""

images_per_exposure: PositiveInt = Field(default=1)
"""Number of frames the detector should produce for every trigger sent to it."""

exposure_timeout: float = Field(
default_factory=lambda d: d["livetime"] + d["deadtime"] + DEFAULT_TIMEOUT,
gt=0,
Expand Down Expand Up @@ -166,10 +169,11 @@ async def prepare_internal(self, num: int, livetime: float, deadtime: float):
"""
raise NotImplementedError(self)

async def prepare_edge(self, num: int, livetime: float):
async def prepare_edge(self, num: int, images_per_exposure: int, livetime: float):
"""Prepare the detector to take external edge triggered exposures.

:param num: the number of exposures to take
:param images_per_trigger: the number frames each exposure should produce
:param livetime: how long the exposure should be, 0 means what is currently set
"""
raise NotImplementedError(self)
Expand Down Expand Up @@ -601,6 +605,7 @@ async def prepare(self, value: TriggerInfo) -> None:
case DetectorTrigger.EXTERNAL_EDGE:
await self._trigger_logic.prepare_edge(
num=value.number_of_exposures,
images_per_exposure=value.images_per_exposure,
livetime=value.livetime,
)
case DetectorTrigger.EXTERNAL_LEVEL:
Expand Down
2 changes: 1 addition & 1 deletion src/ophyd_async/epics/adandor.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ async def prepare_internal(self, num: int, livetime: float, deadtime: float):
await self.driver.trigger_mode.set(Andor2TriggerMode.INTERNAL)
await prepare_exposures(self.driver, num or _MAX_NUM_IMAGE, livetime, deadtime)

async def prepare_edge(self, num: int, livetime: float):
async def prepare_edge(self, num: int, images_per_exposure, livetime: float):
await self.driver.trigger_mode.set(Andor2TriggerMode.EXT_TRIGGER)
await prepare_exposures(self.driver, num or _MAX_NUM_IMAGE, livetime)

Expand Down
2 changes: 1 addition & 1 deletion src/ophyd_async/epics/adaravis.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ async def prepare_internal(self, num: int, livetime: float, deadtime: float):
await self.driver.trigger_mode.set(OnOff.OFF)
await prepare_exposures(self.driver, num, livetime, deadtime)

async def prepare_edge(self, num: int, livetime: float):
async def prepare_edge(self, num: int, images_per_exposure, livetime: float):
# Trigger on the rising edge of Line1
# trigger mode must be set first and on its own!
# Hardware race condition in Aravis firmware requires setting trigger mode
Expand Down
2 changes: 1 addition & 1 deletion src/ophyd_async/epics/adkinetix.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ async def prepare_internal(self, num: int, livetime: float, deadtime: float):
await self.driver.trigger_mode.set(KinetixTriggerMode.INTERNAL)
await prepare_exposures(self.driver, num, livetime, deadtime)

async def prepare_edge(self, num: int, livetime: float):
async def prepare_edge(self, num: int, images_per_exposure, livetime: float):
await self.driver.trigger_mode.set(KinetixTriggerMode.EDGE)
await prepare_exposures(self.driver, num, livetime)

Expand Down
2 changes: 1 addition & 1 deletion src/ophyd_async/epics/admerlin.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ async def prepare_internal(self, num: int, livetime: float, deadtime: float):
await self.driver.trigger_mode.set(MerlinTriggerMode.INTERNAL)
await prepare_exposures(self.driver, num, livetime, deadtime)

async def prepare_edge(self, num: int, livetime: float):
async def prepare_edge(self, num: int, images_per_exposure, livetime: float):
# Is this the right trigger mode?
await self.driver.trigger_mode.set(MerlinTriggerMode.TRIGGER_START_RISING)
await prepare_exposures(self.driver, num, livetime)
Expand Down
2 changes: 1 addition & 1 deletion src/ophyd_async/epics/adpilatus.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ 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)

async def prepare_edge(self, num: int, livetime: float):
async def prepare_edge(self, num: int, images_per_exposure, livetime: float):
await self.driver.trigger_mode.set(self.edge_trigger_mode)
await prepare_exposures(self.driver, num or _MAX_NUM_IMAGE, livetime)

Expand Down
2 changes: 1 addition & 1 deletion src/ophyd_async/epics/advimba.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ async def prepare_internal(self, num: int, livetime: float, deadtime: float):
)
await prepare_exposures(self.driver, num, livetime, deadtime)

async def prepare_edge(self, num: int, livetime: float):
async def prepare_edge(self, num: int, images_per_exposure, livetime: float):
await asyncio.gather(
self.driver.trigger_mode.set(OnOff.ON),
self.driver.exposure_mode.set(VimbaExposeOutMode.TIMED),
Expand Down
16 changes: 11 additions & 5 deletions src/ophyd_async/fastcs/eiger/_trigger_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ async def _prepare_detector(
detector: EigerDetectorIO,
trigger_mode: EigerTriggerMode,
num: int,
images_per_exposure: int,
livetime: float = 0.0,
):
coros = [
detector.trigger_mode.set(trigger_mode),
detector.nimages.set(num),
detector.ntrigger.set(num),
detector.nimages.set(images_per_exposure),
]
if livetime:
coros += [
Expand All @@ -33,10 +35,14 @@ def get_deadtime(self, config_values: SignalDict) -> float:
return 0.0001

async def prepare_internal(self, num: int, livetime: float, deadtime: float):
await _prepare_detector(self.detector, EigerTriggerMode.INTERNAL, num, livetime)
await _prepare_detector(
self.detector, EigerTriggerMode.INTERNAL, 1, num, livetime
)

async def prepare_edge(self, num: int, livetime: float):
await _prepare_detector(self.detector, EigerTriggerMode.EDGE, num, livetime)
async def prepare_edge(self, num: int, images_per_exposure: int, livetime: float):
await _prepare_detector(
self.detector, EigerTriggerMode.EDGE, num, images_per_exposure, livetime
)

async def prepare_level(self, num: int):
await _prepare_detector(self.detector, EigerTriggerMode.GATE, num)
await _prepare_detector(self.detector, EigerTriggerMode.GATE, 1, num)
2 changes: 1 addition & 1 deletion src/ophyd_async/fastcs/jungfrau/_trigger_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ async def prepare_internal(self, num: int, livetime: float, deadtime: float):
self.detector, JungfrauTriggerMode.INTERNAL, num, livetime, deadtime
)

async def prepare_edge(self, num: int, livetime: float):
async def prepare_edge(self, num: int, images_per_exposure, livetime: float):
acquisition_type = await get_and_reset_acquisition_type(self.acquisition_type)
if acquisition_type == AcquisitionType.PEDESTAL:
raise ValueError(
Expand Down
4 changes: 2 additions & 2 deletions tests/unit_tests/core/test_standard_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ async def prepare_internal(self, num: int, livetime: float, deadtime: float):
self.trigger = DetectorTrigger.INTERNAL
self.num, self.livetime, self.deadtime = num, livetime, deadtime

async def prepare_edge(self, num: int, livetime: float):
async def prepare_edge(self, num: int, images_per_exposure, livetime: float):
self.trigger = DetectorTrigger.EXTERNAL_EDGE
self.num, self.livetime = num, livetime

Expand Down Expand Up @@ -1071,7 +1071,7 @@ async def test_trigger_logic_not_implemented_errors():
await logic.prepare_internal(1, 0.1, 0.01)

with pytest.raises(NotImplementedError):
await logic.prepare_edge(1, 0.1)
await logic.prepare_edge(1, 1, 0.1)

with pytest.raises(NotImplementedError):
await logic.prepare_level(1)
Expand Down
64 changes: 64 additions & 0 deletions tests/unit_tests/fastcs/eiger/test_eiger_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ async def test_prepare_internal_calls_correct_parameters(
detector,
[
call.detector.trigger_mode.put(EigerTriggerMode.INTERNAL),
call.detector.ntrigger.put(1),
call.detector.nimages.put(10),
call.detector.count_time.put(0.1),
call.detector.frame_time.put(0.1),
Expand All @@ -57,6 +58,69 @@ async def test_prepare_internal_calls_correct_parameters(
)


async def test_prepare_edge_calls_correct_parameters(detector: EigerDetector, tmp_path):
await detector.prepare(
TriggerInfo(
number_of_events=10,
images_per_exposure=5,
livetime=0.1,
trigger=DetectorTrigger.EXTERNAL_EDGE,
)
)
assert_has_calls(
detector,
[
call.detector.trigger_mode.put(EigerTriggerMode.EDGE),
call.detector.ntrigger.put(10),
call.detector.nimages.put(5),
call.detector.count_time.put(0.1),
call.detector.frame_time.put(0.1),
call.od.acquisition_id.put(""),
call.od.file_prefix.put("filename"),
call.od.file_path.put(str(tmp_path)),
call.od.fp.data_compression.put("BSLZ4"),
call.od.fp.data_datatype.put("uint16"),
call.od.fp.frames.put(0),
call.od.block_size.put(100000),
call.pixel_mask.put(str(tmp_path / "filename_meta.h5") + "//mask"),
call.od.fp.start_writing.execute(),
call.arm_when_ready.execute(),
],
reset_after=False,
)


async def test_prepare_level_calls_correct_parameters(
detector: EigerDetector, tmp_path
):
await detector.prepare(
TriggerInfo(
number_of_events=10,
livetime=0.1,
trigger=DetectorTrigger.EXTERNAL_LEVEL,
)
)
assert_has_calls(
detector,
[
call.detector.trigger_mode.put(EigerTriggerMode.GATE),
call.detector.ntrigger.put(1),
call.detector.nimages.put(10),
call.od.acquisition_id.put(""),
call.od.file_prefix.put("filename"),
call.od.file_path.put(str(tmp_path)),
call.od.fp.data_compression.put("BSLZ4"),
call.od.fp.data_datatype.put("uint16"),
call.od.fp.frames.put(0),
call.od.block_size.put(100000),
call.pixel_mask.put(str(tmp_path / "filename_meta.h5") + "//mask"),
call.od.fp.start_writing.execute(),
call.arm_when_ready.execute(),
],
reset_after=False,
)


async def test_deadtime_correct(detector: EigerDetector):
supported_triggers, deadtime = await detector.get_trigger_deadtime()
assert supported_triggers == {
Expand Down
Loading