diff --git a/pyproject.toml b/pyproject.toml index ed5693a70..a94c357c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,6 +92,7 @@ hyperion = "mx_bluesky.hyperion.__main__:main" hyperion-callbacks = "mx_bluesky.hyperion.external_interaction.callbacks.__main__:main" hyperion-generate-test-nexus = "mx_bluesky.hyperion.utils.validation:generate_test_nexus" hyperion-populate-test-and-meta-files = "mx_bluesky.hyperion.utils.validation:copy_test_meta_data_files" +jungfrau-commissioning = "mx_bluesky.beamlines.i24.jungfrau_commissioning.main:main" [project.urls] GitHub = "https://github.com/DiamondLightSource/mx-bluesky" diff --git a/src/mx_bluesky/beamlines/i24/jungfrau_commissioning/main.py b/src/mx_bluesky/beamlines/i24/jungfrau_commissioning/main.py index cbd630bfb..60675e658 100755 --- a/src/mx_bluesky/beamlines/i24/jungfrau_commissioning/main.py +++ b/src/mx_bluesky/beamlines/i24/jungfrau_commissioning/main.py @@ -1,4 +1,5 @@ import inspect +import pathlib from collections.abc import Callable from inspect import getmembers, isgeneratorfunction, signature @@ -105,7 +106,8 @@ def hlp(arg: Callable | None = None): setup: Config = Config() -setup.InteractiveShellApp.exec_files = ["setup_ipython.py"] +main_dir = pathlib.Path(__file__).parent.resolve() +setup.InteractiveShellApp.exec_files = [str(main_dir / "setup_ipython.py")] def main(): diff --git a/src/mx_bluesky/beamlines/i24/jungfrau_commissioning/setup_ipython.py b/src/mx_bluesky/beamlines/i24/jungfrau_commissioning/setup_ipython.py new file mode 100755 index 000000000..fa6ae9d94 --- /dev/null +++ b/src/mx_bluesky/beamlines/i24/jungfrau_commissioning/setup_ipython.py @@ -0,0 +1,31 @@ +# flake8: noqa + +# This file runs in the iPython session on startup + +from pathlib import Path + +from bluesky.run_engine import RunEngine +from dodal.beamlines import i24 + +from mx_bluesky.beamlines.i24.jungfrau_commissioning.main import ( + hlp, + list_devices, + list_plans, +) +from mx_bluesky.beamlines.i24.jungfrau_commissioning.plans.gain_mode_darks_plans import * +from mx_bluesky.beamlines.i24.jungfrau_commissioning.plans.jungfrau_plans import * +from mx_bluesky.beamlines.i24.jungfrau_commissioning.plans.rotation_scan_plans import * +from mx_bluesky.beamlines.i24.jungfrau_commissioning.plans.zebra_plans import * +from mx_bluesky.beamlines.i24.jungfrau_commissioning.utils.log import ( + set_up_logging_handlers, +) +from mx_bluesky.beamlines.i24.jungfrau_commissioning.utils import text_colors as col + +DIRECTORY = "/dls/i24/data/2024/cm37275-4/jungfrau_commissioning/" + + +set_up_logging_handlers() +hlp() +print(f"Creating Bluesky RunEngine with name {col.CYAN}RE{col.ENDC}") +RE = RunEngine({}) +print("System Ready!") diff --git a/tests/unit_tests/beamlines/i24/jungfrau_commissioning/test_data/complete_example_params.json b/tests/unit_tests/beamlines/i24/jungfrau_commissioning/test_data/complete_example_params.json new file mode 100644 index 000000000..75a9991f9 --- /dev/null +++ b/tests/unit_tests/beamlines/i24/jungfrau_commissioning/test_data/complete_example_params.json @@ -0,0 +1,15 @@ +{ + "scan_width_deg": 360.0, + "rotation_increment_deg": 0.1, + "omega_start_deg": 0.0, + "exposure_time_s": 0.01, + "acquire_time_s": 0.01, + "rotation_direction": "Positive", + "offset_deg": 1.0, + "shutter_opening_time_s": 0.6, + "storage_directory": "/tmp/jungfrau_data/", + "data_filename": "scan", + "x_start_um": 0.1, + "y_start_um": 0.1, + "z_start_um": 0.1 +} diff --git a/tests/unit_tests/beamlines/i24/jungfrau_commissioning/test_data/example_params.json b/tests/unit_tests/beamlines/i24/jungfrau_commissioning/test_data/example_params.json new file mode 100644 index 000000000..973f55d3d --- /dev/null +++ b/tests/unit_tests/beamlines/i24/jungfrau_commissioning/test_data/example_params.json @@ -0,0 +1,12 @@ +{ + "scan_width_deg": 360.0, + "rotation_increment_deg": 0.1, + "omega_start_deg": 0.0, + "exposure_time_s": 0.01, + "acquire_time_s": 0.01, + "rotation_direction": "Negative", + "offset_deg": 1.0, + "shutter_opening_time_s": 0.6, + "storage_directory": "/tmp/jungfrau_data/", + "data_filename": "scan" +} diff --git a/tests/unit_tests/beamlines/i24/jungfrau_commissioning/test_metadata_writer.py b/tests/unit_tests/beamlines/i24/jungfrau_commissioning/test_metadata_writer.py new file mode 100755 index 000000000..1b1389363 --- /dev/null +++ b/tests/unit_tests/beamlines/i24/jungfrau_commissioning/test_metadata_writer.py @@ -0,0 +1,148 @@ +import os +from collections.abc import Callable +from pathlib import Path +from unittest.mock import MagicMock, patch + +import bluesky.plan_stubs as bps +from bluesky.run_engine import RunEngine + +from mx_bluesky.beamlines.i24.jungfrau_commissioning.plans.rotation_scan_plans import ( + JfDevices, + get_rotation_scan_plan, +) +from mx_bluesky.beamlines.i24.jungfrau_commissioning.utils.params import ( + EXPERIMENT_PARAM_DUMP_FILENAME, + READING_DUMP_FILENAME, + RotationScanParameters, +) + + +def _fake_wait_for_writing(*_, **__): + yield from bps.sleep(0.2) + + +@patch( + "bluesky.plan_stubs.wait", +) +@patch( + "mx_bluesky.beamlines.i24.jungfrau_commissioning.plans.rotation_scan_plans.JsonMetadataWriter", +) +@patch( + "mx_bluesky.beamlines.i24.jungfrau_commissioning.plans.rotation_scan_plans.wait_for_writing", + _fake_wait_for_writing, +) +def test_rotation_scan_plan_metadata_callback_setup( + json_callback: MagicMock, + bps_wait: MagicMock, + fake_create_devices_function: Callable[..., JfDevices], + RE: RunEngine, + params: RotationScanParameters, +): + with patch( + "mx_bluesky.beamlines.i24.jungfrau_commissioning.plans.rotation_scan_plans.create_rotation_scan_devices", + fake_create_devices_function, + ): + plan = get_rotation_scan_plan(params) + + RE(plan) + + callback_instance: MagicMock = json_callback.return_value + callback_calls = callback_instance.call_args_list + assert len(callback_calls) == 8 + call_1 = callback_calls[0] + assert call_1.args[0] == "start" + assert call_1.args[1]["subplan_name"] == "rotation_scan_with_cleanup" + assert "rotation_scan_params" in call_1.args[1] + + +@patch( + "bluesky.plan_stubs.wait", +) +@patch( + "mx_bluesky.beamlines.i24.jungfrau_commissioning.plans.rotation_scan_plans.JsonMetadataWriter", +) +@patch( + "mx_bluesky.beamlines.i24.jungfrau_commissioning.plans.rotation_scan_plans.wait_for_writing", + _fake_wait_for_writing, +) +def test_rotation_scan_plan_metadata_callback_calls( + json_callback: MagicMock, + bps_wait: MagicMock, + fake_create_devices_function: Callable[..., JfDevices], + RE: RunEngine, + params: RotationScanParameters, +): + with patch( + "mx_bluesky.beamlines.i24.jungfrau_commissioning.plans.rotation_scan_plans.create_rotation_scan_devices", + fake_create_devices_function, + ): + devices = fake_create_devices_function() + devices["gonio"].x.user_readback.sim_put(0.1) # type: ignore + devices["gonio"].yh.user_readback.sim_put(0.2) # type: ignore + devices["gonio"].z.user_readback.sim_put(0.3) # type: ignore + plan = get_rotation_scan_plan(params) + + RE(plan) + + callback_instance: MagicMock = json_callback.return_value + callback_calls = callback_instance.call_args_list + pos_event_data = callback_calls[3][0][1]["data"] + assert pos_event_data["vgonio_x"] == 0.1 + assert pos_event_data["vgonio_yh"] == 0.2 + assert pos_event_data["vgonio_z"] == 0.3 + beam_event_data = callback_calls[5][0][1]["data"] + for key in ( + "beam_params_transmission", + "beam_params_wavelength", + "beam_params_energy", + "beam_params_intensity", + "beam_params_flux_xbpm2", + "beam_params_flux_xbpm3", + ): + assert key in beam_event_data.keys() + + +@patch( + "bluesky.plan_stubs.wait", +) +@patch( + "mx_bluesky.beamlines.i24.jungfrau_commissioning.plans.rotation_scan_plans.wait_for_writing", + _fake_wait_for_writing, +) +def test_rotation_scan_plan_metadata_callback_writes_files( + bps_wait: MagicMock, + fake_create_devices_function: Callable[..., JfDevices], + RE: RunEngine, + params: RotationScanParameters, + tmp_path: Path, +): + with open(tmp_path / "run_number.txt", "w") as f: + f.write("00005") + params.storage_directory = str(tmp_path) + params.data_filename = "test" + tm = fake_create_devices_function()["beam_params"].transmission.get() + expt_dir = f"{6:05d}_{params.data_filename}_scan_{int(params.scan_width_deg)}deg_{tm:.3f}transmission" + params_file_path = ( + Path(params.storage_directory) / expt_dir + ) / EXPERIMENT_PARAM_DUMP_FILENAME + reading_file_path = ( + Path(params.storage_directory) / expt_dir + ) / READING_DUMP_FILENAME + + if os.path.isfile(params_file_path): + os.remove(params_file_path) + if os.path.isfile(reading_file_path): + os.remove(reading_file_path) + + with patch( + "mx_bluesky.beamlines.i24.jungfrau_commissioning.plans.rotation_scan_plans.create_rotation_scan_devices", + fake_create_devices_function, + ): + plan = get_rotation_scan_plan(params) + + RE(plan) + + assert os.path.isfile(params_file_path) + assert os.path.isfile(reading_file_path) + os.remove(params_file_path) + os.remove(reading_file_path)