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.
Files changed (105) hide show
  1. mx_bluesky/__init__.py +8 -3
  2. mx_bluesky/__main__.py +12 -7
  3. mx_bluesky/_version.py +2 -2
  4. mx_bluesky/beamlines/i04/callbacks/murko_callback.py +14 -4
  5. mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +178 -0
  6. mx_bluesky/beamlines/i04/thawing_plan.py +49 -11
  7. mx_bluesky/beamlines/i24/serial/__init__.py +3 -0
  8. mx_bluesky/beamlines/i24/serial/dcid.py +143 -171
  9. mx_bluesky/beamlines/i24/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +1 -1
  10. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +121 -110
  11. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +3 -6
  12. mx_bluesky/beamlines/i24/serial/fixed_target/ft_utils.py +0 -1
  13. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +164 -169
  14. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +149 -225
  15. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +7 -216
  16. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +18 -17
  17. mx_bluesky/beamlines/i24/serial/log.py +58 -49
  18. mx_bluesky/beamlines/i24/serial/parameters/__init__.py +4 -0
  19. mx_bluesky/beamlines/i24/serial/parameters/constants.py +6 -1
  20. mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py +42 -15
  21. mx_bluesky/beamlines/i24/serial/parameters/fixed_target/cs/cs_maker.json +3 -3
  22. mx_bluesky/beamlines/i24/serial/run_extruder.sh +30 -5
  23. mx_bluesky/beamlines/i24/serial/run_fixed_target.sh +30 -5
  24. mx_bluesky/beamlines/i24/serial/run_serial.py +24 -8
  25. mx_bluesky/beamlines/i24/serial/setup_beamline/ca.py +0 -2
  26. mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +2 -0
  27. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +104 -82
  28. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +9 -20
  29. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +26 -28
  30. mx_bluesky/beamlines/i24/serial/write_nexus.py +74 -72
  31. mx_bluesky/common/__init__.py +0 -0
  32. mx_bluesky/common/device_setup_plans/read_hardware_for_setup.py +14 -0
  33. mx_bluesky/common/external_interaction/config_server.py +46 -0
  34. mx_bluesky/common/parameters/components.py +258 -0
  35. mx_bluesky/common/parameters/constants.py +143 -0
  36. mx_bluesky/common/parameters/gridscan.py +94 -0
  37. mx_bluesky/common/parameters/robot_load.py +16 -0
  38. mx_bluesky/common/plans/__init__.py +1 -0
  39. mx_bluesky/common/plans/do_fgs.py +121 -0
  40. mx_bluesky/common/utils/log.py +118 -0
  41. mx_bluesky/{hyperion → common/utils}/tracing.py +2 -2
  42. mx_bluesky/hyperion/__main__.py +13 -10
  43. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +47 -52
  44. mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +6 -12
  45. mx_bluesky/hyperion/device_setup_plans/setup_oav.py +6 -12
  46. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +5 -6
  47. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +49 -18
  48. mx_bluesky/hyperion/device_setup_plans/smargon.py +9 -9
  49. mx_bluesky/hyperion/device_setup_plans/utils.py +2 -2
  50. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +4 -4
  51. mx_bluesky/hyperion/exceptions.py +13 -1
  52. mx_bluesky/hyperion/experiment_plans/__init__.py +4 -0
  53. mx_bluesky/hyperion/experiment_plans/change_aperture_then_move_plan.py +83 -0
  54. mx_bluesky/hyperion/experiment_plans/common/xrc_result.py +47 -0
  55. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +9 -9
  56. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +147 -169
  57. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +48 -22
  58. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +75 -9
  59. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +21 -20
  60. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +9 -6
  61. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +2 -2
  62. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +40 -21
  63. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +22 -22
  64. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +43 -39
  65. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +69 -18
  66. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +17 -7
  67. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +13 -13
  68. mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +0 -4
  69. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +5 -2
  70. mx_bluesky/hyperion/external_interaction/callbacks/common/abstract_event.py +66 -0
  71. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +5 -0
  72. mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +1 -1
  73. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +30 -25
  74. mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +29 -12
  75. mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +1 -1
  76. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +19 -11
  77. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +7 -4
  78. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +5 -3
  79. mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/__init__.py +0 -0
  80. mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/sample_handling_callback.py +84 -0
  81. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +38 -27
  82. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +5 -4
  83. mx_bluesky/hyperion/external_interaction/config_server.py +11 -28
  84. mx_bluesky/hyperion/external_interaction/exceptions.py +0 -9
  85. mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +65 -15
  86. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +1 -1
  87. mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +2 -2
  88. mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +1 -1
  89. mx_bluesky/hyperion/log.py +0 -84
  90. mx_bluesky/hyperion/parameters/components.py +4 -251
  91. mx_bluesky/hyperion/parameters/constants.py +22 -119
  92. mx_bluesky/hyperion/parameters/gridscan.py +35 -74
  93. mx_bluesky/hyperion/parameters/load_centre_collect.py +16 -11
  94. mx_bluesky/hyperion/parameters/rotation.py +23 -10
  95. mx_bluesky/hyperion/utils/utils.py +17 -0
  96. mx_bluesky/hyperion/utils/validation.py +5 -6
  97. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1.dist-info}/METADATA +36 -33
  98. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1.dist-info}/RECORD +102 -89
  99. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1.dist-info}/WHEEL +1 -1
  100. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +0 -161
  101. mx_bluesky/example.py +0 -19
  102. mx_bluesky/hyperion/parameters/robot_load.py +0 -16
  103. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1.dist-info}/LICENSE +0 -0
  104. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1.dist-info}/entry_points.txt +0 -0
  105. {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()