mx-bluesky 1.4.0__py3-none-any.whl → 1.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mx_bluesky/_version.py +2 -2
- mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +178 -0
- mx_bluesky/beamlines/i04/thawing_plan.py +1 -1
- mx_bluesky/beamlines/i24/serial/dcid.py +143 -171
- mx_bluesky/beamlines/i24/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +1 -1
- mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +54 -21
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +2 -5
- mx_bluesky/beamlines/i24/serial/fixed_target/ft_utils.py +0 -1
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +67 -50
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +26 -79
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +0 -199
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +4 -6
- mx_bluesky/beamlines/i24/serial/log.py +1 -1
- mx_bluesky/beamlines/i24/serial/parameters/__init__.py +4 -0
- mx_bluesky/beamlines/i24/serial/parameters/constants.py +6 -1
- mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py +42 -15
- mx_bluesky/beamlines/i24/serial/run_fixed_target.sh +4 -3
- mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +2 -0
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +103 -81
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +1 -2
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +24 -26
- mx_bluesky/beamlines/i24/serial/write_nexus.py +74 -72
- mx_bluesky/common/external_interaction/config_server.py +46 -0
- mx_bluesky/common/parameters/components.py +52 -15
- mx_bluesky/common/parameters/constants.py +11 -1
- mx_bluesky/common/parameters/gridscan.py +94 -0
- mx_bluesky/{hyperion → common}/parameters/robot_load.py +2 -2
- mx_bluesky/common/plans/do_fgs.py +2 -2
- mx_bluesky/common/utils/log.py +2 -0
- mx_bluesky/hyperion/__main__.py +2 -1
- mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +21 -31
- mx_bluesky/hyperion/device_setup_plans/setup_panda.py +4 -4
- mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +1 -1
- mx_bluesky/hyperion/device_setup_plans/smargon.py +3 -3
- mx_bluesky/hyperion/exceptions.py +13 -1
- mx_bluesky/hyperion/experiment_plans/__init__.py +4 -0
- mx_bluesky/hyperion/experiment_plans/change_aperture_then_move_plan.py +83 -0
- mx_bluesky/hyperion/experiment_plans/common/xrc_result.py +47 -0
- mx_bluesky/hyperion/experiment_plans/experiment_registry.py +9 -9
- mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +133 -97
- mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +42 -18
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +75 -9
- mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +2 -2
- mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +1 -1
- mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +2 -2
- mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +36 -17
- mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +5 -5
- mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +28 -28
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +64 -16
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +11 -3
- mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +10 -10
- mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +0 -4
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +4 -0
- mx_bluesky/hyperion/external_interaction/callbacks/common/abstract_event.py +66 -0
- mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +5 -0
- mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +15 -15
- mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +18 -10
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +3 -1
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +5 -3
- mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/sample_handling_callback.py +84 -0
- mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +15 -9
- mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +5 -4
- mx_bluesky/hyperion/external_interaction/config_server.py +8 -37
- mx_bluesky/hyperion/external_interaction/exceptions.py +0 -9
- mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +65 -15
- mx_bluesky/hyperion/parameters/components.py +4 -9
- mx_bluesky/hyperion/parameters/constants.py +0 -1
- mx_bluesky/hyperion/parameters/gridscan.py +33 -76
- mx_bluesky/hyperion/parameters/load_centre_collect.py +14 -9
- mx_bluesky/hyperion/parameters/rotation.py +15 -6
- {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1.dist-info}/METADATA +35 -34
- {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1.dist-info}/RECORD +77 -70
- {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1.dist-info}/WHEEL +1 -1
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +0 -150
- {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1.dist-info}/LICENSE +0 -0
- {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1.dist-info}/top_level.txt +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import dataclasses
|
|
4
3
|
from typing import cast
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
import bluesky.preprocessors as bpp
|
|
6
|
+
import pydantic
|
|
7
|
+
from blueapi.core import BlueskyContext
|
|
8
|
+
from bluesky.utils import MsgGenerator
|
|
7
9
|
from dodal.devices.aperturescatterguard import ApertureScatterguard
|
|
8
10
|
from dodal.devices.attenuator import Attenuator
|
|
9
11
|
from dodal.devices.backlight import Backlight
|
|
@@ -32,26 +34,36 @@ from dodal.log import LOGGER
|
|
|
32
34
|
from ophyd_async.fastcs.panda import HDFPanda
|
|
33
35
|
|
|
34
36
|
from mx_bluesky.common.parameters.constants import OavConstants
|
|
37
|
+
from mx_bluesky.common.parameters.gridscan import RobotLoadThenCentre
|
|
35
38
|
from mx_bluesky.hyperion.device_setup_plans.utils import (
|
|
36
39
|
fill_in_energy_if_not_supplied,
|
|
37
40
|
start_preparing_data_collection_then_do_plan,
|
|
38
41
|
)
|
|
42
|
+
from mx_bluesky.hyperion.experiment_plans.change_aperture_then_move_plan import (
|
|
43
|
+
change_aperture_then_move_to_xtal,
|
|
44
|
+
)
|
|
45
|
+
from mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan import (
|
|
46
|
+
XRayCentreEventHandler,
|
|
47
|
+
)
|
|
39
48
|
from mx_bluesky.hyperion.experiment_plans.grid_detect_then_xray_centre_plan import (
|
|
40
49
|
GridDetectThenXRayCentreComposite,
|
|
41
50
|
)
|
|
42
51
|
from mx_bluesky.hyperion.experiment_plans.pin_centre_then_xray_centre_plan import (
|
|
43
|
-
|
|
52
|
+
pin_centre_then_flyscan_plan,
|
|
44
53
|
)
|
|
45
54
|
from mx_bluesky.hyperion.experiment_plans.robot_load_and_change_energy import (
|
|
46
55
|
RobotLoadAndEnergyChangeComposite,
|
|
47
56
|
pin_already_loaded,
|
|
48
57
|
robot_load_and_change_energy_plan,
|
|
49
58
|
)
|
|
59
|
+
from mx_bluesky.hyperion.experiment_plans.set_energy_plan import (
|
|
60
|
+
SetEnergyComposite,
|
|
61
|
+
set_energy_plan,
|
|
62
|
+
)
|
|
50
63
|
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
51
|
-
from mx_bluesky.hyperion.parameters.gridscan import RobotLoadThenCentre
|
|
52
64
|
|
|
53
65
|
|
|
54
|
-
@dataclasses.dataclass
|
|
66
|
+
@pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
|
|
55
67
|
class RobotLoadThenCentreComposite:
|
|
56
68
|
# common fields
|
|
57
69
|
xbpm_feedback: XBPMFeedback
|
|
@@ -88,6 +100,10 @@ class RobotLoadThenCentreComposite:
|
|
|
88
100
|
webcam: Webcam
|
|
89
101
|
lower_gonio: XYZPositioner
|
|
90
102
|
|
|
103
|
+
@property
|
|
104
|
+
def sample_motors(self):
|
|
105
|
+
return self.smargon
|
|
106
|
+
|
|
91
107
|
|
|
92
108
|
def create_devices(context: BlueskyContext) -> RobotLoadThenCentreComposite:
|
|
93
109
|
from mx_bluesky.hyperion.utils.context import device_composite_from_context
|
|
@@ -95,18 +111,18 @@ def create_devices(context: BlueskyContext) -> RobotLoadThenCentreComposite:
|
|
|
95
111
|
return device_composite_from_context(context, RobotLoadThenCentreComposite)
|
|
96
112
|
|
|
97
113
|
|
|
98
|
-
def
|
|
114
|
+
def _flyscan_plan_from_robot_load_params(
|
|
99
115
|
composite: RobotLoadThenCentreComposite,
|
|
100
116
|
params: RobotLoadThenCentre,
|
|
101
117
|
oav_config_file: str = OavConstants.OAV_CONFIG_JSON,
|
|
102
118
|
):
|
|
103
|
-
yield from
|
|
119
|
+
yield from pin_centre_then_flyscan_plan(
|
|
104
120
|
cast(GridDetectThenXRayCentreComposite, composite),
|
|
105
121
|
params.pin_centre_then_xray_centre_params(),
|
|
106
122
|
)
|
|
107
123
|
|
|
108
124
|
|
|
109
|
-
def
|
|
125
|
+
def _robot_load_then_flyscan_plan(
|
|
110
126
|
composite: RobotLoadThenCentreComposite,
|
|
111
127
|
params: RobotLoadThenCentre,
|
|
112
128
|
oav_config_file: str = OavConstants.OAV_CONFIG_JSON,
|
|
@@ -116,13 +132,36 @@ def robot_load_then_centre_plan(
|
|
|
116
132
|
params.robot_load_params(),
|
|
117
133
|
)
|
|
118
134
|
|
|
119
|
-
yield from
|
|
135
|
+
yield from _flyscan_plan_from_robot_load_params(composite, params, oav_config_file)
|
|
120
136
|
|
|
121
137
|
|
|
122
138
|
def robot_load_then_centre(
|
|
123
139
|
composite: RobotLoadThenCentreComposite,
|
|
124
140
|
parameters: RobotLoadThenCentre,
|
|
125
141
|
) -> MsgGenerator:
|
|
142
|
+
"""Perform pin-tip detection followed by a flyscan to determine centres of interest.
|
|
143
|
+
Performs a robot load if necessary. Centre on the best diffracting centre.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
xray_centre_event_handler = XRayCentreEventHandler()
|
|
147
|
+
|
|
148
|
+
yield from bpp.subs_wrapper(
|
|
149
|
+
robot_load_then_xray_centre(composite, parameters), xray_centre_event_handler
|
|
150
|
+
)
|
|
151
|
+
flyscan_results = xray_centre_event_handler.xray_centre_results
|
|
152
|
+
if flyscan_results is not None:
|
|
153
|
+
yield from change_aperture_then_move_to_xtal(
|
|
154
|
+
flyscan_results[0], composite.smargon, composite.aperture_scatterguard
|
|
155
|
+
)
|
|
156
|
+
# else no chi change, no need to recentre.
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def robot_load_then_xray_centre(
|
|
160
|
+
composite: RobotLoadThenCentreComposite,
|
|
161
|
+
parameters: RobotLoadThenCentre,
|
|
162
|
+
) -> MsgGenerator:
|
|
163
|
+
"""Perform pin-tip detection followed by a flyscan to determine centres of interest.
|
|
164
|
+
Performs a robot load if necessary."""
|
|
126
165
|
eiger: EigerDetector = composite.eiger
|
|
127
166
|
|
|
128
167
|
# TODO: get these from one source of truth #254
|
|
@@ -138,17 +177,26 @@ def robot_load_then_centre(
|
|
|
138
177
|
doing_chi_change = parameters.chi_start_deg is not None
|
|
139
178
|
|
|
140
179
|
if doing_sample_load:
|
|
141
|
-
|
|
180
|
+
LOGGER.info("Pin not loaded, loading and centring")
|
|
181
|
+
plan = _robot_load_then_flyscan_plan(
|
|
142
182
|
composite,
|
|
143
183
|
parameters,
|
|
144
184
|
)
|
|
145
|
-
LOGGER.info("Pin not loaded, loading and centring")
|
|
146
|
-
elif doing_chi_change:
|
|
147
|
-
plan = centring_plan_from_robot_load_params(composite, parameters)
|
|
148
|
-
LOGGER.info("Pin already loaded but chi changed so centring")
|
|
149
185
|
else:
|
|
150
|
-
|
|
151
|
-
|
|
186
|
+
# Robot load normally sets the energy so we should do this explicitly if no load is
|
|
187
|
+
# being done
|
|
188
|
+
demand_energy_ev = parameters.demand_energy_ev
|
|
189
|
+
LOGGER.info(f"Setting the energy to {demand_energy_ev}eV")
|
|
190
|
+
yield from set_energy_plan(
|
|
191
|
+
demand_energy_ev, cast(SetEnergyComposite, composite)
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if doing_chi_change:
|
|
195
|
+
plan = _flyscan_plan_from_robot_load_params(composite, parameters)
|
|
196
|
+
LOGGER.info("Pin already loaded but chi changed so centring")
|
|
197
|
+
else:
|
|
198
|
+
LOGGER.info("Pin already loaded and chi not changed so doing nothing")
|
|
199
|
+
return
|
|
152
200
|
|
|
153
201
|
detector_params = yield from fill_in_energy_if_not_supplied(
|
|
154
202
|
composite.dcm, parameters.detector_params
|
|
@@ -4,7 +4,9 @@ import dataclasses
|
|
|
4
4
|
|
|
5
5
|
import bluesky.plan_stubs as bps
|
|
6
6
|
import bluesky.preprocessors as bpp
|
|
7
|
-
|
|
7
|
+
import pydantic
|
|
8
|
+
from blueapi.core import BlueskyContext
|
|
9
|
+
from bluesky.utils import MsgGenerator
|
|
8
10
|
from dodal.devices.aperturescatterguard import ApertureScatterguard
|
|
9
11
|
from dodal.devices.attenuator import Attenuator
|
|
10
12
|
from dodal.devices.backlight import Backlight
|
|
@@ -22,7 +24,7 @@ from dodal.devices.undulator import Undulator
|
|
|
22
24
|
from dodal.devices.xbpm_feedback import XBPMFeedback
|
|
23
25
|
from dodal.devices.zebra import RotationDirection, Zebra
|
|
24
26
|
from dodal.devices.zebra_controlled_shutter import ZebraShutter
|
|
25
|
-
from dodal.
|
|
27
|
+
from dodal.plan_stubs.check_topup import check_topup_and_wait_if_necessary
|
|
26
28
|
|
|
27
29
|
from mx_bluesky.common.device_setup_plans.read_hardware_for_setup import (
|
|
28
30
|
read_hardware_for_zocalo,
|
|
@@ -62,7 +64,7 @@ from mx_bluesky.hyperion.parameters.rotation import (
|
|
|
62
64
|
from mx_bluesky.hyperion.utils.context import device_composite_from_context
|
|
63
65
|
|
|
64
66
|
|
|
65
|
-
@dataclasses.dataclass
|
|
67
|
+
@pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
|
|
66
68
|
class RotationScanComposite(OavSnapshotComposite):
|
|
67
69
|
"""All devices which are directly or indirectly required by this plan"""
|
|
68
70
|
|
|
@@ -409,6 +411,11 @@ def multi_rotation_scan(
|
|
|
409
411
|
}
|
|
410
412
|
)
|
|
411
413
|
@bpp.stage_decorator([eiger])
|
|
414
|
+
@transmission_and_xbpm_feedback_for_collection_decorator(
|
|
415
|
+
composite.xbpm_feedback,
|
|
416
|
+
composite.attenuator,
|
|
417
|
+
parameters.transmission_frac,
|
|
418
|
+
)
|
|
412
419
|
@bpp.finalize_decorator(lambda: _cleanup_plan(composite))
|
|
413
420
|
def _multi_rotation_scan():
|
|
414
421
|
for single_scan in parameters.single_rotation_scans:
|
|
@@ -418,6 +425,7 @@ def multi_rotation_scan(
|
|
|
418
425
|
md={
|
|
419
426
|
"subplan_name": CONST.PLAN.ROTATION_OUTER,
|
|
420
427
|
CONST.TRIGGER.ZOCALO: CONST.PLAN.ROTATION_MAIN,
|
|
428
|
+
"zocalo_environment": CONST.ZOCALO_ENV,
|
|
421
429
|
"hyperion_parameters": single_scan.model_dump_json(),
|
|
422
430
|
}
|
|
423
431
|
)
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
* reenable feedback
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
|
|
8
|
+
import pydantic
|
|
10
9
|
from bluesky import plan_stubs as bps
|
|
11
10
|
from dodal.devices.attenuator import Attenuator
|
|
12
11
|
from dodal.devices.dcm import DCM
|
|
@@ -24,7 +23,7 @@ DESIRED_TRANSMISSION_FRACTION = 0.1
|
|
|
24
23
|
UNDULATOR_GROUP = "UNDULATOR_GROUP"
|
|
25
24
|
|
|
26
25
|
|
|
27
|
-
@dataclasses.dataclass
|
|
26
|
+
@pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
|
|
28
27
|
class SetEnergyComposite:
|
|
29
28
|
vfm: FocusingMirrorWithStripes
|
|
30
29
|
mirror_voltages: MirrorVoltages
|
|
@@ -49,12 +48,13 @@ def _set_energy_plan(
|
|
|
49
48
|
|
|
50
49
|
|
|
51
50
|
def set_energy_plan(
|
|
52
|
-
|
|
51
|
+
energy_ev: float | None,
|
|
53
52
|
composite: SetEnergyComposite,
|
|
54
53
|
):
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
if energy_ev:
|
|
55
|
+
yield from transmission_and_xbpm_feedback_for_collection_wrapper(
|
|
56
|
+
_set_energy_plan(energy_ev / 1000, composite),
|
|
57
|
+
composite.xbpm_feedback,
|
|
58
|
+
composite.attenuator,
|
|
59
|
+
DESIRED_TRANSMISSION_FRACTION,
|
|
60
|
+
)
|
|
@@ -20,6 +20,9 @@ from mx_bluesky.hyperion.external_interaction.callbacks.rotation.ispyb_callback
|
|
|
20
20
|
from mx_bluesky.hyperion.external_interaction.callbacks.rotation.nexus_callback import (
|
|
21
21
|
RotationNexusFileCallback,
|
|
22
22
|
)
|
|
23
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.sample_handling.sample_handling_callback import (
|
|
24
|
+
SampleHandlingCallback,
|
|
25
|
+
)
|
|
23
26
|
from mx_bluesky.hyperion.external_interaction.callbacks.xray_centre.ispyb_callback import (
|
|
24
27
|
GridscanISPyBCallback,
|
|
25
28
|
)
|
|
@@ -49,6 +52,7 @@ def setup_callbacks():
|
|
|
49
52
|
RotationISPyBCallback(emit=zocalo),
|
|
50
53
|
LogUidTaggingCallback(),
|
|
51
54
|
RobotLoadISPyBCallback(),
|
|
55
|
+
SampleHandlingCallback(),
|
|
52
56
|
]
|
|
53
57
|
|
|
54
58
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
import dataclasses
|
|
3
|
+
import time
|
|
4
|
+
from abc import ABC
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
from bluesky.protocols import Readable, Reading
|
|
8
|
+
from event_model import DataKey
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclasses.dataclass(frozen=True)
|
|
12
|
+
class AbstractEvent(Readable, ABC):
|
|
13
|
+
"""An abstract superclass that can be extended to provide lightweight software events
|
|
14
|
+
for bluesky plans, without having to incur the overhead of creating ophyd-async devices
|
|
15
|
+
specifically for the purpose.
|
|
16
|
+
|
|
17
|
+
The currently supported types for field annotations in the event are ``str``, ``int``, ``float``, ``bool``
|
|
18
|
+
|
|
19
|
+
In future array types may be supported.
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
Subclasses should extend this class and decorate with::
|
|
23
|
+
|
|
24
|
+
@dataclasses.dataclass(frozen=True)
|
|
25
|
+
|
|
26
|
+
To raise an event, simply construct the event and then ``read`` it as you would a device::
|
|
27
|
+
|
|
28
|
+
yield from bps.create("MY_EVENT_NAME")
|
|
29
|
+
my_event = MyEvent(an_int=1)
|
|
30
|
+
yield from bps.read(my_event)
|
|
31
|
+
yield from bps.save()
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def read(self) -> dict[str, Reading]:
|
|
35
|
+
return {
|
|
36
|
+
f.name: AbstractEvent._reading_from_value(getattr(self, f.name))
|
|
37
|
+
for f in dataclasses.fields(self)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
def describe(self) -> dict[str, DataKey]:
|
|
41
|
+
return {
|
|
42
|
+
f.name: DataKey(dtype=AbstractEvent._dtype_of(f.type), shape=[], source="")
|
|
43
|
+
for f in dataclasses.fields(self)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def _reading_from_value(cls, value):
|
|
48
|
+
return Reading(timestamp=time.time(), value=value)
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def _dtype_of(cls, t) -> Literal["string", "number", "boolean", "integer"]:
|
|
52
|
+
match t:
|
|
53
|
+
case builtins.str:
|
|
54
|
+
return "string"
|
|
55
|
+
case builtins.bool:
|
|
56
|
+
return "boolean"
|
|
57
|
+
case builtins.int:
|
|
58
|
+
return "integer"
|
|
59
|
+
case builtins.float:
|
|
60
|
+
return "number"
|
|
61
|
+
# TODO array support
|
|
62
|
+
raise ValueError(f"Unsupported type for AbstractEvent: {t}")
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def name(self) -> str:
|
|
66
|
+
return type(self).__name__
|
|
@@ -11,6 +11,9 @@ from mx_bluesky.hyperion.external_interaction.callbacks.rotation.ispyb_callback
|
|
|
11
11
|
from mx_bluesky.hyperion.external_interaction.callbacks.rotation.nexus_callback import (
|
|
12
12
|
RotationNexusFileCallback,
|
|
13
13
|
)
|
|
14
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.sample_handling.sample_handling_callback import (
|
|
15
|
+
SampleHandlingCallback,
|
|
16
|
+
)
|
|
14
17
|
from mx_bluesky.hyperion.external_interaction.callbacks.xray_centre.ispyb_callback import (
|
|
15
18
|
GridscanISPyBCallback,
|
|
16
19
|
)
|
|
@@ -53,6 +56,7 @@ def create_load_centre_collect_callbacks() -> (
|
|
|
53
56
|
RobotLoadISPyBCallback,
|
|
54
57
|
RotationNexusFileCallback,
|
|
55
58
|
RotationISPyBCallback,
|
|
59
|
+
SampleHandlingCallback,
|
|
56
60
|
]
|
|
57
61
|
):
|
|
58
62
|
return (
|
|
@@ -61,4 +65,5 @@ def create_load_centre_collect_callbacks() -> (
|
|
|
61
65
|
RobotLoadISPyBCallback(),
|
|
62
66
|
RotationNexusFileCallback(),
|
|
63
67
|
RotationISPyBCallback(emit=ZocaloCallback()),
|
|
68
|
+
SampleHandlingCallback(),
|
|
64
69
|
)
|
|
@@ -28,7 +28,7 @@ class GridDetectionCallback(CallbackBase):
|
|
|
28
28
|
*args,
|
|
29
29
|
) -> None:
|
|
30
30
|
super().__init__(*args)
|
|
31
|
-
self.
|
|
31
|
+
self.start_positions_mm: list = []
|
|
32
32
|
self.box_numbers: list = []
|
|
33
33
|
|
|
34
34
|
def event(self, doc: Event):
|
|
@@ -55,16 +55,16 @@ class GridDetectionCallback(CallbackBase):
|
|
|
55
55
|
beam_x = data["oav-beam_centre_i"]
|
|
56
56
|
beam_y = data["oav-beam_centre_j"]
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
position_grid_start_mm = calculate_x_y_z_of_pixel(
|
|
59
59
|
current_xyz,
|
|
60
60
|
smargon_omega,
|
|
61
61
|
centre_of_first_box,
|
|
62
62
|
(beam_x, beam_y),
|
|
63
63
|
(microns_per_pixel_x, microns_per_pixel_y),
|
|
64
64
|
)
|
|
65
|
-
LOGGER.info(f"Calculated start position {
|
|
65
|
+
LOGGER.info(f"Calculated start position {position_grid_start_mm}")
|
|
66
66
|
|
|
67
|
-
self.
|
|
67
|
+
self.start_positions_mm.append(position_grid_start_mm)
|
|
68
68
|
self.box_numbers.append(
|
|
69
69
|
(
|
|
70
70
|
data["oav-grid_snapshot-num_boxes_x"],
|
|
@@ -72,22 +72,22 @@ class GridDetectionCallback(CallbackBase):
|
|
|
72
72
|
)
|
|
73
73
|
)
|
|
74
74
|
|
|
75
|
-
self.
|
|
76
|
-
self.
|
|
77
|
-
self.
|
|
75
|
+
self.x_step_size_um = box_width_px * microns_per_pixel_x
|
|
76
|
+
self.y_step_size_um = box_width_px * microns_per_pixel_y
|
|
77
|
+
self.z_step_size_um = box_width_px * microns_per_pixel_y
|
|
78
78
|
return doc
|
|
79
79
|
|
|
80
80
|
def get_grid_parameters(self) -> GridParamUpdate:
|
|
81
81
|
return {
|
|
82
|
-
"x_start_um": self.
|
|
83
|
-
"y_start_um": self.
|
|
84
|
-
"y2_start_um": self.
|
|
85
|
-
"z_start_um": self.
|
|
86
|
-
"z2_start_um": self.
|
|
82
|
+
"x_start_um": self.start_positions_mm[0][0] * 1000,
|
|
83
|
+
"y_start_um": self.start_positions_mm[0][1] * 1000,
|
|
84
|
+
"y2_start_um": self.start_positions_mm[0][1] * 1000,
|
|
85
|
+
"z_start_um": self.start_positions_mm[1][2] * 1000,
|
|
86
|
+
"z2_start_um": self.start_positions_mm[1][2] * 1000,
|
|
87
87
|
"x_steps": self.box_numbers[0][0],
|
|
88
88
|
"y_steps": self.box_numbers[0][1],
|
|
89
89
|
"z_steps": self.box_numbers[1][1],
|
|
90
|
-
"x_step_size_um": self.
|
|
91
|
-
"y_step_size_um": self.
|
|
92
|
-
"z_step_size_um": self.
|
|
90
|
+
"x_step_size_um": self.x_step_size_um,
|
|
91
|
+
"y_step_size_um": self.y_step_size_um,
|
|
92
|
+
"z_step_size_um": self.z_step_size_um,
|
|
93
93
|
}
|
|
@@ -2,8 +2,6 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
-
from event_model.documents import EventDescriptor
|
|
6
|
-
|
|
7
5
|
from mx_bluesky.hyperion.external_interaction.callbacks.common.ispyb_mapping import (
|
|
8
6
|
get_proposal_and_session_from_visit_string,
|
|
9
7
|
)
|
|
@@ -11,6 +9,7 @@ from mx_bluesky.hyperion.external_interaction.callbacks.plan_reactive_callback i
|
|
|
11
9
|
PlanReactiveCallback,
|
|
12
10
|
)
|
|
13
11
|
from mx_bluesky.hyperion.external_interaction.ispyb.exp_eye_store import (
|
|
12
|
+
BLSampleStatus,
|
|
14
13
|
ExpeyeInteraction,
|
|
15
14
|
RobotActionID,
|
|
16
15
|
)
|
|
@@ -25,6 +24,7 @@ class RobotLoadISPyBCallback(PlanReactiveCallback):
|
|
|
25
24
|
def __init__(self) -> None:
|
|
26
25
|
ISPYB_LOGGER.debug("Initialising ISPyB Robot Load Callback")
|
|
27
26
|
super().__init__(log=ISPYB_LOGGER)
|
|
27
|
+
self._metadata: dict | None = None
|
|
28
28
|
self.run_uid: str | None = None
|
|
29
29
|
self.descriptors: dict[str, EventDescriptor] = {}
|
|
30
30
|
self.action_id: RobotActionID | None = None
|
|
@@ -35,16 +35,17 @@ class RobotLoadISPyBCallback(PlanReactiveCallback):
|
|
|
35
35
|
if doc.get("subplan_name") == CONST.PLAN.ROBOT_LOAD:
|
|
36
36
|
ISPYB_LOGGER.debug(f"ISPyB robot load callback received: {doc}")
|
|
37
37
|
self.run_uid = doc.get("uid")
|
|
38
|
-
|
|
38
|
+
self._metadata = doc.get("metadata")
|
|
39
|
+
assert isinstance(self._metadata, dict)
|
|
39
40
|
proposal, session = get_proposal_and_session_from_visit_string(
|
|
40
|
-
|
|
41
|
+
self._metadata["visit"]
|
|
41
42
|
)
|
|
42
43
|
self.action_id = self.expeye.start_load(
|
|
43
44
|
proposal,
|
|
44
45
|
session,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
self._metadata["sample_id"],
|
|
47
|
+
self._metadata["sample_puck"],
|
|
48
|
+
self._metadata["sample_pin"],
|
|
48
49
|
)
|
|
49
50
|
return super().activity_gated_start(doc)
|
|
50
51
|
|
|
@@ -77,10 +78,17 @@ class RobotLoadISPyBCallback(PlanReactiveCallback):
|
|
|
77
78
|
assert (
|
|
78
79
|
self.action_id is not None
|
|
79
80
|
), "ISPyB Robot load callback stop called unexpectedly"
|
|
80
|
-
exit_status = (
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
exit_status = doc.get("exit_status")
|
|
82
|
+
assert exit_status, "Exit status not available in stop document!"
|
|
83
|
+
assert self._metadata, "Metadata not received before stop document."
|
|
83
84
|
reason = doc.get("reason") or "OK"
|
|
85
|
+
|
|
84
86
|
self.expeye.end_load(self.action_id, exit_status, reason)
|
|
87
|
+
self.expeye.update_sample_status(
|
|
88
|
+
self._metadata["sample_id"],
|
|
89
|
+
BLSampleStatus.LOADED
|
|
90
|
+
if exit_status == "success"
|
|
91
|
+
else BLSampleStatus.ERROR_BEAMLINE,
|
|
92
|
+
)
|
|
85
93
|
self.action_id = None
|
|
86
94
|
return super().activity_gated_stop(doc)
|
|
@@ -61,7 +61,9 @@ class RotationISPyBCallback(BaseISPyBCallback):
|
|
|
61
61
|
ISPYB_LOGGER.info(
|
|
62
62
|
"ISPyB callback received start document with experiment parameters."
|
|
63
63
|
)
|
|
64
|
-
|
|
64
|
+
hyperion_params = doc.get("hyperion_parameters")
|
|
65
|
+
assert isinstance(hyperion_params, str)
|
|
66
|
+
self.params = RotationScan.model_validate_json(hyperion_params)
|
|
65
67
|
dcgid = (
|
|
66
68
|
self.ispyb_ids.data_collection_group_id
|
|
67
69
|
if (self.params.sample_id == self.last_sample_id)
|
|
@@ -78,12 +78,14 @@ class RotationNexusFileCallback(PlanReactiveCallback):
|
|
|
78
78
|
self.meta_data_run_number = doc.get("meta_data_run_number")
|
|
79
79
|
if doc.get("subplan_name") == CONST.PLAN.ROTATION_OUTER:
|
|
80
80
|
self.run_uid = doc.get("uid")
|
|
81
|
-
|
|
81
|
+
hyperion_params = doc.get("hyperion_parameters")
|
|
82
|
+
assert isinstance(hyperion_params, str)
|
|
82
83
|
NEXUS_LOGGER.info(
|
|
83
|
-
f"Nexus writer received start document with experiment parameters {
|
|
84
|
+
f"Nexus writer received start document with experiment parameters {hyperion_params}"
|
|
84
85
|
)
|
|
85
|
-
parameters = RotationScan.
|
|
86
|
+
parameters = RotationScan.model_validate_json(hyperion_params)
|
|
86
87
|
NEXUS_LOGGER.info("Setting up nexus file...")
|
|
88
|
+
|
|
87
89
|
det_size = (
|
|
88
90
|
parameters.detector_params.detector_size_constants.det_size_pixels
|
|
89
91
|
)
|
|
File without changes
|
mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/sample_handling_callback.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
from collections.abc import Generator
|
|
3
|
+
from functools import partial
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import bluesky.plan_stubs as bps
|
|
7
|
+
from bluesky.preprocessors import contingency_wrapper
|
|
8
|
+
from bluesky.utils import Msg, make_decorator
|
|
9
|
+
from event_model import Event, EventDescriptor, RunStart
|
|
10
|
+
|
|
11
|
+
from mx_bluesky.hyperion.exceptions import CrystalNotFoundException, SampleException
|
|
12
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.common.abstract_event import (
|
|
13
|
+
AbstractEvent,
|
|
14
|
+
)
|
|
15
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.plan_reactive_callback import (
|
|
16
|
+
PlanReactiveCallback,
|
|
17
|
+
)
|
|
18
|
+
from mx_bluesky.hyperion.external_interaction.ispyb.exp_eye_store import (
|
|
19
|
+
BLSampleStatus,
|
|
20
|
+
ExpeyeInteraction,
|
|
21
|
+
)
|
|
22
|
+
from mx_bluesky.hyperion.log import ISPYB_LOGGER
|
|
23
|
+
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
24
|
+
|
|
25
|
+
# TODO remove this event-raising shenanigans once
|
|
26
|
+
# https://github.com/bluesky/bluesky/issues/1829 is addressed
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclasses.dataclass(frozen=True)
|
|
30
|
+
class _ExceptionEvent(AbstractEvent):
|
|
31
|
+
exception_type: str
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _exception_interceptor(exception: Exception) -> Generator[Msg, Any, Any]:
|
|
35
|
+
yield from bps.create(CONST.DESCRIPTORS.SAMPLE_HANDLING_EXCEPTION)
|
|
36
|
+
yield from bps.read(_ExceptionEvent(type(exception).__name__))
|
|
37
|
+
yield from bps.save()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
sample_handling_callback_decorator = make_decorator(
|
|
41
|
+
partial(contingency_wrapper, except_plan=_exception_interceptor)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SampleHandlingCallback(PlanReactiveCallback):
|
|
46
|
+
"""Intercepts exceptions from experiment plans and updates the ISPyB BLSampleStatus
|
|
47
|
+
field according to the type of exception raised."""
|
|
48
|
+
|
|
49
|
+
def __init__(self):
|
|
50
|
+
super().__init__(log=ISPYB_LOGGER)
|
|
51
|
+
self._sample_id: int | None = None
|
|
52
|
+
self._descriptor: str | None = None
|
|
53
|
+
|
|
54
|
+
def activity_gated_start(self, doc: RunStart):
|
|
55
|
+
if not self._sample_id:
|
|
56
|
+
sample_id = doc.get("metadata", {}).get("sample_id")
|
|
57
|
+
self.log.info(f"Recording sample ID at run start {sample_id}")
|
|
58
|
+
self._sample_id = sample_id
|
|
59
|
+
|
|
60
|
+
def activity_gated_descriptor(self, doc: EventDescriptor) -> EventDescriptor | None:
|
|
61
|
+
if doc.get("name") == CONST.DESCRIPTORS.SAMPLE_HANDLING_EXCEPTION:
|
|
62
|
+
self._descriptor = doc["uid"]
|
|
63
|
+
return super().activity_gated_descriptor(doc)
|
|
64
|
+
|
|
65
|
+
def activity_gated_event(self, doc: Event) -> Event | None:
|
|
66
|
+
if doc["descriptor"] == self._descriptor:
|
|
67
|
+
exception_type = doc["data"]["exception_type"]
|
|
68
|
+
self.log.info(
|
|
69
|
+
f"Sample handling callback intercepted exception of type {exception_type}"
|
|
70
|
+
)
|
|
71
|
+
self._record_exception(exception_type)
|
|
72
|
+
return doc
|
|
73
|
+
|
|
74
|
+
def _record_exception(self, exception_type: str):
|
|
75
|
+
expeye = ExpeyeInteraction()
|
|
76
|
+
assert self._sample_id, "Unable to record exception due to no sample ID"
|
|
77
|
+
sample_status = self._decode_sample_status(exception_type)
|
|
78
|
+
expeye.update_sample_status(self._sample_id, sample_status)
|
|
79
|
+
|
|
80
|
+
def _decode_sample_status(self, exception_type: str) -> BLSampleStatus:
|
|
81
|
+
match exception_type:
|
|
82
|
+
case SampleException.__name__ | CrystalNotFoundException.__name__:
|
|
83
|
+
return BLSampleStatus.ERROR_SAMPLE
|
|
84
|
+
return BLSampleStatus.ERROR_BEAMLINE
|
|
@@ -5,8 +5,8 @@ from time import time
|
|
|
5
5
|
from typing import TYPE_CHECKING, Any
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
|
-
from blueapi.core import MsgGenerator
|
|
9
8
|
from bluesky import preprocessors as bpp
|
|
9
|
+
from bluesky.utils import MsgGenerator
|
|
10
10
|
from dodal.devices.zocalo.zocalo_results import (
|
|
11
11
|
ZOCALO_READING_PLAN_NAME,
|
|
12
12
|
get_processing_results_from_event,
|
|
@@ -53,13 +53,16 @@ if TYPE_CHECKING:
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
def ispyb_activation_wrapper(plan_generator: MsgGenerator, parameters):
|
|
56
|
-
return bpp.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
return bpp.set_run_key_wrapper(
|
|
57
|
+
bpp.run_wrapper(
|
|
58
|
+
plan_generator,
|
|
59
|
+
md={
|
|
60
|
+
"activate_callbacks": ["GridscanISPyBCallback"],
|
|
61
|
+
"subplan_name": CONST.PLAN.GRID_DETECT_AND_DO_GRIDSCAN,
|
|
62
|
+
"hyperion_parameters": parameters.model_dump_json(),
|
|
63
|
+
},
|
|
64
|
+
),
|
|
65
|
+
CONST.PLAN.ISPYB_ACTIVATION,
|
|
63
66
|
)
|
|
64
67
|
|
|
65
68
|
|
|
@@ -98,7 +101,9 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
98
101
|
"ISPyB callback received start document with experiment parameters and "
|
|
99
102
|
f"uid: {self.uid_to_finalize_on}"
|
|
100
103
|
)
|
|
101
|
-
|
|
104
|
+
hyperion_params = doc.get("hyperion_parameters")
|
|
105
|
+
assert isinstance(hyperion_params, str)
|
|
106
|
+
self.params = GridCommon.model_validate_json(hyperion_params)
|
|
102
107
|
self.ispyb = StoreInIspyb(self.ispyb_config)
|
|
103
108
|
data_collection_group_info = populate_data_collection_group(self.params)
|
|
104
109
|
|
|
@@ -152,6 +157,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
152
157
|
ISPYB_LOGGER.info(
|
|
153
158
|
f"Amending comment based on Zocalo reading doc: {format_doc_for_log(doc)}"
|
|
154
159
|
)
|
|
160
|
+
|
|
155
161
|
raw_results = get_processing_results_from_event("zocalo", doc)
|
|
156
162
|
if len(raw_results) > 0:
|
|
157
163
|
for n, res in enumerate(raw_results):
|