mx-bluesky 1.2.0__py3-none-any.whl → 1.4.1__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/redis_to_murko_forwarder.py +178 -0
- mx_bluesky/beamlines/i04/thawing_plan.py +49 -11
- mx_bluesky/beamlines/i24/serial/__init__.py +3 -0
- mx_bluesky/beamlines/i24/serial/dcid.py +143 -171
- mx_bluesky/beamlines/i24/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +1 -1
- mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +121 -110
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +3 -6
- mx_bluesky/beamlines/i24/serial/fixed_target/ft_utils.py +0 -1
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +164 -169
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +149 -225
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +7 -216
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +18 -17
- mx_bluesky/beamlines/i24/serial/log.py +58 -49
- mx_bluesky/beamlines/i24/serial/parameters/__init__.py +4 -0
- mx_bluesky/beamlines/i24/serial/parameters/constants.py +6 -1
- mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py +42 -15
- 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 +30 -5
- 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/pv.py +2 -0
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +104 -82
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +9 -20
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +26 -28
- mx_bluesky/beamlines/i24/serial/write_nexus.py +74 -72
- mx_bluesky/common/__init__.py +0 -0
- mx_bluesky/common/device_setup_plans/read_hardware_for_setup.py +14 -0
- mx_bluesky/common/external_interaction/config_server.py +46 -0
- mx_bluesky/common/parameters/components.py +258 -0
- mx_bluesky/common/parameters/constants.py +143 -0
- mx_bluesky/common/parameters/gridscan.py +94 -0
- mx_bluesky/common/parameters/robot_load.py +16 -0
- mx_bluesky/common/plans/__init__.py +1 -0
- mx_bluesky/common/plans/do_fgs.py +121 -0
- mx_bluesky/common/utils/log.py +118 -0
- mx_bluesky/{hyperion → common/utils}/tracing.py +2 -2
- mx_bluesky/hyperion/__main__.py +13 -10
- mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +47 -52
- 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 +5 -6
- mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +49 -18
- mx_bluesky/hyperion/device_setup_plans/smargon.py +9 -9
- mx_bluesky/hyperion/device_setup_plans/utils.py +2 -2
- mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +4 -4
- mx_bluesky/hyperion/exceptions.py +13 -1
- mx_bluesky/hyperion/experiment_plans/__init__.py +4 -0
- mx_bluesky/hyperion/experiment_plans/change_aperture_then_move_plan.py +83 -0
- mx_bluesky/hyperion/experiment_plans/common/xrc_result.py +47 -0
- mx_bluesky/hyperion/experiment_plans/experiment_registry.py +9 -9
- mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +147 -169
- mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +48 -22
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +75 -9
- mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +21 -20
- mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +9 -6
- mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +2 -2
- mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +40 -21
- mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +22 -22
- mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +43 -39
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +69 -18
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +17 -7
- mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +13 -13
- mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +0 -4
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +5 -2
- mx_bluesky/hyperion/external_interaction/callbacks/common/abstract_event.py +66 -0
- mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +5 -0
- mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +1 -1
- mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +30 -25
- 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 +19 -11
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +7 -4
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +5 -3
- mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/sample_handling_callback.py +84 -0
- mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +38 -27
- mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +5 -4
- mx_bluesky/hyperion/external_interaction/config_server.py +11 -28
- mx_bluesky/hyperion/external_interaction/exceptions.py +0 -9
- mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +65 -15
- 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 +4 -251
- mx_bluesky/hyperion/parameters/constants.py +22 -119
- mx_bluesky/hyperion/parameters/gridscan.py +35 -74
- mx_bluesky/hyperion/parameters/load_centre_collect.py +16 -11
- mx_bluesky/hyperion/parameters/rotation.py +23 -10
- 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.1.dist-info}/METADATA +36 -33
- {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1.dist-info}/RECORD +102 -89
- {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1.dist-info}/WHEEL +1 -1
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +0 -161
- mx_bluesky/example.py +0 -19
- mx_bluesky/hyperion/parameters/robot_load.py +0 -16
- {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1.dist-info}/LICENSE +0 -0
- {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from abc import abstractmethod
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from enum import StrEnum
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Literal, SupportsInt, cast
|
|
9
|
+
|
|
10
|
+
from dodal.devices.aperturescatterguard import ApertureValue
|
|
11
|
+
from dodal.devices.detector import (
|
|
12
|
+
DetectorParams,
|
|
13
|
+
TriggerMode,
|
|
14
|
+
)
|
|
15
|
+
from pydantic import (
|
|
16
|
+
BaseModel,
|
|
17
|
+
ConfigDict,
|
|
18
|
+
Field,
|
|
19
|
+
field_validator,
|
|
20
|
+
model_validator,
|
|
21
|
+
)
|
|
22
|
+
from pydantic_extra_types.semantic_version import SemanticVersion
|
|
23
|
+
from scanspec.core import AxesPoints
|
|
24
|
+
from semver import Version
|
|
25
|
+
|
|
26
|
+
from mx_bluesky.common.parameters.constants import (
|
|
27
|
+
TEST_MODE,
|
|
28
|
+
DetectorParamConstants,
|
|
29
|
+
GridscanParamConstants,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
PARAMETER_VERSION = Version.parse("5.2.0")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class RotationAxis(StrEnum):
|
|
36
|
+
OMEGA = "omega"
|
|
37
|
+
PHI = "phi"
|
|
38
|
+
CHI = "chi"
|
|
39
|
+
KAPPA = "kappa"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class XyzAxis(StrEnum):
|
|
43
|
+
X = "sam_x"
|
|
44
|
+
Y = "sam_y"
|
|
45
|
+
Z = "sam_z"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class IspybExperimentType(StrEnum):
|
|
49
|
+
# Enum values from ispyb column data type
|
|
50
|
+
SAD = "SAD" # at or slightly above the peak
|
|
51
|
+
SAD_INVERSE_BEAM = "SAD - Inverse Beam"
|
|
52
|
+
OSC = "OSC" # "native" (in the absence of a heavy atom)
|
|
53
|
+
COLLECT_MULTIWEDGE = (
|
|
54
|
+
"Collect - Multiwedge" # "poorly determined" ~ EDNA complex strategy???
|
|
55
|
+
)
|
|
56
|
+
MAD = "MAD"
|
|
57
|
+
HELICAL = "Helical"
|
|
58
|
+
MULTI_POSITIONAL = "Multi-positional"
|
|
59
|
+
MESH = "Mesh"
|
|
60
|
+
BURN = "Burn"
|
|
61
|
+
MAD_INVERSE_BEAM = "MAD - Inverse Beam"
|
|
62
|
+
CHARACTERIZATION = "Characterization"
|
|
63
|
+
DEHYDRATION = "Dehydration"
|
|
64
|
+
TOMO = "tomo"
|
|
65
|
+
EXPERIMENT = "experiment"
|
|
66
|
+
EM = "EM"
|
|
67
|
+
PDF = "PDF"
|
|
68
|
+
PDF_BRAGG = "PDF+Bragg"
|
|
69
|
+
BRAGG = "Bragg"
|
|
70
|
+
SINGLE_PARTICLE = "single particle"
|
|
71
|
+
SERIAL_FIXED = "Serial Fixed"
|
|
72
|
+
SERIAL_JET = "Serial Jet"
|
|
73
|
+
STANDARD = "Standard" # Routine structure determination experiment
|
|
74
|
+
TIME_RESOLVED = "Time Resolved" # Investigate the change of a system over time
|
|
75
|
+
DLS_ANVIL_HP = "Diamond Anvil High Pressure" # HP sample environment pressure cell
|
|
76
|
+
CUSTOM = "Custom" # Special or non-standard data collection
|
|
77
|
+
XRF_MAP = "XRF map"
|
|
78
|
+
ENERGY_SCAN = "Energy scan"
|
|
79
|
+
XRF_SPECTRUM = "XRF spectrum"
|
|
80
|
+
XRF_MAP_XAS = "XRF map xas"
|
|
81
|
+
MESH_3D = "Mesh3D"
|
|
82
|
+
SCREENING = "Screening"
|
|
83
|
+
STILL = "Still"
|
|
84
|
+
SSX_CHIP = "SSX-Chip"
|
|
85
|
+
SSX_JET = "SSX-Jet"
|
|
86
|
+
|
|
87
|
+
# Aliases for historic hyperion experiment type mapping
|
|
88
|
+
ROTATION = "SAD"
|
|
89
|
+
GRIDSCAN_2D = "mesh"
|
|
90
|
+
GRIDSCAN_3D = "Mesh3D"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class MxBlueskyParameters(BaseModel):
|
|
94
|
+
model_config = ConfigDict(
|
|
95
|
+
arbitrary_types_allowed=True,
|
|
96
|
+
extra="allow",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def __hash__(self) -> int:
|
|
100
|
+
return self.model_dump_json().__hash__()
|
|
101
|
+
|
|
102
|
+
parameter_model_version: SemanticVersion
|
|
103
|
+
|
|
104
|
+
@field_validator("parameter_model_version")
|
|
105
|
+
@classmethod
|
|
106
|
+
def _validate_version(cls, version: Version):
|
|
107
|
+
assert (
|
|
108
|
+
version >= Version(major=PARAMETER_VERSION.major)
|
|
109
|
+
), f"Parameter version too old! This version of hyperion uses {PARAMETER_VERSION}"
|
|
110
|
+
assert (
|
|
111
|
+
version <= Version(major=PARAMETER_VERSION.major + 1)
|
|
112
|
+
), f"Parameter version too new! This version of hyperion uses {PARAMETER_VERSION}"
|
|
113
|
+
return version
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class WithSnapshot(BaseModel):
|
|
117
|
+
snapshot_directory: Path
|
|
118
|
+
snapshot_omegas_deg: list[float] | None = None
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def take_snapshots(self) -> bool:
|
|
122
|
+
return bool(self.snapshot_omegas_deg)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class WithOptionalEnergyChange(BaseModel):
|
|
126
|
+
demand_energy_ev: float | None = Field(default=None, gt=0)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class WithVisit(BaseModel):
|
|
130
|
+
beamline: str = Field(default="BL03I", pattern=r"BL\d{2}[BIJS]")
|
|
131
|
+
visit: str = Field(min_length=1)
|
|
132
|
+
det_dist_to_beam_converter_path: str = Field(
|
|
133
|
+
default=DetectorParamConstants.BEAM_XY_LUT_PATH
|
|
134
|
+
)
|
|
135
|
+
insertion_prefix: str = "SR03S" if TEST_MODE else "SR03I"
|
|
136
|
+
detector_distance_mm: float | None = Field(default=None, gt=0)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class DiffractionExperiment(
|
|
140
|
+
MxBlueskyParameters, WithSnapshot, WithOptionalEnergyChange, WithVisit
|
|
141
|
+
):
|
|
142
|
+
"""For all experiments which use beam"""
|
|
143
|
+
|
|
144
|
+
file_name: str
|
|
145
|
+
exposure_time_s: float = Field(gt=0)
|
|
146
|
+
comment: str = Field(default="")
|
|
147
|
+
trigger_mode: TriggerMode = Field(default=TriggerMode.FREE_RUN)
|
|
148
|
+
run_number: int | None = Field(default=None, ge=0)
|
|
149
|
+
selected_aperture: ApertureValue | None = Field(default=None)
|
|
150
|
+
transmission_frac: float = Field(default=0.1)
|
|
151
|
+
ispyb_experiment_type: IspybExperimentType
|
|
152
|
+
storage_directory: str
|
|
153
|
+
|
|
154
|
+
@model_validator(mode="before")
|
|
155
|
+
@classmethod
|
|
156
|
+
def validate_directories(cls, values):
|
|
157
|
+
os.makedirs(values["storage_directory"], exist_ok=True)
|
|
158
|
+
|
|
159
|
+
values["snapshot_directory"] = values.get(
|
|
160
|
+
"snapshot_directory",
|
|
161
|
+
Path(values["storage_directory"], "snapshots").as_posix(),
|
|
162
|
+
)
|
|
163
|
+
return values
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def num_images(self) -> int:
|
|
167
|
+
return 0
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
@abstractmethod
|
|
171
|
+
def detector_params(self) -> DetectorParams: ...
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class WithScan(BaseModel):
|
|
175
|
+
"""For experiments where the scan is known"""
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
@abstractmethod
|
|
179
|
+
def scan_points(self) -> AxesPoints: ...
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
@abstractmethod
|
|
183
|
+
def num_images(self) -> int: ...
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class WithPandaGridScan(BaseModel):
|
|
187
|
+
"""For experiments which use a PandA for constant-motion grid scans"""
|
|
188
|
+
|
|
189
|
+
panda_runup_distance_mm: float = Field(
|
|
190
|
+
default=GridscanParamConstants.PANDA_RUN_UP_DISTANCE_MM
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class SplitScan(BaseModel):
|
|
195
|
+
@property
|
|
196
|
+
@abstractmethod
|
|
197
|
+
def scan_indices(self) -> Sequence[SupportsInt]:
|
|
198
|
+
"""Should return the first index of each scan (i.e. for each nexus file)"""
|
|
199
|
+
...
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class WithSample(BaseModel):
|
|
203
|
+
sample_id: int
|
|
204
|
+
sample_puck: int | None = None
|
|
205
|
+
sample_pin: int | None = None
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class DiffractionExperimentWithSample(DiffractionExperiment, WithSample): ...
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class MultiXtalSelection(BaseModel):
|
|
212
|
+
name: str
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class TopNByMaxCountSelection(MultiXtalSelection):
|
|
216
|
+
name: Literal["TopNByMaxCount"] = "TopNByMaxCount" # pyright: ignore [reportIncompatibleVariableOverride]
|
|
217
|
+
n: int
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class WithCentreSelection(BaseModel):
|
|
221
|
+
select_centres: TopNByMaxCountSelection = Field(
|
|
222
|
+
discriminator="name", default=TopNByMaxCountSelection(n=1)
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def selection_params(self) -> MultiXtalSelection:
|
|
227
|
+
"""A helper property because pydantic does not allow polymorphism with base classes
|
|
228
|
+
# only type unions"""
|
|
229
|
+
cast1 = cast(MultiXtalSelection, self.select_centres)
|
|
230
|
+
return cast1
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class OptionalXyzStarts(BaseModel):
|
|
234
|
+
x_start_um: float | None = None
|
|
235
|
+
y_start_um: float | None = None
|
|
236
|
+
z_start_um: float | None = None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class XyzStarts(BaseModel):
|
|
240
|
+
x_start_um: float
|
|
241
|
+
y_start_um: float
|
|
242
|
+
z_start_um: float
|
|
243
|
+
|
|
244
|
+
def _start_for_axis(self, axis: XyzAxis) -> float:
|
|
245
|
+
match axis:
|
|
246
|
+
case XyzAxis.X:
|
|
247
|
+
return self.x_start_um
|
|
248
|
+
case XyzAxis.Y:
|
|
249
|
+
return self.y_start_um
|
|
250
|
+
case XyzAxis.Z:
|
|
251
|
+
return self.z_start_um
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class OptionalGonioAngleStarts(BaseModel):
|
|
255
|
+
omega_start_deg: float | None = None
|
|
256
|
+
phi_start_deg: float | None = None
|
|
257
|
+
chi_start_deg: float | None = None
|
|
258
|
+
kappa_start_deg: float | None = None
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from dodal.devices.aperturescatterguard import ApertureValue
|
|
4
|
+
from dodal.devices.detector import EIGER2_X_16M_SIZE
|
|
5
|
+
from dodal.devices.zocalo.zocalo_constants import ZOCALO_ENV as ZOCALO_ENV_FROM_DODAL
|
|
6
|
+
from dodal.utils import get_beamline_name
|
|
7
|
+
from pydantic.dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
BEAMLINE = get_beamline_name("test")
|
|
10
|
+
TEST_MODE = BEAMLINE == "test"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class DocDescriptorNames:
|
|
15
|
+
# Robot load event descriptor
|
|
16
|
+
ROBOT_LOAD = "robot_load"
|
|
17
|
+
# For callbacks to use
|
|
18
|
+
OAV_ROTATION_SNAPSHOT_TRIGGERED = "rotation_snapshot_triggered"
|
|
19
|
+
OAV_GRID_SNAPSHOT_TRIGGERED = "snapshot_to_ispyb"
|
|
20
|
+
HARDWARE_READ_PRE = "read_hardware_for_callbacks_pre_collection"
|
|
21
|
+
HARDWARE_READ_DURING = "read_hardware_for_callbacks_during_collection"
|
|
22
|
+
SAMPLE_HANDLING_EXCEPTION = "sample_handling_exception"
|
|
23
|
+
ZOCALO_HW_READ = "zocalo_read_hardware_plan"
|
|
24
|
+
FLYSCAN_RESULTS = "flyscan_results_obtained"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class OavConstants:
|
|
29
|
+
OAV_CONFIG_JSON = (
|
|
30
|
+
"tests/test_data/test_OAVCentring.json"
|
|
31
|
+
if TEST_MODE
|
|
32
|
+
else (
|
|
33
|
+
f"/dls_sw/{BEAMLINE}/software/daq_configuration/json/OAVCentring_hyperion.json"
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class PlanNameConstants:
|
|
40
|
+
LOAD_CENTRE_COLLECT = "load_centre_collect"
|
|
41
|
+
# Robot load subplan
|
|
42
|
+
ROBOT_LOAD = "robot_load"
|
|
43
|
+
# Gridscan
|
|
44
|
+
GRID_DETECT_AND_DO_GRIDSCAN = "grid_detect_and_do_gridscan"
|
|
45
|
+
GRID_DETECT_INNER = "grid_detect"
|
|
46
|
+
GRIDSCAN_OUTER = "run_gridscan_move_and_tidy"
|
|
47
|
+
GRIDSCAN_AND_MOVE = "run_gridscan_and_move"
|
|
48
|
+
GRIDSCAN_MAIN = "run_gridscan"
|
|
49
|
+
DO_FGS = "do_fgs"
|
|
50
|
+
# IspyB callback activation
|
|
51
|
+
ISPYB_ACTIVATION = "ispyb_activation"
|
|
52
|
+
ROBOT_LOAD_AND_SNAPSHOTS = "robot_load_and_snapshots"
|
|
53
|
+
# Rotation scan
|
|
54
|
+
ROTATION_MULTI = "multi_rotation_wrapper"
|
|
55
|
+
ROTATION_OUTER = "rotation_scan_with_cleanup"
|
|
56
|
+
ROTATION_MAIN = "rotation_scan_main"
|
|
57
|
+
FLYSCAN_RESULTS = "xray_centre_results"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True)
|
|
61
|
+
class EnvironmentConstants:
|
|
62
|
+
ZOCALO_ENV = ZOCALO_ENV_FROM_DODAL
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(frozen=True)
|
|
66
|
+
class TriggerConstants:
|
|
67
|
+
ZOCALO = "trigger_zocalo_on"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True)
|
|
71
|
+
class HardwareConstants:
|
|
72
|
+
OAV_REFRESH_DELAY = 0.3
|
|
73
|
+
PANDA_FGS_RUN_UP_DEFAULT = 0.17
|
|
74
|
+
CRYOJET_MARGIN_MM = 0.2
|
|
75
|
+
THAWING_TIME = 20
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass(frozen=True)
|
|
79
|
+
class GridscanParamConstants:
|
|
80
|
+
WIDTH_UM = 600.0
|
|
81
|
+
EXPOSURE_TIME_S = 0.004
|
|
82
|
+
USE_ROI = True
|
|
83
|
+
BOX_WIDTH_UM = 20.0
|
|
84
|
+
OMEGA_1 = 0.0
|
|
85
|
+
OMEGA_2 = 90.0
|
|
86
|
+
PANDA_RUN_UP_DISTANCE_MM = 0.2
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass(frozen=True)
|
|
90
|
+
class RotationParamConstants:
|
|
91
|
+
DEFAULT_APERTURE_POSITION = ApertureValue.LARGE
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True)
|
|
95
|
+
class DetectorParamConstants:
|
|
96
|
+
BEAM_XY_LUT_PATH = (
|
|
97
|
+
"tests/test_data/test_det_dist_converter.txt"
|
|
98
|
+
if TEST_MODE
|
|
99
|
+
else f"/dls_sw/{BEAMLINE}/software/daq_configuration/lookup/DetDistToBeamXYConverter.txt"
|
|
100
|
+
)
|
|
101
|
+
DETECTOR = EIGER2_X_16M_SIZE
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass(frozen=True)
|
|
105
|
+
class ExperimentParamConstants:
|
|
106
|
+
DETECTOR = DetectorParamConstants()
|
|
107
|
+
GRIDSCAN = GridscanParamConstants()
|
|
108
|
+
ROTATION = RotationParamConstants()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass(frozen=True)
|
|
112
|
+
class PlanGroupCheckpointConstants:
|
|
113
|
+
# For places to synchronise / stop and wait in plans, use as bluesky group names
|
|
114
|
+
GRID_READY_FOR_DC = "grid_ready_for_data_collection"
|
|
115
|
+
ROTATION_READY_FOR_DC = "rotation_ready_for_data_collection"
|
|
116
|
+
MOVE_GONIO_TO_START = "move_gonio_to_start"
|
|
117
|
+
READY_FOR_OAV = "ready_for_oav"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@dataclass(frozen=True)
|
|
121
|
+
class SimConstants:
|
|
122
|
+
BEAMLINE = "BL03S"
|
|
123
|
+
INSERTION_PREFIX = "SR03S"
|
|
124
|
+
# this one is for unit tests
|
|
125
|
+
ISPYB_CONFIG = "tests/test_data/test_config.cfg"
|
|
126
|
+
# this one is for system tests
|
|
127
|
+
DEV_ISPYB_DATABASE_CFG = "/dls_sw/dasc/mariadb/credentials/ispyb-hyperion-dev.cfg"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class Actions(Enum):
|
|
131
|
+
START = "start"
|
|
132
|
+
STOP = "stop"
|
|
133
|
+
SHUTDOWN = "shutdown"
|
|
134
|
+
STATUS = "status"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class Status(Enum):
|
|
138
|
+
WARN = "Warn"
|
|
139
|
+
FAILED = "Failed"
|
|
140
|
+
SUCCESS = "Success"
|
|
141
|
+
BUSY = "Busy"
|
|
142
|
+
ABORTING = "Aborting"
|
|
143
|
+
IDLE = "Idle"
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from dodal.devices.aperturescatterguard import ApertureValue
|
|
6
|
+
from dodal.devices.detector import (
|
|
7
|
+
DetectorParams,
|
|
8
|
+
)
|
|
9
|
+
from pydantic import Field
|
|
10
|
+
|
|
11
|
+
from mx_bluesky.common.parameters.components import (
|
|
12
|
+
DiffractionExperimentWithSample,
|
|
13
|
+
IspybExperimentType,
|
|
14
|
+
OptionalGonioAngleStarts,
|
|
15
|
+
WithScan,
|
|
16
|
+
XyzStarts,
|
|
17
|
+
)
|
|
18
|
+
from mx_bluesky.common.parameters.constants import (
|
|
19
|
+
DetectorParamConstants,
|
|
20
|
+
GridscanParamConstants,
|
|
21
|
+
HardwareConstants,
|
|
22
|
+
)
|
|
23
|
+
from mx_bluesky.common.parameters.robot_load import RobotLoadAndEnergyChange
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class GridCommon(
|
|
27
|
+
DiffractionExperimentWithSample,
|
|
28
|
+
OptionalGonioAngleStarts,
|
|
29
|
+
):
|
|
30
|
+
grid_width_um: float = Field(default=GridscanParamConstants.WIDTH_UM)
|
|
31
|
+
exposure_time_s: float = Field(default=GridscanParamConstants.EXPOSURE_TIME_S)
|
|
32
|
+
use_roi_mode: bool = Field(default=GridscanParamConstants.USE_ROI)
|
|
33
|
+
|
|
34
|
+
ispyb_experiment_type: IspybExperimentType = Field(
|
|
35
|
+
default=IspybExperimentType.GRIDSCAN_3D
|
|
36
|
+
)
|
|
37
|
+
selected_aperture: ApertureValue | None = Field(default=ApertureValue.SMALL)
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def detector_params(self):
|
|
41
|
+
self.det_dist_to_beam_converter_path = (
|
|
42
|
+
self.det_dist_to_beam_converter_path
|
|
43
|
+
or DetectorParamConstants.BEAM_XY_LUT_PATH
|
|
44
|
+
)
|
|
45
|
+
optional_args = {}
|
|
46
|
+
if self.run_number:
|
|
47
|
+
optional_args["run_number"] = self.run_number
|
|
48
|
+
assert (
|
|
49
|
+
self.detector_distance_mm is not None
|
|
50
|
+
), "Detector distance must be filled before generating DetectorParams"
|
|
51
|
+
os.makedirs(self.storage_directory, exist_ok=True)
|
|
52
|
+
return DetectorParams(
|
|
53
|
+
detector_size_constants=DetectorParamConstants.DETECTOR,
|
|
54
|
+
expected_energy_ev=self.demand_energy_ev,
|
|
55
|
+
exposure_time=self.exposure_time_s,
|
|
56
|
+
directory=self.storage_directory,
|
|
57
|
+
prefix=self.file_name,
|
|
58
|
+
detector_distance=self.detector_distance_mm,
|
|
59
|
+
omega_start=self.omega_start_deg or 0,
|
|
60
|
+
omega_increment=0,
|
|
61
|
+
num_images_per_trigger=1,
|
|
62
|
+
num_triggers=self.num_images,
|
|
63
|
+
use_roi_mode=self.use_roi_mode,
|
|
64
|
+
det_dist_to_beam_converter_path=self.det_dist_to_beam_converter_path,
|
|
65
|
+
trigger_mode=self.trigger_mode,
|
|
66
|
+
**optional_args,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class RobotLoadThenCentre(GridCommon):
|
|
71
|
+
thawing_time: float = Field(default=HardwareConstants.THAWING_TIME)
|
|
72
|
+
|
|
73
|
+
def robot_load_params(self):
|
|
74
|
+
my_params = self.model_dump()
|
|
75
|
+
return RobotLoadAndEnergyChange(**my_params)
|
|
76
|
+
|
|
77
|
+
def pin_centre_then_xray_centre_params(self):
|
|
78
|
+
my_params = self.model_dump()
|
|
79
|
+
del my_params["thawing_time"]
|
|
80
|
+
return PinTipCentreThenXrayCentre(**my_params)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class GridScanWithEdgeDetect(GridCommon):
|
|
84
|
+
box_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class PinTipCentreThenXrayCentre(GridCommon):
|
|
88
|
+
tip_offset_um: float = 0
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class SpecifiedGrid(XyzStarts, WithScan):
|
|
92
|
+
"""A specified grid is one which has defined values for the start position,
|
|
93
|
+
grid and box sizes, etc., as opposed to parameters for a plan which will create
|
|
94
|
+
those parameters at some point (e.g. through optical pin detection)."""
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from pydantic import Field
|
|
2
|
+
|
|
3
|
+
from mx_bluesky.common.parameters.components import (
|
|
4
|
+
MxBlueskyParameters,
|
|
5
|
+
WithOptionalEnergyChange,
|
|
6
|
+
WithSample,
|
|
7
|
+
WithSnapshot,
|
|
8
|
+
WithVisit,
|
|
9
|
+
)
|
|
10
|
+
from mx_bluesky.common.parameters.constants import HardwareConstants
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RobotLoadAndEnergyChange(
|
|
14
|
+
MxBlueskyParameters, WithSample, WithSnapshot, WithOptionalEnergyChange, WithVisit
|
|
15
|
+
):
|
|
16
|
+
thawing_time: float = Field(default=HardwareConstants.THAWING_TIME)
|
|
@@ -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 bluesky.utils 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.plan_stubs.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()
|