mx-bluesky 1.2.0__py3-none-any.whl → 1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mx_bluesky/__init__.py +8 -3
- mx_bluesky/__main__.py +12 -7
- mx_bluesky/_version.py +2 -2
- mx_bluesky/beamlines/i04/callbacks/murko_callback.py +14 -4
- mx_bluesky/beamlines/i04/thawing_plan.py +48 -10
- mx_bluesky/beamlines/i24/serial/__init__.py +3 -0
- mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +68 -90
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +1 -1
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +104 -126
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +139 -162
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +25 -36
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +24 -34
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +14 -11
- mx_bluesky/beamlines/i24/serial/log.py +58 -49
- mx_bluesky/beamlines/i24/serial/parameters/fixed_target/cs/cs_maker.json +3 -3
- mx_bluesky/beamlines/i24/serial/run_extruder.sh +30 -5
- mx_bluesky/beamlines/i24/serial/run_fixed_target.sh +31 -7
- mx_bluesky/beamlines/i24/serial/run_serial.py +24 -8
- mx_bluesky/beamlines/i24/serial/setup_beamline/ca.py +0 -2
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +1 -1
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +8 -18
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +2 -2
- mx_bluesky/common/__init__.py +0 -0
- mx_bluesky/common/device_setup_plans/read_hardware_for_setup.py +14 -0
- mx_bluesky/common/parameters/components.py +221 -0
- mx_bluesky/common/parameters/constants.py +133 -0
- mx_bluesky/common/plans/__init__.py +1 -0
- mx_bluesky/common/plans/do_fgs.py +121 -0
- mx_bluesky/common/utils/log.py +116 -0
- mx_bluesky/{hyperion → common/utils}/tracing.py +2 -2
- mx_bluesky/hyperion/__main__.py +11 -9
- mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +31 -26
- mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +6 -12
- mx_bluesky/hyperion/device_setup_plans/setup_oav.py +6 -12
- mx_bluesky/hyperion/device_setup_plans/setup_panda.py +1 -2
- mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +48 -17
- mx_bluesky/hyperion/device_setup_plans/smargon.py +6 -6
- mx_bluesky/hyperion/device_setup_plans/utils.py +2 -2
- mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +4 -4
- mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +25 -83
- mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +7 -5
- mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +19 -18
- mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +8 -5
- mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +4 -4
- mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +17 -17
- mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +15 -11
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +6 -3
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +6 -4
- mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +3 -3
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +1 -2
- mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +1 -1
- mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +18 -13
- mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +29 -12
- mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +1 -1
- mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +1 -1
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +4 -3
- mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +23 -18
- mx_bluesky/hyperion/external_interaction/config_server.py +22 -10
- mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +1 -1
- mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +2 -2
- mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +1 -1
- mx_bluesky/hyperion/log.py +0 -84
- mx_bluesky/hyperion/parameters/components.py +1 -243
- mx_bluesky/hyperion/parameters/constants.py +22 -118
- mx_bluesky/hyperion/parameters/gridscan.py +13 -9
- mx_bluesky/hyperion/parameters/load_centre_collect.py +3 -3
- mx_bluesky/hyperion/parameters/robot_load.py +3 -3
- mx_bluesky/hyperion/parameters/rotation.py +9 -5
- mx_bluesky/hyperion/utils/utils.py +17 -0
- mx_bluesky/hyperion/utils/validation.py +5 -6
- {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.0.dist-info}/METADATA +4 -2
- {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.0.dist-info}/RECORD +76 -70
- {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.0.dist-info}/WHEEL +1 -1
- mx_bluesky/example.py +0 -19
- {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.0.dist-info}/LICENSE +0 -0
- {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.0.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.0.dist-info}/top_level.txt +0 -0
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import argparse
|
|
2
2
|
import subprocess
|
|
3
3
|
from os import environ
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
from mx_bluesky.beamlines.i24.serial.log import SSX_LOGGER
|
|
7
|
+
from mx_bluesky.beamlines.i24.serial.parameters import SSXType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _parse_input(expt: SSXType):
|
|
11
|
+
parser = argparse.ArgumentParser(description=f"Run a {expt} collection.")
|
|
12
|
+
parser.add_argument("-t", "--test", action="store_true", help="Run in test mode.")
|
|
13
|
+
args = parser.parse_args()
|
|
14
|
+
return args
|
|
7
15
|
|
|
8
16
|
|
|
9
17
|
def get_location(default: str = "dev") -> str:
|
|
@@ -19,18 +27,26 @@ def _get_file_path() -> Path:
|
|
|
19
27
|
|
|
20
28
|
|
|
21
29
|
def run_extruder():
|
|
30
|
+
args = _parse_input(SSXType.EXTRUDER)
|
|
22
31
|
loc = get_location()
|
|
23
|
-
|
|
32
|
+
SSX_LOGGER.info(f"Running on {loc}.")
|
|
24
33
|
edm_path = get_edm_path()
|
|
25
34
|
filepath = _get_file_path()
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
test_mode = "--test" if args.test else ""
|
|
36
|
+
SSX_LOGGER.debug(f"Running {filepath}/run_extruder.sh")
|
|
37
|
+
subprocess.run(
|
|
38
|
+
["bash", filepath / "run_extruder.sh", edm_path.as_posix(), test_mode]
|
|
39
|
+
)
|
|
28
40
|
|
|
29
41
|
|
|
30
42
|
def run_fixed_target():
|
|
43
|
+
args = _parse_input(SSXType.FIXED)
|
|
31
44
|
loc = get_location()
|
|
32
|
-
|
|
45
|
+
SSX_LOGGER.info(f"Running on {loc}.")
|
|
33
46
|
edm_path = get_edm_path()
|
|
34
47
|
filepath = _get_file_path()
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
test_mode = "--test" if args.test else ""
|
|
49
|
+
SSX_LOGGER.debug(f"Running {filepath}/run_fixed_target.sh")
|
|
50
|
+
subprocess.run(
|
|
51
|
+
["bash", filepath / "run_fixed_target.sh", edm_path.as_posix(), test_mode]
|
|
52
|
+
)
|
|
@@ -22,8 +22,6 @@ def caget(pv):
|
|
|
22
22
|
a = Popen(["caget", pv], stdout=PIPE, stderr=PIPE)
|
|
23
23
|
a_stdout, a_stderr = a.communicate()
|
|
24
24
|
val = a_stdout.split()[1].decode("ascii")
|
|
25
|
-
# val = evaluate(val)
|
|
26
|
-
# val = val.decode('ascii')
|
|
27
25
|
except Exception:
|
|
28
26
|
print("Exception in ca_py3.py caget, maybe this PV doesnt exist:", pv)
|
|
29
27
|
pass
|
|
@@ -42,7 +42,7 @@ def move_detector_stage_to_position_plan(
|
|
|
42
42
|
logger.debug(
|
|
43
43
|
f"Waiting for detector move. Detector distance: {detector_distance} mm."
|
|
44
44
|
)
|
|
45
|
-
yield from bps.mv(detector_stage.z, detector_distance)
|
|
45
|
+
yield from bps.mv(detector_stage.z, detector_distance) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def modechange(action):
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
Utilities for defining the detector in use, and moving the stage.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import logging
|
|
6
|
-
import time
|
|
7
5
|
from collections.abc import Generator
|
|
8
6
|
from enum import IntEnum
|
|
9
7
|
|
|
@@ -13,7 +11,7 @@ from bluesky.utils import Msg
|
|
|
13
11
|
from dodal.common import inject
|
|
14
12
|
from dodal.devices.i24.i24_detector_motion import DetectorMotion
|
|
15
13
|
|
|
16
|
-
from mx_bluesky.beamlines.i24.serial import
|
|
14
|
+
from mx_bluesky.beamlines.i24.serial.log import SSX_LOGGER
|
|
17
15
|
from mx_bluesky.beamlines.i24.serial.parameters import SSXType
|
|
18
16
|
from mx_bluesky.beamlines.i24.serial.setup_beamline import pv
|
|
19
17
|
from mx_bluesky.beamlines.i24.serial.setup_beamline.ca import caget
|
|
@@ -23,8 +21,6 @@ from mx_bluesky.beamlines.i24.serial.setup_beamline.pv_abstract import (
|
|
|
23
21
|
Pilatus,
|
|
24
22
|
)
|
|
25
23
|
|
|
26
|
-
logger = logging.getLogger("I24ssx.sup_det")
|
|
27
|
-
|
|
28
24
|
EXPT_TYPE_DETECTOR_PVS = {
|
|
29
25
|
SSXType.FIXED: pv.me14e_gp101,
|
|
30
26
|
SSXType.EXTRUDER: pv.ioc12_gp15,
|
|
@@ -39,11 +35,6 @@ class DetRequest(IntEnum):
|
|
|
39
35
|
return self.name
|
|
40
36
|
|
|
41
37
|
|
|
42
|
-
def setup_logging():
|
|
43
|
-
logfile = time.strftime("SSXdetectorOps_%d%B%y.log").lower()
|
|
44
|
-
log.config(logfile)
|
|
45
|
-
|
|
46
|
-
|
|
47
38
|
class UnknownDetectorType(Exception):
|
|
48
39
|
pass
|
|
49
40
|
|
|
@@ -53,19 +44,19 @@ def get_detector_type(detector_stage: DetectorMotion) -> Generator[Msg, None, De
|
|
|
53
44
|
# DetectorMotion should also be used for this.
|
|
54
45
|
# This should be part of https://github.com/DiamondLightSource/mx_bluesky/issues/51
|
|
55
46
|
if float(det_y) < Eiger.det_y_threshold:
|
|
56
|
-
|
|
47
|
+
SSX_LOGGER.info("Eiger detector in use.")
|
|
57
48
|
return Eiger()
|
|
58
49
|
elif float(det_y) > Pilatus.det_y_threshold:
|
|
59
|
-
|
|
50
|
+
SSX_LOGGER.info("Pilatus detector in use.")
|
|
60
51
|
return Pilatus()
|
|
61
52
|
else:
|
|
62
|
-
|
|
53
|
+
SSX_LOGGER.error("Detector not found.")
|
|
63
54
|
raise UnknownDetectorType("Detector not found.")
|
|
64
55
|
|
|
65
56
|
|
|
66
57
|
def _move_detector_stage(detector_stage: DetectorMotion, target: float) -> MsgGenerator:
|
|
67
|
-
|
|
68
|
-
yield from bps.mv(detector_stage.y, target)
|
|
58
|
+
SSX_LOGGER.info(f"Moving detector stage to target position: {target}.")
|
|
59
|
+
yield from bps.mv(detector_stage.y, target) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
|
|
69
60
|
|
|
70
61
|
|
|
71
62
|
# Workaround in case the PV value has been set to the detector name
|
|
@@ -94,14 +85,13 @@ def _get_requested_detector(det_type_pv: str) -> str:
|
|
|
94
85
|
def setup_detector_stage(
|
|
95
86
|
expt_type: SSXType, detector_stage: DetectorMotion = inject("detector_motion")
|
|
96
87
|
) -> MsgGenerator:
|
|
97
|
-
setup_logging()
|
|
98
88
|
# Grab the correct PV depending on experiment
|
|
99
89
|
# Its value is set with MUX on edm screen
|
|
100
90
|
det_type_pv = EXPT_TYPE_DETECTOR_PVS[expt_type]
|
|
101
91
|
requested_detector = _get_requested_detector(det_type_pv)
|
|
102
|
-
|
|
92
|
+
SSX_LOGGER.info(f"Requested detector: {requested_detector}.")
|
|
103
93
|
det_y_target = (
|
|
104
94
|
Eiger.det_y_target if requested_detector == "eiger" else Pilatus.det_y_target
|
|
105
95
|
)
|
|
106
96
|
yield from _move_detector_stage(detector_stage, det_y_target)
|
|
107
|
-
|
|
97
|
+
SSX_LOGGER.info("Detector setup done.")
|
|
@@ -70,12 +70,12 @@ def get_zebra_settings_for_extruder(
|
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
def arm_zebra(zebra: Zebra):
|
|
73
|
-
yield from bps.abs_set(zebra.pc.arm, ArmDemand.ARM, wait=True)
|
|
73
|
+
yield from bps.abs_set(zebra.pc.arm, ArmDemand.ARM, wait=True) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
|
|
74
74
|
logger.info("Zebra armed.")
|
|
75
75
|
|
|
76
76
|
|
|
77
77
|
def disarm_zebra(zebra: Zebra):
|
|
78
|
-
yield from bps.abs_set(zebra.pc.arm, ArmDemand.DISARM, wait=True)
|
|
78
|
+
yield from bps.abs_set(zebra.pc.arm, ArmDemand.DISARM, wait=True) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
|
|
79
79
|
logger.info("Zebra disarmed.")
|
|
80
80
|
|
|
81
81
|
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import bluesky.plan_stubs as bps
|
|
2
|
+
from dodal.devices.eiger import EigerDetector
|
|
3
|
+
|
|
4
|
+
from mx_bluesky.common.parameters.constants import DocDescriptorNames
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def read_hardware_for_zocalo(detector: EigerDetector):
|
|
8
|
+
""" "
|
|
9
|
+
If the RunEngine is subscribed to the ZocaloCallback, this plan will also trigger zocalo.
|
|
10
|
+
A bluesky run must be open to use this plan
|
|
11
|
+
"""
|
|
12
|
+
yield from bps.create(name=DocDescriptorNames.ZOCALO_HW_READ)
|
|
13
|
+
yield from bps.read(detector.odin.file_writer.id) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
|
|
14
|
+
yield from bps.save()
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from abc import abstractmethod
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import SupportsInt
|
|
7
|
+
|
|
8
|
+
from dodal.devices.aperturescatterguard import ApertureValue
|
|
9
|
+
from dodal.devices.detector import (
|
|
10
|
+
DetectorParams,
|
|
11
|
+
TriggerMode,
|
|
12
|
+
)
|
|
13
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
14
|
+
from pydantic_extra_types.semantic_version import SemanticVersion
|
|
15
|
+
from scanspec.core import AxesPoints
|
|
16
|
+
from semver import Version
|
|
17
|
+
|
|
18
|
+
from mx_bluesky.common.parameters.constants import TEST_MODE, DetectorParamConstants
|
|
19
|
+
|
|
20
|
+
PARAMETER_VERSION = Version.parse("5.2.0")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RotationAxis(StrEnum):
|
|
24
|
+
OMEGA = "omega"
|
|
25
|
+
PHI = "phi"
|
|
26
|
+
CHI = "chi"
|
|
27
|
+
KAPPA = "kappa"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class XyzAxis(StrEnum):
|
|
31
|
+
X = "sam_x"
|
|
32
|
+
Y = "sam_y"
|
|
33
|
+
Z = "sam_z"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class IspybExperimentType(StrEnum):
|
|
37
|
+
# Enum values from ispyb column data type
|
|
38
|
+
SAD = "SAD" # at or slightly above the peak
|
|
39
|
+
SAD_INVERSE_BEAM = "SAD - Inverse Beam"
|
|
40
|
+
OSC = "OSC" # "native" (in the absence of a heavy atom)
|
|
41
|
+
COLLECT_MULTIWEDGE = (
|
|
42
|
+
"Collect - Multiwedge" # "poorly determined" ~ EDNA complex strategy???
|
|
43
|
+
)
|
|
44
|
+
MAD = "MAD"
|
|
45
|
+
HELICAL = "Helical"
|
|
46
|
+
MULTI_POSITIONAL = "Multi-positional"
|
|
47
|
+
MESH = "Mesh"
|
|
48
|
+
BURN = "Burn"
|
|
49
|
+
MAD_INVERSE_BEAM = "MAD - Inverse Beam"
|
|
50
|
+
CHARACTERIZATION = "Characterization"
|
|
51
|
+
DEHYDRATION = "Dehydration"
|
|
52
|
+
TOMO = "tomo"
|
|
53
|
+
EXPERIMENT = "experiment"
|
|
54
|
+
EM = "EM"
|
|
55
|
+
PDF = "PDF"
|
|
56
|
+
PDF_BRAGG = "PDF+Bragg"
|
|
57
|
+
BRAGG = "Bragg"
|
|
58
|
+
SINGLE_PARTICLE = "single particle"
|
|
59
|
+
SERIAL_FIXED = "Serial Fixed"
|
|
60
|
+
SERIAL_JET = "Serial Jet"
|
|
61
|
+
STANDARD = "Standard" # Routine structure determination experiment
|
|
62
|
+
TIME_RESOLVED = "Time Resolved" # Investigate the change of a system over time
|
|
63
|
+
DLS_ANVIL_HP = "Diamond Anvil High Pressure" # HP sample environment pressure cell
|
|
64
|
+
CUSTOM = "Custom" # Special or non-standard data collection
|
|
65
|
+
XRF_MAP = "XRF map"
|
|
66
|
+
ENERGY_SCAN = "Energy scan"
|
|
67
|
+
XRF_SPECTRUM = "XRF spectrum"
|
|
68
|
+
XRF_MAP_XAS = "XRF map xas"
|
|
69
|
+
MESH_3D = "Mesh3D"
|
|
70
|
+
SCREENING = "Screening"
|
|
71
|
+
STILL = "Still"
|
|
72
|
+
SSX_CHIP = "SSX-Chip"
|
|
73
|
+
SSX_JET = "SSX-Jet"
|
|
74
|
+
|
|
75
|
+
# Aliases for historic hyperion experiment type mapping
|
|
76
|
+
ROTATION = "SAD"
|
|
77
|
+
GRIDSCAN_2D = "mesh"
|
|
78
|
+
GRIDSCAN_3D = "Mesh3D"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class MxBlueskyParameters(BaseModel):
|
|
82
|
+
model_config = ConfigDict(
|
|
83
|
+
arbitrary_types_allowed=True,
|
|
84
|
+
extra="allow",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def __hash__(self) -> int:
|
|
88
|
+
return self.model_dump_json().__hash__()
|
|
89
|
+
|
|
90
|
+
parameter_model_version: SemanticVersion
|
|
91
|
+
|
|
92
|
+
@field_validator("parameter_model_version")
|
|
93
|
+
@classmethod
|
|
94
|
+
def _validate_version(cls, version: Version):
|
|
95
|
+
assert (
|
|
96
|
+
version >= Version(major=PARAMETER_VERSION.major)
|
|
97
|
+
), f"Parameter version too old! This version of hyperion uses {PARAMETER_VERSION}"
|
|
98
|
+
assert (
|
|
99
|
+
version <= Version(major=PARAMETER_VERSION.major + 1)
|
|
100
|
+
), f"Parameter version too new! This version of hyperion uses {PARAMETER_VERSION}"
|
|
101
|
+
return version
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def from_json(cls, input: str | None):
|
|
105
|
+
assert input is not None
|
|
106
|
+
return cls(**json.loads(input))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class WithSnapshot(BaseModel):
|
|
110
|
+
snapshot_directory: Path
|
|
111
|
+
snapshot_omegas_deg: list[float] | None = None
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def take_snapshots(self) -> bool:
|
|
115
|
+
return bool(self.snapshot_omegas_deg)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class WithOptionalEnergyChange(BaseModel):
|
|
119
|
+
demand_energy_ev: float | None = Field(default=None, gt=0)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class WithVisit(BaseModel):
|
|
123
|
+
beamline: str = Field(default="BL03I", pattern=r"BL\d{2}[BIJS]")
|
|
124
|
+
visit: str = Field(min_length=1)
|
|
125
|
+
det_dist_to_beam_converter_path: str = Field(
|
|
126
|
+
default=DetectorParamConstants.BEAM_XY_LUT_PATH
|
|
127
|
+
)
|
|
128
|
+
insertion_prefix: str = "SR03S" if TEST_MODE else "SR03I"
|
|
129
|
+
detector_distance_mm: float | None = Field(default=None, gt=0)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class DiffractionExperiment(
|
|
133
|
+
MxBlueskyParameters, WithSnapshot, WithOptionalEnergyChange, WithVisit
|
|
134
|
+
):
|
|
135
|
+
"""For all experiments which use beam"""
|
|
136
|
+
|
|
137
|
+
file_name: str
|
|
138
|
+
exposure_time_s: float = Field(gt=0)
|
|
139
|
+
comment: str = Field(default="")
|
|
140
|
+
trigger_mode: TriggerMode = Field(default=TriggerMode.FREE_RUN)
|
|
141
|
+
run_number: int | None = Field(default=None, ge=0)
|
|
142
|
+
selected_aperture: ApertureValue | None = Field(default=None)
|
|
143
|
+
transmission_frac: float = Field(default=0.1)
|
|
144
|
+
ispyb_experiment_type: IspybExperimentType
|
|
145
|
+
storage_directory: str
|
|
146
|
+
|
|
147
|
+
@model_validator(mode="before")
|
|
148
|
+
@classmethod
|
|
149
|
+
def validate_snapshot_directory(cls, values):
|
|
150
|
+
snapshot_dir = values.get(
|
|
151
|
+
"snapshot_directory", Path(values["storage_directory"], "snapshots")
|
|
152
|
+
)
|
|
153
|
+
values["snapshot_directory"] = (
|
|
154
|
+
snapshot_dir if isinstance(snapshot_dir, Path) else Path(snapshot_dir)
|
|
155
|
+
)
|
|
156
|
+
return values
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def num_images(self) -> int:
|
|
160
|
+
return 0
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
@abstractmethod
|
|
164
|
+
def detector_params(self) -> DetectorParams: ...
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class WithScan(BaseModel):
|
|
168
|
+
"""For experiments where the scan is known"""
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
@abstractmethod
|
|
172
|
+
def scan_points(self) -> AxesPoints: ...
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
@abstractmethod
|
|
176
|
+
def num_images(self) -> int: ...
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class SplitScan(BaseModel):
|
|
180
|
+
@property
|
|
181
|
+
@abstractmethod
|
|
182
|
+
def scan_indices(self) -> Sequence[SupportsInt]:
|
|
183
|
+
"""Should return the first index of each scan (i.e. for each nexus file)"""
|
|
184
|
+
...
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class WithSample(BaseModel):
|
|
188
|
+
sample_id: int
|
|
189
|
+
sample_puck: int | None = None
|
|
190
|
+
sample_pin: int | None = None
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class DiffractionExperimentWithSample(DiffractionExperiment, WithSample): ...
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class OptionalXyzStarts(BaseModel):
|
|
197
|
+
x_start_um: float | None = None
|
|
198
|
+
y_start_um: float | None = None
|
|
199
|
+
z_start_um: float | None = None
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class XyzStarts(BaseModel):
|
|
203
|
+
x_start_um: float
|
|
204
|
+
y_start_um: float
|
|
205
|
+
z_start_um: float
|
|
206
|
+
|
|
207
|
+
def _start_for_axis(self, axis: XyzAxis) -> float:
|
|
208
|
+
match axis:
|
|
209
|
+
case XyzAxis.X:
|
|
210
|
+
return self.x_start_um
|
|
211
|
+
case XyzAxis.Y:
|
|
212
|
+
return self.y_start_um
|
|
213
|
+
case XyzAxis.Z:
|
|
214
|
+
return self.z_start_um
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class OptionalGonioAngleStarts(BaseModel):
|
|
218
|
+
omega_start_deg: float | None = None
|
|
219
|
+
phi_start_deg: float | None = None
|
|
220
|
+
chi_start_deg: float | None = None
|
|
221
|
+
kappa_start_deg: float | None = None
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from dodal.devices.aperturescatterguard import ApertureValue
|
|
4
|
+
from dodal.devices.zocalo.zocalo_constants import ZOCALO_ENV as ZOCALO_ENV_FROM_DODAL
|
|
5
|
+
from dodal.utils import get_beamline_name
|
|
6
|
+
from pydantic.dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
BEAMLINE = get_beamline_name("test")
|
|
9
|
+
TEST_MODE = BEAMLINE == "test"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class DocDescriptorNames:
|
|
14
|
+
# Robot load event descriptor
|
|
15
|
+
ROBOT_LOAD = "robot_load"
|
|
16
|
+
# For callbacks to use
|
|
17
|
+
OAV_ROTATION_SNAPSHOT_TRIGGERED = "rotation_snapshot_triggered"
|
|
18
|
+
OAV_GRID_SNAPSHOT_TRIGGERED = "snapshot_to_ispyb"
|
|
19
|
+
HARDWARE_READ_PRE = "read_hardware_for_callbacks_pre_collection"
|
|
20
|
+
HARDWARE_READ_DURING = "read_hardware_for_callbacks_during_collection"
|
|
21
|
+
ZOCALO_HW_READ = "zocalo_read_hardware_plan"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class OavConstants:
|
|
26
|
+
OAV_CONFIG_JSON = (
|
|
27
|
+
"tests/test_data/test_OAVCentring.json"
|
|
28
|
+
if TEST_MODE
|
|
29
|
+
else (
|
|
30
|
+
f"/dls_sw/{BEAMLINE}/software/daq_configuration/json/OAVCentring_hyperion.json"
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class PlanNameConstants:
|
|
37
|
+
# Robot load subplan
|
|
38
|
+
ROBOT_LOAD = "robot_load"
|
|
39
|
+
# Gridscan
|
|
40
|
+
GRID_DETECT_AND_DO_GRIDSCAN = "grid_detect_and_do_gridscan"
|
|
41
|
+
GRID_DETECT_INNER = "grid_detect"
|
|
42
|
+
GRIDSCAN_OUTER = "run_gridscan_move_and_tidy"
|
|
43
|
+
GRIDSCAN_AND_MOVE = "run_gridscan_and_move"
|
|
44
|
+
GRIDSCAN_MAIN = "run_gridscan"
|
|
45
|
+
DO_FGS = "do_fgs"
|
|
46
|
+
# Rotation scan
|
|
47
|
+
ROTATION_MULTI = "multi_rotation_wrapper"
|
|
48
|
+
ROTATION_OUTER = "rotation_scan_with_cleanup"
|
|
49
|
+
ROTATION_MAIN = "rotation_scan_main"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class EnvironmentConstants:
|
|
54
|
+
ZOCALO_ENV = ZOCALO_ENV_FROM_DODAL
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class TriggerConstants:
|
|
59
|
+
ZOCALO = "trigger_zocalo_on"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class HardwareConstants:
|
|
64
|
+
OAV_REFRESH_DELAY = 0.3
|
|
65
|
+
PANDA_FGS_RUN_UP_DEFAULT = 0.17
|
|
66
|
+
CRYOJET_MARGIN_MM = 0.2
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(frozen=True)
|
|
70
|
+
class GridscanParamConstants:
|
|
71
|
+
WIDTH_UM = 600.0
|
|
72
|
+
EXPOSURE_TIME_S = 0.004
|
|
73
|
+
USE_ROI = True
|
|
74
|
+
BOX_WIDTH_UM = 20.0
|
|
75
|
+
OMEGA_1 = 0.0
|
|
76
|
+
OMEGA_2 = 90.0
|
|
77
|
+
PANDA_RUN_UP_DISTANCE_MM = 0.2
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass(frozen=True)
|
|
81
|
+
class RotationParamConstants:
|
|
82
|
+
DEFAULT_APERTURE_POSITION = ApertureValue.LARGE
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(frozen=True)
|
|
86
|
+
class DetectorParamConstants:
|
|
87
|
+
BEAM_XY_LUT_PATH = (
|
|
88
|
+
"tests/test_data/test_det_dist_converter.txt"
|
|
89
|
+
if TEST_MODE
|
|
90
|
+
else "/dls_sw/{BEAMLINE}/software/daq_configuration/lookup/DetDistToBeamXYConverter.txt"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True)
|
|
95
|
+
class ExperimentParamConstants:
|
|
96
|
+
DETECTOR = DetectorParamConstants()
|
|
97
|
+
GRIDSCAN = GridscanParamConstants()
|
|
98
|
+
ROTATION = RotationParamConstants()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass(frozen=True)
|
|
102
|
+
class PlanGroupCheckpointConstants:
|
|
103
|
+
# For places to synchronise / stop and wait in plans, use as bluesky group names
|
|
104
|
+
GRID_READY_FOR_DC = "grid_ready_for_data_collection"
|
|
105
|
+
ROTATION_READY_FOR_DC = "rotation_ready_for_data_collection"
|
|
106
|
+
MOVE_GONIO_TO_START = "move_gonio_to_start"
|
|
107
|
+
READY_FOR_OAV = "ready_for_oav"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass(frozen=True)
|
|
111
|
+
class SimConstants:
|
|
112
|
+
BEAMLINE = "BL03S"
|
|
113
|
+
INSERTION_PREFIX = "SR03S"
|
|
114
|
+
# this one is for unit tests
|
|
115
|
+
ISPYB_CONFIG = "tests/test_data/test_config.cfg"
|
|
116
|
+
# this one is for system tests
|
|
117
|
+
DEV_ISPYB_DATABASE_CFG = "/dls_sw/dasc/mariadb/credentials/ispyb-hyperion-dev.cfg"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class Actions(Enum):
|
|
121
|
+
START = "start"
|
|
122
|
+
STOP = "stop"
|
|
123
|
+
SHUTDOWN = "shutdown"
|
|
124
|
+
STATUS = "status"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class Status(Enum):
|
|
128
|
+
WARN = "Warn"
|
|
129
|
+
FAILED = "Failed"
|
|
130
|
+
SUCCESS = "Success"
|
|
131
|
+
BUSY = "Busy"
|
|
132
|
+
ABORTING = "Aborting"
|
|
133
|
+
IDLE = "Idle"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Common MX plans which includes open and close data collection runs. These be used in isolation or as part of a larger plan"""
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from time import time
|
|
3
|
+
|
|
4
|
+
import bluesky.plan_stubs as bps
|
|
5
|
+
import bluesky.preprocessors as bpp
|
|
6
|
+
from blueapi.core import MsgGenerator
|
|
7
|
+
from dodal.devices.eiger import EigerDetector
|
|
8
|
+
from dodal.devices.fast_grid_scan import FastGridScanCommon
|
|
9
|
+
from dodal.devices.synchrotron import Synchrotron
|
|
10
|
+
from dodal.devices.zocalo.zocalo_results import (
|
|
11
|
+
ZOCALO_STAGE_GROUP,
|
|
12
|
+
)
|
|
13
|
+
from dodal.log import LOGGER
|
|
14
|
+
from dodal.plans.check_topup import check_topup_and_wait_if_necessary
|
|
15
|
+
from scanspec.core import AxesPoints, Axis
|
|
16
|
+
|
|
17
|
+
from mx_bluesky.common.device_setup_plans.read_hardware_for_setup import (
|
|
18
|
+
read_hardware_for_zocalo,
|
|
19
|
+
)
|
|
20
|
+
from mx_bluesky.common.parameters.constants import (
|
|
21
|
+
EnvironmentConstants,
|
|
22
|
+
PlanNameConstants,
|
|
23
|
+
TriggerConstants,
|
|
24
|
+
)
|
|
25
|
+
from mx_bluesky.common.utils.tracing import TRACER
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _wait_for_zocalo_to_stage_then_do_fgs(
|
|
29
|
+
grid_scan_device: FastGridScanCommon,
|
|
30
|
+
detector: EigerDetector,
|
|
31
|
+
synchrotron: Synchrotron,
|
|
32
|
+
during_collection_plan: Callable[[], MsgGenerator] | None = None,
|
|
33
|
+
):
|
|
34
|
+
expected_images = yield from bps.rd(grid_scan_device.expected_images)
|
|
35
|
+
exposure_sec_per_image = yield from bps.rd(detector.cam.acquire_time) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
|
|
36
|
+
LOGGER.info("waiting for topup if necessary...")
|
|
37
|
+
yield from check_topup_and_wait_if_necessary(
|
|
38
|
+
synchrotron,
|
|
39
|
+
expected_images * exposure_sec_per_image,
|
|
40
|
+
30.0,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Make sure ZocaloResults queue is clear and ready to accept our new data. Zocalo MUST
|
|
44
|
+
# have been staged using ZOCALO_STAGE_GROUP prior to this
|
|
45
|
+
LOGGER.info("Waiting for Zocalo device queue to have been cleared...")
|
|
46
|
+
yield from bps.wait(ZOCALO_STAGE_GROUP)
|
|
47
|
+
|
|
48
|
+
# Triggers Zocalo if RE is subscribed to ZocaloCallback
|
|
49
|
+
yield from read_hardware_for_zocalo(detector)
|
|
50
|
+
LOGGER.info("Wait for all moves with no assigned group")
|
|
51
|
+
yield from bps.wait()
|
|
52
|
+
|
|
53
|
+
LOGGER.info("kicking off FGS")
|
|
54
|
+
yield from bps.kickoff(grid_scan_device, wait=True)
|
|
55
|
+
gridscan_start_time = time()
|
|
56
|
+
if during_collection_plan:
|
|
57
|
+
yield from during_collection_plan()
|
|
58
|
+
LOGGER.info("completing FGS")
|
|
59
|
+
yield from bps.complete(grid_scan_device, wait=True)
|
|
60
|
+
# Remove this logging statement once metrics have been added
|
|
61
|
+
LOGGER.info(
|
|
62
|
+
f"Grid scan motion program took {round(time()-gridscan_start_time,2)} to complete"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def kickoff_and_complete_gridscan(
|
|
67
|
+
gridscan: FastGridScanCommon,
|
|
68
|
+
detector: EigerDetector, # Once Eiger inherits from StandardDetector, use that type instead
|
|
69
|
+
synchrotron: Synchrotron,
|
|
70
|
+
scan_points: list[AxesPoints[Axis]],
|
|
71
|
+
scan_start_indices: list[int],
|
|
72
|
+
plan_during_collection: Callable[[], MsgGenerator] | None = None,
|
|
73
|
+
zocalo_environment: str = EnvironmentConstants.ZOCALO_ENV,
|
|
74
|
+
):
|
|
75
|
+
"""Triggers a grid scan motion program and waits for completion, accounting for synchrotron topup.
|
|
76
|
+
If the RunEngine is subscribed to ZocaloCallback, this plan will also trigger Zocalo.
|
|
77
|
+
|
|
78
|
+
Can be used for multiple successive grid scans, see Hyperion's usage
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
gridscan (FastGridScanCommon): Device which can trigger a fast grid scan and wait for completion
|
|
82
|
+
detector (EigerDetector) Detector device
|
|
83
|
+
synchrotron (Synchrotron): Synchrotron device
|
|
84
|
+
scan_points (list[AxesPoints[Axis]]): Each element in the list contains all the grid points for that grid scan.
|
|
85
|
+
Two elements in this list indicates that two grid scans will be done, eg for Hyperion's 3D grid scans.
|
|
86
|
+
scan_start_indices (list[int]): Contains the first index of each grid scan
|
|
87
|
+
plan_during_collection (Optional, MsgGenerator): Generic plan called in between kickoff and completion,
|
|
88
|
+
eg waiting on zocalo.
|
|
89
|
+
zocalo_environment (Optional, str) Used for zocalo connection
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
assert len(scan_points) == len(
|
|
93
|
+
scan_start_indices
|
|
94
|
+
), "scan_points and scan_start_indices must be lists of the same length!"
|
|
95
|
+
|
|
96
|
+
plan_name = PlanNameConstants.DO_FGS
|
|
97
|
+
|
|
98
|
+
@TRACER.start_as_current_span(plan_name)
|
|
99
|
+
@bpp.set_run_key_decorator(plan_name)
|
|
100
|
+
@bpp.run_decorator(
|
|
101
|
+
md={
|
|
102
|
+
"subplan_name": plan_name,
|
|
103
|
+
TriggerConstants.ZOCALO: plan_name,
|
|
104
|
+
"scan_points": scan_points,
|
|
105
|
+
"scan_start_indices": scan_start_indices,
|
|
106
|
+
"zocalo_environment": zocalo_environment,
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
@bpp.contingency_decorator(
|
|
110
|
+
except_plan=lambda e: (yield from bps.stop(detector)), # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
|
|
111
|
+
else_plan=lambda: (yield from bps.unstage(detector)), # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
|
|
112
|
+
)
|
|
113
|
+
def _decorated_do_fgs():
|
|
114
|
+
yield from _wait_for_zocalo_to_stage_then_do_fgs(
|
|
115
|
+
gridscan,
|
|
116
|
+
detector,
|
|
117
|
+
synchrotron,
|
|
118
|
+
during_collection_plan=plan_during_collection,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
yield from _decorated_do_fgs()
|