mx-bluesky 1.5.5__py3-none-any.whl → 1.5.7__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/_version.py +2 -2
- mx_bluesky/beamlines/i02_1/parameters/__init__.py +0 -0
- mx_bluesky/beamlines/i02_1/parameters/gridscan.py +35 -0
- mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +6 -3
- mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +3 -1
- mx_bluesky/beamlines/i04/thawing_plan.py +15 -8
- mx_bluesky/beamlines/i24/jungfrau_commissioning/do_external_acquisition.py +44 -0
- mx_bluesky/beamlines/i24/jungfrau_commissioning/do_internal_acquisition.py +46 -0
- mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_utils.py +73 -0
- mx_bluesky/common/device_setup_plans/robot_load_unload.py +2 -1
- mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +29 -5
- mx_bluesky/common/experiment_plans/inner_plans/do_fgs.py +7 -8
- mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py +6 -6
- mx_bluesky/common/external_interaction/callbacks/common/zocalo_callback.py +30 -22
- mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py +73 -15
- mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_mapping.py +0 -20
- mx_bluesky/common/parameters/components.py +1 -0
- mx_bluesky/common/parameters/device_composites.py +2 -2
- mx_bluesky/common/parameters/gridscan.py +67 -49
- mx_bluesky/hyperion/__main__.py +16 -3
- mx_bluesky/hyperion/baton_handler.py +39 -9
- mx_bluesky/hyperion/device_setup_plans/smargon.py +13 -8
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +19 -8
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +2 -2
- mx_bluesky/hyperion/external_interaction/agamemnon.py +6 -2
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +10 -2
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +37 -1
- mx_bluesky/hyperion/parameters/constants.py +1 -0
- mx_bluesky/hyperion/parameters/device_composites.py +2 -2
- mx_bluesky/hyperion/parameters/gridscan.py +3 -3
- mx_bluesky/hyperion/plan_runner.py +2 -4
- mx_bluesky/hyperion/plan_runner_api.py +43 -0
- {mx_bluesky-1.5.5.dist-info → mx_bluesky-1.5.7.dist-info}/METADATA +2 -2
- {mx_bluesky-1.5.5.dist-info → mx_bluesky-1.5.7.dist-info}/RECORD +38 -32
- {mx_bluesky-1.5.5.dist-info → mx_bluesky-1.5.7.dist-info}/WHEEL +0 -0
- {mx_bluesky-1.5.5.dist-info → mx_bluesky-1.5.7.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.5.5.dist-info → mx_bluesky-1.5.7.dist-info}/licenses/LICENSE +0 -0
- {mx_bluesky-1.5.5.dist-info → mx_bluesky-1.5.7.dist-info}/top_level.txt +0 -0
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import Callable, Sequence
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
from math import isclose
|
|
4
6
|
from time import time
|
|
5
7
|
from typing import TYPE_CHECKING, Any, TypeVar
|
|
6
8
|
|
|
7
9
|
from bluesky import preprocessors as bpp
|
|
8
10
|
from bluesky.utils import MsgGenerator, make_decorator
|
|
11
|
+
from dodal.devices.zocalo import ZocaloStartInfo
|
|
9
12
|
|
|
10
13
|
from mx_bluesky.common.external_interaction.callbacks.common.ispyb_callback_base import (
|
|
11
14
|
BaseISPyBCallback,
|
|
15
|
+
D,
|
|
12
16
|
)
|
|
13
17
|
from mx_bluesky.common.external_interaction.callbacks.common.ispyb_mapping import (
|
|
14
18
|
populate_data_collection_group,
|
|
15
19
|
populate_remaining_data_collection_info,
|
|
16
20
|
)
|
|
21
|
+
from mx_bluesky.common.external_interaction.callbacks.common.zocalo_callback import (
|
|
22
|
+
ZocaloInfoGenerator,
|
|
23
|
+
)
|
|
17
24
|
from mx_bluesky.common.external_interaction.callbacks.xray_centre.ispyb_mapping import (
|
|
18
25
|
construct_comment_for_gridscan,
|
|
19
|
-
populate_xy_data_collection_info,
|
|
20
|
-
populate_xz_data_collection_info,
|
|
21
26
|
)
|
|
22
27
|
from mx_bluesky.common.external_interaction.ispyb.data_model import (
|
|
23
28
|
DataCollectionGridInfo,
|
|
@@ -33,14 +38,21 @@ from mx_bluesky.common.external_interaction.ispyb.ispyb_store import (
|
|
|
33
38
|
)
|
|
34
39
|
from mx_bluesky.common.parameters.components import DiffractionExperimentWithSample
|
|
35
40
|
from mx_bluesky.common.parameters.constants import DocDescriptorNames, PlanNameConstants
|
|
36
|
-
from mx_bluesky.common.parameters.gridscan import
|
|
37
|
-
GridCommon,
|
|
38
|
-
)
|
|
41
|
+
from mx_bluesky.common.parameters.gridscan import GridCommon
|
|
39
42
|
from mx_bluesky.common.utils.exceptions import (
|
|
40
43
|
ISPyBDepositionNotMade,
|
|
41
44
|
SampleException,
|
|
42
45
|
)
|
|
43
46
|
from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER, set_dcgid_tag
|
|
47
|
+
from mx_bluesky.common.utils.utils import number_of_frames_from_scan_spec
|
|
48
|
+
|
|
49
|
+
OMEGA_TOLERANCE = 1
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class GridscanPlane(StrEnum):
|
|
53
|
+
OMEGA_XY = "0"
|
|
54
|
+
OMEGA_XZ = "90"
|
|
55
|
+
|
|
44
56
|
|
|
45
57
|
if TYPE_CHECKING:
|
|
46
58
|
from event_model import Event, RunStart, RunStop
|
|
@@ -89,10 +101,10 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
89
101
|
) -> None:
|
|
90
102
|
super().__init__(emit=emit)
|
|
91
103
|
self.ispyb: StoreInIspyb
|
|
92
|
-
self.ispyb_ids: IspybIds = IspybIds()
|
|
93
104
|
self.param_type = param_type
|
|
94
105
|
self._start_of_fgs_uid: str | None = None
|
|
95
106
|
self._processing_start_time: float | None = None
|
|
107
|
+
self._grid_plane_to_id_map: dict[GridscanPlane, int] = {}
|
|
96
108
|
self.data_collection_group_info: DataCollectionGroupInfo | None
|
|
97
109
|
|
|
98
110
|
def activity_gated_start(self, doc: RunStart):
|
|
@@ -108,6 +120,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
108
120
|
mx_bluesky_parameters = doc.get("mx_bluesky_parameters")
|
|
109
121
|
assert isinstance(mx_bluesky_parameters, str)
|
|
110
122
|
self.params = self.param_type.model_validate_json(mx_bluesky_parameters)
|
|
123
|
+
assert isinstance(self.params, DiffractionExperimentWithSample)
|
|
111
124
|
self.ispyb = StoreInIspyb(self.ispyb_config)
|
|
112
125
|
self.data_collection_group_info = populate_data_collection_group(
|
|
113
126
|
self.params
|
|
@@ -118,8 +131,8 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
118
131
|
data_collection_info=populate_remaining_data_collection_info(
|
|
119
132
|
"MX-Bluesky: Xray centring 1 -",
|
|
120
133
|
None,
|
|
121
|
-
|
|
122
|
-
self.params.detector_params,
|
|
134
|
+
DataCollectionInfo(
|
|
135
|
+
data_collection_number=self.params.detector_params.run_number,
|
|
123
136
|
),
|
|
124
137
|
self.params,
|
|
125
138
|
),
|
|
@@ -128,7 +141,11 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
128
141
|
data_collection_info=populate_remaining_data_collection_info(
|
|
129
142
|
"MX-Bluesky: Xray centring 2 -",
|
|
130
143
|
None,
|
|
131
|
-
|
|
144
|
+
DataCollectionInfo(
|
|
145
|
+
data_collection_number=(
|
|
146
|
+
self.params.detector_params.run_number + 1
|
|
147
|
+
),
|
|
148
|
+
),
|
|
132
149
|
self.params,
|
|
133
150
|
)
|
|
134
151
|
),
|
|
@@ -175,7 +192,6 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
175
192
|
assert self.params, "ISPyB handler didn't receive parameters!"
|
|
176
193
|
assert self.data_collection_group_info, "No data collection group"
|
|
177
194
|
data = doc["data"]
|
|
178
|
-
data_collection_id = None
|
|
179
195
|
data_collection_info = DataCollectionInfo(
|
|
180
196
|
xtal_snapshot1=data.get("oav-grid_snapshot-last_path_full_overlay"),
|
|
181
197
|
xtal_snapshot2=data.get("oav-grid_snapshot-last_path_outer"),
|
|
@@ -214,10 +230,9 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
214
230
|
f"by {data_collection_grid_info.steps_y} "
|
|
215
231
|
)
|
|
216
232
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
]
|
|
233
|
+
data_collection_id = self.ispyb_ids.data_collection_ids[
|
|
234
|
+
self._oav_snapshot_event_idx
|
|
235
|
+
]
|
|
221
236
|
self._populate_axis_info(data_collection_info, doc["data"]["smargon-omega"])
|
|
222
237
|
|
|
223
238
|
scan_data_info = ScanDataInfo(
|
|
@@ -228,6 +243,11 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
228
243
|
ISPYB_ZOCALO_CALLBACK_LOGGER.info(
|
|
229
244
|
"Updating ispyb data collection after oav snapshot."
|
|
230
245
|
)
|
|
246
|
+
grid_plane = _smargon_omega_to_xyxz_plane(doc["data"]["smargon-omega"])
|
|
247
|
+
# Snapshots may be triggered in a different order to gridscans, so save
|
|
248
|
+
# the mapping to the data collection id in order to trigger Zocalo correctly.
|
|
249
|
+
self._grid_plane_to_id_map[grid_plane] = data_collection_id
|
|
250
|
+
|
|
231
251
|
self._oav_snapshot_event_idx += 1
|
|
232
252
|
return [scan_data_info]
|
|
233
253
|
|
|
@@ -294,5 +314,43 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
294
314
|
self.ispyb_ids.data_collection_group_id,
|
|
295
315
|
)
|
|
296
316
|
self.data_collection_group_info = None
|
|
317
|
+
self._grid_plane_to_id_map.clear()
|
|
297
318
|
return super().activity_gated_stop(doc)
|
|
298
|
-
return self.
|
|
319
|
+
return self.tag_doc(doc)
|
|
320
|
+
|
|
321
|
+
def tag_doc(self, doc: D) -> D:
|
|
322
|
+
doc = super().tag_doc(doc)
|
|
323
|
+
assert isinstance(doc, dict)
|
|
324
|
+
if self._grid_plane_to_id_map:
|
|
325
|
+
doc["grid_plane_to_id_map"] = self._grid_plane_to_id_map
|
|
326
|
+
return doc # type: ignore
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def generate_start_info_from_omega_map() -> ZocaloInfoGenerator:
|
|
330
|
+
"""
|
|
331
|
+
Generate the zocalo trigger info from bluesky runs where the frame number is
|
|
332
|
+
computed using metadata added to the document by the ISPyB callback and the
|
|
333
|
+
run start which together can be used to determine the correct frame numbering.
|
|
334
|
+
"""
|
|
335
|
+
doc = yield []
|
|
336
|
+
omega_to_scan_spec = doc["omega_to_scan_spec"]
|
|
337
|
+
start_frame = 0
|
|
338
|
+
infos = []
|
|
339
|
+
for i, omega in enumerate([GridscanPlane.OMEGA_XY, GridscanPlane.OMEGA_XZ]):
|
|
340
|
+
frames = number_of_frames_from_scan_spec(omega_to_scan_spec[omega])
|
|
341
|
+
infos.append(
|
|
342
|
+
ZocaloStartInfo(
|
|
343
|
+
doc["grid_plane_to_id_map"][omega], None, start_frame, frames, i
|
|
344
|
+
)
|
|
345
|
+
)
|
|
346
|
+
start_frame += frames
|
|
347
|
+
yield infos
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _smargon_omega_to_xyxz_plane(smargon_omega: float) -> GridscanPlane:
|
|
351
|
+
modulo_180 = abs(smargon_omega) % 180
|
|
352
|
+
is_xy = isclose(modulo_180, 0, abs_tol=OMEGA_TOLERANCE)
|
|
353
|
+
assert is_xy or isclose(modulo_180, 90, abs_tol=OMEGA_TOLERANCE), (
|
|
354
|
+
f"Smargon snapshot omega not in tolerance of compass point {smargon_omega}"
|
|
355
|
+
)
|
|
356
|
+
return GridscanPlane.OMEGA_XY if is_xy else GridscanPlane.OMEGA_XZ
|
|
@@ -1,33 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import numpy
|
|
4
|
-
from dodal.devices.detector import DetectorParams
|
|
5
4
|
from dodal.devices.oav import utils as oav_utils
|
|
6
5
|
|
|
7
6
|
from mx_bluesky.common.external_interaction.ispyb.data_model import (
|
|
8
7
|
DataCollectionGridInfo,
|
|
9
|
-
DataCollectionInfo,
|
|
10
8
|
)
|
|
11
9
|
|
|
12
10
|
|
|
13
|
-
def populate_xz_data_collection_info(detector_params: DetectorParams):
|
|
14
|
-
assert (
|
|
15
|
-
detector_params.omega_start is not None
|
|
16
|
-
and detector_params.run_number is not None
|
|
17
|
-
), "StoreGridscanInIspyb failed to get parameters"
|
|
18
|
-
run_number = detector_params.run_number + 1
|
|
19
|
-
info = DataCollectionInfo(
|
|
20
|
-
data_collection_number=run_number,
|
|
21
|
-
)
|
|
22
|
-
return info
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def populate_xy_data_collection_info(detector_params: DetectorParams):
|
|
26
|
-
return DataCollectionInfo(
|
|
27
|
-
data_collection_number=detector_params.run_number,
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
|
|
31
11
|
def construct_comment_for_gridscan(grid_info: DataCollectionGridInfo) -> str:
|
|
32
12
|
assert grid_info is not None, "StoreGridScanInIspyb failed to get parameters"
|
|
33
13
|
|
|
@@ -8,7 +8,7 @@ from dodal.devices.common_dcm import BaseDCM
|
|
|
8
8
|
from dodal.devices.detector.detector_motion import DetectorMotion
|
|
9
9
|
from dodal.devices.eiger import EigerDetector
|
|
10
10
|
from dodal.devices.fast_grid_scan import (
|
|
11
|
-
|
|
11
|
+
ZebraFastGridScanThreeD,
|
|
12
12
|
)
|
|
13
13
|
from dodal.devices.flux import Flux
|
|
14
14
|
from dodal.devices.mx_phase1.beamstop import Beamstop
|
|
@@ -53,7 +53,7 @@ class GridDetectThenXRayCentreComposite(FlyScanEssentialDevices):
|
|
|
53
53
|
beamstop: Beamstop
|
|
54
54
|
dcm: BaseDCM
|
|
55
55
|
detector_motion: DetectorMotion
|
|
56
|
-
zebra_fast_grid_scan:
|
|
56
|
+
zebra_fast_grid_scan: ZebraFastGridScanThreeD
|
|
57
57
|
flux: Flux
|
|
58
58
|
oav: OAV
|
|
59
59
|
pin_tip_detection: PinTipDetection
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from typing import Generic, TypeVar
|
|
5
|
+
|
|
3
6
|
from dodal.devices.aperturescatterguard import ApertureValue
|
|
4
7
|
from dodal.devices.detector.det_dim_constants import EIGER2_X_9M_SIZE, EIGER2_X_16M_SIZE
|
|
5
8
|
from dodal.devices.detector.detector import DetectorParams
|
|
6
9
|
from dodal.devices.fast_grid_scan import (
|
|
7
|
-
|
|
10
|
+
GridScanParamsCommon,
|
|
11
|
+
ZebraGridScanParamsThreeD,
|
|
8
12
|
)
|
|
9
13
|
from dodal.utils import get_beamline_name
|
|
10
14
|
from pydantic import Field, PrivateAttr
|
|
11
15
|
from scanspec.core import Path as ScanPath
|
|
12
|
-
from scanspec.specs import Line, Static
|
|
16
|
+
from scanspec.specs import Concat, Line, Product, Static
|
|
13
17
|
|
|
14
18
|
from mx_bluesky.common.parameters.components import (
|
|
15
19
|
DiffractionExperimentWithSample,
|
|
@@ -33,6 +37,10 @@ DETECTOR_SIZE_PER_BEAMLINE = {
|
|
|
33
37
|
"i04": EIGER2_X_16M_SIZE,
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
GridScanParamType = TypeVar(
|
|
41
|
+
"GridScanParamType", bound=GridScanParamsCommon, covariant=True
|
|
42
|
+
)
|
|
43
|
+
|
|
36
44
|
|
|
37
45
|
class GridCommon(
|
|
38
46
|
DiffractionExperimentWithSample,
|
|
@@ -87,36 +95,80 @@ class GridCommon(
|
|
|
87
95
|
)
|
|
88
96
|
|
|
89
97
|
|
|
90
|
-
class SpecifiedGrid(XyzStarts, WithScan):
|
|
98
|
+
class SpecifiedGrid(GridCommon, XyzStarts, WithScan, Generic[GridScanParamType]):
|
|
91
99
|
"""A specified grid is one which has defined values for the start position,
|
|
92
100
|
grid and box sizes, etc., as opposed to parameters for a plan which will create
|
|
93
101
|
those parameters at some point (e.g. through optical pin detection)."""
|
|
94
102
|
|
|
103
|
+
grid1_omega_deg: float = Field(default=GridscanParamConstants.OMEGA_1)
|
|
104
|
+
x_step_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM)
|
|
105
|
+
y_step_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM)
|
|
106
|
+
x_steps: int = Field(gt=0)
|
|
107
|
+
y_steps: int = Field(gt=0)
|
|
108
|
+
_set_stub_offsets: bool = PrivateAttr(default_factory=lambda: False)
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
@abstractmethod
|
|
112
|
+
def FGS_params(self) -> GridScanParamType: ...
|
|
113
|
+
|
|
114
|
+
def do_set_stub_offsets(self, value: bool):
|
|
115
|
+
self._set_stub_offsets = value
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def grid_1_spec(self):
|
|
119
|
+
x_end = self.x_start_um + self.x_step_size_um * (self.x_steps - 1)
|
|
120
|
+
y1_end = self.y_start_um + self.y_step_size_um * (self.y_steps - 1)
|
|
121
|
+
grid_1_x = Line("sam_x", self.x_start_um, x_end, self.x_steps)
|
|
122
|
+
grid_1_y = Line("sam_y", self.y_start_um, y1_end, self.y_steps)
|
|
123
|
+
grid_1_z = Static("sam_z", self.z_start_um)
|
|
124
|
+
return grid_1_y.zip(grid_1_z) * ~grid_1_x
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def scan_indices(self) -> list[int]:
|
|
128
|
+
"""The first index of each gridscan, useful for writing nexus files/VDS"""
|
|
129
|
+
return [
|
|
130
|
+
0,
|
|
131
|
+
len(ScanPath(self.grid_1_spec.calculate()).consume().midpoints["sam_x"]),
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
@abstractmethod
|
|
136
|
+
def scan_spec(self) -> Product[str] | Concat[str]:
|
|
137
|
+
"""A fully specified ScanSpec object representing all grids, with x, y, z and
|
|
138
|
+
omega positions."""
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def scan_points(self):
|
|
142
|
+
"""A list of all the points in the scan_spec."""
|
|
143
|
+
return ScanPath(self.scan_spec.calculate()).consume().midpoints
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def scan_points_first_grid(self):
|
|
147
|
+
"""A list of all the points in the first grid scan."""
|
|
148
|
+
return ScanPath(self.grid_1_spec.calculate()).consume().midpoints
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def num_images(self) -> int:
|
|
152
|
+
return len(self.scan_points["sam_x"])
|
|
153
|
+
|
|
95
154
|
|
|
96
155
|
class SpecifiedThreeDGridScan(
|
|
97
|
-
|
|
98
|
-
SpecifiedGrid,
|
|
156
|
+
SpecifiedGrid[ZebraGridScanParamsThreeD],
|
|
99
157
|
SplitScan,
|
|
100
158
|
WithOptionalEnergyChange,
|
|
101
159
|
):
|
|
102
160
|
"""Parameters representing a so-called 3D grid scan, which consists of doing a
|
|
103
161
|
gridscan in X and Y, followed by one in X and Z."""
|
|
104
162
|
|
|
105
|
-
|
|
106
|
-
grid2_omega_deg: float = Field(default=GridscanParamConstants.OMEGA_2)
|
|
107
|
-
x_step_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM)
|
|
108
|
-
y_step_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM)
|
|
163
|
+
z_steps: int = Field(gt=0)
|
|
109
164
|
z_step_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM)
|
|
110
165
|
y2_start_um: float
|
|
111
166
|
z2_start_um: float
|
|
112
|
-
|
|
113
|
-
y_steps: int = Field(gt=0)
|
|
114
|
-
z_steps: int = Field(gt=0)
|
|
115
|
-
_set_stub_offsets: bool = PrivateAttr(default_factory=lambda: False)
|
|
167
|
+
grid2_omega_deg: float = Field(default=GridscanParamConstants.OMEGA_2)
|
|
116
168
|
|
|
117
169
|
@property
|
|
118
|
-
def FGS_params(self) ->
|
|
119
|
-
return
|
|
170
|
+
def FGS_params(self) -> ZebraGridScanParamsThreeD:
|
|
171
|
+
return ZebraGridScanParamsThreeD(
|
|
120
172
|
x_steps=self.x_steps,
|
|
121
173
|
y_steps=self.y_steps,
|
|
122
174
|
z_steps=self.z_steps,
|
|
@@ -133,18 +185,6 @@ class SpecifiedThreeDGridScan(
|
|
|
133
185
|
transmission_fraction=self.transmission_frac,
|
|
134
186
|
)
|
|
135
187
|
|
|
136
|
-
def do_set_stub_offsets(self, value: bool):
|
|
137
|
-
self._set_stub_offsets = value
|
|
138
|
-
|
|
139
|
-
@property
|
|
140
|
-
def grid_1_spec(self):
|
|
141
|
-
x_end = self.x_start_um + self.x_step_size_um * (self.x_steps - 1)
|
|
142
|
-
y1_end = self.y_start_um + self.y_step_size_um * (self.y_steps - 1)
|
|
143
|
-
grid_1_x = Line("sam_x", self.x_start_um, x_end, self.x_steps)
|
|
144
|
-
grid_1_y = Line("sam_y", self.y_start_um, y1_end, self.y_steps)
|
|
145
|
-
grid_1_z = Static("sam_z", self.z_start_um)
|
|
146
|
-
return grid_1_y.zip(grid_1_z) * ~grid_1_x
|
|
147
|
-
|
|
148
188
|
@property
|
|
149
189
|
def grid_2_spec(self):
|
|
150
190
|
x_end = self.x_start_um + self.x_step_size_um * (self.x_steps - 1)
|
|
@@ -154,35 +194,13 @@ class SpecifiedThreeDGridScan(
|
|
|
154
194
|
grid_2_y = Static("sam_y", self.y2_start_um)
|
|
155
195
|
return grid_2_z.zip(grid_2_y) * ~grid_2_x
|
|
156
196
|
|
|
157
|
-
@property
|
|
158
|
-
def scan_indices(self):
|
|
159
|
-
"""The first index of each gridscan, useful for writing nexus files/VDS"""
|
|
160
|
-
return [
|
|
161
|
-
0,
|
|
162
|
-
len(ScanPath(self.grid_1_spec.calculate()).consume().midpoints["sam_x"]),
|
|
163
|
-
]
|
|
164
|
-
|
|
165
197
|
@property
|
|
166
198
|
def scan_spec(self):
|
|
167
199
|
"""A fully specified ScanSpec object representing both grids, with x, y, z and
|
|
168
200
|
omega positions."""
|
|
169
201
|
return self.grid_1_spec.concat(self.grid_2_spec)
|
|
170
202
|
|
|
171
|
-
@property
|
|
172
|
-
def scan_points(self):
|
|
173
|
-
"""A list of all the points in the scan_spec."""
|
|
174
|
-
return ScanPath(self.scan_spec.calculate()).consume().midpoints
|
|
175
|
-
|
|
176
|
-
@property
|
|
177
|
-
def scan_points_first_grid(self):
|
|
178
|
-
"""A list of all the points in the first grid scan."""
|
|
179
|
-
return ScanPath(self.grid_1_spec.calculate()).consume().midpoints
|
|
180
|
-
|
|
181
203
|
@property
|
|
182
204
|
def scan_points_second_grid(self):
|
|
183
205
|
"""A list of all the points in the second grid scan."""
|
|
184
206
|
return ScanPath(self.grid_2_spec.calculate()).consume().midpoints
|
|
185
|
-
|
|
186
|
-
@property
|
|
187
|
-
def num_images(self) -> int:
|
|
188
|
-
return len(self.scan_points["sam_x"])
|
mx_bluesky/hyperion/__main__.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import signal
|
|
2
3
|
import threading
|
|
3
4
|
from dataclasses import asdict
|
|
4
5
|
from sys import argv
|
|
@@ -32,9 +33,10 @@ from mx_bluesky.hyperion.parameters.cli import (
|
|
|
32
33
|
HyperionMode,
|
|
33
34
|
parse_cli_args,
|
|
34
35
|
)
|
|
35
|
-
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
36
|
+
from mx_bluesky.hyperion.parameters.constants import CONST, HyperionConstants
|
|
36
37
|
from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
|
|
37
38
|
from mx_bluesky.hyperion.plan_runner import PlanRunner
|
|
39
|
+
from mx_bluesky.hyperion.plan_runner_api import create_server_for_udc
|
|
38
40
|
from mx_bluesky.hyperion.runner import (
|
|
39
41
|
GDARunner,
|
|
40
42
|
StatusAndMessage,
|
|
@@ -170,7 +172,7 @@ def main():
|
|
|
170
172
|
"""Main application entry point."""
|
|
171
173
|
args = parse_cli_args()
|
|
172
174
|
initialise_globals(args)
|
|
173
|
-
hyperion_port =
|
|
175
|
+
hyperion_port = HyperionConstants.HYPERION_PORT
|
|
174
176
|
context = setup_context(dev_mode=args.dev_mode)
|
|
175
177
|
|
|
176
178
|
if args.mode == HyperionMode.GDA:
|
|
@@ -188,7 +190,18 @@ def main():
|
|
|
188
190
|
)
|
|
189
191
|
runner.wait_on_queue()
|
|
190
192
|
else:
|
|
191
|
-
|
|
193
|
+
plan_runner = PlanRunner(context, args.dev_mode)
|
|
194
|
+
create_server_for_udc(plan_runner)
|
|
195
|
+
_register_sigterm_handler(plan_runner)
|
|
196
|
+
run_forever(plan_runner)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _register_sigterm_handler(runner: PlanRunner):
|
|
200
|
+
def shutdown_on_sigterm(sig_num, frame):
|
|
201
|
+
LOGGER.info("Received SIGTERM, shutting down...")
|
|
202
|
+
runner.shutdown()
|
|
203
|
+
|
|
204
|
+
signal.signal(signal.SIGTERM, shutdown_on_sigterm)
|
|
192
205
|
|
|
193
206
|
|
|
194
207
|
if __name__ == "__main__":
|
|
@@ -6,8 +6,14 @@ from blueapi.core.context import BlueskyContext
|
|
|
6
6
|
from bluesky import plan_stubs as bps
|
|
7
7
|
from bluesky import preprocessors as bpp
|
|
8
8
|
from bluesky.utils import MsgGenerator, RunEngineInterrupted
|
|
9
|
+
from dodal.common.beamlines.commissioning_mode import set_commissioning_signal
|
|
10
|
+
from dodal.devices.aperturescatterguard import ApertureScatterguard
|
|
9
11
|
from dodal.devices.baton import Baton
|
|
12
|
+
from dodal.devices.motors import XYZStage
|
|
13
|
+
from dodal.devices.robot import BartRobot
|
|
14
|
+
from dodal.devices.smargon import Smargon
|
|
10
15
|
|
|
16
|
+
from mx_bluesky.common.device_setup_plans.robot_load_unload import robot_unload
|
|
11
17
|
from mx_bluesky.common.experiment_plans.inner_plans.udc_default_state import (
|
|
12
18
|
UDCDefaultDevices,
|
|
13
19
|
move_to_udc_default_state,
|
|
@@ -77,6 +83,7 @@ def run_udc_when_requested(context: BlueskyContext, runner: PlanRunner):
|
|
|
77
83
|
|
|
78
84
|
def acquire_baton() -> MsgGenerator:
|
|
79
85
|
yield from _wait_for_hyperion_requested(baton)
|
|
86
|
+
LOGGER.debug("Hyperion is now current baton holder.")
|
|
80
87
|
yield from bps.abs_set(baton.current_user, HYPERION_USER)
|
|
81
88
|
|
|
82
89
|
def collect() -> MsgGenerator:
|
|
@@ -96,14 +103,20 @@ def run_udc_when_requested(context: BlueskyContext, runner: PlanRunner):
|
|
|
96
103
|
|
|
97
104
|
# re-fetch the baton because the device has been reinstantiated
|
|
98
105
|
baton = _get_baton(context)
|
|
106
|
+
current_visit: str | None = None
|
|
99
107
|
while (yield from _is_requesting_baton(baton)):
|
|
100
|
-
yield from _fetch_and_process_agamemnon_instruction(
|
|
108
|
+
current_visit = yield from _fetch_and_process_agamemnon_instruction(
|
|
109
|
+
baton, runner, current_visit
|
|
110
|
+
)
|
|
111
|
+
if current_visit:
|
|
112
|
+
yield from _perform_robot_unload(runner.context, current_visit)
|
|
101
113
|
|
|
102
114
|
def release_baton() -> MsgGenerator:
|
|
103
115
|
# If hyperion has given up the baton itself we need to also release requested
|
|
104
116
|
# user so that hyperion doesn't think we're requested again
|
|
105
117
|
baton = _get_baton(context)
|
|
106
|
-
previous_requested_user = yield from
|
|
118
|
+
previous_requested_user = yield from _unrequest_baton(baton)
|
|
119
|
+
LOGGER.debug("Hyperion no longer current baton holder.")
|
|
107
120
|
yield from bps.abs_set(baton.current_user, NO_USER, wait=True)
|
|
108
121
|
_raise_baton_released_alert(get_alerting_service(), previous_requested_user)
|
|
109
122
|
|
|
@@ -111,11 +124,11 @@ def run_udc_when_requested(context: BlueskyContext, runner: PlanRunner):
|
|
|
111
124
|
yield from bpp.contingency_wrapper(collect(), final_plan=release_baton)
|
|
112
125
|
|
|
113
126
|
context.run_engine(acquire_baton())
|
|
114
|
-
_initialise_udc(context)
|
|
127
|
+
_initialise_udc(context, runner.is_dev_mode)
|
|
115
128
|
context.run_engine(collect_then_release())
|
|
116
129
|
|
|
117
130
|
|
|
118
|
-
def _initialise_udc(context: BlueskyContext):
|
|
131
|
+
def _initialise_udc(context: BlueskyContext, dev_mode: bool):
|
|
119
132
|
"""
|
|
120
133
|
Perform all initialisation that happens at the start of UDC just after the
|
|
121
134
|
baton is acquired, but before we execute any plans or move hardware.
|
|
@@ -125,21 +138,25 @@ def _initialise_udc(context: BlueskyContext):
|
|
|
125
138
|
"""
|
|
126
139
|
LOGGER.info("Initialising mx-bluesky for UDC start...")
|
|
127
140
|
clear_all_device_caches(context)
|
|
128
|
-
|
|
141
|
+
LOGGER.debug("Reinitialising beamline devices")
|
|
142
|
+
setup_devices(context, dev_mode)
|
|
143
|
+
set_commissioning_signal(_get_baton(context).commissioning)
|
|
129
144
|
|
|
130
145
|
|
|
131
146
|
def _wait_for_hyperion_requested(baton: Baton):
|
|
147
|
+
LOGGER.debug("Hyperion waiting for baton...")
|
|
132
148
|
SLEEP_PER_CHECK = 0.1
|
|
133
149
|
while True:
|
|
134
150
|
requested_user = yield from bps.rd(baton.requested_user)
|
|
135
151
|
if requested_user == HYPERION_USER:
|
|
152
|
+
LOGGER.debug("Baton requested for Hyperion")
|
|
136
153
|
break
|
|
137
154
|
yield from bps.sleep(SLEEP_PER_CHECK)
|
|
138
155
|
|
|
139
156
|
|
|
140
157
|
def _fetch_and_process_agamemnon_instruction(
|
|
141
|
-
baton: Baton, runner: PlanRunner
|
|
142
|
-
) -> MsgGenerator:
|
|
158
|
+
baton: Baton, runner: PlanRunner, current_visit: str | None
|
|
159
|
+
) -> MsgGenerator[str | None]:
|
|
143
160
|
parameter_list: Sequence[MxBlueskyParameters] = create_parameters_from_agamemnon()
|
|
144
161
|
if parameter_list:
|
|
145
162
|
for parameters in parameter_list:
|
|
@@ -148,6 +165,7 @@ def _fetch_and_process_agamemnon_instruction(
|
|
|
148
165
|
)
|
|
149
166
|
match parameters:
|
|
150
167
|
case LoadCentreCollect():
|
|
168
|
+
current_visit = parameters.visit
|
|
151
169
|
devices: Any = create_devices(runner.context)
|
|
152
170
|
yield from runner.execute_plan(
|
|
153
171
|
partial(load_centre_collect_full, devices, parameters)
|
|
@@ -161,7 +179,8 @@ def _fetch_and_process_agamemnon_instruction(
|
|
|
161
179
|
else:
|
|
162
180
|
_raise_udc_completed_alert(get_alerting_service())
|
|
163
181
|
# Release the baton for orderly exit from the instruction loop
|
|
164
|
-
yield from
|
|
182
|
+
yield from _unrequest_baton(baton)
|
|
183
|
+
return current_visit
|
|
165
184
|
|
|
166
185
|
|
|
167
186
|
def _raise_udc_start_alert(alert_service: AlertService):
|
|
@@ -205,7 +224,7 @@ def _get_baton(context: BlueskyContext) -> Baton:
|
|
|
205
224
|
return find_device_in_context(context, "baton", Baton)
|
|
206
225
|
|
|
207
226
|
|
|
208
|
-
def
|
|
227
|
+
def _unrequest_baton(baton: Baton) -> MsgGenerator[str]:
|
|
209
228
|
"""Relinquish the requested user of the baton if it is not already requested
|
|
210
229
|
by another user.
|
|
211
230
|
|
|
@@ -214,6 +233,17 @@ def _safely_release_baton(baton: Baton) -> MsgGenerator[str]:
|
|
|
214
233
|
"""
|
|
215
234
|
requested_user = yield from bps.rd(baton.requested_user)
|
|
216
235
|
if requested_user == HYPERION_USER:
|
|
236
|
+
LOGGER.debug("Hyperion no longer requesting baton")
|
|
217
237
|
yield from bps.abs_set(baton.requested_user, NO_USER)
|
|
218
238
|
return NO_USER
|
|
219
239
|
return requested_user
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _perform_robot_unload(context: BlueskyContext, visit: str) -> MsgGenerator:
|
|
243
|
+
robot = find_device_in_context(context, "robot", BartRobot)
|
|
244
|
+
smargon = find_device_in_context(context, "smargon", Smargon)
|
|
245
|
+
aperture_scatterguard = find_device_in_context(
|
|
246
|
+
context, "aperture_scatterguard", ApertureScatterguard
|
|
247
|
+
)
|
|
248
|
+
lower_gonio = find_device_in_context(context, "lower_gonio", XYZStage)
|
|
249
|
+
yield from robot_unload(robot, smargon, aperture_scatterguard, lower_gonio, visit)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
from bluesky import plan_stubs as bps
|
|
3
|
+
from bluesky.utils import FailedStatus
|
|
3
4
|
from dodal.devices.smargon import CombinedMove, Smargon
|
|
5
|
+
from ophyd_async.epics.motor import MotorLimitsException
|
|
4
6
|
|
|
5
7
|
from mx_bluesky.common.utils.exceptions import SampleException
|
|
6
8
|
|
|
@@ -9,12 +11,15 @@ def move_smargon_warn_on_out_of_range(
|
|
|
9
11
|
smargon: Smargon, position: np.ndarray | list[float] | tuple[float, float, float]
|
|
10
12
|
):
|
|
11
13
|
"""Throws a SampleException if the specified position is out of range for the
|
|
12
|
-
smargon. Otherwise moves to that position."""
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"Pin tip centring failed - pin too long/short/bent and out of range"
|
|
14
|
+
smargon. Otherwise moves to that position. The check is from ophyd-async"""
|
|
15
|
+
try:
|
|
16
|
+
yield from bps.mv(
|
|
17
|
+
smargon, CombinedMove(x=position[0], y=position[1], z=position[2])
|
|
17
18
|
)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
except FailedStatus as fs:
|
|
20
|
+
if isinstance(fs.__cause__, MotorLimitsException):
|
|
21
|
+
raise SampleException(
|
|
22
|
+
"Pin tip centring failed - pin too long/short/bent and out of range"
|
|
23
|
+
) from fs.__cause__
|
|
24
|
+
else:
|
|
25
|
+
raise fs
|