mx-bluesky 1.5.2__py3-none-any.whl → 1.5.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 +16 -3
- mx_bluesky/beamlines/i04/__init__.py +7 -3
- mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +3 -0
- mx_bluesky/beamlines/i24/serial/blueapi_config.yaml +2 -2
- mx_bluesky/common/experiment_plans/oav_grid_detection_plan.py +12 -2
- mx_bluesky/common/external_interaction/alerting/__init__.py +13 -0
- mx_bluesky/common/external_interaction/alerting/_service.py +82 -0
- mx_bluesky/common/external_interaction/alerting/log_based_service.py +57 -0
- mx_bluesky/common/external_interaction/callbacks/sample_handling/sample_handling_callback.py +28 -4
- mx_bluesky/common/external_interaction/config_server.py +151 -54
- mx_bluesky/common/parameters/constants.py +26 -8
- mx_bluesky/common/parameters/gridscan.py +1 -1
- mx_bluesky/hyperion/__main__.py +50 -178
- mx_bluesky/hyperion/baton_handler.py +125 -69
- mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +29 -24
- mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +4 -1
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +12 -4
- mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +1 -1
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +2 -3
- mx_bluesky/hyperion/external_interaction/agamemnon.py +128 -73
- mx_bluesky/hyperion/external_interaction/alerting/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/alerting/constants.py +12 -0
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +5 -0
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +2 -2
- mx_bluesky/hyperion/external_interaction/config_server.py +12 -31
- mx_bluesky/hyperion/parameters/cli.py +15 -3
- mx_bluesky/hyperion/parameters/components.py +7 -5
- mx_bluesky/hyperion/parameters/constants.py +20 -4
- mx_bluesky/hyperion/parameters/gridscan.py +22 -14
- mx_bluesky/hyperion/parameters/load_centre_collect.py +1 -14
- mx_bluesky/hyperion/parameters/robot_load.py +1 -4
- mx_bluesky/hyperion/parameters/rotation.py +1 -2
- mx_bluesky/hyperion/plan_runner.py +78 -0
- mx_bluesky/hyperion/runner.py +189 -0
- {mx_bluesky-1.5.2.dist-info → mx_bluesky-1.5.3.dist-info}/METADATA +4 -3
- {mx_bluesky-1.5.2.dist-info → mx_bluesky-1.5.3.dist-info}/RECORD +40 -33
- {mx_bluesky-1.5.2.dist-info → mx_bluesky-1.5.3.dist-info}/entry_points.txt +0 -2
- {mx_bluesky-1.5.2.dist-info → mx_bluesky-1.5.3.dist-info}/WHEEL +0 -0
- {mx_bluesky-1.5.2.dist-info → mx_bluesky-1.5.3.dist-info}/licenses/LICENSE +0 -0
- {mx_bluesky-1.5.2.dist-info → mx_bluesky-1.5.3.dist-info}/top_level.txt +0 -0
|
@@ -24,7 +24,10 @@ from mx_bluesky.hyperion.experiment_plans.rotation_scan_plan import (
|
|
|
24
24
|
RotationScanComposite,
|
|
25
25
|
rotation_scan_internal,
|
|
26
26
|
)
|
|
27
|
-
from mx_bluesky.hyperion.
|
|
27
|
+
from mx_bluesky.hyperion.external_interaction.config_server import (
|
|
28
|
+
get_hyperion_config_client,
|
|
29
|
+
)
|
|
30
|
+
from mx_bluesky.hyperion.parameters.constants import CONST, I03Constants
|
|
28
31
|
from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
|
|
29
32
|
from mx_bluesky.hyperion.parameters.rotation import RotationScanPerSweep
|
|
30
33
|
|
|
@@ -51,7 +54,8 @@ def load_centre_collect_full(
|
|
|
51
54
|
* If X-ray centring finds a diffracting centre then move to that centre and
|
|
52
55
|
* do a collection with the specified parameters.
|
|
53
56
|
"""
|
|
54
|
-
|
|
57
|
+
|
|
58
|
+
get_hyperion_config_client().refresh_cache()
|
|
55
59
|
|
|
56
60
|
if not oav_params:
|
|
57
61
|
oav_params = OAVParameters(context="xrayCentring")
|
|
@@ -60,7 +64,11 @@ def load_centre_collect_full(
|
|
|
60
64
|
@set_run_key_decorator(CONST.PLAN.LOAD_CENTRE_COLLECT)
|
|
61
65
|
@run_decorator(
|
|
62
66
|
md={
|
|
63
|
-
"metadata": {
|
|
67
|
+
"metadata": {
|
|
68
|
+
"sample_id": parameters.sample_id,
|
|
69
|
+
"visit": parameters.visit,
|
|
70
|
+
"container": parameters.sample_puck,
|
|
71
|
+
},
|
|
64
72
|
"activate_callbacks": ["BeamDrawingCallback", "SampleHandlingCallback"],
|
|
65
73
|
"with_snapshot": parameters.multi_rotation_scan.model_dump_json(
|
|
66
74
|
include=WithSnapshot.model_fields.keys() # type: ignore
|
|
@@ -117,7 +125,7 @@ def load_centre_collect_full(
|
|
|
117
125
|
|
|
118
126
|
multi_rotation.rotation_scans.clear()
|
|
119
127
|
|
|
120
|
-
is_alternating =
|
|
128
|
+
is_alternating = I03Constants.ALTERNATE_ROTATION_DIRECTION
|
|
121
129
|
|
|
122
130
|
generator = rotation_scan_generator(is_alternating)
|
|
123
131
|
next(generator)
|
|
@@ -155,7 +155,7 @@ def pin_tip_centre_plan(
|
|
|
155
155
|
tip = yield from move_pin_into_view(pin_tip_detect, smargon)
|
|
156
156
|
yield from offset_and_move(tip)
|
|
157
157
|
|
|
158
|
-
yield from bps.mvr(smargon.omega, 90)
|
|
158
|
+
yield from bps.mvr(smargon.omega, -90)
|
|
159
159
|
|
|
160
160
|
# need to wait for the OAV image to update
|
|
161
161
|
# See #673 for improvements
|
|
@@ -58,7 +58,7 @@ from mx_bluesky.hyperion.device_setup_plans.setup_zebra import (
|
|
|
58
58
|
setup_zebra_for_rotation,
|
|
59
59
|
tidy_up_zebra_after_rotation_scan,
|
|
60
60
|
)
|
|
61
|
-
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
61
|
+
from mx_bluesky.hyperion.parameters.constants import CONST, I03Constants
|
|
62
62
|
from mx_bluesky.hyperion.parameters.rotation import (
|
|
63
63
|
RotationScan,
|
|
64
64
|
SingleRotationScan,
|
|
@@ -133,7 +133,7 @@ def calculate_motion_profile(
|
|
|
133
133
|
direction = params.rotation_direction
|
|
134
134
|
start_scan_deg = params.omega_start_deg
|
|
135
135
|
|
|
136
|
-
if
|
|
136
|
+
if I03Constants.OMEGA_FLIP:
|
|
137
137
|
# If omega_flip is True then the motor omega axis is inverted with respect to the
|
|
138
138
|
# hyperion coordinate system.
|
|
139
139
|
start_scan_deg = -start_scan_deg
|
|
@@ -386,7 +386,6 @@ def rotation_scan_internal(
|
|
|
386
386
|
parameters: RotationScan,
|
|
387
387
|
oav_params: OAVParameters | None = None,
|
|
388
388
|
) -> MsgGenerator:
|
|
389
|
-
parameters.features.update_self_from_server()
|
|
390
389
|
if not oav_params:
|
|
391
390
|
oav_params = OAVParameters(context="xrayCentring")
|
|
392
391
|
eiger: EigerDetector = composite.eiger
|
|
@@ -3,6 +3,7 @@ import json
|
|
|
3
3
|
import re
|
|
4
4
|
import traceback
|
|
5
5
|
from collections.abc import Sequence
|
|
6
|
+
from enum import StrEnum
|
|
6
7
|
from os import path
|
|
7
8
|
from typing import Any, TypeVar
|
|
8
9
|
|
|
@@ -14,6 +15,7 @@ from pydantic_extra_types.semantic_version import SemanticVersion
|
|
|
14
15
|
|
|
15
16
|
from mx_bluesky.common.parameters.components import (
|
|
16
17
|
PARAMETER_VERSION,
|
|
18
|
+
MxBlueskyParameters,
|
|
17
19
|
WithVisit,
|
|
18
20
|
)
|
|
19
21
|
from mx_bluesky.common.parameters.constants import (
|
|
@@ -21,6 +23,7 @@ from mx_bluesky.common.parameters.constants import (
|
|
|
21
23
|
)
|
|
22
24
|
from mx_bluesky.common.utils.log import LOGGER
|
|
23
25
|
from mx_bluesky.common.utils.utils import convert_angstrom_to_eV
|
|
26
|
+
from mx_bluesky.hyperion.parameters.components import Wait
|
|
24
27
|
from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
|
|
25
28
|
|
|
26
29
|
T = TypeVar("T", bound=WithVisit)
|
|
@@ -31,8 +34,13 @@ MULTIPIN_REGEX = rf"^{MULTIPIN_PREFIX}_(\d+)x(\d+(?:\.\d+)?)\+(\d+(?:\.\d+)?)$"
|
|
|
31
34
|
MX_GENERAL_ROOT_REGEX = r"^/dls/(?P<beamline>[^/]+)/data/[^/]*/(?P<visit>[^/]+)(?:/|$)"
|
|
32
35
|
|
|
33
36
|
|
|
37
|
+
class _InstructionType(StrEnum):
|
|
38
|
+
WAIT = "wait"
|
|
39
|
+
COLLECT = "collect"
|
|
40
|
+
|
|
41
|
+
|
|
34
42
|
@dataclasses.dataclass
|
|
35
|
-
class
|
|
43
|
+
class _PinType:
|
|
36
44
|
expected_number_of_crystals: int
|
|
37
45
|
single_well_width_um: float
|
|
38
46
|
tip_to_first_well_um: float = 0
|
|
@@ -54,7 +62,7 @@ class PinType:
|
|
|
54
62
|
)
|
|
55
63
|
|
|
56
64
|
|
|
57
|
-
class
|
|
65
|
+
class _SinglePin(_PinType):
|
|
58
66
|
def __init__(self):
|
|
59
67
|
super().__init__(1, GridscanParamConstants.WIDTH_UM)
|
|
60
68
|
|
|
@@ -63,23 +71,115 @@ class SinglePin(PinType):
|
|
|
63
71
|
return self.single_well_width_um
|
|
64
72
|
|
|
65
73
|
|
|
74
|
+
def create_parameters_from_agamemnon() -> Sequence[MxBlueskyParameters]:
|
|
75
|
+
"""Fetch the next instruction from agamemnon and convert it into one or more
|
|
76
|
+
mx-bluesky instructions.
|
|
77
|
+
Returns:
|
|
78
|
+
The generated sequence of mx-bluesky parameters, or empty list if
|
|
79
|
+
no instructions."""
|
|
80
|
+
beamline_name = get_beamline_name("i03")
|
|
81
|
+
agamemnon_instruction = _get_next_instruction(beamline_name)
|
|
82
|
+
if agamemnon_instruction:
|
|
83
|
+
match _instruction_and_data(agamemnon_instruction):
|
|
84
|
+
case (_InstructionType.COLLECT, data):
|
|
85
|
+
return _populate_parameters_from_agamemnon(data)
|
|
86
|
+
case (_InstructionType.WAIT, data):
|
|
87
|
+
return [
|
|
88
|
+
Wait.model_validate(
|
|
89
|
+
{
|
|
90
|
+
"duration_s": data,
|
|
91
|
+
"parameter_model_version": _get_param_version(),
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
return []
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def compare_params(load_centre_collect_params: LoadCentreCollect):
|
|
100
|
+
"""Compare the supplied parameters (as supplied from GDA) with those directly
|
|
101
|
+
created from agamemnon. Any differences are logged.
|
|
102
|
+
Args:
|
|
103
|
+
load_centre_collect_params: The parameters from GDA to compare."""
|
|
104
|
+
try:
|
|
105
|
+
lcc_requests = create_parameters_from_agamemnon()
|
|
106
|
+
# Log differences against GDA populated parameters
|
|
107
|
+
if not lcc_requests:
|
|
108
|
+
LOGGER.info("Agamemnon returned no instructions")
|
|
109
|
+
else:
|
|
110
|
+
differences = DeepDiff(
|
|
111
|
+
lcc_requests[0], load_centre_collect_params, math_epsilon=1e-5
|
|
112
|
+
)
|
|
113
|
+
if differences:
|
|
114
|
+
LOGGER.info(
|
|
115
|
+
f"Different parameters found when directly reading from Hyperion: {differences}"
|
|
116
|
+
)
|
|
117
|
+
except (ValueError, KeyError):
|
|
118
|
+
LOGGER.warning(f"Failed to compare parameters: {traceback.format_exc()}")
|
|
119
|
+
except Exception:
|
|
120
|
+
LOGGER.warning(
|
|
121
|
+
f"Unexpected error occurred. Failed to compare parameters: {traceback.format_exc()}"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def update_params_from_agamemnon(parameters: T) -> T:
|
|
126
|
+
"""Update the supplied parameters with additional information from agamemnon.
|
|
127
|
+
This is currently necessary for multipin processing and called when Hyperion is invoked
|
|
128
|
+
from GDA.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
parameters: The LoadCentreCollectParameters that will be updated with additional info,
|
|
132
|
+
such as multipin dimensions, number of crystals.
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
beamline_name = get_beamline_name("i03")
|
|
136
|
+
agamemnon_params = _get_next_instruction(beamline_name)
|
|
137
|
+
instruction, collect_params = _instruction_and_data(agamemnon_params)
|
|
138
|
+
assert instruction == _InstructionType.COLLECT, (
|
|
139
|
+
"Unable to augment GDA parameters from agamemnon, agamemnon reports 'wait'"
|
|
140
|
+
)
|
|
141
|
+
pin_type = _get_pin_type_from_agamemnon_collect_parameters(collect_params)
|
|
142
|
+
if isinstance(parameters, LoadCentreCollect):
|
|
143
|
+
parameters.robot_load_then_centre.tip_offset_um = pin_type.full_width / 2
|
|
144
|
+
parameters.robot_load_then_centre.grid_width_um = pin_type.full_width
|
|
145
|
+
parameters.select_centres.n = pin_type.expected_number_of_crystals
|
|
146
|
+
if pin_type != _SinglePin():
|
|
147
|
+
# Rotation snapshots will be generated from the gridscan snapshots,
|
|
148
|
+
# no need to specify snapshot omega.
|
|
149
|
+
parameters.multi_rotation_scan.snapshot_omegas_deg = []
|
|
150
|
+
parameters.multi_rotation_scan.use_grid_snapshots = True
|
|
151
|
+
except (ValueError, ValidationError) as e:
|
|
152
|
+
LOGGER.warning(f"Failed to update parameters: {e}")
|
|
153
|
+
except Exception as e:
|
|
154
|
+
LOGGER.warning(f"Unexpected error occurred. Failed to update parameters: {e}")
|
|
155
|
+
|
|
156
|
+
return parameters
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _instruction_and_data(agamemnon_instruction: dict) -> tuple[str, Any]:
|
|
160
|
+
instruction, data = next(iter(agamemnon_instruction.items()))
|
|
161
|
+
if instruction not in _InstructionType.__members__.values():
|
|
162
|
+
raise KeyError(
|
|
163
|
+
f"Unexpected instruction from agamemnon: {agamemnon_instruction}"
|
|
164
|
+
)
|
|
165
|
+
return instruction, data
|
|
166
|
+
|
|
167
|
+
|
|
66
168
|
def _get_parameters_from_url(url: str) -> dict:
|
|
67
169
|
response = requests.get(url, headers={"Accept": "application/json"})
|
|
68
170
|
response.raise_for_status()
|
|
69
|
-
|
|
70
|
-
try:
|
|
71
|
-
return response_json["collect"]
|
|
72
|
-
except KeyError as e:
|
|
73
|
-
raise KeyError(f"Unexpected json from agamemnon: {response_json}") from e
|
|
171
|
+
return json.loads(response.content)
|
|
74
172
|
|
|
75
173
|
|
|
76
|
-
def
|
|
77
|
-
|
|
174
|
+
def _get_pin_type_from_agamemnon_collect_parameters(
|
|
175
|
+
collect_parameters: dict,
|
|
176
|
+
) -> _PinType:
|
|
177
|
+
loop_type_name: str | None = collect_parameters["sample"]["loopType"]
|
|
78
178
|
if loop_type_name:
|
|
79
179
|
regex_search = re.search(MULTIPIN_REGEX, loop_type_name)
|
|
80
180
|
if regex_search:
|
|
81
181
|
wells, well_size, tip_to_first_well = regex_search.groups()
|
|
82
|
-
return
|
|
182
|
+
return _PinType(int(wells), float(well_size), float(tip_to_first_well))
|
|
83
183
|
else:
|
|
84
184
|
loop_type_message = (
|
|
85
185
|
f"Agamemnon loop type of {loop_type_name} not recognised"
|
|
@@ -87,14 +187,14 @@ def get_pin_type_from_agamemnon_parameters(parameters: dict) -> PinType:
|
|
|
87
187
|
if loop_type_name.startswith(MULTIPIN_PREFIX):
|
|
88
188
|
raise ValueError(f"{loop_type_message}. {MULTIPIN_FORMAT_DESC}")
|
|
89
189
|
LOGGER.warning(f"{loop_type_message}, assuming single pin")
|
|
90
|
-
return
|
|
190
|
+
return _SinglePin()
|
|
91
191
|
|
|
92
192
|
|
|
93
|
-
def
|
|
193
|
+
def _get_next_instruction(beamline: str) -> dict:
|
|
94
194
|
return _get_parameters_from_url(AGAMEMNON_URL + f"getnextcollect/{beamline}")
|
|
95
195
|
|
|
96
196
|
|
|
97
|
-
def
|
|
197
|
+
def _get_withvisit_parameters_from_agamemnon(parameters: dict) -> tuple:
|
|
98
198
|
try:
|
|
99
199
|
prefix = parameters["prefix"]
|
|
100
200
|
collection = parameters["collection"]
|
|
@@ -113,7 +213,7 @@ def get_withvisit_parameters_from_agamemnon(parameters: dict) -> tuple:
|
|
|
113
213
|
)
|
|
114
214
|
|
|
115
215
|
|
|
116
|
-
def
|
|
216
|
+
def _get_withenergy_parameters_from_agamemnon(parameters: dict) -> dict[str, Any]:
|
|
117
217
|
try:
|
|
118
218
|
first_collection: dict = parameters["collection"][0]
|
|
119
219
|
wavelength = first_collection.get("wavelength")
|
|
@@ -124,21 +224,29 @@ def get_withenergy_parameters_from_agamemnon(parameters: dict) -> dict[str, Any]
|
|
|
124
224
|
return {"demand_energy_ev": None}
|
|
125
225
|
|
|
126
226
|
|
|
127
|
-
def
|
|
227
|
+
def _get_param_version() -> SemanticVersion:
|
|
128
228
|
return SemanticVersion.validate_from_str(str(PARAMETER_VERSION))
|
|
129
229
|
|
|
130
230
|
|
|
131
|
-
def
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
231
|
+
def _populate_parameters_from_agamemnon(
|
|
232
|
+
agamemnon_params,
|
|
233
|
+
) -> Sequence[LoadCentreCollect]:
|
|
234
|
+
if not agamemnon_params:
|
|
235
|
+
# Empty dict means no instructions
|
|
236
|
+
return []
|
|
237
|
+
|
|
238
|
+
visit, detector_distance = _get_withvisit_parameters_from_agamemnon(
|
|
239
|
+
agamemnon_params
|
|
240
|
+
)
|
|
241
|
+
with_energy_params = _get_withenergy_parameters_from_agamemnon(agamemnon_params)
|
|
242
|
+
pin_type = _get_pin_type_from_agamemnon_collect_parameters(agamemnon_params)
|
|
135
243
|
collections = agamemnon_params["collection"]
|
|
136
244
|
visit_directory, file_name = path.split(agamemnon_params["prefix"])
|
|
137
245
|
|
|
138
246
|
return [
|
|
139
247
|
LoadCentreCollect.model_validate(
|
|
140
248
|
{
|
|
141
|
-
"parameter_model_version":
|
|
249
|
+
"parameter_model_version": _get_param_version(),
|
|
142
250
|
"visit": visit,
|
|
143
251
|
"detector_distance_mm": detector_distance,
|
|
144
252
|
"sample_id": agamemnon_params["sample"]["id"],
|
|
@@ -148,7 +256,6 @@ def populate_parameters_from_agamemnon(agamemnon_params) -> Sequence[LoadCentreC
|
|
|
148
256
|
"name": "TopNByMaxCount",
|
|
149
257
|
"n": pin_type.expected_number_of_crystals,
|
|
150
258
|
},
|
|
151
|
-
"features": {"use_gpu_results": True},
|
|
152
259
|
"robot_load_then_centre": {
|
|
153
260
|
"storage_directory": str(visit_directory) + "/xraycentring",
|
|
154
261
|
"file_name": file_name,
|
|
@@ -186,55 +293,3 @@ def populate_parameters_from_agamemnon(agamemnon_params) -> Sequence[LoadCentreC
|
|
|
186
293
|
)
|
|
187
294
|
for collection in collections
|
|
188
295
|
]
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def create_parameters_from_agamemnon() -> Sequence[LoadCentreCollect]:
|
|
192
|
-
beamline_name = get_beamline_name("i03")
|
|
193
|
-
agamemnon_params = get_next_instruction(beamline_name)
|
|
194
|
-
return (
|
|
195
|
-
populate_parameters_from_agamemnon(agamemnon_params) if agamemnon_params else []
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
def compare_params(load_centre_collect_params: LoadCentreCollect):
|
|
200
|
-
try:
|
|
201
|
-
lcc_requests = create_parameters_from_agamemnon()
|
|
202
|
-
# Log differences against GDA populated parameters
|
|
203
|
-
if not lcc_requests:
|
|
204
|
-
LOGGER.info("Agamemnon returned no instructions")
|
|
205
|
-
else:
|
|
206
|
-
differences = DeepDiff(
|
|
207
|
-
lcc_requests[0], load_centre_collect_params, math_epsilon=1e-5
|
|
208
|
-
)
|
|
209
|
-
if differences:
|
|
210
|
-
LOGGER.info(
|
|
211
|
-
f"Different parameters found when directly reading from Hyperion: {differences}"
|
|
212
|
-
)
|
|
213
|
-
except (ValueError, KeyError):
|
|
214
|
-
LOGGER.warning(f"Failed to compare parameters: {traceback.format_exc()}")
|
|
215
|
-
except Exception:
|
|
216
|
-
LOGGER.warning(
|
|
217
|
-
f"Unexpected error occurred. Failed to compare parameters: {traceback.format_exc()}"
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
def update_params_from_agamemnon(parameters: T) -> T:
|
|
222
|
-
try:
|
|
223
|
-
beamline_name = get_beamline_name("i03")
|
|
224
|
-
agamemnon_params = get_next_instruction(beamline_name)
|
|
225
|
-
pin_type = get_pin_type_from_agamemnon_parameters(agamemnon_params)
|
|
226
|
-
if isinstance(parameters, LoadCentreCollect):
|
|
227
|
-
parameters.robot_load_then_centre.tip_offset_um = pin_type.full_width / 2
|
|
228
|
-
parameters.robot_load_then_centre.grid_width_um = pin_type.full_width
|
|
229
|
-
parameters.select_centres.n = pin_type.expected_number_of_crystals
|
|
230
|
-
if pin_type != SinglePin():
|
|
231
|
-
# Rotation snapshots will be generated from the gridscan snapshots,
|
|
232
|
-
# no need to specify snapshot omega.
|
|
233
|
-
parameters.multi_rotation_scan.snapshot_omegas_deg = []
|
|
234
|
-
parameters.multi_rotation_scan.use_grid_snapshots = True
|
|
235
|
-
except (ValueError, ValidationError) as e:
|
|
236
|
-
LOGGER.warning(f"Failed to update parameters: {e}")
|
|
237
|
-
except Exception as e:
|
|
238
|
-
LOGGER.warning(f"Unexpected error occurred. Failed to update parameters: {e}")
|
|
239
|
-
|
|
240
|
-
return parameters
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Subjects(StrEnum):
|
|
5
|
+
UDC_STARTED = "UDC Started"
|
|
6
|
+
UDC_BATON_PASSED = "UDC Baton was passed"
|
|
7
|
+
UDC_RESUMED_OPERATION = "UDC Resumed operation"
|
|
8
|
+
UDC_SUSPENDED_OPERATION = "UDC Suspended operation"
|
|
9
|
+
NEW_CONTAINER = "Hyperion is collecting from a new container"
|
|
10
|
+
NEW_VISIT = "Hyperion has changed visit"
|
|
11
|
+
SAMPLE_ERROR = "Hyperion has encountered a sample error"
|
|
12
|
+
BEAMLINE_ERROR = "Hyperion has encountered a beamline error"
|
|
@@ -8,6 +8,10 @@ from bluesky.callbacks.zmq import Proxy, RemoteDispatcher
|
|
|
8
8
|
from dodal.log import LOGGER as dodal_logger
|
|
9
9
|
from dodal.log import set_up_all_logging_handlers
|
|
10
10
|
|
|
11
|
+
from mx_bluesky.common.external_interaction.alerting import set_alerting_service
|
|
12
|
+
from mx_bluesky.common.external_interaction.alerting.log_based_service import (
|
|
13
|
+
LoggingAlertService,
|
|
14
|
+
)
|
|
11
15
|
from mx_bluesky.common.external_interaction.callbacks.common.log_uid_tag_callback import (
|
|
12
16
|
LogUidTaggingCallback,
|
|
13
17
|
)
|
|
@@ -156,6 +160,7 @@ class HyperionCallbackRunner:
|
|
|
156
160
|
def __init__(self, dev_mode) -> None:
|
|
157
161
|
setup_logging(dev_mode)
|
|
158
162
|
log_info("Hyperion callback process started.")
|
|
163
|
+
set_alerting_service(LoggingAlertService(CONST.GRAYLOG_STREAM_ID))
|
|
159
164
|
|
|
160
165
|
self.callbacks = setup_callbacks()
|
|
161
166
|
self.proxy, self.dispatcher, start_proxy, start_dispatcher = setup_threads()
|
|
@@ -15,7 +15,7 @@ from mx_bluesky.common.external_interaction.nexus.nexus_utils import (
|
|
|
15
15
|
)
|
|
16
16
|
from mx_bluesky.common.external_interaction.nexus.write_nexus import NexusWriter
|
|
17
17
|
from mx_bluesky.common.utils.log import NEXUS_LOGGER
|
|
18
|
-
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
18
|
+
from mx_bluesky.hyperion.parameters.constants import CONST, I03Constants
|
|
19
19
|
from mx_bluesky.hyperion.parameters.rotation import SingleRotationScan
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
@@ -103,6 +103,6 @@ class RotationNexusFileCallback(PlanReactiveCallback):
|
|
|
103
103
|
full_num_of_images=self.full_num_of_images,
|
|
104
104
|
meta_data_run_number=self.meta_data_run_number,
|
|
105
105
|
axis_direction=AxisDirection.NEGATIVE
|
|
106
|
-
if
|
|
106
|
+
if I03Constants.OMEGA_FLIP
|
|
107
107
|
else AxisDirection.POSITIVE,
|
|
108
108
|
)
|
|
@@ -1,35 +1,16 @@
|
|
|
1
1
|
from functools import cache
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from mx_bluesky.common.external_interaction.config_server import MXConfigClient
|
|
4
|
+
from mx_bluesky.hyperion.parameters.constants import (
|
|
5
|
+
HyperionFeatureSetting,
|
|
6
|
+
HyperionFeatureSettingources,
|
|
7
|
+
)
|
|
4
8
|
|
|
5
|
-
from mx_bluesky.common.external_interaction.config_server import FeatureFlags
|
|
6
|
-
from mx_bluesky.common.utils.log import LOGGER
|
|
7
|
-
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
use_gpu_results: If True then GPU result processing is enabled
|
|
17
|
-
and the GPU result is taken.
|
|
18
|
-
set_stub_offsets: If True then set the stub offsets after moving to the crystal (ignored for
|
|
19
|
-
multi-centre)
|
|
20
|
-
omega_flip: If True then invert the smargon omega motor rotation commands with respect to
|
|
21
|
-
the hyperion request. See "Hyperion Coordinate Systems" in the documentation.
|
|
22
|
-
alternate_rotation_direction: If True then the for multi-sample pins the rotation direction of
|
|
23
|
-
successive rotation scans is alternated between positive and negative.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
@staticmethod
|
|
27
|
-
@cache
|
|
28
|
-
def get_config_server() -> ConfigServer:
|
|
29
|
-
return ConfigServer(CONST.CONFIG_SERVER_URL, LOGGER)
|
|
30
|
-
|
|
31
|
-
use_panda_for_gridscan: bool = CONST.I03.USE_PANDA_FOR_GRIDSCAN
|
|
32
|
-
use_gpu_results: bool = CONST.I03.USE_GPU_RESULTS
|
|
33
|
-
set_stub_offsets: bool = CONST.I03.SET_STUB_OFFSETS
|
|
34
|
-
omega_flip: bool = CONST.I03.OMEGA_FLIP
|
|
35
|
-
alternate_rotation_direction: bool = CONST.I03.ALTERNATE_ROTATION_DIRECTION
|
|
10
|
+
@cache
|
|
11
|
+
def get_hyperion_config_client() -> MXConfigClient[HyperionFeatureSetting]:
|
|
12
|
+
return MXConfigClient(
|
|
13
|
+
feature_sources=HyperionFeatureSettingources,
|
|
14
|
+
feature_dc=HyperionFeatureSetting,
|
|
15
|
+
url="https://daq-config.diamond.ac.uk",
|
|
16
|
+
)
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import argparse
|
|
2
|
+
from enum import StrEnum
|
|
2
3
|
|
|
3
4
|
from pydantic.dataclasses import dataclass
|
|
4
5
|
|
|
5
6
|
from mx_bluesky._version import version
|
|
6
7
|
|
|
7
8
|
|
|
9
|
+
class HyperionMode(StrEnum):
|
|
10
|
+
GDA = "gda"
|
|
11
|
+
UDC = "udc"
|
|
12
|
+
|
|
13
|
+
|
|
8
14
|
@dataclass
|
|
9
15
|
class HyperionArgs:
|
|
16
|
+
mode: HyperionMode
|
|
10
17
|
dev_mode: bool = False
|
|
11
18
|
|
|
12
19
|
|
|
@@ -39,7 +46,12 @@ def parse_cli_args() -> HyperionArgs:
|
|
|
39
46
|
action="version",
|
|
40
47
|
version=version,
|
|
41
48
|
)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
parser.add_argument(
|
|
50
|
+
"--mode",
|
|
51
|
+
help="Launch in the specified mode (default is 'gda')",
|
|
52
|
+
default=HyperionMode.GDA,
|
|
53
|
+
type=HyperionMode,
|
|
54
|
+
choices=HyperionMode.__members__.values(),
|
|
45
55
|
)
|
|
56
|
+
args = parser.parse_args()
|
|
57
|
+
return HyperionArgs(dev_mode=args.dev or False, mode=args.mode)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
from
|
|
1
|
+
from mx_bluesky.common.parameters.components import MxBlueskyParameters
|
|
2
2
|
|
|
3
|
-
from mx_bluesky.common.parameters.components import WithPandaGridScan
|
|
4
|
-
from mx_bluesky.hyperion.external_interaction.config_server import HyperionFeatureFlags
|
|
5
3
|
|
|
4
|
+
class Wait(MxBlueskyParameters):
|
|
5
|
+
"""Represents an instruction from Agamemnon for Hyperion to wait for a specified time
|
|
6
|
+
Attributes:
|
|
7
|
+
duration_s: duration to wait in seconds
|
|
8
|
+
"""
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
features: HyperionFeatureFlags = Field(default=HyperionFeatureFlags())
|
|
10
|
+
duration_s: float
|
|
@@ -8,6 +8,8 @@ from mx_bluesky.common.parameters.constants import (
|
|
|
8
8
|
DocDescriptorNames,
|
|
9
9
|
EnvironmentConstants,
|
|
10
10
|
ExperimentParamConstants,
|
|
11
|
+
FeatureSetting,
|
|
12
|
+
FeatureSettingources,
|
|
11
13
|
HardwareConstants,
|
|
12
14
|
OavConstants,
|
|
13
15
|
PlanGroupCheckpointConstants,
|
|
@@ -24,13 +26,26 @@ class I03Constants:
|
|
|
24
26
|
INSERTION_PREFIX = "SR03S" if TEST_MODE else "SR03I"
|
|
25
27
|
OAV_CENTRING_FILE = OavConstants.OAV_CONFIG_JSON
|
|
26
28
|
SHUTTER_TIME_S = 0.06
|
|
27
|
-
|
|
28
|
-
SET_STUB_OFFSETS = False
|
|
29
|
+
USE_GPU_RESULTS = True
|
|
29
30
|
OMEGA_FLIP = True
|
|
30
31
|
ALTERNATE_ROTATION_DIRECTION = True
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
|
|
34
|
+
# These currently exist in GDA domain.properties
|
|
35
|
+
class HyperionFeatureSettingources(FeatureSettingources):
|
|
36
|
+
USE_GPU_RESULTS = "gda.mx.hyperion.xrc.use_gpu_results"
|
|
37
|
+
USE_PANDA_FOR_GRIDSCAN = "gda.mx.hyperion.use_panda_for_gridscans"
|
|
38
|
+
SET_STUB_OFFSETS = "gda.mx.hyperion.do_stub_offsets"
|
|
39
|
+
PANDA_RUNUP_DISTANCE_MM = "gda.mx.hyperion.panda_runup_distance_mm"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Use these defaults if we can't read from the config server
|
|
43
|
+
@dataclass
|
|
44
|
+
class HyperionFeatureSetting(FeatureSetting):
|
|
45
|
+
USE_GPU_RESULTS: bool = True
|
|
46
|
+
USE_PANDA_FOR_GRIDSCAN: bool = False
|
|
47
|
+
SET_STUB_OFFSETS: bool = False
|
|
48
|
+
PANDA_RUNUP_DISTANCE_MM: float = 0.16
|
|
34
49
|
|
|
35
50
|
|
|
36
51
|
@dataclass(frozen=True)
|
|
@@ -49,6 +64,7 @@ class HyperionConstants:
|
|
|
49
64
|
else "https://daq-config.diamond.ac.uk/api"
|
|
50
65
|
)
|
|
51
66
|
GRAYLOG_PORT = 12232 # Hyperion stream
|
|
67
|
+
GRAYLOG_STREAM_ID = "66264f5519ccca6d1c9e4e03"
|
|
52
68
|
PARAMETER_SCHEMA_DIRECTORY = "src/hyperion/parameters/schemas/"
|
|
53
69
|
LOG_FILE_NAME = "hyperion.log"
|
|
54
70
|
DEVICE_SETTINGS_CONSTANTS = DeviceSettingsConstants()
|
|
@@ -9,10 +9,12 @@ from mx_bluesky.common.parameters.gridscan import (
|
|
|
9
9
|
GridCommon,
|
|
10
10
|
SpecifiedThreeDGridScan,
|
|
11
11
|
)
|
|
12
|
-
from mx_bluesky.hyperion.
|
|
12
|
+
from mx_bluesky.hyperion.external_interaction.config_server import (
|
|
13
|
+
get_hyperion_config_client,
|
|
14
|
+
)
|
|
13
15
|
|
|
14
16
|
|
|
15
|
-
class GridCommonWithHyperionDetectorParams(GridCommon
|
|
17
|
+
class GridCommonWithHyperionDetectorParams(GridCommon):
|
|
16
18
|
"""Used by models which require detector parameters but have no specifications of the grid"""
|
|
17
19
|
|
|
18
20
|
# These detector params only exist so that we can properly select enable_dev_shm. Remove in
|
|
@@ -20,11 +22,13 @@ class GridCommonWithHyperionDetectorParams(GridCommon, WithHyperionUDCFeatures):
|
|
|
20
22
|
@property
|
|
21
23
|
def detector_params(self):
|
|
22
24
|
params = super().detector_params
|
|
23
|
-
params.enable_dev_shm =
|
|
25
|
+
params.enable_dev_shm = (
|
|
26
|
+
get_hyperion_config_client().get_feature_flags().USE_GPU_RESULTS
|
|
27
|
+
)
|
|
24
28
|
return params
|
|
25
29
|
|
|
26
30
|
|
|
27
|
-
class HyperionSpecifiedThreeDGridScan(
|
|
31
|
+
class HyperionSpecifiedThreeDGridScan(SpecifiedThreeDGridScan):
|
|
28
32
|
"""Hyperion's 3D grid scan deviates from the common class due to: optionally using a PandA, optionally using dev_shm for GPU analysis, and using a config server for features"""
|
|
29
33
|
|
|
30
34
|
# These detector params only exist so that we can properly select enable_dev_shm. Remove in
|
|
@@ -33,7 +37,9 @@ class HyperionSpecifiedThreeDGridScan(WithHyperionUDCFeatures, SpecifiedThreeDGr
|
|
|
33
37
|
@property
|
|
34
38
|
def detector_params(self):
|
|
35
39
|
params = super().detector_params
|
|
36
|
-
params.enable_dev_shm =
|
|
40
|
+
params.enable_dev_shm = (
|
|
41
|
+
get_hyperion_config_client().get_feature_flags().USE_GPU_RESULTS
|
|
42
|
+
)
|
|
37
43
|
return params
|
|
38
44
|
|
|
39
45
|
# Relative to common grid scan, stub offsets are defined by config server
|
|
@@ -51,7 +57,9 @@ class HyperionSpecifiedThreeDGridScan(WithHyperionUDCFeatures, SpecifiedThreeDGr
|
|
|
51
57
|
z1_start_mm=self.z_start_um / 1000,
|
|
52
58
|
y2_start_mm=self.y2_start_um / 1000,
|
|
53
59
|
z2_start_mm=self.z2_start_um / 1000,
|
|
54
|
-
set_stub_offsets=
|
|
60
|
+
set_stub_offsets=get_hyperion_config_client()
|
|
61
|
+
.get_feature_flags()
|
|
62
|
+
.SET_STUB_OFFSETS,
|
|
55
63
|
dwell_time_ms=self.exposure_time_s * 1000,
|
|
56
64
|
transmission_fraction=self.transmission_frac,
|
|
57
65
|
)
|
|
@@ -75,8 +83,12 @@ class HyperionSpecifiedThreeDGridScan(WithHyperionUDCFeatures, SpecifiedThreeDGr
|
|
|
75
83
|
z1_start_mm=self.z_start_um / 1000,
|
|
76
84
|
y2_start_mm=self.y2_start_um / 1000,
|
|
77
85
|
z2_start_mm=self.z2_start_um / 1000,
|
|
78
|
-
set_stub_offsets=
|
|
79
|
-
|
|
86
|
+
set_stub_offsets=get_hyperion_config_client()
|
|
87
|
+
.get_feature_flags()
|
|
88
|
+
.SET_STUB_OFFSETS,
|
|
89
|
+
run_up_distance_mm=get_hyperion_config_client()
|
|
90
|
+
.get_feature_flags()
|
|
91
|
+
.PANDA_RUNUP_DISTANCE_MM,
|
|
80
92
|
transmission_fraction=self.transmission_frac,
|
|
81
93
|
)
|
|
82
94
|
|
|
@@ -84,13 +96,9 @@ class HyperionSpecifiedThreeDGridScan(WithHyperionUDCFeatures, SpecifiedThreeDGr
|
|
|
84
96
|
class OddYStepsException(Exception): ...
|
|
85
97
|
|
|
86
98
|
|
|
87
|
-
class PinTipCentreThenXrayCentre(
|
|
88
|
-
GridCommonWithHyperionDetectorParams, WithHyperionUDCFeatures
|
|
89
|
-
):
|
|
99
|
+
class PinTipCentreThenXrayCentre(GridCommonWithHyperionDetectorParams):
|
|
90
100
|
tip_offset_um: float = 0
|
|
91
101
|
|
|
92
102
|
|
|
93
|
-
class GridScanWithEdgeDetect(
|
|
94
|
-
GridCommonWithHyperionDetectorParams, WithHyperionUDCFeatures
|
|
95
|
-
):
|
|
103
|
+
class GridScanWithEdgeDetect(GridCommonWithHyperionDetectorParams):
|
|
96
104
|
pass
|