From adb59f0a78fa469af2fc3abe347dbe657fec0160 Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Tue, 22 Apr 2025 09:07:58 +0000 Subject: [PATCH 01/22] Add plan to test fastcs-eiger with adodin arming --- src/dodal/beamlines/i03.py | 23 +++ .../beamlines/load_eiger_metadata_plan.py | 167 ++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 src/dodal/beamlines/load_eiger_metadata_plan.py diff --git a/src/dodal/beamlines/i03.py b/src/dodal/beamlines/i03.py index 8ccc8454424..e3302df561f 100644 --- a/src/dodal/beamlines/i03.py +++ b/src/dodal/beamlines/i03.py @@ -1,3 +1,7 @@ +from pathlib import Path + +from ophyd_async.core import StaticFilenameProvider, StaticPathProvider +from ophyd_async.fastcs.eiger import EigerDetector as FastEiger from ophyd_async.fastcs.panda import HDFPanda from dodal.common.beamlines.beamline_parameters import get_beamline_parameters @@ -173,6 +177,25 @@ def eiger(mock: bool = False) -> EigerDetector: ) +@device_factory(skip=BL == "s03") +def fastcs_eiger(mock: bool = False) -> FastEiger: + """Get the i03 Eiger device, instantiate it if it hasn't already been. + If this is called when already instantiated in i03, it will return the existing object. + """ + + file_name = StaticFilenameProvider("eiger_test_file") + path_provider = StaticPathProvider(file_name, Path("/scratch/qqh35939")) + + return device_instantiation( + device_factory=FastEiger, + name="fastcs_eiger", + path_provider=path_provider, + prefix="", + wait=False, + fake=mock, + ) + + @device_factory() def zebra_fast_grid_scan() -> ZebraFastGridScan: """Get the i03 zebra_fast_grid_scan device, instantiate it if it hasn't already been. diff --git a/src/dodal/beamlines/load_eiger_metadata_plan.py b/src/dodal/beamlines/load_eiger_metadata_plan.py new file mode 100644 index 00000000000..ef1a175fde8 --- /dev/null +++ b/src/dodal/beamlines/load_eiger_metadata_plan.py @@ -0,0 +1,167 @@ +import time + +import bluesky.plan_stubs as bps +from bluesky.run_engine import RunEngine +from ophyd_async.fastcs.eiger import EigerDetector, EigerTriggerInfo + +from dodal.beamlines.i03 import fastcs_eiger +from dodal.devices.detector import DetectorParams +from dodal.log import LOGGER, do_default_logging_setup + +params = DetectorParams( + expected_energy_ev=12700, + exposure_time_s=0.004, + directory="/dls/i03/data/2025/cm40607-2/test_new_eiger", + prefix="", + detector_distance=255, + omega_start=0, + omega_increment=0.2, + num_images_per_trigger=1, + num_triggers=50, + use_roi_mode=True, + det_dist_to_beam_converter_path="/dls_sw/i03/software/daq_configuration/lookup/DetDistToBeamXYConverter.txt", +) + + +def load_metadata( + eiger: EigerDetector, + enable: bool, + detector_params: DetectorParams, +): + start = time.time() + assert detector_params.expected_energy_ev + yield from bps.abs_set(eiger.odin.file_writer.capture, 0) # type: ignore + yield from bps.abs_set(eiger.odin.meta.stop_writing, 1) # type: ignore + LOGGER.info(f"Stopping Odin: {time.time() - start}s") + yield from set_cam_pvs(eiger, detector_params, wait=True) + LOGGER.info(f"Setting CAM PVs: {time.time() - start}s") + yield from set_odin_pvs(eiger, detector_params, wait=True) + LOGGER.info(f"Setting Odin PVs: {time.time() - start}s") + yield from change_roi_mode(eiger, enable, detector_params, wait=True) + LOGGER.info(f"Changing ROI Mode: {time.time() - start}s") + yield from bps.abs_set(eiger.odin.file_writer.num_frames_chunks, 1) # type: ignore + LOGGER.info(f"Setting # of Frame Chunks: {time.time() - start}s") + yield from set_mx_settings_pvs(eiger, detector_params, wait=True) + LOGGER.info(f"Setting MX PVs: {time.time() - start}s") + + trigger_info = EigerTriggerInfo( + number_of_events=detector_params.num_triggers, + energy_ev=detector_params.expected_energy_ev, + ) + + yield from bps.prepare(eiger, trigger_info) + LOGGER.info(f"Preparing Eiger: {time.time() - start}s") + + +def set_cam_pvs( + eiger: EigerDetector, + detector_params: DetectorParams, + wait: bool, + group="cam_pvs", +): + yield from bps.abs_set( + eiger.drv.detector.count_time, detector_params.exposure_time_s, group=group + ) + yield from bps.abs_set( + eiger.drv.detector.frame_time, detector_params.exposure_time_s, group=group + ) + yield from bps.abs_set(eiger.drv.detector.nexpi, 1, group=group) + yield from bps.abs_set(eiger.drv.detector.trigger_mode, "exts", group=group) + # Image mode not set... + + if wait: + yield from bps.wait(group) + + +def change_roi_mode( + eiger: EigerDetector, + enable: bool, + detector_params: DetectorParams, + wait: bool, + group="roi_mode", +): + detector_dimensions = ( + detector_params.detector_size_constants.roi_size_pixels + if enable + else detector_params.detector_size_constants.det_size_pixels + ) + + yield from bps.abs_set(eiger.drv.detector.roi_mode, 1 if enable else 0, group=group) + yield from bps.abs_set( + eiger.odin.image_height, + detector_dimensions.height, + group=group, + ) + yield from bps.abs_set( + eiger.odin.image_width, + detector_dimensions.width, + group=group, + ) + yield from bps.abs_set( + eiger.odin.num_row_chunks, + detector_dimensions.height, + group=group, + ) + yield from bps.abs_set( + eiger.odin.num_col_chunks, + detector_dimensions.width, + group=group, + ) + + if wait: + yield from bps.wait(group) + + +def set_mx_settings_pvs( + eiger: EigerDetector, + detector_params: DetectorParams, + wait: bool, + group="mx_settings", +): + beam_x_pixels, beam_y_pixels = detector_params.get_beam_position_pixels( + detector_params.detector_distance + ) + + yield from bps.abs_set(eiger.drv.detector.beam_center_x, beam_x_pixels, group) + yield from bps.abs_set(eiger.drv.detector.beam_center_y, beam_y_pixels, group) + yield from bps.abs_set( + eiger.drv.detector.detector_distance, detector_params.detector_distance, group + ) + + yield from bps.abs_set( + eiger.drv.detector.omega_start, detector_params.omega_start, group + ) + yield from bps.abs_set( + eiger.drv.detector.omega_increment, detector_params.omega_increment, group + ) + + if wait: + yield from bps.wait(group) + + +def set_odin_pvs( + eiger: EigerDetector, + detector_params: DetectorParams, + wait: bool, + group="odin_pvs", +): + yield from bps.abs_set( + eiger.odin.file_writer.file_path, # type: ignore + detector_params.directory, + group=group, + ) + yield from bps.abs_set( + eiger.odin.file_writer.file_name, # type: ignore + detector_params.full_filename, + group=group, + ) + + if wait: + yield from bps.wait(group) + + +if __name__ == "__main__": + RE = RunEngine() + do_default_logging_setup() + eiger = fastcs_eiger(connect_immediately=True) + RE(load_metadata(eiger=eiger, enable=True, detector_params=params)) From 554c58c301f711ead2ee6a98b92aec1d7c0f76b1 Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Tue, 22 Apr 2025 09:14:30 +0000 Subject: [PATCH 02/22] Remove type ignores --- src/dodal/beamlines/load_eiger_metadata_plan.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/dodal/beamlines/load_eiger_metadata_plan.py b/src/dodal/beamlines/load_eiger_metadata_plan.py index ef1a175fde8..1b80d86aa0b 100644 --- a/src/dodal/beamlines/load_eiger_metadata_plan.py +++ b/src/dodal/beamlines/load_eiger_metadata_plan.py @@ -30,8 +30,7 @@ def load_metadata( ): start = time.time() assert detector_params.expected_energy_ev - yield from bps.abs_set(eiger.odin.file_writer.capture, 0) # type: ignore - yield from bps.abs_set(eiger.odin.meta.stop_writing, 1) # type: ignore + yield from bps.abs_set(eiger.odin.capture, 0) LOGGER.info(f"Stopping Odin: {time.time() - start}s") yield from set_cam_pvs(eiger, detector_params, wait=True) LOGGER.info(f"Setting CAM PVs: {time.time() - start}s") @@ -39,7 +38,7 @@ def load_metadata( LOGGER.info(f"Setting Odin PVs: {time.time() - start}s") yield from change_roi_mode(eiger, enable, detector_params, wait=True) LOGGER.info(f"Changing ROI Mode: {time.time() - start}s") - yield from bps.abs_set(eiger.odin.file_writer.num_frames_chunks, 1) # type: ignore + yield from bps.abs_set(eiger.odin.num_frames_chunks, 1) LOGGER.info(f"Setting # of Frame Chunks: {time.time() - start}s") yield from set_mx_settings_pvs(eiger, detector_params, wait=True) LOGGER.info(f"Setting MX PVs: {time.time() - start}s") @@ -146,12 +145,12 @@ def set_odin_pvs( group="odin_pvs", ): yield from bps.abs_set( - eiger.odin.file_writer.file_path, # type: ignore + eiger.odin.file_path, detector_params.directory, group=group, ) yield from bps.abs_set( - eiger.odin.file_writer.file_name, # type: ignore + eiger.odin.file_name, detector_params.full_filename, group=group, ) From d3bc82e8b59dec93e42a706506398389bbc8f5ec Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Tue, 22 Apr 2025 13:17:48 +0100 Subject: [PATCH 03/22] Change logic from beamline testing --- src/dodal/beamlines/i03.py | 6 ++++-- src/dodal/beamlines/load_eiger_metadata_plan.py | 13 ++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/dodal/beamlines/i03.py b/src/dodal/beamlines/i03.py index e3302df561f..e91dc6de8a5 100644 --- a/src/dodal/beamlines/i03.py +++ b/src/dodal/beamlines/i03.py @@ -183,8 +183,10 @@ def fastcs_eiger(mock: bool = False) -> FastEiger: If this is called when already instantiated in i03, it will return the existing object. """ - file_name = StaticFilenameProvider("eiger_test_file") - path_provider = StaticPathProvider(file_name, Path("/scratch/qqh35939")) + file_name = StaticFilenameProvider("test_eiger_1") + path_provider = StaticPathProvider( + file_name, Path("/dls/i03/data/2025/cm40607-2/test_new_eiger/") + ) return device_instantiation( device_factory=FastEiger, diff --git a/src/dodal/beamlines/load_eiger_metadata_plan.py b/src/dodal/beamlines/load_eiger_metadata_plan.py index 1b80d86aa0b..f4402463c6d 100644 --- a/src/dodal/beamlines/load_eiger_metadata_plan.py +++ b/src/dodal/beamlines/load_eiger_metadata_plan.py @@ -2,6 +2,7 @@ import bluesky.plan_stubs as bps from bluesky.run_engine import RunEngine +from ophyd_async.core import DetectorTrigger from ophyd_async.fastcs.eiger import EigerDetector, EigerTriggerInfo from dodal.beamlines.i03 import fastcs_eiger @@ -9,9 +10,9 @@ from dodal.log import LOGGER, do_default_logging_setup params = DetectorParams( - expected_energy_ev=12700, - exposure_time_s=0.004, - directory="/dls/i03/data/2025/cm40607-2/test_new_eiger", + expected_energy_ev=12800, + exposure_time_s=0.01, + directory="/scratch/rye74444/test_eiger", prefix="", detector_distance=255, omega_start=0, @@ -46,9 +47,11 @@ def load_metadata( trigger_info = EigerTriggerInfo( number_of_events=detector_params.num_triggers, energy_ev=detector_params.expected_energy_ev, + trigger=DetectorTrigger.INTERNAL, + deadtime=0.0001, ) - yield from bps.prepare(eiger, trigger_info) + yield from bps.prepare(eiger, trigger_info, wait=True) LOGGER.info(f"Preparing Eiger: {time.time() - start}s") @@ -65,7 +68,7 @@ def set_cam_pvs( eiger.drv.detector.frame_time, detector_params.exposure_time_s, group=group ) yield from bps.abs_set(eiger.drv.detector.nexpi, 1, group=group) - yield from bps.abs_set(eiger.drv.detector.trigger_mode, "exts", group=group) + yield from bps.abs_set(eiger.drv.detector.trigger_mode, "ints", group=group) # Image mode not set... if wait: From ed41f176ccf2faea3d95034bda43ff6566dccdf1 Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Tue, 22 Apr 2025 17:24:02 +0100 Subject: [PATCH 04/22] Change logic from beamline again --- src/dodal/beamlines/i03.py | 2 +- src/dodal/beamlines/load_eiger_metadata_plan.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dodal/beamlines/i03.py b/src/dodal/beamlines/i03.py index e91dc6de8a5..3c66ea24746 100644 --- a/src/dodal/beamlines/i03.py +++ b/src/dodal/beamlines/i03.py @@ -183,7 +183,7 @@ def fastcs_eiger(mock: bool = False) -> FastEiger: If this is called when already instantiated in i03, it will return the existing object. """ - file_name = StaticFilenameProvider("test_eiger_1") + file_name = StaticFilenameProvider("test_eiger_2") path_provider = StaticPathProvider( file_name, Path("/dls/i03/data/2025/cm40607-2/test_new_eiger/") ) diff --git a/src/dodal/beamlines/load_eiger_metadata_plan.py b/src/dodal/beamlines/load_eiger_metadata_plan.py index f4402463c6d..97311c11c73 100644 --- a/src/dodal/beamlines/load_eiger_metadata_plan.py +++ b/src/dodal/beamlines/load_eiger_metadata_plan.py @@ -18,8 +18,8 @@ omega_start=0, omega_increment=0.2, num_images_per_trigger=1, - num_triggers=50, - use_roi_mode=True, + num_triggers=5, + use_roi_mode=False, det_dist_to_beam_converter_path="/dls_sw/i03/software/daq_configuration/lookup/DetDistToBeamXYConverter.txt", ) @@ -68,7 +68,7 @@ def set_cam_pvs( eiger.drv.detector.frame_time, detector_params.exposure_time_s, group=group ) yield from bps.abs_set(eiger.drv.detector.nexpi, 1, group=group) - yield from bps.abs_set(eiger.drv.detector.trigger_mode, "ints", group=group) + # yield from bps.abs_set(eiger.drv.detector.trigger_mode, "ints", group=group) # Image mode not set... if wait: From bd2ff64324d754b9c195ce23f4cf70bfc8c6ef9a Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Wed, 23 Apr 2025 08:48:54 +0000 Subject: [PATCH 05/22] Remove reference to scratch --- src/dodal/beamlines/load_eiger_metadata_plan.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/dodal/beamlines/load_eiger_metadata_plan.py b/src/dodal/beamlines/load_eiger_metadata_plan.py index 97311c11c73..3a155d7f6d1 100644 --- a/src/dodal/beamlines/load_eiger_metadata_plan.py +++ b/src/dodal/beamlines/load_eiger_metadata_plan.py @@ -12,7 +12,7 @@ params = DetectorParams( expected_energy_ev=12800, exposure_time_s=0.01, - directory="/scratch/rye74444/test_eiger", + directory="/dls/i03/data/2025/cm40607-2/test_new_eiger/", prefix="", detector_distance=255, omega_start=0, @@ -68,8 +68,6 @@ def set_cam_pvs( eiger.drv.detector.frame_time, detector_params.exposure_time_s, group=group ) yield from bps.abs_set(eiger.drv.detector.nexpi, 1, group=group) - # yield from bps.abs_set(eiger.drv.detector.trigger_mode, "ints", group=group) - # Image mode not set... if wait: yield from bps.wait(group) From 41263e1f12376276ca1befd9dce0596bc7ac0d6c Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Wed, 23 Apr 2025 08:50:40 +0000 Subject: [PATCH 06/22] Move load metadata plan to plans directory --- src/dodal/{beamlines => plans}/load_eiger_metadata_plan.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/dodal/{beamlines => plans}/load_eiger_metadata_plan.py (100%) diff --git a/src/dodal/beamlines/load_eiger_metadata_plan.py b/src/dodal/plans/load_eiger_metadata_plan.py similarity index 100% rename from src/dodal/beamlines/load_eiger_metadata_plan.py rename to src/dodal/plans/load_eiger_metadata_plan.py From 346d7f9724992d7542902cc405e127db76dfe800 Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Wed, 23 Apr 2025 09:01:31 +0000 Subject: [PATCH 07/22] Remove entrypoint and detector params --- src/dodal/plans/load_eiger_metadata_plan.py | 25 +-------------------- 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/src/dodal/plans/load_eiger_metadata_plan.py b/src/dodal/plans/load_eiger_metadata_plan.py index 3a155d7f6d1..180c6b76890 100644 --- a/src/dodal/plans/load_eiger_metadata_plan.py +++ b/src/dodal/plans/load_eiger_metadata_plan.py @@ -1,27 +1,11 @@ import time import bluesky.plan_stubs as bps -from bluesky.run_engine import RunEngine from ophyd_async.core import DetectorTrigger from ophyd_async.fastcs.eiger import EigerDetector, EigerTriggerInfo -from dodal.beamlines.i03 import fastcs_eiger from dodal.devices.detector import DetectorParams -from dodal.log import LOGGER, do_default_logging_setup - -params = DetectorParams( - expected_energy_ev=12800, - exposure_time_s=0.01, - directory="/dls/i03/data/2025/cm40607-2/test_new_eiger/", - prefix="", - detector_distance=255, - omega_start=0, - omega_increment=0.2, - num_images_per_trigger=1, - num_triggers=5, - use_roi_mode=False, - det_dist_to_beam_converter_path="/dls_sw/i03/software/daq_configuration/lookup/DetDistToBeamXYConverter.txt", -) +from dodal.log import LOGGER def load_metadata( @@ -158,10 +142,3 @@ def set_odin_pvs( if wait: yield from bps.wait(group) - - -if __name__ == "__main__": - RE = RunEngine() - do_default_logging_setup() - eiger = fastcs_eiger(connect_immediately=True) - RE(load_metadata(eiger=eiger, enable=True, detector_params=params)) From 287f75d85c4902ef390b13db2df1243caacb2393 Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Wed, 23 Apr 2025 09:07:59 +0000 Subject: [PATCH 08/22] Remove hard coded path from fastcs_eiger device --- src/dodal/beamlines/i03.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/dodal/beamlines/i03.py b/src/dodal/beamlines/i03.py index 3c66ea24746..5894e2db23a 100644 --- a/src/dodal/beamlines/i03.py +++ b/src/dodal/beamlines/i03.py @@ -1,6 +1,3 @@ -from pathlib import Path - -from ophyd_async.core import StaticFilenameProvider, StaticPathProvider from ophyd_async.fastcs.eiger import EigerDetector as FastEiger from ophyd_async.fastcs.panda import HDFPanda @@ -179,19 +176,14 @@ def eiger(mock: bool = False) -> EigerDetector: @device_factory(skip=BL == "s03") def fastcs_eiger(mock: bool = False) -> FastEiger: - """Get the i03 Eiger device, instantiate it if it hasn't already been. + """Get the i03 FastCS Eiger device, instantiate it if it hasn't already been. If this is called when already instantiated in i03, it will return the existing object. """ - file_name = StaticFilenameProvider("test_eiger_2") - path_provider = StaticPathProvider( - file_name, Path("/dls/i03/data/2025/cm40607-2/test_new_eiger/") - ) - return device_instantiation( device_factory=FastEiger, name="fastcs_eiger", - path_provider=path_provider, + path_provider=get_path_provider(), prefix="", wait=False, fake=mock, From 2405ad7e7b2fa5877d958dcaf806e681af59dc92 Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Mon, 28 Apr 2025 09:19:48 +0000 Subject: [PATCH 09/22] Address review comments --- src/dodal/beamlines/i03.py | 10 +- src/dodal/plans/load_eiger_metadata_plan.py | 144 -------------------- 2 files changed, 4 insertions(+), 150 deletions(-) delete mode 100644 src/dodal/plans/load_eiger_metadata_plan.py diff --git a/src/dodal/beamlines/i03.py b/src/dodal/beamlines/i03.py index 5894e2db23a..64ce1df08df 100644 --- a/src/dodal/beamlines/i03.py +++ b/src/dodal/beamlines/i03.py @@ -180,13 +180,11 @@ def fastcs_eiger(mock: bool = False) -> FastEiger: If this is called when already instantiated in i03, it will return the existing object. """ - return device_instantiation( - device_factory=FastEiger, - name="fastcs_eiger", + return FastEiger( + prefix=PREFIX.beamline_prefix, path_provider=get_path_provider(), - prefix="", - wait=False, - fake=mock, + drv_suffix="-EA-EIGER-02:", + hdf_suffix="-EA-EIGER-01:OD:", ) diff --git a/src/dodal/plans/load_eiger_metadata_plan.py b/src/dodal/plans/load_eiger_metadata_plan.py deleted file mode 100644 index 180c6b76890..00000000000 --- a/src/dodal/plans/load_eiger_metadata_plan.py +++ /dev/null @@ -1,144 +0,0 @@ -import time - -import bluesky.plan_stubs as bps -from ophyd_async.core import DetectorTrigger -from ophyd_async.fastcs.eiger import EigerDetector, EigerTriggerInfo - -from dodal.devices.detector import DetectorParams -from dodal.log import LOGGER - - -def load_metadata( - eiger: EigerDetector, - enable: bool, - detector_params: DetectorParams, -): - start = time.time() - assert detector_params.expected_energy_ev - yield from bps.abs_set(eiger.odin.capture, 0) - LOGGER.info(f"Stopping Odin: {time.time() - start}s") - yield from set_cam_pvs(eiger, detector_params, wait=True) - LOGGER.info(f"Setting CAM PVs: {time.time() - start}s") - yield from set_odin_pvs(eiger, detector_params, wait=True) - LOGGER.info(f"Setting Odin PVs: {time.time() - start}s") - yield from change_roi_mode(eiger, enable, detector_params, wait=True) - LOGGER.info(f"Changing ROI Mode: {time.time() - start}s") - yield from bps.abs_set(eiger.odin.num_frames_chunks, 1) - LOGGER.info(f"Setting # of Frame Chunks: {time.time() - start}s") - yield from set_mx_settings_pvs(eiger, detector_params, wait=True) - LOGGER.info(f"Setting MX PVs: {time.time() - start}s") - - trigger_info = EigerTriggerInfo( - number_of_events=detector_params.num_triggers, - energy_ev=detector_params.expected_energy_ev, - trigger=DetectorTrigger.INTERNAL, - deadtime=0.0001, - ) - - yield from bps.prepare(eiger, trigger_info, wait=True) - LOGGER.info(f"Preparing Eiger: {time.time() - start}s") - - -def set_cam_pvs( - eiger: EigerDetector, - detector_params: DetectorParams, - wait: bool, - group="cam_pvs", -): - yield from bps.abs_set( - eiger.drv.detector.count_time, detector_params.exposure_time_s, group=group - ) - yield from bps.abs_set( - eiger.drv.detector.frame_time, detector_params.exposure_time_s, group=group - ) - yield from bps.abs_set(eiger.drv.detector.nexpi, 1, group=group) - - if wait: - yield from bps.wait(group) - - -def change_roi_mode( - eiger: EigerDetector, - enable: bool, - detector_params: DetectorParams, - wait: bool, - group="roi_mode", -): - detector_dimensions = ( - detector_params.detector_size_constants.roi_size_pixels - if enable - else detector_params.detector_size_constants.det_size_pixels - ) - - yield from bps.abs_set(eiger.drv.detector.roi_mode, 1 if enable else 0, group=group) - yield from bps.abs_set( - eiger.odin.image_height, - detector_dimensions.height, - group=group, - ) - yield from bps.abs_set( - eiger.odin.image_width, - detector_dimensions.width, - group=group, - ) - yield from bps.abs_set( - eiger.odin.num_row_chunks, - detector_dimensions.height, - group=group, - ) - yield from bps.abs_set( - eiger.odin.num_col_chunks, - detector_dimensions.width, - group=group, - ) - - if wait: - yield from bps.wait(group) - - -def set_mx_settings_pvs( - eiger: EigerDetector, - detector_params: DetectorParams, - wait: bool, - group="mx_settings", -): - beam_x_pixels, beam_y_pixels = detector_params.get_beam_position_pixels( - detector_params.detector_distance - ) - - yield from bps.abs_set(eiger.drv.detector.beam_center_x, beam_x_pixels, group) - yield from bps.abs_set(eiger.drv.detector.beam_center_y, beam_y_pixels, group) - yield from bps.abs_set( - eiger.drv.detector.detector_distance, detector_params.detector_distance, group - ) - - yield from bps.abs_set( - eiger.drv.detector.omega_start, detector_params.omega_start, group - ) - yield from bps.abs_set( - eiger.drv.detector.omega_increment, detector_params.omega_increment, group - ) - - if wait: - yield from bps.wait(group) - - -def set_odin_pvs( - eiger: EigerDetector, - detector_params: DetectorParams, - wait: bool, - group="odin_pvs", -): - yield from bps.abs_set( - eiger.odin.file_path, - detector_params.directory, - group=group, - ) - yield from bps.abs_set( - eiger.odin.file_name, - detector_params.full_filename, - group=group, - ) - - if wait: - yield from bps.wait(group) From 0674e54c854dae40af683f836a07438fc9db32bd Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Mon, 28 Apr 2025 11:21:23 +0000 Subject: [PATCH 10/22] Rename file and add __main__ --- src/dodal/plans/configure_and_arm_detector.py | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 src/dodal/plans/configure_and_arm_detector.py diff --git a/src/dodal/plans/configure_and_arm_detector.py b/src/dodal/plans/configure_and_arm_detector.py new file mode 100644 index 00000000000..bc8c04307fe --- /dev/null +++ b/src/dodal/plans/configure_and_arm_detector.py @@ -0,0 +1,169 @@ +import time + +import bluesky.plan_stubs as bps +from bluesky.run_engine import RunEngine +from ophyd_async.core import DetectorTrigger +from ophyd_async.fastcs.eiger import EigerDetector, EigerTriggerInfo + +from dodal.beamlines.i03 import fastcs_eiger +from dodal.devices.detector import DetectorParams +from dodal.log import LOGGER, do_default_logging_setup + +TEST_PARAMS = DetectorParams( + expected_energy_ev=12800, + exposure_time_s=0.01, + directory="/dls/i03/data/2025/cm40607-2/test_new_eiger/", + prefix="", + detector_distance=255, + omega_start=0, + omega_increment=0.2, + num_images_per_trigger=1, + num_triggers=5, + use_roi_mode=False, + det_dist_to_beam_converter_path="/dls_sw/i03/software/daq_configuration/lookup/DetDistToBeamXYConverter.txt", +) + + +def configure_and_arm_detector( + eiger: EigerDetector, + detector_params: DetectorParams, +): + start = time.time() + assert detector_params.expected_energy_ev + yield from bps.abs_set(eiger.odin.capture, 0) + LOGGER.info(f"Stopping Odin: {time.time() - start}s") + yield from set_cam_pvs(eiger, detector_params, wait=True) + LOGGER.info(f"Setting CAM PVs: {time.time() - start}s") + yield from set_odin_pvs(eiger, detector_params, wait=True) + LOGGER.info(f"Setting Odin PVs: {time.time() - start}s") + yield from change_roi_mode(eiger, detector_params, wait=True) + LOGGER.info(f"Changing ROI Mode: {time.time() - start}s") + yield from bps.abs_set(eiger.odin.num_frames_chunks, 1) + LOGGER.info(f"Setting # of Frame Chunks: {time.time() - start}s") + yield from set_mx_settings_pvs(eiger, detector_params, wait=True) + LOGGER.info(f"Setting MX PVs: {time.time() - start}s") + + trigger_info = EigerTriggerInfo( + number_of_events=detector_params.num_triggers, + energy_ev=detector_params.expected_energy_ev, + trigger=DetectorTrigger.INTERNAL, + deadtime=0.0001, + ) + + yield from bps.prepare(eiger, trigger_info, wait=True) + LOGGER.info(f"Preparing Eiger: {time.time() - start}s") + + +def set_cam_pvs( + eiger: EigerDetector, + detector_params: DetectorParams, + wait: bool, + group="cam_pvs", +): + yield from bps.abs_set( + eiger.drv.detector.count_time, detector_params.exposure_time_s, group=group + ) + yield from bps.abs_set( + eiger.drv.detector.frame_time, detector_params.exposure_time_s, group=group + ) + yield from bps.abs_set(eiger.drv.detector.nexpi, 1, group=group) + + if wait: + yield from bps.wait(group) + + +def change_roi_mode( + eiger: EigerDetector, + detector_params: DetectorParams, + wait: bool, + group="roi_mode", +): + detector_dimensions = ( + detector_params.detector_size_constants.roi_size_pixels + if detector_params.use_roi_mode + else detector_params.detector_size_constants.det_size_pixels + ) + + yield from bps.abs_set( + eiger.drv.detector.roi_mode, + 1 if detector_params.use_roi_mode else 0, + group=group, + ) + yield from bps.abs_set( + eiger.odin.image_height, + detector_dimensions.height, + group=group, + ) + yield from bps.abs_set( + eiger.odin.image_width, + detector_dimensions.width, + group=group, + ) + yield from bps.abs_set( + eiger.odin.num_row_chunks, + detector_dimensions.height, + group=group, + ) + yield from bps.abs_set( + eiger.odin.num_col_chunks, + detector_dimensions.width, + group=group, + ) + + if wait: + yield from bps.wait(group) + + +def set_mx_settings_pvs( + eiger: EigerDetector, + detector_params: DetectorParams, + wait: bool, + group="mx_settings", +): + beam_x_pixels, beam_y_pixels = detector_params.get_beam_position_pixels( + detector_params.detector_distance + ) + + yield from bps.abs_set(eiger.drv.detector.beam_center_x, beam_x_pixels, group) + yield from bps.abs_set(eiger.drv.detector.beam_center_y, beam_y_pixels, group) + yield from bps.abs_set( + eiger.drv.detector.detector_distance, detector_params.detector_distance, group + ) + + yield from bps.abs_set( + eiger.drv.detector.omega_start, detector_params.omega_start, group + ) + yield from bps.abs_set( + eiger.drv.detector.omega_increment, detector_params.omega_increment, group + ) + + if wait: + yield from bps.wait(group) + + +def set_odin_pvs( + eiger: EigerDetector, + detector_params: DetectorParams, + wait: bool, + group="odin_pvs", +): + yield from bps.abs_set( + eiger.odin.file_path, + detector_params.directory, + group=group, + ) + yield from bps.abs_set( + eiger.odin.file_name, + detector_params.full_filename, + group=group, + ) + + if wait: + yield from bps.wait(group) + + +if __name__ == "__main__": + RE = RunEngine() + do_default_logging_setup() + eiger = fastcs_eiger(connect_immediately=True) + RE(configure_and_arm_detector(eiger=eiger, detector_params=TEST_PARAMS)) From e707d66fac669fc54532a69ee32f139dec7e499f Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Mon, 28 Apr 2025 13:23:50 +0000 Subject: [PATCH 11/22] Add minimal test for configure and arm eiger plan --- src/dodal/plans/configure_and_arm_detector.py | 49 ++++++++++--------- tests/conftest.py | 22 +++++++++ tests/devices/unit_tests/test_eiger.py | 43 +++------------- 3 files changed, 54 insertions(+), 60 deletions(-) diff --git a/src/dodal/plans/configure_and_arm_detector.py b/src/dodal/plans/configure_and_arm_detector.py index bc8c04307fe..e0fe4c234b7 100644 --- a/src/dodal/plans/configure_and_arm_detector.py +++ b/src/dodal/plans/configure_and_arm_detector.py @@ -9,24 +9,11 @@ from dodal.devices.detector import DetectorParams from dodal.log import LOGGER, do_default_logging_setup -TEST_PARAMS = DetectorParams( - expected_energy_ev=12800, - exposure_time_s=0.01, - directory="/dls/i03/data/2025/cm40607-2/test_new_eiger/", - prefix="", - detector_distance=255, - omega_start=0, - omega_increment=0.2, - num_images_per_trigger=1, - num_triggers=5, - use_roi_mode=False, - det_dist_to_beam_converter_path="/dls_sw/i03/software/daq_configuration/lookup/DetDistToBeamXYConverter.txt", -) - def configure_and_arm_detector( eiger: EigerDetector, detector_params: DetectorParams, + trigger_info: EigerTriggerInfo, ): start = time.time() assert detector_params.expected_energy_ev @@ -42,14 +29,6 @@ def configure_and_arm_detector( LOGGER.info(f"Setting # of Frame Chunks: {time.time() - start}s") yield from set_mx_settings_pvs(eiger, detector_params, wait=True) LOGGER.info(f"Setting MX PVs: {time.time() - start}s") - - trigger_info = EigerTriggerInfo( - number_of_events=detector_params.num_triggers, - energy_ev=detector_params.expected_energy_ev, - trigger=DetectorTrigger.INTERNAL, - deadtime=0.0001, - ) - yield from bps.prepare(eiger, trigger_info, wait=True) LOGGER.info(f"Preparing Eiger: {time.time() - start}s") @@ -166,4 +145,28 @@ def set_odin_pvs( RE = RunEngine() do_default_logging_setup() eiger = fastcs_eiger(connect_immediately=True) - RE(configure_and_arm_detector(eiger=eiger, detector_params=TEST_PARAMS)) + + RE( + configure_and_arm_detector( + eiger=eiger, + detector_params=DetectorParams( + expected_energy_ev=12800, + exposure_time_s=0.01, + directory="/dls/i03/data/2025/cm40607-2/test_new_eiger/", + prefix="", + detector_distance=255, + omega_start=0, + omega_increment=0.2, + num_images_per_trigger=1, + num_triggers=5, + use_roi_mode=False, + det_dist_to_beam_converter_path="/dls_sw/i03/software/daq_configuration/lookup/DetDistToBeamXYConverter.txt", + ), + trigger_info=EigerTriggerInfo( + number_of_events=5, + energy_ev=12800, + trigger=DetectorTrigger.INTERNAL, + deadtime=0.0001, + ), + ) + ) diff --git a/tests/conftest.py b/tests/conftest.py index 046953474b1..9707fdf36ca 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ import importlib import os +from pathlib import Path from unittest.mock import patch import pytest @@ -7,6 +8,8 @@ from conftest import mock_attributes_table from dodal.beamlines import i03 from dodal.common.beamlines import beamline_parameters, beamline_utils +from dodal.devices.detector import DetectorParams +from dodal.devices.detector.det_dim_constants import EIGER2_X_16M_SIZE from dodal.utils import collect_factories, make_all_devices @@ -44,3 +47,22 @@ def clear_device_factory_caches_after_every_test(i03_device_factories): yield None for f in i03_device_factories: f.cache_clear() # type: ignore + + +@pytest.fixture +def eiger_params(tmp_path: Path) -> DetectorParams: + return DetectorParams( + expected_energy_ev=100.0, + exposure_time_s=1.0, + directory=str(tmp_path), + prefix="test", + run_number=0, + detector_distance=1.0, + omega_start=0.0, + omega_increment=1.0, + num_images_per_trigger=1, + num_triggers=2000, + use_roi_mode=False, + det_dist_to_beam_converter_path="tests/devices/unit_tests/test_lookup_table.txt", + detector_size_constants=EIGER2_X_16M_SIZE.det_type_string, # type: ignore + ) diff --git a/tests/devices/unit_tests/test_eiger.py b/tests/devices/unit_tests/test_eiger.py index f9541166e9b..db93be933b9 100644 --- a/tests/devices/unit_tests/test_eiger.py +++ b/tests/devices/unit_tests/test_eiger.py @@ -1,6 +1,5 @@ # type: ignore # Eiger will soon be ophyd-async https://github.com/DiamondLightSource/dodal/issues/700 import threading -from pathlib import Path from unittest.mock import ANY, MagicMock, Mock, call, create_autospec, patch import pytest @@ -16,19 +15,8 @@ from dodal.devices.util.epics_util import run_functions_without_blocking from dodal.log import LOGGER -TEST_DETECTOR_SIZE_CONSTANTS = EIGER2_X_16M_SIZE - -TEST_EXPECTED_ENERGY = 100.0 -TEST_EXPOSURE_TIME = 1.0 TEST_PREFIX = "test" TEST_RUN_NUMBER = 0 -TEST_DETECTOR_DISTANCE = 1.0 -TEST_OMEGA_START = 0.0 -TEST_OMEGA_INCREMENT = 1.0 -TEST_NUM_IMAGES_PER_TRIGGER = 1 -TEST_NUM_TRIGGERS = 2000 -TEST_USE_ROI_MODE = False -TEST_DET_DIST_TO_BEAM_CONVERTER_PATH = "tests/devices/unit_tests/test_lookup_table.txt" class StatusException(Exception): @@ -36,29 +24,10 @@ class StatusException(Exception): @pytest.fixture -def params(tmp_path: Path) -> DetectorParams: - return DetectorParams( - expected_energy_ev=TEST_EXPECTED_ENERGY, - exposure_time_s=TEST_EXPOSURE_TIME, - directory=str(tmp_path), - prefix=TEST_PREFIX, - run_number=TEST_RUN_NUMBER, - detector_distance=TEST_DETECTOR_DISTANCE, - omega_start=TEST_OMEGA_START, - omega_increment=TEST_OMEGA_INCREMENT, - num_images_per_trigger=TEST_NUM_IMAGES_PER_TRIGGER, - num_triggers=TEST_NUM_TRIGGERS, - use_roi_mode=TEST_USE_ROI_MODE, - det_dist_to_beam_converter_path=TEST_DET_DIST_TO_BEAM_CONVERTER_PATH, - detector_size_constants=TEST_DETECTOR_SIZE_CONSTANTS.det_type_string, - ) - - -@pytest.fixture -def fake_eiger(request, params: DetectorParams): +def fake_eiger(request, eiger_params: DetectorParams): FakeEigerDetector: EigerDetector = make_fake_device(EigerDetector) fake_eiger: EigerDetector = FakeEigerDetector.with_params( - params=params, name=f"test fake Eiger: {request.node.name}" + params=eiger_params, name=f"test fake Eiger: {request.node.name}" ) return fake_eiger @@ -255,8 +224,8 @@ def test_disable_roi_mode_sets_correct_roi_mode(fake_eiger): @pytest.mark.parametrize( "roi_mode, expected_detector_dimensions", [ - (True, TEST_DETECTOR_SIZE_CONSTANTS.roi_size_pixels), - (False, TEST_DETECTOR_SIZE_CONSTANTS.det_size_pixels), + (True, EIGER2_X_16M_SIZE.roi_size_pixels), + (False, EIGER2_X_16M_SIZE.det_size_pixels), ], ) def test_change_roi_mode_sets_correct_detector_size_constants( @@ -733,10 +702,10 @@ def test_when_eiger_is_stopped_then_dev_shm_disabled(fake_eiger: EigerDetector): assert fake_eiger.odin.fan.dev_shm_enable.get() == 0 -def test_for_other_beamlines_i03_used_as_default(params: DetectorParams): +def test_for_other_beamlines_i03_used_as_default(eiger_params: DetectorParams): FakeEigerDetector: EigerDetector = make_fake_device(EigerDetector) fake_eiger: EigerDetector = FakeEigerDetector.with_params( - params=params, beamline="ixx" + params=eiger_params, beamline="ixx" ) assert fake_eiger.beamline == "ixx" assert fake_eiger.timeouts == AVAILABLE_TIMEOUTS["i03"] From 68b71f016e828c5728b3dc82fad47c236fbec635 Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Mon, 28 Apr 2025 13:55:20 +0000 Subject: [PATCH 12/22] Add test again as ignored last time --- .../plans/test_configure_and_arm_detector.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/plans/test_configure_and_arm_detector.py diff --git a/tests/plans/test_configure_and_arm_detector.py b/tests/plans/test_configure_and_arm_detector.py new file mode 100644 index 00000000000..9513c770694 --- /dev/null +++ b/tests/plans/test_configure_and_arm_detector.py @@ -0,0 +1,46 @@ +from unittest.mock import AsyncMock, MagicMock + +import pytest +from bluesky.run_engine import RunEngine +from ophyd_async.core import DetectorTrigger +from ophyd_async.fastcs.eiger import EigerDetector as FastEiger +from ophyd_async.fastcs.eiger import EigerTriggerInfo +from ophyd_async.testing import callback_on_mock_put, set_mock_value + +from dodal.plans.configure_and_arm_detector import ( + configure_and_arm_detector, +) + + +@pytest.fixture +async def fake_eiger(): + fake_eiger = FastEiger("", MagicMock()) + await fake_eiger.connect(mock=True) + fake_eiger.drv.detector.arm.trigger = AsyncMock() + return fake_eiger + + +async def test_configure_and_arm_detector(fake_eiger, eiger_params, RE: RunEngine): + trigger_info = EigerTriggerInfo( + number_of_events=eiger_params.num_triggers, + energy_ev=eiger_params.expected_energy_ev, + trigger=DetectorTrigger.EDGE_TRIGGER, + deadtime=0.0001, + ) + + def set_meta_active(*args, **kwargs) -> None: + set_mock_value(fake_eiger.odin.meta_active, "Active") + + def set_capture_rbv_and_meta_writing(*args, **kwargs) -> None: + set_mock_value(fake_eiger.odin.capture_rbv, "Capturing") + set_mock_value(fake_eiger.odin.meta_writing, "Writing") + + callback_on_mock_put(fake_eiger.odin.num_to_capture, set_meta_active) + callback_on_mock_put(fake_eiger.odin.capture, set_capture_rbv_and_meta_writing) + + RE(configure_and_arm_detector(fake_eiger, eiger_params, trigger_info)) + fake_eiger.drv.detector.arm.trigger.assert_called_once() + assert ( + await fake_eiger.drv.detector.photon_energy.get_value() + == eiger_params.expected_energy_ev + ) From f97ed236c19f335615ab523ad4bbd3c75602fd01 Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Tue, 29 Apr 2025 10:29:33 +0000 Subject: [PATCH 13/22] Test against branch that awaits eiger arm --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7e83dd24603..f469e5c69d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ description = "Ophyd devices and other utils that could be used across DLS beaml dependencies = [ "click", "ophyd", - "ophyd-async>=0.10.0a3", + "ophyd-async@e383bf03fb5afe6a87de60dd075b1d278fb7b13a", "bluesky", "pyepics", "dataclasses-json", From 09f9eb17cb8640c96663291dc8a7f4a3f88e88bf Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Tue, 29 Apr 2025 10:33:28 +0000 Subject: [PATCH 14/22] Pin to branch --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f469e5c69d3..5645661ddc7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ description = "Ophyd devices and other utils that could be used across DLS beaml dependencies = [ "click", "ophyd", - "ophyd-async@e383bf03fb5afe6a87de60dd075b1d278fb7b13a", + "ophyd-async@git+https://github.com/bluesky/ophyd-async.git@await_eiger_controller_arm", "bluesky", "pyepics", "dataclasses-json", From 1b1a069c9090a6c5c75057fd9d8ba17baca868e7 Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Tue, 29 Apr 2025 11:01:34 +0000 Subject: [PATCH 15/22] Change _calculate_expected_images params type to int --- src/dodal/devices/fast_grid_scan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dodal/devices/fast_grid_scan.py b/src/dodal/devices/fast_grid_scan.py index c1aa6b81736..4aa97d076e9 100644 --- a/src/dodal/devices/fast_grid_scan.py +++ b/src/dodal/devices/fast_grid_scan.py @@ -234,7 +234,7 @@ def __init__(self, prefix: str, smargon_prefix: str, name: str = "") -> None: } super().__init__(name) - def _calculate_expected_images(self, x: float, y: float, z: float) -> float: + def _calculate_expected_images(self, x: int, y: int, z: int) -> int: LOGGER.info(f"Reading num of images found {x, y, z} images in each axis") first_grid = x * y second_grid = x * z From 0821c87488ef03e9a436721ae665810b29a47dd1 Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Fri, 9 May 2025 15:28:51 +0000 Subject: [PATCH 16/22] Add trigger and disarm to plan and tests --- src/dodal/plans/configure_and_arm_detector.py | 4 ++++ tests/conftest.py | 2 +- tests/plans/test_configure_and_arm_detector.py | 11 ++++++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/dodal/plans/configure_and_arm_detector.py b/src/dodal/plans/configure_and_arm_detector.py index e0fe4c234b7..9bea4ed8ab2 100644 --- a/src/dodal/plans/configure_and_arm_detector.py +++ b/src/dodal/plans/configure_and_arm_detector.py @@ -31,6 +31,10 @@ def configure_and_arm_detector( LOGGER.info(f"Setting MX PVs: {time.time() - start}s") yield from bps.prepare(eiger, trigger_info, wait=True) LOGGER.info(f"Preparing Eiger: {time.time() - start}s") + yield from bps.trigger(eiger, wait=True) + LOGGER.info(f"Triggering Eiger: {time.time() - start}s") + yield from bps.unstage(eiger, wait=True) + LOGGER.info(f"Disarming Eiger: {time.time() - start}s") def set_cam_pvs( diff --git a/tests/conftest.py b/tests/conftest.py index 88edf02935c..2fa889e8c8c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -56,7 +56,7 @@ def eiger_params(tmp_path: Path) -> DetectorParams: omega_start=0.0, omega_increment=1.0, num_images_per_trigger=1, - num_triggers=2000, + num_triggers=1, use_roi_mode=False, det_dist_to_beam_converter_path="tests/devices/unit_tests/test_lookup_table.txt", detector_size_constants=EIGER2_X_16M_SIZE.det_type_string, # type: ignore diff --git a/tests/plans/test_configure_and_arm_detector.py b/tests/plans/test_configure_and_arm_detector.py index 9513c770694..033ca470ca6 100644 --- a/tests/plans/test_configure_and_arm_detector.py +++ b/tests/plans/test_configure_and_arm_detector.py @@ -1,3 +1,4 @@ +from collections.abc import AsyncGenerator from unittest.mock import AsyncMock, MagicMock import pytest @@ -17,14 +18,20 @@ async def fake_eiger(): fake_eiger = FastEiger("", MagicMock()) await fake_eiger.connect(mock=True) fake_eiger.drv.detector.arm.trigger = AsyncMock() + fake_eiger.drv.detector.disarm.trigger = AsyncMock() + fake_eiger._writer.observe_indices_written = fake_observe_indices_written return fake_eiger +async def fake_observe_indices_written(timeout: float) -> AsyncGenerator[int, None]: + yield 1 + + async def test_configure_and_arm_detector(fake_eiger, eiger_params, RE: RunEngine): trigger_info = EigerTriggerInfo( number_of_events=eiger_params.num_triggers, energy_ev=eiger_params.expected_energy_ev, - trigger=DetectorTrigger.EDGE_TRIGGER, + trigger=DetectorTrigger.INTERNAL, deadtime=0.0001, ) @@ -40,6 +47,8 @@ def set_capture_rbv_and_meta_writing(*args, **kwargs) -> None: RE(configure_and_arm_detector(fake_eiger, eiger_params, trigger_info)) fake_eiger.drv.detector.arm.trigger.assert_called_once() + # Disarm occurs after a trigger in the plan. + fake_eiger.drv.detector.disarm.trigger.assert_called_once() assert ( await fake_eiger.drv.detector.photon_energy.get_value() == eiger_params.expected_energy_ev From 4f90da0c09bdaa6bfb00014bc1200c6575ba11a5 Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Fri, 9 May 2025 16:23:41 +0000 Subject: [PATCH 17/22] Amend conftest eiger params --- tests/conftest.py | 2 +- tests/plans/test_configure_and_arm_detector.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 2fa889e8c8c..88edf02935c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -56,7 +56,7 @@ def eiger_params(tmp_path: Path) -> DetectorParams: omega_start=0.0, omega_increment=1.0, num_images_per_trigger=1, - num_triggers=1, + num_triggers=2000, use_roi_mode=False, det_dist_to_beam_converter_path="tests/devices/unit_tests/test_lookup_table.txt", detector_size_constants=EIGER2_X_16M_SIZE.det_type_string, # type: ignore diff --git a/tests/plans/test_configure_and_arm_detector.py b/tests/plans/test_configure_and_arm_detector.py index 033ca470ca6..84a14075a7e 100644 --- a/tests/plans/test_configure_and_arm_detector.py +++ b/tests/plans/test_configure_and_arm_detector.py @@ -29,7 +29,8 @@ async def fake_observe_indices_written(timeout: float) -> AsyncGenerator[int, No async def test_configure_and_arm_detector(fake_eiger, eiger_params, RE: RunEngine): trigger_info = EigerTriggerInfo( - number_of_events=eiger_params.num_triggers, + # Manual trigger, so setting number of triggers to 1. + number_of_events=1, energy_ev=eiger_params.expected_energy_ev, trigger=DetectorTrigger.INTERNAL, deadtime=0.0001, From 21cef352c13b9dfca8d0dd42e9d486e1d65e7983 Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Mon, 19 May 2025 13:47:13 +0000 Subject: [PATCH 18/22] Remove setting filename and add kickoff and complete --- ...figure_arm_trigger_and_disarm_detector.py} | 53 ++++++++----------- ...figure_arm_trigger_and_disarm_detector.py} | 0 2 files changed, 22 insertions(+), 31 deletions(-) rename src/dodal/plans/{configure_and_arm_detector.py => configure_arm_trigger_and_disarm_detector.py} (85%) rename tests/plans/{test_configure_and_arm_detector.py => test_configure_arm_trigger_and_disarm_detector.py} (100%) diff --git a/src/dodal/plans/configure_and_arm_detector.py b/src/dodal/plans/configure_arm_trigger_and_disarm_detector.py similarity index 85% rename from src/dodal/plans/configure_and_arm_detector.py rename to src/dodal/plans/configure_arm_trigger_and_disarm_detector.py index 9bea4ed8ab2..cf8289ebd65 100644 --- a/src/dodal/plans/configure_and_arm_detector.py +++ b/src/dodal/plans/configure_arm_trigger_and_disarm_detector.py @@ -1,6 +1,7 @@ import time import bluesky.plan_stubs as bps +from bluesky import preprocessors as bpp from bluesky.run_engine import RunEngine from ophyd_async.core import DetectorTrigger from ophyd_async.fastcs.eiger import EigerDetector, EigerTriggerInfo @@ -10,29 +11,41 @@ from dodal.log import LOGGER, do_default_logging_setup +@bpp.run_decorator() def configure_and_arm_detector( eiger: EigerDetector, detector_params: DetectorParams, trigger_info: EigerTriggerInfo, ): - start = time.time() assert detector_params.expected_energy_ev - yield from bps.abs_set(eiger.odin.capture, 0) - LOGGER.info(f"Stopping Odin: {time.time() - start}s") + start = time.time() + yield from bps.unstage(eiger, wait=True) + LOGGER.info(f"Stopping Eiger-Odin: {time.time() - start}s") + start = time.time() yield from set_cam_pvs(eiger, detector_params, wait=True) LOGGER.info(f"Setting CAM PVs: {time.time() - start}s") - yield from set_odin_pvs(eiger, detector_params, wait=True) - LOGGER.info(f"Setting Odin PVs: {time.time() - start}s") + start = time.time() yield from change_roi_mode(eiger, detector_params, wait=True) LOGGER.info(f"Changing ROI Mode: {time.time() - start}s") + start = time.time() yield from bps.abs_set(eiger.odin.num_frames_chunks, 1) LOGGER.info(f"Setting # of Frame Chunks: {time.time() - start}s") + start = time.time() yield from set_mx_settings_pvs(eiger, detector_params, wait=True) LOGGER.info(f"Setting MX PVs: {time.time() - start}s") + start = time.time() yield from bps.prepare(eiger, trigger_info, wait=True) LOGGER.info(f"Preparing Eiger: {time.time() - start}s") - yield from bps.trigger(eiger, wait=True) + start = time.time() + yield from bps.kickoff(eiger, wait=True) + LOGGER.info(f"Kickoff Eiger: {time.time() - start}s") + start = time.time() + yield from bps.trigger(eiger.drv.detector.trigger) # type: ignore LOGGER.info(f"Triggering Eiger: {time.time() - start}s") + start = time.time() + yield from bps.complete(eiger, wait=True) + LOGGER.info(f"Completing Capture: {time.time() - start}s") + start = time.time() yield from bps.unstage(eiger, wait=True) LOGGER.info(f"Disarming Eiger: {time.time() - start}s") @@ -124,32 +137,10 @@ def set_mx_settings_pvs( yield from bps.wait(group) -def set_odin_pvs( - eiger: EigerDetector, - detector_params: DetectorParams, - wait: bool, - group="odin_pvs", -): - yield from bps.abs_set( - eiger.odin.file_path, - detector_params.directory, - group=group, - ) - yield from bps.abs_set( - eiger.odin.file_name, - detector_params.full_filename, - group=group, - ) - - if wait: - yield from bps.wait(group) - - if __name__ == "__main__": RE = RunEngine() do_default_logging_setup() eiger = fastcs_eiger(connect_immediately=True) - RE( configure_and_arm_detector( eiger=eiger, @@ -160,14 +151,14 @@ def set_odin_pvs( prefix="", detector_distance=255, omega_start=0, - omega_increment=0.2, + omega_increment=0.1, num_images_per_trigger=1, - num_triggers=5, + num_triggers=1, use_roi_mode=False, det_dist_to_beam_converter_path="/dls_sw/i03/software/daq_configuration/lookup/DetDistToBeamXYConverter.txt", ), trigger_info=EigerTriggerInfo( - number_of_events=5, + number_of_events=1, energy_ev=12800, trigger=DetectorTrigger.INTERNAL, deadtime=0.0001, diff --git a/tests/plans/test_configure_and_arm_detector.py b/tests/plans/test_configure_arm_trigger_and_disarm_detector.py similarity index 100% rename from tests/plans/test_configure_and_arm_detector.py rename to tests/plans/test_configure_arm_trigger_and_disarm_detector.py From d1e4eefe154478ff2bb95807f72571a7d903d8d4 Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Tue, 20 May 2025 10:24:33 +0000 Subject: [PATCH 19/22] Change plan name to be more descriptive --- .../configure_arm_trigger_and_disarm_detector.py | 4 ++-- ...st_configure_arm_trigger_and_disarm_detector.py | 14 ++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/dodal/plans/configure_arm_trigger_and_disarm_detector.py b/src/dodal/plans/configure_arm_trigger_and_disarm_detector.py index cf8289ebd65..0e5f63e7293 100644 --- a/src/dodal/plans/configure_arm_trigger_and_disarm_detector.py +++ b/src/dodal/plans/configure_arm_trigger_and_disarm_detector.py @@ -12,7 +12,7 @@ @bpp.run_decorator() -def configure_and_arm_detector( +def configure_arm_trigger_and_disarm_detector( eiger: EigerDetector, detector_params: DetectorParams, trigger_info: EigerTriggerInfo, @@ -142,7 +142,7 @@ def set_mx_settings_pvs( do_default_logging_setup() eiger = fastcs_eiger(connect_immediately=True) RE( - configure_and_arm_detector( + configure_arm_trigger_and_disarm_detector( eiger=eiger, detector_params=DetectorParams( expected_energy_ev=12800, diff --git a/tests/plans/test_configure_arm_trigger_and_disarm_detector.py b/tests/plans/test_configure_arm_trigger_and_disarm_detector.py index 84a14075a7e..b90e31560bf 100644 --- a/tests/plans/test_configure_arm_trigger_and_disarm_detector.py +++ b/tests/plans/test_configure_arm_trigger_and_disarm_detector.py @@ -8,8 +8,8 @@ from ophyd_async.fastcs.eiger import EigerTriggerInfo from ophyd_async.testing import callback_on_mock_put, set_mock_value -from dodal.plans.configure_and_arm_detector import ( - configure_and_arm_detector, +from dodal.plans.configure_arm_trigger_and_disarm_detector import ( + configure_arm_trigger_and_disarm_detector, ) @@ -27,7 +27,9 @@ async def fake_observe_indices_written(timeout: float) -> AsyncGenerator[int, No yield 1 -async def test_configure_and_arm_detector(fake_eiger, eiger_params, RE: RunEngine): +async def test_configure_arm_trigger_and_disarm_detector( + fake_eiger, eiger_params, RE: RunEngine +): trigger_info = EigerTriggerInfo( # Manual trigger, so setting number of triggers to 1. number_of_events=1, @@ -46,7 +48,11 @@ def set_capture_rbv_and_meta_writing(*args, **kwargs) -> None: callback_on_mock_put(fake_eiger.odin.num_to_capture, set_meta_active) callback_on_mock_put(fake_eiger.odin.capture, set_capture_rbv_and_meta_writing) - RE(configure_and_arm_detector(fake_eiger, eiger_params, trigger_info)) + RE( + configure_arm_trigger_and_disarm_detector( + fake_eiger, eiger_params, trigger_info + ) + ) fake_eiger.drv.detector.arm.trigger.assert_called_once() # Disarm occurs after a trigger in the plan. fake_eiger.drv.detector.disarm.trigger.assert_called_once() From 909643bcafb70371d98450555cf4b1fbfcb4d6e7 Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Tue, 20 May 2025 10:58:04 +0000 Subject: [PATCH 20/22] Fix assertions and add mock callback --- pyproject.toml | 2 +- ...test_configure_arm_trigger_and_disarm_detector.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5645661ddc7..f59288242e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ description = "Ophyd devices and other utils that could be used across DLS beaml dependencies = [ "click", "ophyd", - "ophyd-async@git+https://github.com/bluesky/ophyd-async.git@await_eiger_controller_arm", + "ophyd-async@git+https://github.com/bluesky/ophyd-async.git@amend_eiger_odin_for_dodal", "bluesky", "pyepics", "dataclasses-json", diff --git a/tests/plans/test_configure_arm_trigger_and_disarm_detector.py b/tests/plans/test_configure_arm_trigger_and_disarm_detector.py index b90e31560bf..0cb096bc12e 100644 --- a/tests/plans/test_configure_arm_trigger_and_disarm_detector.py +++ b/tests/plans/test_configure_arm_trigger_and_disarm_detector.py @@ -41,12 +41,16 @@ async def test_configure_arm_trigger_and_disarm_detector( def set_meta_active(*args, **kwargs) -> None: set_mock_value(fake_eiger.odin.meta_active, "Active") - def set_capture_rbv_and_meta_writing(*args, **kwargs) -> None: + def set_capture_rbv_meta_writing_and_detector_state(*args, **kwargs) -> None: + # Mimics capturing and immediete completion status on Eiger. set_mock_value(fake_eiger.odin.capture_rbv, "Capturing") set_mock_value(fake_eiger.odin.meta_writing, "Writing") + set_mock_value(fake_eiger.drv.detector.state, "idle") callback_on_mock_put(fake_eiger.odin.num_to_capture, set_meta_active) - callback_on_mock_put(fake_eiger.odin.capture, set_capture_rbv_and_meta_writing) + callback_on_mock_put( + fake_eiger.odin.capture, set_capture_rbv_meta_writing_and_detector_state + ) RE( configure_arm_trigger_and_disarm_detector( @@ -54,8 +58,8 @@ def set_capture_rbv_and_meta_writing(*args, **kwargs) -> None: ) ) fake_eiger.drv.detector.arm.trigger.assert_called_once() - # Disarm occurs after a trigger in the plan. - fake_eiger.drv.detector.disarm.trigger.assert_called_once() + # Disarm occurs at the start and end of the plan. + assert len(fake_eiger.drv.detector.disarm.trigger.call_args_list) == 2 assert ( await fake_eiger.drv.detector.photon_energy.get_value() == eiger_params.expected_energy_ev From b0dac4bb1c5a9d31295d679a72381c6b67957e9c Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Wed, 21 May 2025 14:23:47 +0100 Subject: [PATCH 21/22] Use latest ophyd-async version --- pyproject.toml | 10 +++++----- src/dodal/beamlines/i03.py | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f59288242e1..a7959ee3d77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ description = "Ophyd devices and other utils that could be used across DLS beaml dependencies = [ "click", "ophyd", - "ophyd-async@git+https://github.com/bluesky/ophyd-async.git@amend_eiger_odin_for_dodal", + "ophyd-async>=0.10.0a4", "bluesky", "pyepics", "dataclasses-json", @@ -24,9 +24,9 @@ dependencies = [ "requests", "graypy", "pydantic>=2.0", - "opencv-python-headless", # For pin-tip detection. - "aioca", # Required for CA support with ophyd-async. - "p4p", # Required for PVA support with ophyd-async. + "opencv-python-headless", # For pin-tip detection. + "aioca", # Required for CA support with ophyd-async. + "p4p", # Required for PVA support with ophyd-async. "numpy", "aiofiles", "aiohttp", @@ -105,7 +105,7 @@ asyncio_mode = "auto" markers = [ "adsim: marks tests as requiring the containerised AreaDetector simulator running (deselect with '-m \"not adsim\"')", "skip_in_pycharm: marks test as not working in pycharm testrunner", - "system_test: marks test as other system test that requires infrastructure" + "system_test: marks test as other system test that requires infrastructure", ] addopts = """ --cov=dodal --cov-report term --cov-report xml:cov.xml diff --git a/src/dodal/beamlines/i03.py b/src/dodal/beamlines/i03.py index d0a3108f76b..8e51ac773a3 100644 --- a/src/dodal/beamlines/i03.py +++ b/src/dodal/beamlines/i03.py @@ -174,7 +174,6 @@ def eiger(mock: bool = False) -> EigerDetector: ) -@device_factory(skip=BL == "s03") def fastcs_eiger(mock: bool = False) -> FastEiger: """Get the i03 FastCS Eiger device, instantiate it if it hasn't already been. If this is called when already instantiated in i03, it will return the existing object. From 25a6b53231fd2357841cd92f83cde73e9af6f4bf Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Wed, 21 May 2025 14:29:10 +0100 Subject: [PATCH 22/22] Fix typo --- src/dodal/beamlines/i03.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dodal/beamlines/i03.py b/src/dodal/beamlines/i03.py index 8e51ac773a3..dc374829002 100644 --- a/src/dodal/beamlines/i03.py +++ b/src/dodal/beamlines/i03.py @@ -174,7 +174,8 @@ def eiger(mock: bool = False) -> EigerDetector: ) -def fastcs_eiger(mock: bool = False) -> FastEiger: +@device_factory() +def fastcs_eiger() -> FastEiger: """Get the i03 FastCS Eiger device, instantiate it if it hasn't already been. If this is called when already instantiated in i03, it will return the existing object. """