diff --git a/pixi.lock b/pixi.lock index 8dc266c..e936b86 100644 --- a/pixi.lock +++ b/pixi.lock @@ -299,7 +299,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - pypi: git+https://github.com/NSLS2/nslsii?rev=main#93ef2e4a24892699debc56d6f9891e57dc2205d5 - - pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#5495b4d80acd7a98b69bdb7dab73f137a3dcc3ca + - pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#9c4c762d6c333f7907084f109ffc4c1ce0af7da8 - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/10/6195be29962a61ebb5f4bd9e4c7519890b172f7968a0a0d880398c6ddb02/pymongo-4.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/13/b5/3515126cb6dce557cb928cc3147a77d5f84b9cade3c8548b802792e6bbe4/redis_json_dict-0.2.2-py3-none-any.whl @@ -800,7 +800,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - pypi: git+https://github.com/NSLS2/nslsii?rev=main#93ef2e4a24892699debc56d6f9891e57dc2205d5 - - pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#5495b4d80acd7a98b69bdb7dab73f137a3dcc3ca + - pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#9c4c762d6c333f7907084f109ffc4c1ce0af7da8 - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/10/6195be29962a61ebb5f4bd9e4c7519890b172f7968a0a0d880398c6ddb02/pymongo-4.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/13/b5/3515126cb6dce557cb928cc3147a77d5f84b9cade3c8548b802792e6bbe4/redis_json_dict-0.2.2-py3-none-any.whl @@ -1213,7 +1213,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - pypi: git+https://github.com/NSLS2/nslsii?rev=main#93ef2e4a24892699debc56d6f9891e57dc2205d5 - - pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#5495b4d80acd7a98b69bdb7dab73f137a3dcc3ca + - pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#9c4c762d6c333f7907084f109ffc4c1ce0af7da8 - pypi: https://files.pythonhosted.org/packages/23/ef/3b82c9a5c6fe4bcc7ebafbc4a06efe4f631929441bddd5d53541434fb941/pyOlog-4.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/26/c2/2ec71837ac862d823de280e72c245686b37de6dde0a18a02c9c18f589c39/setuptools_dso-2.12.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2f/35/b577d82c6d1be7aee7ac7e249bc86f7847998345042e5f8360de238e177b/pymongo-4.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl @@ -1698,7 +1698,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - pypi: git+https://github.com/NSLS2/nslsii?rev=main#93ef2e4a24892699debc56d6f9891e57dc2205d5 - - pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#5495b4d80acd7a98b69bdb7dab73f137a3dcc3ca + - pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#9c4c762d6c333f7907084f109ffc4c1ce0af7da8 - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/29/ce9c8e81a6b9be7722ac00cb592ce6b652405eecb178bf410810732957ec/epicscorelibs-7.0.10.99.0.1-cp311-cp311-manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/13/b5/3515126cb6dce557cb928cc3147a77d5f84b9cade3c8548b802792e6bbe4/redis_json_dict-0.2.2-py3-none-any.whl @@ -2199,7 +2199,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - pypi: git+https://github.com/NSLS2/nslsii?rev=main#93ef2e4a24892699debc56d6f9891e57dc2205d5 - - pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#5495b4d80acd7a98b69bdb7dab73f137a3dcc3ca + - pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#9c4c762d6c333f7907084f109ffc4c1ce0af7da8 - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/b5/3515126cb6dce557cb928cc3147a77d5f84b9cade3c8548b802792e6bbe4/redis_json_dict-0.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1b/dd/a9fe6a0a09512da23951c68bf36466aeecd89def3183dc095edbc807ddc5/pint-0.25.3-py3-none-any.whl @@ -2700,7 +2700,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - pypi: git+https://github.com/NSLS2/nslsii?rev=main#93ef2e4a24892699debc56d6f9891e57dc2205d5 - - pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#5495b4d80acd7a98b69bdb7dab73f137a3dcc3ca + - pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#9c4c762d6c333f7907084f109ffc4c1ce0af7da8 - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/10/6195be29962a61ebb5f4bd9e4c7519890b172f7968a0a0d880398c6ddb02/pymongo-4.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/13/b5/3515126cb6dce557cb928cc3147a77d5f84b9cade3c8548b802792e6bbe4/redis_json_dict-0.2.2-py3-none-any.whl @@ -3132,7 +3132,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - pypi: git+https://github.com/NSLS2/nslsii?rev=main#93ef2e4a24892699debc56d6f9891e57dc2205d5 - - pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#5495b4d80acd7a98b69bdb7dab73f137a3dcc3ca + - pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#9c4c762d6c333f7907084f109ffc4c1ce0af7da8 - pypi: https://files.pythonhosted.org/packages/23/ef/3b82c9a5c6fe4bcc7ebafbc4a06efe4f631929441bddd5d53541434fb941/pyOlog-4.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/26/c2/2ec71837ac862d823de280e72c245686b37de6dde0a18a02c9c18f589c39/setuptools_dso-2.12.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2f/35/b577d82c6d1be7aee7ac7e249bc86f7847998345042e5f8360de238e177b/pymongo-4.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl @@ -3536,7 +3536,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - pypi: git+https://github.com/NSLS2/nslsii?rev=main#93ef2e4a24892699debc56d6f9891e57dc2205d5 - - pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#5495b4d80acd7a98b69bdb7dab73f137a3dcc3ca + - pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#9c4c762d6c333f7907084f109ffc4c1ce0af7da8 - pypi: https://files.pythonhosted.org/packages/23/ef/3b82c9a5c6fe4bcc7ebafbc4a06efe4f631929441bddd5d53541434fb941/pyOlog-4.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/26/c2/2ec71837ac862d823de280e72c245686b37de6dde0a18a02c9c18f589c39/setuptools_dso-2.12.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2f/35/b577d82c6d1be7aee7ac7e249bc86f7847998345042e5f8360de238e177b/pymongo-4.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl @@ -4042,7 +4042,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - pypi: git+https://github.com/NSLS2/nslsii?rev=main#93ef2e4a24892699debc56d6f9891e57dc2205d5 - - pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#5495b4d80acd7a98b69bdb7dab73f137a3dcc3ca + - pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#9c4c762d6c333f7907084f109ffc4c1ce0af7da8 - pypi: https://files.pythonhosted.org/packages/1b/dd/a9fe6a0a09512da23951c68bf36466aeecd89def3183dc095edbc807ddc5/pint-0.25.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/bd/4d1f59c9287ec5f93f9d879db3ac06785ba7c4d04a7120678d894e0c53d0/caproto-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/23/ef/3b82c9a5c6fe4bcc7ebafbc4a06efe4f631929441bddd5d53541434fb941/pyOlog-4.5.1-py3-none-any.whl @@ -13693,9 +13693,9 @@ packages: - requests - shortuuid requires_python: '>=3.11' -- pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#5495b4d80acd7a98b69bdb7dab73f137a3dcc3ca +- pypi: git+https://github.com/jwlodek/ophyd-async?rev=add-codec-and-process-plugin-allow-for-filtering#9c4c762d6c333f7907084f109ffc4c1ce0af7da8 name: ophyd-async - version: 0.1.dev1025+g5495b4d80 + version: 0.1.dev1040+g9c4c762d6 requires_dist: - numpy - bluesky>=1.13.1rc2 diff --git a/src/hextools/tomography/flyscans.py b/src/hextools/tomography/flyscans.py index cf2040b..9082ee5 100644 --- a/src/hextools/tomography/flyscans.py +++ b/src/hextools/tomography/flyscans.py @@ -93,7 +93,7 @@ def tomo_flyscan( _md = { "detectors": [det.name for det in detectors], "num_points": num_images, - "plan_name": "single_axis_flyscan", + "plan_name": "tomo_flyscan", "hints": {}, } if sample_name is not None: diff --git a/src/hextools/tomography/take_radiograph.py b/src/hextools/tomography/take_radiograph.py new file mode 100644 index 0000000..7462708 --- /dev/null +++ b/src/hextools/tomography/take_radiograph.py @@ -0,0 +1,158 @@ +""" +Radiograph acquisition plan for HEX beamline. + +Equivalent of the old pyepics script: + hex-acq-pyepics/techniques/tomography/kinetix/take_radiograph.py + +What this plan does +------------------- +1. Check the front-end shutter and open the photon shutter. + The front-end shutter is only checked at entry; must already be open — this + plan never actuates it. +2. For each burst: fire ``frames_per_burst`` frames, then wait + ``wait_between_bursts``. +3. Close the photon shutter. + +Everything from shutter-open onward runs under a finalizer, so an error or +interrupt still closes the shutter. + +Trigger model +------------- +Each burst is a single ``bps.trigger_and_read`` on the camera's internal +trigger, with ``num_images`` set to ``frames_per_burst`` so one trigger fires +the whole burst. The plan owns the timing directly: ``acquire_time`` is set to +``exposure_time`` and ``acquire_period`` to ``frame_period``, so +``frame_period - exposure_time`` is the readout margin that keeps frames +non-overlapping — the same "period larger than exposure" discipline the old +PandA-paced script enforced with its PULSE step. A PandA-paced external-trigger +variant remains possible if precision frame timing is ever needed. + +Usage +----- + RE(take_radiograph( + [kinetix1], fe_shutter, ph_shutter, + exposure_time=0.5, + frames_per_burst=10, + num_bursts=5, + wait_between_bursts=10.0, + )) + +``detectors`` is a list (``[kinetix1]``) since multiple detectors are supported. + +Where files land is decided by each detector's path provider (set in the +profile), not by this plan — the old script's proposal-folder logic is gone. +""" + +import bluesky.plan_stubs as bps +import bluesky.preprocessors as bpp +from ophyd_async.epics.adcore import AreaDetector + +from hextools.photon_delivery_system import Shutter + +# Readout headroom (s) added to exposure_time when frame_period is unset; +# same margin the beamline's deployed PandA plan kept between step and exposure. +FRAME_PERIOD_MARGIN = 0.1 + + +def take_radiograph( + detectors: list[AreaDetector], + front_end_shutter: Shutter, + photon_shutter: Shutter, + exposure_time: float, + frames_per_burst: int = 10, + num_bursts: int = 5, + wait_between_bursts: float = 10.0, + frame_period: float | None = None, + use_shutter: bool = True, + sample_name: str | None = None, + md: dict | None = None, +): + """Acquire a burst-mode radiograph series on the HEX beamline. + + Parameters + ---------- + detectors : list[AreaDetector] + detectors to trigger; any ophyd-async detector is accepted + front_end_shutter : Shutter + the front-end shutter to check before opening the photon shutter + photon_shutter : Shutter + the photon shutter to open/close around the acquisition + exposure_time : float + camera exposure time, in seconds (no default — depends on the sample) + frames_per_burst : int + number of frames fired in each burst + num_bursts : int + number of bursts to acquire + wait_between_bursts : float + idle time between bursts, in seconds + frame_period : float, optional + minimum time per frame, in seconds; must exceed ``exposure_time``, and + the difference is enforced as the camera's deadtime. If None, computed + from ``exposure_time`` plus a readout margin + use_shutter : bool + whether to open/check the photon shutter during the scan + sample_name : str, optional + name of the sample being imaged + md : dict, optional + extra metadata to merge into the run's metadata + """ + # Validate arguments before touching hardware. + if frame_period is None: + frame_period = exposure_time + FRAME_PERIOD_MARGIN + if frame_period <= exposure_time: + raise ValueError( + f"frame_period ({frame_period}) must be larger than exposure_time " + f"({exposure_time}) to leave readout margin." + ) + + if use_shutter: + # FE shutter must already be open; this plan never actuates it. + fe_shutter_open = yield from bps.rd(front_end_shutter.status) + if not fe_shutter_open: + raise ValueError( + "Front-end shutter is closed. Please open it before starting the scan." + ) + + def _body(): + if use_shutter: + photon_shutter_open = yield from bps.rd(photon_shutter.status) + if not photon_shutter_open: + yield from bps.mv(photon_shutter, True) + + _md = { + "detectors": [det.name for det in detectors], + "num_points": num_bursts, + "plan_name": "take_radiograph", + "hints": {}, + # burst structure — lets analysis reconstruct the timing + "frames_per_burst": frames_per_burst, + "num_bursts": num_bursts, + "wait_between_bursts": wait_between_bursts, + "frame_period": frame_period, + "exposure_time": exposure_time, + } + for det in detectors: + yield from bps.mv(det.driver.num_images, frames_per_burst) + yield from bps.mv(det.driver.acquire_time, exposure_time) + yield from bps.mv(det.driver.acquire_period, frame_period) + + if sample_name is not None: + _md["sample_name"] = sample_name + _md.update(md or {}) + yield from bps.open_run(md=_md) + + yield from bps.stage_all(*detectors) + + for burst in range(num_bursts): + yield from bps.trigger_and_read(detectors) + if burst < num_bursts - 1: + yield from bps.sleep(wait_between_bursts) + + yield from bps.unstage_all(*detectors) + yield from bps.close_run() + + def _cleanup(): + if use_shutter: + yield from bps.mv(photon_shutter, False) + + return (yield from bpp.finalize_wrapper(_body(), _cleanup())) diff --git a/tests/tomography/test_take_radiograph.py b/tests/tomography/test_take_radiograph.py new file mode 100644 index 0000000..a1a93c4 --- /dev/null +++ b/tests/tomography/test_take_radiograph.py @@ -0,0 +1,155 @@ +from collections.abc import Callable +from pathlib import Path +from typing import Any + +import pytest +from bluesky import Msg, RunEngine +from bluesky import plan_stubs as bps +from ophyd_async.core import ( + StaticPathProvider, + UUIDFilenameProvider, + callback_on_mock_execute, + callback_on_mock_put, + init_devices, + set_mock_value, +) +from ophyd_async.epics.adcore import ADBaseDataType, ADWriterFactory, NDPluginFileIO +from ophyd_async.epics.adkinetix import KinetixDetector + +from hextools.photon_delivery_system import Shutter +from hextools.tomography.take_radiograph import FRAME_PERIOD_MARGIN, take_radiograph + +# --- shutters: same shape as tests/tomography/test_alignment.py --------------- + + +@pytest.fixture +def shutter_factory() -> Callable[[str], Shutter]: + def _factory(name: str) -> Shutter: + with init_devices(mock=True): + shutter = Shutter(name, name=name) + # the only two arcs Shutter.set awaits: a command put flips the status readback + callback_on_mock_execute( + shutter.open_cmd, lambda *_: set_mock_value(shutter.status, True) + ) + callback_on_mock_execute( + shutter.close_cmd, lambda *_: set_mock_value(shutter.status, False) + ) + return shutter + + return _factory + + +@pytest.fixture +def two_shutters(shutter_factory: Callable[[str], Shutter]) -> tuple[Shutter, Shutter]: + return shutter_factory("front_end_shutter"), shutter_factory("photon_shutter") + + +# --- detector: Kinetix with an HDF writer, minimum causal chain --------------- + + +@pytest.fixture +def static_path_provider(tmp_path: Path) -> StaticPathProvider: + return StaticPathProvider(UUIDFilenameProvider(), tmp_path) + + +@pytest.fixture +def kinetix_hdf_factory( + static_path_provider: StaticPathProvider, +) -> Callable[[int], KinetixDetector]: + def _factory(num: int) -> KinetixDetector: + with init_devices(mock=True): + ktx = KinetixDetector( + f"KTX{num}", + ADWriterFactory.hdf(static_path_provider), + name=f"kinetix{num}", + ) + hdf = ktx.get_plugin_by_name("hdf", NDPluginFileIO) + + # what the descriptor needs to describe the image + set_mock_value(ktx.driver.array_size_x, 3200) + set_mock_value(ktx.driver.array_size_y, 3200) + set_mock_value(ktx.driver.data_type, ADBaseDataType.UINT16) + + async def _one_burst_arrives(_): + # one acquire produces a whole burst: num_images frames land in the file + n = await ktx.driver.num_images.get_value() + got = await hdf.num_captured.get_value() + set_mock_value(hdf.num_captured, got + n) + + # prepare: setting the directory must make the IOC report it exists + callback_on_mock_put( + hdf.file_path, lambda _: set_mock_value(hdf.file_path_exists, True) + ) + # prepare: starting capture resets the frame counter + callback_on_mock_put(hdf.capture, lambda _: set_mock_value(hdf.num_captured, 0)) + # trigger: the writer waits on num_captured reaching the expected count + callback_on_mock_put(ktx.driver.acquire, _one_burst_arrives) + return ktx + + return _factory + + +# --- one row of the happy path, to prove the arcs before the table exists ----- + + +async def test_take_radiograph_single_row( + RE: RunEngine, + kinetix_hdf_factory: Callable[[int], KinetixDetector], + two_shutters: tuple[Shutter, Shutter], + monkeypatch: pytest.MonkeyPatch, +): + # the profile sets this; tests do not load the profile + monkeypatch.setenv("OPHYD_ASYNC_PRESERVE_DETECTOR_STATE", "YES") + exposure_time, frames_per_burst, num_bursts, wait = 0.1, 10, 5, 0.01 + + fe_shutter, photon_shutter = two_shutters + ktx = kinetix_hdf_factory(1) + RE(bps.mv(fe_shutter, True)) # precondition: front end already open + + docs: dict[str, list[dict[str, Any]]] = {} + + def cache_docs(name: str, doc: dict[str, Any]): + docs.setdefault(name, []).append(doc) + + messages_by_type: dict[str, list[Msg]] = {} + + def msg_hook(msg: Msg): + messages_by_type.setdefault(msg.command, []).append(msg) + + RE.msg_hook = msg_hook + + RE( + take_radiograph( + [ktx], + fe_shutter, + photon_shutter, + exposure_time, + frames_per_burst=frames_per_burst, + num_bursts=num_bursts, + wait_between_bursts=wait, + ), + cache_docs, # type: ignore + ) + + for kind in ("start", "descriptor", "stream_resource", "stop"): + assert len(docs[kind]) == 1 + assert len(docs["event"]) == num_bursts + assert len(docs["stream_datum"]) == num_bursts + + start = docs["start"][0] + assert start["plan_name"] == "take_radiograph" + assert start["frames_per_burst"] == frames_per_burst + assert start["num_bursts"] == num_bursts + assert start["num_points"] == num_bursts + + sleeps = messages_by_type.get("sleep", []) + assert len(sleeps) == num_bursts - 1 + assert all(m.args == (wait,) for m in sleeps) + + assert await ktx.driver.acquire_time.get_value() == exposure_time + assert await ktx.driver.num_images.get_value() == frames_per_burst + assert await photon_shutter.status.get_value() is False # finalizer closed it + + assert await ktx.driver.acquire_period.get_value() == pytest.approx( + exposure_time + FRAME_PERIOD_MARGIN + )