diff --git a/src/hyperion/device_setup_plans/manipulate_sample.py b/src/hyperion/device_setup_plans/manipulate_sample.py index d0f60544c..b7ebf46dd 100644 --- a/src/hyperion/device_setup_plans/manipulate_sample.py +++ b/src/hyperion/device_setup_plans/manipulate_sample.py @@ -61,3 +61,25 @@ def move_x_y_z( yield from bps.abs_set(smargon.z, z, group=group) if wait: yield from bps.wait(group) + + +def move_phi_chi_omega( + smargon: Smargon, + phi: float | None = None, + chi: float | None = None, + omega: float | None = None, + wait=False, + group="move_phi_chi_omega", +): + """Move the x, y, and z axes of the given smargon to the specified position. All + axes are optional.""" + + LOGGER.info(f"Moving smargon to phi, chi, omega: {(phi, chi, omega)}") + if phi: + yield from bps.abs_set(smargon.phi, phi, group=group) + if chi: + yield from bps.abs_set(smargon.chi, chi, group=group) + if omega: + yield from bps.abs_set(smargon.omega, omega, group=group) + if wait: + yield from bps.wait(group) diff --git a/src/hyperion/experiment_plans/rotation_scan_plan.py b/src/hyperion/experiment_plans/rotation_scan_plan.py index cee48c16a..ca2c5bb52 100644 --- a/src/hyperion/experiment_plans/rotation_scan_plan.py +++ b/src/hyperion/experiment_plans/rotation_scan_plan.py @@ -1,7 +1,6 @@ from __future__ import annotations import dataclasses -from typing import Any, Union import bluesky.plan_stubs as bps import bluesky.preprocessors as bpp @@ -10,7 +9,6 @@ from dodal.devices.attenuator import Attenuator from dodal.devices.backlight import Backlight from dodal.devices.dcm import DCM -from dodal.devices.detector import DetectorParams from dodal.devices.detector.detector_motion import DetectorMotion from dodal.devices.eiger import EigerDetector from dodal.devices.flux import Flux @@ -24,6 +22,7 @@ from hyperion.device_setup_plans.manipulate_sample import ( cleanup_sample_environment, + move_phi_chi_omega, move_x_y_z, setup_sample_environment, ) @@ -41,10 +40,6 @@ ) from hyperion.log import LOGGER from hyperion.parameters.constants import CONST -from hyperion.parameters.plan_specific.rotation_scan_internal_params import ( - RotationInternalParameters, - RotationScanParams, -) from hyperion.parameters.rotation import RotationScan from hyperion.utils.context import device_composite_from_context @@ -101,8 +96,7 @@ class RotationMotionProfile: def calculate_motion_profile( - detector_params: DetectorParams, - expt_params: RotationScanParams, + params: RotationScan, motor_time_to_speed_s: float, max_velocity_deg_s: float, ) -> RotationMotionProfile: @@ -114,19 +108,19 @@ def calculate_motion_profile( See https://github.com/DiamondLightSource/hyperion/wiki/rotation-scan-geometry for a simple pictorial explanation.""" - direction = ROTATION_DIRECTION[expt_params.rotation_direction] - num_images = expt_params.get_num_images() - shutter_time_s = expt_params.shutter_opening_time_s - image_width_deg = detector_params.omega_increment - exposure_time_s = detector_params.exposure_time + direction = ROTATION_DIRECTION[params.rotation_direction] + num_images = params.num_images + shutter_time_s = params.shutter_opening_time_s + image_width_deg = params.rotation_increment_deg + exposure_time_s = params.exposure_time_s motor_time_to_speed_s *= ACCELERATION_MARGIN - start_scan_deg = detector_params.omega_start + start_scan_deg = params.omega_start_deg LOGGER.info("Calculating rotation scan motion profile:") LOGGER.info( f"{num_images=}, {shutter_time_s=}, {image_width_deg=}, {exposure_time_s=}, {direction=}" ) - LOGGER.info(f"{(scan_width_deg := num_images * detector_params.omega_increment)=}") + LOGGER.info(f"{(scan_width_deg := num_images * params.rotation_increment_deg)=}") LOGGER.info(f"{(speed_for_rotation_deg_s := image_width_deg / exposure_time_s)=}") LOGGER.info( f"{(acceleration_offset_deg := motor_time_to_speed_s * speed_for_rotation_deg_s)=}" @@ -135,7 +129,7 @@ def calculate_motion_profile( f"{(start_motion_deg := start_scan_deg - (acceleration_offset_deg * direction))=}" ) LOGGER.info( - f"{(shutter_opening_deg := speed_for_rotation_deg_s * expt_params.shutter_opening_time_s)=}" + f"{(shutter_opening_deg := speed_for_rotation_deg_s * shutter_time_s)=}" ) LOGGER.info(f"{(total_exposure_s := num_images * exposure_time_s)=}") LOGGER.info( @@ -147,7 +141,7 @@ def calculate_motion_profile( start_motion_deg=start_motion_deg, scan_width_deg=scan_width_deg, shutter_time_s=shutter_time_s, - direction=expt_params.rotation_direction, + direction=params.rotation_direction, speed_for_rotation_deg_s=speed_for_rotation_deg_s, acceleration_offset_deg=acceleration_offset_deg, shutter_opening_deg=shutter_opening_deg, @@ -159,7 +153,7 @@ def calculate_motion_profile( def rotation_scan_plan( composite: RotationScanComposite, - params: RotationInternalParameters, + params: RotationScan, motion_values: RotationMotionProfile, ): """A plan to collect diffraction images from a sample continuously rotating about @@ -170,8 +164,8 @@ def rotation_scan_plan( @bpp.run_decorator( md={ "subplan_name": CONST.PLAN.ROTATION_MAIN, - "zocalo_environment": params.hyperion_params.zocalo_environment, - "scan_points": [params.get_scan_points()], + "zocalo_environment": params.zocalo_environment, + "scan_points": [params.scan_points], } ) def _rotation_scan_plan( @@ -188,7 +182,7 @@ def _rotation_scan_plan( yield from bps.abs_set( axis, motion_values.start_motion_deg, - group="move_to_start", + group="move_to_rotation_start", wait=True, ) yield from setup_zebra_for_rotation( @@ -205,8 +199,8 @@ def _rotation_scan_plan( LOGGER.info("Wait for any previous moves...") # wait for all the setup tasks at once yield from bps.wait("setup_senv") - yield from bps.wait("move_x_y_z") - yield from bps.wait("move_to_start") + yield from bps.wait("move_gonio_to_start") + yield from bps.wait("move_to_rotation_start") yield from bps.wait("setup_zebra") # get some information for the ispyb deposition and trigger the callback @@ -253,20 +247,14 @@ def cleanup_plan(composite: RotationScanComposite, max_vel: float, **kwargs): def rotation_scan( composite: RotationScanComposite, - parameters: Union[RotationScan, RotationInternalParameters, Any], + parameters: RotationScan, ) -> MsgGenerator: - old_parameters = ( - parameters - if isinstance(parameters, RotationInternalParameters) - else parameters.old_parameters() - ) - @bpp.set_run_key_decorator("rotation_scan") @bpp.run_decorator( # attach experiment metadata to the start document md={ "subplan_name": CONST.PLAN.ROTATION_OUTER, CONST.TRIGGER.ZOCALO: CONST.PLAN.ROTATION_MAIN, - "hyperion_internal_parameters": parameters.json(), + "hyperion_internal_parameters": parameters.old_parameters().json(), "activate_callbacks": [ "RotationISPyBCallback", "RotationNexusFileCallback", @@ -274,7 +262,7 @@ def rotation_scan( } ) def rotation_scan_plan_with_stage_and_cleanup( - params: RotationInternalParameters, + params: RotationScan, ): motor_time_to_speed = yield from bps.rd(composite.smargon.omega.acceleration) max_vel = ( @@ -282,33 +270,38 @@ def rotation_scan_plan_with_stage_and_cleanup( or DEFAULT_MAX_VELOCITY ) motion_values = calculate_motion_profile( - params.hyperion_params.detector_params, - params.experiment_params, + params, motor_time_to_speed, max_vel, ) eiger: EigerDetector = composite.eiger - eiger.set_detector_parameters(params.hyperion_params.detector_params) + eiger.set_detector_parameters(params.detector_params) @bpp.stage_decorator([eiger]) @bpp.finalize_decorator(lambda: cleanup_plan(composite, max_vel)) - def rotation_with_cleanup_and_stage(params: RotationInternalParameters): + def rotation_with_cleanup_and_stage(params: RotationScan): LOGGER.info("setting up sample environment...") yield from setup_sample_environment( composite.detector_motion, composite.backlight, composite.attenuator, - params.experiment_params.transmission_fraction, - params.hyperion_params.detector_params.detector_distance, + params.transmission_frac, + params.detector_params.detector_distance, ) LOGGER.info("moving to position (if specified)") yield from move_x_y_z( composite.smargon, - params.experiment_params.x, - params.experiment_params.y, - params.experiment_params.z, - group="move_x_y_z", + params.x_start_um, + params.y_start_um, + params.z_start_um, + group="move_gonio_to_start", + ) + yield from move_phi_chi_omega( + composite.smargon, + params.phi_start_deg, + params.chi_start_deg, + group="move_gonio_to_start", ) yield from rotation_scan_plan( composite, @@ -319,4 +312,4 @@ def rotation_with_cleanup_and_stage(params: RotationInternalParameters): LOGGER.info("setting up and staging eiger...") yield from rotation_with_cleanup_and_stage(params) - yield from rotation_scan_plan_with_stage_and_cleanup(old_parameters) + yield from rotation_scan_plan_with_stage_and_cleanup(parameters) diff --git a/src/hyperion/parameters/components.py b/src/hyperion/parameters/components.py index 0ed145800..9b5194fbd 100644 --- a/src/hyperion/parameters/components.py +++ b/src/hyperion/parameters/components.py @@ -61,7 +61,6 @@ class XyzAxis(str, Enum): class HyperionParameters(BaseModel): class Config: arbitrary_types_allowed = True - use_enum_values = True extra = Extra.forbid json_encoders = { ParameterVersion: lambda pv: str(pv), diff --git a/tests/conftest.py b/tests/conftest.py index f39528b17..4c46b498a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -69,9 +69,7 @@ from hyperion.parameters.plan_specific.panda.panda_gridscan_internal_params import ( PandAGridscanInternalParameters, ) -from hyperion.parameters.plan_specific.rotation_scan_internal_params import ( - RotationInternalParameters, -) +from hyperion.parameters.rotation import RotationScan i03.DAQ_CONFIGURATION_PATH = "tests/test_data/test_daq_configuration" @@ -240,18 +238,18 @@ def test_panda_fgs_params(): @pytest.fixture def test_rotation_params(): - return RotationInternalParameters( + return RotationScan( **raw_params_from_file( - "tests/test_data/parameter_json_files/good_test_rotation_scan_parameters.json" + "tests/test_data/new_parameter_json_files/good_test_rotation_scan_parameters.json" ) ) @pytest.fixture def test_rotation_params_nomove(): - return RotationInternalParameters( + return RotationScan( **raw_params_from_file( - "tests/test_data/parameter_json_files/good_test_rotation_scan_parameters_nomove.json" + "tests/test_data/new_parameter_json_files/good_test_rotation_scan_parameters_nomove.json" ) ) diff --git a/tests/test_data/new_parameter_json_files/good_test_rotation_scan_parameters.json b/tests/test_data/new_parameter_json_files/good_test_rotation_scan_parameters.json new file mode 100644 index 000000000..b28ac6dcc --- /dev/null +++ b/tests/test_data/new_parameter_json_files/good_test_rotation_scan_parameters.json @@ -0,0 +1,55 @@ +{ + "parameter_model_version": "5.0.0", + "comment": "test", + "det_dist_to_beam_converter_path": "tests/test_data/test_lookup_table.txt", + "storage_directory": "/tmp/dls/i03/data/2024/cm31105-4/auto/123456/", + "detector_distance_mm": 100.0, + "detector": "EIGER2_X_16M", + "demand_energy_ev": 100, + "exposure_time_s": 0.1, + "insertion_prefix": "SR03S", + "omega_start_deg": 0.0, + "file_name": "file_name", + "scan_width_deg": 180.0, + "rotation_axis": "omega", + "rotation_direction": "Negative", + "rotation_increment_deg": 0.1, + "run_number": 0, + "sample_id": 123456, + "shutter_opening_time_s": 0.6, + "visit": "cm31105-4", + "zocalo_environment": "dev_artemis", + "transmission_frac": 0.1, + "phi_start_deg": 0.47, + "chi_start_deg": 23.85, + "x_start_um": 1.0, + "y_start_um": 2.0, + "z_start_um": 3.0, + "ispyb_extras": { + "position": [ + 10.0, + 20.0, + 30.0 + ], + "xtal_snapshots_omega_start": [ + "test_1_y", + "test_2_y", + "test_3_y" + ], + "xtal_snapshots_omega_end": [ + "test_1_z", + "test_2_z", + "test_3_z" + ], + "xtal_snapshots": [ + "test_1", + "test_2", + "test_3" + ], + "beam_size_x": 1.0, + "beam_size_y": 1.0, + "focal_spot_size_x": 1.0, + "focal_spot_size_y": 1.0, + "resolution": 1.0 + } +} \ No newline at end of file diff --git a/tests/unit_tests/experiment_plans/test_rotation_scan_plan.py b/tests/unit_tests/experiment_plans/test_rotation_scan_plan.py index 339e35aa3..6675d35b6 100644 --- a/tests/unit_tests/experiment_plans/test_rotation_scan_plan.py +++ b/tests/unit_tests/experiment_plans/test_rotation_scan_plan.py @@ -17,9 +17,7 @@ rotation_scan, rotation_scan_plan, ) -from hyperion.parameters.plan_specific.rotation_scan_internal_params import ( - RotationInternalParameters, -) +from hyperion.parameters.rotation import RotationScan from .conftest import fake_read @@ -33,7 +31,7 @@ def do_rotation_main_plan_for_tests( run_eng: RunEngine, - expt_params: RotationInternalParameters, + expt_params: RotationScan, devices: RotationScanComposite, motion_values: RotationMotionProfile, plan: Callable = rotation_scan_plan, @@ -50,7 +48,7 @@ def do_rotation_main_plan_for_tests( @pytest.fixture def run_full_rotation_plan( RE: RunEngine, - test_rotation_params: RotationInternalParameters, + test_rotation_params: RotationScan, fake_create_rotation_devices: RotationScanComposite, ): with patch( @@ -64,18 +62,17 @@ def run_full_rotation_plan( @pytest.fixture -def motion_values(test_rotation_params: RotationInternalParameters): +def motion_values(test_rotation_params: RotationScan): return calculate_motion_profile( - test_rotation_params.hyperion_params.detector_params, - test_rotation_params.experiment_params, - 0.005, + test_rotation_params, + 0.005, # time for acceleration 222, ) def setup_and_run_rotation_plan_for_tests( RE: RunEngine, - test_params: RotationInternalParameters, + test_params: RotationScan, fake_create_rotation_devices: RotationScanComposite, motion_values, ): @@ -106,7 +103,7 @@ def side_set_w_return(obj, *args): @pytest.fixture def setup_and_run_rotation_plan_for_tests_standard( RE: RunEngine, - test_rotation_params: RotationInternalParameters, + test_rotation_params: RotationScan, fake_create_rotation_devices: RotationScanComposite, motion_values, ): @@ -118,7 +115,7 @@ def setup_and_run_rotation_plan_for_tests_standard( @pytest.fixture def setup_and_run_rotation_plan_for_tests_nomove( RE: RunEngine, - test_rotation_params_nomove: RotationInternalParameters, + test_rotation_params_nomove: RotationScan, fake_create_rotation_devices: RotationScanComposite, motion_values, ): @@ -127,14 +124,12 @@ def setup_and_run_rotation_plan_for_tests_nomove( ) -def test_rotation_scan_calculations(test_rotation_params: RotationInternalParameters): - test_rotation_params.hyperion_params.detector_params.exposure_time = 0.2 - test_rotation_params.hyperion_params.detector_params.omega_start = 10 - test_rotation_params.experiment_params.omega_start = 10 +def test_rotation_scan_calculations(test_rotation_params: RotationScan): + test_rotation_params.exposure_time_s = 0.2 + test_rotation_params.omega_start_deg = 10 motion_values = calculate_motion_profile( - test_rotation_params.hyperion_params.detector_params, - test_rotation_params.experiment_params, + test_rotation_params, 0.005, # time for acceleration 224, ) @@ -160,7 +155,7 @@ def test_rotation_scan_calculations(test_rotation_params: RotationInternalParame def test_rotation_scan( plan: MagicMock, RE: RunEngine, - test_rotation_params, + test_rotation_params: RotationScan, fake_create_rotation_devices: RotationScanComposite, ): composite = fake_create_rotation_devices @@ -179,14 +174,12 @@ async def test_rotation_plan_zebra_settings( setup_and_run_rotation_plan_for_tests_standard, ) -> None: zebra: Zebra = setup_and_run_rotation_plan_for_tests_standard["zebra"] - params: RotationInternalParameters = setup_and_run_rotation_plan_for_tests_standard[ + params: RotationScan = setup_and_run_rotation_plan_for_tests_standard[ "test_rotation_params" ] - expt_params = params.experiment_params - assert await zebra.pc.gate_start.get_value() == expt_params.omega_start - assert await zebra.pc.gate_start.get_value() == expt_params.omega_start - assert await zebra.pc.pulse_start.get_value() == expt_params.shutter_opening_time_s + assert await zebra.pc.gate_start.get_value() == params.omega_start_deg + assert await zebra.pc.pulse_start.get_value() == params.shutter_opening_time_s def test_full_rotation_plan_smargon_settings( @@ -194,22 +187,19 @@ def test_full_rotation_plan_smargon_settings( test_rotation_params, ) -> None: smargon: Smargon = run_full_rotation_plan.smargon - params: RotationInternalParameters = test_rotation_params - expt_params = params.experiment_params + params: RotationScan = test_rotation_params test_max_velocity = smargon.omega.max_velocity.get() omega_set: MagicMock = smargon.omega.set # type: ignore omega_velocity_set: MagicMock = smargon.omega.velocity.set # type: ignore - rotation_speed = ( - expt_params.image_width / params.hyperion_params.detector_params.exposure_time - ) + rotation_speed = params.rotation_increment_deg / params.exposure_time_s - assert smargon.phi.user_readback.get() == expt_params.phi_start - assert smargon.chi.user_readback.get() == expt_params.chi_start - assert smargon.x.user_readback.get() == expt_params.x - assert smargon.y.user_readback.get() == expt_params.y - assert smargon.z.user_readback.get() == expt_params.z + assert smargon.phi.user_readback.get() == params.phi_start_deg + assert smargon.chi.user_readback.get() == params.chi_start_deg + assert smargon.x.user_readback.get() == params.x_start_um + assert smargon.y.user_readback.get() == params.y_start_um + assert smargon.z.user_readback.get() == params.z_start_um assert omega_set.call_count == 2 assert omega_velocity_set.call_count == 3 assert omega_velocity_set.call_args_list == [ @@ -223,16 +213,14 @@ def test_rotation_plan_smargon_doesnt_move_xyz_if_not_given_in_params( setup_and_run_rotation_plan_for_tests_nomove, ) -> None: smargon: Smargon = setup_and_run_rotation_plan_for_tests_nomove["smargon"] - params: RotationInternalParameters = setup_and_run_rotation_plan_for_tests_nomove[ + params: RotationScan = setup_and_run_rotation_plan_for_tests_nomove[ "test_rotation_params" ] - expt_params = params.experiment_params - - assert expt_params.phi_start is None - assert expt_params.chi_start is None - assert expt_params.x is None - assert expt_params.y is None - assert expt_params.z is None + assert params.phi_start_deg is None + assert params.chi_start_deg is None + assert params.x_start_um is None + assert params.y_start_um is None + assert params.z_start_um is None for motor in [smargon.phi, smargon.chi, smargon.x, smargon.y, smargon.z]: assert motor.user_readback.get() == 0 motor.set.assert_not_called() # type: ignore diff --git a/tests/unit_tests/parameters/test_parameter_model.py b/tests/unit_tests/parameters/test_parameter_model.py index ba0be3c84..38866fe02 100644 --- a/tests/unit_tests/parameters/test_parameter_model.py +++ b/tests/unit_tests/parameters/test_parameter_model.py @@ -159,8 +159,6 @@ class TestNewGdaParams: omega_start = 0.023 transmission = 45 / 100 visit = "cm66666-6" - microns_per_pixel_x = 0.7844 - microns_per_pixel_y = 0.7111 position = [123.0, 234.0, 345.0] beam_size_x = 131 / 1000.0 beam_size_y = 204 / 1000.0 @@ -219,8 +217,6 @@ def test_pin_then_xray(self): "sample_id": self.sample_id, "visit_path": "/tmp/dls/i03/data/2024/cm66666-6", "undulator_gap": 0.5, - "microns_per_pixel_x": self.microns_per_pixel_x, - "microns_per_pixel_y": self.microns_per_pixel_y, "position": self.position, "beam_size_x": self.beam_size_x, "beam_size_y": self.beam_size_y, @@ -310,8 +306,6 @@ def test_rotation_new_params(self): "sample_id": self.sample_id, "visit_path": "/tmp/dls/i03/data/2024/cm66666-6", "undulator_gap": 0.5, - "microns_per_pixel_x": self.microns_per_pixel_x, - "microns_per_pixel_y": self.microns_per_pixel_y, "position": self.position, "beam_size_x": self.beam_size_x, "beam_size_y": self.beam_size_y,