mx-bluesky 1.5.14__py3-none-any.whl → 1.5.16__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/Getting started.ipynb +1 -0
- mx_bluesky/_version.py +2 -2
- mx_bluesky/beamlines/i04/callbacks/murko_callback.py +18 -0
- mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +6 -7
- mx_bluesky/beamlines/i04/thawing_plan.py +1 -0
- mx_bluesky/beamlines/i24/jungfrau_commissioning/__init__.py +13 -0
- mx_bluesky/beamlines/i24/jungfrau_commissioning/callbacks/__init__.py +0 -0
- mx_bluesky/beamlines/i24/jungfrau_commissioning/callbacks/metadata_writer.py +86 -0
- mx_bluesky/beamlines/i24/jungfrau_commissioning/composites.py +35 -0
- mx_bluesky/beamlines/i24/jungfrau_commissioning/experiment_plans/do_darks.py +18 -19
- mx_bluesky/beamlines/i24/jungfrau_commissioning/experiment_plans/rotation_scan_plan.py +292 -0
- mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/do_external_acquisition.py +3 -8
- mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/do_internal_acquisition.py +4 -5
- mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/plan_utils.py +14 -18
- mx_bluesky/beamlines/i24/parameters/__init__.py +0 -0
- mx_bluesky/beamlines/i24/parameters/constants.py +9 -0
- mx_bluesky/beamlines/i24/serial/extruder/i24ssx_extruder_collect_py3v2.py +1 -1
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +2 -2
- mx_bluesky/beamlines/i24/serial/web_gui_plans/oav_plans.py +21 -0
- mx_bluesky/common/device_setup_plans/robot_load_unload.py +2 -24
- mx_bluesky/common/device_setup_plans/setup_zebra_and_shutter.py +5 -2
- mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +1 -1
- mx_bluesky/common/experiment_plans/inner_plans/read_hardware.py +2 -0
- mx_bluesky/common/experiment_plans/rotation/__init__.py +0 -0
- mx_bluesky/common/experiment_plans/rotation/rotation_utils.py +127 -0
- mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py +14 -2
- mx_bluesky/common/external_interaction/callbacks/common/ispyb_mapping.py +1 -2
- mx_bluesky/common/external_interaction/ispyb/data_model.py +4 -1
- mx_bluesky/common/external_interaction/ispyb/exp_eye_store.py +3 -1
- mx_bluesky/common/parameters/components.py +17 -7
- mx_bluesky/common/parameters/constants.py +6 -0
- mx_bluesky/{hyperion → common}/parameters/rotation.py +10 -8
- mx_bluesky/common/preprocessors/preprocessors.py +98 -36
- mx_bluesky/hyperion/__main__.py +55 -22
- mx_bluesky/hyperion/baton_handler.py +24 -64
- mx_bluesky/hyperion/blueapi_config.yaml +17 -0
- mx_bluesky/hyperion/blueapi_dev_config.yaml +16 -0
- mx_bluesky/hyperion/blueapi_plans/__init__.py +96 -0
- mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +8 -6
- mx_bluesky/hyperion/device_setup_plans/setup_panda.py +1 -1
- mx_bluesky/hyperion/experiment_plans/experiment_registry.py +3 -1
- mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +4 -5
- mx_bluesky/hyperion/experiment_plans/hyperion_grid_detect_then_xray_centre_plan.py +2 -2
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +3 -1
- mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +17 -6
- mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +0 -3
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +12 -126
- mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +2 -2
- mx_bluesky/hyperion/experiment_plans/udc_default_state.py +8 -2
- mx_bluesky/hyperion/external_interaction/agamemnon.py +3 -8
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +121 -47
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +3 -1
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +3 -1
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +6 -3
- mx_bluesky/hyperion/external_interaction/callbacks/stomp/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/callbacks/stomp/dispatcher.py +33 -0
- mx_bluesky/hyperion/in_process_runner.py +132 -0
- mx_bluesky/hyperion/parameters/cli.py +43 -4
- mx_bluesky/hyperion/parameters/components.py +13 -0
- mx_bluesky/hyperion/parameters/constants.py +2 -9
- mx_bluesky/hyperion/parameters/load_centre_collect.py +3 -1
- mx_bluesky/hyperion/plan_runner.py +45 -66
- mx_bluesky/hyperion/plan_runner_api.py +3 -4
- mx_bluesky/hyperion/supervisor/__init__.py +3 -0
- mx_bluesky/hyperion/supervisor/_supervisor.py +116 -0
- mx_bluesky/hyperion/supervisor/client_config.yaml +6 -0
- mx_bluesky/hyperion/supervisor/supervisor_config.yaml +10 -0
- mx_bluesky/hyperion/supervisor/supervisor_dev_config.yaml +9 -0
- {mx_bluesky-1.5.14.dist-info → mx_bluesky-1.5.16.dist-info}/METADATA +3 -31
- {mx_bluesky-1.5.14.dist-info → mx_bluesky-1.5.16.dist-info}/RECORD +74 -54
- {mx_bluesky-1.5.14.dist-info → mx_bluesky-1.5.16.dist-info}/WHEEL +0 -0
- {mx_bluesky-1.5.14.dist-info → mx_bluesky-1.5.16.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.5.14.dist-info → mx_bluesky-1.5.16.dist-info}/licenses/LICENSE +0 -0
- {mx_bluesky-1.5.14.dist-info → mx_bluesky-1.5.16.dist-info}/top_level.txt +0 -0
|
@@ -13,6 +13,9 @@ from dodal.devices.oav.oav_parameters import OAVParameters
|
|
|
13
13
|
|
|
14
14
|
import mx_bluesky.common.xrc_result as flyscan_result
|
|
15
15
|
from mx_bluesky.common.parameters.components import WithSnapshot
|
|
16
|
+
from mx_bluesky.common.parameters.rotation import (
|
|
17
|
+
RotationScanPerSweep,
|
|
18
|
+
)
|
|
16
19
|
from mx_bluesky.common.utils.context import device_composite_from_context
|
|
17
20
|
from mx_bluesky.common.utils.exceptions import CrystalNotFoundError
|
|
18
21
|
from mx_bluesky.common.utils.log import LOGGER
|
|
@@ -31,7 +34,6 @@ from mx_bluesky.hyperion.external_interaction.config_server import (
|
|
|
31
34
|
)
|
|
32
35
|
from mx_bluesky.hyperion.parameters.constants import CONST, I03Constants
|
|
33
36
|
from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
|
|
34
|
-
from mx_bluesky.hyperion.parameters.rotation import RotationScanPerSweep
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
@pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
|
|
@@ -28,7 +28,10 @@ from mx_bluesky.common.experiment_plans.pin_tip_centring_plan import (
|
|
|
28
28
|
from mx_bluesky.common.external_interaction.callbacks.xray_centre.ispyb_callback import (
|
|
29
29
|
ispyb_activation_wrapper,
|
|
30
30
|
)
|
|
31
|
-
from mx_bluesky.common.parameters.constants import OavConstants
|
|
31
|
+
from mx_bluesky.common.parameters.constants import OavConstants, PlanNameConstants
|
|
32
|
+
from mx_bluesky.common.preprocessors.preprocessors import (
|
|
33
|
+
pause_xbpm_feedback_during_collection_at_desired_transmission_decorator,
|
|
34
|
+
)
|
|
32
35
|
from mx_bluesky.common.utils.context import device_composite_from_context
|
|
33
36
|
from mx_bluesky.common.utils.log import LOGGER
|
|
34
37
|
from mx_bluesky.common.xrc_result import XRayCentreEventHandler
|
|
@@ -103,13 +106,21 @@ def pin_centre_then_flyscan_plan(
|
|
|
103
106
|
grid_detect_params = create_parameters_for_grid_detection(parameters)
|
|
104
107
|
oav_params = OAVParameters("xrayCentring", oav_config_file)
|
|
105
108
|
|
|
106
|
-
|
|
109
|
+
@pause_xbpm_feedback_during_collection_at_desired_transmission_decorator(
|
|
107
110
|
composite,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
HyperionSpecifiedThreeDGridScan,
|
|
111
|
-
construct_hyperion_specific_features,
|
|
111
|
+
parameters.transmission_frac,
|
|
112
|
+
PlanNameConstants.GRIDSCAN_OUTER,
|
|
112
113
|
)
|
|
114
|
+
def _grid_detect_plan():
|
|
115
|
+
yield from detect_grid_and_do_gridscan(
|
|
116
|
+
composite,
|
|
117
|
+
grid_detect_params,
|
|
118
|
+
oav_params,
|
|
119
|
+
HyperionSpecifiedThreeDGridScan,
|
|
120
|
+
construct_hyperion_specific_features,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
yield from _grid_detect_plan()
|
|
113
124
|
|
|
114
125
|
yield from ispyb_activation_wrapper(_pin_centre_then_flyscan_plan(), parameters)
|
|
115
126
|
|
|
@@ -27,7 +27,6 @@ from dodal.devices.xbpm_feedback import XBPMFeedback
|
|
|
27
27
|
from mx_bluesky.common.device_setup_plans.robot_load_unload import (
|
|
28
28
|
do_plan_while_lower_gonio_at_home,
|
|
29
29
|
prepare_for_robot_load,
|
|
30
|
-
wait_for_smargon_not_disabled,
|
|
31
30
|
)
|
|
32
31
|
from mx_bluesky.hyperion.experiment_plans.set_energy_plan import (
|
|
33
32
|
SetEnergyComposite,
|
|
@@ -95,8 +94,6 @@ def do_robot_load(
|
|
|
95
94
|
|
|
96
95
|
yield from bps.wait("robot_load")
|
|
97
96
|
|
|
98
|
-
yield from wait_for_smargon_not_disabled(composite.smargon)
|
|
99
|
-
|
|
100
97
|
yield from bps.mv(composite.thawer, OnOff.ON)
|
|
101
98
|
|
|
102
99
|
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import dataclasses
|
|
4
|
-
|
|
5
1
|
import bluesky.plan_stubs as bps
|
|
6
2
|
import bluesky.preprocessors as bpp
|
|
7
3
|
import pydantic
|
|
@@ -25,7 +21,7 @@ from dodal.devices.synchrotron import Synchrotron
|
|
|
25
21
|
from dodal.devices.thawer import Thawer
|
|
26
22
|
from dodal.devices.undulator import UndulatorInKeV
|
|
27
23
|
from dodal.devices.xbpm_feedback import XBPMFeedback
|
|
28
|
-
from dodal.devices.zebra.zebra import
|
|
24
|
+
from dodal.devices.zebra.zebra import Zebra
|
|
29
25
|
from dodal.devices.zebra.zebra_controlled_shutter import ZebraShutter
|
|
30
26
|
from dodal.plan_stubs.check_topup import check_topup_and_wait_if_necessary
|
|
31
27
|
from dodal.plans.preprocessors.verify_undulator_gap import (
|
|
@@ -53,20 +49,24 @@ from mx_bluesky.common.experiment_plans.oav_snapshot_plan import (
|
|
|
53
49
|
oav_snapshot_plan,
|
|
54
50
|
setup_beamline_for_oav,
|
|
55
51
|
)
|
|
52
|
+
from mx_bluesky.common.experiment_plans.rotation.rotation_utils import (
|
|
53
|
+
RotationMotionProfile,
|
|
54
|
+
calculate_motion_profile,
|
|
55
|
+
)
|
|
56
56
|
from mx_bluesky.common.parameters.components import WithSnapshot
|
|
57
|
+
from mx_bluesky.common.parameters.rotation import (
|
|
58
|
+
RotationScan,
|
|
59
|
+
SingleRotationScan,
|
|
60
|
+
)
|
|
57
61
|
from mx_bluesky.common.preprocessors.preprocessors import (
|
|
58
|
-
|
|
62
|
+
pause_xbpm_feedback_during_collection_at_desired_transmission_decorator,
|
|
59
63
|
)
|
|
60
64
|
from mx_bluesky.common.utils.context import device_composite_from_context
|
|
61
65
|
from mx_bluesky.common.utils.log import LOGGER
|
|
62
66
|
from mx_bluesky.hyperion.device_setup_plans.setup_zebra import (
|
|
63
67
|
arm_zebra,
|
|
64
68
|
)
|
|
65
|
-
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
66
|
-
from mx_bluesky.hyperion.parameters.rotation import (
|
|
67
|
-
RotationScan,
|
|
68
|
-
SingleRotationScan,
|
|
69
|
-
)
|
|
69
|
+
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
@pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
|
|
@@ -100,120 +100,6 @@ def create_devices(context: BlueskyContext) -> RotationScanComposite:
|
|
|
100
100
|
return device_composite_from_context(context, RotationScanComposite)
|
|
101
101
|
|
|
102
102
|
|
|
103
|
-
DEFAULT_DIRECTION = RotationDirection.NEGATIVE
|
|
104
|
-
DEFAULT_MAX_VELOCITY = 120
|
|
105
|
-
# Use a slightly larger time to acceleration than EPICS as it's better to be cautious
|
|
106
|
-
ACCELERATION_MARGIN = 1.5
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
@dataclasses.dataclass
|
|
110
|
-
class RotationMotionProfile:
|
|
111
|
-
start_scan_deg: float
|
|
112
|
-
start_motion_deg: float
|
|
113
|
-
scan_width_deg: float
|
|
114
|
-
shutter_time_s: float
|
|
115
|
-
direction: RotationDirection
|
|
116
|
-
speed_for_rotation_deg_s: float
|
|
117
|
-
acceleration_offset_deg: float
|
|
118
|
-
shutter_opening_deg: float
|
|
119
|
-
total_exposure_s: float
|
|
120
|
-
distance_to_move_deg: float
|
|
121
|
-
max_velocity_deg_s: float
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def calculate_motion_profile(
|
|
125
|
-
params: SingleRotationScan,
|
|
126
|
-
motor_time_to_speed_s: float,
|
|
127
|
-
max_velocity_deg_s: float,
|
|
128
|
-
) -> RotationMotionProfile:
|
|
129
|
-
"""Calculates the various numbers needed for motions in the rotation scan.
|
|
130
|
-
Rotates through "scan width" plus twice an "offset" to take into account
|
|
131
|
-
acceleration at the start and deceleration at the end, plus the number of extra
|
|
132
|
-
degrees of rotation needed to make sure the fast shutter has fully opened before the
|
|
133
|
-
detector trigger is sent.
|
|
134
|
-
See https://github.com/DiamondLightSource/hyperion/wiki/rotation-scan-geometry
|
|
135
|
-
for a simple pictorial explanation."""
|
|
136
|
-
|
|
137
|
-
assert params.rotation_increment_deg > 0
|
|
138
|
-
|
|
139
|
-
direction = params.rotation_direction
|
|
140
|
-
start_scan_deg = params.omega_start_deg
|
|
141
|
-
|
|
142
|
-
if I03Constants.OMEGA_FLIP:
|
|
143
|
-
# If omega_flip is True then the motor omega axis is inverted with respect to the
|
|
144
|
-
# hyperion coordinate system.
|
|
145
|
-
start_scan_deg = -start_scan_deg
|
|
146
|
-
direction = (
|
|
147
|
-
direction.POSITIVE
|
|
148
|
-
if direction == direction.NEGATIVE
|
|
149
|
-
else direction.NEGATIVE
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
num_images = params.num_images
|
|
153
|
-
shutter_time_s = params.shutter_opening_time_s
|
|
154
|
-
image_width_deg = params.rotation_increment_deg
|
|
155
|
-
exposure_time_s = params.exposure_time_s
|
|
156
|
-
motor_time_to_speed_s *= ACCELERATION_MARGIN
|
|
157
|
-
|
|
158
|
-
LOGGER.info("Calculating rotation scan motion profile:")
|
|
159
|
-
LOGGER.info(
|
|
160
|
-
f"{num_images=}, {shutter_time_s=}, {image_width_deg=}, {exposure_time_s=}, {direction=}"
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
scan_width_deg = num_images * params.rotation_increment_deg
|
|
164
|
-
LOGGER.info(f"{scan_width_deg=} = {num_images=} * {params.rotation_increment_deg=}")
|
|
165
|
-
|
|
166
|
-
speed_for_rotation_deg_s = image_width_deg / exposure_time_s
|
|
167
|
-
LOGGER.info("speed_for_rotation_deg_s = image_width_deg / exposure_time_s")
|
|
168
|
-
LOGGER.info(
|
|
169
|
-
f"{speed_for_rotation_deg_s=} = {image_width_deg=} / {exposure_time_s=}"
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
acceleration_offset_deg = motor_time_to_speed_s * speed_for_rotation_deg_s
|
|
173
|
-
LOGGER.info(
|
|
174
|
-
f"{acceleration_offset_deg=} = {motor_time_to_speed_s=} * {speed_for_rotation_deg_s=}"
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
start_motion_deg = start_scan_deg - (acceleration_offset_deg * direction.multiplier)
|
|
178
|
-
LOGGER.info(
|
|
179
|
-
f"{start_motion_deg=} = {start_scan_deg=} - ({acceleration_offset_deg=} * {direction.multiplier=})"
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
shutter_opening_deg = speed_for_rotation_deg_s * shutter_time_s
|
|
183
|
-
LOGGER.info(
|
|
184
|
-
f"{shutter_opening_deg=} = {speed_for_rotation_deg_s=} * {shutter_time_s=}"
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
shutter_opening_deg = speed_for_rotation_deg_s * shutter_time_s
|
|
188
|
-
LOGGER.info(
|
|
189
|
-
f"{shutter_opening_deg=} = {speed_for_rotation_deg_s=} * {shutter_time_s=}"
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
total_exposure_s = num_images * exposure_time_s
|
|
193
|
-
LOGGER.info(f"{total_exposure_s=} = {num_images=} * {exposure_time_s=}")
|
|
194
|
-
|
|
195
|
-
distance_to_move_deg = (
|
|
196
|
-
scan_width_deg + shutter_opening_deg + acceleration_offset_deg * 2
|
|
197
|
-
) * direction.multiplier
|
|
198
|
-
LOGGER.info(
|
|
199
|
-
f"{distance_to_move_deg=} = ({scan_width_deg=} + {shutter_opening_deg=} + {acceleration_offset_deg=} * 2) * {direction=})"
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
return RotationMotionProfile(
|
|
203
|
-
start_scan_deg=start_scan_deg,
|
|
204
|
-
start_motion_deg=start_motion_deg,
|
|
205
|
-
scan_width_deg=scan_width_deg,
|
|
206
|
-
shutter_time_s=shutter_time_s,
|
|
207
|
-
direction=direction,
|
|
208
|
-
speed_for_rotation_deg_s=speed_for_rotation_deg_s,
|
|
209
|
-
acceleration_offset_deg=acceleration_offset_deg,
|
|
210
|
-
shutter_opening_deg=shutter_opening_deg,
|
|
211
|
-
total_exposure_s=total_exposure_s,
|
|
212
|
-
distance_to_move_deg=distance_to_move_deg,
|
|
213
|
-
max_velocity_deg_s=max_velocity_deg_s,
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
|
|
217
103
|
def rotation_scan_plan(
|
|
218
104
|
composite: RotationScanComposite,
|
|
219
105
|
params: SingleRotationScan,
|
|
@@ -399,7 +285,7 @@ def rotation_scan_internal(
|
|
|
399
285
|
eiger: EigerDetector = composite.eiger
|
|
400
286
|
eiger.set_detector_parameters(parameters.detector_params)
|
|
401
287
|
|
|
402
|
-
@
|
|
288
|
+
@pause_xbpm_feedback_during_collection_at_desired_transmission_decorator(
|
|
403
289
|
composite,
|
|
404
290
|
parameters.transmission_frac,
|
|
405
291
|
)
|
|
@@ -17,7 +17,7 @@ from dodal.devices.xbpm_feedback import XBPMFeedback
|
|
|
17
17
|
|
|
18
18
|
from mx_bluesky.common.parameters.constants import PlanNameConstants
|
|
19
19
|
from mx_bluesky.common.preprocessors.preprocessors import (
|
|
20
|
-
|
|
20
|
+
pause_xbpm_feedback_during_collection_at_desired_transmission_wrapper,
|
|
21
21
|
)
|
|
22
22
|
from mx_bluesky.hyperion.device_setup_plans import dcm_pitch_roll_mirror_adjuster
|
|
23
23
|
|
|
@@ -74,7 +74,7 @@ def set_energy_plan(
|
|
|
74
74
|
)
|
|
75
75
|
|
|
76
76
|
if energy_ev:
|
|
77
|
-
yield from
|
|
77
|
+
yield from pause_xbpm_feedback_during_collection_at_desired_transmission_wrapper(
|
|
78
78
|
_set_energy_plan(energy_ev / 1000, composite),
|
|
79
79
|
composite_for_wrapper,
|
|
80
80
|
DESIRED_TRANSMISSION_FRACTION,
|
|
@@ -17,6 +17,7 @@ from dodal.devices.fluorescence_detector_motion import FluorescenceDetector
|
|
|
17
17
|
from dodal.devices.fluorescence_detector_motion import InOut as FlouInOut
|
|
18
18
|
from dodal.devices.hutch_shutter import HutchShutter, ShutterDemand
|
|
19
19
|
from dodal.devices.mx_phase1.beamstop import BeamstopPositions
|
|
20
|
+
from dodal.devices.oav.oav_detector import OAV
|
|
20
21
|
from dodal.devices.robot import BartRobot, PinMounted
|
|
21
22
|
from dodal.devices.scintillator import InOut as ScinInOut
|
|
22
23
|
from dodal.devices.scintillator import Scintillator
|
|
@@ -49,6 +50,7 @@ class UDCDefaultDevices(BeamstopCheckDevices):
|
|
|
49
50
|
robot: BartRobot
|
|
50
51
|
scintillator: Scintillator
|
|
51
52
|
smargon: Smargon
|
|
53
|
+
oav: OAV
|
|
52
54
|
|
|
53
55
|
|
|
54
56
|
class UnexpectedSampleError(BeamlineCheckFailureError): ...
|
|
@@ -109,7 +111,7 @@ def move_to_udc_default_state(devices: UDCDefaultDevices):
|
|
|
109
111
|
)
|
|
110
112
|
|
|
111
113
|
# Wait for all of the above to complete
|
|
112
|
-
yield from bps.wait(group=_GROUP_PRE_BEAMSTOP_CHECK, timeout=
|
|
114
|
+
yield from bps.wait(group=_GROUP_PRE_BEAMSTOP_CHECK, timeout=10)
|
|
113
115
|
|
|
114
116
|
feature_flags: HyperionFeatureSettings = (
|
|
115
117
|
get_hyperion_config_client().get_feature_flags()
|
|
@@ -141,7 +143,11 @@ def move_to_udc_default_state(devices: UDCDefaultDevices):
|
|
|
141
143
|
devices.cryojet.fine, CryoInOut.IN, group=_GROUP_POST_BEAMSTOP_CHECK
|
|
142
144
|
)
|
|
143
145
|
|
|
144
|
-
yield from bps.
|
|
146
|
+
yield from bps.abs_set(
|
|
147
|
+
devices.oav.zoom_controller, "1.0x", group=_GROUP_POST_BEAMSTOP_CHECK
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
yield from bps.wait(_GROUP_POST_BEAMSTOP_CHECK, timeout=10)
|
|
145
151
|
|
|
146
152
|
|
|
147
153
|
def _verify_correct_cryostream_selected(
|
|
@@ -12,12 +12,11 @@ import requests
|
|
|
12
12
|
from deepdiff.diff import DeepDiff
|
|
13
13
|
from dodal.utils import get_beamline_name
|
|
14
14
|
from jsonschema import ValidationError
|
|
15
|
-
from pydantic_extra_types.semantic_version import SemanticVersion
|
|
16
15
|
|
|
17
16
|
from mx_bluesky.common.parameters.components import (
|
|
18
|
-
PARAMETER_VERSION,
|
|
19
17
|
MxBlueskyParameters,
|
|
20
18
|
WithVisit,
|
|
19
|
+
get_param_version,
|
|
21
20
|
)
|
|
22
21
|
from mx_bluesky.common.parameters.constants import (
|
|
23
22
|
GridscanParamConstants,
|
|
@@ -88,7 +87,7 @@ def create_parameters_from_agamemnon() -> Sequence[MxBlueskyParameters]:
|
|
|
88
87
|
Wait.model_validate(
|
|
89
88
|
{
|
|
90
89
|
"duration_s": data,
|
|
91
|
-
"parameter_model_version":
|
|
90
|
+
"parameter_model_version": get_param_version(),
|
|
92
91
|
}
|
|
93
92
|
)
|
|
94
93
|
]
|
|
@@ -228,10 +227,6 @@ def _get_withenergy_parameters_from_agamemnon(parameters: dict) -> dict[str, Any
|
|
|
228
227
|
return {"demand_energy_ev": None}
|
|
229
228
|
|
|
230
229
|
|
|
231
|
-
def _get_param_version() -> SemanticVersion:
|
|
232
|
-
return SemanticVersion.validate_from_str(str(PARAMETER_VERSION))
|
|
233
|
-
|
|
234
|
-
|
|
235
230
|
def _populate_parameters_from_agamemnon(
|
|
236
231
|
agamemnon_params,
|
|
237
232
|
) -> Sequence[LoadCentreCollect]:
|
|
@@ -250,7 +245,7 @@ def _populate_parameters_from_agamemnon(
|
|
|
250
245
|
return [
|
|
251
246
|
LoadCentreCollect.model_validate(
|
|
252
247
|
{
|
|
253
|
-
"parameter_model_version":
|
|
248
|
+
"parameter_model_version": get_param_version(),
|
|
254
249
|
"visit": visit,
|
|
255
250
|
"detector_distance_mm": detector_distance,
|
|
256
251
|
"sample_id": agamemnon_params["sample"]["id"],
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from
|
|
2
|
+
from abc import abstractmethod
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from contextlib import AbstractContextManager
|
|
3
5
|
from threading import Thread
|
|
4
6
|
from time import sleep # noqa
|
|
5
7
|
from urllib import request
|
|
6
8
|
from urllib.error import URLError
|
|
7
9
|
|
|
10
|
+
from blueapi.config import ApplicationConfig, ConfigLoader
|
|
8
11
|
from bluesky.callbacks import CallbackBase
|
|
9
12
|
from bluesky.callbacks.zmq import Proxy, RemoteDispatcher
|
|
13
|
+
from bluesky_stomp.messaging import StompClient
|
|
14
|
+
from bluesky_stomp.models import Broker
|
|
10
15
|
from dodal.log import LOGGER as DODAL_LOGGER
|
|
11
16
|
from dodal.log import set_up_all_logging_handlers
|
|
12
17
|
|
|
@@ -52,8 +57,11 @@ from mx_bluesky.hyperion.external_interaction.callbacks.rotation.nexus_callback
|
|
|
52
57
|
from mx_bluesky.hyperion.external_interaction.callbacks.snapshot_callback import (
|
|
53
58
|
BeamDrawingCallback,
|
|
54
59
|
)
|
|
55
|
-
from mx_bluesky.hyperion.
|
|
56
|
-
|
|
60
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.stomp.dispatcher import (
|
|
61
|
+
StompDispatcher,
|
|
62
|
+
)
|
|
63
|
+
from mx_bluesky.hyperion.parameters.cli import CallbackArgs, parse_callback_args
|
|
64
|
+
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
57
65
|
from mx_bluesky.hyperion.parameters.gridscan import (
|
|
58
66
|
GridCommonWithHyperionDetectorParams,
|
|
59
67
|
HyperionSpecifiedThreeDGridScan,
|
|
@@ -143,29 +151,72 @@ def log_debug(msg, *args, **kwargs):
|
|
|
143
151
|
NEXUS_LOGGER.debug(msg, *args, **kwargs)
|
|
144
152
|
|
|
145
153
|
|
|
146
|
-
def wait_for_threads_forever(threads: Sequence[Thread]):
|
|
147
|
-
alive = [t.is_alive() for t in threads]
|
|
148
|
-
try:
|
|
149
|
-
log_debug("Trying to wait forever on callback and dispatcher threads")
|
|
150
|
-
while all(alive):
|
|
151
|
-
sleep(LIVENESS_POLL_SECONDS)
|
|
152
|
-
alive = [t.is_alive() for t in threads]
|
|
153
|
-
except KeyboardInterrupt:
|
|
154
|
-
log_info("Main thread received interrupt - exiting.")
|
|
155
|
-
else:
|
|
156
|
-
log_info("Proxy or dispatcher thread ended - exiting.")
|
|
157
|
-
|
|
158
|
-
|
|
159
154
|
class HyperionCallbackRunner:
|
|
160
155
|
"""Runs Nexus, ISPyB and Zocalo callbacks in their own process."""
|
|
161
156
|
|
|
162
|
-
def __init__(self,
|
|
163
|
-
setup_logging(dev_mode)
|
|
157
|
+
def __init__(self, callback_args: CallbackArgs) -> None:
|
|
158
|
+
setup_logging(callback_args.dev_mode)
|
|
164
159
|
log_info("Hyperion callback process started.")
|
|
165
160
|
set_alerting_service(LoggingAlertService(CONST.GRAYLOG_STREAM_ID))
|
|
166
161
|
|
|
167
162
|
self.callbacks = setup_callbacks()
|
|
168
163
|
|
|
164
|
+
self.watchdog_thread = Thread(
|
|
165
|
+
target=run_watchdog,
|
|
166
|
+
daemon=True,
|
|
167
|
+
name="Watchdog",
|
|
168
|
+
args=[callback_args.watchdog_port],
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
self._dispatcher_cm: DispatcherContextMgr
|
|
172
|
+
if callback_args.stomp_config:
|
|
173
|
+
self._dispatcher_cm = StompDispatcherContextMgr(
|
|
174
|
+
callback_args, self.callbacks
|
|
175
|
+
)
|
|
176
|
+
else:
|
|
177
|
+
self._dispatcher_cm = RemoteDispatcherContextMgr(self.callbacks)
|
|
178
|
+
|
|
179
|
+
def start(self):
|
|
180
|
+
log_info(f"Launching threads, with callbacks: {self.callbacks}")
|
|
181
|
+
self.watchdog_thread.start()
|
|
182
|
+
with self._dispatcher_cm:
|
|
183
|
+
ping_watchdog_while_alive(self._dispatcher_cm, self.watchdog_thread)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def run_watchdog(watchdog_port: int):
|
|
187
|
+
log_info("Hyperion watchdog keepalive running")
|
|
188
|
+
while True:
|
|
189
|
+
try:
|
|
190
|
+
with request.urlopen(
|
|
191
|
+
f"http://localhost:{watchdog_port}/callbackPing",
|
|
192
|
+
timeout=PING_TIMEOUT_S,
|
|
193
|
+
) as response:
|
|
194
|
+
if response.status != 200:
|
|
195
|
+
log_debug(
|
|
196
|
+
f"Unable to ping Hyperion liveness endpoint, status {response.status}"
|
|
197
|
+
)
|
|
198
|
+
except URLError as e:
|
|
199
|
+
log_debug("Unable to ping Hyperion liveness endpoint", exc_info=e)
|
|
200
|
+
sleep(HYPERION_PING_INTERVAL_S)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def main(dev_mode=False) -> None:
|
|
204
|
+
callback_args = parse_callback_args()
|
|
205
|
+
callback_args.dev_mode = dev_mode or callback_args.dev_mode
|
|
206
|
+
print(f"In dev mode: {dev_mode}")
|
|
207
|
+
runner = HyperionCallbackRunner(callback_args)
|
|
208
|
+
runner.start()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class DispatcherContextMgr(AbstractContextManager):
|
|
212
|
+
@abstractmethod
|
|
213
|
+
def is_alive(self) -> bool: ...
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class RemoteDispatcherContextMgr(DispatcherContextMgr):
|
|
217
|
+
def __init__(self, callbacks: list[CallbackBase]):
|
|
218
|
+
super().__init__()
|
|
219
|
+
|
|
169
220
|
self.proxy = Proxy(*CONST.CALLBACK_0MQ_PROXY_PORTS)
|
|
170
221
|
self.proxy_thread = Thread(
|
|
171
222
|
target=self.proxy.start, daemon=True, name="0MQ Proxy"
|
|
@@ -182,47 +233,70 @@ class HyperionCallbackRunner:
|
|
|
182
233
|
|
|
183
234
|
self.dispatcher_thread = Thread(
|
|
184
235
|
target=start_dispatcher,
|
|
185
|
-
args=[
|
|
236
|
+
args=[callbacks],
|
|
186
237
|
daemon=True,
|
|
187
238
|
name="0MQ Dispatcher",
|
|
188
239
|
)
|
|
189
|
-
|
|
190
|
-
self.watchdog_thread = Thread(target=run_watchdog, daemon=True, name="Watchdog")
|
|
191
240
|
log_info("Created 0MQ proxy and local RemoteDispatcher.")
|
|
192
241
|
|
|
193
|
-
def
|
|
194
|
-
log_info(
|
|
242
|
+
def __enter__(self):
|
|
243
|
+
log_info("Proxy and dispatcher thread launched.")
|
|
195
244
|
self.proxy_thread.start()
|
|
196
245
|
self.dispatcher_thread.start()
|
|
197
|
-
self
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
246
|
+
return self
|
|
247
|
+
|
|
248
|
+
def __exit__(self, exc_type, exc_value, traceback, /):
|
|
249
|
+
self.dispatcher.stop()
|
|
250
|
+
# proxy has no way to stop
|
|
251
|
+
|
|
252
|
+
def is_alive(self):
|
|
253
|
+
return self.proxy_thread.is_alive() and self.dispatcher_thread.is_alive()
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class StompDispatcherContextMgr(DispatcherContextMgr):
|
|
257
|
+
def __init__(self, args: CallbackArgs, callbacks: list[CallbackBase]):
|
|
258
|
+
super().__init__()
|
|
259
|
+
loader = ConfigLoader(ApplicationConfig)
|
|
260
|
+
loader.use_values_from_yaml(args.stomp_config)
|
|
261
|
+
config = loader.load()
|
|
262
|
+
log_info(
|
|
263
|
+
f"Stomp client configured on {config.stomp.url.host}:{config.stomp.url.port}"
|
|
264
|
+
)
|
|
265
|
+
self._stomp_client = StompClient.for_broker(
|
|
266
|
+
broker=Broker(
|
|
267
|
+
host=config.stomp.url.host,
|
|
268
|
+
port=config.stomp.url.port,
|
|
269
|
+
auth=config.stomp.auth,
|
|
270
|
+
)
|
|
201
271
|
)
|
|
272
|
+
self._dispatcher = StompDispatcher(self._stomp_client)
|
|
273
|
+
for cb in callbacks:
|
|
274
|
+
self._dispatcher.subscribe(cb)
|
|
202
275
|
|
|
276
|
+
def is_alive(self) -> bool:
|
|
277
|
+
return self._stomp_client.is_connected()
|
|
203
278
|
|
|
204
|
-
def
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
try:
|
|
208
|
-
with request.urlopen(
|
|
209
|
-
f"http://localhost:{HyperionConstants.HYPERION_PORT}/callbackPing",
|
|
210
|
-
timeout=PING_TIMEOUT_S,
|
|
211
|
-
) as response:
|
|
212
|
-
if response.status != 200:
|
|
213
|
-
log_debug(
|
|
214
|
-
f"Unable to ping Hyperion liveness endpoint, status {response.status}"
|
|
215
|
-
)
|
|
216
|
-
except URLError as e:
|
|
217
|
-
log_debug("Unable to ping Hyperion liveness endpoint", exc_info=e)
|
|
218
|
-
sleep(HYPERION_PING_INTERVAL_S)
|
|
279
|
+
def __enter__(self):
|
|
280
|
+
self._dispatcher.__enter__()
|
|
281
|
+
return self
|
|
219
282
|
|
|
283
|
+
def __exit__(self, exc_type, exc_value, traceback, /):
|
|
284
|
+
self._dispatcher.__exit__(exc_type, exc_value, traceback)
|
|
220
285
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
286
|
+
|
|
287
|
+
def ping_watchdog_while_alive(
|
|
288
|
+
dispatcher_cm: DispatcherContextMgr, watchdog_thread: Thread
|
|
289
|
+
):
|
|
290
|
+
alive = watchdog_thread.is_alive() and dispatcher_cm.is_alive()
|
|
291
|
+
try:
|
|
292
|
+
log_debug("Trying to wait forever on callback and dispatcher threads")
|
|
293
|
+
while alive:
|
|
294
|
+
sleep(LIVENESS_POLL_SECONDS)
|
|
295
|
+
alive = watchdog_thread.is_alive() and dispatcher_cm.is_alive()
|
|
296
|
+
except KeyboardInterrupt:
|
|
297
|
+
log_info("Main thread received interrupt - exiting.")
|
|
298
|
+
else:
|
|
299
|
+
log_info("Proxy or dispatcher thread ended - exiting.")
|
|
226
300
|
|
|
227
301
|
|
|
228
302
|
if __name__ == "__main__":
|
|
@@ -25,13 +25,15 @@ from mx_bluesky.common.external_interaction.ispyb.ispyb_store import (
|
|
|
25
25
|
StoreInIspyb,
|
|
26
26
|
)
|
|
27
27
|
from mx_bluesky.common.parameters.components import IspybExperimentType
|
|
28
|
+
from mx_bluesky.common.parameters.rotation import (
|
|
29
|
+
SingleRotationScan,
|
|
30
|
+
)
|
|
28
31
|
from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER, set_dcgid_tag
|
|
29
32
|
from mx_bluesky.common.utils.utils import number_of_frames_from_scan_spec
|
|
30
33
|
from mx_bluesky.hyperion.external_interaction.callbacks.rotation.ispyb_mapping import (
|
|
31
34
|
populate_data_collection_info_for_rotation,
|
|
32
35
|
)
|
|
33
36
|
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
34
|
-
from mx_bluesky.hyperion.parameters.rotation import SingleRotationScan
|
|
35
37
|
|
|
36
38
|
if TYPE_CHECKING:
|
|
37
39
|
from event_model.documents import Event, RunStart, RunStop
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from mx_bluesky.common.external_interaction.ispyb.data_model import DataCollectionInfo
|
|
4
|
-
from mx_bluesky.
|
|
4
|
+
from mx_bluesky.common.parameters.rotation import (
|
|
5
|
+
SingleRotationScan,
|
|
6
|
+
)
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
def populate_data_collection_info_for_rotation(params: SingleRotationScan):
|
|
@@ -11,9 +11,12 @@ from mx_bluesky.common.external_interaction.nexus.nexus_utils import (
|
|
|
11
11
|
vds_type_based_on_bit_depth,
|
|
12
12
|
)
|
|
13
13
|
from mx_bluesky.common.external_interaction.nexus.write_nexus import NexusWriter
|
|
14
|
+
from mx_bluesky.common.parameters.constants import RotationParamConstants
|
|
15
|
+
from mx_bluesky.common.parameters.rotation import (
|
|
16
|
+
SingleRotationScan,
|
|
17
|
+
)
|
|
14
18
|
from mx_bluesky.common.utils.log import NEXUS_LOGGER, format_doc_for_log
|
|
15
|
-
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
16
|
-
from mx_bluesky.hyperion.parameters.rotation import SingleRotationScan
|
|
19
|
+
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
17
20
|
|
|
18
21
|
if TYPE_CHECKING:
|
|
19
22
|
from event_model.documents import Event, EventDescriptor, RunStart
|
|
@@ -100,6 +103,6 @@ class RotationNexusFileCallback(PlanReactiveCallback):
|
|
|
100
103
|
full_num_of_images=self.full_num_of_images,
|
|
101
104
|
meta_data_run_number=self.meta_data_run_number,
|
|
102
105
|
axis_direction=AxisDirection.NEGATIVE
|
|
103
|
-
if
|
|
106
|
+
if RotationParamConstants.OMEGA_FLIP
|
|
104
107
|
else AxisDirection.POSITIVE,
|
|
105
108
|
)
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from blueapi.client.event_bus import AnyEvent
|
|
2
|
+
from blueapi.core import DataEvent
|
|
3
|
+
from bluesky.run_engine import Dispatcher
|
|
4
|
+
from bluesky_stomp.messaging import MessageContext, StompClient
|
|
5
|
+
from bluesky_stomp.models import MessageTopic
|
|
6
|
+
from event_model import DocumentNames
|
|
7
|
+
|
|
8
|
+
from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER as LOGGER
|
|
9
|
+
|
|
10
|
+
BLUEAPI_EVENT_TOPIC = "public.worker.event"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class StompDispatcher(Dispatcher):
|
|
14
|
+
def __init__(self, stomp_client: StompClient):
|
|
15
|
+
super().__init__()
|
|
16
|
+
self._client = stomp_client
|
|
17
|
+
|
|
18
|
+
def __enter__(self):
|
|
19
|
+
self._subscription_id = self._client.subscribe(
|
|
20
|
+
MessageTopic(name=BLUEAPI_EVENT_TOPIC), self._on_event
|
|
21
|
+
)
|
|
22
|
+
LOGGER.info("Connecting to stomp broker...")
|
|
23
|
+
self._client.connect()
|
|
24
|
+
|
|
25
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
26
|
+
LOGGER.info("Disconnecting from stomp and unsubscribing...")
|
|
27
|
+
self._client.disconnect()
|
|
28
|
+
self._client.unsubscribe(self._subscription_id)
|
|
29
|
+
|
|
30
|
+
def _on_event(self, event: AnyEvent, context: MessageContext):
|
|
31
|
+
match event:
|
|
32
|
+
case DataEvent(name=name, doc=doc): # type: ignore
|
|
33
|
+
self.process(DocumentNames[name], doc)
|