mx-bluesky 1.4.5__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 +9 -4
- 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 +45 -28
- mx_bluesky/beamlines/i04/thawing_plan.py +19 -14
- mx_bluesky/beamlines/i24/serial/__init__.py +14 -0
- mx_bluesky/beamlines/i24/serial/dcid.py +3 -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 +31 -30
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +16 -14
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +19 -21
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +11 -4
- mx_bluesky/beamlines/i24/serial/parameters/constants.py +1 -1
- 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 +48 -49
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +2 -2
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +11 -9
- mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +109 -0
- mx_bluesky/beamlines/i24/serial/write_nexus.py +5 -4
- 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/plan_reactive_callback.py +2 -2
- mx_bluesky/common/external_interaction/callbacks/common/zocalo_callback.py +18 -15
- mx_bluesky/common/external_interaction/callbacks/sample_handling/__init__.py +0 -0
- mx_bluesky/{hyperion → common}/external_interaction/callbacks/sample_handling/sample_handling_callback.py +29 -12
- mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py +43 -7
- mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_mapping.py +1 -1
- 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/exp_eye_store.py +6 -2
- mx_bluesky/common/external_interaction/ispyb/ispyb_store.py +21 -1
- mx_bluesky/common/external_interaction/nexus/nexus_utils.py +1 -1
- mx_bluesky/common/parameters/constants.py +3 -1
- mx_bluesky/common/parameters/gridscan.py +36 -1
- mx_bluesky/common/plans/do_fgs.py +4 -6
- mx_bluesky/common/plans/read_hardware.py +78 -0
- 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/common/utils/context.py +68 -0
- mx_bluesky/{hyperion/experiment_plans/common → common}/xrc_result.py +16 -0
- mx_bluesky/hyperion/__main__.py +7 -9
- mx_bluesky/hyperion/baton_handler.py +84 -0
- mx_bluesky/hyperion/device_setup_plans/setup_oav.py +5 -5
- mx_bluesky/hyperion/device_setup_plans/setup_panda.py +5 -1
- mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +2 -2
- mx_bluesky/hyperion/device_setup_plans/smargon.py +6 -6
- mx_bluesky/hyperion/device_setup_plans/utils.py +2 -2
- mx_bluesky/hyperion/experiment_plans/__init__.py +0 -4
- mx_bluesky/hyperion/experiment_plans/change_aperture_then_move_plan.py +12 -31
- mx_bluesky/hyperion/experiment_plans/experiment_registry.py +0 -7
- mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +44 -97
- mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +6 -6
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +8 -6
- mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +11 -11
- mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +5 -5
- mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +1 -1
- mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +2 -4
- mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +15 -13
- mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +10 -10
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +1 -29
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +30 -27
- mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +25 -6
- mx_bluesky/hyperion/external_interaction/agamemnon.py +242 -0
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +12 -6
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +1 -1
- mx_bluesky/hyperion/external_interaction/callbacks/snapshot_callback.py +107 -0
- mx_bluesky/hyperion/external_interaction/config_server.py +6 -6
- mx_bluesky/hyperion/parameters/device_composites.py +49 -0
- mx_bluesky/hyperion/parameters/gridscan.py +3 -3
- mx_bluesky/hyperion/parameters/rotation.py +1 -1
- mx_bluesky/hyperion/utils/__init__.py +1 -0
- mx_bluesky/hyperion/utils/context.py +0 -65
- mx_bluesky/hyperion/utils/validation.py +3 -3
- {mx_bluesky-1.4.5.dist-info → mx_bluesky-1.4.7.dist-info}/METADATA +6 -5
- {mx_bluesky-1.4.5.dist-info → mx_bluesky-1.4.7.dist-info}/RECORD +86 -72
- {mx_bluesky-1.4.5.dist-info → mx_bluesky-1.4.7.dist-info}/WHEEL +1 -1
- {mx_bluesky-1.4.5.dist-info → mx_bluesky-1.4.7.dist-info}/entry_points.txt +1 -0
- mx_bluesky/common/device_setup_plans/read_hardware_for_setup.py +0 -14
- mx_bluesky/common/external_interaction/callbacks/common/aperture_change_callback.py +0 -22
- mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +0 -54
- mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +0 -103
- /mx_bluesky/{hyperion/external_interaction/callbacks/sample_handling → beamlines/i24/serial/web_gui_plans}/__init__.py +0 -0
- {mx_bluesky-1.4.5.dist-info → mx_bluesky-1.4.7.dist-info/licenses}/LICENSE +0 -0
- {mx_bluesky-1.4.5.dist-info → mx_bluesky-1.4.7.dist-info}/top_level.txt +0 -0
|
@@ -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,
|
|
@@ -206,9 +219,16 @@ class StoreInIspyb:
|
|
|
206
219
|
def _store_data_collection_table(
|
|
207
220
|
self, conn, data_collection_id, data_collection_info
|
|
208
221
|
):
|
|
222
|
+
if data_collection_id and data_collection_info.comments:
|
|
223
|
+
self.append_to_comment(
|
|
224
|
+
data_collection_id, data_collection_info.comments, " "
|
|
225
|
+
)
|
|
226
|
+
data_collection_info.comments = None
|
|
227
|
+
|
|
209
228
|
params = self._fill_common_data_collection_params(
|
|
210
229
|
conn, data_collection_id, data_collection_info
|
|
211
230
|
)
|
|
231
|
+
|
|
212
232
|
return self._upsert_data_collection(conn, params)
|
|
213
233
|
|
|
214
234
|
def _store_single_scan_data(
|
|
@@ -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
|
|
|
@@ -58,6 +58,8 @@ class PlanNameConstants:
|
|
|
58
58
|
ROTATION_OUTER = "rotation_scan_with_cleanup"
|
|
59
59
|
ROTATION_MAIN = "rotation_scan_main"
|
|
60
60
|
FLYSCAN_RESULTS = "xray_centre_results"
|
|
61
|
+
SET_ENERGY = "set_energy"
|
|
62
|
+
UNNAMED_RUN = "unnamed_run"
|
|
61
63
|
|
|
62
64
|
|
|
63
65
|
@dataclass(frozen=True)
|
|
@@ -130,7 +132,7 @@ class PlanGroupCheckpointConstants:
|
|
|
130
132
|
class DeviceSettingsConstants:
|
|
131
133
|
PANDA_FLYSCAN_SETTINGS_FILENAME = "panda-gridscan"
|
|
132
134
|
PANDA_FLYSCAN_SETTINGS_DIR = os.path.abspath(
|
|
133
|
-
f"{ROOT_DIR}/hyperion/resources/panda/
|
|
135
|
+
f"{ROOT_DIR}/hyperion/resources/panda/"
|
|
134
136
|
)
|
|
135
137
|
|
|
136
138
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dodal.devices.aperturescatterguard import ApertureValue
|
|
4
|
+
from dodal.devices.detector.det_dim_constants import EIGER2_X_9M_SIZE, EIGER2_X_16M_SIZE
|
|
5
|
+
from dodal.devices.detector.detector import DetectorParams
|
|
4
6
|
from dodal.devices.fast_grid_scan import (
|
|
5
7
|
ZebraGridScanParams,
|
|
6
8
|
)
|
|
9
|
+
from dodal.utils import get_beamline_name
|
|
7
10
|
from pydantic import Field, PrivateAttr
|
|
8
11
|
from scanspec.core import Path as ScanPath
|
|
9
12
|
from scanspec.specs import Line, Static
|
|
@@ -18,11 +21,12 @@ from mx_bluesky.common.parameters.components import (
|
|
|
18
21
|
XyzStarts,
|
|
19
22
|
)
|
|
20
23
|
from mx_bluesky.common.parameters.constants import (
|
|
24
|
+
DetectorParamConstants,
|
|
21
25
|
GridscanParamConstants,
|
|
22
26
|
HardwareConstants,
|
|
23
27
|
)
|
|
24
28
|
|
|
25
|
-
""
|
|
29
|
+
DETECTOR_SIZE_PER_BEAMLINE = {"i02-1": EIGER2_X_9M_SIZE, "dev": EIGER2_X_16M_SIZE}
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
class GridCommon(
|
|
@@ -146,3 +150,34 @@ class SpecifiedThreeDGridScan(
|
|
|
146
150
|
@property
|
|
147
151
|
def num_images(self) -> int:
|
|
148
152
|
return len(self.scan_points["sam_x"])
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def detector_params(self):
|
|
156
|
+
self.det_dist_to_beam_converter_path = (
|
|
157
|
+
self.det_dist_to_beam_converter_path
|
|
158
|
+
or DetectorParamConstants.BEAM_XY_LUT_PATH
|
|
159
|
+
)
|
|
160
|
+
optional_args = {}
|
|
161
|
+
if self.run_number:
|
|
162
|
+
optional_args["run_number"] = self.run_number
|
|
163
|
+
assert self.detector_distance_mm is not None, (
|
|
164
|
+
"Detector distance must be filled before generating DetectorParams"
|
|
165
|
+
)
|
|
166
|
+
return DetectorParams(
|
|
167
|
+
detector_size_constants=DETECTOR_SIZE_PER_BEAMLINE[
|
|
168
|
+
get_beamline_name("dev")
|
|
169
|
+
],
|
|
170
|
+
expected_energy_ev=self.demand_energy_ev,
|
|
171
|
+
exposure_time_s=self.exposure_time_s,
|
|
172
|
+
directory=self.storage_directory,
|
|
173
|
+
prefix=self.file_name,
|
|
174
|
+
detector_distance=self.detector_distance_mm,
|
|
175
|
+
omega_start=self.omega_start_deg or 0,
|
|
176
|
+
omega_increment=0,
|
|
177
|
+
num_images_per_trigger=1,
|
|
178
|
+
num_triggers=self.num_images,
|
|
179
|
+
use_roi_mode=self.use_roi_mode,
|
|
180
|
+
det_dist_to_beam_converter_path=self.det_dist_to_beam_converter_path,
|
|
181
|
+
trigger_mode=self.trigger_mode,
|
|
182
|
+
**optional_args,
|
|
183
|
+
)
|
|
@@ -14,12 +14,10 @@ from dodal.log import LOGGER
|
|
|
14
14
|
from dodal.plan_stubs.check_topup import check_topup_and_wait_if_necessary
|
|
15
15
|
from scanspec.core import AxesPoints, Axis
|
|
16
16
|
|
|
17
|
-
from mx_bluesky.common.device_setup_plans.read_hardware_for_setup import (
|
|
18
|
-
read_hardware_for_zocalo,
|
|
19
|
-
)
|
|
20
17
|
from mx_bluesky.common.parameters.constants import (
|
|
21
18
|
PlanNameConstants,
|
|
22
19
|
)
|
|
20
|
+
from mx_bluesky.common.plans.read_hardware import read_hardware_for_zocalo
|
|
23
21
|
from mx_bluesky.common.utils.tracing import TRACER
|
|
24
22
|
|
|
25
23
|
|
|
@@ -30,7 +28,7 @@ def _wait_for_zocalo_to_stage_then_do_fgs(
|
|
|
30
28
|
during_collection_plan: Callable[[], MsgGenerator] | None = None,
|
|
31
29
|
):
|
|
32
30
|
expected_images = yield from bps.rd(grid_scan_device.expected_images)
|
|
33
|
-
exposure_sec_per_image = yield from bps.rd(detector.cam.acquire_time) # type: ignore #
|
|
31
|
+
exposure_sec_per_image = yield from bps.rd(detector.cam.acquire_time) # type: ignore # Fix types in ophyd-async (https://github.com/DiamondLightSource/mx-bluesky/issues/855)
|
|
34
32
|
LOGGER.info("waiting for topup if necessary...")
|
|
35
33
|
yield from check_topup_and_wait_if_necessary(
|
|
36
34
|
synchrotron,
|
|
@@ -101,8 +99,8 @@ def kickoff_and_complete_gridscan(
|
|
|
101
99
|
}
|
|
102
100
|
)
|
|
103
101
|
@bpp.contingency_decorator(
|
|
104
|
-
except_plan=lambda e: (yield from bps.stop(detector)), # type: ignore #
|
|
105
|
-
else_plan=lambda: (yield from bps.unstage(detector)),
|
|
102
|
+
except_plan=lambda e: (yield from bps.stop(detector)), # type: ignore # Fix types in ophyd-async (https://github.com/DiamondLightSource/mx-bluesky/issues/855)
|
|
103
|
+
else_plan=lambda: (yield from bps.unstage(detector)),
|
|
106
104
|
)
|
|
107
105
|
def _decorated_do_fgs():
|
|
108
106
|
yield from _wait_for_zocalo_to_stage_then_do_fgs(
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import bluesky.plan_stubs as bps
|
|
4
|
+
from bluesky.protocols import Readable
|
|
5
|
+
from dodal.devices.aperturescatterguard import ApertureScatterguard
|
|
6
|
+
from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
|
|
7
|
+
from dodal.devices.dcm import DCM
|
|
8
|
+
from dodal.devices.eiger import EigerDetector
|
|
9
|
+
from dodal.devices.flux import Flux
|
|
10
|
+
from dodal.devices.s4_slit_gaps import S4SlitGaps
|
|
11
|
+
from dodal.devices.smargon import Smargon
|
|
12
|
+
from dodal.devices.synchrotron import Synchrotron
|
|
13
|
+
from dodal.devices.undulator import Undulator
|
|
14
|
+
|
|
15
|
+
from mx_bluesky.common.parameters.constants import (
|
|
16
|
+
DocDescriptorNames,
|
|
17
|
+
)
|
|
18
|
+
from mx_bluesky.common.utils.log import LOGGER
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def read_hardware_plan(
|
|
22
|
+
signals: list[Readable],
|
|
23
|
+
event_name: str,
|
|
24
|
+
):
|
|
25
|
+
LOGGER.info(f"Reading status of beamline for event, {event_name}")
|
|
26
|
+
yield from bps.create(name=event_name)
|
|
27
|
+
for signal in signals:
|
|
28
|
+
yield from bps.read(signal)
|
|
29
|
+
yield from bps.save()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def read_hardware_for_zocalo(detector: EigerDetector):
|
|
33
|
+
""" "
|
|
34
|
+
If the RunEngine is subscribed to the ZocaloCallback, this plan will also trigger zocalo.
|
|
35
|
+
"""
|
|
36
|
+
yield from read_hardware_plan(
|
|
37
|
+
[detector.odin.file_writer.id], # type: ignore
|
|
38
|
+
DocDescriptorNames.ZOCALO_HW_READ,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def standard_read_hardware_pre_collection(
|
|
43
|
+
undulator: Undulator,
|
|
44
|
+
synchrotron: Synchrotron,
|
|
45
|
+
s4_slit_gaps: S4SlitGaps,
|
|
46
|
+
dcm: DCM,
|
|
47
|
+
smargon: Smargon,
|
|
48
|
+
):
|
|
49
|
+
LOGGER.info("Reading status of beamline for callbacks, pre collection.")
|
|
50
|
+
signals_to_read_pre_flyscan = [
|
|
51
|
+
undulator.current_gap,
|
|
52
|
+
synchrotron.synchrotron_mode,
|
|
53
|
+
s4_slit_gaps,
|
|
54
|
+
smargon,
|
|
55
|
+
dcm.energy_in_kev,
|
|
56
|
+
]
|
|
57
|
+
yield from read_hardware_plan(
|
|
58
|
+
signals_to_read_pre_flyscan, DocDescriptorNames.HARDWARE_READ_PRE
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def standard_read_hardware_during_collection(
|
|
63
|
+
aperture_scatterguard: ApertureScatterguard,
|
|
64
|
+
attenuator: BinaryFilterAttenuator,
|
|
65
|
+
flux: Flux,
|
|
66
|
+
dcm: DCM,
|
|
67
|
+
detector: EigerDetector,
|
|
68
|
+
):
|
|
69
|
+
signals_to_read_during_collection = [
|
|
70
|
+
aperture_scatterguard,
|
|
71
|
+
attenuator.actual_transmission,
|
|
72
|
+
flux.flux_reading,
|
|
73
|
+
dcm.energy_in_kev,
|
|
74
|
+
detector.bit_depth,
|
|
75
|
+
]
|
|
76
|
+
yield from read_hardware_plan(
|
|
77
|
+
signals_to_read_during_collection, DocDescriptorNames.HARDWARE_READ_DURING
|
|
78
|
+
)
|
|
@@ -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
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
from typing import Any, ClassVar, Protocol, TypeVar, get_type_hints
|
|
3
|
+
|
|
4
|
+
from blueapi.core import BlueskyContext
|
|
5
|
+
from blueapi.core.bluesky_types import Device
|
|
6
|
+
|
|
7
|
+
from mx_bluesky.common.utils.log import LOGGER
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T", bound=Device)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class _IsDataclass(Protocol):
|
|
13
|
+
"""Protocol followed by any dataclass"""
|
|
14
|
+
|
|
15
|
+
__dataclass_fields__: ClassVar[dict]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
DT = TypeVar("DT", bound=_IsDataclass)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def find_device_in_context(
|
|
22
|
+
context: BlueskyContext,
|
|
23
|
+
name: str,
|
|
24
|
+
# Typing in here is wrong (see https://github.com/microsoft/pyright/issues/7228#issuecomment-1934500232)
|
|
25
|
+
# but this whole thing will go away when we do https://github.com/DiamondLightSource/hyperion/issues/868
|
|
26
|
+
expected_type: type[T] = Device, # type: ignore
|
|
27
|
+
) -> T:
|
|
28
|
+
LOGGER.debug(f"Looking for device {name} of type {expected_type} in context")
|
|
29
|
+
|
|
30
|
+
device = context.find_device(name)
|
|
31
|
+
if device is None:
|
|
32
|
+
raise ValueError(
|
|
33
|
+
f"Cannot find device named '{name}' in bluesky context {context.devices}."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if not isinstance(device, expected_type):
|
|
37
|
+
raise ValueError(
|
|
38
|
+
f"Found device named '{name}' and expected it to be a '{expected_type}' but it was a '{device.__class__.__name__}'"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
LOGGER.debug(f"Found matching device {device}")
|
|
42
|
+
return device
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def device_composite_from_context(context: BlueskyContext, dc: type[DT]) -> DT:
|
|
46
|
+
"""
|
|
47
|
+
Initializes all of the devices referenced in a given dataclass from a provided
|
|
48
|
+
context, checking that the types of devices returned by the context are compatible
|
|
49
|
+
with the type annotations of the dataclass.
|
|
50
|
+
|
|
51
|
+
Note that if the context was not created with `wait_for_connection=True` devices may
|
|
52
|
+
still be unconnected.
|
|
53
|
+
"""
|
|
54
|
+
LOGGER.debug(
|
|
55
|
+
f"Attempting to initialize devices referenced in dataclass {dc} from blueapi context"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
devices: dict[str, Any] = {}
|
|
59
|
+
dc_type_hints: dict[str, Any] = get_type_hints(dc)
|
|
60
|
+
|
|
61
|
+
for field in dataclasses.fields(dc):
|
|
62
|
+
device = find_device_in_context(
|
|
63
|
+
context, field.name, expected_type=dc_type_hints.get(field.name, Device)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
devices[field.name] = device
|
|
67
|
+
|
|
68
|
+
return dc(**devices)
|
|
@@ -5,6 +5,8 @@ from collections.abc import Callable, Sequence
|
|
|
5
5
|
from functools import partial
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
|
+
from bluesky.callbacks import CallbackBase
|
|
9
|
+
from event_model import RunStart
|
|
8
10
|
|
|
9
11
|
from mx_bluesky.common.parameters.components import (
|
|
10
12
|
MultiXtalSelection,
|
|
@@ -12,6 +14,20 @@ from mx_bluesky.common.parameters.components import (
|
|
|
12
14
|
)
|
|
13
15
|
|
|
14
16
|
|
|
17
|
+
class XRayCentreEventHandler(CallbackBase):
|
|
18
|
+
def __init__(self):
|
|
19
|
+
super().__init__()
|
|
20
|
+
self.xray_centre_results: Sequence[XRayCentreResult] | None = None
|
|
21
|
+
|
|
22
|
+
def start(self, doc: RunStart) -> RunStart | None:
|
|
23
|
+
if "xray_centre_results" in doc:
|
|
24
|
+
self.xray_centre_results = [
|
|
25
|
+
XRayCentreResult(**result_dict)
|
|
26
|
+
for result_dict in doc["xray_centre_results"] # type: ignore
|
|
27
|
+
]
|
|
28
|
+
return doc
|
|
29
|
+
|
|
30
|
+
|
|
15
31
|
@dataclasses.dataclass
|
|
16
32
|
class XRayCentreResult:
|
|
17
33
|
"""
|
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
|
)
|
|
@@ -38,6 +35,10 @@ from mx_bluesky.hyperion.experiment_plans.experiment_registry import (
|
|
|
38
35
|
PLAN_REGISTRY,
|
|
39
36
|
PlanNotFound,
|
|
40
37
|
)
|
|
38
|
+
from mx_bluesky.hyperion.external_interaction.agamemnon import (
|
|
39
|
+
compare_params,
|
|
40
|
+
update_params_from_agamemnon,
|
|
41
|
+
)
|
|
41
42
|
from mx_bluesky.hyperion.parameters.cli import parse_cli_args
|
|
42
43
|
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
43
44
|
from mx_bluesky.hyperion.utils.context import setup_context
|
|
@@ -86,13 +87,11 @@ class BlueskyRunner:
|
|
|
86
87
|
self.command_queue: Queue[Command] = Queue()
|
|
87
88
|
self.current_status: StatusAndMessage = StatusAndMessage(Status.IDLE)
|
|
88
89
|
self.last_run_aborted: bool = False
|
|
89
|
-
self.aperture_change_callback = ApertureChangeCallback()
|
|
90
90
|
self.logging_uid_tag_callback = LogUidTaggingCallback()
|
|
91
91
|
self.context: BlueskyContext
|
|
92
92
|
|
|
93
93
|
self.RE = RE
|
|
94
94
|
self.context = context
|
|
95
|
-
RE.subscribe(self.aperture_change_callback)
|
|
96
95
|
RE.subscribe(self.logging_uid_tag_callback)
|
|
97
96
|
|
|
98
97
|
LOGGER.info("Connecting to external callback ZMQ proxy...")
|
|
@@ -172,10 +171,7 @@ class BlueskyRunner:
|
|
|
172
171
|
with TRACER.start_span("do_run"):
|
|
173
172
|
self.RE(command.experiment(command.devices, command.parameters))
|
|
174
173
|
|
|
175
|
-
self.current_status = StatusAndMessage(
|
|
176
|
-
Status.IDLE,
|
|
177
|
-
self.aperture_change_callback.last_selected_aperture,
|
|
178
|
-
)
|
|
174
|
+
self.current_status = StatusAndMessage(Status.IDLE)
|
|
179
175
|
|
|
180
176
|
self.last_run_aborted = False
|
|
181
177
|
except WarningException as exception:
|
|
@@ -208,6 +204,8 @@ def compose_start_args(context: BlueskyContext, plan_name: str, action: Actions)
|
|
|
208
204
|
)
|
|
209
205
|
try:
|
|
210
206
|
parameters = experiment_internal_param_type(**json.loads(request.data))
|
|
207
|
+
parameters = update_params_from_agamemnon(parameters)
|
|
208
|
+
compare_params(parameters)
|
|
211
209
|
if parameters.model_extra:
|
|
212
210
|
raise ValueError(f"Extra fields not allowed {parameters.model_extra}")
|
|
213
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
|
+
)
|
|
@@ -55,14 +55,14 @@ def setup_pin_tip_detection_params(
|
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
def setup_general_oav_params(oav: OAV, parameters: OAVParameters):
|
|
58
|
-
yield from set_using_group(oav.cam.color_mode, ColorMode.RGB1)
|
|
59
|
-
yield from set_using_group(oav.cam.acquire_period, parameters.acquire_period)
|
|
60
|
-
yield from set_using_group(oav.cam.acquire_time, parameters.exposure)
|
|
61
|
-
yield from set_using_group(oav.cam.gain, parameters.gain)
|
|
58
|
+
yield from set_using_group(oav.cam.color_mode, ColorMode.RGB1)
|
|
59
|
+
yield from set_using_group(oav.cam.acquire_period, parameters.acquire_period)
|
|
60
|
+
yield from set_using_group(oav.cam.acquire_time, parameters.exposure)
|
|
61
|
+
yield from set_using_group(oav.cam.gain, parameters.gain)
|
|
62
62
|
|
|
63
63
|
zoom_level_str = f"{float(parameters.zoom)}x"
|
|
64
64
|
yield from bps.abs_set(
|
|
65
|
-
oav.zoom_controller,
|
|
65
|
+
oav.zoom_controller,
|
|
66
66
|
zoom_level_str,
|
|
67
67
|
wait=True,
|
|
68
68
|
)
|