mx-bluesky 1.2.0__py3-none-any.whl → 1.4.1a0__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/__init__.py +8 -3
- mx_bluesky/__main__.py +12 -7
- mx_bluesky/_version.py +2 -2
- mx_bluesky/beamlines/i04/callbacks/murko_callback.py +14 -4
- mx_bluesky/beamlines/i04/thawing_plan.py +49 -11
- mx_bluesky/beamlines/i24/serial/__init__.py +3 -0
- mx_bluesky/beamlines/i24/serial/dcid.py +19 -21
- mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +69 -91
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +2 -5
- mx_bluesky/beamlines/i24/serial/fixed_target/ft_utils.py +0 -1
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +111 -143
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +141 -222
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +7 -216
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +18 -17
- mx_bluesky/beamlines/i24/serial/log.py +58 -49
- mx_bluesky/beamlines/i24/serial/parameters/constants.py +0 -1
- mx_bluesky/beamlines/i24/serial/parameters/fixed_target/cs/cs_maker.json +3 -3
- mx_bluesky/beamlines/i24/serial/run_extruder.sh +30 -5
- mx_bluesky/beamlines/i24/serial/run_fixed_target.sh +30 -5
- mx_bluesky/beamlines/i24/serial/run_serial.py +24 -8
- mx_bluesky/beamlines/i24/serial/setup_beamline/ca.py +0 -2
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +79 -81
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +9 -20
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +26 -28
- mx_bluesky/beamlines/i24/serial/write_nexus.py +11 -11
- mx_bluesky/common/__init__.py +0 -0
- mx_bluesky/common/device_setup_plans/read_hardware_for_setup.py +14 -0
- mx_bluesky/common/external_interaction/config_server.py +46 -0
- mx_bluesky/common/parameters/components.py +258 -0
- mx_bluesky/common/parameters/constants.py +138 -0
- mx_bluesky/common/parameters/gridscan.py +94 -0
- mx_bluesky/common/parameters/robot_load.py +16 -0
- mx_bluesky/common/plans/__init__.py +1 -0
- mx_bluesky/common/plans/do_fgs.py +121 -0
- mx_bluesky/common/utils/log.py +118 -0
- mx_bluesky/{hyperion → common/utils}/tracing.py +2 -2
- mx_bluesky/hyperion/__main__.py +13 -10
- mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +31 -26
- mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +6 -12
- mx_bluesky/hyperion/device_setup_plans/setup_oav.py +6 -12
- mx_bluesky/hyperion/device_setup_plans/setup_panda.py +5 -6
- mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +49 -18
- mx_bluesky/hyperion/device_setup_plans/smargon.py +6 -6
- mx_bluesky/hyperion/device_setup_plans/utils.py +2 -2
- mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +4 -4
- mx_bluesky/hyperion/experiment_plans/__init__.py +4 -0
- mx_bluesky/hyperion/experiment_plans/change_aperture_then_move_plan.py +83 -0
- mx_bluesky/hyperion/experiment_plans/common/xrc_result.py +47 -0
- mx_bluesky/hyperion/experiment_plans/experiment_registry.py +9 -9
- mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +145 -161
- mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +56 -22
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +52 -10
- mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +21 -20
- mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +11 -14
- mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +2 -2
- mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +40 -21
- mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +19 -19
- mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +21 -21
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +51 -13
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +24 -7
- mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +5 -6
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +1 -2
- mx_bluesky/hyperion/external_interaction/callbacks/common/abstract_event.py +66 -0
- mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +1 -1
- mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +30 -25
- mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +29 -12
- mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +1 -1
- mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +1 -1
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +7 -4
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +5 -3
- mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +28 -20
- mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +5 -4
- mx_bluesky/hyperion/external_interaction/config_server.py +11 -28
- mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +1 -1
- mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +1 -1
- mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +2 -2
- mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +1 -1
- mx_bluesky/hyperion/log.py +0 -84
- mx_bluesky/hyperion/parameters/components.py +4 -251
- mx_bluesky/hyperion/parameters/constants.py +22 -119
- mx_bluesky/hyperion/parameters/gridscan.py +35 -74
- mx_bluesky/hyperion/parameters/load_centre_collect.py +16 -11
- mx_bluesky/hyperion/parameters/rotation.py +23 -10
- mx_bluesky/hyperion/utils/utils.py +17 -0
- mx_bluesky/hyperion/utils/validation.py +5 -6
- {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/METADATA +36 -33
- {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/RECORD +91 -81
- {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/WHEEL +1 -1
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +0 -161
- mx_bluesky/example.py +0 -19
- mx_bluesky/hyperion/parameters/robot_load.py +0 -16
- {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/LICENSE +0 -0
- {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/top_level.txt +0 -0
|
@@ -9,8 +9,6 @@ the edm screen, while on the schematics they are 0 indexed. Thus, `Soft In 1` fr
|
|
|
9
9
|
schematics corresponds to soft_in_2 in the code.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
import logging
|
|
13
|
-
|
|
14
12
|
import bluesky.plan_stubs as bps
|
|
15
13
|
from dodal.devices.zebra import (
|
|
16
14
|
AND3,
|
|
@@ -35,6 +33,8 @@ from dodal.devices.zebra import (
|
|
|
35
33
|
Zebra,
|
|
36
34
|
)
|
|
37
35
|
|
|
36
|
+
from mx_bluesky.beamlines.i24.serial.log import SSX_LOGGER
|
|
37
|
+
|
|
38
38
|
# Detector specific outs
|
|
39
39
|
TTL_EIGER = 1
|
|
40
40
|
TTL_PILATUS = 2
|
|
@@ -48,8 +48,6 @@ SHUTTER_MODE = {
|
|
|
48
48
|
GATE_START = 1.0
|
|
49
49
|
SHUTTER_OPEN_TIME = 0.05 # For pp with long delays
|
|
50
50
|
|
|
51
|
-
logger = logging.getLogger("I24ssx.setup_zebra")
|
|
52
|
-
|
|
53
51
|
|
|
54
52
|
def get_zebra_settings_for_extruder(
|
|
55
53
|
exp_time: float,
|
|
@@ -70,29 +68,29 @@ def get_zebra_settings_for_extruder(
|
|
|
70
68
|
|
|
71
69
|
|
|
72
70
|
def arm_zebra(zebra: Zebra):
|
|
73
|
-
yield from bps.abs_set(zebra.pc.arm, ArmDemand.ARM, wait=True)
|
|
74
|
-
|
|
71
|
+
yield from bps.abs_set(zebra.pc.arm, ArmDemand.ARM, wait=True) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
|
|
72
|
+
SSX_LOGGER.info("Zebra armed.")
|
|
75
73
|
|
|
76
74
|
|
|
77
75
|
def disarm_zebra(zebra: Zebra):
|
|
78
|
-
yield from bps.abs_set(zebra.pc.arm, ArmDemand.DISARM, wait=True)
|
|
79
|
-
|
|
76
|
+
yield from bps.abs_set(zebra.pc.arm, ArmDemand.DISARM, wait=True) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
|
|
77
|
+
SSX_LOGGER.info("Zebra disarmed.")
|
|
80
78
|
|
|
81
79
|
|
|
82
80
|
def open_fast_shutter(zebra: Zebra):
|
|
83
81
|
yield from bps.abs_set(zebra.inputs.soft_in_2, SoftInState.YES, wait=True)
|
|
84
|
-
|
|
82
|
+
SSX_LOGGER.info("Fast shutter open.")
|
|
85
83
|
|
|
86
84
|
|
|
87
85
|
def close_fast_shutter(zebra: Zebra):
|
|
88
86
|
yield from bps.abs_set(zebra.inputs.soft_in_2, SoftInState.NO, wait=True)
|
|
89
|
-
|
|
87
|
+
SSX_LOGGER.info("Fast shutter closed.")
|
|
90
88
|
|
|
91
89
|
|
|
92
90
|
def set_shutter_mode(zebra: Zebra, mode: str):
|
|
93
91
|
# SOFT_IN:B0 has to be disabled for manual mode
|
|
94
92
|
yield from bps.abs_set(zebra.inputs.soft_in_1, SHUTTER_MODE[mode], wait=True)
|
|
95
|
-
|
|
93
|
+
SSX_LOGGER.info(f"Shutter mode set to {mode}.")
|
|
96
94
|
|
|
97
95
|
|
|
98
96
|
def setup_pc_sources(
|
|
@@ -124,12 +122,12 @@ def setup_zebra_for_quickshot_plan(
|
|
|
124
122
|
exp_time (float): Collection exposure time, in s.
|
|
125
123
|
num_images (float): Number of images to be collected.
|
|
126
124
|
"""
|
|
127
|
-
|
|
125
|
+
SSX_LOGGER.info("Setup ZEBRA for quickshot collection.")
|
|
128
126
|
yield from bps.abs_set(zebra.pc.arm_source, ArmSource.SOFT, group=group)
|
|
129
127
|
yield from setup_pc_sources(zebra, TrigSource.TIME, TrigSource.EXTERNAL)
|
|
130
128
|
|
|
131
129
|
gate_width = exp_time * num_images + 0.5
|
|
132
|
-
|
|
130
|
+
SSX_LOGGER.info(f"Gate start set to {GATE_START}, with width {gate_width}.")
|
|
133
131
|
yield from bps.abs_set(zebra.pc.gate_start, GATE_START, group=group)
|
|
134
132
|
yield from bps.abs_set(zebra.pc.gate_width, gate_width, group=group)
|
|
135
133
|
|
|
@@ -138,7 +136,7 @@ def setup_zebra_for_quickshot_plan(
|
|
|
138
136
|
|
|
139
137
|
if wait:
|
|
140
138
|
yield from bps.wait(group)
|
|
141
|
-
|
|
139
|
+
SSX_LOGGER.info("Finished setting up zebra.")
|
|
142
140
|
|
|
143
141
|
|
|
144
142
|
def set_logic_gates_for_porto_triggering(
|
|
@@ -204,7 +202,7 @@ def setup_zebra_for_extruder_with_pump_probe_plan(
|
|
|
204
202
|
pulse1_delay (float, optional): Delay to start pulse1 (the laser control) after \
|
|
205
203
|
gate start. Defaults to 0.0.
|
|
206
204
|
"""
|
|
207
|
-
|
|
205
|
+
SSX_LOGGER.info("Setup ZEBRA for pump probe extruder collection.")
|
|
208
206
|
|
|
209
207
|
yield from set_shutter_mode(zebra, "manual")
|
|
210
208
|
|
|
@@ -226,7 +224,7 @@ def setup_zebra_for_extruder_with_pump_probe_plan(
|
|
|
226
224
|
gate_width, gate_step = get_zebra_settings_for_extruder(
|
|
227
225
|
exp_time, pump_exp, pump_delay
|
|
228
226
|
)
|
|
229
|
-
|
|
227
|
+
SSX_LOGGER.info(
|
|
230
228
|
f"""
|
|
231
229
|
Gate start set to {GATE_START}, with calculated width {gate_width}
|
|
232
230
|
and step {gate_step}.
|
|
@@ -241,13 +239,13 @@ def setup_zebra_for_extruder_with_pump_probe_plan(
|
|
|
241
239
|
# Settings for extruder pump probe:
|
|
242
240
|
# PULSE1_DLY is the start (0 usually), PULSE1_WID is the laser dwell set on edm
|
|
243
241
|
# PULSE2_DLY is the laser delay set on edm, PULSE2_WID is the exposure time
|
|
244
|
-
|
|
242
|
+
SSX_LOGGER.info(
|
|
245
243
|
f"Pulse1 starting at {pulse1_delay} with width set to laser dwell {pump_exp}."
|
|
246
244
|
)
|
|
247
245
|
yield from bps.abs_set(zebra.output.pulse_1.input, PC_GATE, group=group)
|
|
248
246
|
yield from bps.abs_set(zebra.output.pulse_1.delay, pulse1_delay, group=group)
|
|
249
247
|
yield from bps.abs_set(zebra.output.pulse_1.width, pump_exp, group=group)
|
|
250
|
-
|
|
248
|
+
SSX_LOGGER.info(
|
|
251
249
|
f"""
|
|
252
250
|
Pulse2 starting at laser delay {pump_delay} with width set to \
|
|
253
251
|
exposure time {exp_time}.
|
|
@@ -259,7 +257,7 @@ def setup_zebra_for_extruder_with_pump_probe_plan(
|
|
|
259
257
|
|
|
260
258
|
if wait:
|
|
261
259
|
yield from bps.wait(group)
|
|
262
|
-
|
|
260
|
+
SSX_LOGGER.info("Finished setting up zebra.")
|
|
263
261
|
|
|
264
262
|
|
|
265
263
|
def setup_zebra_for_fastchip_plan(
|
|
@@ -302,7 +300,7 @@ def setup_zebra_for_fastchip_plan(
|
|
|
302
300
|
start_time_offset (float): Delay on the start of the position compare. \
|
|
303
301
|
Defaults to 0.0 (standard chip collection).
|
|
304
302
|
"""
|
|
305
|
-
|
|
303
|
+
SSX_LOGGER.info("Setup ZEBRA for a fixed target collection.")
|
|
306
304
|
|
|
307
305
|
yield from set_shutter_mode(zebra, "manual")
|
|
308
306
|
|
|
@@ -341,7 +339,7 @@ def setup_zebra_for_fastchip_plan(
|
|
|
341
339
|
|
|
342
340
|
if wait:
|
|
343
341
|
yield from bps.wait(group)
|
|
344
|
-
|
|
342
|
+
SSX_LOGGER.info("Finished setting up zebra.")
|
|
345
343
|
|
|
346
344
|
|
|
347
345
|
def open_fast_shutter_at_each_position_plan(
|
|
@@ -371,10 +369,10 @@ def open_fast_shutter_at_each_position_plan(
|
|
|
371
369
|
num_exposures (int): Number of times data is collected in each aperture.
|
|
372
370
|
exposure_time (float): Exposure time for each shot.
|
|
373
371
|
"""
|
|
374
|
-
|
|
372
|
+
SSX_LOGGER.info(
|
|
375
373
|
"ZEBRA setup for fastchip collection with long delays between exposures."
|
|
376
374
|
)
|
|
377
|
-
|
|
375
|
+
SSX_LOGGER.debug("Controlling the fast shutter on PULSE2.")
|
|
378
376
|
# Output panel pulse_2 settings
|
|
379
377
|
yield from bps.abs_set(zebra.output.pulse_2.input, PC_GATE, group=group)
|
|
380
378
|
yield from bps.abs_set(zebra.output.pulse_2.delay, 0.0, group=group)
|
|
@@ -386,7 +384,7 @@ def open_fast_shutter_at_each_position_plan(
|
|
|
386
384
|
|
|
387
385
|
if wait:
|
|
388
386
|
yield from bps.wait(group=group)
|
|
389
|
-
|
|
387
|
+
SSX_LOGGER.debug("Finished setting up for long delays.")
|
|
390
388
|
|
|
391
389
|
|
|
392
390
|
def reset_pc_gate_and_pulse(zebra: Zebra, group: str = "reset_pc"):
|
|
@@ -444,16 +442,16 @@ def zebra_return_to_normal_plan(
|
|
|
444
442
|
|
|
445
443
|
if wait:
|
|
446
444
|
yield from bps.wait(group)
|
|
447
|
-
|
|
445
|
+
SSX_LOGGER.info("Zebra settings back to normal.")
|
|
448
446
|
|
|
449
447
|
|
|
450
448
|
def reset_zebra_when_collection_done_plan(zebra: Zebra):
|
|
451
449
|
"""
|
|
452
450
|
End of collection zebra operations: close fast shutter, disarm and reset settings.
|
|
453
451
|
"""
|
|
454
|
-
|
|
452
|
+
SSX_LOGGER.debug("Close the fast shutter.")
|
|
455
453
|
yield from close_fast_shutter(zebra)
|
|
456
|
-
|
|
454
|
+
SSX_LOGGER.debug("Disarm the zebra.")
|
|
457
455
|
yield from disarm_zebra(zebra)
|
|
458
|
-
|
|
456
|
+
SSX_LOGGER.debug("Set zebra back to normal.")
|
|
459
457
|
yield from zebra_return_to_normal_plan(zebra, wait=True)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
import os
|
|
3
2
|
import pathlib
|
|
4
3
|
import pprint
|
|
@@ -9,14 +8,13 @@ from typing import Literal
|
|
|
9
8
|
import requests
|
|
10
9
|
|
|
11
10
|
from mx_bluesky.beamlines.i24.serial.fixed_target.ft_utils import ChipType, MappingType
|
|
11
|
+
from mx_bluesky.beamlines.i24.serial.log import SSX_LOGGER
|
|
12
12
|
from mx_bluesky.beamlines.i24.serial.parameters import (
|
|
13
13
|
ExtruderParameters,
|
|
14
14
|
FixedTargetParameters,
|
|
15
15
|
)
|
|
16
16
|
from mx_bluesky.beamlines.i24.serial.setup_beamline import Eiger, caget, cagetstring
|
|
17
17
|
|
|
18
|
-
logger = logging.getLogger("I24ssx.nexus_writer")
|
|
19
|
-
|
|
20
18
|
|
|
21
19
|
def call_nexgen(
|
|
22
20
|
chip_prog_dict: dict | None,
|
|
@@ -54,23 +52,23 @@ def call_nexgen(
|
|
|
54
52
|
)
|
|
55
53
|
t0 = time.time()
|
|
56
54
|
max_wait = 60 # seconds
|
|
57
|
-
|
|
55
|
+
SSX_LOGGER.info(f"Watching for {meta_h5}")
|
|
58
56
|
while time.time() - t0 < max_wait:
|
|
59
57
|
if meta_h5.exists():
|
|
60
|
-
|
|
58
|
+
SSX_LOGGER.info(f"Found {meta_h5} after {time.time() - t0:.1f} seconds")
|
|
61
59
|
time.sleep(5)
|
|
62
60
|
break
|
|
63
|
-
|
|
61
|
+
SSX_LOGGER.debug(f"Waiting for {meta_h5}")
|
|
64
62
|
time.sleep(1)
|
|
65
63
|
if not meta_h5.exists():
|
|
66
|
-
|
|
64
|
+
SSX_LOGGER.warning(f"Giving up waiting for {meta_h5} after {max_wait} seconds")
|
|
67
65
|
return False
|
|
68
66
|
|
|
69
67
|
transmission = (float(caget(Eiger.pv.transmission)),)
|
|
70
68
|
|
|
71
69
|
if det_type == Eiger.name:
|
|
72
70
|
bit_depth = int(caget(Eiger.pv.bit_depth))
|
|
73
|
-
|
|
71
|
+
SSX_LOGGER.debug(
|
|
74
72
|
f"Call to nexgen server with the following chip definition: \n{chip_prog_dict}"
|
|
75
73
|
)
|
|
76
74
|
|
|
@@ -96,10 +94,12 @@ def call_nexgen(
|
|
|
96
94
|
"wavelength": wavelength,
|
|
97
95
|
"bit_depth": bit_depth,
|
|
98
96
|
}
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
SSX_LOGGER.info(f"Sending POST request to {url} with payload:")
|
|
98
|
+
SSX_LOGGER.info(pprint.pformat(payload))
|
|
101
99
|
response = requests.post(url, headers=headers, json=payload)
|
|
102
|
-
|
|
100
|
+
SSX_LOGGER.info(
|
|
101
|
+
f"Response: {response.text} (status code: {response.status_code})"
|
|
102
|
+
)
|
|
103
103
|
# the following will raise an error if the request was unsuccessful
|
|
104
104
|
return response.status_code == requests.codes.ok
|
|
105
105
|
return False
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import bluesky.plan_stubs as bps
|
|
2
|
+
from dodal.devices.eiger import EigerDetector
|
|
3
|
+
|
|
4
|
+
from mx_bluesky.common.parameters.constants import DocDescriptorNames
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def read_hardware_for_zocalo(detector: EigerDetector):
|
|
8
|
+
""" "
|
|
9
|
+
If the RunEngine is subscribed to the ZocaloCallback, this plan will also trigger zocalo.
|
|
10
|
+
A bluesky run must be open to use this plan
|
|
11
|
+
"""
|
|
12
|
+
yield from bps.create(name=DocDescriptorNames.ZOCALO_HW_READ)
|
|
13
|
+
yield from bps.read(detector.odin.file_writer.id) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
|
|
14
|
+
yield from bps.save()
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from functools import cache
|
|
3
|
+
|
|
4
|
+
from daq_config_server.client import ConfigServer
|
|
5
|
+
from pydantic import BaseModel, Field, model_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FeatureFlags(BaseModel, ABC):
|
|
9
|
+
"""Abstract class to use ConfigServer to toggle features for an experiment
|
|
10
|
+
|
|
11
|
+
A module wanting to use FeatureFlags should inherit this class, add boolean features
|
|
12
|
+
as attributes, and implement a get_config_server method, which returns a cached creation of
|
|
13
|
+
ConfigServer. See HyperionFeatureFlags for an example
|
|
14
|
+
|
|
15
|
+
Values supplied upon class instantiation will always take priority over the config server. If connection to the server cannot
|
|
16
|
+
be made AND values were not supplied, attributes will use their default values
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# Feature values supplied at construction will override values from the config server
|
|
20
|
+
overriden_features: dict = Field(default_factory=dict, exclude=True)
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
@cache
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def get_config_server() -> ConfigServer: ...
|
|
26
|
+
|
|
27
|
+
@model_validator(mode="before")
|
|
28
|
+
@classmethod
|
|
29
|
+
def mark_overridden_features(cls, values):
|
|
30
|
+
assert isinstance(values, dict)
|
|
31
|
+
values["overriden_features"] = values.copy()
|
|
32
|
+
return values
|
|
33
|
+
|
|
34
|
+
def _get_flags(self):
|
|
35
|
+
flags = type(self).get_config_server().best_effort_get_all_feature_flags()
|
|
36
|
+
return {f: flags[f] for f in flags if f in self.model_fields.keys()}
|
|
37
|
+
|
|
38
|
+
def update_self_from_server(self):
|
|
39
|
+
"""Used to update the feature flags from the server during a plan. Where there are flags which were explicitly set from externally supplied parameters, these values will be used instead."""
|
|
40
|
+
for flag, value in self._get_flags().items():
|
|
41
|
+
updated_value = (
|
|
42
|
+
value
|
|
43
|
+
if flag not in self.overriden_features.keys()
|
|
44
|
+
else self.overriden_features[flag]
|
|
45
|
+
)
|
|
46
|
+
setattr(self, flag, updated_value)
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from abc import abstractmethod
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from enum import StrEnum
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Literal, SupportsInt, cast
|
|
9
|
+
|
|
10
|
+
from dodal.devices.aperturescatterguard import ApertureValue
|
|
11
|
+
from dodal.devices.detector import (
|
|
12
|
+
DetectorParams,
|
|
13
|
+
TriggerMode,
|
|
14
|
+
)
|
|
15
|
+
from pydantic import (
|
|
16
|
+
BaseModel,
|
|
17
|
+
ConfigDict,
|
|
18
|
+
Field,
|
|
19
|
+
field_validator,
|
|
20
|
+
model_validator,
|
|
21
|
+
)
|
|
22
|
+
from pydantic_extra_types.semantic_version import SemanticVersion
|
|
23
|
+
from scanspec.core import AxesPoints
|
|
24
|
+
from semver import Version
|
|
25
|
+
|
|
26
|
+
from mx_bluesky.common.parameters.constants import (
|
|
27
|
+
TEST_MODE,
|
|
28
|
+
DetectorParamConstants,
|
|
29
|
+
GridscanParamConstants,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
PARAMETER_VERSION = Version.parse("5.2.0")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class RotationAxis(StrEnum):
|
|
36
|
+
OMEGA = "omega"
|
|
37
|
+
PHI = "phi"
|
|
38
|
+
CHI = "chi"
|
|
39
|
+
KAPPA = "kappa"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class XyzAxis(StrEnum):
|
|
43
|
+
X = "sam_x"
|
|
44
|
+
Y = "sam_y"
|
|
45
|
+
Z = "sam_z"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class IspybExperimentType(StrEnum):
|
|
49
|
+
# Enum values from ispyb column data type
|
|
50
|
+
SAD = "SAD" # at or slightly above the peak
|
|
51
|
+
SAD_INVERSE_BEAM = "SAD - Inverse Beam"
|
|
52
|
+
OSC = "OSC" # "native" (in the absence of a heavy atom)
|
|
53
|
+
COLLECT_MULTIWEDGE = (
|
|
54
|
+
"Collect - Multiwedge" # "poorly determined" ~ EDNA complex strategy???
|
|
55
|
+
)
|
|
56
|
+
MAD = "MAD"
|
|
57
|
+
HELICAL = "Helical"
|
|
58
|
+
MULTI_POSITIONAL = "Multi-positional"
|
|
59
|
+
MESH = "Mesh"
|
|
60
|
+
BURN = "Burn"
|
|
61
|
+
MAD_INVERSE_BEAM = "MAD - Inverse Beam"
|
|
62
|
+
CHARACTERIZATION = "Characterization"
|
|
63
|
+
DEHYDRATION = "Dehydration"
|
|
64
|
+
TOMO = "tomo"
|
|
65
|
+
EXPERIMENT = "experiment"
|
|
66
|
+
EM = "EM"
|
|
67
|
+
PDF = "PDF"
|
|
68
|
+
PDF_BRAGG = "PDF+Bragg"
|
|
69
|
+
BRAGG = "Bragg"
|
|
70
|
+
SINGLE_PARTICLE = "single particle"
|
|
71
|
+
SERIAL_FIXED = "Serial Fixed"
|
|
72
|
+
SERIAL_JET = "Serial Jet"
|
|
73
|
+
STANDARD = "Standard" # Routine structure determination experiment
|
|
74
|
+
TIME_RESOLVED = "Time Resolved" # Investigate the change of a system over time
|
|
75
|
+
DLS_ANVIL_HP = "Diamond Anvil High Pressure" # HP sample environment pressure cell
|
|
76
|
+
CUSTOM = "Custom" # Special or non-standard data collection
|
|
77
|
+
XRF_MAP = "XRF map"
|
|
78
|
+
ENERGY_SCAN = "Energy scan"
|
|
79
|
+
XRF_SPECTRUM = "XRF spectrum"
|
|
80
|
+
XRF_MAP_XAS = "XRF map xas"
|
|
81
|
+
MESH_3D = "Mesh3D"
|
|
82
|
+
SCREENING = "Screening"
|
|
83
|
+
STILL = "Still"
|
|
84
|
+
SSX_CHIP = "SSX-Chip"
|
|
85
|
+
SSX_JET = "SSX-Jet"
|
|
86
|
+
|
|
87
|
+
# Aliases for historic hyperion experiment type mapping
|
|
88
|
+
ROTATION = "SAD"
|
|
89
|
+
GRIDSCAN_2D = "mesh"
|
|
90
|
+
GRIDSCAN_3D = "Mesh3D"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class MxBlueskyParameters(BaseModel):
|
|
94
|
+
model_config = ConfigDict(
|
|
95
|
+
arbitrary_types_allowed=True,
|
|
96
|
+
extra="allow",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def __hash__(self) -> int:
|
|
100
|
+
return self.model_dump_json().__hash__()
|
|
101
|
+
|
|
102
|
+
parameter_model_version: SemanticVersion
|
|
103
|
+
|
|
104
|
+
@field_validator("parameter_model_version")
|
|
105
|
+
@classmethod
|
|
106
|
+
def _validate_version(cls, version: Version):
|
|
107
|
+
assert (
|
|
108
|
+
version >= Version(major=PARAMETER_VERSION.major)
|
|
109
|
+
), f"Parameter version too old! This version of hyperion uses {PARAMETER_VERSION}"
|
|
110
|
+
assert (
|
|
111
|
+
version <= Version(major=PARAMETER_VERSION.major + 1)
|
|
112
|
+
), f"Parameter version too new! This version of hyperion uses {PARAMETER_VERSION}"
|
|
113
|
+
return version
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class WithSnapshot(BaseModel):
|
|
117
|
+
snapshot_directory: Path
|
|
118
|
+
snapshot_omegas_deg: list[float] | None = None
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def take_snapshots(self) -> bool:
|
|
122
|
+
return bool(self.snapshot_omegas_deg)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class WithOptionalEnergyChange(BaseModel):
|
|
126
|
+
demand_energy_ev: float | None = Field(default=None, gt=0)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class WithVisit(BaseModel):
|
|
130
|
+
beamline: str = Field(default="BL03I", pattern=r"BL\d{2}[BIJS]")
|
|
131
|
+
visit: str = Field(min_length=1)
|
|
132
|
+
det_dist_to_beam_converter_path: str = Field(
|
|
133
|
+
default=DetectorParamConstants.BEAM_XY_LUT_PATH
|
|
134
|
+
)
|
|
135
|
+
insertion_prefix: str = "SR03S" if TEST_MODE else "SR03I"
|
|
136
|
+
detector_distance_mm: float | None = Field(default=None, gt=0)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class DiffractionExperiment(
|
|
140
|
+
MxBlueskyParameters, WithSnapshot, WithOptionalEnergyChange, WithVisit
|
|
141
|
+
):
|
|
142
|
+
"""For all experiments which use beam"""
|
|
143
|
+
|
|
144
|
+
file_name: str
|
|
145
|
+
exposure_time_s: float = Field(gt=0)
|
|
146
|
+
comment: str = Field(default="")
|
|
147
|
+
trigger_mode: TriggerMode = Field(default=TriggerMode.FREE_RUN)
|
|
148
|
+
run_number: int | None = Field(default=None, ge=0)
|
|
149
|
+
selected_aperture: ApertureValue | None = Field(default=None)
|
|
150
|
+
transmission_frac: float = Field(default=0.1)
|
|
151
|
+
ispyb_experiment_type: IspybExperimentType
|
|
152
|
+
storage_directory: str
|
|
153
|
+
|
|
154
|
+
@model_validator(mode="before")
|
|
155
|
+
@classmethod
|
|
156
|
+
def validate_directories(cls, values):
|
|
157
|
+
os.makedirs(values["storage_directory"], exist_ok=True)
|
|
158
|
+
|
|
159
|
+
values["snapshot_directory"] = values.get(
|
|
160
|
+
"snapshot_directory",
|
|
161
|
+
Path(values["storage_directory"], "snapshots").as_posix(),
|
|
162
|
+
)
|
|
163
|
+
return values
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def num_images(self) -> int:
|
|
167
|
+
return 0
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
@abstractmethod
|
|
171
|
+
def detector_params(self) -> DetectorParams: ...
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class WithScan(BaseModel):
|
|
175
|
+
"""For experiments where the scan is known"""
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
@abstractmethod
|
|
179
|
+
def scan_points(self) -> AxesPoints: ...
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
@abstractmethod
|
|
183
|
+
def num_images(self) -> int: ...
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class WithPandaGridScan(BaseModel):
|
|
187
|
+
"""For experiments which use a PandA for constant-motion grid scans"""
|
|
188
|
+
|
|
189
|
+
panda_runup_distance_mm: float = Field(
|
|
190
|
+
default=GridscanParamConstants.PANDA_RUN_UP_DISTANCE_MM
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class SplitScan(BaseModel):
|
|
195
|
+
@property
|
|
196
|
+
@abstractmethod
|
|
197
|
+
def scan_indices(self) -> Sequence[SupportsInt]:
|
|
198
|
+
"""Should return the first index of each scan (i.e. for each nexus file)"""
|
|
199
|
+
...
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class WithSample(BaseModel):
|
|
203
|
+
sample_id: int
|
|
204
|
+
sample_puck: int | None = None
|
|
205
|
+
sample_pin: int | None = None
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class DiffractionExperimentWithSample(DiffractionExperiment, WithSample): ...
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class MultiXtalSelection(BaseModel):
|
|
212
|
+
name: str
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class TopNByMaxCountSelection(MultiXtalSelection):
|
|
216
|
+
name: Literal["TopNByMaxCount"] = "TopNByMaxCount" # pyright: ignore [reportIncompatibleVariableOverride]
|
|
217
|
+
n: int
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class WithCentreSelection(BaseModel):
|
|
221
|
+
select_centres: TopNByMaxCountSelection = Field(
|
|
222
|
+
discriminator="name", default=TopNByMaxCountSelection(n=1)
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def selection_params(self) -> MultiXtalSelection:
|
|
227
|
+
"""A helper property because pydantic does not allow polymorphism with base classes
|
|
228
|
+
# only type unions"""
|
|
229
|
+
cast1 = cast(MultiXtalSelection, self.select_centres)
|
|
230
|
+
return cast1
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class OptionalXyzStarts(BaseModel):
|
|
234
|
+
x_start_um: float | None = None
|
|
235
|
+
y_start_um: float | None = None
|
|
236
|
+
z_start_um: float | None = None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class XyzStarts(BaseModel):
|
|
240
|
+
x_start_um: float
|
|
241
|
+
y_start_um: float
|
|
242
|
+
z_start_um: float
|
|
243
|
+
|
|
244
|
+
def _start_for_axis(self, axis: XyzAxis) -> float:
|
|
245
|
+
match axis:
|
|
246
|
+
case XyzAxis.X:
|
|
247
|
+
return self.x_start_um
|
|
248
|
+
case XyzAxis.Y:
|
|
249
|
+
return self.y_start_um
|
|
250
|
+
case XyzAxis.Z:
|
|
251
|
+
return self.z_start_um
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class OptionalGonioAngleStarts(BaseModel):
|
|
255
|
+
omega_start_deg: float | None = None
|
|
256
|
+
phi_start_deg: float | None = None
|
|
257
|
+
chi_start_deg: float | None = None
|
|
258
|
+
kappa_start_deg: float | None = None
|