mx-bluesky 0.3.1__py3-none-any.whl → 1.2.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/_version.py +2 -2
- mx_bluesky/beamlines/i04/__init__.py +3 -0
- mx_bluesky/{i04 → beamlines/i04}/thawing_plan.py +5 -4
- mx_bluesky/{i24 → beamlines/i24}/serial/blueapi_config.yaml +1 -1
- mx_bluesky/{i24 → beamlines/i24}/serial/dcid.py +2 -2
- mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/DetStage.edl +3 -3
- mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +7 -7
- mx_bluesky/{i24 → beamlines/i24}/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +12 -9
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/CustomChip_py3v1.edl +3 -3
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DetStage.edl +3 -3
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +245 -200
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +4 -4
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +8 -8
- mx_bluesky/beamlines/i24/serial/fixed_target/__init__.py +0 -0
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +80 -70
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +20 -21
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +5 -5
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +7 -4
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_moveonclick.py +59 -39
- mx_bluesky/{i24 → beamlines/i24}/serial/log.py +1 -9
- mx_bluesky/beamlines/i24/serial/parameters/__init__.py +15 -0
- mx_bluesky/{i24 → beamlines/i24}/serial/parameters/constants.py +1 -1
- mx_bluesky/{i24 → beamlines/i24}/serial/parameters/experiment_parameters.py +4 -25
- mx_bluesky/{i24 → beamlines/i24}/serial/parameters/utils.py +5 -3
- mx_bluesky/{i24 → beamlines/i24}/serial/run_serial.py +1 -1
- mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/pv_abstract.py +1 -1
- mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_beamline.py +2 -2
- mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_detector.py +5 -5
- mx_bluesky/{i24 → beamlines/i24}/serial/write_nexus.py +6 -3
- 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 +55 -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 +93 -0
- mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +537 -0
- mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +209 -0
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +46 -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_and_change_energy.py +237 -0
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +162 -0
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +436 -0
- mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +60 -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 +64 -0
- mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +62 -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 +86 -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 +27 -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/{parameters → hyperion/parameters}/components.py +80 -26
- mx_bluesky/hyperion/parameters/constants.py +158 -0
- mx_bluesky/hyperion/parameters/gridscan.py +221 -0
- mx_bluesky/hyperion/parameters/load_centre_collect.py +50 -0
- mx_bluesky/hyperion/parameters/robot_load.py +16 -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.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/METADATA +26 -11
- mx_bluesky-1.2.0.dist-info/RECORD +140 -0
- {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/WHEEL +1 -1
- mx_bluesky-1.2.0.dist-info/entry_points.txt +8 -0
- mx_bluesky/i04/__init__.py +0 -3
- mx_bluesky/i24/serial/parameters/__init__.py +0 -15
- mx_bluesky/parameters/__init__.py +0 -31
- mx_bluesky-0.3.1.dist-info/RECORD +0 -67
- mx_bluesky-0.3.1.dist-info/entry_points.txt +0 -4
- /mx_bluesky/{i24 → beamlines}/__init__.py +0 -0
- /mx_bluesky/{i04 → beamlines/i04}/callbacks/murko_callback.py +0 -0
- /mx_bluesky/{i24/serial/extruder → beamlines/i24}/__init__.py +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/__init__.py +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/microdrop_alignment.edl +0 -0
- /mx_bluesky/{i24/serial/fixed_target → beamlines/i24/serial/extruder}/__init__.py +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/ME14E-GeneralPurpose.edl +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/PMAC_Command.edl +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/Shutter_Control.edl +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/microdrop_alignment.edl +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/nudgechip.edl +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/short1-laser.png +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/short2-laser.png +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/ft_utils.py +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/cs/cs_maker.json +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/cs/motor_direction.txt +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/minichip-oxford.pvar +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/oxford.pvar +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/run_extruder.sh +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/run_fixed_target.sh +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/run_ssx.sh +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/set_visit_directory.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/{i24 → beamlines/i24}/serial/setup_beamline/pv.py +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_zebra_plans.py +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/start_blueapi.sh +0 -0
- {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/LICENSE +0 -0
- {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
|
|
5
|
+
import bluesky.plan_stubs as bps
|
|
6
|
+
import bluesky.preprocessors as bpp
|
|
7
|
+
from blueapi.core import BlueskyContext, MsgGenerator
|
|
8
|
+
from dodal.devices.aperturescatterguard import ApertureScatterguard
|
|
9
|
+
from dodal.devices.attenuator import Attenuator
|
|
10
|
+
from dodal.devices.backlight import Backlight
|
|
11
|
+
from dodal.devices.dcm import DCM
|
|
12
|
+
from dodal.devices.detector.detector_motion import DetectorMotion
|
|
13
|
+
from dodal.devices.eiger import EigerDetector
|
|
14
|
+
from dodal.devices.flux import Flux
|
|
15
|
+
from dodal.devices.oav.oav_detector import OAV
|
|
16
|
+
from dodal.devices.oav.oav_parameters import OAVParameters
|
|
17
|
+
from dodal.devices.robot import BartRobot
|
|
18
|
+
from dodal.devices.s4_slit_gaps import S4SlitGaps
|
|
19
|
+
from dodal.devices.smargon import Smargon
|
|
20
|
+
from dodal.devices.synchrotron import Synchrotron
|
|
21
|
+
from dodal.devices.undulator import Undulator
|
|
22
|
+
from dodal.devices.xbpm_feedback import XBPMFeedback
|
|
23
|
+
from dodal.devices.zebra import RotationDirection, Zebra
|
|
24
|
+
from dodal.devices.zebra_controlled_shutter import ZebraShutter
|
|
25
|
+
from dodal.plans.check_topup import check_topup_and_wait_if_necessary
|
|
26
|
+
|
|
27
|
+
from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import (
|
|
28
|
+
cleanup_sample_environment,
|
|
29
|
+
move_phi_chi_omega,
|
|
30
|
+
move_x_y_z,
|
|
31
|
+
setup_sample_environment,
|
|
32
|
+
)
|
|
33
|
+
from mx_bluesky.hyperion.device_setup_plans.read_hardware_for_setup import (
|
|
34
|
+
read_hardware_during_collection,
|
|
35
|
+
read_hardware_for_zocalo,
|
|
36
|
+
read_hardware_pre_collection,
|
|
37
|
+
)
|
|
38
|
+
from mx_bluesky.hyperion.device_setup_plans.setup_zebra import (
|
|
39
|
+
arm_zebra,
|
|
40
|
+
setup_zebra_for_rotation,
|
|
41
|
+
tidy_up_zebra_after_rotation_scan,
|
|
42
|
+
)
|
|
43
|
+
from mx_bluesky.hyperion.device_setup_plans.utils import (
|
|
44
|
+
start_preparing_data_collection_then_do_plan,
|
|
45
|
+
)
|
|
46
|
+
from mx_bluesky.hyperion.device_setup_plans.xbpm_feedback import (
|
|
47
|
+
transmission_and_xbpm_feedback_for_collection_decorator,
|
|
48
|
+
)
|
|
49
|
+
from mx_bluesky.hyperion.experiment_plans.oav_snapshot_plan import (
|
|
50
|
+
OavSnapshotComposite,
|
|
51
|
+
oav_snapshot_plan,
|
|
52
|
+
setup_beamline_for_OAV,
|
|
53
|
+
)
|
|
54
|
+
from mx_bluesky.hyperion.log import LOGGER
|
|
55
|
+
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
56
|
+
from mx_bluesky.hyperion.parameters.rotation import (
|
|
57
|
+
MultiRotationScan,
|
|
58
|
+
RotationScan,
|
|
59
|
+
)
|
|
60
|
+
from mx_bluesky.hyperion.utils.context import device_composite_from_context
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclasses.dataclass
|
|
64
|
+
class RotationScanComposite(OavSnapshotComposite):
|
|
65
|
+
"""All devices which are directly or indirectly required by this plan"""
|
|
66
|
+
|
|
67
|
+
aperture_scatterguard: ApertureScatterguard
|
|
68
|
+
attenuator: Attenuator
|
|
69
|
+
backlight: Backlight
|
|
70
|
+
dcm: DCM
|
|
71
|
+
detector_motion: DetectorMotion
|
|
72
|
+
eiger: EigerDetector
|
|
73
|
+
flux: Flux
|
|
74
|
+
robot: BartRobot
|
|
75
|
+
smargon: Smargon
|
|
76
|
+
undulator: Undulator
|
|
77
|
+
synchrotron: Synchrotron
|
|
78
|
+
s4_slit_gaps: S4SlitGaps
|
|
79
|
+
sample_shutter: ZebraShutter
|
|
80
|
+
zebra: Zebra
|
|
81
|
+
oav: OAV
|
|
82
|
+
xbpm_feedback: XBPMFeedback
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def create_devices(context: BlueskyContext) -> RotationScanComposite:
|
|
86
|
+
"""Ensures necessary devices have been instantiated"""
|
|
87
|
+
|
|
88
|
+
return device_composite_from_context(context, RotationScanComposite)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
DEFAULT_DIRECTION = RotationDirection.NEGATIVE
|
|
92
|
+
DEFAULT_MAX_VELOCITY = 120
|
|
93
|
+
# Use a slightly larger time to acceleration than EPICS as it's better to be cautious
|
|
94
|
+
ACCELERATION_MARGIN = 1.5
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclasses.dataclass
|
|
98
|
+
class RotationMotionProfile:
|
|
99
|
+
start_scan_deg: float
|
|
100
|
+
start_motion_deg: float
|
|
101
|
+
scan_width_deg: float
|
|
102
|
+
shutter_time_s: float
|
|
103
|
+
direction: RotationDirection
|
|
104
|
+
speed_for_rotation_deg_s: float
|
|
105
|
+
acceleration_offset_deg: float
|
|
106
|
+
shutter_opening_deg: float
|
|
107
|
+
total_exposure_s: float
|
|
108
|
+
distance_to_move_deg: float
|
|
109
|
+
max_velocity_deg_s: float
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def calculate_motion_profile(
|
|
113
|
+
params: RotationScan,
|
|
114
|
+
motor_time_to_speed_s: float,
|
|
115
|
+
max_velocity_deg_s: float,
|
|
116
|
+
) -> RotationMotionProfile:
|
|
117
|
+
"""Calculates the various numbers needed for motions in the rotation scan.
|
|
118
|
+
Rotates through "scan width" plus twice an "offset" to take into account
|
|
119
|
+
acceleration at the start and deceleration at the end, plus the number of extra
|
|
120
|
+
degrees of rotation needed to make sure the fast shutter has fully opened before the
|
|
121
|
+
detector trigger is sent.
|
|
122
|
+
See https://github.com/DiamondLightSource/hyperion/wiki/rotation-scan-geometry
|
|
123
|
+
for a simple pictorial explanation."""
|
|
124
|
+
|
|
125
|
+
direction = params.rotation_direction.multiplier
|
|
126
|
+
num_images = params.num_images
|
|
127
|
+
shutter_time_s = params.shutter_opening_time_s
|
|
128
|
+
image_width_deg = params.rotation_increment_deg
|
|
129
|
+
exposure_time_s = params.exposure_time_s
|
|
130
|
+
motor_time_to_speed_s *= ACCELERATION_MARGIN
|
|
131
|
+
start_scan_deg = params.omega_start_deg
|
|
132
|
+
|
|
133
|
+
LOGGER.info("Calculating rotation scan motion profile:")
|
|
134
|
+
LOGGER.info(
|
|
135
|
+
f"{num_images=}, {shutter_time_s=}, {image_width_deg=}, {exposure_time_s=}, {direction=}"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
scan_width_deg = num_images * params.rotation_increment_deg
|
|
139
|
+
LOGGER.info(f"{scan_width_deg=} = {num_images=} * {params.rotation_increment_deg=}")
|
|
140
|
+
|
|
141
|
+
speed_for_rotation_deg_s = image_width_deg / exposure_time_s
|
|
142
|
+
LOGGER.info("speed_for_rotation_deg_s = image_width_deg / exposure_time_s")
|
|
143
|
+
LOGGER.info(
|
|
144
|
+
f"{speed_for_rotation_deg_s=} = {image_width_deg=} / {exposure_time_s=}"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
acceleration_offset_deg = motor_time_to_speed_s * speed_for_rotation_deg_s
|
|
148
|
+
LOGGER.info(
|
|
149
|
+
f"{acceleration_offset_deg=} = {motor_time_to_speed_s=} * {speed_for_rotation_deg_s=}"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
start_motion_deg = start_scan_deg - (acceleration_offset_deg * direction)
|
|
153
|
+
LOGGER.info(
|
|
154
|
+
f"{start_motion_deg=} = {start_scan_deg=} - ({acceleration_offset_deg=} * {direction=})"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
shutter_opening_deg = speed_for_rotation_deg_s * shutter_time_s
|
|
158
|
+
LOGGER.info(
|
|
159
|
+
f"{shutter_opening_deg=} = {speed_for_rotation_deg_s=} * {shutter_time_s=}"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
shutter_opening_deg = speed_for_rotation_deg_s * shutter_time_s
|
|
163
|
+
LOGGER.info(
|
|
164
|
+
f"{shutter_opening_deg=} = {speed_for_rotation_deg_s=} * {shutter_time_s=}"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
total_exposure_s = num_images * exposure_time_s
|
|
168
|
+
LOGGER.info(f"{total_exposure_s=} = {num_images=} * {exposure_time_s=}")
|
|
169
|
+
|
|
170
|
+
distance_to_move_deg = (
|
|
171
|
+
scan_width_deg + shutter_opening_deg + acceleration_offset_deg * 2
|
|
172
|
+
) * direction
|
|
173
|
+
LOGGER.info(
|
|
174
|
+
f"{distance_to_move_deg=} = ({scan_width_deg=} + {shutter_opening_deg=} + {acceleration_offset_deg=} * 2) * {direction=})"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return RotationMotionProfile(
|
|
178
|
+
start_scan_deg=start_scan_deg,
|
|
179
|
+
start_motion_deg=start_motion_deg,
|
|
180
|
+
scan_width_deg=scan_width_deg,
|
|
181
|
+
shutter_time_s=shutter_time_s,
|
|
182
|
+
direction=params.rotation_direction,
|
|
183
|
+
speed_for_rotation_deg_s=speed_for_rotation_deg_s,
|
|
184
|
+
acceleration_offset_deg=acceleration_offset_deg,
|
|
185
|
+
shutter_opening_deg=shutter_opening_deg,
|
|
186
|
+
total_exposure_s=total_exposure_s,
|
|
187
|
+
distance_to_move_deg=distance_to_move_deg,
|
|
188
|
+
max_velocity_deg_s=max_velocity_deg_s,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def rotation_scan_plan(
|
|
193
|
+
composite: RotationScanComposite,
|
|
194
|
+
params: RotationScan,
|
|
195
|
+
motion_values: RotationMotionProfile,
|
|
196
|
+
):
|
|
197
|
+
"""A stub plan to collect diffraction images from a sample continuously rotating
|
|
198
|
+
about a fixed axis - for now this axis is limited to omega.
|
|
199
|
+
Needs additional setup of the sample environment and a wrapper to clean up."""
|
|
200
|
+
|
|
201
|
+
@bpp.set_run_key_decorator(CONST.PLAN.ROTATION_MAIN)
|
|
202
|
+
@bpp.run_decorator(
|
|
203
|
+
md={
|
|
204
|
+
"subplan_name": CONST.PLAN.ROTATION_MAIN,
|
|
205
|
+
"scan_points": [params.scan_points],
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
def _rotation_scan_plan(
|
|
209
|
+
motion_values: RotationMotionProfile,
|
|
210
|
+
composite: RotationScanComposite,
|
|
211
|
+
):
|
|
212
|
+
axis = composite.smargon.omega
|
|
213
|
+
|
|
214
|
+
# can move to start as fast as possible
|
|
215
|
+
yield from bps.abs_set(
|
|
216
|
+
axis.velocity, motion_values.max_velocity_deg_s, wait=True
|
|
217
|
+
)
|
|
218
|
+
LOGGER.info(f"moving omega to beginning, {motion_values.start_scan_deg=}")
|
|
219
|
+
yield from bps.abs_set(
|
|
220
|
+
axis,
|
|
221
|
+
motion_values.start_motion_deg,
|
|
222
|
+
group=CONST.WAIT.ROTATION_READY_FOR_DC,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
yield from setup_zebra_for_rotation(
|
|
226
|
+
composite.zebra,
|
|
227
|
+
composite.sample_shutter,
|
|
228
|
+
start_angle=motion_values.start_scan_deg,
|
|
229
|
+
scan_width=motion_values.scan_width_deg,
|
|
230
|
+
direction=motion_values.direction,
|
|
231
|
+
shutter_opening_deg=motion_values.shutter_opening_deg,
|
|
232
|
+
shutter_opening_s=motion_values.shutter_time_s,
|
|
233
|
+
group="setup_zebra",
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
yield from setup_sample_environment(
|
|
237
|
+
composite.aperture_scatterguard,
|
|
238
|
+
params.selected_aperture,
|
|
239
|
+
composite.backlight,
|
|
240
|
+
group=CONST.WAIT.ROTATION_READY_FOR_DC,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
LOGGER.info("Wait for any previous moves...")
|
|
244
|
+
# wait for all the setup tasks at once
|
|
245
|
+
yield from bps.wait(CONST.WAIT.ROTATION_READY_FOR_DC)
|
|
246
|
+
yield from bps.wait(CONST.WAIT.MOVE_GONIO_TO_START)
|
|
247
|
+
|
|
248
|
+
# get some information for the ispyb deposition and trigger the callback
|
|
249
|
+
yield from read_hardware_for_zocalo(composite.eiger)
|
|
250
|
+
|
|
251
|
+
yield from read_hardware_pre_collection(
|
|
252
|
+
composite.undulator,
|
|
253
|
+
composite.synchrotron,
|
|
254
|
+
composite.s4_slit_gaps,
|
|
255
|
+
composite.robot,
|
|
256
|
+
composite.smargon,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
# Get ready for the actual scan
|
|
260
|
+
yield from bps.abs_set(
|
|
261
|
+
axis.velocity, motion_values.speed_for_rotation_deg_s, wait=True
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
yield from bps.wait("setup_zebra")
|
|
265
|
+
yield from arm_zebra(composite.zebra)
|
|
266
|
+
|
|
267
|
+
# Check topup gate
|
|
268
|
+
yield from check_topup_and_wait_if_necessary(
|
|
269
|
+
composite.synchrotron,
|
|
270
|
+
motion_values.total_exposure_s,
|
|
271
|
+
ops_time=10.0, # Additional time to account for rotation, is s
|
|
272
|
+
) # See #https://github.com/DiamondLightSource/hyperion/issues/932
|
|
273
|
+
|
|
274
|
+
LOGGER.info("Executing rotation scan")
|
|
275
|
+
yield from bps.rel_set(axis, motion_values.distance_to_move_deg, wait=True)
|
|
276
|
+
|
|
277
|
+
yield from read_hardware_during_collection(
|
|
278
|
+
composite.aperture_scatterguard,
|
|
279
|
+
composite.attenuator,
|
|
280
|
+
composite.flux,
|
|
281
|
+
composite.dcm,
|
|
282
|
+
composite.eiger,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
yield from _rotation_scan_plan(motion_values, composite)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _cleanup_plan(composite: RotationScanComposite, **kwargs):
|
|
289
|
+
LOGGER.info("Cleaning up after rotation scan")
|
|
290
|
+
max_vel = yield from bps.rd(composite.smargon.omega.max_velocity)
|
|
291
|
+
yield from cleanup_sample_environment(composite.detector_motion, group="cleanup")
|
|
292
|
+
yield from bps.abs_set(composite.smargon.omega.velocity, max_vel, group="cleanup")
|
|
293
|
+
yield from tidy_up_zebra_after_rotation_scan(
|
|
294
|
+
composite.zebra, composite.sample_shutter, group="cleanup", wait=False
|
|
295
|
+
)
|
|
296
|
+
yield from bps.wait("cleanup")
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _move_and_rotation(
|
|
300
|
+
composite: RotationScanComposite,
|
|
301
|
+
params: RotationScan,
|
|
302
|
+
oav_params: OAVParameters,
|
|
303
|
+
):
|
|
304
|
+
motor_time_to_speed = yield from bps.rd(composite.smargon.omega.acceleration_time)
|
|
305
|
+
max_vel = yield from bps.rd(composite.smargon.omega.max_velocity)
|
|
306
|
+
motion_values = calculate_motion_profile(params, motor_time_to_speed, max_vel)
|
|
307
|
+
|
|
308
|
+
def _div_by_1000_if_not_none(num: float | None):
|
|
309
|
+
return num / 1000 if num else num
|
|
310
|
+
|
|
311
|
+
LOGGER.info("moving to position (if specified)")
|
|
312
|
+
yield from move_x_y_z(
|
|
313
|
+
composite.smargon,
|
|
314
|
+
_div_by_1000_if_not_none(params.x_start_um),
|
|
315
|
+
_div_by_1000_if_not_none(params.y_start_um),
|
|
316
|
+
_div_by_1000_if_not_none(params.z_start_um),
|
|
317
|
+
group=CONST.WAIT.MOVE_GONIO_TO_START,
|
|
318
|
+
)
|
|
319
|
+
yield from move_phi_chi_omega(
|
|
320
|
+
composite.smargon,
|
|
321
|
+
params.phi_start_deg,
|
|
322
|
+
params.chi_start_deg,
|
|
323
|
+
group=CONST.WAIT.MOVE_GONIO_TO_START,
|
|
324
|
+
)
|
|
325
|
+
if params.take_snapshots:
|
|
326
|
+
yield from bps.wait(CONST.WAIT.MOVE_GONIO_TO_START)
|
|
327
|
+
yield from setup_beamline_for_OAV(
|
|
328
|
+
composite.smargon, composite.backlight, composite.aperture_scatterguard
|
|
329
|
+
)
|
|
330
|
+
yield from oav_snapshot_plan(composite, params, oav_params)
|
|
331
|
+
yield from rotation_scan_plan(
|
|
332
|
+
composite,
|
|
333
|
+
params,
|
|
334
|
+
motion_values,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def rotation_scan(
|
|
339
|
+
composite: RotationScanComposite,
|
|
340
|
+
parameters: RotationScan,
|
|
341
|
+
oav_params: OAVParameters | None = None,
|
|
342
|
+
) -> MsgGenerator:
|
|
343
|
+
if not oav_params:
|
|
344
|
+
oav_params = OAVParameters(context="xrayCentring")
|
|
345
|
+
|
|
346
|
+
@bpp.set_run_key_decorator("rotation_scan")
|
|
347
|
+
@bpp.run_decorator( # attach experiment metadata to the start document
|
|
348
|
+
md={
|
|
349
|
+
"subplan_name": CONST.PLAN.ROTATION_OUTER,
|
|
350
|
+
CONST.TRIGGER.ZOCALO: CONST.PLAN.ROTATION_MAIN,
|
|
351
|
+
"zocalo_environment": parameters.zocalo_environment,
|
|
352
|
+
"hyperion_parameters": parameters.model_dump_json(),
|
|
353
|
+
"activate_callbacks": [
|
|
354
|
+
"RotationISPyBCallback",
|
|
355
|
+
"RotationNexusFileCallback",
|
|
356
|
+
],
|
|
357
|
+
}
|
|
358
|
+
)
|
|
359
|
+
@transmission_and_xbpm_feedback_for_collection_decorator(
|
|
360
|
+
composite.xbpm_feedback,
|
|
361
|
+
composite.attenuator,
|
|
362
|
+
parameters.transmission_frac,
|
|
363
|
+
)
|
|
364
|
+
def rotation_scan_plan_with_stage_and_cleanup(
|
|
365
|
+
params: RotationScan,
|
|
366
|
+
):
|
|
367
|
+
eiger: EigerDetector = composite.eiger
|
|
368
|
+
eiger.set_detector_parameters(params.detector_params)
|
|
369
|
+
|
|
370
|
+
@bpp.finalize_decorator(lambda: _cleanup_plan(composite))
|
|
371
|
+
def rotation_with_cleanup_and_stage(params: RotationScan):
|
|
372
|
+
yield from _move_and_rotation(composite, params, oav_params)
|
|
373
|
+
|
|
374
|
+
LOGGER.info("setting up and staging eiger...")
|
|
375
|
+
yield from start_preparing_data_collection_then_do_plan(
|
|
376
|
+
eiger,
|
|
377
|
+
composite.detector_motion,
|
|
378
|
+
params.detector_distance_mm,
|
|
379
|
+
rotation_with_cleanup_and_stage(params),
|
|
380
|
+
group=CONST.WAIT.ROTATION_READY_FOR_DC,
|
|
381
|
+
)
|
|
382
|
+
yield from bps.unstage(eiger)
|
|
383
|
+
|
|
384
|
+
yield from rotation_scan_plan_with_stage_and_cleanup(parameters)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def multi_rotation_scan(
|
|
388
|
+
composite: RotationScanComposite,
|
|
389
|
+
parameters: MultiRotationScan,
|
|
390
|
+
oav_params: OAVParameters | None = None,
|
|
391
|
+
) -> MsgGenerator:
|
|
392
|
+
if not oav_params:
|
|
393
|
+
oav_params = OAVParameters(context="xrayCentring")
|
|
394
|
+
eiger: EigerDetector = composite.eiger
|
|
395
|
+
eiger.set_detector_parameters(parameters.detector_params)
|
|
396
|
+
|
|
397
|
+
@bpp.set_run_key_decorator("multi_rotation_scan")
|
|
398
|
+
@bpp.run_decorator(
|
|
399
|
+
md={
|
|
400
|
+
"subplan_name": CONST.PLAN.ROTATION_MULTI,
|
|
401
|
+
"full_num_of_images": parameters.num_images,
|
|
402
|
+
"meta_data_run_number": parameters.detector_params.run_number,
|
|
403
|
+
"activate_callbacks": [
|
|
404
|
+
"RotationISPyBCallback",
|
|
405
|
+
"RotationNexusFileCallback",
|
|
406
|
+
],
|
|
407
|
+
}
|
|
408
|
+
)
|
|
409
|
+
@bpp.stage_decorator([eiger])
|
|
410
|
+
@bpp.finalize_decorator(lambda: _cleanup_plan(composite))
|
|
411
|
+
def _multi_rotation_scan():
|
|
412
|
+
for single_scan in parameters.single_rotation_scans:
|
|
413
|
+
|
|
414
|
+
@bpp.set_run_key_decorator("rotation_scan")
|
|
415
|
+
@bpp.run_decorator( # attach experiment metadata to the start document
|
|
416
|
+
md={
|
|
417
|
+
"subplan_name": CONST.PLAN.ROTATION_OUTER,
|
|
418
|
+
CONST.TRIGGER.ZOCALO: CONST.PLAN.ROTATION_MAIN,
|
|
419
|
+
"hyperion_parameters": single_scan.model_dump_json(),
|
|
420
|
+
}
|
|
421
|
+
)
|
|
422
|
+
def rotation_scan_core(
|
|
423
|
+
params: RotationScan,
|
|
424
|
+
):
|
|
425
|
+
yield from _move_and_rotation(composite, params, oav_params)
|
|
426
|
+
|
|
427
|
+
yield from rotation_scan_core(single_scan)
|
|
428
|
+
|
|
429
|
+
LOGGER.info("setting up and staging eiger...")
|
|
430
|
+
yield from start_preparing_data_collection_then_do_plan(
|
|
431
|
+
eiger,
|
|
432
|
+
composite.detector_motion,
|
|
433
|
+
parameters.detector_distance_mm,
|
|
434
|
+
_multi_rotation_scan(),
|
|
435
|
+
group=CONST.WAIT.ROTATION_READY_FOR_DC,
|
|
436
|
+
)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Plan that comprises:
|
|
2
|
+
* Disable feedback
|
|
3
|
+
* Set undulator energy to the requested amount
|
|
4
|
+
* Adjust DCM and mirrors for the new energy
|
|
5
|
+
* reenable feedback
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import dataclasses
|
|
9
|
+
|
|
10
|
+
from bluesky import plan_stubs as bps
|
|
11
|
+
from dodal.devices.attenuator import Attenuator
|
|
12
|
+
from dodal.devices.dcm import DCM
|
|
13
|
+
from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, VFMMirrorVoltages
|
|
14
|
+
from dodal.devices.undulator_dcm import UndulatorDCM
|
|
15
|
+
from dodal.devices.xbpm_feedback import XBPMFeedback
|
|
16
|
+
|
|
17
|
+
from mx_bluesky.hyperion.device_setup_plans import dcm_pitch_roll_mirror_adjuster
|
|
18
|
+
from mx_bluesky.hyperion.device_setup_plans.xbpm_feedback import (
|
|
19
|
+
transmission_and_xbpm_feedback_for_collection_wrapper,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
DESIRED_TRANSMISSION_FRACTION = 0.1
|
|
23
|
+
|
|
24
|
+
UNDULATOR_GROUP = "UNDULATOR_GROUP"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclasses.dataclass
|
|
28
|
+
class SetEnergyComposite:
|
|
29
|
+
vfm: FocusingMirrorWithStripes
|
|
30
|
+
vfm_mirror_voltages: VFMMirrorVoltages
|
|
31
|
+
dcm: DCM
|
|
32
|
+
undulator_dcm: UndulatorDCM
|
|
33
|
+
xbpm_feedback: XBPMFeedback
|
|
34
|
+
attenuator: Attenuator
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _set_energy_plan(
|
|
38
|
+
energy_kev,
|
|
39
|
+
composite: SetEnergyComposite,
|
|
40
|
+
):
|
|
41
|
+
yield from bps.abs_set(composite.undulator_dcm, energy_kev, group=UNDULATOR_GROUP)
|
|
42
|
+
yield from dcm_pitch_roll_mirror_adjuster.adjust_dcm_pitch_roll_vfm_from_lut(
|
|
43
|
+
composite.undulator_dcm,
|
|
44
|
+
composite.vfm,
|
|
45
|
+
composite.vfm_mirror_voltages,
|
|
46
|
+
energy_kev,
|
|
47
|
+
)
|
|
48
|
+
yield from bps.wait(group=UNDULATOR_GROUP)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def set_energy_plan(
|
|
52
|
+
energy_kev,
|
|
53
|
+
composite: SetEnergyComposite,
|
|
54
|
+
):
|
|
55
|
+
yield from transmission_and_xbpm_feedback_for_collection_wrapper(
|
|
56
|
+
_set_energy_plan(energy_kev, composite),
|
|
57
|
+
composite.xbpm_feedback,
|
|
58
|
+
composite.attenuator,
|
|
59
|
+
DESIRED_TRANSMISSION_FRACTION,
|
|
60
|
+
)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Provides external interaction functionality to Hyperion, including Nexus file
|
|
2
|
+
creation, ISPyB deposition, and Zocalo processing submissions.
|
|
3
|
+
|
|
4
|
+
Functionality from this module can/should be used through the callback functions in
|
|
5
|
+
external_interaction.callbacks which can subscribe to the Bluesky RunEngine and handle
|
|
6
|
+
these various interactions based on the documents emitted by the RunEngine during the
|
|
7
|
+
execution of the experimental plan. It's not recommended to use the interaction classes
|
|
8
|
+
here directly in plans except through the use of such callbacks.
|
|
9
|
+
"""
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Callbacks which can be subscribed to by the Bluesky RunEngine in order to perform
|
|
2
|
+
external interactions in response to the 'documents' emitted when events occur in the
|
|
3
|
+
execution of an experimental plan.
|
|
4
|
+
|
|
5
|
+
Callbacks used for the Hyperion fast grid scan are prefixed with 'FGS'.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .__main__ import main
|
|
9
|
+
|
|
10
|
+
__all__ = ["main"]
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from collections.abc import Callable, Sequence
|
|
3
|
+
from threading import Thread
|
|
4
|
+
from time import sleep
|
|
5
|
+
|
|
6
|
+
from bluesky.callbacks.zmq import Proxy, RemoteDispatcher
|
|
7
|
+
from dodal.log import LOGGER as dodal_logger
|
|
8
|
+
from dodal.log import set_up_all_logging_handlers
|
|
9
|
+
|
|
10
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.log_uid_tag_callback import (
|
|
11
|
+
LogUidTaggingCallback,
|
|
12
|
+
)
|
|
13
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.robot_load.ispyb_callback import (
|
|
14
|
+
RobotLoadISPyBCallback,
|
|
15
|
+
)
|
|
16
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.rotation.ispyb_callback import (
|
|
17
|
+
RotationISPyBCallback,
|
|
18
|
+
)
|
|
19
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.rotation.nexus_callback import (
|
|
20
|
+
RotationNexusFileCallback,
|
|
21
|
+
)
|
|
22
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.xray_centre.ispyb_callback import (
|
|
23
|
+
GridscanISPyBCallback,
|
|
24
|
+
)
|
|
25
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.xray_centre.nexus_callback import (
|
|
26
|
+
GridscanNexusFileCallback,
|
|
27
|
+
)
|
|
28
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.zocalo_callback import (
|
|
29
|
+
ZocaloCallback,
|
|
30
|
+
)
|
|
31
|
+
from mx_bluesky.hyperion.log import (
|
|
32
|
+
ISPYB_LOGGER,
|
|
33
|
+
NEXUS_LOGGER,
|
|
34
|
+
_get_logging_dir,
|
|
35
|
+
tag_filter,
|
|
36
|
+
)
|
|
37
|
+
from mx_bluesky.hyperion.parameters.cli import parse_callback_dev_mode_arg
|
|
38
|
+
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
39
|
+
|
|
40
|
+
LIVENESS_POLL_SECONDS = 1
|
|
41
|
+
ERROR_LOG_BUFFER_LINES = 5000
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def setup_callbacks():
|
|
45
|
+
zocalo = ZocaloCallback()
|
|
46
|
+
return [
|
|
47
|
+
GridscanNexusFileCallback(),
|
|
48
|
+
GridscanISPyBCallback(emit=zocalo),
|
|
49
|
+
RotationNexusFileCallback(),
|
|
50
|
+
RotationISPyBCallback(emit=zocalo),
|
|
51
|
+
LogUidTaggingCallback(),
|
|
52
|
+
RobotLoadISPyBCallback(),
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def setup_logging(dev_mode: bool):
|
|
57
|
+
for logger, filename in [
|
|
58
|
+
(ISPYB_LOGGER, "hyperion_ispyb_callback.log"),
|
|
59
|
+
(NEXUS_LOGGER, "hyperion_nexus_callback.log"),
|
|
60
|
+
]:
|
|
61
|
+
if logger.handlers == []:
|
|
62
|
+
handlers = set_up_all_logging_handlers(
|
|
63
|
+
logger,
|
|
64
|
+
_get_logging_dir(),
|
|
65
|
+
filename,
|
|
66
|
+
dev_mode,
|
|
67
|
+
error_log_buffer_lines=ERROR_LOG_BUFFER_LINES,
|
|
68
|
+
graylog_port=CONST.GRAYLOG_PORT,
|
|
69
|
+
)
|
|
70
|
+
handlers["graylog_handler"].addFilter(tag_filter)
|
|
71
|
+
log_info(f"Loggers initialised with dev_mode={dev_mode}")
|
|
72
|
+
nexgen_logger = logging.getLogger("nexgen")
|
|
73
|
+
nexgen_logger.parent = NEXUS_LOGGER
|
|
74
|
+
dodal_logger.parent = ISPYB_LOGGER
|
|
75
|
+
log_debug("nexgen logger added to nexus logger")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def setup_threads():
|
|
79
|
+
proxy = Proxy(*CONST.CALLBACK_0MQ_PROXY_PORTS)
|
|
80
|
+
dispatcher = RemoteDispatcher(f"localhost:{CONST.CALLBACK_0MQ_PROXY_PORTS[1]}")
|
|
81
|
+
log_debug("Created proxy and dispatcher objects")
|
|
82
|
+
|
|
83
|
+
def start_proxy():
|
|
84
|
+
proxy.start()
|
|
85
|
+
|
|
86
|
+
def start_dispatcher(callbacks: list[Callable]):
|
|
87
|
+
[dispatcher.subscribe(cb) for cb in callbacks]
|
|
88
|
+
dispatcher.start()
|
|
89
|
+
|
|
90
|
+
return proxy, dispatcher, start_proxy, start_dispatcher
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def log_info(msg, *args, **kwargs):
|
|
94
|
+
ISPYB_LOGGER.info(msg, *args, **kwargs)
|
|
95
|
+
NEXUS_LOGGER.info(msg, *args, **kwargs)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def log_debug(msg, *args, **kwargs):
|
|
99
|
+
ISPYB_LOGGER.debug(msg, *args, **kwargs)
|
|
100
|
+
NEXUS_LOGGER.debug(msg, *args, **kwargs)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def wait_for_threads_forever(threads: Sequence[Thread]):
|
|
104
|
+
alive = [t.is_alive() for t in threads]
|
|
105
|
+
try:
|
|
106
|
+
log_debug("Trying to wait forever on callback and dispatcher threads")
|
|
107
|
+
while all(alive):
|
|
108
|
+
sleep(LIVENESS_POLL_SECONDS)
|
|
109
|
+
alive = [t.is_alive() for t in threads]
|
|
110
|
+
except KeyboardInterrupt:
|
|
111
|
+
log_info("Main thread received interrupt - exiting.")
|
|
112
|
+
else:
|
|
113
|
+
log_info("Proxy or dispatcher thread ended - exiting.")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class HyperionCallbackRunner:
|
|
117
|
+
"""Runs Nexus, ISPyB and Zocalo callbacks in their own process."""
|
|
118
|
+
|
|
119
|
+
def __init__(self, dev_mode) -> None:
|
|
120
|
+
setup_logging(dev_mode)
|
|
121
|
+
log_info("Hyperion callback process started.")
|
|
122
|
+
|
|
123
|
+
self.callbacks = setup_callbacks()
|
|
124
|
+
self.proxy, self.dispatcher, start_proxy, start_dispatcher = setup_threads()
|
|
125
|
+
log_info("Created 0MQ proxy and local RemoteDispatcher.")
|
|
126
|
+
|
|
127
|
+
self.proxy_thread = Thread(target=start_proxy, daemon=True)
|
|
128
|
+
self.dispatcher_thread = Thread(
|
|
129
|
+
target=start_dispatcher, args=[self.callbacks], daemon=True
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def start(self):
|
|
133
|
+
log_info(f"Launching threads, with callbacks: {self.callbacks}")
|
|
134
|
+
self.proxy_thread.start()
|
|
135
|
+
self.dispatcher_thread.start()
|
|
136
|
+
log_info("Proxy and dispatcher thread launched.")
|
|
137
|
+
wait_for_threads_forever([self.proxy_thread, self.dispatcher_thread])
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def main(dev_mode=False) -> None:
|
|
141
|
+
dev_mode = dev_mode or parse_callback_dev_mode_arg()
|
|
142
|
+
print(f"In dev mode: {dev_mode}")
|
|
143
|
+
runner = HyperionCallbackRunner(dev_mode)
|
|
144
|
+
runner.start()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
if __name__ == "__main__":
|
|
148
|
+
main()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from bluesky.callbacks import CallbackBase
|
|
2
|
+
from event_model.documents.run_start import RunStart
|
|
3
|
+
|
|
4
|
+
from mx_bluesky.hyperion.log import LOGGER
|
|
5
|
+
|
|
6
|
+
from .logging_callback import format_doc_for_log
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ApertureChangeCallback(CallbackBase):
|
|
10
|
+
"""A callback that's used to send the selected aperture back to GDA"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
13
|
+
super().__init__(*args, **kwargs)
|
|
14
|
+
self.last_selected_aperture: str = "NONE"
|
|
15
|
+
|
|
16
|
+
def start(self, doc: RunStart):
|
|
17
|
+
if doc.get("subplan_name") == "change_aperture":
|
|
18
|
+
LOGGER.debug(f"START: {format_doc_for_log(doc)}")
|
|
19
|
+
ap_size = doc.get("aperture_size")
|
|
20
|
+
assert isinstance(ap_size, str)
|
|
21
|
+
LOGGER.info(f"Updating most recent in-plan aperture change to {ap_size}.")
|
|
22
|
+
self.last_selected_aperture = ap_size
|
|
File without changes
|