mx-bluesky 1.5.0__py3-none-any.whl → 1.5.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/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +8 -8
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +5 -5
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +4 -4
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +2 -2
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +5 -5
- mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +3 -3
- mx_bluesky/common/device_setup_plans/robot_load_unload.py +123 -0
- mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +1 -0
- mx_bluesky/common/external_interaction/ispyb/exp_eye_store.py +26 -24
- mx_bluesky/common/external_interaction/ispyb/ispyb_store.py +0 -1
- mx_bluesky/common/external_interaction/nexus/write_nexus.py +2 -2
- mx_bluesky/common/parameters/components.py +7 -2
- mx_bluesky/common/parameters/constants.py +4 -3
- mx_bluesky/common/xrc_result.py +25 -2
- mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +7 -1
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +26 -8
- mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +21 -75
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +2 -2
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +9 -4
- mx_bluesky/hyperion/external_interaction/agamemnon.py +4 -4
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +1 -1
- mx_bluesky/hyperion/external_interaction/callbacks/{robot_load → robot_actions}/ispyb_callback.py +28 -19
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +1 -1
- mx_bluesky/hyperion/external_interaction/callbacks/snapshot_callback.py +3 -0
- mx_bluesky/hyperion/external_interaction/config_server.py +0 -11
- mx_bluesky/hyperion/parameters/constants.py +1 -5
- mx_bluesky/hyperion/parameters/gridscan.py +2 -6
- mx_bluesky/hyperion/parameters/load_centre_collect.py +15 -0
- mx_bluesky/hyperion/parameters/rotation.py +7 -3
- {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.1.dist-info}/METADATA +4 -3
- {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.1.dist-info}/RECORD +36 -36
- mx_bluesky/hyperion/utils/validation.py +0 -196
- {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.1.dist-info}/WHEEL +0 -0
- {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.1.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.1.dist-info}/licenses/LICENSE +0 -0
- {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.1.dist-info}/top_level.txt +0 -0
|
@@ -76,17 +76,29 @@ def load_centre_collect_full(
|
|
|
76
76
|
flyscan_event_handler,
|
|
77
77
|
)
|
|
78
78
|
|
|
79
|
-
locations_to_collect_um: list[np.ndarray]
|
|
79
|
+
locations_to_collect_um: list[np.ndarray]
|
|
80
|
+
samples_to_collect: list[int]
|
|
80
81
|
|
|
81
82
|
if flyscan_event_handler.xray_centre_results:
|
|
82
83
|
selection_func = flyscan_result.resolve_selection_fn(
|
|
83
84
|
parameters.selection_params
|
|
84
85
|
)
|
|
85
86
|
hits = selection_func(flyscan_event_handler.xray_centre_results)
|
|
86
|
-
|
|
87
|
+
hits_to_collect = []
|
|
88
|
+
for hit in hits:
|
|
89
|
+
if hit.sample_id is None:
|
|
90
|
+
LOGGER.warning(
|
|
91
|
+
f"Diffracting centre {hit} not collected because no sample id was assigned."
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
hits_to_collect.append(hit)
|
|
87
95
|
|
|
96
|
+
locations_to_collect_um = [
|
|
97
|
+
hit.centre_of_mass_mm * 1000 for hit in hits_to_collect
|
|
98
|
+
]
|
|
99
|
+
samples_to_collect = [hit.sample_id for hit in hits_to_collect]
|
|
88
100
|
LOGGER.info(
|
|
89
|
-
f"Selected hits {
|
|
101
|
+
f"Selected hits {hits_to_collect} using {selection_func}, args={parameters.selection_params}"
|
|
90
102
|
)
|
|
91
103
|
else:
|
|
92
104
|
# If the xray centring hasn't found a result but has not thrown an error it
|
|
@@ -98,6 +110,7 @@ def load_centre_collect_full(
|
|
|
98
110
|
locations_to_collect_um = [
|
|
99
111
|
np.array([initial_x_mm, initial_y_mm, initial_z_mm]) * 1000
|
|
100
112
|
]
|
|
113
|
+
samples_to_collect = [parameters.sample_id]
|
|
101
114
|
|
|
102
115
|
multi_rotation = parameters.multi_rotation_scan
|
|
103
116
|
rotation_template = multi_rotation.rotation_scans.copy()
|
|
@@ -108,9 +121,11 @@ def load_centre_collect_full(
|
|
|
108
121
|
|
|
109
122
|
generator = rotation_scan_generator(is_alternating)
|
|
110
123
|
next(generator)
|
|
111
|
-
for location in
|
|
124
|
+
for location, sample_id in zip(
|
|
125
|
+
locations_to_collect_um, samples_to_collect, strict=True
|
|
126
|
+
):
|
|
112
127
|
for rot in rotation_template:
|
|
113
|
-
combination = generator.send((rot, location))
|
|
128
|
+
combination = generator.send((rot, location, sample_id))
|
|
114
129
|
multi_rotation.rotation_scans.append(combination)
|
|
115
130
|
multi_rotation = RotationScan.model_validate(multi_rotation)
|
|
116
131
|
|
|
@@ -125,8 +140,10 @@ def load_centre_collect_full(
|
|
|
125
140
|
|
|
126
141
|
def rotation_scan_generator(
|
|
127
142
|
is_alternating: bool,
|
|
128
|
-
) -> Generator[
|
|
129
|
-
|
|
143
|
+
) -> Generator[
|
|
144
|
+
RotationScanPerSweep, tuple[RotationScanPerSweep, np.ndarray, int], None
|
|
145
|
+
]:
|
|
146
|
+
scan_template, location, sample_id = yield # type: ignore
|
|
130
147
|
next_rotation_direction = scan_template.rotation_direction
|
|
131
148
|
while True:
|
|
132
149
|
scan = scan_template.model_copy()
|
|
@@ -135,6 +152,7 @@ def rotation_scan_generator(
|
|
|
135
152
|
scan.y_start_um,
|
|
136
153
|
scan.z_start_um,
|
|
137
154
|
) = location
|
|
155
|
+
scan.sample_id = sample_id
|
|
138
156
|
if is_alternating:
|
|
139
157
|
if next_rotation_direction != scan.rotation_direction:
|
|
140
158
|
# If originally specified direction of the current scan is different
|
|
@@ -146,4 +164,4 @@ def rotation_scan_generator(
|
|
|
146
164
|
scan.rotation_direction = next_rotation_direction
|
|
147
165
|
next_rotation_direction = next_rotation_direction.opposite
|
|
148
166
|
|
|
149
|
-
scan_template, location = yield scan
|
|
167
|
+
scan_template, location, sample_id = yield scan
|
|
@@ -10,22 +10,25 @@ import bluesky.preprocessors as bpp
|
|
|
10
10
|
import pydantic
|
|
11
11
|
from blueapi.core import BlueskyContext
|
|
12
12
|
from bluesky.utils import Msg
|
|
13
|
-
from dodal.devices.aperturescatterguard import ApertureScatterguard
|
|
13
|
+
from dodal.devices.aperturescatterguard import ApertureScatterguard
|
|
14
14
|
from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
|
|
15
15
|
from dodal.devices.backlight import Backlight, BacklightPosition
|
|
16
16
|
from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, MirrorVoltages
|
|
17
17
|
from dodal.devices.i03.dcm import DCM
|
|
18
18
|
from dodal.devices.i03.undulator_dcm import UndulatorDCM
|
|
19
|
-
from dodal.devices.motors import
|
|
19
|
+
from dodal.devices.motors import XYZStage
|
|
20
20
|
from dodal.devices.oav.oav_detector import OAV
|
|
21
21
|
from dodal.devices.robot import BartRobot, SampleLocation
|
|
22
|
-
from dodal.devices.smargon import
|
|
22
|
+
from dodal.devices.smargon import Smargon
|
|
23
23
|
from dodal.devices.thawer import Thawer
|
|
24
24
|
from dodal.devices.webcam import Webcam
|
|
25
25
|
from dodal.devices.xbpm_feedback import XBPMFeedback
|
|
26
|
-
from dodal.plan_stubs.motor_utils import MoveTooLarge, home_and_reset_wrapper
|
|
27
26
|
|
|
28
|
-
from mx_bluesky.common.
|
|
27
|
+
from mx_bluesky.common.device_setup_plans.robot_load_unload import (
|
|
28
|
+
do_plan_while_lower_gonio_at_home,
|
|
29
|
+
prepare_for_robot_load,
|
|
30
|
+
wait_for_smargon_not_disabled,
|
|
31
|
+
)
|
|
29
32
|
from mx_bluesky.hyperion.experiment_plans.set_energy_plan import (
|
|
30
33
|
SetEnergyComposite,
|
|
31
34
|
set_energy_plan,
|
|
@@ -47,7 +50,7 @@ class RobotLoadAndEnergyChangeComposite:
|
|
|
47
50
|
# RobotLoad fields
|
|
48
51
|
robot: BartRobot
|
|
49
52
|
webcam: Webcam
|
|
50
|
-
lower_gonio:
|
|
53
|
+
lower_gonio: XYZStage
|
|
51
54
|
thawer: Thawer
|
|
52
55
|
oav: OAV
|
|
53
56
|
smargon: Smargon
|
|
@@ -61,25 +64,6 @@ def create_devices(context: BlueskyContext) -> RobotLoadAndEnergyChangeComposite
|
|
|
61
64
|
return device_composite_from_context(context, RobotLoadAndEnergyChangeComposite)
|
|
62
65
|
|
|
63
66
|
|
|
64
|
-
def wait_for_smargon_not_disabled(smargon: Smargon, timeout=60):
|
|
65
|
-
"""Waits for the smargon disabled flag to go low. The robot hardware is responsible
|
|
66
|
-
for setting this to low when it is safe to move. It does this through a physical
|
|
67
|
-
connection between the robot and the smargon.
|
|
68
|
-
"""
|
|
69
|
-
LOGGER.info("Waiting for smargon enabled")
|
|
70
|
-
SLEEP_PER_CHECK = 0.1
|
|
71
|
-
times_to_check = int(timeout / SLEEP_PER_CHECK)
|
|
72
|
-
for _ in range(times_to_check):
|
|
73
|
-
smargon_disabled = yield from bps.rd(smargon.disabled)
|
|
74
|
-
if not smargon_disabled:
|
|
75
|
-
LOGGER.info("Smargon now enabled")
|
|
76
|
-
return
|
|
77
|
-
yield from bps.sleep(SLEEP_PER_CHECK)
|
|
78
|
-
raise TimeoutError(
|
|
79
|
-
"Timed out waiting for smargon to become enabled after robot load"
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
|
|
83
67
|
def take_robot_snapshots(oav: OAV, webcam: Webcam, directory: Path):
|
|
84
68
|
time_now = datetime.now()
|
|
85
69
|
snapshot_format = f"{time_now.strftime('%H%M%S')}_{{device}}_after_load"
|
|
@@ -93,28 +77,15 @@ def take_robot_snapshots(oav: OAV, webcam: Webcam, directory: Path):
|
|
|
93
77
|
yield from bps.wait("snapshots")
|
|
94
78
|
|
|
95
79
|
|
|
96
|
-
def prepare_for_robot_load(
|
|
97
|
-
aperture_scatterguard: ApertureScatterguard, smargon: Smargon
|
|
98
|
-
):
|
|
99
|
-
yield from bps.abs_set(
|
|
100
|
-
aperture_scatterguard.selected_aperture,
|
|
101
|
-
ApertureValue.OUT_OF_BEAM,
|
|
102
|
-
group="prepare_robot_load",
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
yield from bps.mv(smargon.stub_offsets, StubPosition.RESET_TO_ROBOT_LOAD)
|
|
106
|
-
|
|
107
|
-
yield from bps.mv(smargon, CombinedMove(x=0, y=0, z=0, chi=0, phi=0, omega=0))
|
|
108
|
-
|
|
109
|
-
yield from bps.wait("prepare_robot_load")
|
|
110
|
-
|
|
111
|
-
|
|
112
80
|
def do_robot_load(
|
|
113
81
|
composite: RobotLoadAndEnergyChangeComposite,
|
|
114
82
|
sample_location: SampleLocation,
|
|
83
|
+
sample_id: int,
|
|
115
84
|
demand_energy_ev: float | None,
|
|
116
85
|
thawing_time: float,
|
|
117
86
|
):
|
|
87
|
+
yield from bps.abs_set(composite.robot.next_sample_id, sample_id, wait=True)
|
|
88
|
+
|
|
118
89
|
yield from bps.abs_set(
|
|
119
90
|
composite.robot,
|
|
120
91
|
sample_location,
|
|
@@ -133,16 +104,6 @@ def do_robot_load(
|
|
|
133
104
|
yield from wait_for_smargon_not_disabled(composite.smargon)
|
|
134
105
|
|
|
135
106
|
|
|
136
|
-
def raise_exception_if_moved_out_of_cryojet(exception):
|
|
137
|
-
yield from bps.null()
|
|
138
|
-
if isinstance(exception, MoveTooLarge):
|
|
139
|
-
raise Exception(
|
|
140
|
-
f"Moving {exception.axis} back to {exception.position} after \
|
|
141
|
-
robot load would move it out of the cryojet. The max safe \
|
|
142
|
-
distance is {exception.maximum_move}"
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
|
|
146
107
|
def pin_already_loaded(
|
|
147
108
|
robot: BartRobot, sample_location: SampleLocation
|
|
148
109
|
) -> Generator[Msg, None, bool]:
|
|
@@ -158,6 +119,7 @@ def robot_load_and_snapshots(
|
|
|
158
119
|
composite: RobotLoadAndEnergyChangeComposite,
|
|
159
120
|
location: SampleLocation,
|
|
160
121
|
snapshot_directory: Path,
|
|
122
|
+
sample_id: int,
|
|
161
123
|
thawing_time: float,
|
|
162
124
|
demand_energy_ev: float | None,
|
|
163
125
|
):
|
|
@@ -166,37 +128,25 @@ def robot_load_and_snapshots(
|
|
|
166
128
|
robot_load_plan = do_robot_load(
|
|
167
129
|
composite,
|
|
168
130
|
location,
|
|
131
|
+
sample_id,
|
|
169
132
|
demand_energy_ev,
|
|
170
133
|
thawing_time,
|
|
171
134
|
)
|
|
172
135
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
# to the lower gonio and the move is quicker than the robot takes to get to the
|
|
176
|
-
# load position.
|
|
177
|
-
yield from bpp.contingency_wrapper(
|
|
178
|
-
home_and_reset_wrapper(
|
|
179
|
-
robot_load_plan,
|
|
180
|
-
composite.lower_gonio,
|
|
181
|
-
BartRobot.LOAD_TOLERANCE_MM,
|
|
182
|
-
CONST.HARDWARE.CRYOJET_MARGIN_MM,
|
|
183
|
-
"lower_gonio",
|
|
184
|
-
wait_for_all=False,
|
|
185
|
-
),
|
|
186
|
-
except_plan=raise_exception_if_moved_out_of_cryojet,
|
|
136
|
+
gonio_finished = yield from do_plan_while_lower_gonio_at_home(
|
|
137
|
+
robot_load_plan, composite.lower_gonio
|
|
187
138
|
)
|
|
188
|
-
|
|
189
139
|
yield from bps.wait(group="snapshot")
|
|
190
140
|
|
|
191
141
|
yield from take_robot_snapshots(composite.oav, composite.webcam, snapshot_directory)
|
|
192
142
|
|
|
193
|
-
yield from bps.create(name=CONST.DESCRIPTORS.
|
|
194
|
-
yield from bps.read(composite.robot
|
|
143
|
+
yield from bps.create(name=CONST.DESCRIPTORS.ROBOT_UPDATE)
|
|
144
|
+
yield from bps.read(composite.robot)
|
|
195
145
|
yield from bps.read(composite.oav.snapshot)
|
|
196
146
|
yield from bps.read(composite.webcam)
|
|
197
147
|
yield from bps.save()
|
|
198
148
|
|
|
199
|
-
yield from bps.wait(
|
|
149
|
+
yield from bps.wait(gonio_finished)
|
|
200
150
|
|
|
201
151
|
|
|
202
152
|
def robot_load_and_change_energy_plan(
|
|
@@ -218,17 +168,13 @@ def robot_load_and_change_energy_plan(
|
|
|
218
168
|
composite,
|
|
219
169
|
sample_location,
|
|
220
170
|
params.snapshot_directory,
|
|
171
|
+
params.sample_id,
|
|
221
172
|
params.thawing_time,
|
|
222
173
|
params.demand_energy_ev,
|
|
223
174
|
),
|
|
224
175
|
md={
|
|
225
176
|
"subplan_name": CONST.PLAN.ROBOT_LOAD,
|
|
226
|
-
"metadata": {
|
|
227
|
-
"visit": params.visit,
|
|
228
|
-
"sample_id": params.sample_id,
|
|
229
|
-
"sample_puck": sample_location.puck,
|
|
230
|
-
"sample_pin": sample_location.pin,
|
|
231
|
-
},
|
|
177
|
+
"metadata": {"visit": params.visit, "sample_id": params.sample_id},
|
|
232
178
|
"activate_callbacks": [
|
|
233
179
|
"RobotLoadISPyBCallback",
|
|
234
180
|
],
|
|
@@ -18,7 +18,7 @@ from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, MirrorVolta
|
|
|
18
18
|
from dodal.devices.i03 import Beamstop
|
|
19
19
|
from dodal.devices.i03.dcm import DCM
|
|
20
20
|
from dodal.devices.i03.undulator_dcm import UndulatorDCM
|
|
21
|
-
from dodal.devices.motors import
|
|
21
|
+
from dodal.devices.motors import XYZStage
|
|
22
22
|
from dodal.devices.oav.oav_detector import OAV
|
|
23
23
|
from dodal.devices.oav.pin_image_recognition import PinTipDetection
|
|
24
24
|
from dodal.devices.robot import BartRobot, SampleLocation
|
|
@@ -96,7 +96,7 @@ class RobotLoadThenCentreComposite:
|
|
|
96
96
|
# RobotLoad fields
|
|
97
97
|
robot: BartRobot
|
|
98
98
|
webcam: Webcam
|
|
99
|
-
lower_gonio:
|
|
99
|
+
lower_gonio: XYZStage
|
|
100
100
|
beamstop: Beamstop
|
|
101
101
|
|
|
102
102
|
|
|
@@ -342,10 +342,15 @@ def _move_and_rotation(
|
|
|
342
342
|
|
|
343
343
|
if params.take_snapshots:
|
|
344
344
|
yield from bps.wait(CONST.WAIT.MOVE_GONIO_TO_START)
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
345
|
+
|
|
346
|
+
if not params.use_grid_snapshots:
|
|
347
|
+
yield from setup_beamline_for_OAV(
|
|
348
|
+
composite.smargon,
|
|
349
|
+
composite.backlight,
|
|
350
|
+
composite.aperture_scatterguard,
|
|
351
|
+
wait=True,
|
|
352
|
+
)
|
|
353
|
+
|
|
349
354
|
if params.selected_aperture:
|
|
350
355
|
yield from bps.prepare(
|
|
351
356
|
composite.aperture_scatterguard,
|
|
@@ -148,6 +148,7 @@ def populate_parameters_from_agamemnon(agamemnon_params) -> Sequence[LoadCentreC
|
|
|
148
148
|
"name": "TopNByMaxCount",
|
|
149
149
|
"n": pin_type.expected_number_of_crystals,
|
|
150
150
|
},
|
|
151
|
+
"features": {"use_gpu_results": True},
|
|
151
152
|
"robot_load_then_centre": {
|
|
152
153
|
"storage_directory": str(visit_directory) + "/xraycentring",
|
|
153
154
|
"file_name": file_name,
|
|
@@ -156,7 +157,6 @@ def populate_parameters_from_agamemnon(agamemnon_params) -> Sequence[LoadCentreC
|
|
|
156
157
|
"omega_start_deg": 0.0,
|
|
157
158
|
"chi_start_deg": collection["chi"],
|
|
158
159
|
"transmission_frac": 1.0,
|
|
159
|
-
"features": {"use_gpu_results": True},
|
|
160
160
|
**with_energy_params,
|
|
161
161
|
},
|
|
162
162
|
"multi_rotation_scan": {
|
|
@@ -228,10 +228,10 @@ def update_params_from_agamemnon(parameters: T) -> T:
|
|
|
228
228
|
parameters.robot_load_then_centre.grid_width_um = pin_type.full_width
|
|
229
229
|
parameters.select_centres.n = pin_type.expected_number_of_crystals
|
|
230
230
|
if pin_type != SinglePin():
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
# this will give no snapshots but that's preferable
|
|
231
|
+
# Rotation snapshots will be generated from the gridscan snapshots,
|
|
232
|
+
# no need to specify snapshot omega.
|
|
234
233
|
parameters.multi_rotation_scan.snapshot_omegas_deg = []
|
|
234
|
+
parameters.multi_rotation_scan.use_grid_snapshots = True
|
|
235
235
|
except (ValueError, ValidationError) as e:
|
|
236
236
|
LOGGER.warning(f"Failed to update parameters: {e}")
|
|
237
237
|
except Exception as e:
|
|
@@ -29,7 +29,7 @@ from mx_bluesky.common.utils.log import (
|
|
|
29
29
|
_get_logging_dirs,
|
|
30
30
|
tag_filter,
|
|
31
31
|
)
|
|
32
|
-
from mx_bluesky.hyperion.external_interaction.callbacks.
|
|
32
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.robot_actions.ispyb_callback import (
|
|
33
33
|
RobotLoadISPyBCallback,
|
|
34
34
|
)
|
|
35
35
|
from mx_bluesky.hyperion.external_interaction.callbacks.rotation.ispyb_callback import (
|
mx_bluesky/hyperion/external_interaction/callbacks/{robot_load → robot_actions}/ispyb_callback.py
RENAMED
|
@@ -12,6 +12,7 @@ from mx_bluesky.common.external_interaction.ispyb.exp_eye_store import (
|
|
|
12
12
|
BLSampleStatus,
|
|
13
13
|
ExpeyeInteraction,
|
|
14
14
|
RobotActionID,
|
|
15
|
+
create_update_data_from_event_doc,
|
|
15
16
|
)
|
|
16
17
|
from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER
|
|
17
18
|
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
@@ -20,11 +21,21 @@ if TYPE_CHECKING:
|
|
|
20
21
|
from event_model.documents import Event, EventDescriptor, RunStart, RunStop
|
|
21
22
|
|
|
22
23
|
|
|
24
|
+
robot_update_mapping = {
|
|
25
|
+
"robot-barcode": "sampleBarcode",
|
|
26
|
+
"robot-current_pin": "containerLocation",
|
|
27
|
+
"robot-current_puck": "dewarLocation",
|
|
28
|
+
# I03 uses webcam/oav snapshots in place of before/after snapshots
|
|
29
|
+
"webcam-last_saved_path": "xtalSnapshotBefore",
|
|
30
|
+
"oav-snapshot-last_saved_path": "xtalSnapshotAfter",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
23
34
|
class RobotLoadISPyBCallback(PlanReactiveCallback):
|
|
24
35
|
def __init__(self) -> None:
|
|
25
36
|
ISPYB_ZOCALO_CALLBACK_LOGGER.debug("Initialising ISPyB Robot Load Callback")
|
|
26
37
|
super().__init__(log=ISPYB_ZOCALO_CALLBACK_LOGGER)
|
|
27
|
-
self.
|
|
38
|
+
self._sample_id: int | None = None
|
|
28
39
|
|
|
29
40
|
self.run_uid: str | None = None
|
|
30
41
|
self.descriptors: dict[str, EventDescriptor] = {}
|
|
@@ -35,22 +46,24 @@ class RobotLoadISPyBCallback(PlanReactiveCallback):
|
|
|
35
46
|
ISPYB_ZOCALO_CALLBACK_LOGGER.debug(
|
|
36
47
|
"ISPyB robot load callback received start document."
|
|
37
48
|
)
|
|
38
|
-
|
|
49
|
+
subplan = doc.get("subplan_name")
|
|
50
|
+
if subplan == CONST.PLAN.ROBOT_LOAD or subplan == CONST.PLAN.ROBOT_UNLOAD:
|
|
39
51
|
ISPYB_ZOCALO_CALLBACK_LOGGER.debug(
|
|
40
52
|
f"ISPyB robot load callback received: {doc}"
|
|
41
53
|
)
|
|
42
54
|
self.run_uid = doc.get("uid")
|
|
43
|
-
|
|
44
|
-
assert isinstance(
|
|
55
|
+
metadata = doc.get("metadata")
|
|
56
|
+
assert isinstance(metadata, dict)
|
|
57
|
+
self._sample_id = metadata["sample_id"]
|
|
58
|
+
assert isinstance(self._sample_id, int)
|
|
45
59
|
proposal, session = get_proposal_and_session_from_visit_string(
|
|
46
|
-
|
|
60
|
+
metadata["visit"]
|
|
47
61
|
)
|
|
48
|
-
self.action_id = self.expeye.
|
|
62
|
+
self.action_id = self.expeye.start_robot_action(
|
|
63
|
+
"LOAD" if subplan == CONST.PLAN.ROBOT_LOAD else "UNLOAD",
|
|
49
64
|
proposal,
|
|
50
65
|
session,
|
|
51
|
-
self.
|
|
52
|
-
self._metadata["sample_puck"],
|
|
53
|
-
self._metadata["sample_pin"],
|
|
66
|
+
self._sample_id,
|
|
54
67
|
)
|
|
55
68
|
return super().activity_gated_start(doc)
|
|
56
69
|
|
|
@@ -62,18 +75,14 @@ class RobotLoadISPyBCallback(PlanReactiveCallback):
|
|
|
62
75
|
event_descriptor = self.descriptors.get(doc["descriptor"])
|
|
63
76
|
if (
|
|
64
77
|
event_descriptor
|
|
65
|
-
and event_descriptor.get("name") == CONST.DESCRIPTORS.
|
|
78
|
+
and event_descriptor.get("name") == CONST.DESCRIPTORS.ROBOT_UPDATE
|
|
66
79
|
):
|
|
67
80
|
assert self.action_id is not None, (
|
|
68
81
|
"ISPyB Robot load callback event called unexpectedly"
|
|
69
82
|
)
|
|
70
|
-
barcode = doc["data"]["robot-barcode"]
|
|
71
|
-
oav_snapshot = doc["data"]["oav-snapshot-last_saved_path"]
|
|
72
|
-
webcam_snapshot = doc["data"]["webcam-last_saved_path"]
|
|
73
83
|
# I03 uses webcam/oav snapshots in place of before/after snapshots
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
)
|
|
84
|
+
update_data = create_update_data_from_event_doc(robot_update_mapping, doc)
|
|
85
|
+
self.expeye.update_robot_action(self.action_id, update_data)
|
|
77
86
|
|
|
78
87
|
return super().activity_gated_event(doc)
|
|
79
88
|
|
|
@@ -87,12 +96,12 @@ class RobotLoadISPyBCallback(PlanReactiveCallback):
|
|
|
87
96
|
)
|
|
88
97
|
exit_status = doc.get("exit_status")
|
|
89
98
|
assert exit_status, "Exit status not available in stop document!"
|
|
90
|
-
assert self.
|
|
99
|
+
assert self._sample_id is not None, "Stop called before start"
|
|
91
100
|
reason = doc.get("reason") or "OK"
|
|
92
101
|
|
|
93
|
-
self.expeye.
|
|
102
|
+
self.expeye.end_robot_action(self.action_id, exit_status, reason)
|
|
94
103
|
self.expeye.update_sample_status(
|
|
95
|
-
self.
|
|
104
|
+
self._sample_id,
|
|
96
105
|
BLSampleStatus.LOADED
|
|
97
106
|
if exit_status == "success"
|
|
98
107
|
else BLSampleStatus.ERROR_BEAMLINE,
|
|
@@ -54,6 +54,7 @@ class RotationISPyBCallback(BaseISPyBCallback):
|
|
|
54
54
|
super().__init__(emit=emit)
|
|
55
55
|
self.last_sample_id: int | None = None
|
|
56
56
|
self.ispyb_ids: IspybIds = IspybIds()
|
|
57
|
+
self.ispyb = StoreInIspyb(self.ispyb_config)
|
|
57
58
|
|
|
58
59
|
def activity_gated_start(self, doc: RunStart):
|
|
59
60
|
if doc.get("subplan_name") == CONST.PLAN.ROTATION_OUTER:
|
|
@@ -82,7 +83,6 @@ class RotationISPyBCallback(BaseISPyBCallback):
|
|
|
82
83
|
f"Collection is {self.params.ispyb_experiment_type} - storing sampleID to bundle images"
|
|
83
84
|
)
|
|
84
85
|
self.last_sample_id = self.params.sample_id
|
|
85
|
-
self.ispyb = StoreInIspyb(self.ispyb_config)
|
|
86
86
|
ISPYB_ZOCALO_CALLBACK_LOGGER.info("Beginning ispyb deposition")
|
|
87
87
|
data_collection_group_info = populate_data_collection_group(self.params)
|
|
88
88
|
data_collection_info = populate_data_collection_info_for_rotation(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import dataclasses
|
|
2
|
+
import os
|
|
2
3
|
import re
|
|
3
4
|
from collections.abc import Iterator
|
|
4
5
|
from datetime import datetime
|
|
@@ -172,6 +173,8 @@ class BeamDrawingCallback(PlanReactiveCallback):
|
|
|
172
173
|
f"Generating snapshot at {current_sample_pos_mm} from base snapshot {snapshot_info}"
|
|
173
174
|
)
|
|
174
175
|
output_snapshot_directory = data["oav-snapshot-directory"]
|
|
176
|
+
if not os.path.exists(output_snapshot_directory):
|
|
177
|
+
os.mkdir(output_snapshot_directory)
|
|
175
178
|
base_file_stem = Path(snapshot_info.snapshot_path).stem
|
|
176
179
|
output_snapshot_filename = _snapshot_filename(base_file_stem)
|
|
177
180
|
output_snapshot_path = (
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from functools import cache
|
|
2
2
|
|
|
3
3
|
from daq_config_server.client import ConfigServer
|
|
4
|
-
from pydantic import model_validator
|
|
5
4
|
|
|
6
5
|
from mx_bluesky.common.external_interaction.config_server import FeatureFlags
|
|
7
6
|
from mx_bluesky.common.utils.log import LOGGER
|
|
@@ -14,8 +13,6 @@ class HyperionFeatureFlags(FeatureFlags):
|
|
|
14
13
|
|
|
15
14
|
Attributes:
|
|
16
15
|
use_panda_for_gridscan: If True then the PandA is used for gridscans, otherwise the zebra is used
|
|
17
|
-
compare_cpu_and_gpu_zocalo: If True then GPU result processing is enabled
|
|
18
|
-
alongside CPU and the results are compared. The CPU result is still take.n
|
|
19
16
|
use_gpu_results: If True then GPU result processing is enabled
|
|
20
17
|
and the GPU result is taken.
|
|
21
18
|
set_stub_offsets: If True then set the stub offsets after moving to the crystal (ignored for
|
|
@@ -31,15 +28,7 @@ class HyperionFeatureFlags(FeatureFlags):
|
|
|
31
28
|
def get_config_server() -> ConfigServer:
|
|
32
29
|
return ConfigServer(CONST.CONFIG_SERVER_URL, LOGGER)
|
|
33
30
|
|
|
34
|
-
@model_validator(mode="after")
|
|
35
|
-
def use_gpu_and_compare_cannot_both_be_true(self):
|
|
36
|
-
assert not (self.use_gpu_results and self.compare_cpu_and_gpu_zocalo), (
|
|
37
|
-
"Cannot both use GPU results and compare them to CPU"
|
|
38
|
-
)
|
|
39
|
-
return self
|
|
40
|
-
|
|
41
31
|
use_panda_for_gridscan: bool = CONST.I03.USE_PANDA_FOR_GRIDSCAN
|
|
42
|
-
compare_cpu_and_gpu_zocalo: bool = CONST.I03.COMPARE_CPU_AND_GPU_ZOCALO
|
|
43
32
|
use_gpu_results: bool = CONST.I03.USE_GPU_RESULTS
|
|
44
33
|
set_stub_offsets: bool = CONST.I03.SET_STUB_OFFSETS
|
|
45
34
|
omega_flip: bool = CONST.I03.OMEGA_FLIP
|
|
@@ -29,12 +29,8 @@ class I03Constants:
|
|
|
29
29
|
OMEGA_FLIP = True
|
|
30
30
|
ALTERNATE_ROTATION_DIRECTION = True
|
|
31
31
|
|
|
32
|
-
# Turns on GPU processing for zocalo and logs a comparison between GPU and CPU-
|
|
33
|
-
# processed results.
|
|
34
|
-
COMPARE_CPU_AND_GPU_ZOCALO = False
|
|
35
|
-
|
|
36
32
|
# Turns on GPU processing for zocalo and uses the results that come back
|
|
37
|
-
USE_GPU_RESULTS =
|
|
33
|
+
USE_GPU_RESULTS = True
|
|
38
34
|
|
|
39
35
|
|
|
40
36
|
@dataclass(frozen=True)
|
|
@@ -20,9 +20,7 @@ class GridCommonWithHyperionDetectorParams(GridCommon, WithHyperionUDCFeatures):
|
|
|
20
20
|
@property
|
|
21
21
|
def detector_params(self):
|
|
22
22
|
params = super().detector_params
|
|
23
|
-
params.enable_dev_shm =
|
|
24
|
-
self.features.compare_cpu_and_gpu_zocalo or self.features.use_gpu_results
|
|
25
|
-
)
|
|
23
|
+
params.enable_dev_shm = self.features.use_gpu_results
|
|
26
24
|
return params
|
|
27
25
|
|
|
28
26
|
|
|
@@ -35,9 +33,7 @@ class HyperionSpecifiedThreeDGridScan(WithHyperionUDCFeatures, SpecifiedThreeDGr
|
|
|
35
33
|
@property
|
|
36
34
|
def detector_params(self):
|
|
37
35
|
params = super().detector_params
|
|
38
|
-
params.enable_dev_shm =
|
|
39
|
-
self.features.compare_cpu_and_gpu_zocalo or self.features.use_gpu_results
|
|
40
|
-
)
|
|
36
|
+
params.enable_dev_shm = self.features.use_gpu_results
|
|
41
37
|
return params
|
|
42
38
|
|
|
43
39
|
# Relative to common grid scan, stub offsets are defined by config server
|
|
@@ -50,6 +50,12 @@ class LoadCentreCollect(
|
|
|
50
50
|
f"Unexpected fields found in LoadCentreCollect {disallowed_keys}"
|
|
51
51
|
)
|
|
52
52
|
|
|
53
|
+
assert "features" not in values["robot_load_then_centre"], (
|
|
54
|
+
"Features flags must be specified at top-level in LoadCentreCollect"
|
|
55
|
+
)
|
|
56
|
+
assert "features" not in values["multi_rotation_scan"], (
|
|
57
|
+
"Features flags must be specified at top-level in LoadCentreCollect"
|
|
58
|
+
)
|
|
53
59
|
keys_from_outer_load_centre_collect = (
|
|
54
60
|
MxBlueskyParameters.model_fields.keys()
|
|
55
61
|
| WithSample.model_fields.keys()
|
|
@@ -70,6 +76,9 @@ class LoadCentreCollect(
|
|
|
70
76
|
f"Unexpected keys in multi_rotation_scan: {', '.join(duplicated_multi_rotation_scan_keys)}"
|
|
71
77
|
)
|
|
72
78
|
|
|
79
|
+
for rotation in values["multi_rotation_scan"]["rotation_scans"]:
|
|
80
|
+
rotation["sample_id"] = values["sample_id"]
|
|
81
|
+
|
|
73
82
|
new_robot_load_then_centre_params = construct_from_values(
|
|
74
83
|
values, values["robot_load_then_centre"], RobotLoadThenCentre
|
|
75
84
|
)
|
|
@@ -80,6 +89,12 @@ class LoadCentreCollect(
|
|
|
80
89
|
values["robot_load_then_centre"] = new_robot_load_then_centre_params
|
|
81
90
|
return values
|
|
82
91
|
|
|
92
|
+
@model_validator(mode="after")
|
|
93
|
+
def _ensure_features_are_internally_consistent(self) -> Self:
|
|
94
|
+
self.robot_load_then_centre.features = self.features
|
|
95
|
+
self.multi_rotation_scan.features = self.features
|
|
96
|
+
return self
|
|
97
|
+
|
|
83
98
|
@model_validator(mode="after")
|
|
84
99
|
def _check_rotation_start_xyz_is_not_specified(self) -> Self:
|
|
85
100
|
for scan in self.multi_rotation_scan.single_rotation_scans:
|
|
@@ -18,12 +18,14 @@ from scanspec.core import Path as ScanPath
|
|
|
18
18
|
from scanspec.specs import Line
|
|
19
19
|
|
|
20
20
|
from mx_bluesky.common.parameters.components import (
|
|
21
|
+
DiffractionExperiment,
|
|
21
22
|
DiffractionExperimentWithSample,
|
|
22
23
|
IspybExperimentType,
|
|
23
24
|
OptionalGonioAngleStarts,
|
|
24
25
|
OptionalXyzStarts,
|
|
25
26
|
RotationAxis,
|
|
26
27
|
SplitScan,
|
|
28
|
+
WithSample,
|
|
27
29
|
WithScan,
|
|
28
30
|
)
|
|
29
31
|
from mx_bluesky.hyperion.parameters.components import WithHyperionUDCFeatures
|
|
@@ -33,7 +35,7 @@ from mx_bluesky.hyperion.parameters.constants import (
|
|
|
33
35
|
)
|
|
34
36
|
|
|
35
37
|
|
|
36
|
-
class RotationScanPerSweep(OptionalGonioAngleStarts, OptionalXyzStarts):
|
|
38
|
+
class RotationScanPerSweep(OptionalGonioAngleStarts, OptionalXyzStarts, WithSample):
|
|
37
39
|
"""
|
|
38
40
|
Describes a rotation scan about the specified axis.
|
|
39
41
|
|
|
@@ -54,7 +56,7 @@ class RotationScanPerSweep(OptionalGonioAngleStarts, OptionalXyzStarts):
|
|
|
54
56
|
nexus_vds_start_img: int = Field(default=0, ge=0)
|
|
55
57
|
|
|
56
58
|
|
|
57
|
-
class RotationExperiment(
|
|
59
|
+
class RotationExperiment(DiffractionExperiment, WithHyperionUDCFeatures):
|
|
58
60
|
shutter_opening_time_s: float = Field(default=CONST.I03.SHUTTER_TIME_S)
|
|
59
61
|
rotation_increment_deg: float = Field(default=0.1, gt=0)
|
|
60
62
|
ispyb_experiment_type: IspybExperimentType = Field(
|
|
@@ -105,7 +107,9 @@ class RotationExperiment(DiffractionExperimentWithSample, WithHyperionUDCFeature
|
|
|
105
107
|
return aperture_position
|
|
106
108
|
|
|
107
109
|
|
|
108
|
-
class SingleRotationScan(
|
|
110
|
+
class SingleRotationScan(
|
|
111
|
+
WithScan, RotationScanPerSweep, RotationExperiment, DiffractionExperimentWithSample
|
|
112
|
+
):
|
|
109
113
|
@property
|
|
110
114
|
def detector_params(self):
|
|
111
115
|
return self._detector_params(self.omega_start_deg)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mx-bluesky
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.1
|
|
4
4
|
Summary: Bluesky tools for MX Beamlines at DLS
|
|
5
5
|
Author-email: Dominic Oram <dominic.oram@diamond.ac.uk>
|
|
6
6
|
License: Apache License
|
|
@@ -221,7 +221,7 @@ Requires-Dist: ispyb
|
|
|
221
221
|
Requires-Dist: jupyterlab
|
|
222
222
|
Requires-Dist: matplotlib
|
|
223
223
|
Requires-Dist: nexgen>=0.11.0
|
|
224
|
-
Requires-Dist: numpy
|
|
224
|
+
Requires-Dist: numpy==2.2.6
|
|
225
225
|
Requires-Dist: opencv-python
|
|
226
226
|
Requires-Dist: opentelemetry-distro
|
|
227
227
|
Requires-Dist: opentelemetry-exporter-otlp
|
|
@@ -233,13 +233,14 @@ Requires-Dist: requests
|
|
|
233
233
|
Requires-Dist: scanspec
|
|
234
234
|
Requires-Dist: scipy
|
|
235
235
|
Requires-Dist: semver
|
|
236
|
+
Requires-Dist: deepdiff
|
|
236
237
|
Requires-Dist: matplotlib
|
|
237
238
|
Requires-Dist: blueapi>=0.11.1
|
|
238
239
|
Requires-Dist: daq-config-server==0.1.1
|
|
239
240
|
Requires-Dist: ophyd>=1.10.5
|
|
240
241
|
Requires-Dist: ophyd-async>=0.10.0a2
|
|
241
242
|
Requires-Dist: bluesky>=1.13.1
|
|
242
|
-
Requires-Dist: dls-dodal==1.
|
|
243
|
+
Requires-Dist: dls-dodal==1.51.0
|
|
243
244
|
Provides-Extra: dev
|
|
244
245
|
Requires-Dist: black; extra == "dev"
|
|
245
246
|
Requires-Dist: build; extra == "dev"
|