diff --git a/docs/developer/hyperion/reference/gridscan.puml b/docs/developer/hyperion/reference/gridscan.puml index 3c22210486..c0f8fdab39 100644 --- a/docs/developer/hyperion/reference/gridscan.puml +++ b/docs/developer/hyperion/reference/gridscan.puml @@ -1,38 +1,70 @@ @startuml title Gridscan Parameter Relationships -class DiffractionExperiment -class DiffractionExperimentWithSample -class GridCommon { - grid_width_um - exposure_time_s +package dodal { + class AbstractExperimentParameterBase + class AbstractExperimentWithBeamParams { + transmission_fraction + } + class GridScanParamsCommon { + x_steps + y_steps + x_step_size_mm + y_step_size_mm + x_start_mm + y1_start_mm + z1_start_mm + set_stub_offsets + } + class GridScanParamsThreeD + class PandAGridScanParams + class ZebraGridScanParamsThreeD + class ZebraGridScanParamsTwoD } -class GridScanWithEdgeDetect { - box_size_um -} -class HyperionGridCommon { - enable_dev_shm + +rectangle "Internal Experiment Parameter Model" { + class MxBlueSkyParameters + class DiffractionExperiment + class DiffractionExperimentWithSample + class GenericGrid { + grid_width_um + exposure_time_s + } + class GenericGridWithHyperionDetectorParams + class GridScanWithEdgeDetect { + box_size_um + } + class PinTipCentreThenXrayCentre + class RobotLoadThenCentre } -class HyperionThreeDGridScan { - x_step_size_um - y_step_size_um - z_step_size_um - y2_start_um - z2_start_um - -- - grid_1_spec - grid_2_spec - scan_indices - scan_spec - scan_points - scan_points_first_grid - scan_points_second_grid - num_images - FGS_Params - panda_FGS_Params + + + +rectangle "Specified Grids" { + class SpecifiedGrids { + omega_starts_deg: float[] + x_step_size_um + y_step_sizes_um + x_steps + y_steps: int[] + -- + fast_grid_scan_params: GenericParamType + grid_specs + scan_indices + scan_points + scan_spec + num_images + } + class "SpecifiedGrids" as SpecifiedGridsZebraGridScanParamsThreeD + class "SpecifiedGrids" as SpecifiedGridsZebraGridScanParamsTwoD + class SpecifiedTwoDGridScan + class SpecifiedThreeDGridScan + class HyperionSpecifiedThreeDGridScan { + -- + panda_fast_gridscan_params: PandAGridScanParams + } } -class MxBlueSkyParameters -class SpecifiedGrid + class XyzStarts { x_start_um y_start_um @@ -43,29 +75,28 @@ class OptionalXYZStarts { y_start_um z_start_um } -class RotationScanPerSweep MxBlueSkyParameters <|-- DiffractionExperiment DiffractionExperiment <|-- DiffractionExperimentWithSample -DiffractionExperimentWithSample <|-- GridCommon -GridCommon <|-- GridScanWithEdgeDetect -GridCommon <|-- HyperionGridCommon -HyperionGridCommon <|-- HyperionThreeDGridScan -SpecifiedGrid <|-- HyperionThreeDGridScan -XyzStarts <|-- SpecifiedGrid +DiffractionExperimentWithSample <|-- GenericGrid +GenericGrid <|-- GenericGridWithHyperionDetectorParams +GenericGridWithHyperionDetectorParams <|-- GridScanWithEdgeDetect +GenericGridWithHyperionDetectorParams <|-- PinTipCentreThenXrayCentre +GenericGridWithHyperionDetectorParams <|-- RobotLoadThenCentre +GenericGrid <|-- SpecifiedGrids +SpecifiedGridsZebraGridScanParamsTwoD <|-- SpecifiedTwoDGridScan +SpecifiedGridsZebraGridScanParamsThreeD <|-- SpecifiedThreeDGridScan +SpecifiedThreeDGridScan <|-- HyperionSpecifiedThreeDGridScan +XyzStarts <|-- SpecifiedGrids OptionalXYZStarts <|-- RotationScanPerSweep class GridParamUpdate { - x_start_um - y_start_um - y2_start_um - z_start_um - z2_start_um - x_steps - y_steps - z_steps - x_step_size_um - y_step_size_um - z_step_size_um + x_start_um: float + y_starts_um: float[] + z_starts_um: float[] + x_steps: float + y_steps: float[] + x_step_size_um: float + y_step_sizes_um: float[] } class GridDetectionCallback { @@ -73,37 +104,22 @@ class GridDetectionCallback { } GridDetectionCallback --> GridParamUpdate : generates from event. Adds 0.5 to get box-centres -GridParamUpdate --> HyperionThreeDGridScan : combines with GridScanWithEdgeDetect +GridParamUpdate --> HyperionSpecifiedThreeDGridScan : combines with GridScanWithEdgeDetect class experiment_plans { grid_detect_then_xray_centre() common_flyscan_xray_centre() - create_parameters_for_flyscan_xray_centre(GridScanWithEdgeDetect, GridParamUpdate) -> HyperionThreeDGridScan -} - -class AbstractExperimentBase -class AbstractExperimentWithBeamParams -class GridScanParamsCommon { - x_steps - y_steps - z_steps - x_step_size_mm - y_step_size_mm - z_step_size_mm - x_start_mm - y1_start_mm - y2_start_mm - z1_start_mm - z2_start_mm + create_parameters_for_flyscan_xray_centre(GridScanWithEdgeDetect, GridParamUpdate) -> HyperionSpecifiedThreeDGridScan } -class PandAGridScanParams -class ZebraGridScanParamsThreeD -AbstractExperimentBase <|-- AbstractExperimentWithBeamParams +AbstractExperimentParameterBase <|-- AbstractExperimentWithBeamParams AbstractExperimentWithBeamParams <|-- GridScanParamsCommon -GridScanParamsCommon <|-- PandAGridScanParams -GridScanParamsCommon <|-- ZebraGridScanParamsThreeD +GridScanParamsCommon <|-- ZebraGridScanParamsTwoD +GridScanParamsCommon <|-- GridScanParamsThreeD +GridScanParamsThreeD <|-- PandAGridScanParams +GridScanParamsThreeD <|-- ZebraGridScanParamsThreeD -HyperionThreeDGridScan --> ZebraGridScanParamsThreeD : generates -HyperionThreeDGridScan --> PandAGridScanParams : generates +HyperionSpecifiedThreeDGridScan --> ZebraGridScanParamsThreeD : generates +HyperionSpecifiedThreeDGridScan --> PandAGridScanParams : generates +SpecifiedTwoDGridScan --> ZebraGridScanParamsTwoD : generates @enduml diff --git a/src/mx_bluesky/beamlines/i02_1/parameters/gridscan.py b/src/mx_bluesky/beamlines/i02_1/parameters/gridscan.py index a3d568c0dd..7c31bc5830 100644 --- a/src/mx_bluesky/beamlines/i02_1/parameters/gridscan.py +++ b/src/mx_bluesky/beamlines/i02_1/parameters/gridscan.py @@ -1,34 +1,27 @@ from dodal.devices.beamlines.i02_1.fast_grid_scan import ZebraGridScanParamsTwoD -from scanspec.specs import Product from mx_bluesky.common.parameters.components import SplitScan, WithOptionalEnergyChange -from mx_bluesky.common.parameters.gridscan import SpecifiedGrid +from mx_bluesky.common.parameters.gridscan import SpecifiedGrids class SpecifiedTwoDGridScan( - SpecifiedGrid[ZebraGridScanParamsTwoD], + SpecifiedGrids[ZebraGridScanParamsTwoD], SplitScan, WithOptionalEnergyChange, ): """Parameters representing a so-called 2D grid scan, which consists of doing a gridscan in X and Y.""" - @property - def scan_spec(self) -> Product[str]: - """A fully specified ScanSpec object representing the grid, with x, y, z and - omega positions.""" - return self.grid_1_spec - @property def fast_gridscan_params(self) -> ZebraGridScanParamsTwoD: return ZebraGridScanParamsTwoD( x_steps=self.x_steps, - y_steps=self.y_steps, + y_steps=self.y_steps[0], x_step_size_mm=self.x_step_size_um / 1000, - y_step_size_mm=self.y_step_size_um / 1000, + y_step_size_mm=self.y_step_sizes_um[0] / 1000, x_start_mm=self.x_start_um / 1000, - y1_start_mm=self.y_start_um / 1000, - z1_start_mm=self.z_start_um / 1000, + y1_start_mm=self.y_starts_um[0] / 1000, + z1_start_mm=self.z_starts_um[0] / 1000, set_stub_offsets=self._set_stub_offsets, transmission_fraction=0.5, dwell_time_ms=self.exposure_time_s, diff --git a/src/mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py b/src/mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py index a4c5e08f5a..9581760880 100644 --- a/src/mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +++ b/src/mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py @@ -80,7 +80,7 @@ GridDetectThenXRayCentreComposite, ) from mx_bluesky.common.parameters.gridscan import ( - GridCommon, + GenericGrid, SpecifiedThreeDGridScan, ) from mx_bluesky.common.preprocessors.preprocessors import ( @@ -105,7 +105,7 @@ class I04AutoXrcParams(BaseModel): def _change_beamsize( - transfocator: Transfocator, beamsize: float, parameters: GridCommon + transfocator: Transfocator, beamsize: float, parameters: GenericGrid ): """i04 always uses the large aperture and changes beamsize with the transfocator. @@ -188,7 +188,7 @@ def i04_default_grid_detect_and_xray_centre( initial_z = yield from bps.rd(smargon.z.user_readback) _current_wavelength_a = yield from bps.rd(composite.dcm.wavelength_in_a) - grid_common_params = _get_grid_common_params(_current_wavelength_a, parameters) + grid_common_params = _get_generic_grid_params(_current_wavelength_a, parameters) def tidy_beamline(): yield from bps.mv(transfocator, initial_beamsize) @@ -275,7 +275,7 @@ def create_gridscan_callbacks() -> tuple[ return ( GridscanNexusFileCallback(param_type=SpecifiedThreeDGridScan), GridscanISPyBCallback( - param_type=GridCommon, + param_type=GenericGrid, emit=ZocaloCallback( PlanNameConstants.DO_FGS, EnvironmentConstants.ZOCALO_ENV, @@ -336,9 +336,9 @@ def construct_i04_specific_features( ) -def _get_grid_common_params( +def _get_generic_grid_params( _current_wavelength_a: float, parameters: I04AutoXrcParams -) -> GridCommon: +) -> GenericGrid: """Calculate scaled transmission and exposure by comparing current beamline energy to default energy""" feature_settings = get_i04_feature_settings() _assumed_wavelength_a = feature_settings.ASSUMED_WAVELENGTH_IN_A @@ -353,7 +353,7 @@ def _get_grid_common_params( ) ) - return GridCommon( + return GenericGrid( sample_id=parameters.sample_id, file_name=parameters.file_name, visit=parameters.visit, diff --git a/src/mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py b/src/mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py index 13cf7fc7a7..d5d60d9ba5 100644 --- a/src/mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +++ b/src/mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py @@ -204,7 +204,8 @@ def run_gridscan( beamline_specific.fgs_motors, fgs_composite.eiger, fgs_composite.synchrotron, - [parameters.scan_points_first_grid, parameters.scan_points_second_grid], + parameters.scan_points, + parameters.omega_starts_deg, plan_during_collection=beamline_specific.read_during_collection_plan, ) diff --git a/src/mx_bluesky/common/experiment_plans/common_grid_detect_then_xray_centre_plan.py b/src/mx_bluesky/common/experiment_plans/common_grid_detect_then_xray_centre_plan.py index 9da2347b27..ce2508c1b2 100644 --- a/src/mx_bluesky/common/experiment_plans/common_grid_detect_then_xray_centre_plan.py +++ b/src/mx_bluesky/common/experiment_plans/common_grid_detect_then_xray_centre_plan.py @@ -43,7 +43,7 @@ FlyScanEssentialDevices, GridDetectThenXRayCentreComposite, ) -from mx_bluesky.common.parameters.gridscan import GridCommon, SpecifiedThreeDGridScan +from mx_bluesky.common.parameters.gridscan import GenericGrid, SpecifiedThreeDGridScan from mx_bluesky.common.utils.log import LOGGER TFlyScanEssentialDevices = TypeVar( @@ -56,7 +56,7 @@ def grid_detect_then_xray_centre( composite: GridDetectThenXRayCentreComposite, - parameters: GridCommon, + parameters: GenericGrid, xrc_params_type: type[SpecifiedThreeDGridScan], construct_beamline_specific: ConstructBeamlineSpecificFeatures, oav_config: str = OavConstants.OAV_CONFIG_JSON, @@ -97,7 +97,7 @@ def plan_to_perform(): # This function should be private but is currently called by Hyperion, see https://github.com/DiamondLightSource/mx-bluesky/issues/1148 def detect_grid_and_do_gridscan( composite: GridDetectThenXRayCentreComposite, - parameters: GridCommon, + parameters: GenericGrid, oav_params: OAVParameters, xrc_params_type: type[SpecifiedThreeDGridScan], construct_beamline_specific: ConstructBeamlineSpecificFeatures, @@ -181,7 +181,7 @@ def __call__( def create_parameters_for_flyscan_xray_centre( - parameters: GridCommon, + parameters: GenericGrid, grid_parameters: GridParamUpdate, xrc_params_type: type[SpecifiedThreeDGridScan], ) -> SpecifiedThreeDGridScan: diff --git a/src/mx_bluesky/common/experiment_plans/inner_plans/do_fgs.py b/src/mx_bluesky/common/experiment_plans/inner_plans/do_fgs.py index 9257dba5ac..d7e973a926 100644 --- a/src/mx_bluesky/common/experiment_plans/inner_plans/do_fgs.py +++ b/src/mx_bluesky/common/experiment_plans/inner_plans/do_fgs.py @@ -17,9 +17,6 @@ from mx_bluesky.common.experiment_plans.inner_plans.read_hardware import ( read_hardware_for_zocalo, ) -from mx_bluesky.common.external_interaction.callbacks.xray_centre.ispyb_callback import ( - GridscanPlane, -) from mx_bluesky.common.parameters.constants import ( PlanNameConstants, ) @@ -68,6 +65,7 @@ def kickoff_and_complete_gridscan( detector: EigerDetector, # Once Eiger inherits from StandardDetector, use that type instead synchrotron: Synchrotron, scan_points: list[AxesPoints[Axis]], + omega_starts_deg: list[float], plan_during_collection: Callable[[], MsgGenerator] | None = None, ): """Triggers a grid scan motion program and waits for completion, accounting for synchrotron topup. @@ -95,8 +93,9 @@ def kickoff_and_complete_gridscan( "omega_to_scan_spec": { # These have to be cast to strings due to a bug in orsjon. See # https://github.com/ijl/orjson/issues/414 - str(GridscanPlane.OMEGA_XY): scan_points[0], - str(GridscanPlane.OMEGA_XZ): scan_points[1], + # See https://github.com/DiamondLightSource/mx-bluesky/issues/1631 regarding integer cast + str(int(omega_starts_deg[i])): scan_points[i] + for i in range(len(omega_starts_deg)) }, } ) diff --git a/src/mx_bluesky/common/experiment_plans/inner_plans/xrc_results_utils.py b/src/mx_bluesky/common/experiment_plans/inner_plans/xrc_results_utils.py index e0d3d205f4..634f1a838b 100644 --- a/src/mx_bluesky/common/experiment_plans/inner_plans/xrc_results_utils.py +++ b/src/mx_bluesky/common/experiment_plans/inner_plans/xrc_results_utils.py @@ -70,7 +70,23 @@ def fetch_xrc_results_from_zocalo( def _generate_dummy_xrc_result(params: SpecifiedThreeDGridScan) -> XRayCentreResult: - com = [params.x_steps / 2, params.y_steps / 2, params.z_steps / 2] + coms = [] + assert params.num_grids % 2 == 0, ( + "XRC results in commissioning mode currently only works for an even number of grids" + ) + + for grid in range(int(params.num_grids / 2)): + # For even number of grids, Z steps are actually the even indexed y steps + coms.append( + [ + params.x_steps / 2, + params.y_steps[2 * grid] / 2, + params.y_steps[2 * grid + 1] / 2, + ] + ) + + com = [sum(x) / len(x) for x in zip(*coms, strict=True)] # Get average + max_voxel = [round(p) for p in com] return _xrc_result_in_boxes_to_result_in_mm( XrcResult( diff --git a/src/mx_bluesky/common/external_interaction/callbacks/common/grid_detection_callback.py b/src/mx_bluesky/common/external_interaction/callbacks/common/grid_detection_callback.py index 125c8e2ae0..77949d07ad 100644 --- a/src/mx_bluesky/common/external_interaction/callbacks/common/grid_detection_callback.py +++ b/src/mx_bluesky/common/external_interaction/callbacks/common/grid_detection_callback.py @@ -30,16 +30,12 @@ class GridParamUpdate(TypedDict): """ x_start_um: float - y_start_um: float - y2_start_um: float - z_start_um: float - z2_start_um: float + y_starts_um: list[float] + z_starts_um: list[float] x_steps: int - y_steps: int - z_steps: int + y_steps: list[int] x_step_size_um: float - y_step_size_um: float - z_step_size_um: float + y_step_sizes_um: list[float] class XYZParams(TypedDict, Generic[T]): @@ -120,14 +116,10 @@ def event(self, doc: Event): def get_grid_parameters(self) -> GridParamUpdate: return { "x_start_um": self.start_positions_um["x"], - "y_start_um": self.start_positions_um["y"], - "y2_start_um": self.start_positions_um["y"], - "z_start_um": self.start_positions_um["z"], - "z2_start_um": self.start_positions_um["z"], + "y_starts_um": [self.start_positions_um["y"], self.start_positions_um["y"]], + "z_starts_um": [self.start_positions_um["z"], self.start_positions_um["z"]], "x_steps": self.box_numbers["x"], - "y_steps": self.box_numbers["y"], - "z_steps": self.box_numbers["z"], + "y_steps": [self.box_numbers["y"], self.box_numbers["z"]], "x_step_size_um": self.x_step_size_um, - "y_step_size_um": self.y_step_size_um, - "z_step_size_um": self.z_step_size_um, + "y_step_sizes_um": [self.y_step_size_um, self.z_step_size_um], } diff --git a/src/mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py b/src/mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py index d51d6e4701..69736c8037 100644 --- a/src/mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py +++ b/src/mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py @@ -39,7 +39,7 @@ ) from mx_bluesky.common.parameters.components import DiffractionExperimentWithSample from mx_bluesky.common.parameters.constants import DocDescriptorNames, PlanNameConstants -from mx_bluesky.common.parameters.gridscan import GridCommon +from mx_bluesky.common.parameters.gridscan import GenericGrid from mx_bluesky.common.utils.exceptions import ( ISPyBDepositionNotMadeError, SampleError, @@ -58,7 +58,7 @@ class GridscanPlane(StrEnum): if TYPE_CHECKING: from event_model import Event, RunStart, RunStop -T = TypeVar("T", bound="GridCommon") +T = TypeVar("T", bound="GenericGrid") ASSERT_START_BEFORE_EVENT_DOC_MESSAGE = f"No data collection group info - event document has been emitted before a {PlanNameConstants.GRID_DETECT_AND_DO_GRIDSCAN} start document" diff --git a/src/mx_bluesky/common/external_interaction/callbacks/xray_centre/nexus_callback.py b/src/mx_bluesky/common/external_interaction/callbacks/xray_centre/nexus_callback.py index 1a065c48d7..7d6eeacd2e 100644 --- a/src/mx_bluesky/common/external_interaction/callbacks/xray_centre/nexus_callback.py +++ b/src/mx_bluesky/common/external_interaction/callbacks/xray_centre/nexus_callback.py @@ -12,21 +12,43 @@ from mx_bluesky.common.external_interaction.nexus.write_nexus import NexusWriter from mx_bluesky.common.parameters.constants import DocDescriptorNames, PlanNameConstants from mx_bluesky.common.parameters.gridscan import ( - SpecifiedThreeDGridScan, + SpecifiedGrids, ) from mx_bluesky.common.utils.log import NEXUS_LOGGER if TYPE_CHECKING: from event_model.documents import Event, EventDescriptor, RunStart -T = TypeVar("T", bound="SpecifiedThreeDGridScan") +T = TypeVar("T", bound="SpecifiedGrids") + + +def _create_writers_from_params(params: SpecifiedGrids) -> list[NexusWriter]: + num_writers = params.num_grids + writers = [] + d_size = params.detector_params.detector_size_constants.det_size_pixels + for idx in range(num_writers): + images_in_grid = len(params.scan_points[idx]["sam_x"]) + data_shape = (images_in_grid, d_size.width, d_size.height) + run_number = params.detector_params.run_number + idx + + writers.append( + NexusWriter( + params, + data_shape, + params.scan_points[idx], + run_number=run_number, + vds_start_index=params.scan_indices[idx], + omega_start_deg=params.omega_starts_deg[idx], + ) + ) + return writers class GridscanNexusFileCallback(PlanReactiveCallback): """Callback class to handle the creation of Nexus files based on experiment \ parameters. Initialises on receiving a 'start' document for the \ 'run_gridscan_move_and_tidy' sub plan, which must also contain the run parameters, \ - as metadata under the 'hyperion_internal_parameters' key. Actually writes the \ + as metadata under the 'mx_bluesky_parameters' key. Actually writes the \ nexus files on updates the timestamps on receiving the 'ispyb_reading_hardware' event \ document, and finalises the files on getting a 'stop' document for the whole run. @@ -43,10 +65,9 @@ def __init__(self, param_type: type[T]) -> None: super().__init__(NEXUS_LOGGER) self.param_type = param_type self.run_start_uid: str | None = None - self.nexus_writer_1: NexusWriter | None = None - self.nexus_writer_2: NexusWriter | None = None self.descriptors: dict[str, EventDescriptor] = {} self.log = NEXUS_LOGGER + self._writers: list[NexusWriter] = [] def activity_gated_start(self, doc: RunStart): if doc.get("subplan_name") == PlanNameConstants.GRIDSCAN_OUTER: @@ -56,23 +77,8 @@ def activity_gated_start(self, doc: RunStart): f"Nexus writer received start document with experiment parameters {mx_bluesky_parameters}" ) parameters = self.param_type.model_validate_json(mx_bluesky_parameters) - d_size = parameters.detector_params.detector_size_constants.det_size_pixels - grid_n_img_1 = parameters.scan_indices[1] - grid_n_img_2 = parameters.num_images - grid_n_img_1 - data_shape_1 = (grid_n_img_1, d_size.width, d_size.height) - data_shape_2 = (grid_n_img_2, d_size.width, d_size.height) - run_number_2 = parameters.detector_params.run_number + 1 - self.nexus_writer_1 = NexusWriter( - parameters, data_shape_1, parameters.scan_points_first_grid - ) - self.nexus_writer_2 = NexusWriter( - parameters, - data_shape_2, - parameters.scan_points_second_grid, - run_number=run_number_2, - vds_start_index=parameters.scan_indices[1], - omega_start_deg=90, - ) + self._writers = _create_writers_from_params(parameters) + self.run_start_uid = doc.get("uid") def activity_gated_descriptor(self, doc: EventDescriptor): @@ -83,8 +89,8 @@ def activity_gated_event(self, doc: Event) -> Event | None: assert event_descriptor is not None if event_descriptor.get("name") == DocDescriptorNames.HARDWARE_READ_DURING: data = doc["data"] - for nexus_writer in [self.nexus_writer_1, self.nexus_writer_2]: - assert nexus_writer, "Nexus callback did not receive start doc" + assert self._writers, "Nexus callback did not receive start doc" + for nexus_writer in self._writers: ( nexus_writer.beam, nexus_writer.attenuator, diff --git a/src/mx_bluesky/common/parameters/components.py b/src/mx_bluesky/common/parameters/components.py index 97615e82a5..ca7aebf68e 100644 --- a/src/mx_bluesky/common/parameters/components.py +++ b/src/mx_bluesky/common/parameters/components.py @@ -30,7 +30,7 @@ GridscanParamConstants, ) -PARAMETER_VERSION = Version.parse("5.3.0") +PARAMETER_VERSION = Version.parse("6.0.0") def get_param_version() -> SemanticVersion: @@ -96,6 +96,10 @@ class IspybExperimentType(StrEnum): GRIDSCAN_3D = "Mesh3D" +class WithNexusWriter(BaseModel): + indices_per_writer: tuple[int] + + class MxBlueskyParameters(BaseModel): model_config = ConfigDict( extra="allow", @@ -208,12 +212,16 @@ class WithScan(BaseModel): @property @abstractmethod - def scan_points(self) -> AxesPoints: ... + def scan_points(self) -> list[AxesPoints]: ... + + """Per grid""" @property @abstractmethod def num_images(self) -> int: ... + """Must be same for each grid""" + class WithPandaGridScan(BaseModel): """For experiments which use a PandA for constant-motion grid scans""" @@ -241,28 +249,30 @@ class DiffractionExperimentWithSample(DiffractionExperiment, WithSample): ... class OptionalXyzStarts(BaseModel): - x_start_um: float | None = None - y_start_um: float | None = None - z_start_um: float | None = None + x_start_um: float = 0 # See https://github.com/DiamondLightSource/mx-bluesky/issues/1632 for this not being a list + y_starts_um: list[float | None] | None = None + z_starts_um: list[float | None] | None = None class XyzStarts(BaseModel): x_start_um: float - y_start_um: float - z_start_um: float + y_starts_um: list[float] + z_starts_um: list[float] - def _start_for_axis(self, axis: XyzAxis) -> float: + def _start_for_axis(self, axis: XyzAxis, grid: int) -> float: match axis: case XyzAxis.X: return self.x_start_um case XyzAxis.Y: - return self.y_start_um + return self.y_starts_um[grid] case XyzAxis.Z: - return self.z_start_um + return self.z_starts_um[grid] class OptionalGonioAngleStarts(BaseModel): - omega_start_deg: float | None = None + # Gridscans have different omega starts + omega_starts_deg: list[float] = [0, 90] + phi_start_deg: float | None = None chi_start_deg: float | None = None kappa_start_deg: float | None = None diff --git a/src/mx_bluesky/common/parameters/constants.py b/src/mx_bluesky/common/parameters/constants.py index 6dea822334..6fa7e14c5f 100644 --- a/src/mx_bluesky/common/parameters/constants.py +++ b/src/mx_bluesky/common/parameters/constants.py @@ -110,8 +110,8 @@ class GridscanParamConstants: EXPOSURE_TIME_S = 0.004 USE_ROI = True BOX_WIDTH_UM = 20.0 - OMEGA_1 = 0.0 - OMEGA_2 = 90.0 + OMEGA_1 = 0 + OMEGA_2 = 90 PANDA_RUN_UP_DISTANCE_MM = 0.2 ZOCALO_MIN_TOTAL_COUNT_THRESHOLD = 3 diff --git a/src/mx_bluesky/common/parameters/gridscan.py b/src/mx_bluesky/common/parameters/gridscan.py index 15af489686..9da954f826 100644 --- a/src/mx_bluesky/common/parameters/gridscan.py +++ b/src/mx_bluesky/common/parameters/gridscan.py @@ -1,7 +1,7 @@ from __future__ import annotations from abc import abstractmethod -from typing import Generic, TypeVar +from typing import Annotated, Generic, TypeVar from dodal.devices.aperturescatterguard import ApertureValue from dodal.devices.detector.det_dim_constants import EIGER2_X_9M_SIZE, EIGER2_X_16M_SIZE @@ -11,7 +11,8 @@ ZebraGridScanParamsThreeD, ) from dodal.utils import get_beamline_name -from pydantic import Field, PrivateAttr +from pydantic import Field, PrivateAttr, model_validator +from scanspec.core import AxesPoints from scanspec.core import Path as ScanPath from scanspec.specs import Concat, Line, Product, Static @@ -43,14 +44,16 @@ ) -class GridCommon( +class GenericGrid( DiffractionExperimentWithSample, OptionalGonioAngleStarts, ): """ Parameters used in every MX diffraction experiment using grids. This model should be used by plans which have no knowledge of the grid specifications - i.e before - automatic grid detection has completed + automatic grid detection has completed. + + Params in GenericGrid currently must be the same for each grid in the gridscan. """ box_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM) @@ -67,15 +70,17 @@ class GridCommon( # Available after grid detection, used by entry point plans which need to # get the grid parameters to retrieve zocalo results # Can remove this after https://github.com/DiamondLightSource/python-dlstbx/issues/255 is done - _specified_grid_params: SpecifiedGrid | None = PrivateAttr(default=None) + _specified_grids_params: SpecifiedGrids | None = PrivateAttr(default=None) - def set_specified_grid_params(self, params: SpecifiedGrid): + def set_specified_grid_params(self, params: SpecifiedGrids): self._specified_grid_params = params @property - def specified_grid_params(self) -> SpecifiedGrid | None: + def specified_grid_params(self) -> SpecifiedGrids | None: return self._specified_grid_params + # We currently only arm the detector once, regardless of total grids. Detector params + # must be the same for each grid @property def detector_params(self): self.det_dist_to_beam_converter_path = ( @@ -95,7 +100,7 @@ def detector_params(self): directory=self.storage_directory, prefix=self.file_name, detector_distance=self.detector_distance_mm, - omega_start=self.omega_start_deg or 0, + omega_start=0, # Metadata we set on detector isn't currently accurate, but also not used downstream omega_increment=0, num_images_per_trigger=1, num_triggers=self.num_images, @@ -106,18 +111,56 @@ def detector_params(self): ) -class SpecifiedGrid(GridCommon, XyzStarts, WithScan, Generic[GridScanParamType]): +PositiveInt = Annotated[int, Field(gt=0)] +PositiveFloat = Annotated[float, Field(gt=0)] + + +class SpecifiedGrids(GenericGrid, XyzStarts, WithScan, Generic[GridScanParamType]): """A specified grid is one which has defined values for the start position, grid and box sizes, etc., as opposed to parameters for a plan which will create those parameters at some point (e.g. through optical pin detection).""" - grid1_omega_deg: float = Field(default=GridscanParamConstants.OMEGA_1) - x_step_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM) - y_step_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM) - x_steps: int = Field(gt=0) - y_steps: int = Field(gt=0) + # See https://github.com/DiamondLightSource/mx-bluesky/issues/1634 for a better structure for this + # class + + omega_starts_deg: list[float] = Field( + default=[GridscanParamConstants.OMEGA_1, GridscanParamConstants.OMEGA_2] + ) + x_step_size_um: PositiveFloat = Field( + default=GridscanParamConstants.BOX_WIDTH_UM + ) # See https://github.com/DiamondLightSource/mx-bluesky/issues/1632 for this not being a list + + # In a 3D grid scan, y_steps[0] and y_steps[1] refers to Y and Z respectively. + # We do an omega rotation between scanning across N dimensions to make N different axes + y_step_sizes_um: list[PositiveFloat] = Field( + default=[GridscanParamConstants.BOX_WIDTH_UM] * 2 + ) + x_steps: PositiveInt # See https://github.com/DiamondLightSource/mx-bluesky/issues/1632 for this not being a list + y_steps: list[PositiveInt] _set_stub_offsets: bool = PrivateAttr(default_factory=lambda: False) + @model_validator(mode="after") + def _check_lengths_are_same(self): + fields = { + "omega_starts_deg": self.omega_starts_deg, + "y_step_sizes_um": self.y_step_sizes_um, + "y_steps": self.y_steps, + "y_starts_um": self.y_starts_um, + "z_starts_um": self.z_starts_um, + } + + name_and_length = {name: len(value) for name, value in fields.items()} + lengths = name_and_length.values() + if len(set(lengths)) != 1: + details = "\n".join( + f" {name}: length={len(value)}, value={value}" + for name, value in fields.items() + ) + + raise ValueError("Fields must all have the same length:\n" + details) + + return self + @property @abstractmethod def fast_gridscan_params(self) -> GridScanParamType: ... @@ -126,92 +169,104 @@ def do_set_stub_offsets(self, value: bool): self._set_stub_offsets = value @property - def grid_1_spec(self): - x_end = self.x_start_um + self.x_step_size_um * (self.x_steps - 1) - y1_end = self.y_start_um + self.y_step_size_um * (self.y_steps - 1) - grid_1_x = Line("sam_x", self.x_start_um, x_end, self.x_steps) - grid_1_y = Line("sam_y", self.y_start_um, y1_end, self.y_steps) - grid_1_z = Static("sam_z", self.z_start_um) - return grid_1_y.zip(grid_1_z) * ~grid_1_x + def num_grids(self): + return len(self.y_steps) + + def __len__(self) -> int: + return self.num_grids + + @property + def grid_specs(self) -> list[Product[str]]: + _grid_specs = [] + for idx in range(self.num_grids): + x_end = self.x_start_um + self.x_step_size_um * (self.x_steps - 1) + y_end = self.y_starts_um[idx] + self.y_step_sizes_um[idx] * ( + self.y_steps[idx] - 1 + ) + grid_x = Line("sam_x", self.x_start_um, x_end, self.x_steps) + grid_y = Line("sam_y", self.y_starts_um[idx], y_end, self.y_steps[idx]) + grid_z = Static("sam_z", self.z_starts_um[idx]) + _grid_specs.append(grid_y.zip(grid_z) * ~grid_x) + return _grid_specs @property def scan_indices(self) -> list[int]: """The first index of each gridscan, useful for writing nexus files/VDS""" - return [ - 0, - len(ScanPath(self.grid_1_spec.calculate()).consume().midpoints["sam_x"]), - ] + _scan_indices = [0] + for idx in range(self.num_grids - 1): + _scan_indices.append( + len( + ScanPath(self.grid_specs[idx].calculate()) + .consume() + .midpoints["sam_x"] + ) + ) + return _scan_indices @property - @abstractmethod def scan_spec(self) -> Product[str] | Concat[str]: """A fully specified ScanSpec object representing all grids, with x, y, z and omega positions.""" - @property - def scan_points(self): - """A list of all the points in the scan_spec.""" - return ScanPath(self.scan_spec.calculate()).consume().midpoints + _scan_spec = self.grid_specs[0] + + for idx in range(1, self.num_grids - 1): + _scan_spec = _scan_spec.concat( + self.grid_specs[idx].concat(self.grid_specs[idx + 1]) + ) + return _scan_spec @property - def scan_points_first_grid(self): - """A list of all the points in the first grid scan.""" - return ScanPath(self.grid_1_spec.calculate()).consume().midpoints + def scan_points(self) -> list[AxesPoints[str]]: + """A list of all the points in the scan_spec for each grid.""" + _scan_points = [] + for grid in range(self.num_grids): + _scan_points.append( + ScanPath(self.grid_specs[grid].calculate()).consume().midpoints + ) + return _scan_points @property def num_images(self) -> int: - return len(self.scan_points["sam_x"]) + """Total num images in entire scan""" + _num_images = 0 + for grid in range(len(self.scan_points)): + _num_images += len(self.scan_points[grid]["sam_x"]) + return _num_images class SpecifiedThreeDGridScan( - SpecifiedGrid[ZebraGridScanParamsThreeD], + SpecifiedGrids[ZebraGridScanParamsThreeD], SplitScan, WithOptionalEnergyChange, ): """Parameters representing a so-called 3D grid scan, which consists of doing a gridscan in X and Y, followed by one in X and Z.""" - z_steps: int = Field(gt=0) - z_step_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM) - y2_start_um: float - z2_start_um: float - grid2_omega_deg: float = Field(default=GridscanParamConstants.OMEGA_2) + @model_validator(mode="after") + def validate_y_and_z_axes(self): + _err_str = "must be length 2 for 3D scans" + if len(self.y_steps) != 2: + raise ValueError(f"{self.y_steps=} {_err_str}") + if len(self.y_step_sizes_um) != 2: + raise ValueError(f"{self.y_step_sizes_um=} {_err_str}") + return self @property def fast_gridscan_params(self) -> ZebraGridScanParamsThreeD: return ZebraGridScanParamsThreeD( x_steps=self.x_steps, - y_steps=self.y_steps, - z_steps=self.z_steps, + y_steps=self.y_steps[0], + z_steps=self.y_steps[1], x_step_size_mm=self.x_step_size_um / 1000, - y_step_size_mm=self.y_step_size_um / 1000, - z_step_size_mm=self.z_step_size_um / 1000, + y_step_size_mm=self.y_step_sizes_um[0] / 1000, + z_step_size_mm=self.y_step_sizes_um[1] / 1000, x_start_mm=self.x_start_um / 1000, - y1_start_mm=self.y_start_um / 1000, - z1_start_mm=self.z_start_um / 1000, - y2_start_mm=self.y2_start_um / 1000, - z2_start_mm=self.z2_start_um / 1000, + y1_start_mm=self.y_starts_um[0] / 1000, + z1_start_mm=self.z_starts_um[0] / 1000, + y2_start_mm=self.y_starts_um[1] / 1000, + z2_start_mm=self.z_starts_um[1] / 1000, set_stub_offsets=self._set_stub_offsets, dwell_time_ms=self.exposure_time_s * 1000, transmission_fraction=self.transmission_frac, ) - - @property - def grid_2_spec(self): - x_end = self.x_start_um + self.x_step_size_um * (self.x_steps - 1) - z2_end = self.z2_start_um + self.z_step_size_um * (self.z_steps - 1) - grid_2_x = Line("sam_x", self.x_start_um, x_end, self.x_steps) - grid_2_z = Line("sam_z", self.z2_start_um, z2_end, self.z_steps) - grid_2_y = Static("sam_y", self.y2_start_um) - return grid_2_z.zip(grid_2_y) * ~grid_2_x - - @property - def scan_spec(self): - """A fully specified ScanSpec object representing both grids, with x, y, z and - omega positions.""" - return self.grid_1_spec.concat(self.grid_2_spec) - - @property - def scan_points_second_grid(self): - """A list of all the points in the second grid scan.""" - return ScanPath(self.grid_2_spec.calculate()).consume().midpoints diff --git a/src/mx_bluesky/common/parameters/rotation.py b/src/mx_bluesky/common/parameters/rotation.py index 80a3965568..371de4bfff 100644 --- a/src/mx_bluesky/common/parameters/rotation.py +++ b/src/mx_bluesky/common/parameters/rotation.py @@ -22,11 +22,9 @@ DiffractionExperimentWithSample, IspybExperimentType, OptionalGonioAngleStarts, - OptionalXyzStarts, RotationAxis, SplitScan, WithSample, - WithScan, ) from mx_bluesky.common.parameters.constants import ( DetectorParamConstants, @@ -34,7 +32,7 @@ ) -class RotationScanPerSweep(OptionalGonioAngleStarts, OptionalXyzStarts, WithSample): +class RotationScanPerSweep(OptionalGonioAngleStarts, WithSample): """ Describes a rotation scan about the specified axis. @@ -48,6 +46,9 @@ class RotationScanPerSweep(OptionalGonioAngleStarts, OptionalXyzStarts, WithSamp nexus_vds_start_img: The frame number of the first frame captured during the rotation """ + x_start_um: float | None = None + y_start_um: float | None = None + z_start_um: float | None = None omega_start_deg: float = Field(default=0) # type: ignore rotation_axis: RotationAxis = Field(default=RotationAxis.OMEGA) scan_width_deg: float = Field(default=360, gt=0) @@ -109,7 +110,7 @@ def _set_default_aperture_position(cls, aperture_position: ApertureValue | None) class SingleRotationScan( - WithScan, RotationExperiment, RotationScanPerSweep, DiffractionExperimentWithSample + RotationExperiment, RotationScanPerSweep, DiffractionExperimentWithSample ): @property def detector_params(self): diff --git a/src/mx_bluesky/hyperion/blueapi/parameters.py b/src/mx_bluesky/hyperion/blueapi/parameters.py index 117367aee9..01d9d92f88 100644 --- a/src/mx_bluesky/hyperion/blueapi/parameters.py +++ b/src/mx_bluesky/hyperion/blueapi/parameters.py @@ -41,7 +41,7 @@ class RobotLoadThenCentreParams(HyperionParam): file_name: str transmission_frac: float exposure_time_s: float - omega_start_deg: float + omega_starts_deg: list[int] chi_start_deg: float pin_type: SingleSamplePinTypeParam | MultiSamplePinTypeParam = Field( discriminator="name", default=SingleSamplePinTypeParam() diff --git a/src/mx_bluesky/hyperion/external_interaction/agamemnon.py b/src/mx_bluesky/hyperion/external_interaction/agamemnon.py index e355b65d54..a48e36e01e 100644 --- a/src/mx_bluesky/hyperion/external_interaction/agamemnon.py +++ b/src/mx_bluesky/hyperion/external_interaction/agamemnon.py @@ -170,7 +170,7 @@ def _populate_parameters_from_agamemnon( "storage_directory": str(visit_directory) + "/xraycentring", "file_name": file_name, "pin_type": pin_type, - "omega_start_deg": 0.0, + "omega_starts_deg": [0.0, 90.0], "chi_start_deg": collection["chi"], "transmission_frac": 1.0, "exposure_time_s": GridscanParamConstants.EXPOSURE_TIME_S, diff --git a/src/mx_bluesky/hyperion/external_interaction/callbacks/__main__.py b/src/mx_bluesky/hyperion/external_interaction/callbacks/__main__.py index 12fb82475e..325e922f07 100644 --- a/src/mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +++ b/src/mx_bluesky/hyperion/external_interaction/callbacks/__main__.py @@ -67,7 +67,7 @@ from mx_bluesky.hyperion.parameters.cli import CallbackArgs, parse_callback_args from mx_bluesky.hyperion.parameters.constants import CONST from mx_bluesky.hyperion.parameters.gridscan import ( - GridCommonWithHyperionDetectorParams, + GenericGridWithHyperionDetectorParams, HyperionSpecifiedThreeDGridScan, ) @@ -84,7 +84,7 @@ def create_gridscan_callbacks() -> tuple[ return ( GridscanNexusFileCallback(param_type=HyperionSpecifiedThreeDGridScan), GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams, + param_type=GenericGridWithHyperionDetectorParams, emit=ZocaloCallback( CONST.PLAN.DO_FGS, CONST.ZOCALO_ENV, generate_start_info_from_omega_map ), diff --git a/src/mx_bluesky/hyperion/parameters/gridscan.py b/src/mx_bluesky/hyperion/parameters/gridscan.py index 07735360b4..360ec5c04d 100644 --- a/src/mx_bluesky/hyperion/parameters/gridscan.py +++ b/src/mx_bluesky/hyperion/parameters/gridscan.py @@ -6,7 +6,7 @@ ) from mx_bluesky.common.parameters.gridscan import ( - GridCommon, + GenericGrid, SpecifiedThreeDGridScan, ) from mx_bluesky.hyperion.external_interaction.config_server import ( @@ -14,7 +14,7 @@ ) -class GridCommonWithHyperionDetectorParams(GridCommon): +class GenericGridWithHyperionDetectorParams(GenericGrid): """Used by models which require detector parameters but have no specifications of the grid""" # These detector params only exist so that we can properly select enable_dev_shm. Remove in @@ -41,18 +41,22 @@ def detector_params(self): # Relative to common grid scan, stub offsets are defined by config server @property def fast_gridscan_params(self) -> ZebraGridScanParamsThreeD: + """During 3D grid scans, there is an omega rotation before the second grid, + transforming Y -> Z axes, so use the second element of the Y params to set + Z params on the 3D grid scan device. + """ return ZebraGridScanParamsThreeD( x_steps=self.x_steps, - y_steps=self.y_steps, - z_steps=self.z_steps, + y_steps=self.y_steps[0], + z_steps=self.y_steps[1], x_step_size_mm=self.x_step_size_um / 1000, - y_step_size_mm=self.y_step_size_um / 1000, - z_step_size_mm=self.z_step_size_um / 1000, + y_step_size_mm=self.y_step_sizes_um[0] / 1000, + z_step_size_mm=self.y_step_sizes_um[1] / 1000, x_start_mm=self.x_start_um / 1000, - y1_start_mm=self.y_start_um / 1000, - z1_start_mm=self.z_start_um / 1000, - y2_start_mm=self.y2_start_um / 1000, - z2_start_mm=self.z2_start_um / 1000, + y1_start_mm=self.y_starts_um[0] / 1000, + z1_start_mm=self.z_starts_um[0] / 1000, + y2_start_mm=self.y_starts_um[1] / 1000, + z2_start_mm=self.z_starts_um[1] / 1000, set_stub_offsets=get_hyperion_feature_settings().SET_STUB_OFFSETS, dwell_time_ms=self.exposure_time_s * 1000, transmission_fraction=self.transmission_frac, @@ -60,23 +64,23 @@ def fast_gridscan_params(self) -> ZebraGridScanParamsThreeD: @property def panda_fast_gridscan_params(self) -> PandAGridScanParams: - if self.y_steps % 2 and self.z_steps > 0: + if self.y_steps[0] % 2 and self.y_steps[1] > 0: # See https://github.com/DiamondLightSource/hyperion/issues/1118 for explanation raise OddYStepsError( "The number of Y steps must be even for a PandA gridscan" ) return PandAGridScanParams( x_steps=self.x_steps, - y_steps=self.y_steps, - z_steps=self.z_steps, + y_steps=self.y_steps[0], + z_steps=self.y_steps[1], x_step_size_mm=self.x_step_size_um / 1000, - y_step_size_mm=self.y_step_size_um / 1000, - z_step_size_mm=self.z_step_size_um / 1000, + y_step_size_mm=self.y_step_sizes_um[0] / 1000, + z_step_size_mm=self.y_step_sizes_um[1] / 1000, x_start_mm=self.x_start_um / 1000, - y1_start_mm=self.y_start_um / 1000, - z1_start_mm=self.z_start_um / 1000, - y2_start_mm=self.y2_start_um / 1000, - z2_start_mm=self.z2_start_um / 1000, + y1_start_mm=self.y_starts_um[0] / 1000, + z1_start_mm=self.z_starts_um[0] / 1000, + y2_start_mm=self.y_starts_um[1] / 1000, + z2_start_mm=self.z_starts_um[1] / 1000, set_stub_offsets=get_hyperion_feature_settings().SET_STUB_OFFSETS, run_up_distance_mm=get_hyperion_feature_settings().PANDA_RUNUP_DISTANCE_MM, transmission_fraction=self.transmission_frac, @@ -86,9 +90,9 @@ def panda_fast_gridscan_params(self) -> PandAGridScanParams: class OddYStepsError(Exception): ... -class PinTipCentreThenXrayCentre(GridCommonWithHyperionDetectorParams): +class PinTipCentreThenXrayCentre(GenericGridWithHyperionDetectorParams): tip_offset_um: float = 0 -class GridScanWithEdgeDetect(GridCommonWithHyperionDetectorParams): +class GridScanWithEdgeDetect(GenericGridWithHyperionDetectorParams): pass diff --git a/src/mx_bluesky/hyperion/parameters/robot_load.py b/src/mx_bluesky/hyperion/parameters/robot_load.py index 4342d487e9..43b07acbb5 100644 --- a/src/mx_bluesky/hyperion/parameters/robot_load.py +++ b/src/mx_bluesky/hyperion/parameters/robot_load.py @@ -6,7 +6,7 @@ WithVisit, ) from mx_bluesky.hyperion.parameters.gridscan import ( - GridCommonWithHyperionDetectorParams, + GenericGridWithHyperionDetectorParams, PinTipCentreThenXrayCentre, ) @@ -17,7 +17,7 @@ class RobotLoadAndEnergyChange( pass -class RobotLoadThenCentre(GridCommonWithHyperionDetectorParams): +class RobotLoadThenCentre(GenericGridWithHyperionDetectorParams): @property def robot_load_params(self) -> RobotLoadAndEnergyChange: my_params = self.model_dump() diff --git a/tests/conftest.py b/tests/conftest.py index b7396a7625..41407f5980 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -359,13 +359,23 @@ def hyperion_fgs_params(tmp_path): return HyperionSpecifiedThreeDGridScan( **( raw_params_from_file( - "tests/test_data/parameter_json_files/good_test_parameters.json", + "tests/test_data/parameter_json_files/good_test_specified_three_d_grid_params.json", tmp_path, ) ) ) +@pytest.fixture +def test_three_d_grid_params(tmp_path, patch_beamline_env_variable): + return SpecifiedThreeDGridScan( + **raw_params_from_file( + "tests/test_data/parameter_json_files/good_test_specified_three_d_grid_params.json", + tmp_path, + ) + ) + + @pytest.fixture def eiger(): eiger = i03.eiger.build(mock=True) diff --git a/tests/system_tests/hyperion/external_interaction/test_agamemnon.py b/tests/system_tests/hyperion/external_interaction/test_agamemnon.py index 5ad94eff87..ecc97ae4ad 100644 --- a/tests/system_tests/hyperion/external_interaction/test_agamemnon.py +++ b/tests/system_tests/hyperion/external_interaction/test_agamemnon.py @@ -30,7 +30,7 @@ "demand_energy_ev": 12700.045934258673, "tip_offset_um": 300, "grid_width_um": 600, - "omega_start_deg": 0, + "omega_starts_deg": [0, 90], "chi_start_deg": 0, "transmission_frac": 1.0, } diff --git a/tests/system_tests/hyperion/external_interaction/test_ispyb_dev_connection.py b/tests/system_tests/hyperion/external_interaction/test_ispyb_dev_connection.py index f5bdd0b213..aafcf3932c 100644 --- a/tests/system_tests/hyperion/external_interaction/test_ispyb_dev_connection.py +++ b/tests/system_tests/hyperion/external_interaction/test_ispyb_dev_connection.py @@ -55,7 +55,7 @@ HyperionGridDetectThenXRayCentreComposite, ) from mx_bluesky.hyperion.parameters.gridscan import ( - GridCommonWithHyperionDetectorParams, + GenericGridWithHyperionDetectorParams, GridScanWithEdgeDetect, HyperionSpecifiedThreeDGridScan, ) @@ -169,9 +169,9 @@ def scan_xy_data_info_for_update( assert dummy_params is not None scan_data_info_for_update.data_collection_grid_info = DataCollectionGridInfo( dx_in_mm=dummy_params.x_step_size_um, - dy_in_mm=dummy_params.y_step_size_um, + dy_in_mm=dummy_params.y_step_sizes_um[0], steps_x=dummy_params.x_steps, - steps_y=dummy_params.y_steps, + steps_y=dummy_params.y_steps[0], microns_per_pixel_x=1.25, microns_per_pixel_y=1.25, # cast coordinates from numpy int64 to avoid mysql type conversion issues @@ -202,9 +202,9 @@ def scan_data_infos_for_update_3d( assert dummy_params is not None data_collection_grid_info = DataCollectionGridInfo( dx_in_mm=dummy_params.x_step_size_um, - dy_in_mm=dummy_params.z_step_size_um, + dy_in_mm=dummy_params.y_step_sizes_um[1], steps_x=dummy_params.x_steps, - steps_y=dummy_params.z_steps, + steps_y=dummy_params.y_steps[1], microns_per_pixel_x=1.25, microns_per_pixel_y=1.25, # cast coordinates from numpy int64 to avoid mysql type conversion issues @@ -416,7 +416,7 @@ def test_ispyb_deposition_in_gridscan( set_mock_value( grid_detect_then_xray_centre_composite.s4_slit_gaps.ygap.user_readback, 0.1 ) - ispyb_callback = GridscanISPyBCallback(GridCommonWithHyperionDetectorParams) + ispyb_callback = GridscanISPyBCallback(GenericGridWithHyperionDetectorParams) run_engine.subscribe(ispyb_callback) run_engine( grid_detect_then_xray_centre( diff --git a/tests/system_tests/hyperion/external_interaction/test_load_centre_collect_full_plan.py b/tests/system_tests/hyperion/external_interaction/test_load_centre_collect_full_plan.py index a06451e9f4..c9ea83cf4b 100644 --- a/tests/system_tests/hyperion/external_interaction/test_load_centre_collect_full_plan.py +++ b/tests/system_tests/hyperion/external_interaction/test_load_centre_collect_full_plan.py @@ -59,7 +59,9 @@ from mx_bluesky.hyperion.parameters.device_composites import ( HyperionGridDetectThenXRayCentreComposite, ) -from mx_bluesky.hyperion.parameters.gridscan import GridCommonWithHyperionDetectorParams +from mx_bluesky.hyperion.parameters.gridscan import ( + GenericGridWithHyperionDetectorParams, +) from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect from ....conftest import ( @@ -304,7 +306,7 @@ def test_execute_load_centre_collect_full( robot_load_cb: RobotLoadISPyBCallback, ): ispyb_gridscan_cb = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) ispyb_rotation_cb = RotationISPyBCallback() snapshot_cb = BeamDrawingCallback(emit=ispyb_rotation_cb) @@ -486,7 +488,7 @@ def move_to_initial_omega(): run_engine(move_to_initial_omega()) ispyb_gridscan_cb = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) ispyb_rotation_cb = RotationISPyBCallback() snapshot_cb = BeamDrawingCallback(emit=ispyb_rotation_cb) @@ -578,7 +580,7 @@ def test_load_centre_collect_updates_bl_sample_status_pin_tip_detection_fail( ): robot_load_cb = RobotLoadISPyBCallback() ispyb_gridscan_cb = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) sample_handling_cb = SampleHandlingCallback() run_engine.subscribe(robot_load_cb) @@ -612,7 +614,7 @@ def test_load_centre_collect_updates_bl_sample_status_grid_detection_fail_tip_no ): robot_load_cb = RobotLoadISPyBCallback() ispyb_gridscan_cb = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) sample_handling_cb = SampleHandlingCallback() run_engine.subscribe(robot_load_cb) @@ -664,7 +666,7 @@ def test_load_centre_collect_updates_bl_sample_status_gridscan_no_diffraction( ): robot_load_cb = RobotLoadISPyBCallback() ispyb_gridscan_cb = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) sample_handling_cb = SampleHandlingCallback() run_engine.subscribe(robot_load_cb) @@ -696,7 +698,7 @@ def test_load_centre_collect_updates_bl_sample_status_rotation_failure( ): robot_load_cb = RobotLoadISPyBCallback() ispyb_gridscan_cb = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) sample_handling_cb = SampleHandlingCallback() run_engine.subscribe(robot_load_cb) @@ -754,7 +756,7 @@ def test_load_centre_collect_gridscan_result_at_edge_of_grid( zocalo_result, [SimConstants.ST_SAMPLE_ID] ) ispyb_gridscan_cb = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) ispyb_rotation_cb = RotationISPyBCallback() set_mock_value( @@ -788,7 +790,7 @@ def test_execute_load_centre_collect_capture_rotation_snapshots( load_centre_collect_params.multi_rotation_scan.snapshot_directory = tmp_path ispyb_gridscan_cb = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) ispyb_rotation_cb = RotationISPyBCallback() snapshot_callback = BeamDrawingCallback(emit=ispyb_rotation_cb) @@ -870,7 +872,7 @@ def test_load_centre_collect_multisample_pin_reports_correct_sample_ids_in_ispyb ): load_centre_collect_composite.zocalo.my_zocalo_result = zocalo_result ispyb_gridscan_cb = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) ispyb_rotation_cb = RotationISPyBCallback() snapshot_cb = BeamDrawingCallback(emit=ispyb_rotation_cb) @@ -923,7 +925,7 @@ def test_load_centre_collect_multisample_pin_reports_correct_sample_ids_in_ispyb ): load_centre_collect_composite.zocalo.my_zocalo_result = zocalo_result ispyb_gridscan_cb = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) ispyb_rotation_cb = RotationISPyBCallback() snapshot_cb = BeamDrawingCallback(emit=ispyb_rotation_cb) @@ -989,7 +991,7 @@ def test_load_centre_collect_multisample_pin_reports_correct_sample_ids_robot_lo ): load_centre_collect_composite.zocalo.my_zocalo_result = zocalo_result ispyb_gridscan_cb = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) ispyb_rotation_cb = RotationISPyBCallback() snapshot_cb = BeamDrawingCallback(emit=ispyb_rotation_cb) @@ -1046,7 +1048,7 @@ def test_load_centre_collect_multisample_pin_updates_sample_status_for_parent_sa ): load_centre_collect_composite.zocalo.my_zocalo_result = zocalo_result ispyb_gridscan_cb = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) ispyb_rotation_cb = RotationISPyBCallback() snapshot_cb = BeamDrawingCallback(emit=ispyb_rotation_cb) @@ -1116,16 +1118,12 @@ def wrapper(*args, **kwargs): def grid_detect_for_snapshot_generation(): fake_grid_params = GridParamUpdate( x_start_um=-598.4, - y_start_um=-215.3, - y2_start_um=-215.3, - z_start_um=150.6, - z2_start_um=150.6, + y_starts_um=[-215.3] * 2, + z_starts_um=[150.6] * 2, x_steps=30, - y_steps=20, - z_steps=13, + y_steps=[20, 13], x_step_size_um=20, - y_step_size_um=20, - z_step_size_um=20, + y_step_sizes_um=[20, 20], ) with patch( "mx_bluesky.common.experiment_plans.common_grid_detect_then_xray_centre_plan.GridDetectionCallback" @@ -1178,7 +1176,7 @@ def test_load_centre_collect_generate_rotation_snapshots( ) ispyb_gridscan_cb = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) ispyb_rotation_cb = RotationISPyBCallback() snapshot_callback = BeamDrawingCallback(emit=ispyb_rotation_cb) diff --git a/tests/test_data/parameter_json_files/example_load_centre_collect_params.json b/tests/test_data/parameter_json_files/example_load_centre_collect_params.json index 1f98c76aea..0625e0000e 100644 --- a/tests/test_data/parameter_json_files/example_load_centre_collect_params.json +++ b/tests/test_data/parameter_json_files/example_load_centre_collect_params.json @@ -12,7 +12,10 @@ "use_roi_mode": true, "demand_energy_ev": 11100, "transmission_frac": 1.0, - "omega_start_deg": 0, + "omega_starts_deg": [ + 0, + 90 + ], "chi_start_deg": 30 }, "multi_rotation_scan": { diff --git a/tests/test_data/parameter_json_files/external_load_centre_collect_params.json b/tests/test_data/parameter_json_files/external_load_centre_collect_params.json index c394d19c83..b35c5cb0d9 100644 --- a/tests/test_data/parameter_json_files/external_load_centre_collect_params.json +++ b/tests/test_data/parameter_json_files/external_load_centre_collect_params.json @@ -10,7 +10,10 @@ "file_name": "robot_load_centring_file", "transmission_frac": 1.0, "exposure_time_s": 0.002, - "omega_start_deg": 0, + "omega_starts_deg": [ + 0, + 90 + ], "chi_start_deg": 30 }, "multi_rotation_scan": { diff --git a/tests/test_data/parameter_json_files/external_load_centre_collect_params_multipin.json b/tests/test_data/parameter_json_files/external_load_centre_collect_params_multipin.json index bb09a8143e..cc78bb4801 100644 --- a/tests/test_data/parameter_json_files/external_load_centre_collect_params_multipin.json +++ b/tests/test_data/parameter_json_files/external_load_centre_collect_params_multipin.json @@ -10,7 +10,7 @@ "file_name": "robot_load_centring_file", "transmission_frac": 1.0, "exposure_time_s": 0.002, - "omega_start_deg": 0, + "omega_starts_deg": [0, 90], "chi_start_deg": 30, "pin_type": { "name": "msp", diff --git a/tests/test_data/parameter_json_files/good_test_grid_with_edge_detect_parameters.json b/tests/test_data/parameter_json_files/good_test_grid_with_edge_detect_parameters.json index e0b2d30681..f808b0fdde 100644 --- a/tests/test_data/parameter_json_files/good_test_grid_with_edge_detect_parameters.json +++ b/tests/test_data/parameter_json_files/good_test_grid_with_edge_detect_parameters.json @@ -1,5 +1,5 @@ { - "parameter_model_version": "5.0.0", + "parameter_model_version": "6.0.0", "beamline": "BL03S", "insertion_prefix": "SR03S", "storage_directory": "/tmp", @@ -10,7 +10,10 @@ "det_dist_to_beam_converter_path": "tests/test_data/test_lookup_table.txt", "exposure_time_s": 0.1, "detector_distance_mm": 100.0, - "omega_start_deg": 0.0, + "omega_starts_deg": [ + 0.0, + 90.0 + ], "grid_width_um": 290.6, "transmission_frac": 1.0, "visit": "cm31105-4", diff --git a/tests/test_data/parameter_json_files/good_test_load_centre_collect_params.json b/tests/test_data/parameter_json_files/good_test_load_centre_collect_params.json index 5f98f92e58..51c1db22b3 100644 --- a/tests/test_data/parameter_json_files/good_test_load_centre_collect_params.json +++ b/tests/test_data/parameter_json_files/good_test_load_centre_collect_params.json @@ -1,5 +1,5 @@ { - "parameter_model_version": "5.0.0", + "parameter_model_version": "6.0.0", "beamline": "BL03S", "det_dist_to_beam_converter_path": "tests/test_data/test_lookup_table.txt", "insertion_prefix": "SR03S", diff --git a/tests/test_data/parameter_json_files/good_test_load_centre_collect_params_multi_rotation.json b/tests/test_data/parameter_json_files/good_test_load_centre_collect_params_multi_rotation.json index eb84f9bb94..78358fb5fa 100644 --- a/tests/test_data/parameter_json_files/good_test_load_centre_collect_params_multi_rotation.json +++ b/tests/test_data/parameter_json_files/good_test_load_centre_collect_params_multi_rotation.json @@ -1,5 +1,5 @@ { - "parameter_model_version": "5.0.0", + "parameter_model_version": "6.0.0", "beamline": "BL03S", "det_dist_to_beam_converter_path": "tests/test_data/test_lookup_table.txt", "insertion_prefix": "SR03S", @@ -27,7 +27,12 @@ "demand_energy_ev": 11100, "rotation_increment_deg": 0.1, "shutter_opening_time_s": 0.6, - "snapshot_omegas_deg": [0, 90, 180, 270], + "snapshot_omegas_deg": [ + 0, + 90, + 180, + 270 + ], "run_number": 1, "rotation_scans": [ { diff --git a/tests/test_data/parameter_json_files/good_test_multi_rotation_scan_parameters.json b/tests/test_data/parameter_json_files/good_test_multi_rotation_scan_parameters.json index bb2958c3ef..03fffbd02d 100644 --- a/tests/test_data/parameter_json_files/good_test_multi_rotation_scan_parameters.json +++ b/tests/test_data/parameter_json_files/good_test_multi_rotation_scan_parameters.json @@ -1,5 +1,5 @@ { - "parameter_model_version": "5.0.0", + "parameter_model_version": "6.0.0", "comment": "test", "det_dist_to_beam_converter_path": "tests/test_data/test_lookup_table.txt", "storage_directory": "{tmp_data}/123456/", diff --git a/tests/test_data/parameter_json_files/good_test_one_multi_rotation_scan_parameters.json b/tests/test_data/parameter_json_files/good_test_one_multi_rotation_scan_parameters.json index e1874e5164..bc27cd1792 100644 --- a/tests/test_data/parameter_json_files/good_test_one_multi_rotation_scan_parameters.json +++ b/tests/test_data/parameter_json_files/good_test_one_multi_rotation_scan_parameters.json @@ -1,5 +1,5 @@ { - "parameter_model_version": "5.0.0", + "parameter_model_version": "6.0.0", "comment": "test", "det_dist_to_beam_converter_path": "tests/test_data/test_lookup_table.txt", "storage_directory": "{tmp_data}/123456/", diff --git a/tests/test_data/parameter_json_files/good_test_one_multi_rotation_scan_parameters_nomove.json b/tests/test_data/parameter_json_files/good_test_one_multi_rotation_scan_parameters_nomove.json index e8a2c24089..885d286763 100644 --- a/tests/test_data/parameter_json_files/good_test_one_multi_rotation_scan_parameters_nomove.json +++ b/tests/test_data/parameter_json_files/good_test_one_multi_rotation_scan_parameters_nomove.json @@ -1,5 +1,5 @@ { - "parameter_model_version": "5.0.0", + "parameter_model_version": "6.0.0", "comment": "test", "det_dist_to_beam_converter_path": "tests/test_data/test_lookup_table.txt", "storage_directory": "{tmp_data}/123456/", diff --git a/tests/test_data/parameter_json_files/good_test_pin_centre_then_xray_centre_parameters.json b/tests/test_data/parameter_json_files/good_test_pin_centre_then_xray_centre_parameters.json index 4bae2ad6d8..6a9b87e5a7 100644 --- a/tests/test_data/parameter_json_files/good_test_pin_centre_then_xray_centre_parameters.json +++ b/tests/test_data/parameter_json_files/good_test_pin_centre_then_xray_centre_parameters.json @@ -1,5 +1,5 @@ { - "parameter_model_version": "5.0.0", + "parameter_model_version": "6.0.0", "beamline": "BL03S", "insertion_prefix": "SR03S", "storage_directory": "/tmp", @@ -10,7 +10,10 @@ "det_dist_to_beam_converter_path": "tests/test_data/test_lookup_table.txt", "exposure_time_s": 0.1, "detector_distance_mm": 100.0, - "omega_start_deg": 0.0, + "omega_starts_deg": [ + 0.0, + 90.0 + ], "tip_offset_um": 108.9, "grid_width_um": 290.6, "transmission_frac": 1.0, diff --git a/tests/test_data/parameter_json_files/good_test_robot_load_and_centre_params.json b/tests/test_data/parameter_json_files/good_test_robot_load_and_centre_params.json index da32330450..7f5e874691 100644 --- a/tests/test_data/parameter_json_files/good_test_robot_load_and_centre_params.json +++ b/tests/test_data/parameter_json_files/good_test_robot_load_and_centre_params.json @@ -1,5 +1,5 @@ { - "parameter_model_version": "5.0.0", + "parameter_model_version": "6.0.0", "beamline": "BL03S", "insertion_prefix": "SR03S", "storage_directory": "/tmp/", @@ -8,7 +8,10 @@ "run_number": 0, "use_roi_mode": false, "det_dist_to_beam_converter_path": "tests/test_data/test_lookup_table.txt", - "omega_start_deg": 0, + "omega_starts_deg": [ + 0, + 90 + ], "transmission_frac": 1.0, "exposure_time_s": 0.004, "detector_distance_mm": 255, diff --git a/tests/test_data/parameter_json_files/good_test_robot_load_params.json b/tests/test_data/parameter_json_files/good_test_robot_load_params.json index 72e111faa6..d189b97931 100644 --- a/tests/test_data/parameter_json_files/good_test_robot_load_params.json +++ b/tests/test_data/parameter_json_files/good_test_robot_load_params.json @@ -1,9 +1,9 @@ { - "parameter_model_version": "5.0.0", + "parameter_model_version": "6.0.0", "beamline": "BL03S", "insertion_prefix": "SR03S", "snapshot_directory": "/tmp/", - "storage_directory":"/tmp/", + "storage_directory": "/tmp/", "visit": "cm31105-4", "demand_energy_ev": 11100, "sample_id": 12345, diff --git a/tests/test_data/parameter_json_files/good_test_rotation_scan_parameters.json b/tests/test_data/parameter_json_files/good_test_rotation_scan_parameters.json index f149b31936..a0a9c7c2c0 100644 --- a/tests/test_data/parameter_json_files/good_test_rotation_scan_parameters.json +++ b/tests/test_data/parameter_json_files/good_test_rotation_scan_parameters.json @@ -1,5 +1,5 @@ { - "parameter_model_version": "5.0.0", + "parameter_model_version": "6.0.0", "comment": "test", "det_dist_to_beam_converter_path": "tests/test_data/test_lookup_table.txt", "storage_directory": "{tmp_data}/123456/", @@ -7,7 +7,10 @@ "demand_energy_ev": 100, "exposure_time_s": 0.1, "insertion_prefix": "SR03S", - "omega_start_deg": 0.0, + "omega_starts_deg": [ + 0, + 90 + ], "file_name": "file_name", "scan_width_deg": 180.0, "rotation_axis": "omega", diff --git a/tests/test_data/parameter_json_files/good_test_parameters.json b/tests/test_data/parameter_json_files/good_test_specified_three_d_grid_params.json similarity index 62% rename from tests/test_data/parameter_json_files/good_test_parameters.json rename to tests/test_data/parameter_json_files/good_test_specified_three_d_grid_params.json index 14b9b80900..f0be3e0d06 100644 --- a/tests/test_data/parameter_json_files/good_test_parameters.json +++ b/tests/test_data/parameter_json_files/good_test_specified_three_d_grid_params.json @@ -1,5 +1,5 @@ { - "parameter_model_version": "5.0.0", + "parameter_model_version": "6.0.0", "demand_energy_ev": 100, "comment": "test", "det_dist_to_beam_converter_path": "tests/test_data/test_lookup_table.txt", @@ -7,22 +7,33 @@ "visit": "cm31105-4", "exposure_time_s": 0.1, "insertion_prefix": "SR03S", - "omega_start_deg": 0.0, + "omega_starts_deg": [ + 0, + 90 + ], "file_name": "file_name", "sample_id": 123456, "run_number": 0, "use_roi_mode": false, "transmission_frac": 1.0, "x_steps": 40, - "y_steps": 20, - "z_steps": 10, + "y_steps": [ + 20, + 10 + ], "x_step_size_um": 100, - "y_step_size_um": 100, - "z_step_size_um": 100, + "y_step_sizes_um": [ + 100, + 100 + ], "x_start_um": 0.0, - "y_start_um": 0.0, - "y2_start_um": 0.0, - "z_start_um": 0.0, - "z2_start_um": 0.0, + "y_starts_um": [ + 0.0, + 0.0 + ], + "z_starts_um": [ + 0.0, + 0.0 + ], "storage_directory": "{tmp_data}/xraycentring/123456/" } diff --git a/tests/test_data/parameter_json_files/ispyb_gridscan_system_test_parameters.json b/tests/test_data/parameter_json_files/ispyb_gridscan_system_test_parameters.json index 490188798b..72947b1788 100644 --- a/tests/test_data/parameter_json_files/ispyb_gridscan_system_test_parameters.json +++ b/tests/test_data/parameter_json_files/ispyb_gridscan_system_test_parameters.json @@ -1,5 +1,5 @@ { - "parameter_model_version": "5.0.0", + "parameter_model_version": "6.0.0", "beamline": "BL03S", "insertion_prefix": "SR03S", "storage_directory": "{tmp_data}", @@ -10,7 +10,10 @@ "det_dist_to_beam_converter_path": "tests/test_data/test_lookup_table.txt", "exposure_time_s": 0.12, "detector_distance_mm": 100.0, - "omega_start_deg": 0.0, + "omega_starts_deg": [ + 0, + 90 + ], "grid_width_um": 400, "transmission_frac": 0.49118, "visit": "cm31105-4", diff --git a/tests/test_data/parameter_json_files/load_centre_collect_params_top_n_by_max_count.json b/tests/test_data/parameter_json_files/load_centre_collect_params_top_n_by_max_count.json index d4385e7859..a63e1793e6 100644 --- a/tests/test_data/parameter_json_files/load_centre_collect_params_top_n_by_max_count.json +++ b/tests/test_data/parameter_json_files/load_centre_collect_params_top_n_by_max_count.json @@ -1,5 +1,5 @@ { - "parameter_model_version": "5.0.0", + "parameter_model_version": "6.0.0", "visit": "cm37235-4", "detector_distance_mm": 255, "sample_id": 5461074, @@ -12,7 +12,10 @@ "use_roi_mode": true, "demand_energy_ev": 11100, "transmission_frac": 1.0, - "omega_start_deg": 0, + "omega_starts_deg": [ + 0, + 90 + ], "chi_start_deg": 30, "use_panda": false }, diff --git a/tests/test_data/parameter_json_files/test_gridscan_param_defaults.json b/tests/test_data/parameter_json_files/test_gridscan_param_defaults.json index 5383c7bea8..6ffdd31edf 100644 --- a/tests/test_data/parameter_json_files/test_gridscan_param_defaults.json +++ b/tests/test_data/parameter_json_files/test_gridscan_param_defaults.json @@ -1,5 +1,5 @@ { - "parameter_model_version": "5.0.0", + "parameter_model_version": "6.0.0", "beamline": "BL03S", "insertion_prefix": "SR03S", "demand_energy_ev": 100, @@ -13,16 +13,24 @@ "det_dist_to_beam_converter_path": "tests/test_data/test_lookup_table.txt", "transmission_frac": 1.0, "x_steps": 40, - "y_steps": 20, - "z_steps": 10, + "y_steps": [ + 20, + 10 + ], "x_step_size_um": 0.1, - "y_step_size_um": 0.1, - "z_step_size_um": 0.1, + "y_step_sizes_um": [ + 0.1, + 0.1 + ], "x_start_um": 0.0, - "y_start_um": 0.0, - "y2_start_um": 0.0, - "z_start_um": 0.0, - "z2_start_um": 0.0, + "y_starts_um": [ + 0.0, + 0.0 + ], + "z_starts_um": [ + 0.0, + 0.0 + ], "detector_distance_mm": 100.0, "omega_start_deg": 0.0, "exposure_time_s": 0.1 diff --git a/tests/test_data/parameter_json_files/test_oav_snapshot_params.json b/tests/test_data/parameter_json_files/test_oav_snapshot_params.json index df9a7d19bc..78efb496ed 100644 --- a/tests/test_data/parameter_json_files/test_oav_snapshot_params.json +++ b/tests/test_data/parameter_json_files/test_oav_snapshot_params.json @@ -1,5 +1,10 @@ { - "parameter_model_version": "5.0.0", + "parameter_model_version": "6.0.0", "snapshot_directory": "/tmp/my_snapshots", - "snapshot_omegas_deg": [0, 90, 180, 270] + "snapshot_omegas_deg": [ + 0, + 90, + 180, + 270 + ] } diff --git a/tests/unit_tests/beamlines/i04/test_i04_grid_detect_then_xray_centre_plan.py b/tests/unit_tests/beamlines/i04/test_i04_grid_detect_then_xray_centre_plan.py index 2cf3bc9f9e..013de865d5 100644 --- a/tests/unit_tests/beamlines/i04/test_i04_grid_detect_then_xray_centre_plan.py +++ b/tests/unit_tests/beamlines/i04/test_i04_grid_detect_then_xray_centre_plan.py @@ -39,13 +39,13 @@ from mx_bluesky.beamlines.i04.experiment_plans.i04_grid_detect_then_xray_centre_plan import ( DEFAULT_XRC_BEAMSIZE_MICRONS, I04AutoXrcParams, - _get_grid_common_params, + _get_generic_grid_params, get_ready_for_oav_and_close_shutter, i04_default_grid_detect_and_xray_centre, ) from mx_bluesky.common.parameters.constants import PlanNameConstants from mx_bluesky.common.parameters.gridscan import ( - GridCommon, + GenericGrid, SpecifiedThreeDGridScan, ) from mx_bluesky.common.utils.exceptions import CrystalNotFoundError @@ -125,6 +125,21 @@ def i04_grid_detect_then_xrc_default_params( ) +@pytest.fixture() +def give_grid_common_specific_grid(test_three_d_grid_params: SpecifiedThreeDGridScan): + # Using this fixture means grid common always comes with its specific grid params + class GenericGridWithSpecificGrid(GenericGrid): + @property + def specified_grid_params(self): + return test_three_d_grid_params + + with patch( + "mx_bluesky.beamlines.i04.experiment_plans.i04_grid_detect_then_xray_centre_plan.GenericGrid", + GenericGridWithSpecificGrid, + ): + yield + + @patch( "mx_bluesky.beamlines.i04.experiment_plans.i04_grid_detect_then_xray_centre_plan.setup_beamline_for_oav", autospec=True, @@ -243,12 +258,12 @@ def test_i04_default_grid_detect_and_xray_centre_sets_transmission_triggers_xbpm mock_wait: MagicMock, sim_run_engine: RunEngineSimulator, zocalo: ZocaloResults, - hyperion_fgs_params, + test_three_d_grid_params, i04_grid_detect_then_xrc_default_params: partial[MsgGenerator], ): desired_transmission = 0.4 mock_fix_transmission_and_exp_time.return_value = (desired_transmission, 1) - mock_create_parameters.return_value = hyperion_fgs_params + mock_create_parameters.return_value = test_three_d_grid_params sim_run_engine.add_handler( "locate", lambda msg: {"readback": np.array([0, 0])}, @@ -325,10 +340,10 @@ def test_i04_default_grid_detect_and_xray_centre_does_undulator_check_before_col mock_grid_detection_plan: MagicMock, mock_create_gridscan_callbacks: MagicMock, run_engine: RunEngine, - hyperion_fgs_params, + test_three_d_grid_params, i04_grid_detect_then_xrc_default_params: partial[MsgGenerator], ): - mock_create_parameters.return_value = hyperion_fgs_params + mock_create_parameters.return_value = test_three_d_grid_params mock_run_gridscan.side_effect = CompleteError with pytest.raises(CompleteError): run_engine(i04_grid_detect_then_xrc_default_params()) @@ -439,21 +454,6 @@ async def test_given_no_diffraction_found_i04_grid_detect_then_xrc_returns_sampl get_mock_put(smargon.z.user_setpoint).assert_has_calls([call(initial_z)]) -@pytest.fixture() -def give_grid_common_specific_grid(test_fgs_params: SpecifiedThreeDGridScan): - # Using this fixture means grid common always comes with its specific grid params - class GridCommonWithSpecificGrid(GridCommon): - @property - def specified_grid_params(self): - return test_fgs_params - - with patch( - "mx_bluesky.beamlines.i04.experiment_plans.i04_grid_detect_then_xray_centre_plan.GridCommon", - GridCommonWithSpecificGrid, - ): - yield - - @patch( "mx_bluesky.beamlines.i04.experiment_plans.i04_grid_detect_then_xray_centre_plan.get_ready_for_oav_and_close_shutter", new=MagicMock(), @@ -485,8 +485,8 @@ def test_i04_grid_detect_then_xrc_calculates_exposure_and_transmission_then_uses mock_grid_detect_then_xray_centre: MagicMock, i04_grid_detect_then_xrc_default_params: partial[MsgGenerator], run_engine: RunEngine, - test_full_grid_scan_params: GridCommon, - test_fgs_params: SpecifiedThreeDGridScan, + test_full_grid_scan_params: GenericGrid, + test_three_d_grid_params: SpecifiedThreeDGridScan, give_grid_common_specific_grid, ): expected_trans_frac = 1 @@ -499,7 +499,7 @@ def test_i04_grid_detect_then_xrc_calculates_exposure_and_transmission_then_uses grid_common_params = mock_grid_detect_then_xray_centre.call_args.kwargs[ "parameters" ] - assert isinstance(grid_common_params, GridCommon) + assert isinstance(grid_common_params, GenericGrid) assert grid_common_params.exposure_time_s == expected_exposure_time assert grid_common_params.transmission_frac == expected_trans_frac @@ -507,7 +507,7 @@ def test_i04_grid_detect_then_xrc_calculates_exposure_and_transmission_then_uses @patch( "mx_bluesky.beamlines.i04.experiment_plans.i04_grid_detect_then_xray_centre_plan.fix_transmission_and_exposure_time_for_current_wavelength", ) -def test_get_grid_common_params( +def test_get_generic_grid_params( mock_fix_trans_and_exposure: MagicMock, tmp_path, ): @@ -524,7 +524,7 @@ def test_get_grid_common_params( detector_distance_mm=264.5, storage_directory=str(tmp_path), ) - grid_common_params = _get_grid_common_params(1, entry_params) + grid_common_params = _get_generic_grid_params(1, entry_params) assert grid_common_params.exposure_time_s == expected_exposure_time assert grid_common_params.transmission_frac == expected_trans_frac @@ -588,18 +588,21 @@ def test_grid_detect_then_xrc_stages_and_unstages_zocalo_and_gets_results( def test_detect_grid_and_do_gridscan_gives_params_specified_grid( mock_change_aperture_then_move_to_xtal: MagicMock, mock_create_flyscan_params: MagicMock, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, run_engine: RunEngine, i04_grid_detect_then_xrc_default_params: partial[MsgGenerator], ): - mock_create_flyscan_params.return_value = test_fgs_params + mock_create_flyscan_params.return_value = test_three_d_grid_params run_engine( i04_grid_detect_then_xrc_default_params( udc=False, ) ) mock_change_aperture_then_move_to_xtal.assert_called_once() - assert mock_change_aperture_then_move_to_xtal.call_args[0][1] == test_fgs_params + assert ( + mock_change_aperture_then_move_to_xtal.call_args[0][1] + == test_three_d_grid_params + ) @patch( @@ -627,7 +630,7 @@ def test_detect_grid_and_do_gridscan_gives_params_specified_grid( def test_i04_grid_detect_then_xrc_only_sets_aperture_at_start_of_plan( mock_get_xrc_results: MagicMock, mock_create_flyscan_params: MagicMock, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, run_engine: RunEngine, i04_grid_detect_then_xrc_default_params: partial[MsgGenerator], ): @@ -654,7 +657,7 @@ def fake_get_xrc_results_plan(): mock_get_xrc_results.return_value = fake_get_xrc_results_plan() - mock_create_flyscan_params.return_value = test_fgs_params + mock_create_flyscan_params.return_value = test_three_d_grid_params run_engine( i04_grid_detect_then_xrc_default_params( udc=False, diff --git a/tests/unit_tests/beamlines/i24/jungfrau_commissioning/test_data/test_good_rotation_params.json b/tests/unit_tests/beamlines/i24/jungfrau_commissioning/test_data/test_good_rotation_params.json index f9a23453e2..55f1f14424 100644 --- a/tests/unit_tests/beamlines/i24/jungfrau_commissioning/test_data/test_good_rotation_params.json +++ b/tests/unit_tests/beamlines/i24/jungfrau_commissioning/test_data/test_good_rotation_params.json @@ -7,7 +7,7 @@ "sample_id": 123456, "transmission_frac": 0.1, "storage_directory": "from numtracker", - "parameter_model_version": "5.0.0", + "parameter_model_version": "6.0.0", "visit": "from numtracker", "shutter_opening_time_s": 0.5 } diff --git a/tests/unit_tests/common/experiment_plans/inner_plans/test_do_fgs.py b/tests/unit_tests/common/experiment_plans/inner_plans/test_do_fgs.py index c3432748a1..3375b850e3 100644 --- a/tests/unit_tests/common/experiment_plans/inner_plans/test_do_fgs.py +++ b/tests/unit_tests/common/experiment_plans/inner_plans/test_do_fgs.py @@ -64,6 +64,7 @@ def null_plan() -> MsgGenerator: detector, synchrotron, scan_points=create_dummy_scan_spec(), + omega_starts_deg=[0, 90], plan_during_collection=null_plan, ) ) @@ -137,6 +138,7 @@ def event(self, doc: Event): detector, synchrotron, scan_points=expected_scan_points, + omega_starts_deg=[0, 90], ) ) diff --git a/tests/unit_tests/common/experiment_plans/inner_plans/test_read_hardware.py b/tests/unit_tests/common/experiment_plans/inner_plans/test_read_hardware.py index 77f868dbcb..efa7f8004d 100644 --- a/tests/unit_tests/common/experiment_plans/inner_plans/test_read_hardware.py +++ b/tests/unit_tests/common/experiment_plans/inner_plans/test_read_hardware.py @@ -30,12 +30,12 @@ @pytest.fixture -def ispyb_plan(test_fgs_params: SpecifiedThreeDGridScan): +def ispyb_plan(test_three_d_grid_params: SpecifiedThreeDGridScan): @bpp.set_run_key_decorator(PlanNameConstants.GRIDSCAN_OUTER) @bpp.run_decorator( # attach experiment metadata to the start document md={ "subplan_name": PlanNameConstants.GRIDSCAN_OUTER, - "mx_bluesky_parameters": test_fgs_params.model_dump_json(), + "mx_bluesky_parameters": test_three_d_grid_params.model_dump_json(), } ) def standalone_read_hardware_for_ispyb(*args): diff --git a/tests/unit_tests/common/experiment_plans/test_change_aperture_then_move_plan.py b/tests/unit_tests/common/experiment_plans/test_change_aperture_then_move_plan.py index 144fb773d7..467e0a6195 100644 --- a/tests/unit_tests/common/experiment_plans/test_change_aperture_then_move_plan.py +++ b/tests/unit_tests/common/experiment_plans/test_change_aperture_then_move_plan.py @@ -96,14 +96,16 @@ def test_get_results_then_change_aperture_and_move_to_xtal_calls_expected_plans( mock__get_xrc_results: MagicMock, run_engine: RunEngine, grid_detect_xrc_devices: GridDetectThenXRayCentreComposite, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, ): mock_flyscan_event_handler = MagicMock(spec=XRayCentreEventHandler) mock_flyscan_event_handler.xray_centre_results = [0] run_engine( get_results_then_change_aperture_and_move_to_xtal( - grid_detect_xrc_devices, test_fgs_params, mock_flyscan_event_handler + grid_detect_xrc_devices, + test_three_d_grid_params, + mock_flyscan_event_handler, ) ) mock__get_xrc_results.assert_called_once() diff --git a/tests/unit_tests/common/experiment_plans/test_common_flyscan_xray_centre_plan.py b/tests/unit_tests/common/experiment_plans/test_common_flyscan_xray_centre_plan.py index d475fe9696..f04092662a 100644 --- a/tests/unit_tests/common/experiment_plans/test_common_flyscan_xray_centre_plan.py +++ b/tests/unit_tests/common/experiment_plans/test_common_flyscan_xray_centre_plan.py @@ -102,10 +102,10 @@ class TestFlyscanXrayCentrePlan: def test_eiger2_x_16_detector_specified( self, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, ): assert ( - test_fgs_params.detector_params.detector_size_constants.det_type_string + test_three_d_grid_params.detector_params.detector_size_constants.det_type_string == EIGER_TYPE_EIGER2_X_16M ) @@ -119,7 +119,7 @@ def test_when_run_gridscan_called_ispyb_deposition_made_and_records_errors( self, run_engine: RunEngine, fake_fgs_composite: FlyScanEssentialDevices, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, beamline_specific: BeamlineSpecificFGSFeatures, ): ispyb_callback = GridscanISPyBCallback(param_type=SpecifiedThreeDGridScan) @@ -133,9 +133,11 @@ def test_when_run_gridscan_called_ispyb_deposition_made_and_records_errors( run_engine( ispyb_activation_wrapper( common_flyscan_xray_centre( - fake_fgs_composite, test_fgs_params, beamline_specific + fake_fgs_composite, + test_three_d_grid_params, + beamline_specific, ), - test_fgs_params, + test_three_d_grid_params, ), ) @@ -153,16 +155,14 @@ def test_when_run_gridscan_called_ispyb_deposition_made_and_records_errors( def test_results_passed_to_move_motors( self, bps_abs_set: MagicMock, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, fake_fgs_composite: FlyScanEssentialDevices, run_engine: RunEngine, ): from mx_bluesky.common.device_setup_plans.manipulate_sample import move_x_y_z - motor_position = ( - test_fgs_params.fast_gridscan_params.grid_position_to_motor_position( - np.array([1, 2, 3]) - ) + motor_position = test_three_d_grid_params.fast_gridscan_params.grid_position_to_motor_position( + np.array([1, 2, 3]) ) run_engine(move_x_y_z(fake_fgs_composite.gonio, *motor_position)) bps_abs_set.assert_called_with( @@ -183,7 +183,7 @@ def test_individual_plans_triggered_once_and_only_once_in_composite_run( run_gridscan: MagicMock, run_engine_with_subs: ReWithSubs, fake_fgs_composite: FlyScanEssentialDevices, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, beamline_specific: BeamlineSpecificFGSFeatures, ): run_engine, _ = run_engine_with_subs @@ -191,7 +191,7 @@ def test_individual_plans_triggered_once_and_only_once_in_composite_run( def wrapped_gridscan_and_move(): yield from common_flyscan_xray_centre( fake_fgs_composite, - test_fgs_params, + test_three_d_grid_params, beamline_specific, ) @@ -207,7 +207,7 @@ def test_waits_for_motion_program( self, check_topup_and_wait, run_engine: RunEngine, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, fake_fgs_composite: FlyScanEssentialDevices, ): fake_fgs_composite.eiger.unstage = MagicMock( @@ -223,10 +223,8 @@ def test_plan(): fgs, fake_fgs_composite.eiger, fake_fgs_composite.synchrotron, - [ - test_fgs_params.scan_points_first_grid, - test_fgs_params.scan_points_second_grid, - ], + test_three_d_grid_params.scan_points, + test_three_d_grid_params.omega_starts_deg, ) with pytest.raises(FailedStatus): @@ -244,19 +242,21 @@ def test_if_gridscan_prepare_fails_with_invalid_grid_then_sample_exception_raise run_engine: RunEngine, fake_fgs_composite: FlyScanEssentialDevices, beamline_specific: BeamlineSpecificFGSFeatures, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, ): beamline_specific.set_flyscan_params_plan = partial( set_fast_grid_scan_params, beamline_specific.fgs_motors, - test_fgs_params.fast_gridscan_params, + test_three_d_grid_params.fast_gridscan_params, ) set_mock_value(beamline_specific.fgs_motors.device_scan_invalid, 1.0) # type: ignore with pytest.raises(WarningError): run_engine( - run_gridscan(fake_fgs_composite, test_fgs_params, beamline_specific) + run_gridscan( + fake_fgs_composite, test_three_d_grid_params, beamline_specific + ) ) @patch( @@ -268,7 +268,7 @@ def test_if_gridscan_prepare_fails_with_other_exception_then_plan_re_raised( run_engine: RunEngine, fake_fgs_composite: FlyScanEssentialDevices, beamline_specific: BeamlineSpecificFGSFeatures, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, ): exception = FailedStatus() exception.__cause__ = Exception() @@ -277,7 +277,9 @@ def test_if_gridscan_prepare_fails_with_other_exception_then_plan_re_raised( with pytest.raises(FailedStatus) as e: run_engine( - run_gridscan(fake_fgs_composite, test_fgs_params, beamline_specific) + run_gridscan( + fake_fgs_composite, test_three_d_grid_params, beamline_specific + ) ) mock_kickoff_and_complete.assert_not_called() @@ -318,16 +320,15 @@ def test_when_grid_scan_ran_then_eiger_disarmed_before_zocalo_end( mock_kickoff, mock_abs_set, fake_fgs_composite: FlyScanEssentialDevices, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, run_engine_with_subs_snapshots_already_taken: tuple[ RunEngine, tuple[GridscanNexusFileCallback, GridscanISPyBCallback], ], beamline_specific: BeamlineSpecificFGSFeatures, ): - test_fgs_params.x_steps = 9 - test_fgs_params.y_steps = 10 - test_fgs_params.z_steps = 12 + test_three_d_grid_params.x_steps = 9 + test_three_d_grid_params.y_steps = [10, 12] run_engine, (nexus_cb, ispyb_cb) = run_engine_with_subs_snapshots_already_taken # Put both mocks in a parent to easily capture order mock_parent = MagicMock() @@ -352,9 +353,9 @@ def test_when_grid_scan_ran_then_eiger_disarmed_before_zocalo_end( run_engine( ispyb_activation_wrapper( common_flyscan_xray_centre( - fake_fgs_composite, test_fgs_params, beamline_specific + fake_fgs_composite, test_three_d_grid_params, beamline_specific ), - test_fgs_params, + test_three_d_grid_params, ) ) @@ -385,12 +386,16 @@ def test_fgs_arms_eiger_without_grid_detect( mock_complete, mock_wait, fake_fgs_composite: FlyScanEssentialDevices, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, run_engine: RunEngine, beamline_specific: BeamlineSpecificFGSFeatures, ): fake_fgs_composite.eiger.unstage = MagicMock(side_effect=completed_status) - run_engine(run_gridscan(fake_fgs_composite, test_fgs_params, beamline_specific)) + run_engine( + run_gridscan( + fake_fgs_composite, test_three_d_grid_params, beamline_specific + ) + ) fake_fgs_composite.eiger.stage.assert_called_once() # type: ignore fake_fgs_composite.eiger.unstage.assert_called_once() @@ -417,7 +422,7 @@ def test_when_grid_scan_fails_with_exception_then_detector_disarmed_and_correct_ mock_wait, mock_kickoff, fake_fgs_composite: FlyScanEssentialDevices, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, run_engine: RunEngine, beamline_specific: BeamlineSpecificFGSFeatures, ): @@ -443,7 +448,9 @@ def test_when_grid_scan_fails_with_exception_then_detector_disarmed_and_correct_ with pytest.raises(CompleteError): run_engine( bpp.run_wrapper( - run_gridscan(fake_fgs_composite, test_fgs_params, beamline_specific) + run_gridscan( + fake_fgs_composite, test_three_d_grid_params, beamline_specific + ) ) ) @@ -496,7 +503,8 @@ def test_kickoff_and_complete_gridscan_triggers_zocalo( zebra_fast_grid_scan, fake_fgs_composite.eiger, fake_fgs_composite.synchrotron, - scan_points=create_dummy_scan_spec(), + create_dummy_scan_spec(), + [0, 90], ) ) @@ -525,7 +533,7 @@ def test_kickoff_and_complete_gridscan_triggers_zocalo( def test_read_hardware_during_collection_occurs_after_eiger_arm( self, fake_fgs_composite: FlyScanEssentialDevices, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, sim_run_engine: RunEngineSimulator, beamline_specific: BeamlineSpecificFGSFeatures, ): @@ -545,7 +553,9 @@ def test_read_hardware_during_collection_occurs_after_eiger_arm( "gonio-wrapped_omega-offset_and_phase", ) msgs = sim_run_engine.simulate_plan( - run_gridscan(fake_fgs_composite, test_fgs_params, beamline_specific) + run_gridscan( + fake_fgs_composite, test_three_d_grid_params, beamline_specific + ) ) msgs = assert_message_and_return_remaining( msgs, lambda msg: msg.command == "stage" and msg.obj.name == "eiger" @@ -578,22 +588,24 @@ def test_when_gridscan_succeeds_and_results_fetched_ispyb_comment_appended_to( RunEngine, tuple[GridscanNexusFileCallback, GridscanISPyBCallback], ], - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, fake_fgs_composite: FlyScanEssentialDevices, beamline_specific: BeamlineSpecificFGSFeatures, ): run_engine, (nexus_cb, ispyb_cb) = run_engine_with_subs def _wrapped_gridscan_and_move(): - run_generic_ispyb_handler_setup(ispyb_cb, test_fgs_params) + run_generic_ispyb_handler_setup(ispyb_cb, test_three_d_grid_params) yield from common_flyscan_xray_centre( fake_fgs_composite, - test_fgs_params, + test_three_d_grid_params, beamline_specific, ) run_engine( - ispyb_activation_wrapper(_wrapped_gridscan_and_move(), test_fgs_params) + ispyb_activation_wrapper( + _wrapped_gridscan_and_move(), test_three_d_grid_params + ) ) app_to_comment: MagicMock = ispyb_cb.ispyb.append_to_comment # type:ignore app_to_comment.assert_called() diff --git a/tests/unit_tests/common/experiment_plans/test_grid_detection_plan.py b/tests/unit_tests/common/experiment_plans/test_grid_detection_plan.py index ef75311950..2b26600875 100644 --- a/tests/unit_tests/common/experiment_plans/test_grid_detection_plan.py +++ b/tests/unit_tests/common/experiment_plans/test_grid_detection_plan.py @@ -32,7 +32,7 @@ GridscanISPyBCallback, ispyb_activation_wrapper, ) -from mx_bluesky.common.parameters.gridscan import GridCommon, SpecifiedThreeDGridScan +from mx_bluesky.common.parameters.gridscan import GenericGrid, SpecifiedThreeDGridScan from mx_bluesky.common.utils.exceptions import WarningError from ...conftest import assert_event @@ -191,10 +191,14 @@ def decorated(): gridscan_params = grid_param_cb.get_grid_parameters() assert gridscan_params["x_start_um"] == pytest.approx(-804, abs=1) - assert gridscan_params["y_start_um"] == pytest.approx( - -550 - ((box_size_y_pixels / 2) * microns_per_pixel_y), abs=1 + assert ( + gridscan_params["y_starts_um"] + == [ + pytest.approx(-550 - ((box_size_y_pixels / 2) * microns_per_pixel_y), abs=1) + ] + * 2 ) - assert gridscan_params["z_start_um"] == pytest.approx(-534, abs=1) + assert gridscan_params["z_starts_um"] == [pytest.approx(-534, abs=1)] * 2 @patch("bluesky.plan_stubs.sleep", new=MagicMock()) @@ -202,7 +206,7 @@ async def test_when_grid_detection_plan_run_then_ispyb_callback_gets_correct_val fake_devices: tuple[OavGridDetectionComposite, MagicMock], run_engine: RunEngine, test_config_files: dict[str, str], - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, tmp_path: Path, dummy_rotation_data_collection_group_info, ): @@ -210,7 +214,7 @@ async def test_when_grid_detection_plan_run_then_ispyb_callback_gets_correct_val ConfigClient(""), "loopCentring", test_config_files["oav_config_json"] ) composite, _ = fake_devices - cb = GridscanISPyBCallback(param_type=GridCommon) + cb = GridscanISPyBCallback(param_type=GenericGrid) cb.data_collection_group_info = dummy_rotation_data_collection_group_info run_engine.subscribe(cb) @@ -218,7 +222,7 @@ async def test_when_grid_detection_plan_run_then_ispyb_callback_gets_correct_val run_engine( ispyb_activation_wrapper( do_grid_and_edge_detect(composite, params, tmp_path), - test_fgs_params, + test_three_d_grid_params, ) ) @@ -269,7 +273,7 @@ def test_when_grid_detection_plan_run_then_grid_detection_callback_gets_correct_ fake_devices: tuple[OavGridDetectionComposite, MagicMock], run_engine: RunEngine, test_config_files: dict[str, str], - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, tmp_path: Path, ): params = OAVParameters( @@ -282,23 +286,23 @@ def test_when_grid_detection_plan_run_then_grid_detection_callback_gets_correct_ run_engine( ispyb_activation_wrapper( - do_grid_and_edge_detect(composite, params, tmp_path), test_fgs_params + do_grid_and_edge_detect(composite, params, tmp_path), + test_three_d_grid_params, ) ) my_grid_params = cb.get_grid_parameters() assert my_grid_params["x_start_um"] == pytest.approx(-794.22) - assert my_grid_params["y_start_um"] == pytest.approx(-539.84 - (box_size_um / 2)) - assert my_grid_params["y2_start_um"] == pytest.approx(-539.84 - (box_size_um / 2)) - assert my_grid_params["z_start_um"] == pytest.approx(-524.04) - assert my_grid_params["z2_start_um"] == pytest.approx(-524.04) + assert ( + my_grid_params["y_starts_um"] + == [pytest.approx(-539.84 - (box_size_um / 2))] * 2 + ) + assert my_grid_params["z_starts_um"] == [pytest.approx(-524.04)] * 2 assert my_grid_params["x_step_size_um"] == box_size_um - assert my_grid_params["y_step_size_um"] == box_size_um - assert my_grid_params["z_step_size_um"] == box_size_um + assert my_grid_params["y_step_sizes_um"] == [box_size_um] * 2 assert my_grid_params["x_steps"] == pytest.approx(9) - assert my_grid_params["y_steps"] == pytest.approx(2) - assert my_grid_params["z_steps"] == pytest.approx(3) + assert my_grid_params["y_steps"] == [pytest.approx(2), pytest.approx(3)] assert cb.x_step_size_um == cb.y_step_size_um == cb.z_step_size_um == box_size_um @@ -307,7 +311,7 @@ def test_when_grid_detection_plan_run_with_different_omega_order_then_grid_detec fake_devices: tuple[OavGridDetectionComposite, MagicMock], run_engine: RunEngine, test_config_files: dict[str, str], - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, tmp_path: Path, ): params = OAVParameters( @@ -327,23 +331,23 @@ def test_when_grid_detection_plan_run_with_different_omega_order_then_grid_detec run_engine( ispyb_activation_wrapper( - do_grid_and_edge_detect(composite, params, tmp_path), test_fgs_params + do_grid_and_edge_detect(composite, params, tmp_path), + test_three_d_grid_params, ) ) my_grid_params = cb.get_grid_parameters() assert my_grid_params["x_start_um"] == pytest.approx(-794.22) - assert my_grid_params["y_start_um"] == pytest.approx(-539.84 - (box_size_um / 2)) - assert my_grid_params["y2_start_um"] == pytest.approx(-539.84 - (box_size_um / 2)) - assert my_grid_params["z_start_um"] == pytest.approx(-524.04) - assert my_grid_params["z2_start_um"] == pytest.approx(-524.04) + assert ( + my_grid_params["y_starts_um"] + == [pytest.approx(-539.84 - (box_size_um / 2))] * 2 + ) + assert my_grid_params["z_starts_um"] == [pytest.approx(-524.04)] * 2 assert my_grid_params["x_step_size_um"] == box_size_um - assert my_grid_params["y_step_size_um"] == box_size_um - assert my_grid_params["z_step_size_um"] == box_size_um + assert my_grid_params["y_step_sizes_um"] == [box_size_um] * 2 assert my_grid_params["x_steps"] == pytest.approx(9) - assert my_grid_params["y_steps"] == pytest.approx(2) - assert my_grid_params["z_steps"] == pytest.approx(3) + assert my_grid_params["y_steps"] == [pytest.approx(2), pytest.approx(3)] assert cb.x_step_size_um == cb.y_step_size_um == cb.z_step_size_um == box_size_um diff --git a/tests/unit_tests/common/external_interaction/callbacks/common/test_ispyb_callback_base.py b/tests/unit_tests/common/external_interaction/callbacks/common/test_ispyb_callback_base.py index 30bd1fd9dc..b09ad0c957 100644 --- a/tests/unit_tests/common/external_interaction/callbacks/common/test_ispyb_callback_base.py +++ b/tests/unit_tests/common/external_interaction/callbacks/common/test_ispyb_callback_base.py @@ -13,7 +13,7 @@ def test_visit_extracted_from_numtracker( - run_engine: RunEngine, test_fgs_params: SpecifiedThreeDGridScan + run_engine: RunEngine, test_three_d_grid_params: SpecifiedThreeDGridScan ): test_visit = "test_visit" @@ -22,8 +22,8 @@ def test_visit_extracted_from_numtracker( callback = BaseISPyBCallback() callback.activity_gated_stop = MagicMock() - test_fgs_params.visit = USE_NUMTRACKER - callback.params = test_fgs_params + test_three_d_grid_params.visit = USE_NUMTRACKER + callback.params = test_three_d_grid_params run_engine.subscribe(callback) @bpp.run_decorator( @@ -40,12 +40,12 @@ def test_plan(): def test_exception_when_instrument_session_doesnt_exist( - run_engine: RunEngine, test_fgs_params: SpecifiedThreeDGridScan + run_engine: RunEngine, test_three_d_grid_params: SpecifiedThreeDGridScan ): callback = BaseISPyBCallback() callback.activity_gated_stop = MagicMock() - test_fgs_params.visit = USE_NUMTRACKER - callback.params = test_fgs_params + test_three_d_grid_params.visit = USE_NUMTRACKER + callback.params = test_three_d_grid_params run_engine.subscribe(callback) @bpp.run_decorator( diff --git a/tests/unit_tests/common/external_interaction/xray_centre/test_ispyb_callback.py b/tests/unit_tests/common/external_interaction/xray_centre/test_ispyb_callback.py index 032e668dd2..95115e82fa 100644 --- a/tests/unit_tests/common/external_interaction/xray_centre/test_ispyb_callback.py +++ b/tests/unit_tests/common/external_interaction/xray_centre/test_ispyb_callback.py @@ -16,7 +16,9 @@ _smargon_omega_to_xyxz_plane, ) from mx_bluesky.common.parameters.constants import DocDescriptorNames -from mx_bluesky.hyperion.parameters.gridscan import GridCommonWithHyperionDetectorParams +from mx_bluesky.hyperion.parameters.gridscan import ( + GenericGridWithHyperionDetectorParams, +) from .....conftest import ( EXPECTED_START_TIME, @@ -72,7 +74,7 @@ class TestXrayCentreISPyBCallback: def test_activity_gated_start_3d(self, mock_ispyb_conn, test_event_data, tmp_path): callback = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) callback.activity_gated_start( test_event_data.test_grid_detect_and_gridscan_start_document @@ -115,7 +117,7 @@ def test_reason_provided_if_crystal_not_found_error( self, mock_update_data_collection_group_table, mock_ispyb_conn, test_event_data ): callback = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) callback.activity_gated_start( test_event_data.test_grid_detect_and_gridscan_start_document @@ -136,7 +138,7 @@ def test_reason_provided_if_crystal_not_found_error( def test_hardware_read_event_3d(self, mock_ispyb_conn, test_event_data): callback = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) callback.activity_gated_start( test_event_data.test_grid_detect_and_gridscan_start_document @@ -165,7 +167,7 @@ def test_hardware_read_event_3d(self, mock_ispyb_conn, test_event_data): def test_flux_read_events_3d(self, mock_ispyb_conn, test_event_data): callback = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) callback.activity_gated_start( test_event_data.test_grid_detect_and_gridscan_start_document @@ -230,7 +232,7 @@ def test_activity_gated_event_oav_snapshot_triggered( first_comment: str, ): callback = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) callback.activity_gated_start( test_event_data.test_grid_detect_and_gridscan_start_document @@ -333,7 +335,7 @@ async def test_ispyb_callback_handles_read_hardware_in_run_engine( self, run_engine, mock_ispyb_conn, dummy_rotation_data_collection_group_info ): callback = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) callback._handle_ispyb_hardware_read = MagicMock() callback._handle_ispyb_transmission_flux_read = MagicMock() @@ -380,7 +382,7 @@ def test_given_event_doc_before_start_doc_received_then_exception_raised( test_event_data, ): callback = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) callback.activity_gated_descriptor( test_event_data.test_descriptor_document_oav_snapshot @@ -399,7 +401,7 @@ def test_ispyb_callback_clears_state_after_run_stop( self, test_event_data, mock_ispyb_conn ): callback = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) callback.active = True callback.start(test_event_data.test_grid_detect_and_gridscan_start_document) # type: ignore diff --git a/tests/unit_tests/common/external_interaction/xray_centre/test_ispyb_handler.py b/tests/unit_tests/common/external_interaction/xray_centre/test_ispyb_handler.py index 38208065c7..1abe4d71a5 100644 --- a/tests/unit_tests/common/external_interaction/xray_centre/test_ispyb_handler.py +++ b/tests/unit_tests/common/external_interaction/xray_centre/test_ispyb_handler.py @@ -12,7 +12,9 @@ ) from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER from mx_bluesky.hyperion.external_interaction.callbacks.__main__ import setup_logging -from mx_bluesky.hyperion.parameters.gridscan import GridCommonWithHyperionDetectorParams +from mx_bluesky.hyperion.parameters.gridscan import ( + GenericGridWithHyperionDetectorParams, +) from .....conftest import TestData @@ -52,7 +54,7 @@ def mock_store_in_ispyb(config, *args, **kwargs) -> StoreInIspyb: class TestXrayCentreIspybHandler: def test_fgs_failing_results_in_bad_run_status_in_ispyb(self, test_event_data): ispyb_handler = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) ispyb_handler.activity_gated_start( test_event_data.test_grid_detect_and_gridscan_start_document @@ -87,7 +89,7 @@ def test_fgs_raising_no_exception_results_in_good_run_status_in_ispyb( self, test_event_data ): ispyb_handler = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) ispyb_handler.activity_gated_start( test_event_data.test_grid_detect_and_gridscan_start_document @@ -133,7 +135,7 @@ def test_given_ispyb_callback_started_writing_to_ispyb_when_messages_logged_then gelf_handler.emit = MagicMock() ispyb_handler = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) ispyb_handler.activity_gated_start( test_event_data.test_grid_detect_and_gridscan_start_document @@ -169,7 +171,7 @@ def test_given_ispyb_callback_finished_writing_to_ispyb_when_messages_logged_the gelf_handler.emit = MagicMock() ispyb_handler = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams + param_type=GenericGridWithHyperionDetectorParams ) ispyb_handler.activity_gated_start( test_event_data.test_grid_detect_and_gridscan_start_document @@ -202,7 +204,7 @@ def test_given_fgs_plan_finished_when_zocalo_results_event_then_expected_comment self, mock_time, dummy_rotation_data_collection_group_info, test_event_data ): ispyb_handler = GridscanISPyBCallback( - param_type=GridCommonWithHyperionDetectorParams, + param_type=GenericGridWithHyperionDetectorParams, ) ispyb_handler.activity_gated_start( diff --git a/tests/unit_tests/common/external_interaction/xray_centre/test_nexus_handler.py b/tests/unit_tests/common/external_interaction/xray_centre/test_nexus_handler.py index 5af8db32c2..c5f970bb05 100644 --- a/tests/unit_tests/common/external_interaction/xray_centre/test_nexus_handler.py +++ b/tests/unit_tests/common/external_interaction/xray_centre/test_nexus_handler.py @@ -45,8 +45,7 @@ def test_writers_dont_create_on_init_but_do_on_during_collection_read_event( param_type=HyperionSpecifiedThreeDGridScan ) - assert nexus_handler.nexus_writer_1 is None - assert nexus_handler.nexus_writer_2 is None + assert not nexus_handler._writers nexus_handler.activity_gated_start( test_event_data.test_gridscan_outer_start_document @@ -59,10 +58,10 @@ def test_writers_dont_create_on_init_but_do_on_during_collection_read_event( test_event_data.test_event_document_during_data_collection ) - assert nexus_handler.nexus_writer_1 is not None - assert nexus_handler.nexus_writer_2 is not None - nexus_handler.nexus_writer_1.create_nexus_file.assert_called_once() - nexus_handler.nexus_writer_2.create_nexus_file.assert_called_once() + assert nexus_handler._writers[0] is not None + assert nexus_handler._writers[1] is not None + nexus_handler._writers[0].create_nexus_file.assert_called_once() # type: ignore + nexus_handler._writers[1].create_nexus_file.assert_called_once() # type: ignore @pytest.mark.parametrize( @@ -98,12 +97,12 @@ def test_given_different_bit_depths_then_writers_created_wth_correct_virtual_dat nexus_handler.activity_gated_event(event_doc) - assert nexus_handler.nexus_writer_1 is not None - assert nexus_handler.nexus_writer_2 is not None - nexus_handler.nexus_writer_1.create_nexus_file.assert_called_once_with( # type:ignore + assert nexus_handler._writers[0] is not None + assert nexus_handler._writers[1] is not None + nexus_handler._writers[0].create_nexus_file.assert_called_once_with( # type:ignore vds_type ) - nexus_handler.nexus_writer_2.create_nexus_file.assert_called_once_with( # type:ignore + nexus_handler._writers[1].create_nexus_file.assert_called_once_with( # type:ignore vds_type ) @@ -130,7 +129,7 @@ def test_beam_and_attenuator_set_on_ispyb_transmission_event( test_event_data.test_event_document_during_data_collection ) - for writer in [nexus_handler.nexus_writer_1, nexus_handler.nexus_writer_2]: + for writer in [nexus_handler._writers[0], nexus_handler._writers[1]]: assert writer is not None assert writer.attenuator is not None assert writer.beam is not None diff --git a/tests/unit_tests/common/parameters/test_gridscan.py b/tests/unit_tests/common/parameters/test_gridscan.py new file mode 100644 index 0000000000..b619364d64 --- /dev/null +++ b/tests/unit_tests/common/parameters/test_gridscan.py @@ -0,0 +1,107 @@ +import pytest +from pydantic import ValidationError + +from mx_bluesky.common.parameters.components import get_param_version +from mx_bluesky.common.parameters.gridscan import ( + SpecifiedGrids, + SpecifiedThreeDGridScan, +) + + +class GridParamsTest(SpecifiedGrids): + def fast_gridscan_params(): ... # type: ignore + + +@pytest.mark.parametrize( + "y_starts_um, z_starts_um, omega_starts_deg, y_step_sizes_um, y_steps, should_raise", + [ + ([1, 1], [1], [1], [1], [1, 1], True), + ( + [ + 1, + ], + [1], + [1], + [1, 1, 2, 3], + [1], + True, + ), + ([1, 1], [1, 1], [1, 1], [1, 1], [1], True), + ([1, 1, 1, 1], [1, 1, 1], [1], [1], [1, 1], True), + ([1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], False), + ], +) +def test_specified_grids_validation_error( + y_starts_um: list[float], + z_starts_um: list[float], + omega_starts_deg: list[float], + y_step_sizes_um: list[float], + y_steps: list[int], + should_raise: bool, +): + if should_raise: + with pytest.raises( + ValidationError, match="Fields must all have the same length:" + ): + GridParamsTest( + x_start_um=0, + y_starts_um=y_starts_um, + z_starts_um=z_starts_um, + omega_starts_deg=omega_starts_deg, + y_step_sizes_um=y_step_sizes_um, + y_steps=y_steps, + sample_id=0, + visit="/tmp", + parameter_model_version=get_param_version(), + file_name="/tmp", + storage_directory="/tmp", + x_steps=5, + ) + else: + GridParamsTest( + x_start_um=0, + y_starts_um=y_starts_um, + z_starts_um=z_starts_um, + omega_starts_deg=omega_starts_deg, + y_step_sizes_um=y_step_sizes_um, + y_steps=y_steps, + sample_id=0, + visit="/tmp", + parameter_model_version=get_param_version(), + file_name="/tmp", + storage_directory="/tmp", + x_steps=5, + ) + + +@pytest.mark.parametrize( + "y_starts_um, z_starts_um, omega_starts_deg, y_step_sizes_um, y_steps, should_raise", + [ + ([1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], True), + ([1, 1], [1, 1], [1, 1], [1, 1], [1, 1], False), + ], +) +def test_three_d_grid_scan_validation( + y_starts_um: list[float], + z_starts_um: list[float], + omega_starts_deg: list[float], + y_step_sizes_um: list[float], + y_steps: list[int], + should_raise: bool, +): + if should_raise: + with pytest.raises(ValidationError, match="must be length 2 for 3D scans"): + SpecifiedThreeDGridScan( + x_start_um=0, + y_starts_um=y_starts_um, + z_starts_um=z_starts_um, + omega_starts_deg=omega_starts_deg, + y_step_sizes_um=y_step_sizes_um, + y_steps=y_steps, + sample_id=0, + visit="/tmp", + parameter_model_version=get_param_version(), + file_name="/tmp", + storage_directory="/tmp", + x_steps=5, + ) diff --git a/tests/unit_tests/common/utils/test_xrc_result.py b/tests/unit_tests/common/utils/test_xrc_result.py index b782dc6d3e..f261fc1fe1 100644 --- a/tests/unit_tests/common/utils/test_xrc_result.py +++ b/tests/unit_tests/common/utils/test_xrc_result.py @@ -33,14 +33,14 @@ async def test_results_adjusted_and_event_raised( - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, run_engine: RunEngine, zocalo: ZocaloResults, ): x_ray_centre_event_handler = XRayCentreEventHandler() run_engine.subscribe(x_ray_centre_event_handler) mock_zocalo_trigger(zocalo, TestData.test_result_large) - run_engine(fetch_xrc_results_from_zocalo(zocalo, test_fgs_params)) + run_engine(fetch_xrc_results_from_zocalo(zocalo, test_three_d_grid_params)) actual = x_ray_centre_event_handler.xray_centre_results expected = XRayCentreResult( @@ -60,7 +60,7 @@ async def test_results_adjusted_and_event_raised( def test_fetch_results_discards_results_below_threshold( - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, run_engine: RunEngine, zocalo: ZocaloResults, ): @@ -73,7 +73,7 @@ def test_fetch_results_discards_results_below_threshold( + TestData.test_result_below_threshold + TestData.test_result_small, ) - run_engine(fetch_xrc_results_from_zocalo(zocalo, test_fgs_params)) + run_engine(fetch_xrc_results_from_zocalo(zocalo, test_three_d_grid_params)) assert callback.xray_centre_results and len(callback.xray_centre_results) == 2 assert [r.max_count for r in callback.xray_centre_results] == [50000, 1000] @@ -81,18 +81,18 @@ def test_fetch_results_discards_results_below_threshold( def test_no_xtal_found_raises_exception( run_engine: RunEngine, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, zocalo: ZocaloResults, ): mock_zocalo_trigger(zocalo, []) with pytest.raises(CrystalNotFoundError): - run_engine(fetch_xrc_results_from_zocalo(zocalo, test_fgs_params)) + run_engine(fetch_xrc_results_from_zocalo(zocalo, test_three_d_grid_params)) def test_dummy_result_returned_when_no_xtal_and_commissioning_mode_enabled( run_engine: RunEngine, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, fake_fgs_composite: FlyScanEssentialDevices, beamline_specific: BeamlineSpecificFGSFeatures, zocalo: ZocaloResults, @@ -103,11 +103,11 @@ def test_dummy_result_returned_when_no_xtal_and_commissioning_mode_enabled( mock_zocalo_trigger(zocalo, []) - run_engine(fetch_xrc_results_from_zocalo(zocalo, test_fgs_params)) + run_engine(fetch_xrc_results_from_zocalo(zocalo, test_three_d_grid_params)) results = xrc_event_handler.xray_centre_results or [] assert len(results) == 1 result = results[0] - assert result.sample_id == test_fgs_params.sample_id + assert result.sample_id == test_three_d_grid_params.sample_id assert result.max_count == 10000 assert result.total_count == 100000 assert all(np.isclose(result.bounding_box_mm[0], [1.95, 0.95, 0.45])) diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index 82291f9771..9c8e659909 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -73,7 +73,7 @@ FlyScanEssentialDevices, GridDetectThenXRayCentreComposite, ) -from mx_bluesky.common.parameters.gridscan import GridCommon, SpecifiedThreeDGridScan +from mx_bluesky.common.parameters.gridscan import GenericGrid, SpecifiedThreeDGridScan from mx_bluesky.hyperion.parameters.device_composites import ( HyperionGridDetectThenXRayCentreComposite, ) @@ -219,7 +219,7 @@ def use_beamline_t01(): @pytest.fixture -def mock_subscriptions(test_fgs_params): +def mock_subscriptions(test_three_d_grid_params): with ( patch( "mx_bluesky.common.external_interaction.callbacks.common.zocalo_callback.ZocaloTrigger", @@ -255,15 +255,6 @@ def run_engine_with_subs( yield run_engine, mock_subscriptions -@pytest.fixture -def test_fgs_params(tmp_path, patch_beamline_env_variable): - return SpecifiedThreeDGridScan( - **raw_params_from_file( - "tests/test_data/parameter_json_files/good_test_parameters.json", tmp_path - ) - ) - - def mock_zocalo_trigger(zocalo: ZocaloResults, result): @AsyncStatus.wrap async def mock_complete(results): @@ -344,7 +335,7 @@ async def zebra_fast_grid_scan(): @pytest.fixture async def fake_fgs_composite( smargon: Smargon, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, attenuator, xbpm_feedback, synchrotron, @@ -362,7 +353,9 @@ async def fake_fgs_composite( fake_composite.eiger.stage = MagicMock(side_effect=lambda: completed_status()) # unstage should be mocked on a per-test basis because several rely on unstage - fake_composite.eiger.set_detector_parameters(test_fgs_params.detector_params) + fake_composite.eiger.set_detector_parameters( + test_three_d_grid_params.detector_params + ) fake_composite.eiger.stop_odin_when_all_frames_collected = MagicMock() fake_composite.eiger.odin.check_and_wait_for_odin_state = lambda timeout: True @@ -416,7 +409,7 @@ def test_full_grid_scan_params(tmp_path): "tests/test_data/parameter_json_files/good_test_grid_with_edge_detect_parameters.json", tmp_path, ) - return GridCommon(**params) + return GenericGrid(**params) @pytest.fixture diff --git a/tests/unit_tests/hyperion/conftest.py b/tests/unit_tests/hyperion/conftest.py index 2e22b5154a..253171f5a6 100644 --- a/tests/unit_tests/hyperion/conftest.py +++ b/tests/unit_tests/hyperion/conftest.py @@ -119,15 +119,6 @@ def test_multi_rotation_params(tmp_path): ) -@pytest.fixture -def test_fgs_params(tmp_path): - return HyperionSpecifiedThreeDGridScan( - **raw_params_from_file( - "tests/test_data/parameter_json_files/good_test_parameters.json", tmp_path - ) - ) - - @pytest.fixture(params=[False, True]) def test_omega_flip(request): with patch( @@ -144,7 +135,7 @@ def fgs_params_use_panda(tmp_path): new="tests/test_data/test_domain_properties_with_panda", ): params = raw_params_from_file( - "tests/test_data/parameter_json_files/good_test_parameters.json", + "tests/test_data/parameter_json_files/good_test_specified_three_d_grid_params.json", tmp_path, ) yield HyperionSpecifiedThreeDGridScan(**params) diff --git a/tests/unit_tests/hyperion/experiment_plans/conftest.py b/tests/unit_tests/hyperion/experiment_plans/conftest.py index ab445dbc00..5fd785e3f0 100644 --- a/tests/unit_tests/hyperion/experiment_plans/conftest.py +++ b/tests/unit_tests/hyperion/experiment_plans/conftest.py @@ -282,16 +282,13 @@ def grid_detection_callback_with_detected_grid(): "transmission_frac": 1.0, "exposure_time_s": 0, "x_start_um": 0, - "y_start_um": 0, - "y2_start_um": 0, - "z_start_um": 0, - "z2_start_um": 0, + "y_starts_um": [0, 0], + "z_starts_um": [0, 0], "x_steps": 10, - "y_steps": 10, - "z_steps": 10, + "y_steps": [10, 10], "x_step_size_um": 0.1, - "y_step_size_um": 0.1, - "z_step_size_um": 0.1, + "y_step_sizes_um": [0.1, 0.1], + "omega_starts_deg": [0, 90], } yield callback diff --git a/tests/unit_tests/hyperion/experiment_plans/test_load_centre_collect_full_plan.py b/tests/unit_tests/hyperion/experiment_plans/test_load_centre_collect_full_plan.py index 9dac7cd450..01dca80075 100644 --- a/tests/unit_tests/hyperion/experiment_plans/test_load_centre_collect_full_plan.py +++ b/tests/unit_tests/hyperion/experiment_plans/test_load_centre_collect_full_plan.py @@ -89,13 +89,13 @@ @pytest.fixture def load_centre_collect_params_with_patched_create_params( load_centre_collect_params: LoadCentreCollect, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, ): with patch( "mx_bluesky.hyperion.experiment_plans.pin_centre_then_gridscan_plan.create_parameters_for_grid_detection" ) as mock_create_params: load_centre_collect_params.robot_load_then_centre.set_specified_grid_params( - test_fgs_params + test_three_d_grid_params ) mock_create_params.return_value = ( load_centre_collect_params.robot_load_then_centre @@ -1067,7 +1067,7 @@ def test_box_size_passed_through_to_gridscan( load_centre_collect_params: LoadCentreCollect, oav_parameters_for_rotation: OAVParameters, run_engine: RunEngine, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, load_centre_collect_params_with_patched_create_params, ): run_engine( @@ -1076,7 +1076,7 @@ def test_box_size_passed_through_to_gridscan( ) ) detect_grid_call = mock_detect_grid.mock_calls[0] - assert detect_grid_call.args[1].box_size_um == test_fgs_params.box_size_um + assert detect_grid_call.args[1].box_size_um == test_three_d_grid_params.box_size_um @patch( diff --git a/tests/unit_tests/hyperion/experiment_plans/test_pin_centre_then_xray_centre_plan.py b/tests/unit_tests/hyperion/experiment_plans/test_pin_centre_then_xray_centre_plan.py index 0feaefb0bf..9caeac0db9 100644 --- a/tests/unit_tests/hyperion/experiment_plans/test_pin_centre_then_xray_centre_plan.py +++ b/tests/unit_tests/hyperion/experiment_plans/test_pin_centre_then_xray_centre_plan.py @@ -53,25 +53,6 @@ def pin_centre_flyscan_then_fetch_results() -> MsgGenerator: yield from pin_centre_flyscan_then_fetch_results() -@pytest.fixture -def test_grid_params(): - return { - "transmission_frac": 1.0, - "exposure_time_s": 0, - "x_start_um": 0, - "y_start_um": 0, - "y2_start_um": 0, - "z_start_um": 0, - "z2_start_um": 0, - "x_steps": 10, - "y_steps": 10, - "z_steps": 10, - "x_step_size_um": 0.1, - "y_step_size_um": 0.1, - "z_step_size_um": 0.1, - } - - @pytest.fixture def test_pin_centre_then_xray_centre_params( tmp_path, @@ -86,14 +67,14 @@ def test_pin_centre_then_xray_centre_params( @pytest.fixture def pin_centre_then_xray_centre_params_with_patched_create_params( - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, test_pin_centre_then_xray_centre_params: PinTipCentreThenXrayCentre, ): with patch( "mx_bluesky.hyperion.experiment_plans.pin_centre_then_gridscan_plan.create_parameters_for_grid_detection" ) as mock_create_params: test_pin_centre_then_xray_centre_params.set_specified_grid_params( - test_fgs_params + test_three_d_grid_params ) mock_create_params.return_value = test_pin_centre_then_xray_centre_params yield test_pin_centre_then_xray_centre_params @@ -302,9 +283,6 @@ def test_pin_centre_then_gridscan_plan_goes_to_the_starting_chi_and_phi( @pytest.mark.parametrize("transmission_frac", [1, 0.5, 0.25]) -@patch( - "mx_bluesky.common.experiment_plans.common_grid_detect_then_xray_centre_plan.GridDetectionCallback", -) @patch( "mx_bluesky.hyperion.experiment_plans.pin_centre_then_gridscan_plan.pin_tip_centre_plan" ) @@ -322,17 +300,12 @@ def test_pin_tip_centre_then_xray_centre_sets_transmission_fraction_and_xbpm_is_ mock_run_gridscan: MagicMock, mock_grid_detection_plan: MagicMock, mock_pin_tip_centre_plan: MagicMock, - mock_grid_detection_callback: MagicMock, - test_grid_params, + grid_detection_callback_with_detected_grid, transmission_frac: float, sim_run_engine: RunEngineSimulator, hyperion_grid_detect_xrc_devices: HyperionGridDetectThenXRayCentreComposite, test_pin_centre_then_xray_centre_params: PinTipCentreThenXrayCentre, ): - mock_grid_detection_callback.return_value.get_grid_parameters.return_value = ( - test_grid_params - ) - test_pin_centre_then_xray_centre_params.transmission_frac = transmission_frac msgs = sim_run_engine.simulate_plan( @@ -440,11 +413,11 @@ def test_detect_grid_and_do_gridscan_gives_params_specified_grid( mock_create_flyscan_params: MagicMock, test_pin_centre_then_xray_centre_params: PinTipCentreThenXrayCentre, hyperion_grid_detect_xrc_devices: HyperionGridDetectThenXRayCentreComposite, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, test_config_files, run_engine: RunEngine, ): - mock_create_flyscan_params.return_value = test_fgs_params + mock_create_flyscan_params.return_value = test_three_d_grid_params run_engine( pin_centre_then_gridscan_plan( hyperion_grid_detect_xrc_devices, @@ -453,7 +426,7 @@ def test_detect_grid_and_do_gridscan_gives_params_specified_grid( ) ) mock_fetch_xrc_results.assert_called_once() - assert mock_fetch_xrc_results.call_args[0][1] == test_fgs_params + assert mock_fetch_xrc_results.call_args[0][1] == test_three_d_grid_params @patch( diff --git a/tests/unit_tests/hyperion/experiment_plans/test_robot_load_then_centre.py b/tests/unit_tests/hyperion/experiment_plans/test_robot_load_then_centre.py index 4fa72b07ae..7778076880 100644 --- a/tests/unit_tests/hyperion/experiment_plans/test_robot_load_then_centre.py +++ b/tests/unit_tests/hyperion/experiment_plans/test_robot_load_then_centre.py @@ -40,12 +40,14 @@ def robot_load_then_centre_params(tmp_path): @pytest.fixture def robot_load_then_centre_params_with_patched_create_params( robot_load_then_centre_params: RobotLoadThenCentre, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, ): with patch( "mx_bluesky.hyperion.experiment_plans.pin_centre_then_gridscan_plan.create_parameters_for_grid_detection" ) as mock_create_params: - robot_load_then_centre_params.set_specified_grid_params(test_fgs_params) + robot_load_then_centre_params.set_specified_grid_params( + test_three_d_grid_params + ) mock_create_params.return_value = robot_load_then_centre_params yield @@ -527,7 +529,7 @@ def test_box_size_passed_through_to_gridscan( robot_load_then_centre_params: RobotLoadThenCentre, grid_detection_callback_with_detected_grid: MagicMock, run_engine: RunEngine, - test_fgs_params: SpecifiedThreeDGridScan, + test_three_d_grid_params: SpecifiedThreeDGridScan, robot_load_then_centre_params_with_patched_create_params, ): run_engine( @@ -537,7 +539,7 @@ def test_box_size_passed_through_to_gridscan( ) ) detect_grid_call = mock_detect_grid.mock_calls[0] - assert detect_grid_call.args[1].box_size_um == test_fgs_params.box_size_um + assert detect_grid_call.args[1].box_size_um == test_three_d_grid_params.box_size_um async def test_multiple_devices(dcm, undulator, undulator_dcm): diff --git a/tests/unit_tests/hyperion/external_interaction/conftest.py b/tests/unit_tests/hyperion/external_interaction/conftest.py index fa6e9c2a15..2a372948c2 100644 --- a/tests/unit_tests/hyperion/external_interaction/conftest.py +++ b/tests/unit_tests/hyperion/external_interaction/conftest.py @@ -31,13 +31,12 @@ def test_rotation_params(tmp_path): params = SingleRotationScan(**param_dict) params.x_start_um = 0 params.y_start_um = 0 - params.z_start_um = 0 params.exposure_time_s = 0.004 return params @pytest.fixture(params=[1050]) -def test_fgs_params(request, tmp_path): +def test_three_d_grid_params(request, tmp_path): assert request.param % 25 == 0, "Please use a multiple of 25 images" params = HyperionSpecifiedThreeDGridScan(**default_raw_gridscan_params(tmp_path)) params.demand_energy_ev = convert_angstrom_to_ev(1.0) @@ -45,8 +44,8 @@ def test_fgs_params(request, tmp_path): first_scan_img = (request.param // 10) * 6 second_scan_img = (request.param // 10) * 4 params.x_steps = 5 - params.y_steps = first_scan_img // 5 - params.z_steps = second_scan_img // 5 + params.y_steps[0] = first_scan_img // 5 + params.y_steps[1] = second_scan_img // 5 params.storage_directory = ( os.path.dirname(os.path.realpath(__file__)) + "/test_data" ) diff --git a/tests/unit_tests/hyperion/external_interaction/nexus/test_write_nexus.py b/tests/unit_tests/hyperion/external_interaction/nexus/test_write_nexus.py index 248c001f8c..694f248fc3 100644 --- a/tests/unit_tests/hyperion/external_interaction/nexus/test_write_nexus.py +++ b/tests/unit_tests/hyperion/external_interaction/nexus/test_write_nexus.py @@ -16,6 +16,9 @@ ZebraGridScanParamsThreeD, ) +from mx_bluesky.common.external_interaction.callbacks.xray_centre.nexus_callback import ( + _create_writers_from_params, +) from mx_bluesky.common.external_interaction.nexus.nexus_utils import ( AxisDirection, create_beam_and_attenuator_parameters, @@ -38,43 +41,14 @@ def assert_end_data_correct(nexus_writer: NexusWriter): assert "end_time_estimated" in entry -def create_nexus_writer(parameters: HyperionSpecifiedThreeDGridScan, writer_num): - d_size = parameters.detector_params.detector_size_constants.det_size_pixels - n_img = ( - parameters.scan_indices[1] - if writer_num == 1 - else parameters.num_images - parameters.scan_indices[1] - ) - points = ( - parameters.scan_points_first_grid - if writer_num == 1 - else parameters.scan_points_second_grid - ) - data_shape = (n_img, d_size.width, d_size.height) - run_number = parameters.detector_params.run_number + writer_num - 1 - vds_start = 0 if writer_num == 1 else parameters.scan_indices[1] - omega_start = ( - parameters.grid1_omega_deg if writer_num == 1 else parameters.grid2_omega_deg - ) - nexus_writer = NexusWriter( - parameters, - data_shape, - scan_points=points, - run_number=run_number, - vds_start_index=vds_start, - omega_start_deg=omega_start, - ) - nexus_writer.beam, nexus_writer.attenuator = create_beam_and_attenuator_parameters( - 20, TEST_FLUX, 0.5 - ) - return nexus_writer - - @contextmanager def create_nexus_writers(parameters: HyperionSpecifiedThreeDGridScan): - writers = [create_nexus_writer(parameters, i) for i in [1, 2]] - writers[1].start_index = parameters.scan_indices[1] + writers = _create_writers_from_params(parameters) try: + for writer in writers: + writer.beam, writer.attenuator = create_beam_and_attenuator_parameters( + 20, TEST_FLUX, 0.5 + ) yield writers finally: for writer in writers: @@ -84,8 +58,8 @@ def create_nexus_writers(parameters: HyperionSpecifiedThreeDGridScan): @pytest.fixture -def dummy_nexus_writers(test_fgs_params: HyperionSpecifiedThreeDGridScan): - with create_nexus_writers(test_fgs_params) as ( +def dummy_nexus_writers(test_three_d_grid_params: HyperionSpecifiedThreeDGridScan): + with create_nexus_writers(test_three_d_grid_params) as ( nexus_writer_1, nexus_writer_2, ): @@ -94,13 +68,13 @@ def dummy_nexus_writers(test_fgs_params: HyperionSpecifiedThreeDGridScan): @pytest.fixture def dummy_nexus_writers_with_more_images( - test_fgs_params: HyperionSpecifiedThreeDGridScan, + test_three_d_grid_params: HyperionSpecifiedThreeDGridScan, ): x, y, z = 45, 35, 25 - test_fgs_params.x_steps = x - test_fgs_params.y_steps = y - test_fgs_params.z_steps = z - with create_nexus_writers(test_fgs_params) as ( + test_three_d_grid_params.x_steps = x + test_three_d_grid_params.y_steps[0] = y + test_three_d_grid_params.y_steps[1] = z + with create_nexus_writers(test_three_d_grid_params) as ( nexus_writer_1, nexus_writer_2, ): @@ -108,14 +82,16 @@ def dummy_nexus_writers_with_more_images( @pytest.fixture -def single_dummy_file(test_fgs_params: HyperionSpecifiedThreeDGridScan): - test_fgs_params.use_roi_mode = True - d_size = test_fgs_params.detector_params.detector_size_constants.det_size_pixels - data_shape = (test_fgs_params.scan_indices[1], d_size.width, d_size.height) +def single_dummy_file(test_three_d_grid_params: HyperionSpecifiedThreeDGridScan): + test_three_d_grid_params.use_roi_mode = True + d_size = ( + test_three_d_grid_params.detector_params.detector_size_constants.det_size_pixels + ) + data_shape = (test_three_d_grid_params.scan_indices[1], d_size.width, d_size.height) nexus_writer = NexusWriter( - test_fgs_params, + test_three_d_grid_params, data_shape, - scan_points=test_fgs_params.scan_points_first_grid, + scan_points=test_three_d_grid_params.scan_points[0], run_number=1, ) yield nexus_writer @@ -125,12 +101,12 @@ def single_dummy_file(test_fgs_params: HyperionSpecifiedThreeDGridScan): @pytest.mark.parametrize( - "test_fgs_params, expected_num_of_files", + "test_three_d_grid_params, expected_num_of_files", [(2550, 3), (4000, 4), (8975, 9)], - indirect=["test_fgs_params"], + indirect=["test_three_d_grid_params"], ) def test_given_number_of_images_above_1000_then_expected_datafiles_used( - test_fgs_params: HyperionSpecifiedThreeDGridScan, + test_three_d_grid_params: HyperionSpecifiedThreeDGridScan, expected_num_of_files: Literal[3, 4, 9], single_dummy_file: NexusWriter, ): @@ -148,11 +124,13 @@ def test_given_number_of_images_above_1000_then_expected_datafiles_used( def test_given_dummy_data_then_datafile_written_correctly( - test_fgs_params: HyperionSpecifiedThreeDGridScan, + test_three_d_grid_params: HyperionSpecifiedThreeDGridScan, dummy_nexus_writers: tuple[NexusWriter, NexusWriter], ): nexus_writer_1, nexus_writer_2 = dummy_nexus_writers - grid_scan_params: ZebraGridScanParamsThreeD = test_fgs_params.fast_gridscan_params + grid_scan_params: ZebraGridScanParamsThreeD = ( + test_three_d_grid_params.fast_gridscan_params + ) nexus_writer_1.create_nexus_file(np.uint16) for filename in [nexus_writer_1.nexus_file, nexus_writer_1.master_file]: @@ -209,6 +187,7 @@ def test_given_dummy_data_then_datafile_written_correctly( nexus_writer_2.create_nexus_file(np.uint16) + # Nexus writer 2 has data for a rotated grid for filename in [nexus_writer_2.nexus_file, nexus_writer_2.master_file]: with h5py.File(filename, "r") as written_nexus_file: assert isinstance( @@ -217,12 +196,12 @@ def test_given_dummy_data_then_datafile_written_correctly( assert_x_data_stride_correct( data_path, grid_scan_params, grid_scan_params.z_steps ) - assert isinstance(sam_z := data_path["sam_z"], h5py.Dataset) + assert isinstance(sam_y := data_path["sam_y"], h5py.Dataset) assert_varying_axis_stride_correct( - sam_z[:], grid_scan_params, grid_scan_params.z_axis + sam_y[:], grid_scan_params, grid_scan_params.z_axis ) assert_axis_data_fixed( - written_nexus_file, "y", grid_scan_params.y2_start_mm + written_nexus_file, "z", grid_scan_params.y2_start_mm ) assert isinstance( flux := written_nexus_file["/entry/instrument/beam/total_flux"], @@ -324,11 +303,12 @@ def assert_contains_external_link(data_path, entry_name, file_name): def test_nexus_writer_files_are_formatted_as_expected( - test_fgs_params: HyperionSpecifiedThreeDGridScan, single_dummy_file: NexusWriter + test_three_d_grid_params: HyperionSpecifiedThreeDGridScan, + single_dummy_file: NexusWriter, ): for file in [single_dummy_file.nexus_file, single_dummy_file.master_file]: file_name = os.path.basename(file.name) - expected_file_name_prefix = test_fgs_params.file_name + "_1" + expected_file_name_prefix = test_three_d_grid_params.file_name + "_1" assert file_name.startswith(expected_file_name_prefix) @@ -344,11 +324,15 @@ def test_nexus_writer_writes_width_and_height_correctly(single_dummy_file: Nexus @patch.dict(os.environ, {"BEAMLINE": "i03"}) def test_nexus_writer_writes_beamline_name_correctly( - test_fgs_params: HyperionSpecifiedThreeDGridScan, + test_three_d_grid_params: HyperionSpecifiedThreeDGridScan, ): - d_size = test_fgs_params.detector_params.detector_size_constants.det_size_pixels - data_shape = (test_fgs_params.num_images, d_size.width, d_size.height) - nexus_writer = NexusWriter(test_fgs_params, data_shape, test_fgs_params.scan_points) + d_size = ( + test_three_d_grid_params.detector_params.detector_size_constants.det_size_pixels + ) + data_shape = (test_three_d_grid_params.num_images, d_size.width, d_size.height) + nexus_writer = NexusWriter( + test_three_d_grid_params, data_shape, test_three_d_grid_params.scan_points[0] + ) assert nexus_writer.source.beamline == "i03" @@ -414,10 +398,10 @@ def test_given_some_datafiles_outside_of_virtual_dataset_range_then_they_are_not def test_given_data_files_not_yet_written_when_nexus_files_created_then_nexus_files_still_written( - test_fgs_params: HyperionSpecifiedThreeDGridScan, + test_three_d_grid_params: HyperionSpecifiedThreeDGridScan, ): - test_fgs_params.file_name = "non_existant_file" - with create_nexus_writers(test_fgs_params) as ( + test_three_d_grid_params.file_name = "non_existant_file" + with create_nexus_writers(test_three_d_grid_params) as ( nexus_writer_1, nexus_writer_2, ): diff --git a/tests/unit_tests/hyperion/external_interaction/test_agamemnon.py b/tests/unit_tests/hyperion/external_interaction/test_agamemnon.py index bd3ea24824..eebb498ced 100644 --- a/tests/unit_tests/hyperion/external_interaction/test_agamemnon.py +++ b/tests/unit_tests/hyperion/external_interaction/test_agamemnon.py @@ -259,11 +259,11 @@ def test_create_parameters_from_agamemnon_contains_expected_robot_load_then_cent assert robot_load_params.sample_puck == 5 assert robot_load_params.sample_pin == 4 assert robot_load_params.demand_energy_ev == 12700.045934258673 - assert robot_load_params.omega_start_deg == 0.0 + assert robot_load_params.omega_starts_deg == [0.0, 90.0] assert robot_load_params.transmission_frac == 1.0 assert robot_load_params.tip_offset_um == 300.0 assert robot_load_params.grid_width_um == 600.0 - assert str(robot_load_params.parameter_model_version) == "5.3.0" + assert str(robot_load_params.parameter_model_version) == "6.0.0" assert ( robot_load_params.storage_directory == "/dls/i03/data/2025/mx34598-77/auto/CBLBA/CBLBA-x00242/xraycentring" @@ -302,7 +302,7 @@ def test_create_parameters_from_agamemnon_contains_expected_rotation_data( assert rotation_params.ispyb_experiment_type == "OSC" assert rotation_params.demand_energy_ev == 12700.045934258673 - assert str(rotation_params.parameter_model_version) == "5.3.0" + assert str(rotation_params.parameter_model_version) == "6.0.0" assert ( rotation_params.storage_directory == "/dls/i03/data/2025/mx34598-77/auto/CBLBA/CBLBA-x00242" diff --git a/tests/unit_tests/hyperion/parameters/test_parameter_model.py b/tests/unit_tests/hyperion/parameters/test_parameter_model.py index b6ba9e24b1..802f24b004 100644 --- a/tests/unit_tests/hyperion/parameters/test_parameter_model.py +++ b/tests/unit_tests/hyperion/parameters/test_parameter_model.py @@ -49,16 +49,13 @@ def minimal_3d_gridscan_params(): return { "sample_id": 123, "x_start_um": 0.123, - "y_start_um": 0.777, - "z_start_um": 0.05, - "parameter_model_version": "5.0.0", + "y_starts_um": [0.777, 2], + "z_starts_um": [0.05, 2], + "parameter_model_version": "6.0.0", "visit": "cm12345", "file_name": "test_file_name", - "y2_start_um": 2, - "z2_start_um": 2, "x_steps": 5, - "y_steps": 7, - "z_steps": 9, + "y_steps": [7, 9], "storage_directory": "/tmp/dls/i03/data/2024/cm31105-4/xraycentring/123456/", } @@ -66,22 +63,22 @@ def minimal_3d_gridscan_params(): def get_empty_grid_parameters() -> GridParamUpdate: return { "x_start_um": 1, - "y_start_um": 1, - "y2_start_um": 1, - "z_start_um": 1, - "z2_start_um": 1, + "y_starts_um": [1, 1], + "z_starts_um": [1, 1], "x_steps": 1, - "y_steps": 1, - "z_steps": 1, + "y_steps": [1, 1], "x_step_size_um": 1, - "y_step_size_um": 1, - "z_step_size_um": 1, + "y_step_sizes_um": [1, 1], } def test_minimal_3d_gridscan_params(minimal_3d_gridscan_params): test_params = HyperionSpecifiedThreeDGridScan(**minimal_3d_gridscan_params) - assert {"sam_x", "sam_y", "sam_z"} == set(test_params.scan_points.keys()) + assert all( + {"sam_x", "sam_y", "sam_z"} == set(scan_point.keys()) + for scan_point in test_params.scan_points + ) + assert test_params.scan_indices == [0, 35] assert test_params.num_images == (5 * 7 + 5 * 9) assert test_params.exposure_time_s == GridscanParamConstants.EXPOSURE_TIME_S @@ -107,10 +104,11 @@ def test_serialise_deserialise(minimal_3d_gridscan_params): "version, valid", [ ("4.3.0", False), - ("6.3.7", False), - ("5.0.0", True), - ("5.3.0", True), - ("5.3.7", True), + ("7.3.7", False), + ("5.0.0", False), + ("5.3.0", False), + ("5.3.7", False), + ("6.0.0", True), ], ) def test_param_version(minimal_3d_gridscan_params, version: str, valid: bool): @@ -124,7 +122,7 @@ def test_param_version(minimal_3d_gridscan_params, version: str, valid: bool): def test_robot_load_then_centre_params(): params = { - "parameter_model_version": "5.0.0", + "parameter_model_version": "6.0.0", "sample_id": 123456, "visit": "cm12345", "file_name": "file_name",