From 620968cd6ad7e832495fbfd1b82aa6634035ffa4 Mon Sep 17 00:00:00 2001 From: David Perl Date: Wed, 21 Aug 2024 12:30:00 +0100 Subject: [PATCH] Revert "Move the basic parameter model components to MXB (DiamondLightSource/hyperion#1537)" This reverts commit 942706a209d5779736b0ff3b1e4a8db3d56650e2. --- .vscode/hyperion-mx-bluesky.code-workspace | 23 --- setup.cfg | 2 +- .../experiment_plans/oav_snapshot_plan.py | 2 +- .../callbacks/rotation/ispyb_callback.py | 3 +- src/hyperion/parameters/components.py | 190 +++++++++++++++++- src/hyperion/parameters/gridscan.py | 19 +- src/hyperion/parameters/rotation.py | 14 +- .../test_ispyb_dev_connection.py | 6 +- .../test_oav_snapshot_plan.py | 2 +- .../callbacks/test_rotation_callbacks.py | 2 +- .../parameters/test_parameter_model.py | 6 +- 11 files changed, 210 insertions(+), 59 deletions(-) delete mode 100644 .vscode/hyperion-mx-bluesky.code-workspace diff --git a/.vscode/hyperion-mx-bluesky.code-workspace b/.vscode/hyperion-mx-bluesky.code-workspace deleted file mode 100644 index 3b46937de..000000000 --- a/.vscode/hyperion-mx-bluesky.code-workspace +++ /dev/null @@ -1,23 +0,0 @@ -{ - "folders": [ - { - "path": ".." - }, - { - "path": "../../dodal" - }, - { - "path": "../../mx-bluesky" - } - ], - "settings": { - "python.languageServer": "Pylance", - "terminal.integrated.gpuAcceleration": "off", - "esbonio.sphinx.confDir": "", - "[python]": { - "editor.defaultFormatter": "ms-python.black-formatter" - }, - "python.formatting.provider": "none", - "python.analysis.enablePytestExtra": false - } -} \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 6c0add12b..4c140471c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,6 +31,7 @@ install_requires = pyzmq scanspec scipy + semver # # These dependencies may be issued as pre-release versions and should have a pin constraint # as by default pip-install will not upgrade to a pre-release. @@ -40,7 +41,6 @@ install_requires = ophyd-async >= 0.3a5 bluesky >= 1.13.0a4 blueapi >= 0.4.3-rc1 - mx-bluesky @ git+https://github.com/DiamondLightSource/mx-bluesky.git dls-dodal @ git+https://github.com/DiamondLightSource/dodal.git [options.entry_points] diff --git a/src/hyperion/experiment_plans/oav_snapshot_plan.py b/src/hyperion/experiment_plans/oav_snapshot_plan.py index 1e3fdaea6..ac13b5ce7 100644 --- a/src/hyperion/experiment_plans/oav_snapshot_plan.py +++ b/src/hyperion/experiment_plans/oav_snapshot_plan.py @@ -8,9 +8,9 @@ from dodal.devices.oav.oav_detector import OAV from dodal.devices.oav.oav_parameters import OAVParameters from dodal.devices.smargon import Smargon -from mx_bluesky.parameters import WithSnapshot from hyperion.device_setup_plans.setup_oav import setup_general_oav_params +from hyperion.parameters.components import WithSnapshot from hyperion.parameters.constants import DocDescriptorNames OAV_SNAPSHOT_SETUP_GROUP = "oav_snapshot_setup" diff --git a/src/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py b/src/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py index cdfcb10cc..eeaa0372d 100644 --- a/src/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +++ b/src/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py @@ -3,8 +3,6 @@ from collections.abc import Sequence from typing import TYPE_CHECKING, Any, Callable, Optional -from mx_bluesky.parameters import IspybExperimentType - from hyperion.external_interaction.callbacks.common.ispyb_mapping import ( populate_data_collection_group, populate_remaining_data_collection_info, @@ -25,6 +23,7 @@ StoreInIspyb, ) from hyperion.log import ISPYB_LOGGER, set_dcgid_tag +from hyperion.parameters.components import IspybExperimentType from hyperion.parameters.constants import CONST from hyperion.parameters.rotation import RotationScan diff --git a/src/hyperion/parameters/components.py b/src/hyperion/parameters/components.py index 3863b61dc..e5b2d9503 100644 --- a/src/hyperion/parameters/components.py +++ b/src/hyperion/parameters/components.py @@ -3,15 +3,19 @@ import datetime import json from abc import abstractmethod +from enum import StrEnum from pathlib import Path -from typing import TypeVar +from typing import Sequence, SupportsInt, TypeVar +from dodal.devices.aperturescatterguard import AperturePositionGDANames from dodal.devices.detector import ( + DetectorParams, TriggerMode, ) -from mx_bluesky.parameters import DiffractionExperiment, ParameterVersion, WithSample from numpy.typing import NDArray -from pydantic import BaseModel, Extra, Field, validator +from pydantic import BaseModel, Extra, Field, root_validator, validator +from scanspec.core import AxesPoints +from semver import Version from hyperion.external_interaction.config_server import FeatureFlags from hyperion.external_interaction.ispyb.ispyb_dataclass import ( @@ -22,9 +26,85 @@ T = TypeVar("T") +class ParameterVersion(Version): + @classmethod + def _parse(cls, version): + if isinstance(version, cls): + return version + return cls.parse(version) + + @classmethod + def __get_validators__(cls): + """Return a list of validator methods for pydantic models.""" + yield cls._parse + + @classmethod + def __modify_schema__(cls, field_schema): + """Inject/mutate the pydantic field schema in-place.""" + field_schema.update(examples=["1.0.2", "2.15.3-alpha", "21.3.15-beta+12345"]) + + PARAMETER_VERSION = ParameterVersion.parse("5.0.0") +class RotationAxis(StrEnum): + OMEGA = "omega" + PHI = "phi" + CHI = "chi" + KAPPA = "kappa" + + +class XyzAxis(StrEnum): + X = "sam_x" + Y = "sam_y" + Z = "sam_z" + + +class IspybExperimentType(StrEnum): + # Enum values from ispyb column data type + SAD = "SAD" # at or slightly above the peak + SAD_INVERSE_BEAM = "SAD - Inverse Beam" + OSC = "OSC" # "native" (in the absence of a heavy atom) + COLLECT_MULTIWEDGE = ( + "Collect - Multiwedge" # "poorly determined" ~ EDNA complex strategy??? + ) + MAD = "MAD" + HELICAL = "Helical" + MULTI_POSITIONAL = "Multi-positional" + MESH = "Mesh" + BURN = "Burn" + MAD_INVERSE_BEAM = "MAD - Inverse Beam" + CHARACTERIZATION = "Characterization" + DEHYDRATION = "Dehydration" + TOMO = "tomo" + EXPERIMENT = "experiment" + EM = "EM" + PDF = "PDF" + PDF_BRAGG = "PDF+Bragg" + BRAGG = "Bragg" + SINGLE_PARTICLE = "single particle" + SERIAL_FIXED = "Serial Fixed" + SERIAL_JET = "Serial Jet" + STANDARD = "Standard" # Routine structure determination experiment + TIME_RESOLVED = "Time Resolved" # Investigate the change of a system over time + DLS_ANVIL_HP = "Diamond Anvil High Pressure" # HP sample environment pressure cell + CUSTOM = "Custom" # Special or non-standard data collection + XRF_MAP = "XRF map" + ENERGY_SCAN = "Energy scan" + XRF_SPECTRUM = "XRF spectrum" + XRF_MAP_XAS = "XRF map xas" + MESH_3D = "Mesh3D" + SCREENING = "Screening" + STILL = "Still" + SSX_CHIP = "SSX-Chip" + SSX_JET = "SSX-Jet" + + # Aliases for historic hyperion experiment type mapping + ROTATION = "SAD" + GRIDSCAN_2D = "mesh" + GRIDSCAN_3D = "Mesh3D" + + class HyperionParameters(BaseModel): class Config: arbitrary_types_allowed = True @@ -60,18 +140,48 @@ def from_json(cls, input: str | None, *, allow_extras: bool = False): return params -class HyperionDiffractionExperiment(DiffractionExperiment, HyperionParameters): +class WithSnapshot(BaseModel): + snapshot_directory: Path + snapshot_omegas_deg: list[float] | None + + @property + def take_snapshots(self) -> bool: + return bool(self.snapshot_omegas_deg) + + +class DiffractionExperiment(HyperionParameters, WithSnapshot): """For all experiments which use beam""" - beamline: str = Field(default=CONST.I03.BEAMLINE, regex=r"BL\d{2}[BIJS]") + visit: str = Field(min_length=1) + file_name: str = Field(pattern=r"[\w]{2}[\d]+-[\d]+") + exposure_time_s: float = Field(gt=0) + comment: str = Field(default="") + beamline: str = Field(default=CONST.I03.BEAMLINE, pattern=r"BL\d{2}[BIJS]") insertion_prefix: str = Field( - default=CONST.I03.INSERTION_PREFIX, regex=r"SR\d{2}[BIJS]" + default=CONST.I03.INSERTION_PREFIX, pattern=r"SR\d{2}[BIJS]" ) det_dist_to_beam_converter_path: str = Field( default=CONST.PARAM.DETECTOR.BEAM_XY_LUT_PATH ) zocalo_environment: str = Field(default=CONST.ZOCALO_ENV) trigger_mode: TriggerMode = Field(default=TriggerMode.FREE_RUN) + detector_distance_mm: float | None = Field(default=None, gt=0) + demand_energy_ev: float | None = Field(default=None, gt=0) + run_number: int | None = Field(default=None, ge=0) + selected_aperture: AperturePositionGDANames | None = Field(default=None) + transmission_frac: float = Field(default=0.1) + ispyb_experiment_type: IspybExperimentType + storage_directory: str + + @root_validator(pre=True) + def validate_snapshot_directory(cls, values): + snapshot_dir = values.get( + "snapshot_directory", Path(values["storage_directory"], "snapshots") + ) + values["snapshot_directory"] = ( + snapshot_dir if isinstance(snapshot_dir, Path) else Path(snapshot_dir) + ) + return values @property def visit_directory(self) -> Path: @@ -79,13 +189,79 @@ def visit_directory(self) -> Path: Path(CONST.I03.BASE_DATA_DIR) / str(datetime.date.today().year) / self.visit ) + @property + def num_images(self) -> int: + return 0 + + @property + @abstractmethod + def detector_params(self) -> DetectorParams: ... + @property @abstractmethod def ispyb_params(self) -> IspybParams: # Soon to remove ... -class DiffractionExperimentWithSample(HyperionDiffractionExperiment, WithSample): ... +class WithScan(BaseModel): + """For experiments where the scan is known""" + + @property + @abstractmethod + def scan_points(self) -> AxesPoints: ... + + @property + @abstractmethod + def num_images(self) -> int: ... + + +class SplitScan(BaseModel): + @property + @abstractmethod + def scan_indices(self) -> Sequence[SupportsInt]: + """Should return the first index of each scan (i.e. for each nexus file)""" + ... + + +class WithSample(BaseModel): + sample_id: int + sample_puck: int | None = None + sample_pin: int | None = None + + +class DiffractionExperimentWithSample(DiffractionExperiment, WithSample): ... + + +class WithOavCentring(BaseModel): + oav_centring_file: str = Field(default=CONST.I03.OAV_CENTRING_FILE) + + +class OptionalXyzStarts(BaseModel): + x_start_um: float | None = None + y_start_um: float | None = None + z_start_um: float | None = None + + +class XyzStarts(BaseModel): + x_start_um: float + y_start_um: float + z_start_um: float + + def _start_for_axis(self, axis: XyzAxis) -> float: + match axis: + case XyzAxis.X: + return self.x_start_um + case XyzAxis.Y: + return self.y_start_um + case XyzAxis.Z: + return self.z_start_um + + +class OptionalGonioAngleStarts(BaseModel): + omega_start_deg: float | None = None + phi_start_deg: float | None = None + chi_start_deg: float | None = None + kappa_start_deg: float | None = None class TemporaryIspybExtras(BaseModel): diff --git a/src/hyperion/parameters/gridscan.py b/src/hyperion/parameters/gridscan.py index 5b2c97ba0..778b01c90 100644 --- a/src/hyperion/parameters/gridscan.py +++ b/src/hyperion/parameters/gridscan.py @@ -11,14 +11,6 @@ PandAGridScanParams, ZebraGridScanParams, ) -from mx_bluesky.parameters import ( - IspybExperimentType, - OptionalGonioAngleStarts, - SplitScan, - WithOavCentring, - WithScan, - XyzStarts, -) from pydantic import Field, PrivateAttr from scanspec.core import Path as ScanPath from scanspec.specs import Line, Static @@ -26,14 +18,21 @@ from hyperion.external_interaction.ispyb.ispyb_dataclass import ( GridscanIspybParams, ) -from hyperion.parameters.components import DiffractionExperimentWithSample +from hyperion.parameters.components import ( + DiffractionExperimentWithSample, + IspybExperimentType, + OptionalGonioAngleStarts, + SplitScan, + WithOavCentring, + WithScan, + XyzStarts, +) from hyperion.parameters.constants import CONST, I03Constants class GridCommon( DiffractionExperimentWithSample, OptionalGonioAngleStarts, WithOavCentring ): - oav_centring_file: str = Field(default=CONST.I03.OAV_CENTRING_FILE) grid_width_um: float = Field(default=CONST.PARAM.GRIDSCAN.WIDTH_UM) exposure_time_s: float = Field(default=CONST.PARAM.GRIDSCAN.EXPOSURE_TIME_S) use_roi_mode: bool = Field(default=CONST.PARAM.GRIDSCAN.USE_ROI) diff --git a/src/hyperion/parameters/rotation.py b/src/hyperion/parameters/rotation.py index 01d8c6eae..ce59b2988 100644 --- a/src/hyperion/parameters/rotation.py +++ b/src/hyperion/parameters/rotation.py @@ -13,14 +13,6 @@ from dodal.devices.zebra import ( RotationDirection, ) -from mx_bluesky.parameters import ( - IspybExperimentType, - OptionalGonioAngleStarts, - OptionalXyzStarts, - RotationAxis, - SplitScan, - WithScan, -) from pydantic import Field, root_validator from scanspec.core import AxesPoints from scanspec.core import Path as ScanPath @@ -29,7 +21,13 @@ from hyperion.external_interaction.ispyb.ispyb_dataclass import RotationIspybParams from hyperion.parameters.components import ( DiffractionExperimentWithSample, + IspybExperimentType, + OptionalGonioAngleStarts, + OptionalXyzStarts, + RotationAxis, + SplitScan, TemporaryIspybExtras, + WithScan, ) from hyperion.parameters.constants import CONST, I03Constants diff --git a/tests/system_tests/external_interaction/test_ispyb_dev_connection.py b/tests/system_tests/external_interaction/test_ispyb_dev_connection.py index 79a2ae33c..97ab98451 100644 --- a/tests/system_tests/external_interaction/test_ispyb_dev_connection.py +++ b/tests/system_tests/external_interaction/test_ispyb_dev_connection.py @@ -12,7 +12,6 @@ from bluesky.run_engine import RunEngine from dodal.devices.oav.oav_parameters import OAVParameters from dodal.devices.synchrotron import SynchrotronMode -from mx_bluesky.parameters import IspybExperimentType from ophyd.sim import NullStatus from ophyd_async.core import AsyncStatus, set_mock_value @@ -49,6 +48,7 @@ IspybIds, StoreInIspyb, ) +from hyperion.parameters.components import IspybExperimentType from hyperion.parameters.constants import CONST from hyperion.parameters.gridscan import GridScanWithEdgeDetect, ThreeDGridScan from hyperion.parameters.rotation import RotationScan @@ -446,7 +446,9 @@ def composite_for_rotation_scan(fake_create_rotation_devices: RotationScanCompos fake_create_rotation_devices.dcm.energy_in_kev.user_readback, energy_ev / 1000, # pyright: ignore ) - set_mock_value(fake_create_rotation_devices.undulator.current_gap, 1.12) # pyright: ignore + set_mock_value( + fake_create_rotation_devices.undulator.current_gap, 1.12 + ) # pyright: ignore set_mock_value( fake_create_rotation_devices.synchrotron.synchrotron_mode, SynchrotronMode.USER, diff --git a/tests/unit_tests/experiment_plans/test_oav_snapshot_plan.py b/tests/unit_tests/experiment_plans/test_oav_snapshot_plan.py index dd0f22ac9..f01287444 100644 --- a/tests/unit_tests/experiment_plans/test_oav_snapshot_plan.py +++ b/tests/unit_tests/experiment_plans/test_oav_snapshot_plan.py @@ -10,13 +10,13 @@ from dodal.devices.oav.oav_parameters import OAVParameters from dodal.devices.oav.utils import ColorMode from dodal.devices.smargon import Smargon -from mx_bluesky.parameters import WithSnapshot from hyperion.experiment_plans.oav_snapshot_plan import ( OAV_SNAPSHOT_SETUP_SHOT, OavSnapshotComposite, oav_snapshot_plan, ) +from hyperion.parameters.components import WithSnapshot from hyperion.parameters.constants import DocDescriptorNames from ...conftest import raw_params_from_file diff --git a/tests/unit_tests/external_interaction/callbacks/test_rotation_callbacks.py b/tests/unit_tests/external_interaction/callbacks/test_rotation_callbacks.py index 196cb0791..56f9025e8 100644 --- a/tests/unit_tests/external_interaction/callbacks/test_rotation_callbacks.py +++ b/tests/unit_tests/external_interaction/callbacks/test_rotation_callbacks.py @@ -11,7 +11,6 @@ from dodal.devices.eiger import EigerDetector from dodal.devices.flux import Flux from event_model import RunStart -from mx_bluesky.parameters import IspybExperimentType from ophyd.sim import make_fake_device from ophyd_async.core import DeviceCollector, set_mock_value @@ -39,6 +38,7 @@ IspybIds, StoreInIspyb, ) +from hyperion.parameters.components import IspybExperimentType from hyperion.parameters.constants import CONST from hyperion.parameters.rotation import RotationScan diff --git a/tests/unit_tests/parameters/test_parameter_model.py b/tests/unit_tests/parameters/test_parameter_model.py index a4ea14c5e..b5a60869a 100644 --- a/tests/unit_tests/parameters/test_parameter_model.py +++ b/tests/unit_tests/parameters/test_parameter_model.py @@ -22,7 +22,7 @@ def minimal_3d_gridscan_params(): "y_start_um": 0.777, "z_start_um": 0.05, "parameter_model_version": "5.0.0", - "visit": "cm12345-12", + "visit": "cm12345", "file_name": "test_file_name", "y2_start_um": 2, "z2_start_um": 2, @@ -53,7 +53,7 @@ def test_serialise_deserialise(minimal_3d_gridscan_params): serialised = json.loads(test_params.json()) deserialised = ThreeDGridScan(**serialised) assert deserialised.demand_energy_ev is None - assert deserialised.visit == "cm12345-12" + assert deserialised.visit == "cm12345" assert deserialised.x_start_um == 0.123 @@ -76,7 +76,7 @@ def test_robot_load_then_centre_params(): params = { "parameter_model_version": "5.0.0", "sample_id": 123456, - "visit": "cm12345-12", + "visit": "cm12345", "file_name": "file_name", "storage_directory": "/tmp/dls/i03/data/2024/cm31105-4/xraycentring/123456/", }