mx-bluesky 1.5.0__py3-none-any.whl → 1.5.2__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/__init__.py +4 -1
- mx_bluesky/beamlines/i04/callbacks/murko_callback.py +56 -1
- mx_bluesky/beamlines/i04/experiment_plans/__init__.py +0 -0
- mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +259 -0
- 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 +8 -6
- 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/change_aperture_then_move_plan.py +5 -1
- mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +27 -3
- mx_bluesky/common/experiment_plans/common_grid_detect_then_xray_centre_plan.py +1 -0
- mx_bluesky/common/experiment_plans/inner_plans/do_fgs.py +3 -1
- mx_bluesky/common/external_interaction/ispyb/exp_eye_store.py +26 -24
- mx_bluesky/common/external_interaction/ispyb/ispyb_store.py +11 -7
- mx_bluesky/common/external_interaction/nexus/write_nexus.py +2 -2
- mx_bluesky/common/parameters/__init__.py +0 -0
- mx_bluesky/common/parameters/components.py +7 -2
- mx_bluesky/common/parameters/constants.py +5 -3
- mx_bluesky/common/parameters/device_composites.py +1 -1
- mx_bluesky/common/parameters/gridscan.py +1 -0
- mx_bluesky/common/xrc_result.py +25 -2
- mx_bluesky/hyperion/__main__.py +1 -1
- mx_bluesky/hyperion/baton_handler.py +36 -4
- mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +4 -93
- mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +19 -31
- 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 +14 -9
- 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 +2 -7
- 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/hyperion/utils/context.py +19 -5
- mx_bluesky/phase1_zebra/__init__.py +1 -0
- mx_bluesky/phase1_zebra/device_setup_plans/__init__.py +0 -0
- mx_bluesky/phase1_zebra/device_setup_plans/setup_zebra.py +112 -0
- {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.2.dist-info}/METADATA +5 -4
- {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.2.dist-info}/RECORD +55 -49
- mx_bluesky/hyperion/utils/validation.py +0 -196
- /mx_bluesky/common/experiment_plans/{read_hardware.py → inner_plans/read_hardware.py} +0 -0
- /mx_bluesky/common/experiment_plans/{write_sample_status.py → inner_plans/write_sample_status.py} +0 -0
- {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.2.dist-info}/WHEEL +0 -0
- {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.2.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.2.dist-info}/licenses/LICENSE +0 -0
- {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import bluesky.plan_stubs as bps
|
|
4
|
+
import bluesky.preprocessors as bpp
|
|
5
|
+
from bluesky.utils import MsgGenerator
|
|
6
|
+
from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue
|
|
7
|
+
from dodal.devices.motors import XYZStage
|
|
8
|
+
from dodal.devices.robot import BartRobot
|
|
9
|
+
from dodal.devices.smargon import CombinedMove, Smargon, StubPosition
|
|
10
|
+
from dodal.plan_stubs.motor_utils import MoveTooLarge, home_and_reset_wrapper
|
|
11
|
+
|
|
12
|
+
from mx_bluesky.common.utils.log import LOGGER
|
|
13
|
+
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def wait_for_smargon_not_disabled(smargon: Smargon, timeout=60):
|
|
17
|
+
"""Waits for the smargon disabled flag to go low. The robot hardware is responsible
|
|
18
|
+
for setting this to low when it is safe to move. It does this through a physical
|
|
19
|
+
connection between the robot and the smargon.
|
|
20
|
+
"""
|
|
21
|
+
LOGGER.info("Waiting for smargon enabled")
|
|
22
|
+
SLEEP_PER_CHECK = 0.1
|
|
23
|
+
times_to_check = int(timeout / SLEEP_PER_CHECK)
|
|
24
|
+
for _ in range(times_to_check):
|
|
25
|
+
smargon_disabled = yield from bps.rd(smargon.disabled)
|
|
26
|
+
if not smargon_disabled:
|
|
27
|
+
LOGGER.info("Smargon now enabled")
|
|
28
|
+
return
|
|
29
|
+
yield from bps.sleep(SLEEP_PER_CHECK)
|
|
30
|
+
raise TimeoutError(
|
|
31
|
+
"Timed out waiting for smargon to become enabled after robot load"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _raise_exception_if_moved_out_of_cryojet(exception):
|
|
36
|
+
yield from bps.null()
|
|
37
|
+
if isinstance(exception, MoveTooLarge):
|
|
38
|
+
raise Exception(
|
|
39
|
+
f"Moving {exception.axis} back to {exception.position} after \
|
|
40
|
+
robot load would move it out of the cryojet. The max safe \
|
|
41
|
+
distance is {exception.maximum_move}"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def do_plan_while_lower_gonio_at_home(plan: MsgGenerator, lower_gonio: XYZStage):
|
|
46
|
+
"""Moves the lower gonio to home then performs the provided plan and moves it back.
|
|
47
|
+
|
|
48
|
+
The lower gonio must be in the correct position for the robot load and we
|
|
49
|
+
want to put it back afterwards. Note we don't need to wait for the move as the robot
|
|
50
|
+
is interlocked to the lower gonio and the move is quicker than the robot takes to
|
|
51
|
+
get to the load position.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
plan (MsgGenerator): The plan to run while the lower gonio is at home.
|
|
55
|
+
lower_gonio (XYZStage): The lower gonio to home.
|
|
56
|
+
"""
|
|
57
|
+
yield from bpp.contingency_wrapper(
|
|
58
|
+
home_and_reset_wrapper(
|
|
59
|
+
plan,
|
|
60
|
+
lower_gonio,
|
|
61
|
+
BartRobot.LOAD_TOLERANCE_MM,
|
|
62
|
+
CONST.HARDWARE.CRYOJET_MARGIN_MM,
|
|
63
|
+
"lower_gonio",
|
|
64
|
+
wait_for_all=False,
|
|
65
|
+
),
|
|
66
|
+
except_plan=_raise_exception_if_moved_out_of_cryojet,
|
|
67
|
+
)
|
|
68
|
+
return "reset-lower_gonio"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def prepare_for_robot_load(
|
|
72
|
+
aperture_scatterguard: ApertureScatterguard, smargon: Smargon
|
|
73
|
+
):
|
|
74
|
+
yield from bps.abs_set(
|
|
75
|
+
aperture_scatterguard.selected_aperture,
|
|
76
|
+
ApertureValue.OUT_OF_BEAM,
|
|
77
|
+
group="prepare_robot_load",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
yield from bps.mv(smargon.stub_offsets, StubPosition.RESET_TO_ROBOT_LOAD)
|
|
81
|
+
|
|
82
|
+
yield from bps.mv(smargon, CombinedMove(x=0, y=0, z=0, chi=0, phi=0, omega=0))
|
|
83
|
+
|
|
84
|
+
yield from bps.wait("prepare_robot_load")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def robot_unload(
|
|
88
|
+
robot: BartRobot,
|
|
89
|
+
smargon: Smargon,
|
|
90
|
+
aperture_scatterguard: ApertureScatterguard,
|
|
91
|
+
lower_gonio: XYZStage,
|
|
92
|
+
visit: str,
|
|
93
|
+
):
|
|
94
|
+
"""Unloads the currently mounted pin into the location that it was loaded from. The
|
|
95
|
+
loaded location is stored on the robot and so need not be provided.
|
|
96
|
+
"""
|
|
97
|
+
yield from prepare_for_robot_load(aperture_scatterguard, smargon)
|
|
98
|
+
sample_id = yield from bps.rd(robot.sample_id)
|
|
99
|
+
|
|
100
|
+
@bpp.run_decorator(
|
|
101
|
+
md={
|
|
102
|
+
"subplan_name": CONST.PLAN.ROBOT_UNLOAD,
|
|
103
|
+
"metadata": {"visit": visit, "sample_id": sample_id},
|
|
104
|
+
"activate_callbacks": [
|
|
105
|
+
"RobotLoadISPyBCallback",
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
def do_robot_unload_and_send_to_ispyb():
|
|
110
|
+
yield from bps.create(name=CONST.DESCRIPTORS.ROBOT_UPDATE)
|
|
111
|
+
yield from bps.read(robot)
|
|
112
|
+
yield from bps.save()
|
|
113
|
+
|
|
114
|
+
def _unload():
|
|
115
|
+
yield from bps.trigger(robot.unload, wait=True)
|
|
116
|
+
yield from wait_for_smargon_not_disabled(smargon)
|
|
117
|
+
|
|
118
|
+
gonio_finished = yield from do_plan_while_lower_gonio_at_home(
|
|
119
|
+
_unload(), lower_gonio
|
|
120
|
+
)
|
|
121
|
+
yield from bps.wait(gonio_finished)
|
|
122
|
+
|
|
123
|
+
yield from do_robot_unload_and_send_to_ispyb()
|
|
@@ -4,6 +4,7 @@ from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureVal
|
|
|
4
4
|
from dodal.devices.smargon import Smargon, StubPosition
|
|
5
5
|
|
|
6
6
|
from mx_bluesky.common.device_setup_plans.manipulate_sample import move_x_y_z
|
|
7
|
+
from mx_bluesky.common.parameters.constants import PlanGroupCheckpointConstants
|
|
7
8
|
from mx_bluesky.common.utils.log import LOGGER
|
|
8
9
|
from mx_bluesky.common.utils.tracing import TRACER
|
|
9
10
|
from mx_bluesky.common.xrc_result import XRayCentreResult
|
|
@@ -43,6 +44,7 @@ def change_aperture_then_move_to_xtal(
|
|
|
43
44
|
def set_aperture_for_bbox_mm(
|
|
44
45
|
aperture_device: ApertureScatterguard,
|
|
45
46
|
bbox_size_mm: list[float] | numpy.ndarray,
|
|
47
|
+
group=PlanGroupCheckpointConstants.GRID_READY_FOR_DC,
|
|
46
48
|
):
|
|
47
49
|
"""Sets aperture size based on bbox_size.
|
|
48
50
|
|
|
@@ -71,4 +73,6 @@ def set_aperture_for_bbox_mm(
|
|
|
71
73
|
f"Setting aperture to {new_selected_aperture} based on bounding box size {bbox_size_mm}."
|
|
72
74
|
)
|
|
73
75
|
|
|
74
|
-
yield from bps.abs_set(
|
|
76
|
+
yield from bps.abs_set(
|
|
77
|
+
aperture_device.selected_aperture, new_selected_aperture, group=group
|
|
78
|
+
)
|
|
@@ -22,7 +22,7 @@ from mx_bluesky.common.experiment_plans.inner_plans.do_fgs import (
|
|
|
22
22
|
ZOCALO_STAGE_GROUP,
|
|
23
23
|
kickoff_and_complete_gridscan,
|
|
24
24
|
)
|
|
25
|
-
from mx_bluesky.common.experiment_plans.read_hardware import (
|
|
25
|
+
from mx_bluesky.common.experiment_plans.inner_plans.read_hardware import (
|
|
26
26
|
read_hardware_plan,
|
|
27
27
|
)
|
|
28
28
|
from mx_bluesky.common.parameters.constants import (
|
|
@@ -55,6 +55,25 @@ class BeamlineSpecificFGSFeatures:
|
|
|
55
55
|
get_xrc_results_from_zocalo: bool
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
def generic_tidy(xrc_composite: FlyScanEssentialDevices, wait=True) -> MsgGenerator:
|
|
59
|
+
"""Tidy Zocalo and turn off Eiger dev/shm. Ran after the beamline-specific tidy plan"""
|
|
60
|
+
|
|
61
|
+
LOGGER.info("Tidying up Zocalo")
|
|
62
|
+
group = "generic_tidy"
|
|
63
|
+
# make sure we don't consume any other results
|
|
64
|
+
yield from bps.unstage(xrc_composite.zocalo, group=group)
|
|
65
|
+
|
|
66
|
+
# Turn off dev/shm streaming to avoid filling disk, see https://github.com/DiamondLightSource/hyperion/issues/1395
|
|
67
|
+
LOGGER.info("Turning off Eiger dev/shm streaming")
|
|
68
|
+
# Fix types in ophyd-async (https://github.com/DiamondLightSource/mx-bluesky/issues/855)
|
|
69
|
+
yield from bps.abs_set(
|
|
70
|
+
xrc_composite.eiger.odin.fan.dev_shm_enable, # type: ignore
|
|
71
|
+
0,
|
|
72
|
+
group=group,
|
|
73
|
+
)
|
|
74
|
+
yield from bps.wait(group)
|
|
75
|
+
|
|
76
|
+
|
|
58
77
|
def construct_beamline_specific_FGS_features(
|
|
59
78
|
setup_trigger_plan: Callable[..., MsgGenerator],
|
|
60
79
|
tidy_plan: Callable[..., MsgGenerator],
|
|
@@ -71,7 +90,7 @@ def construct_beamline_specific_FGS_features(
|
|
|
71
90
|
Ran directly before kicking off the gridscan.
|
|
72
91
|
|
|
73
92
|
tidy_plan (Callable): Tidy up states of devices. Ran at the end of the flyscan, regardless of
|
|
74
|
-
whether or not it finished successfully.
|
|
93
|
+
whether or not it finished successfully. Zocalo and Eiger are cleaned up separately
|
|
75
94
|
|
|
76
95
|
set_flyscan_params_plan (Callable): Set PV's for the relevant Fast Grid Scan dodal device
|
|
77
96
|
|
|
@@ -135,6 +154,10 @@ def common_flyscan_xray_centre(
|
|
|
135
154
|
There are a few other useful decorators to use with this plan, see: verify_undulator_gap_before_run_decorator, transmission_and_xbpm_feedback_for_collection_decorator
|
|
136
155
|
"""
|
|
137
156
|
|
|
157
|
+
def _overall_tidy():
|
|
158
|
+
yield from beamline_specific.tidy_plan()
|
|
159
|
+
yield from generic_tidy(composite)
|
|
160
|
+
|
|
138
161
|
def _decorated_flyscan():
|
|
139
162
|
@bpp.set_run_key_decorator(PlanNameConstants.GRIDSCAN_OUTER)
|
|
140
163
|
@bpp.run_decorator( # attach experiment metadata to the start document
|
|
@@ -146,7 +169,7 @@ def common_flyscan_xray_centre(
|
|
|
146
169
|
],
|
|
147
170
|
}
|
|
148
171
|
)
|
|
149
|
-
@bpp.finalize_decorator(lambda:
|
|
172
|
+
@bpp.finalize_decorator(lambda: _overall_tidy())
|
|
150
173
|
def run_gridscan_and_tidy(
|
|
151
174
|
fgs_composite: FlyScanEssentialDevices,
|
|
152
175
|
params: SpecifiedThreeDGridScan,
|
|
@@ -288,6 +311,7 @@ def _xrc_result_in_boxes_to_result_in_mm(
|
|
|
288
311
|
),
|
|
289
312
|
max_count=xrc_result["max_count"],
|
|
290
313
|
total_count=xrc_result["total_count"],
|
|
314
|
+
sample_id=xrc_result["sample_id"],
|
|
291
315
|
)
|
|
292
316
|
|
|
293
317
|
|
|
@@ -111,6 +111,7 @@ def grid_detect_then_xray_centre(
|
|
|
111
111
|
)
|
|
112
112
|
|
|
113
113
|
|
|
114
|
+
# This function should be private but is currently called by Hyperion, see https://github.com/DiamondLightSource/mx-bluesky/issues/1148
|
|
114
115
|
def detect_grid_and_do_gridscan(
|
|
115
116
|
composite: GridDetectThenXRayCentreComposite,
|
|
116
117
|
parameters: GridCommon,
|
|
@@ -14,7 +14,9 @@ from dodal.log import LOGGER
|
|
|
14
14
|
from dodal.plan_stubs.check_topup import check_topup_and_wait_if_necessary
|
|
15
15
|
from scanspec.core import AxesPoints, Axis
|
|
16
16
|
|
|
17
|
-
from mx_bluesky.common.experiment_plans.read_hardware import
|
|
17
|
+
from mx_bluesky.common.experiment_plans.inner_plans.read_hardware import (
|
|
18
|
+
read_hardware_for_zocalo,
|
|
19
|
+
)
|
|
18
20
|
from mx_bluesky.common.parameters.constants import (
|
|
19
21
|
PlanNameConstants,
|
|
20
22
|
)
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import configparser
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from enum import StrEnum
|
|
4
|
+
from typing import Any, Literal
|
|
4
5
|
|
|
6
|
+
from event_model.documents import Event
|
|
5
7
|
from requests import JSONDecodeError, patch, post
|
|
6
8
|
from requests.auth import AuthBase
|
|
7
9
|
|
|
@@ -63,6 +65,19 @@ assert all(len(value) <= 20 for value in BLSampleStatus), (
|
|
|
63
65
|
)
|
|
64
66
|
|
|
65
67
|
|
|
68
|
+
def create_update_data_from_event_doc(
|
|
69
|
+
mapping: dict[str, str], event: Event
|
|
70
|
+
) -> dict[str, Any]:
|
|
71
|
+
"""Given a mapping between bluesky event data and an event itself this function will
|
|
72
|
+
create a dict that can be used to update exp-eye."""
|
|
73
|
+
event_data = event["data"]
|
|
74
|
+
return {
|
|
75
|
+
target_key: event_data[source_key]
|
|
76
|
+
for source_key, target_key in mapping.items()
|
|
77
|
+
if source_key in event_data
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
66
81
|
class ExpeyeInteraction:
|
|
67
82
|
"""Exposes functionality from the Expeye core API"""
|
|
68
83
|
|
|
@@ -74,24 +89,22 @@ class ExpeyeInteraction:
|
|
|
74
89
|
self._base_url = url
|
|
75
90
|
self._auth = BearerAuth(token)
|
|
76
91
|
|
|
77
|
-
def
|
|
92
|
+
def start_robot_action(
|
|
78
93
|
self,
|
|
94
|
+
action_type: Literal["LOAD", "UNLOAD"],
|
|
79
95
|
proposal_reference: str,
|
|
80
96
|
visit_number: int,
|
|
81
97
|
sample_id: int,
|
|
82
|
-
dewar_location: int,
|
|
83
|
-
container_location: int,
|
|
84
98
|
) -> RobotActionID:
|
|
85
|
-
"""Create a robot
|
|
99
|
+
"""Create a robot action entry in ispyb.
|
|
86
100
|
|
|
87
101
|
Args:
|
|
102
|
+
action_type ("LOAD" | "UNLOAD"): The robot action being performed
|
|
88
103
|
proposal_reference (str): The proposal of the experiment e.g. cm37235
|
|
89
104
|
visit_number (int): The visit number for the proposal, usually this can be
|
|
90
105
|
found added to the end of the proposal e.g. the data for
|
|
91
106
|
visit number 2 of proposal cm37235 is in cm37235-2
|
|
92
107
|
sample_id (int): The id of the sample in the database
|
|
93
|
-
dewar_location (int): Which puck in the dewar the sample is in
|
|
94
|
-
container_location (int): Which pin in that puck has the sample
|
|
95
108
|
|
|
96
109
|
Returns:
|
|
97
110
|
RobotActionID: The id of the robot load action that is created
|
|
@@ -102,39 +115,28 @@ class ExpeyeInteraction:
|
|
|
102
115
|
|
|
103
116
|
data = {
|
|
104
117
|
"startTimestamp": get_current_time_string(),
|
|
118
|
+
"actionType": action_type,
|
|
105
119
|
"sampleId": sample_id,
|
|
106
|
-
"actionType": "LOAD",
|
|
107
|
-
"containerLocation": container_location,
|
|
108
|
-
"dewarLocation": dewar_location,
|
|
109
120
|
}
|
|
110
121
|
response = _send_and_get_response(self._auth, url, data, post)
|
|
111
122
|
return response["robotActionId"]
|
|
112
123
|
|
|
113
|
-
def
|
|
124
|
+
def update_robot_action(
|
|
114
125
|
self,
|
|
115
126
|
action_id: RobotActionID,
|
|
116
|
-
|
|
117
|
-
snapshot_before_path: str,
|
|
118
|
-
snapshot_after_path: str,
|
|
127
|
+
data: dict[str, Any],
|
|
119
128
|
):
|
|
120
|
-
"""Update
|
|
129
|
+
"""Update an existing robot action to contain additional info.
|
|
121
130
|
|
|
122
131
|
Args:
|
|
123
132
|
action_id (RobotActionID): The id of the action to update
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
snapshot_after_path (str): Path to the snapshot after robot load
|
|
133
|
+
data (dict): The data to update with, where the keys match those expected
|
|
134
|
+
by exp-eye.
|
|
127
135
|
"""
|
|
128
136
|
url = self._base_url + self.UPDATE_ROBOT_ACTION.format(action_id=action_id)
|
|
129
|
-
|
|
130
|
-
data = {
|
|
131
|
-
"sampleBarcode": barcode,
|
|
132
|
-
"xtalSnapshotBefore": snapshot_before_path,
|
|
133
|
-
"xtalSnapshotAfter": snapshot_after_path,
|
|
134
|
-
}
|
|
135
137
|
_send_and_get_response(self._auth, url, data, patch)
|
|
136
138
|
|
|
137
|
-
def
|
|
139
|
+
def end_robot_action(self, action_id: RobotActionID, status: str, reason: str):
|
|
138
140
|
"""Finish an existing robot action, providing final information about how it went
|
|
139
141
|
|
|
140
142
|
Args:
|
|
@@ -41,7 +41,6 @@ class IspybIds(BaseModel):
|
|
|
41
41
|
class StoreInIspyb:
|
|
42
42
|
def __init__(self, ispyb_config: str) -> None:
|
|
43
43
|
self.ISPYB_CONFIG_PATH: str = ispyb_config
|
|
44
|
-
self._data_collection_group_id: int | None
|
|
45
44
|
|
|
46
45
|
def begin_deposition(
|
|
47
46
|
self,
|
|
@@ -146,11 +145,17 @@ class StoreInIspyb:
|
|
|
146
145
|
def append_to_comment(
|
|
147
146
|
self, data_collection_id: int, comment: str, delimiter: str = " "
|
|
148
147
|
) -> None:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
148
|
+
try:
|
|
149
|
+
with ispyb.open(self.ISPYB_CONFIG_PATH) as conn:
|
|
150
|
+
assert conn is not None, "Failed to connect to ISPyB!"
|
|
151
|
+
mx_acquisition: MXAcquisition = conn.mx_acquisition
|
|
152
|
+
mx_acquisition.update_data_collection_append_comments(
|
|
153
|
+
data_collection_id, comment, delimiter
|
|
154
|
+
)
|
|
155
|
+
except ispyb.ReadWriteError as e:
|
|
156
|
+
ISPYB_ZOCALO_CALLBACK_LOGGER.warning(
|
|
157
|
+
f"Unable to log comment, comment probably exceeded column length: {comment}",
|
|
158
|
+
exc_info=e,
|
|
154
159
|
)
|
|
155
160
|
|
|
156
161
|
def update_data_collection_group_table(
|
|
@@ -187,7 +192,6 @@ class StoreInIspyb:
|
|
|
187
192
|
params["parentid"] = data_collection_group_id
|
|
188
193
|
params["endtime"] = end_time
|
|
189
194
|
params["run_status"] = run_status
|
|
190
|
-
|
|
191
195
|
mx_acquisition.upsert_data_collection(list(params.values()))
|
|
192
196
|
|
|
193
197
|
def _store_position_table(
|
|
@@ -20,13 +20,13 @@ from mx_bluesky.common.external_interaction.nexus.nexus_utils import (
|
|
|
20
20
|
create_goniometer_axes,
|
|
21
21
|
get_start_and_predicted_end_time,
|
|
22
22
|
)
|
|
23
|
-
from mx_bluesky.common.parameters.components import
|
|
23
|
+
from mx_bluesky.common.parameters.components import DiffractionExperiment
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class NexusWriter:
|
|
27
27
|
def __init__(
|
|
28
28
|
self,
|
|
29
|
-
parameters:
|
|
29
|
+
parameters: DiffractionExperiment,
|
|
30
30
|
data_shape: tuple[int, int, int],
|
|
31
31
|
scan_points: AxesPoints,
|
|
32
32
|
*,
|
|
File without changes
|
|
@@ -238,9 +238,14 @@ class TopNByMaxCountSelection(MultiXtalSelection):
|
|
|
238
238
|
n: int
|
|
239
239
|
|
|
240
240
|
|
|
241
|
+
class TopNByMaxCountForEachSampleSelection(MultiXtalSelection):
|
|
242
|
+
name: Literal["TopNByMaxCountForEachSample"] = "TopNByMaxCountForEachSample" # pyright: ignore [reportIncompatibleVariableOverride]
|
|
243
|
+
n: int
|
|
244
|
+
|
|
245
|
+
|
|
241
246
|
class WithCentreSelection(BaseModel):
|
|
242
|
-
select_centres: TopNByMaxCountSelection =
|
|
243
|
-
discriminator="name", default=TopNByMaxCountSelection(n=1)
|
|
247
|
+
select_centres: TopNByMaxCountSelection | TopNByMaxCountForEachSampleSelection = (
|
|
248
|
+
Field(discriminator="name", default=TopNByMaxCountSelection(n=1))
|
|
244
249
|
)
|
|
245
250
|
|
|
246
251
|
@property
|
|
@@ -11,12 +11,13 @@ from mx_bluesky.definitions import ROOT_DIR
|
|
|
11
11
|
|
|
12
12
|
BEAMLINE = get_beamline_name("test")
|
|
13
13
|
TEST_MODE = BEAMLINE == "test"
|
|
14
|
+
ZEBRA_STATUS_TIMEOUT = 30
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
@dataclass(frozen=True)
|
|
17
18
|
class DocDescriptorNames:
|
|
18
|
-
# Robot load event descriptor
|
|
19
|
-
|
|
19
|
+
# Robot load/unload event descriptor
|
|
20
|
+
ROBOT_UPDATE = "robot_update"
|
|
20
21
|
# For callbacks to use
|
|
21
22
|
OAV_ROTATION_SNAPSHOT_TRIGGERED = "rotation_snapshot_triggered"
|
|
22
23
|
OAV_GRID_SNAPSHOT_TRIGGERED = "snapshot_to_ispyb"
|
|
@@ -41,8 +42,9 @@ class OavConstants:
|
|
|
41
42
|
@dataclass(frozen=True)
|
|
42
43
|
class PlanNameConstants:
|
|
43
44
|
LOAD_CENTRE_COLLECT = "load_centre_collect"
|
|
44
|
-
# Robot
|
|
45
|
+
# Robot subplans
|
|
45
46
|
ROBOT_LOAD = "robot_load"
|
|
47
|
+
ROBOT_UNLOAD = "robot_unload"
|
|
46
48
|
# Gridscan
|
|
47
49
|
GRID_DETECT_AND_DO_GRIDSCAN = "grid_detect_and_do_gridscan"
|
|
48
50
|
GRID_DETECT_INNER = "grid_detect"
|
|
@@ -11,7 +11,7 @@ from dodal.devices.fast_grid_scan import (
|
|
|
11
11
|
ZebraFastGridScan,
|
|
12
12
|
)
|
|
13
13
|
from dodal.devices.flux import Flux
|
|
14
|
-
from dodal.devices.
|
|
14
|
+
from dodal.devices.mx_phase1.beamstop import Beamstop
|
|
15
15
|
from dodal.devices.oav.oav_detector import OAV
|
|
16
16
|
from dodal.devices.oav.pin_image_recognition import PinTipDetection
|
|
17
17
|
from dodal.devices.robot import BartRobot
|
mx_bluesky/common/xrc_result.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import dataclasses
|
|
4
|
+
from collections import defaultdict
|
|
4
5
|
from collections.abc import Callable, Sequence
|
|
5
6
|
from functools import partial
|
|
6
7
|
|
|
@@ -10,6 +11,7 @@ from event_model import RunStart
|
|
|
10
11
|
|
|
11
12
|
from mx_bluesky.common.parameters.components import (
|
|
12
13
|
MultiXtalSelection,
|
|
14
|
+
TopNByMaxCountForEachSampleSelection,
|
|
13
15
|
TopNByMaxCountSelection,
|
|
14
16
|
)
|
|
15
17
|
|
|
@@ -39,18 +41,21 @@ class XRayCentreResult:
|
|
|
39
41
|
containing the crystal
|
|
40
42
|
max_count: The maximum spot count encountered in any one grid box in the crystal
|
|
41
43
|
total_count: The total count across all boxes in the crystal.
|
|
44
|
+
sample_id: The sample id associated with the centre.
|
|
42
45
|
"""
|
|
43
46
|
|
|
44
47
|
centre_of_mass_mm: np.ndarray
|
|
45
48
|
bounding_box_mm: tuple[np.ndarray, np.ndarray]
|
|
46
49
|
max_count: int
|
|
47
50
|
total_count: int
|
|
51
|
+
sample_id: int | None
|
|
48
52
|
|
|
49
53
|
def __eq__(self, o):
|
|
50
54
|
return (
|
|
51
55
|
isinstance(o, XRayCentreResult)
|
|
52
56
|
and o.max_count == self.max_count
|
|
53
57
|
and o.total_count == self.total_count
|
|
58
|
+
and o.sample_id == self.sample_id
|
|
54
59
|
and all(o.centre_of_mass_mm == self.centre_of_mass_mm)
|
|
55
60
|
and all(o.bounding_box_mm[0] == self.bounding_box_mm[0])
|
|
56
61
|
and all(o.bounding_box_mm[1] == self.bounding_box_mm[1])
|
|
@@ -64,9 +69,27 @@ def top_n_by_max_count(
|
|
|
64
69
|
return sorted_hits[:n]
|
|
65
70
|
|
|
66
71
|
|
|
72
|
+
def top_n_by_max_count_for_each_sample(
|
|
73
|
+
unfiltered: Sequence[XRayCentreResult], n: int
|
|
74
|
+
) -> Sequence[XRayCentreResult]:
|
|
75
|
+
xrc_results_by_sample_id: dict[int | None, list[XRayCentreResult]] = defaultdict(
|
|
76
|
+
list[XRayCentreResult]
|
|
77
|
+
)
|
|
78
|
+
for result in unfiltered:
|
|
79
|
+
xrc_results_by_sample_id[result.sample_id].append(result)
|
|
80
|
+
return [
|
|
81
|
+
result
|
|
82
|
+
for results in xrc_results_by_sample_id.values()
|
|
83
|
+
for result in sorted(results, key=lambda x: x.max_count, reverse=True)[:n]
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
|
|
67
87
|
def resolve_selection_fn(
|
|
68
88
|
params: MultiXtalSelection,
|
|
69
89
|
) -> Callable[[Sequence[XRayCentreResult]], Sequence[XRayCentreResult]]:
|
|
70
|
-
|
|
71
|
-
|
|
90
|
+
match params:
|
|
91
|
+
case TopNByMaxCountSelection():
|
|
92
|
+
return partial(top_n_by_max_count, n=params.n)
|
|
93
|
+
case TopNByMaxCountForEachSampleSelection():
|
|
94
|
+
return partial(top_n_by_max_count_for_each_sample, n=params.n)
|
|
72
95
|
raise ValueError(f"Invalid selection function {params.name}")
|
mx_bluesky/hyperion/__main__.py
CHANGED
|
@@ -218,7 +218,7 @@ class RunExperiment(Resource):
|
|
|
218
218
|
status_and_message = self.runner.start(plan, params, plan_name)
|
|
219
219
|
except Exception as e:
|
|
220
220
|
status_and_message = make_error_status_and_message(e)
|
|
221
|
-
LOGGER.error(format_exception(e))
|
|
221
|
+
LOGGER.error("".join(format_exception(e)))
|
|
222
222
|
|
|
223
223
|
elif action == Actions.STOP.value:
|
|
224
224
|
status_and_message = self.runner.stop()
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
from collections.abc import Sequence
|
|
2
2
|
|
|
3
|
+
from blueapi.core import BlueskyContext
|
|
3
4
|
from bluesky import plan_stubs as bps
|
|
4
5
|
from bluesky import preprocessors as bpp
|
|
5
6
|
from dodal.devices.baton import Baton
|
|
6
7
|
|
|
8
|
+
from mx_bluesky.common.utils.context import find_device_in_context
|
|
7
9
|
from mx_bluesky.common.utils.exceptions import WarningException
|
|
10
|
+
from mx_bluesky.common.utils.log import LOGGER
|
|
8
11
|
from mx_bluesky.hyperion.experiment_plans.load_centre_collect_full_plan import (
|
|
9
12
|
LoadCentreCollectComposite,
|
|
13
|
+
create_devices,
|
|
10
14
|
load_centre_collect_full,
|
|
11
15
|
)
|
|
12
16
|
from mx_bluesky.hyperion.external_interaction.agamemnon import (
|
|
13
17
|
create_parameters_from_agamemnon,
|
|
14
18
|
)
|
|
15
19
|
from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
|
|
20
|
+
from mx_bluesky.hyperion.utils.context import (
|
|
21
|
+
clear_all_device_caches,
|
|
22
|
+
setup_devices,
|
|
23
|
+
)
|
|
16
24
|
|
|
17
25
|
HYPERION_USER = "Hyperion"
|
|
18
26
|
NO_USER = "None"
|
|
@@ -59,7 +67,24 @@ def move_to_default_state():
|
|
|
59
67
|
yield from bps.null()
|
|
60
68
|
|
|
61
69
|
|
|
62
|
-
def
|
|
70
|
+
def initialise_udc(context: BlueskyContext, dev_mode: bool = False):
|
|
71
|
+
"""
|
|
72
|
+
Perform all initialisation that happens at the start of UDC just after the
|
|
73
|
+
baton is acquired, but before we execute any plans or move hardware.
|
|
74
|
+
|
|
75
|
+
Beamline devices are unloaded and reloaded in order to pick up any new configuration,
|
|
76
|
+
bluesky context gets new set of devices.
|
|
77
|
+
"""
|
|
78
|
+
LOGGER.info("Initialising mx-bluesky for UDC start...")
|
|
79
|
+
clear_all_device_caches(context)
|
|
80
|
+
setup_devices(context, dev_mode)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _get_baton(context: BlueskyContext) -> Baton:
|
|
84
|
+
return find_device_in_context(context, "baton", Baton)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def run_udc_when_requested(context: BlueskyContext, dev_mode: bool = False):
|
|
63
88
|
"""This will wait for the baton to be handed to hyperion and then run through the
|
|
64
89
|
UDC queue from agamemnon until:
|
|
65
90
|
1. There are no more instructions from agamemnon
|
|
@@ -69,21 +94,28 @@ def run_udc_when_requested(baton: Baton, composite: LoadCentreCollectComposite):
|
|
|
69
94
|
In the case of 1. or 2. hyperion will immediately release the baton. In the case of
|
|
70
95
|
3. the baton will be released after the next collection has finished."""
|
|
71
96
|
|
|
97
|
+
baton = _get_baton(context)
|
|
72
98
|
yield from wait_for_hyperion_requested(baton)
|
|
73
99
|
yield from bps.abs_set(baton.current_user, HYPERION_USER)
|
|
74
100
|
|
|
75
|
-
def
|
|
101
|
+
def initialise_then_collect():
|
|
102
|
+
initialise_udc(context, dev_mode)
|
|
76
103
|
yield from move_to_default_state()
|
|
77
|
-
|
|
104
|
+
|
|
105
|
+
# re-fetch the baton because the device has been reinstantiated
|
|
106
|
+
new_baton = _get_baton(context)
|
|
107
|
+
composite = create_devices(context)
|
|
108
|
+
yield from main_hyperion_loop(new_baton, composite)
|
|
78
109
|
|
|
79
110
|
def release_baton():
|
|
80
111
|
# If hyperion has given up the baton itself we need to also release requested
|
|
81
112
|
# user so that hyperion doesn't think we're requested again
|
|
113
|
+
baton = _get_baton(context)
|
|
82
114
|
requested_user = yield from bps.rd(baton.requested_user)
|
|
83
115
|
if requested_user == HYPERION_USER:
|
|
84
116
|
yield from bps.abs_set(baton.requested_user, NO_USER)
|
|
85
117
|
yield from bps.abs_set(baton.current_user, NO_USER)
|
|
86
118
|
|
|
87
119
|
yield from bpp.contingency_wrapper(
|
|
88
|
-
|
|
120
|
+
initialise_then_collect(), final_plan=release_baton
|
|
89
121
|
)
|