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
|
@@ -55,12 +55,12 @@ def get_zebra_settings_for_extruder(
|
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
def arm_zebra(zebra: Zebra):
|
|
58
|
-
yield from bps.abs_set(zebra.pc.arm, ArmDemand.ARM, wait=True)
|
|
58
|
+
yield from bps.abs_set(zebra.pc.arm, ArmDemand.ARM, wait=True)
|
|
59
59
|
SSX_LOGGER.info("Zebra armed.")
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
def disarm_zebra(zebra: Zebra):
|
|
63
|
-
yield from bps.abs_set(zebra.pc.arm, ArmDemand.DISARM, wait=True)
|
|
63
|
+
yield from bps.abs_set(zebra.pc.arm, ArmDemand.DISARM, wait=True)
|
|
64
64
|
SSX_LOGGER.info("Zebra disarmed.")
|
|
65
65
|
|
|
66
66
|
|
|
@@ -272,7 +272,7 @@ def setup_zebra_for_fastchip_plan(
|
|
|
272
272
|
det_type: str,
|
|
273
273
|
num_gates: int,
|
|
274
274
|
num_exposures: int,
|
|
275
|
-
|
|
275
|
+
exposure_time_s: float,
|
|
276
276
|
start_time_offset: float = 0.0,
|
|
277
277
|
group: str = "setup_zebra_for_fastchip",
|
|
278
278
|
wait: bool = True,
|
|
@@ -303,7 +303,7 @@ def setup_zebra_for_fastchip_plan(
|
|
|
303
303
|
det_type (str): Detector in use, current choices are Eiger or Pilatus.
|
|
304
304
|
num_gates (int): Number of apertures to visit in a chip.
|
|
305
305
|
num_exposures (int): Number of times data is collected in each aperture.
|
|
306
|
-
|
|
306
|
+
exposure_time_s (float): Exposure time for each shot.
|
|
307
307
|
start_time_offset (float): Delay on the start of the position compare. \
|
|
308
308
|
Defaults to 0.0 (standard chip collection).
|
|
309
309
|
"""
|
|
@@ -341,10 +341,12 @@ def setup_zebra_for_fastchip_plan(
|
|
|
341
341
|
)
|
|
342
342
|
|
|
343
343
|
# Square wave - needs a small drop to make it work for eiger
|
|
344
|
-
pulse_width =
|
|
344
|
+
pulse_width = (
|
|
345
|
+
exposure_time_s - 0.0001 if det_type == "eiger" else exposure_time_s / 2
|
|
346
|
+
)
|
|
345
347
|
|
|
346
348
|
# 100us buffer needed to avoid missing some of the triggers
|
|
347
|
-
exptime_buffer =
|
|
349
|
+
exptime_buffer = exposure_time_s + 0.0001
|
|
348
350
|
|
|
349
351
|
# Number of gates is the number of windows collected
|
|
350
352
|
yield from bps.abs_set(zebra.pc.num_gates, num_gates, group=group)
|
|
@@ -362,7 +364,7 @@ def setup_zebra_for_fastchip_plan(
|
|
|
362
364
|
def open_fast_shutter_at_each_position_plan(
|
|
363
365
|
zebra: Zebra,
|
|
364
366
|
num_exposures: int,
|
|
365
|
-
|
|
367
|
+
exposure_time_s: float,
|
|
366
368
|
group: str = "fast_shutter_control",
|
|
367
369
|
wait: bool = True,
|
|
368
370
|
):
|
|
@@ -384,7 +386,7 @@ def open_fast_shutter_at_each_position_plan(
|
|
|
384
386
|
Args:
|
|
385
387
|
zebra (Zebra): The zebra ophyd device.
|
|
386
388
|
num_exposures (int): Number of times data is collected in each aperture.
|
|
387
|
-
|
|
389
|
+
exposure_time_s (float): Exposure time for each shot.
|
|
388
390
|
"""
|
|
389
391
|
SSX_LOGGER.info(
|
|
390
392
|
"ZEBRA setup for fastchip collection with long delays between exposures."
|
|
@@ -395,7 +397,7 @@ def open_fast_shutter_at_each_position_plan(
|
|
|
395
397
|
zebra.output.pulse_2.input, zebra.mapping.sources.PC_GATE, group=group
|
|
396
398
|
)
|
|
397
399
|
yield from bps.abs_set(zebra.output.pulse_2.delay, 0.0, group=group)
|
|
398
|
-
pulse2_width = num_exposures *
|
|
400
|
+
pulse2_width = num_exposures * exposure_time_s + SHUTTER_OPEN_TIME
|
|
399
401
|
yield from bps.abs_set(zebra.output.pulse_2.width, pulse2_width, group=group)
|
|
400
402
|
|
|
401
403
|
# Fast shutter
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# from collections.abc import Sequence
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
import bluesky.plan_stubs as bps
|
|
5
|
+
import bluesky.preprocessors as bpp
|
|
6
|
+
from blueapi.core import MsgGenerator
|
|
7
|
+
from dodal.beamlines import i24
|
|
8
|
+
|
|
9
|
+
from mx_bluesky.beamlines.i24.serial.fixed_target.ft_utils import (
|
|
10
|
+
ChipType,
|
|
11
|
+
MappingType,
|
|
12
|
+
PumpProbeSetting,
|
|
13
|
+
)
|
|
14
|
+
from mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_moveonclick import (
|
|
15
|
+
_move_on_mouse_click_plan,
|
|
16
|
+
)
|
|
17
|
+
from mx_bluesky.beamlines.i24.serial.log import _read_visit_directory_from_file
|
|
18
|
+
from mx_bluesky.beamlines.i24.serial.parameters import (
|
|
19
|
+
FixedTargetParameters,
|
|
20
|
+
get_chip_format,
|
|
21
|
+
)
|
|
22
|
+
from mx_bluesky.beamlines.i24.serial.setup_beamline import pv
|
|
23
|
+
from mx_bluesky.beamlines.i24.serial.setup_beamline.ca import caput
|
|
24
|
+
from mx_bluesky.beamlines.i24.serial.setup_beamline.pv_abstract import Eiger, Pilatus
|
|
25
|
+
from mx_bluesky.beamlines.i24.serial.setup_beamline.setup_detector import (
|
|
26
|
+
_move_detector_stage,
|
|
27
|
+
get_detector_type,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@bpp.run_decorator()
|
|
32
|
+
def gui_stage_move_on_click(position_px: tuple[int, int]) -> MsgGenerator:
|
|
33
|
+
oav = i24.oav()
|
|
34
|
+
pmac = i24.pmac()
|
|
35
|
+
yield from _move_on_mouse_click_plan(oav, pmac, position_px)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@bpp.run_decorator()
|
|
39
|
+
def gui_gonio_move_on_click(position_px: tuple[int, int]) -> MsgGenerator:
|
|
40
|
+
oav = i24.oav()
|
|
41
|
+
gonio = i24.vgonio()
|
|
42
|
+
|
|
43
|
+
x_pixels_per_micron = yield from bps.rd(oav.microns_per_pixel_x)
|
|
44
|
+
y_pixels_per_micron = yield from bps.rd(oav.microns_per_pixel_y)
|
|
45
|
+
|
|
46
|
+
x_um = position_px[0] * x_pixels_per_micron
|
|
47
|
+
y_um = position_px[1] * y_pixels_per_micron
|
|
48
|
+
|
|
49
|
+
# gonio is in mm?
|
|
50
|
+
yield from bps.mv(gonio.x, x_um / 1000, gonio.yh, y_um / 1000) # type: ignore
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# See https://github.com/DiamondLightSource/mx-bluesky/issues/853
|
|
54
|
+
@bpp.run_decorator()
|
|
55
|
+
def gui_sleep(sec: int) -> MsgGenerator:
|
|
56
|
+
for _ in range(sec):
|
|
57
|
+
yield from bps.sleep(1)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@bpp.run_decorator()
|
|
61
|
+
def gui_move_detector(det: Literal["eiger", "pilatus"]) -> MsgGenerator:
|
|
62
|
+
detector_stage = i24.detector_motion()
|
|
63
|
+
det_y_target = Eiger.det_y_target if det == "eiger" else Pilatus.det_y_target
|
|
64
|
+
yield from _move_detector_stage(detector_stage, det_y_target)
|
|
65
|
+
# Make the output readable
|
|
66
|
+
caput(pv.me14e_gp101, det)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@bpp.run_decorator()
|
|
70
|
+
def gui_set_parameters(
|
|
71
|
+
sub_dir: str,
|
|
72
|
+
chip_name: str,
|
|
73
|
+
exp_time: float,
|
|
74
|
+
det_dist: float,
|
|
75
|
+
transmission: float,
|
|
76
|
+
n_shots: int,
|
|
77
|
+
chip_type: str,
|
|
78
|
+
checker_pattern: bool,
|
|
79
|
+
pump_probe: str,
|
|
80
|
+
laser_dwell: float,
|
|
81
|
+
laser_delay: float,
|
|
82
|
+
pre_pump: float,
|
|
83
|
+
) -> MsgGenerator:
|
|
84
|
+
# NOTE still a work in progress, adding to it as the ui grows
|
|
85
|
+
detector_stage = i24.detector_motion()
|
|
86
|
+
det_type = yield from get_detector_type(detector_stage)
|
|
87
|
+
chip_params = get_chip_format(ChipType[chip_type])
|
|
88
|
+
|
|
89
|
+
params = {
|
|
90
|
+
"visit": _read_visit_directory_from_file().as_posix(), # noqa
|
|
91
|
+
"directory": sub_dir,
|
|
92
|
+
"filename": chip_name,
|
|
93
|
+
"exposure_time_s": exp_time,
|
|
94
|
+
"detector_distance_mm": det_dist,
|
|
95
|
+
"detector_name": str(det_type),
|
|
96
|
+
"num_exposures": n_shots,
|
|
97
|
+
"transmission": transmission,
|
|
98
|
+
"chip": chip_params,
|
|
99
|
+
"map_type": MappingType.NoMap,
|
|
100
|
+
"chip_map": [],
|
|
101
|
+
"pump_repeat": PumpProbeSetting[pump_probe], # pump_repeat,
|
|
102
|
+
"laser_dwell_s": laser_dwell,
|
|
103
|
+
"laser_delay_s": laser_delay,
|
|
104
|
+
"checker_pattern": checker_pattern,
|
|
105
|
+
"pre_pump_exposure_s": pre_pump,
|
|
106
|
+
}
|
|
107
|
+
print(FixedTargetParameters(**params))
|
|
108
|
+
# This will then run the run_fixed_target plan
|
|
109
|
+
yield from bps.sleep(0.5)
|
|
@@ -4,6 +4,7 @@ import pprint
|
|
|
4
4
|
import time
|
|
5
5
|
from datetime import datetime
|
|
6
6
|
|
|
7
|
+
import bluesky.plan_stubs as bps
|
|
7
8
|
import requests
|
|
8
9
|
|
|
9
10
|
from mx_bluesky.beamlines.i24.serial.fixed_target.ft_utils import ChipType, MappingType
|
|
@@ -12,7 +13,7 @@ from mx_bluesky.beamlines.i24.serial.parameters import (
|
|
|
12
13
|
ExtruderParameters,
|
|
13
14
|
FixedTargetParameters,
|
|
14
15
|
)
|
|
15
|
-
from mx_bluesky.beamlines.i24.serial.setup_beamline import Eiger, caget
|
|
16
|
+
from mx_bluesky.beamlines.i24.serial.setup_beamline import Eiger, caget, cagetstring
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def call_nexgen(
|
|
@@ -53,7 +54,7 @@ def call_nexgen(
|
|
|
53
54
|
total_numb_imgs = parameters.num_images
|
|
54
55
|
pump_status = parameters.pump_status
|
|
55
56
|
|
|
56
|
-
filename_prefix =
|
|
57
|
+
filename_prefix = cagetstring(Eiger.pv.filenameRBV)
|
|
57
58
|
meta_h5 = parameters.visit / parameters.directory / f"{filename_prefix}_meta.h5"
|
|
58
59
|
t0 = time.time()
|
|
59
60
|
max_wait = 60 # seconds
|
|
@@ -61,10 +62,10 @@ def call_nexgen(
|
|
|
61
62
|
while time.time() - t0 < max_wait:
|
|
62
63
|
if meta_h5.exists():
|
|
63
64
|
SSX_LOGGER.info(f"Found {meta_h5} after {time.time() - t0:.1f} seconds")
|
|
64
|
-
|
|
65
|
+
yield from bps.sleep(5)
|
|
65
66
|
break
|
|
66
67
|
SSX_LOGGER.debug(f"Waiting for {meta_h5}")
|
|
67
|
-
|
|
68
|
+
yield from bps.sleep(1)
|
|
68
69
|
if not meta_h5.exists():
|
|
69
70
|
SSX_LOGGER.warning(f"Giving up waiting for {meta_h5} after {max_wait} seconds")
|
|
70
71
|
return
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from bluesky import plan_stubs as bps
|
|
2
|
+
from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
|
|
3
|
+
from dodal.devices.xbpm_feedback import Pause, XBPMFeedback
|
|
4
|
+
|
|
5
|
+
from mx_bluesky.common.utils.log import LOGGER
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def unpause_xbpm_feedback_and_set_transmission_to_1(
|
|
9
|
+
xbpm_feedback: XBPMFeedback, attenuator: BinaryFilterAttenuator
|
|
10
|
+
):
|
|
11
|
+
"""Turns the XBPM feedback back on and sets transmission to 1 so that it keeps the
|
|
12
|
+
beam aligned whilst not collecting.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
xbpm_feedback (XBPMFeedback): The XBPM device that is responsible for keeping
|
|
16
|
+
the beam in position
|
|
17
|
+
attenuator (BinaryFilterAttenuator): The attenuator used to set transmission
|
|
18
|
+
"""
|
|
19
|
+
yield from bps.mv(xbpm_feedback.pause_feedback, Pause.RUN, attenuator, 1.0) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def check_and_pause_feedback(
|
|
23
|
+
xbpm_feedback: XBPMFeedback,
|
|
24
|
+
attenuator: BinaryFilterAttenuator,
|
|
25
|
+
desired_transmission_fraction: float,
|
|
26
|
+
):
|
|
27
|
+
"""Checks that the xbpm is in position before then turning it off and setting a new
|
|
28
|
+
transmission.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
xbpm_feedback (XBPMFeedback): The XBPM device that is responsible for keeping
|
|
32
|
+
the beam in position
|
|
33
|
+
attenuator (BinaryFilterAttenuator): The attenuator used to set transmission
|
|
34
|
+
desired_transmission_fraction (float): The desired transmission to set after
|
|
35
|
+
turning XBPM feedback off.
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
yield from bps.mv(attenuator, 1.0) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
|
|
39
|
+
LOGGER.info("Waiting for XBPM feedback to be stable")
|
|
40
|
+
yield from bps.trigger(xbpm_feedback, wait=True)
|
|
41
|
+
LOGGER.info(
|
|
42
|
+
f"XPBM feedback in position, pausing and setting transmission to {desired_transmission_fraction}"
|
|
43
|
+
)
|
|
44
|
+
yield from bps.mv(xbpm_feedback.pause_feedback, Pause.PAUSE) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
|
|
45
|
+
yield from bps.mv(attenuator, desired_transmission_fraction) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
|
|
@@ -121,10 +121,8 @@ class BaseISPyBCallback(PlanReactiveCallback):
|
|
|
121
121
|
ISPYB_ZOCALO_CALLBACK_LOGGER.info(
|
|
122
122
|
"ISPyB handler received event from read hardware"
|
|
123
123
|
)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
SynchrotronMode,
|
|
127
|
-
)
|
|
124
|
+
synchrotron_mode = doc["data"]["synchrotron-synchrotron_mode"]
|
|
125
|
+
assert isinstance(synchrotron_mode, SynchrotronMode)
|
|
128
126
|
|
|
129
127
|
hwscan_data_collection_info = DataCollectionInfo(
|
|
130
128
|
undulator_gap1=doc["data"]["undulator-current_gap"],
|
|
@@ -35,7 +35,7 @@ def populate_remaining_data_collection_info(
|
|
|
35
35
|
data_collection_info.detector_id = I03_EIGER_DETECTOR
|
|
36
36
|
data_collection_info.comments = comment
|
|
37
37
|
data_collection_info.detector_distance = params.detector_params.detector_distance
|
|
38
|
-
data_collection_info.exp_time = params.detector_params.
|
|
38
|
+
data_collection_info.exp_time = params.detector_params.exposure_time_s
|
|
39
39
|
data_collection_info.imgdir = params.detector_params.directory
|
|
40
40
|
data_collection_info.imgprefix = params.detector_params.prefix
|
|
41
41
|
data_collection_info.imgsuffix = EIGER_FILE_SUFFIX
|
|
@@ -36,7 +36,7 @@ class PlanReactiveCallback(CallbackBase):
|
|
|
36
36
|
super().__init__(emit=emit)
|
|
37
37
|
self.emit_cb = emit # to avoid GC; base class only holds a WeakRef
|
|
38
38
|
self.active = False
|
|
39
|
-
self.activity_uid =
|
|
39
|
+
self.activity_uid = ""
|
|
40
40
|
self.log = log
|
|
41
41
|
|
|
42
42
|
def _run_activity_gated(self, name: str, func, doc, override=False):
|
|
@@ -76,7 +76,7 @@ class PlanReactiveCallback(CallbackBase):
|
|
|
76
76
|
do_stop = self.active
|
|
77
77
|
if doc.get("run_start") == self.activity_uid:
|
|
78
78
|
self.active = False
|
|
79
|
-
self.activity_uid =
|
|
79
|
+
self.activity_uid = ""
|
|
80
80
|
return (
|
|
81
81
|
self._run_activity_gated(
|
|
82
82
|
"stop", self.activity_gated_stop, doc, override=True
|
|
@@ -18,10 +18,11 @@ if TYPE_CHECKING:
|
|
|
18
18
|
|
|
19
19
|
class ZocaloCallback(CallbackBase):
|
|
20
20
|
"""Callback class to handle the triggering of Zocalo processing.
|
|
21
|
-
|
|
22
|
-
sub-plan, and sends a run_end signal on receiving a stop document for the same plan.
|
|
21
|
+
Will start listening for collections when {triggering_plan} has been started.
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
For every ispyb deposition that occurs inside this run the callback will send zocalo
|
|
24
|
+
a run_start signal. Once the {triggering_plan} has ended the callback will send a
|
|
25
|
+
run_end signal for all collections.
|
|
25
26
|
|
|
26
27
|
Shouldn't be subscribed directly to the RunEngine, instead should be passed to the
|
|
27
28
|
`emit` argument of an ISPyB callback which appends DCIDs to the relevant start doc.
|
|
@@ -30,7 +31,9 @@ class ZocaloCallback(CallbackBase):
|
|
|
30
31
|
def _reset_state(self):
|
|
31
32
|
self.run_uid: str | None = None
|
|
32
33
|
self.zocalo_info: list[ZocaloStartInfo] = []
|
|
34
|
+
self._started_zocalo_collections: list[ZocaloStartInfo] = []
|
|
33
35
|
self.descriptors: dict[str, EventDescriptor] = {}
|
|
36
|
+
self.start_frame = 0
|
|
34
37
|
|
|
35
38
|
def __init__(self, triggering_plan: str, zocalo_environment: str):
|
|
36
39
|
super().__init__()
|
|
@@ -42,26 +45,21 @@ class ZocaloCallback(CallbackBase):
|
|
|
42
45
|
ISPYB_ZOCALO_CALLBACK_LOGGER.info("Zocalo handler received start document.")
|
|
43
46
|
if self.triggering_plan and doc.get("subplan_name") == self.triggering_plan:
|
|
44
47
|
self.run_uid = doc.get("uid")
|
|
45
|
-
|
|
48
|
+
if self.run_uid:
|
|
46
49
|
if (
|
|
47
|
-
isinstance(
|
|
50
|
+
isinstance(scan_points := doc.get("scan_points"), list)
|
|
51
|
+
and isinstance(ispyb_ids := doc.get("ispyb_dcids"), tuple)
|
|
48
52
|
and len(ispyb_ids) > 0
|
|
49
53
|
):
|
|
50
54
|
ISPYB_ZOCALO_CALLBACK_LOGGER.info(f"Zocalo triggering for {ispyb_ids}")
|
|
51
55
|
ids_and_shape = list(zip(ispyb_ids, scan_points, strict=False))
|
|
52
|
-
start_frame = 0
|
|
53
|
-
self.zocalo_info = []
|
|
54
56
|
for idx, id_and_shape in enumerate(ids_and_shape):
|
|
55
57
|
id, shape = id_and_shape
|
|
56
58
|
num_frames = number_of_frames_from_scan_spec(shape)
|
|
57
59
|
self.zocalo_info.append(
|
|
58
|
-
ZocaloStartInfo(id, None, start_frame, num_frames, idx)
|
|
60
|
+
ZocaloStartInfo(id, None, self.start_frame, num_frames, idx)
|
|
59
61
|
)
|
|
60
|
-
start_frame += num_frames
|
|
61
|
-
else:
|
|
62
|
-
raise ISPyBDepositionNotMade(
|
|
63
|
-
f"No ISPyB IDs received by the start of {self.triggering_plan=}"
|
|
64
|
-
)
|
|
62
|
+
self.start_frame += num_frames
|
|
65
63
|
|
|
66
64
|
def descriptor(self, doc: EventDescriptor):
|
|
67
65
|
self.descriptors[doc["uid"]] = doc
|
|
@@ -73,6 +71,8 @@ class ZocaloCallback(CallbackBase):
|
|
|
73
71
|
for start_info in self.zocalo_info:
|
|
74
72
|
start_info.filename = filename
|
|
75
73
|
self.zocalo_interactor.run_start(start_info)
|
|
74
|
+
self._started_zocalo_collections.append(start_info)
|
|
75
|
+
self.zocalo_info = []
|
|
76
76
|
return doc
|
|
77
77
|
|
|
78
78
|
def stop(self, doc: RunStop):
|
|
@@ -80,7 +80,10 @@ class ZocaloCallback(CallbackBase):
|
|
|
80
80
|
ISPYB_ZOCALO_CALLBACK_LOGGER.info(
|
|
81
81
|
f"Zocalo handler received stop document, for run {doc.get('run_start')}."
|
|
82
82
|
)
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
if not self._started_zocalo_collections:
|
|
84
|
+
raise ISPyBDepositionNotMade(
|
|
85
|
+
f"No ISPyB IDs received by the end of {self.triggering_plan=}"
|
|
86
|
+
)
|
|
87
|
+
for info in self._started_zocalo_collections:
|
|
85
88
|
self.zocalo_interactor.run_end(info.ispyb_dcid)
|
|
86
89
|
self._reset_state()
|
|
File without changes
|
|
@@ -15,30 +15,43 @@ class SampleHandlingCallback(PlanReactiveCallback):
|
|
|
15
15
|
"""Intercepts exceptions from experiment plans and updates the ISPyB BLSampleStatus
|
|
16
16
|
field according to the type of exception raised."""
|
|
17
17
|
|
|
18
|
-
def __init__(self):
|
|
18
|
+
def __init__(self, record_loaded_on_success=False):
|
|
19
19
|
super().__init__(log=ISPYB_ZOCALO_CALLBACK_LOGGER)
|
|
20
20
|
self._sample_id: int | None = None
|
|
21
21
|
self._descriptor: str | None = None
|
|
22
|
+
self._run_id: str | None = None
|
|
23
|
+
|
|
24
|
+
# Record 'sample loaded' if document successfully stops
|
|
25
|
+
self.record_loaded_on_success = record_loaded_on_success
|
|
22
26
|
|
|
23
27
|
def activity_gated_start(self, doc: RunStart):
|
|
24
|
-
if not self._sample_id:
|
|
28
|
+
if not self._sample_id and self.active:
|
|
25
29
|
sample_id = doc.get("metadata", {}).get("sample_id")
|
|
26
30
|
self.log.info(f"Recording sample ID at run start {sample_id}")
|
|
27
31
|
self._sample_id = sample_id
|
|
32
|
+
self._run_id = self.activity_uid
|
|
28
33
|
|
|
29
34
|
def activity_gated_stop(self, doc: RunStop) -> RunStop:
|
|
30
|
-
if
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
if self._run_id == doc.get("run_start"):
|
|
36
|
+
expeye = ExpeyeInteraction()
|
|
37
|
+
if doc["exit_status"] != "success":
|
|
38
|
+
exception_type, message = SampleException.type_and_message_from_reason(
|
|
39
|
+
doc.get("reason", "")
|
|
40
|
+
)
|
|
41
|
+
self.log.info(
|
|
42
|
+
f"Sample handling callback intercepted exception of type {exception_type}: {message}"
|
|
43
|
+
)
|
|
44
|
+
self._record_exception(exception_type, expeye)
|
|
45
|
+
|
|
46
|
+
elif self.record_loaded_on_success:
|
|
47
|
+
self._record_loaded(expeye)
|
|
48
|
+
|
|
49
|
+
self._sample_id = None
|
|
50
|
+
self._run_id = None
|
|
51
|
+
|
|
38
52
|
return doc
|
|
39
53
|
|
|
40
|
-
def _record_exception(self, exception_type: str):
|
|
41
|
-
expeye = ExpeyeInteraction()
|
|
54
|
+
def _record_exception(self, exception_type: str, expeye: ExpeyeInteraction):
|
|
42
55
|
assert self._sample_id, "Unable to record exception due to no sample ID"
|
|
43
56
|
sample_status = self._decode_sample_status(exception_type)
|
|
44
57
|
expeye.update_sample_status(self._sample_id, sample_status)
|
|
@@ -48,3 +61,7 @@ class SampleHandlingCallback(PlanReactiveCallback):
|
|
|
48
61
|
case SampleException.__name__ | CrystalNotFoundException.__name__:
|
|
49
62
|
return BLSampleStatus.ERROR_SAMPLE
|
|
50
63
|
return BLSampleStatus.ERROR_BEAMLINE
|
|
64
|
+
|
|
65
|
+
def _record_loaded(self, expeye: ExpeyeInteraction):
|
|
66
|
+
assert self._sample_id, "Unable to record loaded state due to no sample ID"
|
|
67
|
+
expeye.update_sample_status(self._sample_id, BLSampleStatus.LOADED)
|
|
@@ -29,6 +29,7 @@ from mx_bluesky.common.external_interaction.callbacks.xray_centre.ispyb_mapping
|
|
|
29
29
|
)
|
|
30
30
|
from mx_bluesky.common.external_interaction.ispyb.data_model import (
|
|
31
31
|
DataCollectionGridInfo,
|
|
32
|
+
DataCollectionGroupInfo,
|
|
32
33
|
DataCollectionInfo,
|
|
33
34
|
DataCollectionPositionInfo,
|
|
34
35
|
Orientation,
|
|
@@ -52,6 +53,9 @@ from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER, set_dcgid_
|
|
|
52
53
|
if TYPE_CHECKING:
|
|
53
54
|
from event_model import Event, RunStart, RunStop
|
|
54
55
|
|
|
56
|
+
T = TypeVar("T", bound="GridCommon")
|
|
57
|
+
ASSERT_START_BEFORE_EVENT_DOC_MESSAGE = f"No data collection group info - event document has been emitted before a {PlanNameConstants.GRID_DETECT_AND_DO_GRIDSCAN} start document"
|
|
58
|
+
|
|
55
59
|
|
|
56
60
|
def ispyb_activation_wrapper(plan_generator: MsgGenerator, parameters):
|
|
57
61
|
return bpp.set_run_key_wrapper(
|
|
@@ -67,9 +71,6 @@ def ispyb_activation_wrapper(plan_generator: MsgGenerator, parameters):
|
|
|
67
71
|
)
|
|
68
72
|
|
|
69
73
|
|
|
70
|
-
T = TypeVar("T", bound="GridCommon")
|
|
71
|
-
|
|
72
|
-
|
|
73
74
|
class GridscanISPyBCallback(BaseISPyBCallback):
|
|
74
75
|
"""Callback class to handle the deposition of experiment parameters into the ISPyB
|
|
75
76
|
database. Listens for 'event' and 'descriptor' documents. Creates the ISpyB entry on
|
|
@@ -97,6 +98,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
97
98
|
self.param_type = param_type
|
|
98
99
|
self._start_of_fgs_uid: str | None = None
|
|
99
100
|
self._processing_start_time: float | None = None
|
|
101
|
+
self.data_collection_group_info: DataCollectionGroupInfo | None
|
|
100
102
|
|
|
101
103
|
def activity_gated_start(self, doc: RunStart):
|
|
102
104
|
if doc.get("subplan_name") == PlanNameConstants.DO_FGS:
|
|
@@ -111,12 +113,14 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
111
113
|
assert isinstance(mx_bluesky_parameters, str)
|
|
112
114
|
self.params = self.param_type.model_validate_json(mx_bluesky_parameters)
|
|
113
115
|
self.ispyb = StoreInIspyb(self.ispyb_config)
|
|
114
|
-
data_collection_group_info = populate_data_collection_group(
|
|
116
|
+
self.data_collection_group_info = populate_data_collection_group(
|
|
117
|
+
self.params
|
|
118
|
+
)
|
|
115
119
|
|
|
116
120
|
scan_data_infos = [
|
|
117
121
|
ScanDataInfo(
|
|
118
122
|
data_collection_info=populate_remaining_data_collection_info(
|
|
119
|
-
|
|
123
|
+
"MX-Bluesky: Xray centring 1 -",
|
|
120
124
|
None,
|
|
121
125
|
populate_xy_data_collection_info(
|
|
122
126
|
self.params.detector_params,
|
|
@@ -126,7 +130,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
126
130
|
),
|
|
127
131
|
ScanDataInfo(
|
|
128
132
|
data_collection_info=populate_remaining_data_collection_info(
|
|
129
|
-
|
|
133
|
+
"MX-Bluesky: Xray centring 2 -",
|
|
130
134
|
None,
|
|
131
135
|
populate_xz_data_collection_info(self.params.detector_params),
|
|
132
136
|
self.params,
|
|
@@ -135,12 +139,13 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
135
139
|
]
|
|
136
140
|
|
|
137
141
|
self.ispyb_ids = self.ispyb.begin_deposition(
|
|
138
|
-
data_collection_group_info, scan_data_infos
|
|
142
|
+
self.data_collection_group_info, scan_data_infos
|
|
139
143
|
)
|
|
140
144
|
set_dcgid_tag(self.ispyb_ids.data_collection_group_id)
|
|
141
145
|
return super().activity_gated_start(doc)
|
|
142
146
|
|
|
143
147
|
def activity_gated_event(self, doc: Event):
|
|
148
|
+
assert self.data_collection_group_info, ASSERT_START_BEFORE_EVENT_DOC_MESSAGE
|
|
144
149
|
doc = super().activity_gated_event(doc)
|
|
145
150
|
|
|
146
151
|
descriptor_name = self.descriptors[doc["descriptor"]].get("name")
|
|
@@ -151,10 +156,14 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
151
156
|
self.ispyb_ids = self.ispyb.update_deposition(
|
|
152
157
|
self.ispyb_ids, scan_data_infos
|
|
153
158
|
)
|
|
159
|
+
self.ispyb.update_data_collection_group_table(
|
|
160
|
+
self.data_collection_group_info, self.ispyb_ids.data_collection_group_id
|
|
161
|
+
)
|
|
154
162
|
|
|
155
163
|
return doc
|
|
156
164
|
|
|
157
165
|
def _handle_zocalo_read_event(self, doc):
|
|
166
|
+
assert self.data_collection_group_info, ASSERT_START_BEFORE_EVENT_DOC_MESSAGE
|
|
158
167
|
crystal_summary = ""
|
|
159
168
|
if self._processing_start_time is not None:
|
|
160
169
|
proc_time = time() - self._processing_start_time
|
|
@@ -185,6 +194,11 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
185
194
|
assert self.ispyb_ids.data_collection_ids, (
|
|
186
195
|
"No data collection to add results to"
|
|
187
196
|
)
|
|
197
|
+
|
|
198
|
+
self.data_collection_group_info.comments = (
|
|
199
|
+
self.data_collection_group_info.comments or ""
|
|
200
|
+
) + crystal_summary
|
|
201
|
+
|
|
188
202
|
self.ispyb.append_to_comment(
|
|
189
203
|
self.ispyb_ids.data_collection_ids[0], crystal_summary
|
|
190
204
|
)
|
|
@@ -192,6 +206,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
192
206
|
def _handle_oav_grid_snapshot_triggered(self, doc) -> Sequence[ScanDataInfo]:
|
|
193
207
|
assert self.ispyb_ids.data_collection_ids, "No current data collection"
|
|
194
208
|
assert self.params, "ISPyB handler didn't receive parameters!"
|
|
209
|
+
assert self.data_collection_group_info, "No data collection group"
|
|
195
210
|
data = doc["data"]
|
|
196
211
|
data_collection_id = None
|
|
197
212
|
data_collection_info = DataCollectionInfo(
|
|
@@ -220,6 +235,18 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
220
235
|
data_collection_info.comments = construct_comment_for_gridscan(
|
|
221
236
|
data_collection_grid_info
|
|
222
237
|
)
|
|
238
|
+
|
|
239
|
+
if self.data_collection_group_info.comments:
|
|
240
|
+
self.data_collection_group_info.comments += (
|
|
241
|
+
f"by {data_collection_grid_info.steps_y}."
|
|
242
|
+
)
|
|
243
|
+
else:
|
|
244
|
+
self.data_collection_group_info.comments = (
|
|
245
|
+
f"Diffraction grid scan of "
|
|
246
|
+
f"{data_collection_grid_info.steps_x} "
|
|
247
|
+
f"by {data_collection_grid_info.steps_y} "
|
|
248
|
+
)
|
|
249
|
+
|
|
223
250
|
if len(self.ispyb_ids.data_collection_ids) > self._oav_snapshot_event_idx:
|
|
224
251
|
data_collection_id = self.ispyb_ids.data_collection_ids[
|
|
225
252
|
self._oav_snapshot_event_idx
|
|
@@ -275,6 +302,9 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
275
302
|
return scan_data_infos
|
|
276
303
|
|
|
277
304
|
def activity_gated_stop(self, doc: RunStop) -> RunStop:
|
|
305
|
+
assert self.data_collection_group_info, (
|
|
306
|
+
f"No data collection group info - stop document has been emitted before a {PlanNameConstants.GRID_DETECT_AND_DO_GRIDSCAN} start document"
|
|
307
|
+
)
|
|
278
308
|
if doc.get("run_start") == self._start_of_fgs_uid:
|
|
279
309
|
self._processing_start_time = time()
|
|
280
310
|
if doc.get("run_start") == self.uid_to_finalize_on:
|
|
@@ -289,5 +319,11 @@ class GridscanISPyBCallback(BaseISPyBCallback):
|
|
|
289
319
|
)
|
|
290
320
|
if exception_type:
|
|
291
321
|
doc["reason"] = message
|
|
322
|
+
self.data_collection_group_info.comments = message
|
|
323
|
+
self.ispyb.update_data_collection_group_table(
|
|
324
|
+
self.data_collection_group_info,
|
|
325
|
+
self.ispyb_ids.data_collection_group_id,
|
|
326
|
+
)
|
|
327
|
+
self.data_collection_group_info = None
|
|
292
328
|
return super().activity_gated_stop(doc)
|
|
293
329
|
return self._tag_doc(doc)
|
|
@@ -43,7 +43,7 @@ def construct_comment_for_gridscan(grid_info: DataCollectionGridInfo) -> str:
|
|
|
43
43
|
grid_info.microns_per_pixel_y,
|
|
44
44
|
)
|
|
45
45
|
return (
|
|
46
|
-
"
|
|
46
|
+
"Diffraction grid scan of "
|
|
47
47
|
f"{grid_info.steps_x} by "
|
|
48
48
|
f"{grid_info.steps_y} images in "
|
|
49
49
|
f"{(grid_info.dx_in_mm * 1e3):.1f} um by "
|
|
@@ -79,7 +79,8 @@ class GridscanNexusFileCallback(PlanReactiveCallback):
|
|
|
79
79
|
self.descriptors[doc["uid"]] = doc
|
|
80
80
|
|
|
81
81
|
def activity_gated_event(self, doc: Event) -> Event | None:
|
|
82
|
-
|
|
82
|
+
event_descriptor = self.descriptors.get(doc["descriptor"])
|
|
83
|
+
assert event_descriptor is not None
|
|
83
84
|
if event_descriptor.get("name") == DocDescriptorNames.HARDWARE_READ_DURING:
|
|
84
85
|
data = doc["data"]
|
|
85
86
|
for nexus_writer in [self.nexus_writer_1, self.nexus_writer_2]:
|
|
@@ -2,7 +2,7 @@ import configparser
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from enum import StrEnum
|
|
4
4
|
|
|
5
|
-
from requests import patch, post
|
|
5
|
+
from requests import JSONDecodeError, patch, post
|
|
6
6
|
from requests.auth import AuthBase
|
|
7
7
|
|
|
8
8
|
from mx_bluesky.common.external_interaction.ispyb.ispyb_utils import (
|
|
@@ -34,7 +34,11 @@ def _get_base_url_and_token() -> tuple[str, str]:
|
|
|
34
34
|
def _send_and_get_response(auth, url, data, send_func) -> dict:
|
|
35
35
|
response = send_func(url, auth=auth, json=data)
|
|
36
36
|
if not response.ok:
|
|
37
|
-
|
|
37
|
+
try:
|
|
38
|
+
resp_txt = str(response.json())
|
|
39
|
+
except JSONDecodeError:
|
|
40
|
+
resp_txt = str(response)
|
|
41
|
+
raise ISPyBDepositionNotMade(f"Could not write {data} to {url}: {resp_txt}")
|
|
38
42
|
return response.json()
|
|
39
43
|
|
|
40
44
|
|