mx-bluesky 1.5.1__py3-none-any.whl → 1.5.3__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 +16 -3
- mx_bluesky/beamlines/i04/__init__.py +8 -1
- mx_bluesky/beamlines/i04/callbacks/murko_callback.py +56 -1
- mx_bluesky/beamlines/i04/experiment_plans/__init__.py +0 -0
- mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +262 -0
- mx_bluesky/beamlines/i24/serial/blueapi_config.yaml +2 -2
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +3 -1
- mx_bluesky/common/experiment_plans/change_aperture_then_move_plan.py +5 -1
- mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +26 -3
- mx_bluesky/common/experiment_plans/common_grid_detect_then_xray_centre_plan.py +1 -0
- mx_bluesky/common/experiment_plans/inner_plans/do_fgs.py +3 -1
- mx_bluesky/common/experiment_plans/oav_grid_detection_plan.py +12 -2
- mx_bluesky/common/external_interaction/alerting/__init__.py +13 -0
- mx_bluesky/common/external_interaction/alerting/_service.py +82 -0
- mx_bluesky/common/external_interaction/alerting/log_based_service.py +57 -0
- mx_bluesky/common/external_interaction/callbacks/sample_handling/sample_handling_callback.py +28 -4
- mx_bluesky/common/external_interaction/config_server.py +151 -54
- mx_bluesky/common/external_interaction/ispyb/ispyb_store.py +11 -6
- mx_bluesky/common/parameters/__init__.py +0 -0
- mx_bluesky/common/parameters/constants.py +27 -8
- mx_bluesky/common/parameters/device_composites.py +1 -1
- mx_bluesky/common/parameters/gridscan.py +2 -1
- mx_bluesky/hyperion/__main__.py +51 -179
- mx_bluesky/hyperion/baton_handler.py +142 -54
- mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +29 -24
- mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +4 -93
- mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +23 -38
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +12 -4
- mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +1 -1
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +7 -8
- mx_bluesky/hyperion/external_interaction/agamemnon.py +128 -73
- mx_bluesky/hyperion/external_interaction/alerting/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/alerting/constants.py +12 -0
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +5 -0
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +2 -2
- mx_bluesky/hyperion/external_interaction/config_server.py +12 -31
- mx_bluesky/hyperion/parameters/cli.py +15 -3
- mx_bluesky/hyperion/parameters/components.py +7 -5
- mx_bluesky/hyperion/parameters/constants.py +21 -6
- mx_bluesky/hyperion/parameters/gridscan.py +22 -14
- mx_bluesky/hyperion/parameters/load_centre_collect.py +1 -14
- mx_bluesky/hyperion/parameters/robot_load.py +1 -4
- mx_bluesky/hyperion/parameters/rotation.py +1 -2
- mx_bluesky/hyperion/plan_runner.py +78 -0
- mx_bluesky/hyperion/runner.py +189 -0
- mx_bluesky/hyperion/utils/context.py +19 -5
- mx_bluesky/phase1_zebra/__init__.py +1 -0
- mx_bluesky/phase1_zebra/device_setup_plans/__init__.py +0 -0
- mx_bluesky/phase1_zebra/device_setup_plans/setup_zebra.py +112 -0
- {mx_bluesky-1.5.1.dist-info → mx_bluesky-1.5.3.dist-info}/METADATA +5 -4
- {mx_bluesky-1.5.1.dist-info → mx_bluesky-1.5.3.dist-info}/RECORD +57 -44
- {mx_bluesky-1.5.1.dist-info → mx_bluesky-1.5.3.dist-info}/entry_points.txt +0 -2
- /mx_bluesky/common/experiment_plans/{read_hardware.py → inner_plans/read_hardware.py} +0 -0
- /mx_bluesky/common/experiment_plans/{write_sample_status.py → inner_plans/write_sample_status.py} +0 -0
- {mx_bluesky-1.5.1.dist-info → mx_bluesky-1.5.3.dist-info}/WHEEL +0 -0
- {mx_bluesky-1.5.1.dist-info → mx_bluesky-1.5.3.dist-info}/licenses/LICENSE +0 -0
- {mx_bluesky-1.5.1.dist-info → mx_bluesky-1.5.3.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import bluesky.plan_stubs as bps
|
|
2
|
-
from bluesky.utils import MsgGenerator
|
|
3
2
|
from dodal.devices.zebra.zebra import (
|
|
4
3
|
ArmDemand,
|
|
5
4
|
EncEnum,
|
|
@@ -12,9 +11,11 @@ from dodal.devices.zebra.zebra_controlled_shutter import (
|
|
|
12
11
|
ZebraShutterControl,
|
|
13
12
|
)
|
|
14
13
|
|
|
14
|
+
from mx_bluesky.common.parameters.constants import ZEBRA_STATUS_TIMEOUT
|
|
15
15
|
from mx_bluesky.common.utils.log import LOGGER
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
from mx_bluesky.phase1_zebra.device_setup_plans.setup_zebra import (
|
|
17
|
+
configure_zebra_and_shutter_for_auto_shutter,
|
|
18
|
+
)
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
def arm_zebra(zebra: Zebra):
|
|
@@ -35,47 +36,6 @@ def tidy_up_zebra_after_rotation_scan(
|
|
|
35
36
|
yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
|
|
36
37
|
|
|
37
38
|
|
|
38
|
-
def set_shutter_auto_input(zebra: Zebra, input: int, group="set_shutter_trigger"):
|
|
39
|
-
"""Set the signal that controls the shutter. We use the second input to the
|
|
40
|
-
Zebra's AND2 gate for this input. ZebraShutter control mode must be in auto for this input to take control
|
|
41
|
-
|
|
42
|
-
For more details see the ZebraShutter device."""
|
|
43
|
-
auto_gate = zebra.mapping.AND_GATE_FOR_AUTO_SHUTTER
|
|
44
|
-
auto_shutter_control = zebra.logic_gates.and_gates[auto_gate]
|
|
45
|
-
yield from bps.abs_set(auto_shutter_control.sources[2], input, group)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def configure_zebra_and_shutter_for_auto_shutter(
|
|
49
|
-
zebra: Zebra, zebra_shutter: ZebraShutter, input: int, group="use_automatic_shutter"
|
|
50
|
-
):
|
|
51
|
-
"""Set the shutter to auto mode, and configure the zebra to trigger the shutter on
|
|
52
|
-
an input source. For the input, use one of the source constants in zebra.py
|
|
53
|
-
|
|
54
|
-
When the shutter is in auto/manual, logic in EPICS sets the Zebra's
|
|
55
|
-
SOFT_IN1 to low/high respectively. The Zebra's AND2 gate should be used to control the shutter while in auto mode.
|
|
56
|
-
To do this, we need (AND2 = SOFT_IN1 AND input), where input is the zebra signal we want to control the shutter when in auto mode.
|
|
57
|
-
"""
|
|
58
|
-
# See https://github.com/DiamondLightSource/dodal/issues/813 for better typing here.
|
|
59
|
-
|
|
60
|
-
# Set shutter to auto mode
|
|
61
|
-
yield from bps.abs_set(
|
|
62
|
-
zebra_shutter.control_mode, ZebraShutterControl.AUTO, group=group
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
auto_gate = zebra.mapping.AND_GATE_FOR_AUTO_SHUTTER
|
|
66
|
-
|
|
67
|
-
# Set first input of AND2 gate to SOFT_IN1, which is high when shutter is in auto mode
|
|
68
|
-
# Note the Zebra should ALWAYS be setup this way. See https://github.com/DiamondLightSource/mx-bluesky/issues/551
|
|
69
|
-
yield from bps.abs_set(
|
|
70
|
-
zebra.logic_gates.and_gates[auto_gate].sources[1],
|
|
71
|
-
zebra.mapping.sources.SOFT_IN1,
|
|
72
|
-
group=group,
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
# Set the second input of AND2 gate to the requested zebra input source
|
|
76
|
-
yield from set_shutter_auto_input(zebra, input, group=group)
|
|
77
|
-
|
|
78
|
-
|
|
79
39
|
def setup_zebra_for_rotation(
|
|
80
40
|
zebra: Zebra,
|
|
81
41
|
zebra_shutter: ZebraShutter,
|
|
@@ -155,55 +115,6 @@ def setup_zebra_for_rotation(
|
|
|
155
115
|
yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
|
|
156
116
|
|
|
157
117
|
|
|
158
|
-
def setup_zebra_for_gridscan(
|
|
159
|
-
zebra: Zebra,
|
|
160
|
-
zebra_shutter: ZebraShutter,
|
|
161
|
-
group="setup_zebra_for_gridscan",
|
|
162
|
-
wait=True,
|
|
163
|
-
):
|
|
164
|
-
# Set shutter to automatic and to trigger via motion controller GPIO signal (IN4_TTL)
|
|
165
|
-
yield from configure_zebra_and_shutter_for_auto_shutter(
|
|
166
|
-
zebra, zebra_shutter, zebra.mapping.sources.IN4_TTL, group=group
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
yield from bps.abs_set(
|
|
170
|
-
zebra.output.out_pvs[zebra.mapping.outputs.TTL_DETECTOR],
|
|
171
|
-
zebra.mapping.sources.IN3_TTL,
|
|
172
|
-
group=group,
|
|
173
|
-
)
|
|
174
|
-
yield from bps.abs_set(
|
|
175
|
-
zebra.output.out_pvs[zebra.mapping.outputs.TTL_XSPRESS3],
|
|
176
|
-
zebra.mapping.sources.DISCONNECT,
|
|
177
|
-
group=group,
|
|
178
|
-
)
|
|
179
|
-
yield from bps.abs_set(
|
|
180
|
-
zebra.output.pulse_1.input, zebra.mapping.sources.DISCONNECT, group=group
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
if wait:
|
|
184
|
-
yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
def tidy_up_zebra_after_gridscan(
|
|
188
|
-
zebra: Zebra,
|
|
189
|
-
zebra_shutter: ZebraShutter,
|
|
190
|
-
group="tidy_up_zebra_after_gridscan",
|
|
191
|
-
wait=True,
|
|
192
|
-
) -> MsgGenerator:
|
|
193
|
-
yield from bps.abs_set(
|
|
194
|
-
zebra.output.out_pvs[zebra.mapping.outputs.TTL_DETECTOR],
|
|
195
|
-
zebra.mapping.sources.PC_PULSE,
|
|
196
|
-
group=group,
|
|
197
|
-
)
|
|
198
|
-
yield from bps.abs_set(
|
|
199
|
-
zebra_shutter.control_mode, ZebraShutterControl.MANUAL, group=group
|
|
200
|
-
)
|
|
201
|
-
yield from set_shutter_auto_input(zebra, zebra.mapping.sources.PC_GATE, group=group)
|
|
202
|
-
|
|
203
|
-
if wait:
|
|
204
|
-
yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
|
|
205
|
-
|
|
206
|
-
|
|
207
118
|
def setup_zebra_for_panda_flyscan(
|
|
208
119
|
zebra: Zebra,
|
|
209
120
|
zebra_shutter: ZebraShutter,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Callable
|
|
3
4
|
from functools import partial
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
@@ -19,14 +20,19 @@ from mx_bluesky.hyperion.device_setup_plans.setup_panda import (
|
|
|
19
20
|
setup_panda_for_flyscan,
|
|
20
21
|
)
|
|
21
22
|
from mx_bluesky.hyperion.device_setup_plans.setup_zebra import (
|
|
22
|
-
setup_zebra_for_gridscan,
|
|
23
23
|
setup_zebra_for_panda_flyscan,
|
|
24
|
-
|
|
24
|
+
)
|
|
25
|
+
from mx_bluesky.hyperion.external_interaction.config_server import (
|
|
26
|
+
get_hyperion_config_client,
|
|
25
27
|
)
|
|
26
28
|
from mx_bluesky.hyperion.parameters.device_composites import (
|
|
27
29
|
HyperionFlyScanXRayCentreComposite,
|
|
28
30
|
)
|
|
29
31
|
from mx_bluesky.hyperion.parameters.gridscan import HyperionSpecifiedThreeDGridScan
|
|
32
|
+
from mx_bluesky.phase1_zebra.device_setup_plans.setup_zebra import (
|
|
33
|
+
setup_zebra_for_gridscan,
|
|
34
|
+
tidy_up_zebra_after_gridscan,
|
|
35
|
+
)
|
|
30
36
|
|
|
31
37
|
|
|
32
38
|
class SmargonSpeedException(Exception):
|
|
@@ -59,9 +65,11 @@ def construct_hyperion_specific_features(
|
|
|
59
65
|
xrc_composite.eiger.bit_depth,
|
|
60
66
|
]
|
|
61
67
|
|
|
62
|
-
|
|
68
|
+
setup_trigger_plan: Callable[..., MsgGenerator]
|
|
69
|
+
|
|
70
|
+
if get_hyperion_config_client().get_feature_flags().USE_PANDA_FOR_GRIDSCAN:
|
|
63
71
|
setup_trigger_plan = _panda_triggering_setup
|
|
64
|
-
tidy_plan = _panda_tidy
|
|
72
|
+
tidy_plan = partial(_panda_tidy, xrc_composite)
|
|
65
73
|
set_flyscan_params_plan = partial(
|
|
66
74
|
set_fast_grid_scan_params,
|
|
67
75
|
xrc_composite.panda_fast_grid_scan,
|
|
@@ -70,8 +78,14 @@ def construct_hyperion_specific_features(
|
|
|
70
78
|
fgs_motors = xrc_composite.panda_fast_grid_scan
|
|
71
79
|
|
|
72
80
|
else:
|
|
73
|
-
setup_trigger_plan =
|
|
74
|
-
tidy_plan = partial(
|
|
81
|
+
setup_trigger_plan = setup_zebra_for_gridscan
|
|
82
|
+
tidy_plan = partial(
|
|
83
|
+
tidy_up_zebra_after_gridscan,
|
|
84
|
+
xrc_composite.zebra,
|
|
85
|
+
xrc_composite.sample_shutter,
|
|
86
|
+
group="flyscan_zebra_tidy",
|
|
87
|
+
wait=True,
|
|
88
|
+
)
|
|
75
89
|
set_flyscan_params_plan = partial(
|
|
76
90
|
set_fast_grid_scan_params,
|
|
77
91
|
xrc_composite.zebra_fast_grid_scan,
|
|
@@ -89,46 +103,17 @@ def construct_hyperion_specific_features(
|
|
|
89
103
|
)
|
|
90
104
|
|
|
91
105
|
|
|
92
|
-
def _generic_tidy(
|
|
93
|
-
xrc_composite: HyperionFlyScanXRayCentreComposite, group, wait=True
|
|
94
|
-
) -> MsgGenerator:
|
|
95
|
-
LOGGER.info("Tidying up Zebra")
|
|
96
|
-
yield from tidy_up_zebra_after_gridscan(
|
|
97
|
-
xrc_composite.zebra, xrc_composite.sample_shutter, group=group, wait=wait
|
|
98
|
-
)
|
|
99
|
-
LOGGER.info("Tidying up Zocalo")
|
|
100
|
-
# make sure we don't consume any other results
|
|
101
|
-
yield from bps.unstage(xrc_composite.zocalo, group=group, wait=wait)
|
|
102
|
-
|
|
103
|
-
# Turn off dev/shm streaming to avoid filling disk, see https://github.com/DiamondLightSource/hyperion/issues/1395
|
|
104
|
-
LOGGER.info("Turning off Eiger dev/shm streaming")
|
|
105
|
-
# Fix types in ophyd-async (https://github.com/DiamondLightSource/mx-bluesky/issues/855)
|
|
106
|
-
yield from bps.abs_set(
|
|
107
|
-
xrc_composite.eiger.odin.fan.dev_shm_enable, # type: ignore
|
|
108
|
-
0,
|
|
109
|
-
group=group,
|
|
110
|
-
wait=wait,
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
|
|
114
106
|
def _panda_tidy(xrc_composite: HyperionFlyScanXRayCentreComposite):
|
|
115
107
|
group = "panda_flyscan_tidy"
|
|
116
108
|
LOGGER.info("Disabling panda blocks")
|
|
117
109
|
yield from disarm_panda_for_gridscan(xrc_composite.panda, group)
|
|
118
|
-
yield from
|
|
110
|
+
yield from tidy_up_zebra_after_gridscan(
|
|
111
|
+
xrc_composite.zebra, xrc_composite.sample_shutter, group=group, wait=False
|
|
112
|
+
)
|
|
119
113
|
yield from bps.wait(group, timeout=10)
|
|
120
114
|
yield from bps.unstage(xrc_composite.panda)
|
|
121
115
|
|
|
122
116
|
|
|
123
|
-
def _zebra_triggering_setup(
|
|
124
|
-
xrc_composite: HyperionFlyScanXRayCentreComposite,
|
|
125
|
-
parameters: HyperionSpecifiedThreeDGridScan,
|
|
126
|
-
) -> MsgGenerator:
|
|
127
|
-
yield from setup_zebra_for_gridscan(
|
|
128
|
-
xrc_composite.zebra, xrc_composite.sample_shutter, wait=True
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
|
|
132
117
|
def _panda_triggering_setup(
|
|
133
118
|
xrc_composite: HyperionFlyScanXRayCentreComposite,
|
|
134
119
|
parameters: HyperionSpecifiedThreeDGridScan,
|
|
@@ -24,7 +24,10 @@ from mx_bluesky.hyperion.experiment_plans.rotation_scan_plan import (
|
|
|
24
24
|
RotationScanComposite,
|
|
25
25
|
rotation_scan_internal,
|
|
26
26
|
)
|
|
27
|
-
from mx_bluesky.hyperion.
|
|
27
|
+
from mx_bluesky.hyperion.external_interaction.config_server import (
|
|
28
|
+
get_hyperion_config_client,
|
|
29
|
+
)
|
|
30
|
+
from mx_bluesky.hyperion.parameters.constants import CONST, I03Constants
|
|
28
31
|
from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
|
|
29
32
|
from mx_bluesky.hyperion.parameters.rotation import RotationScanPerSweep
|
|
30
33
|
|
|
@@ -51,7 +54,8 @@ def load_centre_collect_full(
|
|
|
51
54
|
* If X-ray centring finds a diffracting centre then move to that centre and
|
|
52
55
|
* do a collection with the specified parameters.
|
|
53
56
|
"""
|
|
54
|
-
|
|
57
|
+
|
|
58
|
+
get_hyperion_config_client().refresh_cache()
|
|
55
59
|
|
|
56
60
|
if not oav_params:
|
|
57
61
|
oav_params = OAVParameters(context="xrayCentring")
|
|
@@ -60,7 +64,11 @@ def load_centre_collect_full(
|
|
|
60
64
|
@set_run_key_decorator(CONST.PLAN.LOAD_CENTRE_COLLECT)
|
|
61
65
|
@run_decorator(
|
|
62
66
|
md={
|
|
63
|
-
"metadata": {
|
|
67
|
+
"metadata": {
|
|
68
|
+
"sample_id": parameters.sample_id,
|
|
69
|
+
"visit": parameters.visit,
|
|
70
|
+
"container": parameters.sample_puck,
|
|
71
|
+
},
|
|
64
72
|
"activate_callbacks": ["BeamDrawingCallback", "SampleHandlingCallback"],
|
|
65
73
|
"with_snapshot": parameters.multi_rotation_scan.model_dump_json(
|
|
66
74
|
include=WithSnapshot.model_fields.keys() # type: ignore
|
|
@@ -117,7 +125,7 @@ def load_centre_collect_full(
|
|
|
117
125
|
|
|
118
126
|
multi_rotation.rotation_scans.clear()
|
|
119
127
|
|
|
120
|
-
is_alternating =
|
|
128
|
+
is_alternating = I03Constants.ALTERNATE_ROTATION_DIRECTION
|
|
121
129
|
|
|
122
130
|
generator = rotation_scan_generator(is_alternating)
|
|
123
131
|
next(generator)
|
|
@@ -155,7 +155,7 @@ def pin_tip_centre_plan(
|
|
|
155
155
|
tip = yield from move_pin_into_view(pin_tip_detect, smargon)
|
|
156
156
|
yield from offset_and_move(tip)
|
|
157
157
|
|
|
158
|
-
yield from bps.mvr(smargon.omega, 90)
|
|
158
|
+
yield from bps.mvr(smargon.omega, -90)
|
|
159
159
|
|
|
160
160
|
# need to wait for the OAV image to update
|
|
161
161
|
# See #673 for improvements
|
|
@@ -37,16 +37,16 @@ from mx_bluesky.common.device_setup_plans.manipulate_sample import (
|
|
|
37
37
|
from mx_bluesky.common.device_setup_plans.utils import (
|
|
38
38
|
start_preparing_data_collection_then_do_plan,
|
|
39
39
|
)
|
|
40
|
+
from mx_bluesky.common.experiment_plans.inner_plans.read_hardware import (
|
|
41
|
+
read_hardware_for_zocalo,
|
|
42
|
+
standard_read_hardware_during_collection,
|
|
43
|
+
standard_read_hardware_pre_collection,
|
|
44
|
+
)
|
|
40
45
|
from mx_bluesky.common.experiment_plans.oav_snapshot_plan import (
|
|
41
46
|
OavSnapshotComposite,
|
|
42
47
|
oav_snapshot_plan,
|
|
43
48
|
setup_beamline_for_OAV,
|
|
44
49
|
)
|
|
45
|
-
from mx_bluesky.common.experiment_plans.read_hardware import (
|
|
46
|
-
read_hardware_for_zocalo,
|
|
47
|
-
standard_read_hardware_during_collection,
|
|
48
|
-
standard_read_hardware_pre_collection,
|
|
49
|
-
)
|
|
50
50
|
from mx_bluesky.common.parameters.components import WithSnapshot
|
|
51
51
|
from mx_bluesky.common.preprocessors.preprocessors import (
|
|
52
52
|
transmission_and_xbpm_feedback_for_collection_decorator,
|
|
@@ -58,7 +58,7 @@ from mx_bluesky.hyperion.device_setup_plans.setup_zebra import (
|
|
|
58
58
|
setup_zebra_for_rotation,
|
|
59
59
|
tidy_up_zebra_after_rotation_scan,
|
|
60
60
|
)
|
|
61
|
-
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
61
|
+
from mx_bluesky.hyperion.parameters.constants import CONST, I03Constants
|
|
62
62
|
from mx_bluesky.hyperion.parameters.rotation import (
|
|
63
63
|
RotationScan,
|
|
64
64
|
SingleRotationScan,
|
|
@@ -133,7 +133,7 @@ def calculate_motion_profile(
|
|
|
133
133
|
direction = params.rotation_direction
|
|
134
134
|
start_scan_deg = params.omega_start_deg
|
|
135
135
|
|
|
136
|
-
if
|
|
136
|
+
if I03Constants.OMEGA_FLIP:
|
|
137
137
|
# If omega_flip is True then the motor omega axis is inverted with respect to the
|
|
138
138
|
# hyperion coordinate system.
|
|
139
139
|
start_scan_deg = -start_scan_deg
|
|
@@ -386,7 +386,6 @@ def rotation_scan_internal(
|
|
|
386
386
|
parameters: RotationScan,
|
|
387
387
|
oav_params: OAVParameters | None = None,
|
|
388
388
|
) -> MsgGenerator:
|
|
389
|
-
parameters.features.update_self_from_server()
|
|
390
389
|
if not oav_params:
|
|
391
390
|
oav_params = OAVParameters(context="xrayCentring")
|
|
392
391
|
eiger: EigerDetector = composite.eiger
|
|
@@ -3,6 +3,7 @@ import json
|
|
|
3
3
|
import re
|
|
4
4
|
import traceback
|
|
5
5
|
from collections.abc import Sequence
|
|
6
|
+
from enum import StrEnum
|
|
6
7
|
from os import path
|
|
7
8
|
from typing import Any, TypeVar
|
|
8
9
|
|
|
@@ -14,6 +15,7 @@ from pydantic_extra_types.semantic_version import SemanticVersion
|
|
|
14
15
|
|
|
15
16
|
from mx_bluesky.common.parameters.components import (
|
|
16
17
|
PARAMETER_VERSION,
|
|
18
|
+
MxBlueskyParameters,
|
|
17
19
|
WithVisit,
|
|
18
20
|
)
|
|
19
21
|
from mx_bluesky.common.parameters.constants import (
|
|
@@ -21,6 +23,7 @@ from mx_bluesky.common.parameters.constants import (
|
|
|
21
23
|
)
|
|
22
24
|
from mx_bluesky.common.utils.log import LOGGER
|
|
23
25
|
from mx_bluesky.common.utils.utils import convert_angstrom_to_eV
|
|
26
|
+
from mx_bluesky.hyperion.parameters.components import Wait
|
|
24
27
|
from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
|
|
25
28
|
|
|
26
29
|
T = TypeVar("T", bound=WithVisit)
|
|
@@ -31,8 +34,13 @@ MULTIPIN_REGEX = rf"^{MULTIPIN_PREFIX}_(\d+)x(\d+(?:\.\d+)?)\+(\d+(?:\.\d+)?)$"
|
|
|
31
34
|
MX_GENERAL_ROOT_REGEX = r"^/dls/(?P<beamline>[^/]+)/data/[^/]*/(?P<visit>[^/]+)(?:/|$)"
|
|
32
35
|
|
|
33
36
|
|
|
37
|
+
class _InstructionType(StrEnum):
|
|
38
|
+
WAIT = "wait"
|
|
39
|
+
COLLECT = "collect"
|
|
40
|
+
|
|
41
|
+
|
|
34
42
|
@dataclasses.dataclass
|
|
35
|
-
class
|
|
43
|
+
class _PinType:
|
|
36
44
|
expected_number_of_crystals: int
|
|
37
45
|
single_well_width_um: float
|
|
38
46
|
tip_to_first_well_um: float = 0
|
|
@@ -54,7 +62,7 @@ class PinType:
|
|
|
54
62
|
)
|
|
55
63
|
|
|
56
64
|
|
|
57
|
-
class
|
|
65
|
+
class _SinglePin(_PinType):
|
|
58
66
|
def __init__(self):
|
|
59
67
|
super().__init__(1, GridscanParamConstants.WIDTH_UM)
|
|
60
68
|
|
|
@@ -63,23 +71,115 @@ class SinglePin(PinType):
|
|
|
63
71
|
return self.single_well_width_um
|
|
64
72
|
|
|
65
73
|
|
|
74
|
+
def create_parameters_from_agamemnon() -> Sequence[MxBlueskyParameters]:
|
|
75
|
+
"""Fetch the next instruction from agamemnon and convert it into one or more
|
|
76
|
+
mx-bluesky instructions.
|
|
77
|
+
Returns:
|
|
78
|
+
The generated sequence of mx-bluesky parameters, or empty list if
|
|
79
|
+
no instructions."""
|
|
80
|
+
beamline_name = get_beamline_name("i03")
|
|
81
|
+
agamemnon_instruction = _get_next_instruction(beamline_name)
|
|
82
|
+
if agamemnon_instruction:
|
|
83
|
+
match _instruction_and_data(agamemnon_instruction):
|
|
84
|
+
case (_InstructionType.COLLECT, data):
|
|
85
|
+
return _populate_parameters_from_agamemnon(data)
|
|
86
|
+
case (_InstructionType.WAIT, data):
|
|
87
|
+
return [
|
|
88
|
+
Wait.model_validate(
|
|
89
|
+
{
|
|
90
|
+
"duration_s": data,
|
|
91
|
+
"parameter_model_version": _get_param_version(),
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
return []
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def compare_params(load_centre_collect_params: LoadCentreCollect):
|
|
100
|
+
"""Compare the supplied parameters (as supplied from GDA) with those directly
|
|
101
|
+
created from agamemnon. Any differences are logged.
|
|
102
|
+
Args:
|
|
103
|
+
load_centre_collect_params: The parameters from GDA to compare."""
|
|
104
|
+
try:
|
|
105
|
+
lcc_requests = create_parameters_from_agamemnon()
|
|
106
|
+
# Log differences against GDA populated parameters
|
|
107
|
+
if not lcc_requests:
|
|
108
|
+
LOGGER.info("Agamemnon returned no instructions")
|
|
109
|
+
else:
|
|
110
|
+
differences = DeepDiff(
|
|
111
|
+
lcc_requests[0], load_centre_collect_params, math_epsilon=1e-5
|
|
112
|
+
)
|
|
113
|
+
if differences:
|
|
114
|
+
LOGGER.info(
|
|
115
|
+
f"Different parameters found when directly reading from Hyperion: {differences}"
|
|
116
|
+
)
|
|
117
|
+
except (ValueError, KeyError):
|
|
118
|
+
LOGGER.warning(f"Failed to compare parameters: {traceback.format_exc()}")
|
|
119
|
+
except Exception:
|
|
120
|
+
LOGGER.warning(
|
|
121
|
+
f"Unexpected error occurred. Failed to compare parameters: {traceback.format_exc()}"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def update_params_from_agamemnon(parameters: T) -> T:
|
|
126
|
+
"""Update the supplied parameters with additional information from agamemnon.
|
|
127
|
+
This is currently necessary for multipin processing and called when Hyperion is invoked
|
|
128
|
+
from GDA.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
parameters: The LoadCentreCollectParameters that will be updated with additional info,
|
|
132
|
+
such as multipin dimensions, number of crystals.
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
beamline_name = get_beamline_name("i03")
|
|
136
|
+
agamemnon_params = _get_next_instruction(beamline_name)
|
|
137
|
+
instruction, collect_params = _instruction_and_data(agamemnon_params)
|
|
138
|
+
assert instruction == _InstructionType.COLLECT, (
|
|
139
|
+
"Unable to augment GDA parameters from agamemnon, agamemnon reports 'wait'"
|
|
140
|
+
)
|
|
141
|
+
pin_type = _get_pin_type_from_agamemnon_collect_parameters(collect_params)
|
|
142
|
+
if isinstance(parameters, LoadCentreCollect):
|
|
143
|
+
parameters.robot_load_then_centre.tip_offset_um = pin_type.full_width / 2
|
|
144
|
+
parameters.robot_load_then_centre.grid_width_um = pin_type.full_width
|
|
145
|
+
parameters.select_centres.n = pin_type.expected_number_of_crystals
|
|
146
|
+
if pin_type != _SinglePin():
|
|
147
|
+
# Rotation snapshots will be generated from the gridscan snapshots,
|
|
148
|
+
# no need to specify snapshot omega.
|
|
149
|
+
parameters.multi_rotation_scan.snapshot_omegas_deg = []
|
|
150
|
+
parameters.multi_rotation_scan.use_grid_snapshots = True
|
|
151
|
+
except (ValueError, ValidationError) as e:
|
|
152
|
+
LOGGER.warning(f"Failed to update parameters: {e}")
|
|
153
|
+
except Exception as e:
|
|
154
|
+
LOGGER.warning(f"Unexpected error occurred. Failed to update parameters: {e}")
|
|
155
|
+
|
|
156
|
+
return parameters
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _instruction_and_data(agamemnon_instruction: dict) -> tuple[str, Any]:
|
|
160
|
+
instruction, data = next(iter(agamemnon_instruction.items()))
|
|
161
|
+
if instruction not in _InstructionType.__members__.values():
|
|
162
|
+
raise KeyError(
|
|
163
|
+
f"Unexpected instruction from agamemnon: {agamemnon_instruction}"
|
|
164
|
+
)
|
|
165
|
+
return instruction, data
|
|
166
|
+
|
|
167
|
+
|
|
66
168
|
def _get_parameters_from_url(url: str) -> dict:
|
|
67
169
|
response = requests.get(url, headers={"Accept": "application/json"})
|
|
68
170
|
response.raise_for_status()
|
|
69
|
-
|
|
70
|
-
try:
|
|
71
|
-
return response_json["collect"]
|
|
72
|
-
except KeyError as e:
|
|
73
|
-
raise KeyError(f"Unexpected json from agamemnon: {response_json}") from e
|
|
171
|
+
return json.loads(response.content)
|
|
74
172
|
|
|
75
173
|
|
|
76
|
-
def
|
|
77
|
-
|
|
174
|
+
def _get_pin_type_from_agamemnon_collect_parameters(
|
|
175
|
+
collect_parameters: dict,
|
|
176
|
+
) -> _PinType:
|
|
177
|
+
loop_type_name: str | None = collect_parameters["sample"]["loopType"]
|
|
78
178
|
if loop_type_name:
|
|
79
179
|
regex_search = re.search(MULTIPIN_REGEX, loop_type_name)
|
|
80
180
|
if regex_search:
|
|
81
181
|
wells, well_size, tip_to_first_well = regex_search.groups()
|
|
82
|
-
return
|
|
182
|
+
return _PinType(int(wells), float(well_size), float(tip_to_first_well))
|
|
83
183
|
else:
|
|
84
184
|
loop_type_message = (
|
|
85
185
|
f"Agamemnon loop type of {loop_type_name} not recognised"
|
|
@@ -87,14 +187,14 @@ def get_pin_type_from_agamemnon_parameters(parameters: dict) -> PinType:
|
|
|
87
187
|
if loop_type_name.startswith(MULTIPIN_PREFIX):
|
|
88
188
|
raise ValueError(f"{loop_type_message}. {MULTIPIN_FORMAT_DESC}")
|
|
89
189
|
LOGGER.warning(f"{loop_type_message}, assuming single pin")
|
|
90
|
-
return
|
|
190
|
+
return _SinglePin()
|
|
91
191
|
|
|
92
192
|
|
|
93
|
-
def
|
|
193
|
+
def _get_next_instruction(beamline: str) -> dict:
|
|
94
194
|
return _get_parameters_from_url(AGAMEMNON_URL + f"getnextcollect/{beamline}")
|
|
95
195
|
|
|
96
196
|
|
|
97
|
-
def
|
|
197
|
+
def _get_withvisit_parameters_from_agamemnon(parameters: dict) -> tuple:
|
|
98
198
|
try:
|
|
99
199
|
prefix = parameters["prefix"]
|
|
100
200
|
collection = parameters["collection"]
|
|
@@ -113,7 +213,7 @@ def get_withvisit_parameters_from_agamemnon(parameters: dict) -> tuple:
|
|
|
113
213
|
)
|
|
114
214
|
|
|
115
215
|
|
|
116
|
-
def
|
|
216
|
+
def _get_withenergy_parameters_from_agamemnon(parameters: dict) -> dict[str, Any]:
|
|
117
217
|
try:
|
|
118
218
|
first_collection: dict = parameters["collection"][0]
|
|
119
219
|
wavelength = first_collection.get("wavelength")
|
|
@@ -124,21 +224,29 @@ def get_withenergy_parameters_from_agamemnon(parameters: dict) -> dict[str, Any]
|
|
|
124
224
|
return {"demand_energy_ev": None}
|
|
125
225
|
|
|
126
226
|
|
|
127
|
-
def
|
|
227
|
+
def _get_param_version() -> SemanticVersion:
|
|
128
228
|
return SemanticVersion.validate_from_str(str(PARAMETER_VERSION))
|
|
129
229
|
|
|
130
230
|
|
|
131
|
-
def
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
231
|
+
def _populate_parameters_from_agamemnon(
|
|
232
|
+
agamemnon_params,
|
|
233
|
+
) -> Sequence[LoadCentreCollect]:
|
|
234
|
+
if not agamemnon_params:
|
|
235
|
+
# Empty dict means no instructions
|
|
236
|
+
return []
|
|
237
|
+
|
|
238
|
+
visit, detector_distance = _get_withvisit_parameters_from_agamemnon(
|
|
239
|
+
agamemnon_params
|
|
240
|
+
)
|
|
241
|
+
with_energy_params = _get_withenergy_parameters_from_agamemnon(agamemnon_params)
|
|
242
|
+
pin_type = _get_pin_type_from_agamemnon_collect_parameters(agamemnon_params)
|
|
135
243
|
collections = agamemnon_params["collection"]
|
|
136
244
|
visit_directory, file_name = path.split(agamemnon_params["prefix"])
|
|
137
245
|
|
|
138
246
|
return [
|
|
139
247
|
LoadCentreCollect.model_validate(
|
|
140
248
|
{
|
|
141
|
-
"parameter_model_version":
|
|
249
|
+
"parameter_model_version": _get_param_version(),
|
|
142
250
|
"visit": visit,
|
|
143
251
|
"detector_distance_mm": detector_distance,
|
|
144
252
|
"sample_id": agamemnon_params["sample"]["id"],
|
|
@@ -148,7 +256,6 @@ def populate_parameters_from_agamemnon(agamemnon_params) -> Sequence[LoadCentreC
|
|
|
148
256
|
"name": "TopNByMaxCount",
|
|
149
257
|
"n": pin_type.expected_number_of_crystals,
|
|
150
258
|
},
|
|
151
|
-
"features": {"use_gpu_results": True},
|
|
152
259
|
"robot_load_then_centre": {
|
|
153
260
|
"storage_directory": str(visit_directory) + "/xraycentring",
|
|
154
261
|
"file_name": file_name,
|
|
@@ -186,55 +293,3 @@ def populate_parameters_from_agamemnon(agamemnon_params) -> Sequence[LoadCentreC
|
|
|
186
293
|
)
|
|
187
294
|
for collection in collections
|
|
188
295
|
]
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def create_parameters_from_agamemnon() -> Sequence[LoadCentreCollect]:
|
|
192
|
-
beamline_name = get_beamline_name("i03")
|
|
193
|
-
agamemnon_params = get_next_instruction(beamline_name)
|
|
194
|
-
return (
|
|
195
|
-
populate_parameters_from_agamemnon(agamemnon_params) if agamemnon_params else []
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
def compare_params(load_centre_collect_params: LoadCentreCollect):
|
|
200
|
-
try:
|
|
201
|
-
lcc_requests = create_parameters_from_agamemnon()
|
|
202
|
-
# Log differences against GDA populated parameters
|
|
203
|
-
if not lcc_requests:
|
|
204
|
-
LOGGER.info("Agamemnon returned no instructions")
|
|
205
|
-
else:
|
|
206
|
-
differences = DeepDiff(
|
|
207
|
-
lcc_requests[0], load_centre_collect_params, math_epsilon=1e-5
|
|
208
|
-
)
|
|
209
|
-
if differences:
|
|
210
|
-
LOGGER.info(
|
|
211
|
-
f"Different parameters found when directly reading from Hyperion: {differences}"
|
|
212
|
-
)
|
|
213
|
-
except (ValueError, KeyError):
|
|
214
|
-
LOGGER.warning(f"Failed to compare parameters: {traceback.format_exc()}")
|
|
215
|
-
except Exception:
|
|
216
|
-
LOGGER.warning(
|
|
217
|
-
f"Unexpected error occurred. Failed to compare parameters: {traceback.format_exc()}"
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
def update_params_from_agamemnon(parameters: T) -> T:
|
|
222
|
-
try:
|
|
223
|
-
beamline_name = get_beamline_name("i03")
|
|
224
|
-
agamemnon_params = get_next_instruction(beamline_name)
|
|
225
|
-
pin_type = get_pin_type_from_agamemnon_parameters(agamemnon_params)
|
|
226
|
-
if isinstance(parameters, LoadCentreCollect):
|
|
227
|
-
parameters.robot_load_then_centre.tip_offset_um = pin_type.full_width / 2
|
|
228
|
-
parameters.robot_load_then_centre.grid_width_um = pin_type.full_width
|
|
229
|
-
parameters.select_centres.n = pin_type.expected_number_of_crystals
|
|
230
|
-
if pin_type != SinglePin():
|
|
231
|
-
# Rotation snapshots will be generated from the gridscan snapshots,
|
|
232
|
-
# no need to specify snapshot omega.
|
|
233
|
-
parameters.multi_rotation_scan.snapshot_omegas_deg = []
|
|
234
|
-
parameters.multi_rotation_scan.use_grid_snapshots = True
|
|
235
|
-
except (ValueError, ValidationError) as e:
|
|
236
|
-
LOGGER.warning(f"Failed to update parameters: {e}")
|
|
237
|
-
except Exception as e:
|
|
238
|
-
LOGGER.warning(f"Unexpected error occurred. Failed to update parameters: {e}")
|
|
239
|
-
|
|
240
|
-
return parameters
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Subjects(StrEnum):
|
|
5
|
+
UDC_STARTED = "UDC Started"
|
|
6
|
+
UDC_BATON_PASSED = "UDC Baton was passed"
|
|
7
|
+
UDC_RESUMED_OPERATION = "UDC Resumed operation"
|
|
8
|
+
UDC_SUSPENDED_OPERATION = "UDC Suspended operation"
|
|
9
|
+
NEW_CONTAINER = "Hyperion is collecting from a new container"
|
|
10
|
+
NEW_VISIT = "Hyperion has changed visit"
|
|
11
|
+
SAMPLE_ERROR = "Hyperion has encountered a sample error"
|
|
12
|
+
BEAMLINE_ERROR = "Hyperion has encountered a beamline error"
|
|
@@ -8,6 +8,10 @@ from bluesky.callbacks.zmq import Proxy, RemoteDispatcher
|
|
|
8
8
|
from dodal.log import LOGGER as dodal_logger
|
|
9
9
|
from dodal.log import set_up_all_logging_handlers
|
|
10
10
|
|
|
11
|
+
from mx_bluesky.common.external_interaction.alerting import set_alerting_service
|
|
12
|
+
from mx_bluesky.common.external_interaction.alerting.log_based_service import (
|
|
13
|
+
LoggingAlertService,
|
|
14
|
+
)
|
|
11
15
|
from mx_bluesky.common.external_interaction.callbacks.common.log_uid_tag_callback import (
|
|
12
16
|
LogUidTaggingCallback,
|
|
13
17
|
)
|
|
@@ -156,6 +160,7 @@ class HyperionCallbackRunner:
|
|
|
156
160
|
def __init__(self, dev_mode) -> None:
|
|
157
161
|
setup_logging(dev_mode)
|
|
158
162
|
log_info("Hyperion callback process started.")
|
|
163
|
+
set_alerting_service(LoggingAlertService(CONST.GRAYLOG_STREAM_ID))
|
|
159
164
|
|
|
160
165
|
self.callbacks = setup_callbacks()
|
|
161
166
|
self.proxy, self.dispatcher, start_proxy, start_dispatcher = setup_threads()
|
|
@@ -15,7 +15,7 @@ from mx_bluesky.common.external_interaction.nexus.nexus_utils import (
|
|
|
15
15
|
)
|
|
16
16
|
from mx_bluesky.common.external_interaction.nexus.write_nexus import NexusWriter
|
|
17
17
|
from mx_bluesky.common.utils.log import NEXUS_LOGGER
|
|
18
|
-
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
18
|
+
from mx_bluesky.hyperion.parameters.constants import CONST, I03Constants
|
|
19
19
|
from mx_bluesky.hyperion.parameters.rotation import SingleRotationScan
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
@@ -103,6 +103,6 @@ class RotationNexusFileCallback(PlanReactiveCallback):
|
|
|
103
103
|
full_num_of_images=self.full_num_of_images,
|
|
104
104
|
meta_data_run_number=self.meta_data_run_number,
|
|
105
105
|
axis_direction=AxisDirection.NEGATIVE
|
|
106
|
-
if
|
|
106
|
+
if I03Constants.OMEGA_FLIP
|
|
107
107
|
else AxisDirection.POSITIVE,
|
|
108
108
|
)
|