mx-bluesky 0.0.2__py3-none-any.whl → 1.1.0__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/__main__.py +1 -2
- mx_bluesky/_version.py +14 -2
- mx_bluesky/beamlines/i04/__init__.py +3 -0
- mx_bluesky/beamlines/i04/callbacks/murko_callback.py +45 -0
- mx_bluesky/beamlines/i04/thawing_plan.py +85 -0
- mx_bluesky/beamlines/i24/serial/__init__.py +49 -0
- mx_bluesky/beamlines/i24/serial/blueapi_config.yaml +12 -0
- mx_bluesky/{I24 → beamlines/i24}/serial/dcid.py +53 -41
- mx_bluesky/{I24 → beamlines/i24}/serial/extruder/EX-gui-edm/DetStage.edl +3 -4
- mx_bluesky/{I24 → beamlines/i24}/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +28 -32
- mx_bluesky/{I24 → beamlines/i24}/serial/extruder/EX-gui-edm/microdrop_alignment.edl +0 -1
- mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +516 -0
- mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/CustomChip_py3v1.edl +3 -4
- mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DetStage.edl +3 -4
- mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +273 -223
- mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/ME14E-GeneralPurpose.edl +0 -1
- mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +12 -13
- mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/PMAC_Command.edl +0 -1
- mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/Shutter_Control.edl +0 -1
- mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/microdrop_alignment.edl +0 -1
- mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/nudgechip.edl +0 -1
- mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +273 -143
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/short1-laser.png +0 -0
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/short2-laser.png +0 -0
- mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/ft_utils.py +24 -1
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +808 -0
- mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +377 -416
- mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +34 -40
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +328 -0
- mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/i24ssx_moveonclick.py +66 -48
- mx_bluesky/{I24 → beamlines/i24}/serial/log.py +66 -19
- mx_bluesky/beamlines/i24/serial/parameters/__init__.py +15 -0
- mx_bluesky/beamlines/i24/serial/parameters/constants.py +47 -0
- mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py +103 -0
- mx_bluesky/beamlines/i24/serial/parameters/fixed_target/cs/cs_maker.json +9 -0
- mx_bluesky/{I24 → beamlines/i24}/serial/parameters/fixed_target/cs/motor_direction.txt +1 -1
- mx_bluesky/{I24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/minichip-oxford.pvar +1 -1
- mx_bluesky/beamlines/i24/serial/parameters/utils.py +42 -0
- mx_bluesky/beamlines/i24/serial/run_extruder.sh +19 -0
- mx_bluesky/beamlines/i24/serial/run_fixed_target.sh +22 -0
- mx_bluesky/beamlines/i24/serial/run_serial.py +36 -0
- mx_bluesky/{I24 → beamlines/i24}/serial/set_visit_directory.sh +6 -1
- mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/pv.py +1 -62
- mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/pv_abstract.py +6 -7
- mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/setup_beamline.py +90 -269
- mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/setup_detector.py +47 -40
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +459 -0
- mx_bluesky/beamlines/i24/serial/start_blueapi.sh +28 -0
- mx_bluesky/beamlines/i24/serial/write_nexus.py +105 -0
- mx_bluesky/example.py +4 -4
- mx_bluesky/hyperion/__init__.py +1 -0
- mx_bluesky/hyperion/__main__.py +374 -0
- mx_bluesky/hyperion/device_setup_plans/__init__.py +0 -0
- mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +134 -0
- mx_bluesky/hyperion/device_setup_plans/manipulate_sample.py +110 -0
- mx_bluesky/hyperion/device_setup_plans/position_detector.py +16 -0
- mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +60 -0
- mx_bluesky/hyperion/device_setup_plans/setup_oav.py +87 -0
- mx_bluesky/hyperion/device_setup_plans/setup_panda.py +210 -0
- mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +214 -0
- mx_bluesky/hyperion/device_setup_plans/smargon.py +25 -0
- mx_bluesky/hyperion/device_setup_plans/utils.py +44 -0
- mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +93 -0
- mx_bluesky/hyperion/exceptions.py +47 -0
- mx_bluesky/hyperion/experiment_plans/__init__.py +30 -0
- mx_bluesky/hyperion/experiment_plans/experiment_registry.py +84 -0
- mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +528 -0
- mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +209 -0
- mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +173 -0
- mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +81 -0
- mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +463 -0
- mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +119 -0
- mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +164 -0
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +322 -0
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +436 -0
- mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +68 -0
- mx_bluesky/hyperion/external_interaction/__init__.py +9 -0
- mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +10 -0
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +148 -0
- mx_bluesky/hyperion/external_interaction/callbacks/aperture_change_callback.py +22 -0
- mx_bluesky/hyperion/external_interaction/callbacks/common/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +46 -0
- mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +70 -0
- mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +88 -0
- mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +203 -0
- mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +20 -0
- mx_bluesky/hyperion/external_interaction/callbacks/logging_callback.py +29 -0
- mx_bluesky/hyperion/external_interaction/callbacks/plan_reactive_callback.py +101 -0
- mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +88 -0
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +174 -0
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +17 -0
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +102 -0
- mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +269 -0
- mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_mapping.py +53 -0
- mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +95 -0
- mx_bluesky/hyperion/external_interaction/callbacks/zocalo_callback.py +92 -0
- mx_bluesky/hyperion/external_interaction/config_server.py +35 -0
- mx_bluesky/hyperion/external_interaction/exceptions.py +13 -0
- mx_bluesky/hyperion/external_interaction/ispyb/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/ispyb/data_model.py +95 -0
- mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +125 -0
- mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +276 -0
- mx_bluesky/hyperion/external_interaction/ispyb/ispyb_utils.py +29 -0
- mx_bluesky/hyperion/external_interaction/nexus/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +148 -0
- mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +114 -0
- mx_bluesky/hyperion/log.py +99 -0
- mx_bluesky/hyperion/parameters/__init__.py +2 -0
- mx_bluesky/hyperion/parameters/cli.py +68 -0
- mx_bluesky/hyperion/parameters/components.py +253 -0
- mx_bluesky/hyperion/parameters/constants.py +158 -0
- mx_bluesky/hyperion/parameters/gridscan.py +216 -0
- mx_bluesky/hyperion/parameters/rotation.py +160 -0
- mx_bluesky/hyperion/resources/panda/panda-gridscan.yaml +964 -0
- mx_bluesky/hyperion/tracing.py +28 -0
- mx_bluesky/hyperion/utils/context.py +84 -0
- mx_bluesky/hyperion/utils/utils.py +25 -0
- mx_bluesky/hyperion/utils/validation.py +196 -0
- mx_bluesky/jupyter_example.ipynb +3 -2
- {mx_bluesky-0.0.2.dist-info → mx_bluesky-1.1.0.dist-info}/METADATA +53 -32
- mx_bluesky-1.1.0.dist-info/RECORD +136 -0
- {mx_bluesky-0.0.2.dist-info → mx_bluesky-1.1.0.dist-info}/WHEEL +1 -1
- mx_bluesky-1.1.0.dist-info/entry_points.txt +8 -0
- mx_bluesky/I24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +0 -476
- mx_bluesky/I24/serial/fixed_target/FT-gui-edm/ME14E-motors.edl +0 -1874
- mx_bluesky/I24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +0 -706
- mx_bluesky/I24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +0 -463
- mx_bluesky/I24/serial/parameters/__init__.py +0 -5
- mx_bluesky/I24/serial/parameters/constants.py +0 -39
- mx_bluesky/I24/serial/parameters/fixed_target/cs/cs_maker.json +0 -9
- mx_bluesky/I24/serial/parameters/fixed_target/cs/fiducial_1.txt +0 -4
- mx_bluesky/I24/serial/parameters/fixed_target/cs/fiducial_2.txt +0 -4
- mx_bluesky/I24/serial/parameters/fixed_target/litemaps/currentchip.map +0 -81
- mx_bluesky/I24/serial/parameters/fixed_target/parameters.txt +0 -13
- mx_bluesky/I24/serial/run_serial.py +0 -52
- mx_bluesky/I24/serial/write_nexus.py +0 -113
- mx_bluesky-0.0.2.dist-info/RECORD +0 -58
- mx_bluesky-0.0.2.dist-info/entry_points.txt +0 -4
- /mx_bluesky/{I24 → beamlines}/__init__.py +0 -0
- /mx_bluesky/{I24/serial → beamlines/i24}/__init__.py +0 -0
- /mx_bluesky/{I24 → beamlines/i24}/serial/extruder/__init__.py +0 -0
- /mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/__init__.py +0 -0
- /mx_bluesky/{I24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/oxford.pvar +0 -0
- /mx_bluesky/{I24 → beamlines/i24}/serial/run_ssx.sh +0 -0
- /mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/__init__.py +0 -0
- /mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/ca.py +0 -0
- {mx_bluesky-0.0.2.dist-info → mx_bluesky-1.1.0.dist-info}/LICENSE +0 -0
- {mx_bluesky-0.0.2.dist-info → mx_bluesky-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from functools import partial
|
|
2
|
+
|
|
3
|
+
import bluesky.plan_stubs as bps
|
|
4
|
+
from dodal.devices.oav.oav_detector import OAV
|
|
5
|
+
from dodal.devices.oav.oav_errors import OAVError_ZoomLevelNotFound
|
|
6
|
+
from dodal.devices.oav.oav_parameters import OAVParameters
|
|
7
|
+
from dodal.devices.oav.pin_image_recognition import PinTipDetection
|
|
8
|
+
from dodal.devices.oav.utils import ColorMode
|
|
9
|
+
|
|
10
|
+
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
11
|
+
|
|
12
|
+
# Helper function to make sure we set the waiting groups correctly
|
|
13
|
+
set_using_group = partial(bps.abs_set, group=CONST.WAIT.READY_FOR_OAV)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def setup_pin_tip_detection_params(
|
|
17
|
+
pin_tip_detect_device: PinTipDetection, parameters: OAVParameters
|
|
18
|
+
):
|
|
19
|
+
# select which blur to apply to image
|
|
20
|
+
yield from set_using_group(
|
|
21
|
+
pin_tip_detect_device.preprocess_operation, parameters.preprocess
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# sets length scale for blurring
|
|
25
|
+
yield from set_using_group(
|
|
26
|
+
pin_tip_detect_device.preprocess_ksize, parameters.preprocess_K_size
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Canny edge detect - lower
|
|
30
|
+
yield from set_using_group(
|
|
31
|
+
pin_tip_detect_device.canny_lower_threshold,
|
|
32
|
+
parameters.canny_edge_lower_threshold,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Canny edge detect - upper
|
|
36
|
+
yield from set_using_group(
|
|
37
|
+
pin_tip_detect_device.canny_upper_threshold,
|
|
38
|
+
parameters.canny_edge_upper_threshold,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# "Close" morphological operation
|
|
42
|
+
yield from set_using_group(
|
|
43
|
+
pin_tip_detect_device.close_ksize, parameters.close_ksize
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Sample detection direction
|
|
47
|
+
yield from set_using_group(
|
|
48
|
+
pin_tip_detect_device.scan_direction, parameters.direction
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Minimum height
|
|
52
|
+
yield from set_using_group(
|
|
53
|
+
pin_tip_detect_device.min_tip_height,
|
|
54
|
+
parameters.minimum_height,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def setup_general_oav_params(oav: OAV, parameters: OAVParameters):
|
|
59
|
+
yield from set_using_group(oav.cam.color_mode, ColorMode.RGB1)
|
|
60
|
+
yield from set_using_group(oav.cam.acquire_period, parameters.acquire_period)
|
|
61
|
+
yield from set_using_group(oav.cam.acquire_time, parameters.exposure)
|
|
62
|
+
yield from set_using_group(oav.cam.gain, parameters.gain)
|
|
63
|
+
|
|
64
|
+
zoom_level_str = f"{float(parameters.zoom)}x"
|
|
65
|
+
if zoom_level_str not in oav.zoom_controller.allowed_zoom_levels:
|
|
66
|
+
raise OAVError_ZoomLevelNotFound(
|
|
67
|
+
f"Found {zoom_level_str} as a zoom level but expected one of {oav.zoom_controller.allowed_zoom_levels}"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
yield from bps.abs_set(
|
|
71
|
+
oav.zoom_controller,
|
|
72
|
+
zoom_level_str,
|
|
73
|
+
wait=True,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def pre_centring_setup_oav(
|
|
78
|
+
oav: OAV,
|
|
79
|
+
parameters: OAVParameters,
|
|
80
|
+
pin_tip_detection_device: PinTipDetection,
|
|
81
|
+
):
|
|
82
|
+
"""
|
|
83
|
+
Setup OAV PVs with required values.
|
|
84
|
+
"""
|
|
85
|
+
yield from setup_general_oav_params(oav, parameters)
|
|
86
|
+
yield from setup_pin_tip_detection_params(pin_tip_detection_device, parameters)
|
|
87
|
+
yield from bps.wait(CONST.WAIT.READY_FOR_OAV)
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from importlib import resources
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import bluesky.plan_stubs as bps
|
|
7
|
+
from blueapi.core import MsgGenerator
|
|
8
|
+
from dodal.common.beamlines.beamline_utils import get_path_provider
|
|
9
|
+
from dodal.devices.fast_grid_scan import PandAGridScanParams
|
|
10
|
+
from ophyd_async.core import load_device
|
|
11
|
+
from ophyd_async.fastcs.panda import (
|
|
12
|
+
HDFPanda,
|
|
13
|
+
SeqTable,
|
|
14
|
+
SeqTrigger,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
import mx_bluesky.hyperion.resources.panda as panda_resource
|
|
18
|
+
from mx_bluesky.hyperion.log import LOGGER
|
|
19
|
+
|
|
20
|
+
MM_TO_ENCODER_COUNTS = 200000
|
|
21
|
+
GENERAL_TIMEOUT = 60
|
|
22
|
+
TICKS_PER_MS = 1000 # Panda sequencer prescaler will be set to us
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Enabled(Enum):
|
|
26
|
+
ENABLED = "ONE"
|
|
27
|
+
DISABLED = "ZERO"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class PcapArm(Enum):
|
|
31
|
+
ARMED = "Arm"
|
|
32
|
+
DISARMED = "Disarm"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _get_seq_table(
|
|
36
|
+
parameters: PandAGridScanParams, exposure_distance_mm, time_between_steps_ms
|
|
37
|
+
) -> SeqTable:
|
|
38
|
+
"""
|
|
39
|
+
Generate the sequencer table for the panda.
|
|
40
|
+
|
|
41
|
+
- Sending a 'trigger' means trigger PCAP internally and send signal to Eiger via physical panda output
|
|
42
|
+
|
|
43
|
+
SEQUENCER TABLE:
|
|
44
|
+
|
|
45
|
+
1. Wait for physical trigger from motion script to mark start of scan / change of direction
|
|
46
|
+
2. Wait for POSA (X2) to be greater than X_START and send x_steps triggers every time_between_steps_ms
|
|
47
|
+
3. Wait for physical trigger from motion script to mark change of direction
|
|
48
|
+
4. Wait for POSA (X2) to be less than X_START + X_STEP_SIZE * x_steps + exposure distance, then
|
|
49
|
+
send x_steps triggers every time_between_steps_ms
|
|
50
|
+
5. Go back to step one.
|
|
51
|
+
|
|
52
|
+
For a more detailed explanation and a diagram, see https://github.com/DiamondLightSource/hyperion/wiki/PandA-constant%E2%80%90motion-scanning
|
|
53
|
+
|
|
54
|
+
For documentation on Panda itself, see https://pandablocks.github.io/PandABlocks-FPGA/master/index.html
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
exposure_distance_mm: The distance travelled by the sample each time the detector is exposed: exposure time * sample velocity
|
|
58
|
+
time_between_steps_ms: The time taken to traverse between each grid step.
|
|
59
|
+
parameters: Parameters for the panda gridscan
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
An instance of SeqTable describing the panda sequencer table
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
start_of_grid_x_counts = int(parameters.x_start * MM_TO_ENCODER_COUNTS)
|
|
66
|
+
|
|
67
|
+
# x_start is the first trigger point, so we need to travel to x_steps-1 for the final trigger point
|
|
68
|
+
end_of_grid_x_counts = int(
|
|
69
|
+
start_of_grid_x_counts
|
|
70
|
+
+ (parameters.x_step_size * (parameters.x_steps - 1) * MM_TO_ENCODER_COUNTS)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
exposure_distance_x_counts = int(exposure_distance_mm * MM_TO_ENCODER_COUNTS)
|
|
74
|
+
|
|
75
|
+
num_pulses = parameters.x_steps
|
|
76
|
+
|
|
77
|
+
delay_between_pulses = time_between_steps_ms * TICKS_PER_MS
|
|
78
|
+
|
|
79
|
+
PULSE_WIDTH_US = 1
|
|
80
|
+
|
|
81
|
+
assert delay_between_pulses > PULSE_WIDTH_US
|
|
82
|
+
|
|
83
|
+
# BITA_1 trigger wired from TTLIN1, this is the trigger input
|
|
84
|
+
|
|
85
|
+
# +ve direction scan
|
|
86
|
+
|
|
87
|
+
table = (
|
|
88
|
+
SeqTable.row(trigger=SeqTrigger.BITA_1, time2=1)
|
|
89
|
+
+ SeqTable.row(
|
|
90
|
+
repeats=num_pulses,
|
|
91
|
+
trigger=SeqTrigger.POSA_GT,
|
|
92
|
+
position=start_of_grid_x_counts,
|
|
93
|
+
time1=PULSE_WIDTH_US,
|
|
94
|
+
outa1=True,
|
|
95
|
+
time2=delay_between_pulses - PULSE_WIDTH_US,
|
|
96
|
+
outa2=False,
|
|
97
|
+
)
|
|
98
|
+
+
|
|
99
|
+
# -ve direction scan
|
|
100
|
+
SeqTable.row(trigger=SeqTrigger.BITA_1, time2=1)
|
|
101
|
+
+ SeqTable.row(
|
|
102
|
+
repeats=num_pulses,
|
|
103
|
+
trigger=SeqTrigger.POSA_LT,
|
|
104
|
+
position=end_of_grid_x_counts + exposure_distance_x_counts,
|
|
105
|
+
time1=PULSE_WIDTH_US,
|
|
106
|
+
outa1=True,
|
|
107
|
+
time2=delay_between_pulses - PULSE_WIDTH_US,
|
|
108
|
+
outa2=False,
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return table
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def setup_panda_for_flyscan(
|
|
116
|
+
panda: HDFPanda,
|
|
117
|
+
parameters: PandAGridScanParams,
|
|
118
|
+
initial_x: float,
|
|
119
|
+
exposure_time_s: float,
|
|
120
|
+
time_between_x_steps_ms: float,
|
|
121
|
+
sample_velocity_mm_per_s: float,
|
|
122
|
+
) -> MsgGenerator:
|
|
123
|
+
"""Configures the PandA device for a flyscan.
|
|
124
|
+
Sets PVs from a yaml file, calibrates the encoder, and
|
|
125
|
+
adjusts the sequencer table based off the grid parameters. Yaml file can be
|
|
126
|
+
created using ophyd_async.core.save_device()
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
panda (HDFPanda): The PandA Ophyd device
|
|
130
|
+
parameters (PandAGridScanParams): Grid parameters
|
|
131
|
+
initial_x (float): Motor positions at time of PandA setup
|
|
132
|
+
exposure_time_s (float): Detector exposure time per trigger
|
|
133
|
+
time_between_x_steps_ms (float): Time, in ms, between each trigger. Equal to deadtime + exposure time
|
|
134
|
+
sample_velocity_mm_per_s (float): Velocity of the sample in mm/s = x_step_size_mm * 1000 /
|
|
135
|
+
time_between_x_steps_ms
|
|
136
|
+
Returns:
|
|
137
|
+
MsgGenerator
|
|
138
|
+
|
|
139
|
+
Yields:
|
|
140
|
+
Iterator[MsgGenerator]
|
|
141
|
+
"""
|
|
142
|
+
assert parameters.x_steps > 0
|
|
143
|
+
assert time_between_x_steps_ms * 1000 >= exposure_time_s
|
|
144
|
+
assert sample_velocity_mm_per_s * exposure_time_s < parameters.x_step_size
|
|
145
|
+
|
|
146
|
+
yield from bps.stage(panda, group="panda-config")
|
|
147
|
+
|
|
148
|
+
with resources.as_file(
|
|
149
|
+
resources.files(panda_resource) / "panda-gridscan.yaml"
|
|
150
|
+
) as config_yaml_path:
|
|
151
|
+
yield from load_device(panda, str(config_yaml_path))
|
|
152
|
+
|
|
153
|
+
# Home the PandA X encoder using current motor position
|
|
154
|
+
yield from bps.abs_set(
|
|
155
|
+
panda.inenc[1].setp, # type: ignore
|
|
156
|
+
initial_x * MM_TO_ENCODER_COUNTS,
|
|
157
|
+
wait=True,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
yield from bps.abs_set(panda.pulse[1].width, exposure_time_s, group="panda-config")
|
|
161
|
+
|
|
162
|
+
exposure_distance_mm = sample_velocity_mm_per_s * exposure_time_s
|
|
163
|
+
|
|
164
|
+
table = _get_seq_table(parameters, exposure_distance_mm, time_between_x_steps_ms)
|
|
165
|
+
|
|
166
|
+
yield from bps.abs_set(panda.seq[1].table, table, group="panda-config")
|
|
167
|
+
|
|
168
|
+
yield from bps.abs_set(
|
|
169
|
+
panda.pcap.enable, # type: ignore
|
|
170
|
+
Enabled.ENABLED.value,
|
|
171
|
+
group="panda-config",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Values need to be set before blocks are enabled, so wait here
|
|
175
|
+
yield from bps.wait(group="panda-config", timeout=GENERAL_TIMEOUT)
|
|
176
|
+
|
|
177
|
+
LOGGER.info(f"PandA sequencer table has been set to: {str(table)}")
|
|
178
|
+
table_readback = yield from bps.rd(panda.seq[1].table)
|
|
179
|
+
LOGGER.debug(f"PandA sequencer table readback is: {str(table_readback)}")
|
|
180
|
+
|
|
181
|
+
yield from arm_panda_for_gridscan(panda)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def arm_panda_for_gridscan(panda: HDFPanda, group="arm_panda_gridscan"):
|
|
185
|
+
yield from bps.abs_set(panda.seq[1].enable, Enabled.ENABLED.value, group=group) # type: ignore
|
|
186
|
+
yield from bps.abs_set(panda.pulse[1].enable, Enabled.ENABLED.value, group=group) # type: ignore
|
|
187
|
+
yield from bps.abs_set(panda.counter[1].enable, Enabled.ENABLED.value, group=group) # type: ignore
|
|
188
|
+
yield from bps.abs_set(panda.pcap.arm, PcapArm.ARMED.value, group=group) # type: ignore
|
|
189
|
+
yield from bps.wait(group=group, timeout=GENERAL_TIMEOUT)
|
|
190
|
+
LOGGER.info("PandA has been armed")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def disarm_panda_for_gridscan(panda, group="disarm_panda_gridscan") -> MsgGenerator:
|
|
194
|
+
yield from bps.abs_set(panda.pcap.arm, PcapArm.DISARMED.value, group=group) # type: ignore
|
|
195
|
+
yield from bps.abs_set(panda.counter[1].enable, Enabled.DISABLED.value, group=group) # type: ignore
|
|
196
|
+
yield from bps.abs_set(panda.seq[1].enable, Enabled.DISABLED.value, group=group)
|
|
197
|
+
yield from bps.abs_set(panda.pulse[1].enable, Enabled.DISABLED.value, group=group)
|
|
198
|
+
yield from bps.abs_set(panda.pcap.enable, Enabled.DISABLED.value, group=group) # type: ignore
|
|
199
|
+
yield from bps.wait(group=group, timeout=GENERAL_TIMEOUT)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def set_panda_directory(panda_directory: Path) -> MsgGenerator:
|
|
203
|
+
"""Updates the root folder which is used by the PandA's PCAP."""
|
|
204
|
+
|
|
205
|
+
suffix = datetime.now().strftime("_%Y%m%d%H%M%S")
|
|
206
|
+
|
|
207
|
+
async def set_panda_dir():
|
|
208
|
+
await get_path_provider().update(directory=panda_directory, suffix=suffix)
|
|
209
|
+
|
|
210
|
+
yield from bps.wait_for([set_panda_dir])
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from functools import wraps
|
|
3
|
+
|
|
4
|
+
import bluesky.plan_stubs as bps
|
|
5
|
+
import bluesky.preprocessors as bpp
|
|
6
|
+
from blueapi.core import MsgGenerator
|
|
7
|
+
from dodal.devices.zebra import (
|
|
8
|
+
AUTO_SHUTTER_GATE,
|
|
9
|
+
AUTO_SHUTTER_INPUT,
|
|
10
|
+
DISCONNECT,
|
|
11
|
+
IN1_TTL,
|
|
12
|
+
IN3_TTL,
|
|
13
|
+
IN4_TTL,
|
|
14
|
+
PC_GATE,
|
|
15
|
+
PC_PULSE,
|
|
16
|
+
TTL_DETECTOR,
|
|
17
|
+
TTL_PANDA,
|
|
18
|
+
TTL_XSPRESS3,
|
|
19
|
+
ArmDemand,
|
|
20
|
+
EncEnum,
|
|
21
|
+
I03Axes,
|
|
22
|
+
RotationDirection,
|
|
23
|
+
Zebra,
|
|
24
|
+
)
|
|
25
|
+
from dodal.devices.zebra_controlled_shutter import ZebraShutter, ZebraShutterControl
|
|
26
|
+
|
|
27
|
+
from mx_bluesky.hyperion.log import LOGGER
|
|
28
|
+
|
|
29
|
+
ZEBRA_STATUS_TIMEOUT = 30
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def bluesky_retry(func: Callable):
|
|
33
|
+
"""Decorator that will retry the decorated plan if it fails.
|
|
34
|
+
|
|
35
|
+
Use this with care as it knows nothing about the state of the world when things fail.
|
|
36
|
+
If it is possible that your plan fails when the beamline is in a transient state that
|
|
37
|
+
the plan could not act on do not use this decorator without doing some more intelligent
|
|
38
|
+
clean up.
|
|
39
|
+
|
|
40
|
+
You should avoid using this decorator often in general production as it hides errors,
|
|
41
|
+
instead it should be used only for debugging these underlying errors.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
@wraps(func)
|
|
45
|
+
def newfunc(*args, **kwargs):
|
|
46
|
+
def log_and_retry(exception):
|
|
47
|
+
LOGGER.error(f"Function {func.__name__} failed with {exception}, retrying")
|
|
48
|
+
yield from func(*args, **kwargs)
|
|
49
|
+
|
|
50
|
+
yield from bpp.contingency_wrapper(
|
|
51
|
+
func(*args, **kwargs), except_plan=log_and_retry, auto_raise=False
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return newfunc
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def arm_zebra(zebra: Zebra):
|
|
58
|
+
yield from bps.abs_set(zebra.pc.arm, ArmDemand.ARM, wait=True)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def tidy_up_zebra_after_rotation_scan(
|
|
62
|
+
zebra: Zebra,
|
|
63
|
+
zebra_shutter: ZebraShutter,
|
|
64
|
+
group="tidy_up_zebra_after_rotation",
|
|
65
|
+
wait=True,
|
|
66
|
+
):
|
|
67
|
+
yield from bps.abs_set(zebra.pc.arm, ArmDemand.DISARM, group=group)
|
|
68
|
+
yield from bps.abs_set(
|
|
69
|
+
zebra_shutter.control_mode, ZebraShutterControl.MANUAL, group=group
|
|
70
|
+
)
|
|
71
|
+
if wait:
|
|
72
|
+
yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def set_shutter_auto_input(zebra: Zebra, input: int, group="set_shutter_trigger"):
|
|
76
|
+
"""Set the input that the shutter uses when set to auto.
|
|
77
|
+
|
|
78
|
+
For more details see the ZebraShutter device."""
|
|
79
|
+
auto_shutter_control = zebra.logic_gates.and_gates[AUTO_SHUTTER_GATE]
|
|
80
|
+
yield from bps.abs_set(
|
|
81
|
+
auto_shutter_control.sources[AUTO_SHUTTER_INPUT], input, group
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@bluesky_retry
|
|
86
|
+
def setup_zebra_for_rotation(
|
|
87
|
+
zebra: Zebra,
|
|
88
|
+
zebra_shutter: ZebraShutter,
|
|
89
|
+
axis: EncEnum = I03Axes.OMEGA,
|
|
90
|
+
start_angle: float = 0,
|
|
91
|
+
scan_width: float = 360,
|
|
92
|
+
shutter_opening_deg: float = 2.5,
|
|
93
|
+
shutter_opening_s: float = 0.04,
|
|
94
|
+
direction: RotationDirection = RotationDirection.POSITIVE,
|
|
95
|
+
group: str = "setup_zebra_for_rotation",
|
|
96
|
+
wait: bool = True,
|
|
97
|
+
):
|
|
98
|
+
"""Set up the Zebra to collect a rotation dataset. Any plan using this is
|
|
99
|
+
responsible for setting the smargon velocity appropriately so that the desired
|
|
100
|
+
image width is achieved with the exposure time given here.
|
|
101
|
+
|
|
102
|
+
Parameters:
|
|
103
|
+
zebra: The zebra device to use
|
|
104
|
+
axis: I03 axes enum representing which axis to use for position
|
|
105
|
+
compare. Currently always omega.
|
|
106
|
+
start_angle: Position at which the scan should begin, in degrees.
|
|
107
|
+
scan_width: Total angle through which to collect, in degrees.
|
|
108
|
+
shutter_opening_deg:How many degrees of rotation it takes for the fast shutter
|
|
109
|
+
to open. Increases the gate width.
|
|
110
|
+
shutter_opening_s: How many seconds it takes for the fast shutter to open. The
|
|
111
|
+
detector pulse is delayed after the shutter signal by this
|
|
112
|
+
amount.
|
|
113
|
+
direction: RotationDirection enum for positive or negative.
|
|
114
|
+
Defaults to Positive.
|
|
115
|
+
group: A name for the group of statuses generated
|
|
116
|
+
wait: Block until all the settings have completed
|
|
117
|
+
"""
|
|
118
|
+
if not isinstance(direction, RotationDirection):
|
|
119
|
+
raise ValueError(
|
|
120
|
+
"Disallowed rotation direction provided to Zebra setup plan. "
|
|
121
|
+
"Use RotationDirection.POSITIVE or RotationDirection.NEGATIVE."
|
|
122
|
+
)
|
|
123
|
+
yield from bps.abs_set(zebra.pc.dir, direction.value, group=group)
|
|
124
|
+
LOGGER.info("ZEBRA SETUP: START")
|
|
125
|
+
# Set gate start, adjust for shutter opening time if necessary
|
|
126
|
+
LOGGER.info(f"ZEBRA SETUP: degrees to adjust for shutter = {shutter_opening_deg}")
|
|
127
|
+
LOGGER.info(f"ZEBRA SETUP: start angle start: {start_angle}")
|
|
128
|
+
LOGGER.info(f"ZEBRA SETUP: start angle adjusted, gate start set to: {start_angle}")
|
|
129
|
+
yield from bps.abs_set(zebra.pc.gate_start, start_angle, group=group)
|
|
130
|
+
# set gate width to total width
|
|
131
|
+
yield from bps.abs_set(
|
|
132
|
+
zebra.pc.gate_width, scan_width + shutter_opening_deg, group=group
|
|
133
|
+
)
|
|
134
|
+
LOGGER.info(
|
|
135
|
+
f"Pulse start set to shutter open time, set to: {abs(shutter_opening_s)}"
|
|
136
|
+
)
|
|
137
|
+
yield from bps.abs_set(zebra.pc.pulse_start, abs(shutter_opening_s), group=group)
|
|
138
|
+
# Set gate position to be angle of interest
|
|
139
|
+
yield from bps.abs_set(zebra.pc.gate_trigger, axis.value, group=group)
|
|
140
|
+
# Trigger the shutter with the gate
|
|
141
|
+
yield from bps.abs_set(
|
|
142
|
+
zebra_shutter.control_mode, ZebraShutterControl.AUTO, group=group
|
|
143
|
+
)
|
|
144
|
+
yield from set_shutter_auto_input(zebra, PC_GATE, group=group)
|
|
145
|
+
# Trigger the detector with a pulse
|
|
146
|
+
yield from bps.abs_set(zebra.output.out_pvs[TTL_DETECTOR], PC_PULSE, group=group)
|
|
147
|
+
# Don't use the fluorescence detector
|
|
148
|
+
yield from bps.abs_set(zebra.output.out_pvs[TTL_XSPRESS3], DISCONNECT, group=group)
|
|
149
|
+
yield from bps.abs_set(zebra.output.pulse_1.input, DISCONNECT, group=group)
|
|
150
|
+
LOGGER.info(f"ZEBRA SETUP: END - {'' if wait else 'not'} waiting for completion")
|
|
151
|
+
if wait:
|
|
152
|
+
yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@bluesky_retry
|
|
156
|
+
def setup_zebra_for_gridscan(
|
|
157
|
+
zebra: Zebra,
|
|
158
|
+
zebra_shutter: ZebraShutter,
|
|
159
|
+
group="setup_zebra_for_gridscan",
|
|
160
|
+
wait=True,
|
|
161
|
+
):
|
|
162
|
+
yield from bps.abs_set(zebra.output.out_pvs[TTL_DETECTOR], IN3_TTL, group=group)
|
|
163
|
+
yield from bps.abs_set(
|
|
164
|
+
zebra_shutter.control_mode, ZebraShutterControl.AUTO, group=group
|
|
165
|
+
)
|
|
166
|
+
yield from set_shutter_auto_input(zebra, IN4_TTL, group=group)
|
|
167
|
+
yield from bps.abs_set(zebra.output.out_pvs[TTL_XSPRESS3], DISCONNECT, group=group)
|
|
168
|
+
yield from bps.abs_set(zebra.output.pulse_1.input, DISCONNECT, group=group)
|
|
169
|
+
|
|
170
|
+
if wait:
|
|
171
|
+
yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@bluesky_retry
|
|
175
|
+
def tidy_up_zebra_after_gridscan(
|
|
176
|
+
zebra: Zebra,
|
|
177
|
+
zebra_shutter: ZebraShutter,
|
|
178
|
+
group="tidy_up_zebra_after_gridscan",
|
|
179
|
+
wait=True,
|
|
180
|
+
) -> MsgGenerator:
|
|
181
|
+
yield from bps.abs_set(zebra.output.out_pvs[TTL_DETECTOR], PC_PULSE, group=group)
|
|
182
|
+
yield from bps.abs_set(
|
|
183
|
+
zebra_shutter.control_mode, ZebraShutterControl.MANUAL, group=group
|
|
184
|
+
)
|
|
185
|
+
yield from set_shutter_auto_input(zebra, PC_GATE, group=group)
|
|
186
|
+
|
|
187
|
+
if wait:
|
|
188
|
+
yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@bluesky_retry
|
|
192
|
+
def setup_zebra_for_panda_flyscan(
|
|
193
|
+
zebra: Zebra,
|
|
194
|
+
zebra_shutter: ZebraShutter,
|
|
195
|
+
group="setup_zebra_for_panda_flyscan",
|
|
196
|
+
wait=True,
|
|
197
|
+
):
|
|
198
|
+
# Forwards eiger trigger signal from panda
|
|
199
|
+
yield from bps.abs_set(zebra.output.out_pvs[TTL_DETECTOR], IN1_TTL, group=group)
|
|
200
|
+
|
|
201
|
+
# Forwards signal from PPMAC to fast shutter. High while panda PLC is running
|
|
202
|
+
yield from bps.abs_set(
|
|
203
|
+
zebra_shutter.control_mode, ZebraShutterControl.AUTO, group=group
|
|
204
|
+
)
|
|
205
|
+
yield from set_shutter_auto_input(zebra, IN4_TTL, group=group)
|
|
206
|
+
|
|
207
|
+
yield from bps.abs_set(zebra.output.out_pvs[TTL_XSPRESS3], DISCONNECT, group=group)
|
|
208
|
+
|
|
209
|
+
yield from bps.abs_set(
|
|
210
|
+
zebra.output.out_pvs[TTL_PANDA], IN3_TTL, group=group
|
|
211
|
+
) # Tells panda that motion is beginning/changing direction
|
|
212
|
+
|
|
213
|
+
if wait:
|
|
214
|
+
yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from bluesky import plan_stubs as bps
|
|
3
|
+
from dodal.devices.smargon import Smargon
|
|
4
|
+
|
|
5
|
+
from mx_bluesky.hyperion.exceptions import WarningException
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def move_smargon_warn_on_out_of_range(
|
|
9
|
+
smargon: Smargon, position: np.ndarray | list[float] | tuple[float, float, float]
|
|
10
|
+
):
|
|
11
|
+
"""Throws a WarningException if the specified position is out of range for the
|
|
12
|
+
smargon. Otherwise moves to that position."""
|
|
13
|
+
limits = yield from smargon.get_xyz_limits()
|
|
14
|
+
if not limits.position_valid(position):
|
|
15
|
+
raise WarningException(
|
|
16
|
+
"Pin tip centring failed - pin too long/short/bent and out of range"
|
|
17
|
+
)
|
|
18
|
+
yield from bps.mv(
|
|
19
|
+
smargon.x,
|
|
20
|
+
position[0],
|
|
21
|
+
smargon.y,
|
|
22
|
+
position[1],
|
|
23
|
+
smargon.z,
|
|
24
|
+
position[2],
|
|
25
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from collections.abc import Generator
|
|
2
|
+
|
|
3
|
+
from bluesky import plan_stubs as bps
|
|
4
|
+
from bluesky import preprocessors as bpp
|
|
5
|
+
from bluesky.utils import Msg
|
|
6
|
+
from dodal.devices.detector.detector_motion import DetectorMotion, ShutterState
|
|
7
|
+
from dodal.devices.eiger import EigerDetector
|
|
8
|
+
|
|
9
|
+
from mx_bluesky.hyperion.device_setup_plans.position_detector import (
|
|
10
|
+
set_detector_z_position,
|
|
11
|
+
set_shutter,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def start_preparing_data_collection_then_do_plan(
|
|
16
|
+
eiger: EigerDetector,
|
|
17
|
+
detector_motion: DetectorMotion,
|
|
18
|
+
detector_distance_mm: float | None,
|
|
19
|
+
plan_to_run: Generator[Msg, None, None],
|
|
20
|
+
group="ready_for_data_collection",
|
|
21
|
+
) -> Generator[Msg, None, None]:
|
|
22
|
+
"""Starts preparing for the next data collection and then runs the
|
|
23
|
+
given plan.
|
|
24
|
+
|
|
25
|
+
Preparation consists of:
|
|
26
|
+
* Arming the Eiger
|
|
27
|
+
* Moving the detector to the specified position
|
|
28
|
+
* Opening the detect shutter
|
|
29
|
+
If the plan fails it will disarm the eiger.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def wrapped_plan():
|
|
33
|
+
yield from bps.abs_set(eiger.do_arm, 1, group=group)
|
|
34
|
+
if detector_distance_mm:
|
|
35
|
+
yield from set_detector_z_position(
|
|
36
|
+
detector_motion, detector_distance_mm, group
|
|
37
|
+
)
|
|
38
|
+
yield from set_shutter(detector_motion, ShutterState.OPEN, group)
|
|
39
|
+
yield from plan_to_run
|
|
40
|
+
|
|
41
|
+
yield from bpp.contingency_wrapper(
|
|
42
|
+
wrapped_plan(),
|
|
43
|
+
except_plan=lambda e: (yield from bps.stop(eiger)),
|
|
44
|
+
)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from bluesky import plan_stubs as bps
|
|
2
|
+
from bluesky.preprocessors import finalize_wrapper
|
|
3
|
+
from bluesky.utils import make_decorator
|
|
4
|
+
from dodal.devices.attenuator import Attenuator
|
|
5
|
+
from dodal.devices.xbpm_feedback import Pause, XBPMFeedback
|
|
6
|
+
|
|
7
|
+
from mx_bluesky.hyperion.log import LOGGER
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _check_and_pause_feedback(
|
|
11
|
+
xbpm_feedback: XBPMFeedback,
|
|
12
|
+
attenuator: Attenuator,
|
|
13
|
+
desired_transmission_fraction: float,
|
|
14
|
+
):
|
|
15
|
+
"""Checks that the xbpm is in position before then turning it off and setting a new
|
|
16
|
+
transmission.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
xbpm_feedback (XBPMFeedback): The XBPM device that is responsible for keeping
|
|
20
|
+
the beam in position
|
|
21
|
+
attenuator (Attenuator): The attenuator used to set transmission
|
|
22
|
+
desired_transmission_fraction (float): The desired transmission to set after
|
|
23
|
+
turning XBPM feedback off.
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
yield from bps.mv(attenuator, 1.0)
|
|
27
|
+
LOGGER.info("Waiting for XBPM feedback to be stable")
|
|
28
|
+
yield from bps.trigger(xbpm_feedback, wait=True)
|
|
29
|
+
LOGGER.info(
|
|
30
|
+
f"XPBM feedback in position, pausing and setting transmission to {desired_transmission_fraction}"
|
|
31
|
+
)
|
|
32
|
+
yield from bps.mv(xbpm_feedback.pause_feedback, Pause.PAUSE)
|
|
33
|
+
yield from bps.mv(attenuator, desired_transmission_fraction)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _unpause_xbpm_feedback_and_set_transmission_to_1(
|
|
37
|
+
xbpm_feedback: XBPMFeedback, attenuator: Attenuator
|
|
38
|
+
):
|
|
39
|
+
"""Turns the XBPM feedback back on and sets transmission to 1 so that it keeps the
|
|
40
|
+
beam aligned whilst not collecting.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
xbpm_feedback (XBPMFeedback): The XBPM device that is responsible for keeping
|
|
44
|
+
the beam in position
|
|
45
|
+
attenuator (Attenuator): The attenuator used to set transmission
|
|
46
|
+
"""
|
|
47
|
+
yield from bps.mv(xbpm_feedback.pause_feedback, Pause.RUN, attenuator, 1.0)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def transmission_and_xbpm_feedback_for_collection_wrapper(
|
|
51
|
+
plan,
|
|
52
|
+
xbpm_feedback: XBPMFeedback,
|
|
53
|
+
attenuator: Attenuator,
|
|
54
|
+
desired_transmission_fraction: float,
|
|
55
|
+
):
|
|
56
|
+
"""Sets the transmission for the data collection, ensuring the xbpm feedback is valid
|
|
57
|
+
this wrapper should be run around every data collection or movement that may disrupt
|
|
58
|
+
the XBPM feedback.
|
|
59
|
+
|
|
60
|
+
XBPM feedback isn't reliable during collections due to:
|
|
61
|
+
* Objects (e.g. attenuator) crossing the beam can cause large (incorrect) feedback movements
|
|
62
|
+
* Lower transmissions/higher energies are less reliable for the xbpm
|
|
63
|
+
|
|
64
|
+
So we need to keep the transmission at 100% and the feedback on when not collecting
|
|
65
|
+
and then turn it off and set the correct transmission for collection. The feedback
|
|
66
|
+
mostly accounts for slow thermal drift so it is safe to assume that the beam is
|
|
67
|
+
stable during a collection.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
plan: The plan performing the data collection
|
|
71
|
+
xbpm_feedback (XBPMFeedback): The XBPM device that is responsible for keeping
|
|
72
|
+
the beam in position
|
|
73
|
+
attenuator (Attenuator): The attenuator used to set transmission
|
|
74
|
+
desired_transmission_fraction (float): The desired transmission for the collection
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def _inner_plan():
|
|
78
|
+
yield from _check_and_pause_feedback(
|
|
79
|
+
xbpm_feedback, attenuator, desired_transmission_fraction
|
|
80
|
+
)
|
|
81
|
+
return (yield from plan)
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
yield from finalize_wrapper(
|
|
85
|
+
_inner_plan(),
|
|
86
|
+
_unpause_xbpm_feedback_and_set_transmission_to_1(xbpm_feedback, attenuator),
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
transmission_and_xbpm_feedback_for_collection_decorator = make_decorator(
|
|
92
|
+
transmission_and_xbpm_feedback_for_collection_wrapper
|
|
93
|
+
)
|