mx-bluesky 1.4.1a0__py3-none-any.whl → 1.4.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mx_bluesky/_version.py +2 -2
- mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +178 -0
- mx_bluesky/beamlines/i24/serial/__init__.py +0 -6
- mx_bluesky/beamlines/i24/serial/dcid.py +125 -151
- mx_bluesky/beamlines/i24/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +1 -1
- mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +88 -43
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +1 -1
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +2 -46
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +85 -122
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +58 -66
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +1 -19
- mx_bluesky/beamlines/i24/serial/parameters/__init__.py +11 -2
- mx_bluesky/beamlines/i24/serial/parameters/constants.py +16 -2
- mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py +94 -19
- mx_bluesky/beamlines/i24/serial/parameters/utils.py +19 -0
- mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +2 -0
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +61 -8
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +81 -40
- mx_bluesky/beamlines/i24/serial/write_nexus.py +66 -67
- mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/aperture_change_callback.py +1 -1
- mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/grid_detection_callback.py +19 -1
- mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/ispyb_callback_base.py +40 -34
- mx_bluesky/{hyperion → common}/external_interaction/callbacks/common/ispyb_mapping.py +4 -4
- mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/logging_callback.py +1 -1
- mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/zocalo_callback.py +14 -9
- mx_bluesky/{hyperion → common}/external_interaction/callbacks/xray_centre/ispyb_callback.py +46 -38
- mx_bluesky/{hyperion → common}/external_interaction/callbacks/xray_centre/ispyb_mapping.py +2 -2
- mx_bluesky/{hyperion → common}/external_interaction/callbacks/xray_centre/nexus_callback.py +20 -15
- mx_bluesky/common/external_interaction/config_server.py +11 -0
- mx_bluesky/common/external_interaction/ispyb/__init__.py +0 -0
- mx_bluesky/{hyperion → common}/external_interaction/ispyb/data_model.py +2 -0
- mx_bluesky/{hyperion → common}/external_interaction/ispyb/exp_eye_store.py +67 -17
- mx_bluesky/{hyperion → common}/external_interaction/ispyb/ispyb_store.py +20 -18
- mx_bluesky/{hyperion → common}/external_interaction/ispyb/ispyb_utils.py +2 -2
- mx_bluesky/common/external_interaction/nexus/__init__.py +0 -0
- mx_bluesky/{hyperion → common}/external_interaction/nexus/nexus_utils.py +21 -6
- mx_bluesky/{hyperion → common}/external_interaction/nexus/write_nexus.py +5 -5
- mx_bluesky/common/external_interaction/test_config_server.py +38 -0
- mx_bluesky/common/parameters/components.py +10 -8
- mx_bluesky/common/parameters/constants.py +6 -0
- mx_bluesky/common/parameters/gridscan.py +102 -53
- mx_bluesky/common/plans/do_fgs.py +4 -4
- mx_bluesky/{hyperion → common/utils}/exceptions.py +27 -1
- mx_bluesky/common/utils/log.py +17 -7
- mx_bluesky/hyperion/__main__.py +15 -14
- mx_bluesky/hyperion/device_setup_plans/check_beamstop.py +27 -0
- mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +34 -37
- mx_bluesky/hyperion/device_setup_plans/manipulate_sample.py +7 -7
- mx_bluesky/hyperion/device_setup_plans/position_detector.py +1 -1
- mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +3 -3
- mx_bluesky/hyperion/device_setup_plans/setup_panda.py +21 -4
- mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +62 -36
- mx_bluesky/hyperion/device_setup_plans/smargon.py +3 -3
- mx_bluesky/hyperion/device_setup_plans/utils.py +4 -0
- mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +8 -8
- mx_bluesky/hyperion/experiment_plans/change_aperture_then_move_plan.py +28 -17
- mx_bluesky/hyperion/experiment_plans/common/xrc_result.py +10 -1
- mx_bluesky/hyperion/experiment_plans/experiment_registry.py +9 -9
- mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +54 -58
- mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +22 -31
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +57 -40
- mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +3 -3
- mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +8 -2
- mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +6 -14
- mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +12 -11
- mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +4 -4
- mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +39 -30
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +36 -18
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +33 -21
- mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +10 -9
- mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +0 -4
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +31 -20
- mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +46 -30
- mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +39 -24
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +25 -24
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +1 -1
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +13 -9
- mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/sample_handling_callback.py +50 -0
- mx_bluesky/hyperion/external_interaction/config_server.py +15 -1
- mx_bluesky/hyperion/parameters/components.py +3 -2
- mx_bluesky/hyperion/parameters/constants.py +1 -0
- mx_bluesky/hyperion/parameters/gridscan.py +56 -89
- mx_bluesky/hyperion/parameters/load_centre_collect.py +51 -6
- mx_bluesky/hyperion/parameters/robot_load.py +40 -0
- mx_bluesky/hyperion/parameters/rotation.py +28 -3
- mx_bluesky/hyperion/utils/context.py +1 -1
- mx_bluesky/hyperion/utils/validation.py +5 -3
- {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.3.dist-info}/METADATA +6 -6
- mx_bluesky-1.4.3.dist-info/RECORD +155 -0
- {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.3.dist-info}/WHEEL +1 -1
- mx_bluesky/common/parameters/robot_load.py +0 -16
- mx_bluesky/hyperion/external_interaction/exceptions.py +0 -13
- mx_bluesky/hyperion/log.py +0 -15
- mx_bluesky-1.4.1a0.dist-info/RECORD +0 -150
- /mx_bluesky/{hyperion/external_interaction/callbacks/xray_centre → common/external_interaction}/__init__.py +0 -0
- /mx_bluesky/{hyperion/external_interaction/ispyb → common/external_interaction/callbacks/common}/__init__.py +0 -0
- /mx_bluesky/{hyperion → common}/external_interaction/callbacks/common/abstract_event.py +0 -0
- /mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/log_uid_tag_callback.py +0 -0
- /mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/plan_reactive_callback.py +0 -0
- /mx_bluesky/{hyperion/external_interaction/nexus → common/external_interaction/callbacks/xray_centre}/__init__.py +0 -0
- /mx_bluesky/{hyperion → common}/utils/utils.py +0 -0
- {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.3.dist-info}/LICENSE +0 -0
- {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.3.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.3.dist-info}/top_level.txt +0 -0
|
@@ -5,10 +5,13 @@ from typing import TYPE_CHECKING
|
|
|
5
5
|
from bluesky.callbacks import CallbackBase
|
|
6
6
|
from dodal.devices.zocalo import ZocaloStartInfo, ZocaloTrigger
|
|
7
7
|
|
|
8
|
-
from mx_bluesky.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
from mx_bluesky.common.parameters.constants import (
|
|
9
|
+
DocDescriptorNames,
|
|
10
|
+
TriggerConstants,
|
|
11
|
+
)
|
|
12
|
+
from mx_bluesky.common.utils.exceptions import ISPyBDepositionNotMade
|
|
13
|
+
from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER
|
|
14
|
+
from mx_bluesky.common.utils.utils import number_of_frames_from_scan_spec
|
|
12
15
|
|
|
13
16
|
if TYPE_CHECKING:
|
|
14
17
|
from event_model.documents import Event, EventDescriptor, RunStart, RunStop
|
|
@@ -39,11 +42,13 @@ class ZocaloCallback(CallbackBase):
|
|
|
39
42
|
self._reset_state()
|
|
40
43
|
|
|
41
44
|
def start(self, doc: RunStart):
|
|
42
|
-
|
|
43
|
-
if triggering_plan := doc.get(
|
|
45
|
+
ISPYB_ZOCALO_CALLBACK_LOGGER.info("Zocalo handler received start document.")
|
|
46
|
+
if triggering_plan := doc.get(TriggerConstants.ZOCALO):
|
|
44
47
|
self.triggering_plan = triggering_plan
|
|
45
48
|
assert isinstance(zocalo_environment := doc.get("zocalo_environment"), str)
|
|
46
|
-
|
|
49
|
+
ISPYB_ZOCALO_CALLBACK_LOGGER.info(
|
|
50
|
+
f"Zocalo environment set to {zocalo_environment}."
|
|
51
|
+
)
|
|
47
52
|
self.zocalo_interactor = ZocaloTrigger(zocalo_environment)
|
|
48
53
|
|
|
49
54
|
if self.triggering_plan and doc.get("subplan_name") == self.triggering_plan:
|
|
@@ -73,7 +78,7 @@ class ZocaloCallback(CallbackBase):
|
|
|
73
78
|
|
|
74
79
|
def event(self, doc: Event) -> Event:
|
|
75
80
|
event_descriptor = self.descriptors[doc["descriptor"]]
|
|
76
|
-
if event_descriptor.get("name") ==
|
|
81
|
+
if event_descriptor.get("name") == DocDescriptorNames.ZOCALO_HW_READ:
|
|
77
82
|
filename = doc["data"]["eiger_odin_file_writer_id"]
|
|
78
83
|
for start_info in self.zocalo_info:
|
|
79
84
|
start_info.filename = filename
|
|
@@ -83,7 +88,7 @@ class ZocaloCallback(CallbackBase):
|
|
|
83
88
|
|
|
84
89
|
def stop(self, doc: RunStop):
|
|
85
90
|
if doc.get("run_start") == self.run_uid:
|
|
86
|
-
|
|
91
|
+
ISPYB_ZOCALO_CALLBACK_LOGGER.info(
|
|
87
92
|
f"Zocalo handler received stop document, for run {doc.get('run_start')}."
|
|
88
93
|
)
|
|
89
94
|
assert self.zocalo_interactor is not None
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import Callable, Sequence
|
|
4
4
|
from time import time
|
|
5
|
-
from typing import TYPE_CHECKING, Any
|
|
5
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
from bluesky import preprocessors as bpp
|
|
@@ -12,57 +12,61 @@ from dodal.devices.zocalo.zocalo_results import (
|
|
|
12
12
|
get_processing_results_from_event,
|
|
13
13
|
)
|
|
14
14
|
|
|
15
|
-
from mx_bluesky.common.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
from mx_bluesky.
|
|
15
|
+
from mx_bluesky.common.external_interaction.callbacks.common.ispyb_callback_base import (
|
|
16
|
+
BaseISPyBCallback,
|
|
17
|
+
)
|
|
18
|
+
from mx_bluesky.common.external_interaction.callbacks.common.ispyb_mapping import (
|
|
19
19
|
populate_data_collection_group,
|
|
20
20
|
populate_remaining_data_collection_info,
|
|
21
21
|
)
|
|
22
|
-
from mx_bluesky.
|
|
23
|
-
BaseISPyBCallback,
|
|
24
|
-
)
|
|
25
|
-
from mx_bluesky.hyperion.external_interaction.callbacks.logging_callback import (
|
|
22
|
+
from mx_bluesky.common.external_interaction.callbacks.common.logging_callback import (
|
|
26
23
|
format_doc_for_log,
|
|
27
24
|
)
|
|
28
|
-
from mx_bluesky.
|
|
25
|
+
from mx_bluesky.common.external_interaction.callbacks.xray_centre.ispyb_mapping import (
|
|
29
26
|
construct_comment_for_gridscan,
|
|
30
27
|
populate_xy_data_collection_info,
|
|
31
28
|
populate_xz_data_collection_info,
|
|
32
29
|
)
|
|
33
|
-
from mx_bluesky.
|
|
34
|
-
from mx_bluesky.hyperion.external_interaction.ispyb.data_model import (
|
|
30
|
+
from mx_bluesky.common.external_interaction.ispyb.data_model import (
|
|
35
31
|
DataCollectionGridInfo,
|
|
36
32
|
DataCollectionInfo,
|
|
37
33
|
DataCollectionPositionInfo,
|
|
38
34
|
Orientation,
|
|
39
35
|
ScanDataInfo,
|
|
40
36
|
)
|
|
41
|
-
from mx_bluesky.
|
|
37
|
+
from mx_bluesky.common.external_interaction.ispyb.ispyb_store import (
|
|
42
38
|
IspybIds,
|
|
43
39
|
StoreInIspyb,
|
|
44
40
|
)
|
|
45
|
-
from mx_bluesky.
|
|
46
|
-
from mx_bluesky.
|
|
47
|
-
from mx_bluesky.
|
|
41
|
+
from mx_bluesky.common.parameters.components import DiffractionExperimentWithSample
|
|
42
|
+
from mx_bluesky.common.parameters.constants import DocDescriptorNames, PlanNameConstants
|
|
43
|
+
from mx_bluesky.common.parameters.gridscan import (
|
|
48
44
|
GridCommon,
|
|
49
45
|
)
|
|
46
|
+
from mx_bluesky.common.utils.exceptions import ISPyBDepositionNotMade
|
|
47
|
+
from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER, set_dcgid_tag
|
|
50
48
|
|
|
51
49
|
if TYPE_CHECKING:
|
|
52
50
|
from event_model import Event, RunStart, RunStop
|
|
53
51
|
|
|
54
52
|
|
|
55
53
|
def ispyb_activation_wrapper(plan_generator: MsgGenerator, parameters):
|
|
56
|
-
return bpp.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
return bpp.set_run_key_wrapper(
|
|
55
|
+
bpp.run_wrapper(
|
|
56
|
+
plan_generator,
|
|
57
|
+
md={
|
|
58
|
+
"activate_callbacks": ["GridscanISPyBCallback"],
|
|
59
|
+
"subplan_name": PlanNameConstants.GRID_DETECT_AND_DO_GRIDSCAN,
|
|
60
|
+
"mx_bluesky_parameters": parameters.model_dump_json(),
|
|
61
|
+
},
|
|
62
|
+
),
|
|
63
|
+
PlanNameConstants.ISPYB_ACTIVATION,
|
|
63
64
|
)
|
|
64
65
|
|
|
65
66
|
|
|
67
|
+
T = TypeVar("T", bound="GridCommon")
|
|
68
|
+
|
|
69
|
+
|
|
66
70
|
class GridscanISPyBCallback(BaseISPyBCallback):
|
|
67
71
|
"""Callback class to handle the deposition of experiment parameters into the ISPyB
|
|
68
72
|
database. Listens for 'event' and 'descriptor' documents. Creates the ISpyB entry on
|
|
@@ -80,27 +84,29 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
80
84
|
|
|
81
85
|
def __init__(
|
|
82
86
|
self,
|
|
87
|
+
param_type: type[T],
|
|
83
88
|
*,
|
|
84
89
|
emit: Callable[..., Any] | None = None,
|
|
85
90
|
) -> None:
|
|
86
91
|
super().__init__(emit=emit)
|
|
87
92
|
self.ispyb: StoreInIspyb
|
|
88
93
|
self.ispyb_ids: IspybIds = IspybIds()
|
|
94
|
+
self.param_type = param_type
|
|
89
95
|
self._start_of_fgs_uid: str | None = None
|
|
90
96
|
self._processing_start_time: float | None = None
|
|
91
97
|
|
|
92
98
|
def activity_gated_start(self, doc: RunStart):
|
|
93
99
|
if doc.get("subplan_name") == PlanNameConstants.DO_FGS:
|
|
94
100
|
self._start_of_fgs_uid = doc.get("uid")
|
|
95
|
-
if doc.get("subplan_name") ==
|
|
101
|
+
if doc.get("subplan_name") == PlanNameConstants.GRID_DETECT_AND_DO_GRIDSCAN:
|
|
96
102
|
self.uid_to_finalize_on = doc.get("uid")
|
|
97
|
-
|
|
103
|
+
ISPYB_ZOCALO_CALLBACK_LOGGER.info(
|
|
98
104
|
"ISPyB callback received start document with experiment parameters and "
|
|
99
105
|
f"uid: {self.uid_to_finalize_on}"
|
|
100
106
|
)
|
|
101
|
-
|
|
102
|
-
assert isinstance(
|
|
103
|
-
self.params =
|
|
107
|
+
mx_bluesky_parameters = doc.get("mx_bluesky_parameters")
|
|
108
|
+
assert isinstance(mx_bluesky_parameters, str)
|
|
109
|
+
self.params = self.param_type.model_validate_json(mx_bluesky_parameters)
|
|
104
110
|
self.ispyb = StoreInIspyb(self.ispyb_config)
|
|
105
111
|
data_collection_group_info = populate_data_collection_group(self.params)
|
|
106
112
|
|
|
@@ -137,7 +143,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
137
143
|
descriptor_name = self.descriptors[doc["descriptor"]].get("name")
|
|
138
144
|
if descriptor_name == ZOCALO_READING_PLAN_NAME:
|
|
139
145
|
self._handle_zocalo_read_event(doc)
|
|
140
|
-
elif descriptor_name ==
|
|
146
|
+
elif descriptor_name == DocDescriptorNames.OAV_GRID_SNAPSHOT_TRIGGERED:
|
|
141
147
|
scan_data_infos = self._handle_oav_grid_snapshot_triggered(doc)
|
|
142
148
|
self.ispyb_ids = self.ispyb.update_deposition(
|
|
143
149
|
self.ispyb_ids, scan_data_infos
|
|
@@ -151,7 +157,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
151
157
|
proc_time = time() - self._processing_start_time
|
|
152
158
|
crystal_summary = f"Zocalo processing took {proc_time:.2f} s. "
|
|
153
159
|
bboxes: list[np.ndarray] = []
|
|
154
|
-
|
|
160
|
+
ISPYB_ZOCALO_CALLBACK_LOGGER.info(
|
|
155
161
|
f"Amending comment based on Zocalo reading doc: {format_doc_for_log(doc)}"
|
|
156
162
|
)
|
|
157
163
|
|
|
@@ -173,9 +179,9 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
173
179
|
)
|
|
174
180
|
else:
|
|
175
181
|
crystal_summary += "Zocalo found no crystals in this gridscan."
|
|
176
|
-
assert (
|
|
177
|
-
|
|
178
|
-
)
|
|
182
|
+
assert self.ispyb_ids.data_collection_ids, (
|
|
183
|
+
"No data collection to add results to"
|
|
184
|
+
)
|
|
179
185
|
self.ispyb.append_to_comment(
|
|
180
186
|
self.ispyb_ids.data_collection_ids[0], crystal_summary
|
|
181
187
|
)
|
|
@@ -222,7 +228,9 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
222
228
|
data_collection_id=data_collection_id,
|
|
223
229
|
data_collection_grid_info=data_collection_grid_info,
|
|
224
230
|
)
|
|
225
|
-
|
|
231
|
+
ISPYB_ZOCALO_CALLBACK_LOGGER.info(
|
|
232
|
+
"Updating ispyb data collection after oav snapshot."
|
|
233
|
+
)
|
|
226
234
|
self._oav_snapshot_event_idx += 1
|
|
227
235
|
return [scan_data_info]
|
|
228
236
|
|
|
@@ -242,9 +250,9 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
242
250
|
event_sourced_position_info: DataCollectionPositionInfo | None,
|
|
243
251
|
params: DiffractionExperimentWithSample,
|
|
244
252
|
) -> Sequence[ScanDataInfo]:
|
|
245
|
-
assert (
|
|
246
|
-
|
|
247
|
-
)
|
|
253
|
+
assert self.ispyb_ids.data_collection_ids, (
|
|
254
|
+
"Expect at least one valid data collection to record scan data"
|
|
255
|
+
)
|
|
248
256
|
xy_scan_data_info = ScanDataInfo(
|
|
249
257
|
data_collection_info=event_sourced_data_collection_info,
|
|
250
258
|
data_collection_id=self.ispyb_ids.data_collection_ids[0],
|
|
@@ -267,7 +275,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
267
275
|
if doc.get("run_start") == self._start_of_fgs_uid:
|
|
268
276
|
self._processing_start_time = time()
|
|
269
277
|
if doc.get("run_start") == self.uid_to_finalize_on:
|
|
270
|
-
|
|
278
|
+
ISPYB_ZOCALO_CALLBACK_LOGGER.info(
|
|
271
279
|
"ISPyB callback received stop document corresponding to start document "
|
|
272
280
|
f"with uid: {self.uid_to_finalize_on}."
|
|
273
281
|
)
|
|
@@ -4,7 +4,7 @@ import numpy
|
|
|
4
4
|
from dodal.devices.detector import DetectorParams
|
|
5
5
|
from dodal.devices.oav import utils as oav_utils
|
|
6
6
|
|
|
7
|
-
from mx_bluesky.
|
|
7
|
+
from mx_bluesky.common.external_interaction.ispyb.data_model import (
|
|
8
8
|
DataCollectionGridInfo,
|
|
9
9
|
DataCollectionInfo,
|
|
10
10
|
)
|
|
@@ -43,7 +43,7 @@ def construct_comment_for_gridscan(grid_info: DataCollectionGridInfo) -> str:
|
|
|
43
43
|
grid_info.microns_per_pixel_y,
|
|
44
44
|
)
|
|
45
45
|
return (
|
|
46
|
-
"
|
|
46
|
+
"MX-Bluesky: Xray centring - Diffraction grid scan of "
|
|
47
47
|
f"{grid_info.steps_x} by "
|
|
48
48
|
f"{grid_info.steps_y} images in "
|
|
49
49
|
f"{(grid_info.dx_in_mm * 1e3):.1f} um by "
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING, TypeVar
|
|
4
4
|
|
|
5
|
-
from mx_bluesky.
|
|
5
|
+
from mx_bluesky.common.external_interaction.callbacks.common.plan_reactive_callback import (
|
|
6
6
|
PlanReactiveCallback,
|
|
7
7
|
)
|
|
8
|
-
from mx_bluesky.
|
|
8
|
+
from mx_bluesky.common.external_interaction.nexus.nexus_utils import (
|
|
9
9
|
create_beam_and_attenuator_parameters,
|
|
10
10
|
vds_type_based_on_bit_depth,
|
|
11
11
|
)
|
|
12
|
-
from mx_bluesky.
|
|
13
|
-
from mx_bluesky.
|
|
14
|
-
from mx_bluesky.
|
|
15
|
-
|
|
12
|
+
from mx_bluesky.common.external_interaction.nexus.write_nexus import NexusWriter
|
|
13
|
+
from mx_bluesky.common.parameters.constants import DocDescriptorNames, PlanNameConstants
|
|
14
|
+
from mx_bluesky.common.parameters.gridscan import (
|
|
15
|
+
SpecifiedThreeDGridScan,
|
|
16
|
+
)
|
|
17
|
+
from mx_bluesky.common.utils.log import NEXUS_LOGGER
|
|
16
18
|
|
|
17
19
|
if TYPE_CHECKING:
|
|
18
20
|
from event_model.documents import Event, EventDescriptor, RunStart
|
|
19
21
|
|
|
22
|
+
T = TypeVar("T", bound="SpecifiedThreeDGridScan")
|
|
23
|
+
|
|
20
24
|
|
|
21
25
|
class GridscanNexusFileCallback(PlanReactiveCallback):
|
|
22
26
|
"""Callback class to handle the creation of Nexus files based on experiment \
|
|
@@ -35,8 +39,9 @@ class GridscanNexusFileCallback(PlanReactiveCallback):
|
|
|
35
39
|
See: https://blueskyproject.io/bluesky/callbacks.html#ways-to-invoke-callbacks
|
|
36
40
|
"""
|
|
37
41
|
|
|
38
|
-
def __init__(self) -> None:
|
|
42
|
+
def __init__(self, param_type: type[T]) -> None:
|
|
39
43
|
super().__init__(NEXUS_LOGGER)
|
|
44
|
+
self.param_type = param_type
|
|
40
45
|
self.run_start_uid: str | None = None
|
|
41
46
|
self.nexus_writer_1: NexusWriter | None = None
|
|
42
47
|
self.nexus_writer_2: NexusWriter | None = None
|
|
@@ -44,13 +49,13 @@ class GridscanNexusFileCallback(PlanReactiveCallback):
|
|
|
44
49
|
self.log = NEXUS_LOGGER
|
|
45
50
|
|
|
46
51
|
def activity_gated_start(self, doc: RunStart):
|
|
47
|
-
if doc.get("subplan_name") ==
|
|
48
|
-
|
|
49
|
-
assert isinstance(
|
|
52
|
+
if doc.get("subplan_name") == PlanNameConstants.GRIDSCAN_OUTER:
|
|
53
|
+
mx_bluesky_parameters = doc.get("mx_bluesky_parameters")
|
|
54
|
+
assert isinstance(mx_bluesky_parameters, str)
|
|
50
55
|
NEXUS_LOGGER.info(
|
|
51
|
-
f"Nexus writer received start document with experiment parameters {
|
|
56
|
+
f"Nexus writer received start document with experiment parameters {mx_bluesky_parameters}"
|
|
52
57
|
)
|
|
53
|
-
parameters =
|
|
58
|
+
parameters = self.param_type.model_validate_json(mx_bluesky_parameters)
|
|
54
59
|
d_size = parameters.detector_params.detector_size_constants.det_size_pixels
|
|
55
60
|
grid_n_img_1 = parameters.scan_indices[1]
|
|
56
61
|
grid_n_img_2 = parameters.num_images - grid_n_img_1
|
|
@@ -75,7 +80,7 @@ class GridscanNexusFileCallback(PlanReactiveCallback):
|
|
|
75
80
|
|
|
76
81
|
def activity_gated_event(self, doc: Event) -> Event | None:
|
|
77
82
|
assert (event_descriptor := self.descriptors.get(doc["descriptor"])) is not None
|
|
78
|
-
if event_descriptor.get("name") ==
|
|
83
|
+
if event_descriptor.get("name") == DocDescriptorNames.HARDWARE_READ_DURING:
|
|
79
84
|
data = doc["data"]
|
|
80
85
|
for nexus_writer in [self.nexus_writer_1, self.nexus_writer_2]:
|
|
81
86
|
assert nexus_writer, "Nexus callback did not receive start doc"
|
|
@@ -84,7 +89,7 @@ class GridscanNexusFileCallback(PlanReactiveCallback):
|
|
|
84
89
|
nexus_writer.attenuator,
|
|
85
90
|
) = create_beam_and_attenuator_parameters(
|
|
86
91
|
data["dcm-energy_in_kev"],
|
|
87
|
-
data["
|
|
92
|
+
data["flux-flux_reading"],
|
|
88
93
|
data["attenuator-actual_transmission"],
|
|
89
94
|
)
|
|
90
95
|
vds_data_type = vds_type_based_on_bit_depth(
|
|
@@ -29,8 +29,19 @@ class FeatureFlags(BaseModel, ABC):
|
|
|
29
29
|
def mark_overridden_features(cls, values):
|
|
30
30
|
assert isinstance(values, dict)
|
|
31
31
|
values["overriden_features"] = values.copy()
|
|
32
|
+
cls._validate_overridden_features(values)
|
|
32
33
|
return values
|
|
33
34
|
|
|
35
|
+
@classmethod
|
|
36
|
+
def _validate_overridden_features(cls, values: dict):
|
|
37
|
+
"""Validates overridden features to ensure they are defined in the model fields."""
|
|
38
|
+
defined_fields = cls.model_fields.keys()
|
|
39
|
+
invalid_features = [key for key in values.keys() if key not in defined_fields]
|
|
40
|
+
|
|
41
|
+
if invalid_features:
|
|
42
|
+
message = f"Invalid feature toggle(s) supplied: {invalid_features}. "
|
|
43
|
+
raise ValueError(message)
|
|
44
|
+
|
|
34
45
|
def _get_flags(self):
|
|
35
46
|
flags = type(self).get_config_server().best_effort_get_all_feature_flags()
|
|
36
47
|
return {f: flags[f] for f in flags if f in self.model_fields.keys()}
|
|
File without changes
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import configparser
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from enum import StrEnum
|
|
2
4
|
|
|
3
5
|
from requests import patch, post
|
|
4
6
|
from requests.auth import AuthBase
|
|
5
7
|
|
|
6
|
-
from mx_bluesky.
|
|
7
|
-
from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_utils import (
|
|
8
|
+
from mx_bluesky.common.external_interaction.ispyb.ispyb_utils import (
|
|
8
9
|
get_current_time_string,
|
|
9
10
|
get_ispyb_config,
|
|
10
11
|
)
|
|
12
|
+
from mx_bluesky.common.utils.exceptions import ISPyBDepositionNotMade
|
|
11
13
|
|
|
12
14
|
RobotActionID = int
|
|
13
15
|
|
|
@@ -29,20 +31,44 @@ def _get_base_url_and_token() -> tuple[str, str]:
|
|
|
29
31
|
return expeye_config["url"], expeye_config["token"]
|
|
30
32
|
|
|
31
33
|
|
|
34
|
+
def _send_and_get_response(auth, url, data, send_func) -> dict:
|
|
35
|
+
response = send_func(url, auth=auth, json=data)
|
|
36
|
+
if not response.ok:
|
|
37
|
+
raise ISPyBDepositionNotMade(f"Could not write {data} to {url}: {response}")
|
|
38
|
+
return response.json()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class BLSample:
|
|
43
|
+
container_id: int
|
|
44
|
+
bl_sample_id: int
|
|
45
|
+
bl_sample_status: str | None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class BLSampleStatus(StrEnum):
|
|
49
|
+
# The sample has been loaded
|
|
50
|
+
LOADED = "LOADED"
|
|
51
|
+
# Problem with the sample e.g. pin too long/short
|
|
52
|
+
ERROR_SAMPLE = "ERROR - sample"
|
|
53
|
+
# Any other general error
|
|
54
|
+
ERROR_BEAMLINE = "ERROR - beamline"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
assert all(len(value) <= 20 for value in BLSampleStatus), (
|
|
58
|
+
"Column size limit of 20 for BLSampleStatus"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
32
62
|
class ExpeyeInteraction:
|
|
63
|
+
"""Exposes functionality from the Expeye core API"""
|
|
64
|
+
|
|
33
65
|
CREATE_ROBOT_ACTION = "/proposals/{proposal}/sessions/{visit_number}/robot-actions"
|
|
34
66
|
UPDATE_ROBOT_ACTION = "/robot-actions/{action_id}"
|
|
35
67
|
|
|
36
68
|
def __init__(self) -> None:
|
|
37
69
|
url, token = _get_base_url_and_token()
|
|
38
|
-
self.
|
|
39
|
-
self.
|
|
40
|
-
|
|
41
|
-
def _send_and_get_response(self, url, data, send_func) -> dict:
|
|
42
|
-
response = send_func(url, auth=self.auth, json=data)
|
|
43
|
-
if not response.ok:
|
|
44
|
-
raise ISPyBDepositionNotMade(f"Could not write {data} to {url}: {response}")
|
|
45
|
-
return response.json()
|
|
70
|
+
self._base_url = url
|
|
71
|
+
self._auth = BearerAuth(token)
|
|
46
72
|
|
|
47
73
|
def start_load(
|
|
48
74
|
self,
|
|
@@ -66,7 +92,7 @@ class ExpeyeInteraction:
|
|
|
66
92
|
Returns:
|
|
67
93
|
RobotActionID: The id of the robot load action that is created
|
|
68
94
|
"""
|
|
69
|
-
url = self.
|
|
95
|
+
url = self._base_url + self.CREATE_ROBOT_ACTION.format(
|
|
70
96
|
proposal=proposal_reference, visit_number=visit_number
|
|
71
97
|
)
|
|
72
98
|
|
|
@@ -77,7 +103,7 @@ class ExpeyeInteraction:
|
|
|
77
103
|
"containerLocation": container_location,
|
|
78
104
|
"dewarLocation": dewar_location,
|
|
79
105
|
}
|
|
80
|
-
response = self.
|
|
106
|
+
response = _send_and_get_response(self._auth, url, data, post)
|
|
81
107
|
return response["robotActionId"]
|
|
82
108
|
|
|
83
109
|
def update_barcode_and_snapshots(
|
|
@@ -95,14 +121,14 @@ class ExpeyeInteraction:
|
|
|
95
121
|
snapshot_before_path (str): Path to the snapshot before robot load
|
|
96
122
|
snapshot_after_path (str): Path to the snapshot after robot load
|
|
97
123
|
"""
|
|
98
|
-
url = self.
|
|
124
|
+
url = self._base_url + self.UPDATE_ROBOT_ACTION.format(action_id=action_id)
|
|
99
125
|
|
|
100
126
|
data = {
|
|
101
127
|
"sampleBarcode": barcode,
|
|
102
128
|
"xtalSnapshotBefore": snapshot_before_path,
|
|
103
129
|
"xtalSnapshotAfter": snapshot_after_path,
|
|
104
130
|
}
|
|
105
|
-
self.
|
|
131
|
+
_send_and_get_response(self._auth, url, data, patch)
|
|
106
132
|
|
|
107
133
|
def end_load(self, action_id: RobotActionID, status: str, reason: str):
|
|
108
134
|
"""Finish an existing robot action, providing final information about how it went
|
|
@@ -113,13 +139,37 @@ class ExpeyeInteraction:
|
|
|
113
139
|
otherwise error
|
|
114
140
|
reason (str): If the status is in error than the reason for that error
|
|
115
141
|
"""
|
|
116
|
-
url = self.
|
|
142
|
+
url = self._base_url + self.UPDATE_ROBOT_ACTION.format(action_id=action_id)
|
|
117
143
|
|
|
118
144
|
run_status = "SUCCESS" if status == "success" else "ERROR"
|
|
119
145
|
|
|
120
146
|
data = {
|
|
121
147
|
"endTimestamp": get_current_time_string(),
|
|
122
148
|
"status": run_status,
|
|
123
|
-
"message": reason,
|
|
149
|
+
"message": reason[:255] if reason else "",
|
|
124
150
|
}
|
|
125
|
-
self.
|
|
151
|
+
_send_and_get_response(self._auth, url, data, patch)
|
|
152
|
+
|
|
153
|
+
def update_sample_status(
|
|
154
|
+
self, bl_sample_id: int, bl_sample_status: BLSampleStatus
|
|
155
|
+
) -> BLSample:
|
|
156
|
+
"""Update the blSampleStatus of a sample.
|
|
157
|
+
Args:
|
|
158
|
+
bl_sample_id: The sample ID
|
|
159
|
+
bl_sample_status: The sample status
|
|
160
|
+
status_message: An optional message
|
|
161
|
+
Returns:
|
|
162
|
+
The updated sample
|
|
163
|
+
"""
|
|
164
|
+
data = {"blSampleStatus": (str(bl_sample_status))}
|
|
165
|
+
response = _send_and_get_response(
|
|
166
|
+
self._auth, self._base_url + f"/samples/{bl_sample_id}", data, patch
|
|
167
|
+
)
|
|
168
|
+
return self._sample_from_json(response)
|
|
169
|
+
|
|
170
|
+
def _sample_from_json(self, response) -> BLSample:
|
|
171
|
+
return BLSample(
|
|
172
|
+
bl_sample_id=response["blSampleId"],
|
|
173
|
+
bl_sample_status=response["blSampleStatus"],
|
|
174
|
+
container_id=response["containerId"],
|
|
175
|
+
)
|
|
@@ -11,18 +11,18 @@ from ispyb.sp.mxacquisition import MXAcquisition
|
|
|
11
11
|
from ispyb.strictordereddict import StrictOrderedDict
|
|
12
12
|
from pydantic import BaseModel
|
|
13
13
|
|
|
14
|
-
from mx_bluesky.common.
|
|
15
|
-
from mx_bluesky.hyperion.external_interaction.ispyb.data_model import (
|
|
14
|
+
from mx_bluesky.common.external_interaction.ispyb.data_model import (
|
|
16
15
|
DataCollectionGridInfo,
|
|
17
16
|
DataCollectionGroupInfo,
|
|
18
17
|
DataCollectionInfo,
|
|
19
18
|
ScanDataInfo,
|
|
20
19
|
)
|
|
21
|
-
from mx_bluesky.
|
|
20
|
+
from mx_bluesky.common.external_interaction.ispyb.ispyb_utils import (
|
|
22
21
|
get_current_time_string,
|
|
23
22
|
get_session_id_from_visit,
|
|
24
23
|
)
|
|
25
|
-
from mx_bluesky.
|
|
24
|
+
from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER
|
|
25
|
+
from mx_bluesky.common.utils.tracing import TRACER
|
|
26
26
|
|
|
27
27
|
if TYPE_CHECKING:
|
|
28
28
|
pass
|
|
@@ -62,12 +62,12 @@ class StoreInIspyb:
|
|
|
62
62
|
ispyb_ids,
|
|
63
63
|
scan_data_infos: Sequence[ScanDataInfo],
|
|
64
64
|
) -> IspybIds:
|
|
65
|
-
assert (
|
|
66
|
-
|
|
67
|
-
)
|
|
68
|
-
assert (
|
|
69
|
-
|
|
70
|
-
)
|
|
65
|
+
assert ispyb_ids.data_collection_group_id, (
|
|
66
|
+
"Attempted to store scan data without a collection group"
|
|
67
|
+
)
|
|
68
|
+
assert ispyb_ids.data_collection_ids, (
|
|
69
|
+
"Attempted to store scan data without a collection"
|
|
70
|
+
)
|
|
71
71
|
return self._begin_or_update_deposition(ispyb_ids, None, scan_data_infos)
|
|
72
72
|
|
|
73
73
|
def _begin_or_update_deposition(
|
|
@@ -87,7 +87,9 @@ class StoreInIspyb:
|
|
|
87
87
|
)
|
|
88
88
|
)
|
|
89
89
|
else:
|
|
90
|
-
assert ispyb_ids.data_collection_group_id,
|
|
90
|
+
assert ispyb_ids.data_collection_group_id, (
|
|
91
|
+
"Attempt to update data collection without a data collection group ID"
|
|
92
|
+
)
|
|
91
93
|
|
|
92
94
|
grid_ids = list(ispyb_ids.grid_ids)
|
|
93
95
|
data_collection_ids_out = list(ispyb_ids.data_collection_ids)
|
|
@@ -116,15 +118,15 @@ class StoreInIspyb:
|
|
|
116
118
|
return ispyb_ids
|
|
117
119
|
|
|
118
120
|
def end_deposition(self, ispyb_ids: IspybIds, success: str, reason: str):
|
|
119
|
-
assert (
|
|
120
|
-
|
|
121
|
-
)
|
|
122
|
-
assert (
|
|
123
|
-
|
|
124
|
-
)
|
|
121
|
+
assert ispyb_ids.data_collection_ids, (
|
|
122
|
+
"Can't end ISPyB deposition, data_collection IDs are missing"
|
|
123
|
+
)
|
|
124
|
+
assert ispyb_ids.data_collection_group_id is not None, (
|
|
125
|
+
"Cannot end ISPyB deposition without data collection group ID"
|
|
126
|
+
)
|
|
125
127
|
|
|
126
128
|
for id_ in ispyb_ids.data_collection_ids:
|
|
127
|
-
|
|
129
|
+
ISPYB_ZOCALO_CALLBACK_LOGGER.info(
|
|
128
130
|
f"End ispyb deposition with status '{success}' and reason '{reason}'."
|
|
129
131
|
)
|
|
130
132
|
if success == "fail" or success == "abort":
|
|
@@ -7,11 +7,11 @@ from ispyb import NoResult
|
|
|
7
7
|
from ispyb.connector.mysqlsp.main import ISPyBMySQLSPConnector as Connector
|
|
8
8
|
from ispyb.sp.core import Core
|
|
9
9
|
|
|
10
|
-
from mx_bluesky.
|
|
10
|
+
from mx_bluesky.common.parameters.constants import SimConstants
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def get_ispyb_config():
|
|
14
|
-
return os.environ.get("ISPYB_CONFIG_PATH",
|
|
14
|
+
return os.environ.get("ISPYB_CONFIG_PATH", SimConstants.ISPYB_CONFIG)
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def get_session_id_from_visit(conn: Connector, visit: str):
|
|
File without changes
|