mx-bluesky 1.4.6__py3-none-any.whl → 1.4.7__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/aithre_lasershaping/__init__.py +13 -0
- mx_bluesky/beamlines/aithre_lasershaping/check_goniometer_performance.py +29 -0
- mx_bluesky/beamlines/aithre_lasershaping/goniometer_controls.py +18 -0
- mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +31 -25
- mx_bluesky/beamlines/i04/thawing_plan.py +10 -1
- mx_bluesky/beamlines/i24/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +12 -12
- mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +30 -29
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +10 -11
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +8 -10
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +10 -3
- mx_bluesky/beamlines/i24/serial/log.py +1 -0
- mx_bluesky/beamlines/i24/serial/set_visit_directory.sh +1 -1
- mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +16 -16
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +47 -48
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +1 -1
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +9 -7
- mx_bluesky/beamlines/i24/serial/write_nexus.py +3 -2
- mx_bluesky/common/device_setup_plans/xbpm_feedback.py +45 -0
- mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py +2 -4
- mx_bluesky/common/external_interaction/callbacks/common/ispyb_mapping.py +1 -1
- mx_bluesky/common/external_interaction/callbacks/common/zocalo_callback.py +18 -15
- mx_bluesky/{hyperion → common}/external_interaction/callbacks/sample_handling/sample_handling_callback.py +16 -4
- mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py +41 -5
- mx_bluesky/common/external_interaction/callbacks/xray_centre/nexus_callback.py +2 -1
- mx_bluesky/common/external_interaction/ispyb/data_model.py +1 -0
- mx_bluesky/common/external_interaction/ispyb/ispyb_store.py +14 -1
- mx_bluesky/common/external_interaction/nexus/nexus_utils.py +1 -1
- mx_bluesky/common/parameters/constants.py +2 -0
- mx_bluesky/common/parameters/gridscan.py +1 -1
- mx_bluesky/common/plans/write_sample_status.py +46 -0
- mx_bluesky/common/preprocessors/__init__.py +0 -0
- mx_bluesky/common/preprocessors/preprocessors.py +105 -0
- mx_bluesky/common/protocols/__init__.py +0 -0
- mx_bluesky/common/protocols/protocols.py +10 -0
- mx_bluesky/hyperion/__main__.py +3 -9
- mx_bluesky/hyperion/baton_handler.py +84 -0
- mx_bluesky/hyperion/device_setup_plans/setup_panda.py +5 -1
- mx_bluesky/hyperion/experiment_plans/__init__.py +0 -4
- mx_bluesky/hyperion/experiment_plans/change_aperture_then_move_plan.py +10 -25
- mx_bluesky/hyperion/experiment_plans/experiment_registry.py +0 -7
- mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +11 -10
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +5 -1
- mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +2 -2
- mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +5 -3
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +0 -26
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +23 -18
- mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +25 -6
- mx_bluesky/hyperion/external_interaction/agamemnon.py +148 -10
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +12 -6
- mx_bluesky/hyperion/external_interaction/callbacks/snapshot_callback.py +107 -0
- mx_bluesky/hyperion/parameters/gridscan.py +2 -2
- mx_bluesky/hyperion/parameters/rotation.py +1 -1
- {mx_bluesky-1.4.6.dist-info → mx_bluesky-1.4.7.dist-info}/METADATA +7 -7
- {mx_bluesky-1.4.6.dist-info → mx_bluesky-1.4.7.dist-info}/RECORD +60 -51
- {mx_bluesky-1.4.6.dist-info → mx_bluesky-1.4.7.dist-info}/WHEEL +1 -1
- mx_bluesky/common/external_interaction/callbacks/common/aperture_change_callback.py +0 -22
- mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +0 -103
- /mx_bluesky/{hyperion → common}/external_interaction/callbacks/sample_handling/__init__.py +0 -0
- {mx_bluesky-1.4.6.dist-info → mx_bluesky-1.4.7.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.4.6.dist-info → mx_bluesky-1.4.7.dist-info/licenses}/LICENSE +0 -0
- {mx_bluesky-1.4.6.dist-info → mx_bluesky-1.4.7.dist-info}/top_level.txt +0 -0
|
@@ -29,6 +29,7 @@ from mx_bluesky.common.external_interaction.callbacks.xray_centre.ispyb_mapping
|
|
|
29
29
|
)
|
|
30
30
|
from mx_bluesky.common.external_interaction.ispyb.data_model import (
|
|
31
31
|
DataCollectionGridInfo,
|
|
32
|
+
DataCollectionGroupInfo,
|
|
32
33
|
DataCollectionInfo,
|
|
33
34
|
DataCollectionPositionInfo,
|
|
34
35
|
Orientation,
|
|
@@ -52,6 +53,9 @@ from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER, set_dcgid_
|
|
|
52
53
|
if TYPE_CHECKING:
|
|
53
54
|
from event_model import Event, RunStart, RunStop
|
|
54
55
|
|
|
56
|
+
T = TypeVar("T", bound="GridCommon")
|
|
57
|
+
ASSERT_START_BEFORE_EVENT_DOC_MESSAGE = f"No data collection group info - event document has been emitted before a {PlanNameConstants.GRID_DETECT_AND_DO_GRIDSCAN} start document"
|
|
58
|
+
|
|
55
59
|
|
|
56
60
|
def ispyb_activation_wrapper(plan_generator: MsgGenerator, parameters):
|
|
57
61
|
return bpp.set_run_key_wrapper(
|
|
@@ -67,9 +71,6 @@ def ispyb_activation_wrapper(plan_generator: MsgGenerator, parameters):
|
|
|
67
71
|
)
|
|
68
72
|
|
|
69
73
|
|
|
70
|
-
T = TypeVar("T", bound="GridCommon")
|
|
71
|
-
|
|
72
|
-
|
|
73
74
|
class GridscanISPyBCallback(BaseISPyBCallback):
|
|
74
75
|
"""Callback class to handle the deposition of experiment parameters into the ISPyB
|
|
75
76
|
database. Listens for 'event' and 'descriptor' documents. Creates the ISpyB entry on
|
|
@@ -97,6 +98,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
97
98
|
self.param_type = param_type
|
|
98
99
|
self._start_of_fgs_uid: str | None = None
|
|
99
100
|
self._processing_start_time: float | None = None
|
|
101
|
+
self.data_collection_group_info: DataCollectionGroupInfo | None
|
|
100
102
|
|
|
101
103
|
def activity_gated_start(self, doc: RunStart):
|
|
102
104
|
if doc.get("subplan_name") == PlanNameConstants.DO_FGS:
|
|
@@ -111,7 +113,9 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
111
113
|
assert isinstance(mx_bluesky_parameters, str)
|
|
112
114
|
self.params = self.param_type.model_validate_json(mx_bluesky_parameters)
|
|
113
115
|
self.ispyb = StoreInIspyb(self.ispyb_config)
|
|
114
|
-
data_collection_group_info = populate_data_collection_group(
|
|
116
|
+
self.data_collection_group_info = populate_data_collection_group(
|
|
117
|
+
self.params
|
|
118
|
+
)
|
|
115
119
|
|
|
116
120
|
scan_data_infos = [
|
|
117
121
|
ScanDataInfo(
|
|
@@ -135,12 +139,13 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
135
139
|
]
|
|
136
140
|
|
|
137
141
|
self.ispyb_ids = self.ispyb.begin_deposition(
|
|
138
|
-
data_collection_group_info, scan_data_infos
|
|
142
|
+
self.data_collection_group_info, scan_data_infos
|
|
139
143
|
)
|
|
140
144
|
set_dcgid_tag(self.ispyb_ids.data_collection_group_id)
|
|
141
145
|
return super().activity_gated_start(doc)
|
|
142
146
|
|
|
143
147
|
def activity_gated_event(self, doc: Event):
|
|
148
|
+
assert self.data_collection_group_info, ASSERT_START_BEFORE_EVENT_DOC_MESSAGE
|
|
144
149
|
doc = super().activity_gated_event(doc)
|
|
145
150
|
|
|
146
151
|
descriptor_name = self.descriptors[doc["descriptor"]].get("name")
|
|
@@ -151,10 +156,14 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
151
156
|
self.ispyb_ids = self.ispyb.update_deposition(
|
|
152
157
|
self.ispyb_ids, scan_data_infos
|
|
153
158
|
)
|
|
159
|
+
self.ispyb.update_data_collection_group_table(
|
|
160
|
+
self.data_collection_group_info, self.ispyb_ids.data_collection_group_id
|
|
161
|
+
)
|
|
154
162
|
|
|
155
163
|
return doc
|
|
156
164
|
|
|
157
165
|
def _handle_zocalo_read_event(self, doc):
|
|
166
|
+
assert self.data_collection_group_info, ASSERT_START_BEFORE_EVENT_DOC_MESSAGE
|
|
158
167
|
crystal_summary = ""
|
|
159
168
|
if self._processing_start_time is not None:
|
|
160
169
|
proc_time = time() - self._processing_start_time
|
|
@@ -185,6 +194,11 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
185
194
|
assert self.ispyb_ids.data_collection_ids, (
|
|
186
195
|
"No data collection to add results to"
|
|
187
196
|
)
|
|
197
|
+
|
|
198
|
+
self.data_collection_group_info.comments = (
|
|
199
|
+
self.data_collection_group_info.comments or ""
|
|
200
|
+
) + crystal_summary
|
|
201
|
+
|
|
188
202
|
self.ispyb.append_to_comment(
|
|
189
203
|
self.ispyb_ids.data_collection_ids[0], crystal_summary
|
|
190
204
|
)
|
|
@@ -192,6 +206,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
192
206
|
def _handle_oav_grid_snapshot_triggered(self, doc) -> Sequence[ScanDataInfo]:
|
|
193
207
|
assert self.ispyb_ids.data_collection_ids, "No current data collection"
|
|
194
208
|
assert self.params, "ISPyB handler didn't receive parameters!"
|
|
209
|
+
assert self.data_collection_group_info, "No data collection group"
|
|
195
210
|
data = doc["data"]
|
|
196
211
|
data_collection_id = None
|
|
197
212
|
data_collection_info = DataCollectionInfo(
|
|
@@ -220,6 +235,18 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
220
235
|
data_collection_info.comments = construct_comment_for_gridscan(
|
|
221
236
|
data_collection_grid_info
|
|
222
237
|
)
|
|
238
|
+
|
|
239
|
+
if self.data_collection_group_info.comments:
|
|
240
|
+
self.data_collection_group_info.comments += (
|
|
241
|
+
f"by {data_collection_grid_info.steps_y}."
|
|
242
|
+
)
|
|
243
|
+
else:
|
|
244
|
+
self.data_collection_group_info.comments = (
|
|
245
|
+
f"Diffraction grid scan of "
|
|
246
|
+
f"{data_collection_grid_info.steps_x} "
|
|
247
|
+
f"by {data_collection_grid_info.steps_y} "
|
|
248
|
+
)
|
|
249
|
+
|
|
223
250
|
if len(self.ispyb_ids.data_collection_ids) > self._oav_snapshot_event_idx:
|
|
224
251
|
data_collection_id = self.ispyb_ids.data_collection_ids[
|
|
225
252
|
self._oav_snapshot_event_idx
|
|
@@ -275,6 +302,9 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
275
302
|
return scan_data_infos
|
|
276
303
|
|
|
277
304
|
def activity_gated_stop(self, doc: RunStop) -> RunStop:
|
|
305
|
+
assert self.data_collection_group_info, (
|
|
306
|
+
f"No data collection group info - stop document has been emitted before a {PlanNameConstants.GRID_DETECT_AND_DO_GRIDSCAN} start document"
|
|
307
|
+
)
|
|
278
308
|
if doc.get("run_start") == self._start_of_fgs_uid:
|
|
279
309
|
self._processing_start_time = time()
|
|
280
310
|
if doc.get("run_start") == self.uid_to_finalize_on:
|
|
@@ -289,5 +319,11 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
289
319
|
)
|
|
290
320
|
if exception_type:
|
|
291
321
|
doc["reason"] = message
|
|
322
|
+
self.data_collection_group_info.comments = message
|
|
323
|
+
self.ispyb.update_data_collection_group_table(
|
|
324
|
+
self.data_collection_group_info,
|
|
325
|
+
self.ispyb_ids.data_collection_group_id,
|
|
326
|
+
)
|
|
327
|
+
self.data_collection_group_info = None
|
|
292
328
|
return super().activity_gated_stop(doc)
|
|
293
329
|
return self._tag_doc(doc)
|
|
@@ -79,7 +79,8 @@ class GridscanNexusFileCallback(PlanReactiveCallback):
|
|
|
79
79
|
self.descriptors[doc["uid"]] = doc
|
|
80
80
|
|
|
81
81
|
def activity_gated_event(self, doc: Event) -> Event | None:
|
|
82
|
-
|
|
82
|
+
event_descriptor = self.descriptors.get(doc["descriptor"])
|
|
83
|
+
assert event_descriptor is not None
|
|
83
84
|
if event_descriptor.get("name") == DocDescriptorNames.HARDWARE_READ_DURING:
|
|
84
85
|
data = doc["data"]
|
|
85
86
|
for nexus_writer in [self.nexus_writer_1, self.nexus_writer_2]:
|
|
@@ -77,7 +77,7 @@ class StoreInIspyb:
|
|
|
77
77
|
scan_data_infos,
|
|
78
78
|
) -> IspybIds:
|
|
79
79
|
with ispyb.open(self.ISPYB_CONFIG_PATH) as conn:
|
|
80
|
-
assert conn
|
|
80
|
+
assert conn, "Failed to connect to ISPyB"
|
|
81
81
|
if data_collection_group_info:
|
|
82
82
|
ispyb_ids.data_collection_group_id = (
|
|
83
83
|
self._store_data_collection_group_table(
|
|
@@ -152,6 +152,19 @@ class StoreInIspyb:
|
|
|
152
152
|
data_collection_id, comment, delimiter
|
|
153
153
|
)
|
|
154
154
|
|
|
155
|
+
def update_data_collection_group_table(
|
|
156
|
+
self,
|
|
157
|
+
dcg_info: DataCollectionGroupInfo,
|
|
158
|
+
data_collection_group_id: int | None = None,
|
|
159
|
+
) -> None:
|
|
160
|
+
with ispyb.open(self.ISPYB_CONFIG_PATH) as conn:
|
|
161
|
+
assert conn is not None, "Failed to connect to ISPyB!"
|
|
162
|
+
self._store_data_collection_group_table(
|
|
163
|
+
conn,
|
|
164
|
+
dcg_info,
|
|
165
|
+
data_collection_group_id,
|
|
166
|
+
)
|
|
167
|
+
|
|
155
168
|
def _update_scan_with_end_time_and_status(
|
|
156
169
|
self,
|
|
157
170
|
end_time: str,
|
|
@@ -144,7 +144,7 @@ def create_detector_parameters(detector_params: DetectorParams) -> Detector:
|
|
|
144
144
|
list(
|
|
145
145
|
detector_params.get_beam_position_pixels(detector_params.detector_distance)
|
|
146
146
|
),
|
|
147
|
-
detector_params.
|
|
147
|
+
detector_params.exposure_time_s,
|
|
148
148
|
[(-1.0, 0.0, 0.0), (0.0, -1.0, 0.0)],
|
|
149
149
|
)
|
|
150
150
|
|
|
@@ -168,7 +168,7 @@ class SpecifiedThreeDGridScan(
|
|
|
168
168
|
get_beamline_name("dev")
|
|
169
169
|
],
|
|
170
170
|
expected_energy_ev=self.demand_energy_ev,
|
|
171
|
-
|
|
171
|
+
exposure_time_s=self.exposure_time_s,
|
|
172
172
|
directory=self.storage_directory,
|
|
173
173
|
prefix=self.file_name,
|
|
174
174
|
detector_distance=self.detector_distance_mm,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
|
|
3
|
+
import bluesky.plan_stubs as bps
|
|
4
|
+
import bluesky.preprocessors as bpp
|
|
5
|
+
|
|
6
|
+
from mx_bluesky.common.external_interaction.callbacks.sample_handling.sample_handling_callback import (
|
|
7
|
+
SampleHandlingCallback,
|
|
8
|
+
)
|
|
9
|
+
from mx_bluesky.common.utils.exceptions import SampleException
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SampleStatusExceptionType(StrEnum):
|
|
13
|
+
BEAMLINE = "Beamline"
|
|
14
|
+
SAMPLE = "Sample"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@bpp.subs_decorator(SampleHandlingCallback())
|
|
18
|
+
def deposit_sample_error(exception_type: SampleStatusExceptionType, sample_id: int):
|
|
19
|
+
@bpp.run_decorator(
|
|
20
|
+
md={
|
|
21
|
+
"metadata": {"sample_id": sample_id},
|
|
22
|
+
"activate_callbacks": ["SampleHandlingCallback"],
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
def _inner():
|
|
26
|
+
yield from bps.null()
|
|
27
|
+
if exception_type == SampleStatusExceptionType.BEAMLINE:
|
|
28
|
+
raise AssertionError()
|
|
29
|
+
elif exception_type == SampleStatusExceptionType.SAMPLE:
|
|
30
|
+
raise SampleException
|
|
31
|
+
|
|
32
|
+
yield from _inner()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@bpp.subs_decorator(SampleHandlingCallback(record_loaded_on_success=True))
|
|
36
|
+
def deposit_loaded_sample(sample_id: int):
|
|
37
|
+
@bpp.run_decorator(
|
|
38
|
+
md={
|
|
39
|
+
"metadata": {"sample_id": sample_id},
|
|
40
|
+
"activate_callbacks": ["SampleHandlingCallback"],
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
def _inner():
|
|
44
|
+
yield from bps.null()
|
|
45
|
+
|
|
46
|
+
yield from _inner()
|
|
File without changes
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from bluesky import preprocessors as bpp
|
|
2
|
+
from bluesky.preprocessors import plan_mutator
|
|
3
|
+
from bluesky.utils import Msg, MsgGenerator, make_decorator
|
|
4
|
+
|
|
5
|
+
from mx_bluesky.common.device_setup_plans.xbpm_feedback import (
|
|
6
|
+
check_and_pause_feedback,
|
|
7
|
+
unpause_xbpm_feedback_and_set_transmission_to_1,
|
|
8
|
+
)
|
|
9
|
+
from mx_bluesky.common.parameters.constants import PlanNameConstants
|
|
10
|
+
from mx_bluesky.common.protocols.protocols import (
|
|
11
|
+
XBPMPauseDevices,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def transmission_and_xbpm_feedback_for_collection_wrapper(
|
|
16
|
+
plan: MsgGenerator,
|
|
17
|
+
devices: XBPMPauseDevices,
|
|
18
|
+
desired_transmission_fraction: float,
|
|
19
|
+
run_key_to_wrap: PlanNameConstants | None = None,
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
Sets the transmission for the data collection, ensuring the xbpm feedback is valid, then resets it immediately after
|
|
23
|
+
the run has finished.
|
|
24
|
+
|
|
25
|
+
This wrapper should be attached to the entry point of any beamline-specific plan that may disrupt the XBPM feedback,
|
|
26
|
+
such as a data collection or an x-ray center grid scan.
|
|
27
|
+
This wrapper will do nothing if no runs are seen.
|
|
28
|
+
|
|
29
|
+
XBPM feedback isn't reliable during collections due to:
|
|
30
|
+
* Objects (e.g. attenuator) crossing the beam can cause large (incorrect) feedback movements
|
|
31
|
+
* Lower transmissions/higher energies are less reliable for the xbpm
|
|
32
|
+
|
|
33
|
+
So we need to keep the transmission at 100% and the feedback on when not collecting
|
|
34
|
+
and then turn it off and set the correct transmission for collection. The feedback
|
|
35
|
+
mostly accounts for slow thermal drift so it is safe to assume that the beam is
|
|
36
|
+
stable during a collection.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
plan: The plan performing the data collection.
|
|
40
|
+
devices (XBPMPauseDevices): Composite device including The XBPM device that is responsible for keeping
|
|
41
|
+
the beam in position, and attenuator
|
|
42
|
+
desired_transmission_fraction (float): The desired transmission for the collection
|
|
43
|
+
run_key_to_wrap: (str | None): Pausing XBPM and setting transmission is inserted after the 'open_run' message is seen with
|
|
44
|
+
the matching run key, and unpausing and resetting transmission is inserted after the corresponding 'close_run' message is
|
|
45
|
+
seen. If not specified, instead wrap the first run encountered.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
_wrapped_run_name: None | str = None
|
|
49
|
+
|
|
50
|
+
def head(msg: Msg):
|
|
51
|
+
yield from check_and_pause_feedback(
|
|
52
|
+
devices.xbpm_feedback,
|
|
53
|
+
devices.attenuator,
|
|
54
|
+
desired_transmission_fraction,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Allow 'open_run' message to pass through
|
|
58
|
+
yield msg
|
|
59
|
+
|
|
60
|
+
def tail():
|
|
61
|
+
yield from unpause_xbpm_feedback_and_set_transmission_to_1(
|
|
62
|
+
devices.xbpm_feedback, devices.attenuator
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def insert_plans(msg: Msg):
|
|
66
|
+
# Wrap the specified run, or, if none specified, wrap the first run encountered
|
|
67
|
+
nonlocal _wrapped_run_name
|
|
68
|
+
|
|
69
|
+
match msg.command:
|
|
70
|
+
case "open_run":
|
|
71
|
+
# If we specified a run key, did we encounter it
|
|
72
|
+
# If we didn't specify, then insert the plans and track the name of the run
|
|
73
|
+
if (
|
|
74
|
+
not (run_key_to_wrap or _wrapped_run_name)
|
|
75
|
+
or run_key_to_wrap is msg.run
|
|
76
|
+
):
|
|
77
|
+
_wrapped_run_name = msg.run if msg.run else "unnamed_run"
|
|
78
|
+
return head(msg), None
|
|
79
|
+
case "close_run":
|
|
80
|
+
# Check if the run tracked from above was closed
|
|
81
|
+
# An exception is raised in the RunEngine if two unnamed runs are opened
|
|
82
|
+
# at the same time, so we are safe from unpausing on the wrong run
|
|
83
|
+
if (_wrapped_run_name == "unnamed_run" and not msg.run) or (
|
|
84
|
+
msg.run and _wrapped_run_name and _wrapped_run_name is msg.run
|
|
85
|
+
):
|
|
86
|
+
return None, tail()
|
|
87
|
+
|
|
88
|
+
return None, None
|
|
89
|
+
|
|
90
|
+
# Contingency wrapper can cause unpausing to occur on exception and again on close_run.
|
|
91
|
+
# Not needed after https://github.com/bluesky/bluesky/issues/1891
|
|
92
|
+
return (
|
|
93
|
+
yield from bpp.contingency_wrapper(
|
|
94
|
+
plan_mutator(plan, insert_plans),
|
|
95
|
+
except_plan=lambda _: unpause_xbpm_feedback_and_set_transmission_to_1(
|
|
96
|
+
devices.xbpm_feedback,
|
|
97
|
+
devices.attenuator,
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
transmission_and_xbpm_feedback_for_collection_decorator = make_decorator(
|
|
104
|
+
transmission_and_xbpm_feedback_for_collection_wrapper
|
|
105
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from typing import Protocol, runtime_checkable
|
|
2
|
+
|
|
3
|
+
from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
|
|
4
|
+
from dodal.devices.xbpm_feedback import XBPMFeedback
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@runtime_checkable
|
|
8
|
+
class XBPMPauseDevices(Protocol):
|
|
9
|
+
xbpm_feedback: XBPMFeedback
|
|
10
|
+
attenuator: BinaryFilterAttenuator
|
mx_bluesky/hyperion/__main__.py
CHANGED
|
@@ -16,9 +16,6 @@ from flask import Flask, request
|
|
|
16
16
|
from flask_restful import Api, Resource
|
|
17
17
|
from pydantic.dataclasses import dataclass
|
|
18
18
|
|
|
19
|
-
from mx_bluesky.common.external_interaction.callbacks.common.aperture_change_callback import (
|
|
20
|
-
ApertureChangeCallback,
|
|
21
|
-
)
|
|
22
19
|
from mx_bluesky.common.external_interaction.callbacks.common.log_uid_tag_callback import (
|
|
23
20
|
LogUidTaggingCallback,
|
|
24
21
|
)
|
|
@@ -39,6 +36,7 @@ from mx_bluesky.hyperion.experiment_plans.experiment_registry import (
|
|
|
39
36
|
PlanNotFound,
|
|
40
37
|
)
|
|
41
38
|
from mx_bluesky.hyperion.external_interaction.agamemnon import (
|
|
39
|
+
compare_params,
|
|
42
40
|
update_params_from_agamemnon,
|
|
43
41
|
)
|
|
44
42
|
from mx_bluesky.hyperion.parameters.cli import parse_cli_args
|
|
@@ -89,13 +87,11 @@ class BlueskyRunner:
|
|
|
89
87
|
self.command_queue: Queue[Command] = Queue()
|
|
90
88
|
self.current_status: StatusAndMessage = StatusAndMessage(Status.IDLE)
|
|
91
89
|
self.last_run_aborted: bool = False
|
|
92
|
-
self.aperture_change_callback = ApertureChangeCallback()
|
|
93
90
|
self.logging_uid_tag_callback = LogUidTaggingCallback()
|
|
94
91
|
self.context: BlueskyContext
|
|
95
92
|
|
|
96
93
|
self.RE = RE
|
|
97
94
|
self.context = context
|
|
98
|
-
RE.subscribe(self.aperture_change_callback)
|
|
99
95
|
RE.subscribe(self.logging_uid_tag_callback)
|
|
100
96
|
|
|
101
97
|
LOGGER.info("Connecting to external callback ZMQ proxy...")
|
|
@@ -175,10 +171,7 @@ class BlueskyRunner:
|
|
|
175
171
|
with TRACER.start_span("do_run"):
|
|
176
172
|
self.RE(command.experiment(command.devices, command.parameters))
|
|
177
173
|
|
|
178
|
-
self.current_status = StatusAndMessage(
|
|
179
|
-
Status.IDLE,
|
|
180
|
-
self.aperture_change_callback.last_selected_aperture,
|
|
181
|
-
)
|
|
174
|
+
self.current_status = StatusAndMessage(Status.IDLE)
|
|
182
175
|
|
|
183
176
|
self.last_run_aborted = False
|
|
184
177
|
except WarningException as exception:
|
|
@@ -212,6 +205,7 @@ def compose_start_args(context: BlueskyContext, plan_name: str, action: Actions)
|
|
|
212
205
|
try:
|
|
213
206
|
parameters = experiment_internal_param_type(**json.loads(request.data))
|
|
214
207
|
parameters = update_params_from_agamemnon(parameters)
|
|
208
|
+
compare_params(parameters)
|
|
215
209
|
if parameters.model_extra:
|
|
216
210
|
raise ValueError(f"Extra fields not allowed {parameters.model_extra}")
|
|
217
211
|
except Exception as e:
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from bluesky import plan_stubs as bps
|
|
2
|
+
from bluesky import preprocessors as bpp
|
|
3
|
+
from dodal.devices.baton import Baton
|
|
4
|
+
|
|
5
|
+
from mx_bluesky.common.utils.exceptions import WarningException
|
|
6
|
+
from mx_bluesky.hyperion.experiment_plans.load_centre_collect_full_plan import (
|
|
7
|
+
LoadCentreCollectComposite,
|
|
8
|
+
load_centre_collect_full,
|
|
9
|
+
)
|
|
10
|
+
from mx_bluesky.hyperion.external_interaction.agamemnon import (
|
|
11
|
+
create_parameters_from_agamemnon,
|
|
12
|
+
)
|
|
13
|
+
from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
|
|
14
|
+
|
|
15
|
+
HYPERION_USER = "Hyperion"
|
|
16
|
+
NO_USER = "None"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def wait_for_hyperion_requested(baton: Baton):
|
|
20
|
+
SLEEP_PER_CHECK = 0.1
|
|
21
|
+
while True:
|
|
22
|
+
requested_user = yield from bps.rd(baton.requested_user)
|
|
23
|
+
if requested_user == HYPERION_USER:
|
|
24
|
+
break
|
|
25
|
+
yield from bps.sleep(SLEEP_PER_CHECK)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def ignore_sample_errors(exception: Exception):
|
|
29
|
+
yield from bps.null()
|
|
30
|
+
# For sample errors we want to continue the loop
|
|
31
|
+
if not isinstance(exception, WarningException):
|
|
32
|
+
raise exception
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main_hyperion_loop(baton: Baton, composite: LoadCentreCollectComposite):
|
|
36
|
+
requested_user = yield from bps.rd(baton.requested_user)
|
|
37
|
+
while requested_user == HYPERION_USER:
|
|
38
|
+
|
|
39
|
+
def inner_loop():
|
|
40
|
+
parameters: LoadCentreCollect | None = create_parameters_from_agamemnon() # type: ignore # not complete until https://github.com/DiamondLightSource/mx-bluesky/issues/773
|
|
41
|
+
if parameters:
|
|
42
|
+
yield from load_centre_collect_full(composite, parameters)
|
|
43
|
+
else:
|
|
44
|
+
yield from bps.mv(baton.requested_user, NO_USER)
|
|
45
|
+
|
|
46
|
+
yield from bpp.contingency_wrapper(
|
|
47
|
+
inner_loop(), except_plan=ignore_sample_errors, auto_raise=False
|
|
48
|
+
)
|
|
49
|
+
requested_user = yield from bps.rd(baton.requested_user)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def move_to_default_state():
|
|
53
|
+
# To be filled in in https://github.com/DiamondLightSource/mx-bluesky/issues/396
|
|
54
|
+
yield from bps.null()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def run_udc_when_requested(baton: Baton, composite: LoadCentreCollectComposite):
|
|
58
|
+
"""This will wait for the baton to be handed to hyperion and then run through the
|
|
59
|
+
UDC queue from agamemnon until:
|
|
60
|
+
1. There are no more instructions from agamemnon
|
|
61
|
+
2. There is an error on the beamline
|
|
62
|
+
3. The baton is requested by another party
|
|
63
|
+
|
|
64
|
+
In the case of 1. or 2. hyperion will immediately release the baton. In the case of
|
|
65
|
+
3. the baton will be released after the next collection has finished."""
|
|
66
|
+
|
|
67
|
+
yield from wait_for_hyperion_requested(baton)
|
|
68
|
+
yield from bps.abs_set(baton.current_user, HYPERION_USER)
|
|
69
|
+
|
|
70
|
+
def default_state_then_collect():
|
|
71
|
+
yield from move_to_default_state()
|
|
72
|
+
yield from main_hyperion_loop(baton, composite)
|
|
73
|
+
|
|
74
|
+
def release_baton():
|
|
75
|
+
# If hyperion has given up the baton itself we need to also release requested
|
|
76
|
+
# user so that hyperion doesn't think we're requested again
|
|
77
|
+
requested_user = yield from bps.rd(baton.requested_user)
|
|
78
|
+
if requested_user == HYPERION_USER:
|
|
79
|
+
yield from bps.abs_set(baton.requested_user, NO_USER)
|
|
80
|
+
yield from bps.abs_set(baton.current_user, NO_USER)
|
|
81
|
+
|
|
82
|
+
yield from bpp.contingency_wrapper(
|
|
83
|
+
default_state_then_collect(), final_plan=release_baton
|
|
84
|
+
)
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from enum import Enum
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
from typing import cast
|
|
4
5
|
|
|
5
6
|
import bluesky.plan_stubs as bps
|
|
6
7
|
from bluesky.utils import MsgGenerator
|
|
7
8
|
from dodal.common.beamlines.beamline_utils import get_path_provider
|
|
9
|
+
from dodal.common.types import UpdatingPathProvider
|
|
8
10
|
from dodal.devices.fast_grid_scan import PandAGridScanParams
|
|
9
11
|
from dodal.devices.smargon import Smargon
|
|
10
12
|
from ophyd_async.fastcs.panda import (
|
|
@@ -222,6 +224,8 @@ def set_panda_directory(panda_directory: Path) -> MsgGenerator:
|
|
|
222
224
|
suffix = datetime.now().strftime("_%Y%m%d%H%M%S")
|
|
223
225
|
|
|
224
226
|
async def set_panda_dir():
|
|
225
|
-
await get_path_provider().update(
|
|
227
|
+
await cast(UpdatingPathProvider, get_path_provider()).update(
|
|
228
|
+
directory=panda_directory, suffix=suffix
|
|
229
|
+
)
|
|
226
230
|
|
|
227
231
|
yield from bps.wait_for([set_panda_dir])
|
|
@@ -15,9 +15,6 @@ from mx_bluesky.hyperion.experiment_plans.load_centre_collect_full_plan import (
|
|
|
15
15
|
from mx_bluesky.hyperion.experiment_plans.pin_centre_then_xray_centre_plan import (
|
|
16
16
|
pin_tip_centre_then_xray_centre,
|
|
17
17
|
)
|
|
18
|
-
from mx_bluesky.hyperion.experiment_plans.robot_load_then_centre_plan import (
|
|
19
|
-
robot_load_then_centre,
|
|
20
|
-
)
|
|
21
18
|
from mx_bluesky.hyperion.experiment_plans.rotation_scan_plan import (
|
|
22
19
|
multi_rotation_scan,
|
|
23
20
|
rotation_scan,
|
|
@@ -29,6 +26,5 @@ __all__ = [
|
|
|
29
26
|
"rotation_scan",
|
|
30
27
|
"pin_tip_centre_then_xray_centre",
|
|
31
28
|
"multi_rotation_scan",
|
|
32
|
-
"robot_load_then_centre",
|
|
33
29
|
"load_centre_collect_full",
|
|
34
30
|
]
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import bluesky.plan_stubs as bps
|
|
2
|
-
import bluesky.preprocessors as bpp
|
|
3
2
|
import numpy
|
|
4
3
|
from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue
|
|
5
4
|
from dodal.devices.smargon import Smargon, StubPosition
|
|
@@ -21,17 +20,13 @@ def change_aperture_then_move_to_xtal(
|
|
|
21
20
|
* Change the aperture so that the beam size is comparable to the crystal size
|
|
22
21
|
* Centre on the centre-of-mass
|
|
23
22
|
* Reset the stub offsets if specified by params"""
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
bounding_box_size,
|
|
32
|
-
)
|
|
33
|
-
else:
|
|
34
|
-
LOGGER.warning("No bounding box size received")
|
|
23
|
+
bounding_box_size = numpy.abs(
|
|
24
|
+
best_hit.bounding_box_mm[1] - best_hit.bounding_box_mm[0]
|
|
25
|
+
)
|
|
26
|
+
yield from set_aperture_for_bbox_mm(
|
|
27
|
+
aperture_scatterguard,
|
|
28
|
+
bounding_box_size,
|
|
29
|
+
)
|
|
35
30
|
|
|
36
31
|
# once we have the results, go to the appropriate position
|
|
37
32
|
LOGGER.info("Moving to centre of mass.")
|
|
@@ -52,12 +47,12 @@ def set_aperture_for_bbox_mm(
|
|
|
52
47
|
):
|
|
53
48
|
"""Sets aperture size based on bbox_size.
|
|
54
49
|
|
|
55
|
-
This function determines the aperture size needed to
|
|
50
|
+
This function determines the aperture size needed to accommodate the bounding box
|
|
56
51
|
of a crystal. The x-axis length of the bounding box is used, setting the aperture
|
|
57
52
|
to Medium if this is less than 50um, and Large otherwise.
|
|
58
53
|
|
|
59
54
|
Args:
|
|
60
|
-
aperture_device: The aperture scatter
|
|
55
|
+
aperture_device: The aperture scatter guard device we are controlling.
|
|
61
56
|
bbox_size_mm: The [x,y,z] lengths, in mm, of a bounding box
|
|
62
57
|
containing a crystal. This describes (in no particular order):
|
|
63
58
|
* The maximum width a crystal occupies
|
|
@@ -77,14 +72,4 @@ def set_aperture_for_bbox_mm(
|
|
|
77
72
|
f"Setting aperture to {new_selected_aperture} based on bounding box size {bbox_size_mm}."
|
|
78
73
|
)
|
|
79
74
|
|
|
80
|
-
|
|
81
|
-
@bpp.run_decorator(
|
|
82
|
-
md={
|
|
83
|
-
"subplan_name": "change_aperture",
|
|
84
|
-
"aperture_size": new_selected_aperture.value,
|
|
85
|
-
}
|
|
86
|
-
)
|
|
87
|
-
def set_aperture():
|
|
88
|
-
yield from bps.abs_set(aperture_device, new_selected_aperture)
|
|
89
|
-
|
|
90
|
-
yield from set_aperture()
|
|
75
|
+
yield from bps.abs_set(aperture_device, new_selected_aperture)
|
|
@@ -9,7 +9,6 @@ from mx_bluesky.hyperion.experiment_plans import (
|
|
|
9
9
|
grid_detect_then_xray_centre_plan,
|
|
10
10
|
load_centre_collect_full_plan,
|
|
11
11
|
pin_centre_then_xray_centre_plan,
|
|
12
|
-
robot_load_then_centre_plan,
|
|
13
12
|
)
|
|
14
13
|
from mx_bluesky.hyperion.parameters.gridscan import (
|
|
15
14
|
GridScanWithEdgeDetect,
|
|
@@ -17,7 +16,6 @@ from mx_bluesky.hyperion.parameters.gridscan import (
|
|
|
17
16
|
PinTipCentreThenXrayCentre,
|
|
18
17
|
)
|
|
19
18
|
from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
|
|
20
|
-
from mx_bluesky.hyperion.parameters.robot_load import RobotLoadThenCentre
|
|
21
19
|
from mx_bluesky.hyperion.parameters.rotation import MultiRotationScan, RotationScan
|
|
22
20
|
|
|
23
21
|
|
|
@@ -38,7 +36,6 @@ class ExperimentRegistryEntry(TypedDict):
|
|
|
38
36
|
| MultiRotationScan
|
|
39
37
|
| PinTipCentreThenXrayCentre
|
|
40
38
|
| LoadCentreCollect
|
|
41
|
-
| RobotLoadThenCentre
|
|
42
39
|
]
|
|
43
40
|
|
|
44
41
|
|
|
@@ -59,10 +56,6 @@ PLAN_REGISTRY: dict[str, ExperimentRegistryEntry] = {
|
|
|
59
56
|
"setup": pin_centre_then_xray_centre_plan.create_devices,
|
|
60
57
|
"param_type": PinTipCentreThenXrayCentre,
|
|
61
58
|
},
|
|
62
|
-
"robot_load_then_centre": {
|
|
63
|
-
"setup": robot_load_then_centre_plan.create_devices,
|
|
64
|
-
"param_type": RobotLoadThenCentre,
|
|
65
|
-
},
|
|
66
59
|
"multi_rotation_scan": {
|
|
67
60
|
"setup": rotation_scan_plan.create_devices,
|
|
68
61
|
"param_type": MultiRotationScan,
|