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.
Files changed (150) hide show
  1. mx_bluesky/__main__.py +1 -2
  2. mx_bluesky/_version.py +14 -2
  3. mx_bluesky/beamlines/i04/__init__.py +3 -0
  4. mx_bluesky/beamlines/i04/callbacks/murko_callback.py +45 -0
  5. mx_bluesky/beamlines/i04/thawing_plan.py +85 -0
  6. mx_bluesky/beamlines/i24/serial/__init__.py +49 -0
  7. mx_bluesky/beamlines/i24/serial/blueapi_config.yaml +12 -0
  8. mx_bluesky/{I24 → beamlines/i24}/serial/dcid.py +53 -41
  9. mx_bluesky/{I24 → beamlines/i24}/serial/extruder/EX-gui-edm/DetStage.edl +3 -4
  10. mx_bluesky/{I24 → beamlines/i24}/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +28 -32
  11. mx_bluesky/{I24 → beamlines/i24}/serial/extruder/EX-gui-edm/microdrop_alignment.edl +0 -1
  12. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +516 -0
  13. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/CustomChip_py3v1.edl +3 -4
  14. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DetStage.edl +3 -4
  15. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +273 -223
  16. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/ME14E-GeneralPurpose.edl +0 -1
  17. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +12 -13
  18. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/PMAC_Command.edl +0 -1
  19. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/Shutter_Control.edl +0 -1
  20. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/microdrop_alignment.edl +0 -1
  21. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/nudgechip.edl +0 -1
  22. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +273 -143
  23. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/short1-laser.png +0 -0
  24. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/short2-laser.png +0 -0
  25. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/ft_utils.py +24 -1
  26. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +808 -0
  27. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +377 -416
  28. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +34 -40
  29. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +328 -0
  30. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/i24ssx_moveonclick.py +66 -48
  31. mx_bluesky/{I24 → beamlines/i24}/serial/log.py +66 -19
  32. mx_bluesky/beamlines/i24/serial/parameters/__init__.py +15 -0
  33. mx_bluesky/beamlines/i24/serial/parameters/constants.py +47 -0
  34. mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py +103 -0
  35. mx_bluesky/beamlines/i24/serial/parameters/fixed_target/cs/cs_maker.json +9 -0
  36. mx_bluesky/{I24 → beamlines/i24}/serial/parameters/fixed_target/cs/motor_direction.txt +1 -1
  37. mx_bluesky/{I24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/minichip-oxford.pvar +1 -1
  38. mx_bluesky/beamlines/i24/serial/parameters/utils.py +42 -0
  39. mx_bluesky/beamlines/i24/serial/run_extruder.sh +19 -0
  40. mx_bluesky/beamlines/i24/serial/run_fixed_target.sh +22 -0
  41. mx_bluesky/beamlines/i24/serial/run_serial.py +36 -0
  42. mx_bluesky/{I24 → beamlines/i24}/serial/set_visit_directory.sh +6 -1
  43. mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/pv.py +1 -62
  44. mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/pv_abstract.py +6 -7
  45. mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/setup_beamline.py +90 -269
  46. mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/setup_detector.py +47 -40
  47. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +459 -0
  48. mx_bluesky/beamlines/i24/serial/start_blueapi.sh +28 -0
  49. mx_bluesky/beamlines/i24/serial/write_nexus.py +105 -0
  50. mx_bluesky/example.py +4 -4
  51. mx_bluesky/hyperion/__init__.py +1 -0
  52. mx_bluesky/hyperion/__main__.py +374 -0
  53. mx_bluesky/hyperion/device_setup_plans/__init__.py +0 -0
  54. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +134 -0
  55. mx_bluesky/hyperion/device_setup_plans/manipulate_sample.py +110 -0
  56. mx_bluesky/hyperion/device_setup_plans/position_detector.py +16 -0
  57. mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +60 -0
  58. mx_bluesky/hyperion/device_setup_plans/setup_oav.py +87 -0
  59. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +210 -0
  60. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +214 -0
  61. mx_bluesky/hyperion/device_setup_plans/smargon.py +25 -0
  62. mx_bluesky/hyperion/device_setup_plans/utils.py +44 -0
  63. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +93 -0
  64. mx_bluesky/hyperion/exceptions.py +47 -0
  65. mx_bluesky/hyperion/experiment_plans/__init__.py +30 -0
  66. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +84 -0
  67. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +528 -0
  68. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +209 -0
  69. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +173 -0
  70. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +81 -0
  71. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +463 -0
  72. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +119 -0
  73. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +164 -0
  74. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +322 -0
  75. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +436 -0
  76. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +68 -0
  77. mx_bluesky/hyperion/external_interaction/__init__.py +9 -0
  78. mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +10 -0
  79. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +148 -0
  80. mx_bluesky/hyperion/external_interaction/callbacks/aperture_change_callback.py +22 -0
  81. mx_bluesky/hyperion/external_interaction/callbacks/common/__init__.py +0 -0
  82. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +46 -0
  83. mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +70 -0
  84. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +88 -0
  85. mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +203 -0
  86. mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +20 -0
  87. mx_bluesky/hyperion/external_interaction/callbacks/logging_callback.py +29 -0
  88. mx_bluesky/hyperion/external_interaction/callbacks/plan_reactive_callback.py +101 -0
  89. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +88 -0
  90. mx_bluesky/hyperion/external_interaction/callbacks/rotation/__init__.py +0 -0
  91. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +174 -0
  92. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +17 -0
  93. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +102 -0
  94. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/__init__.py +0 -0
  95. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +269 -0
  96. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_mapping.py +53 -0
  97. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +95 -0
  98. mx_bluesky/hyperion/external_interaction/callbacks/zocalo_callback.py +92 -0
  99. mx_bluesky/hyperion/external_interaction/config_server.py +35 -0
  100. mx_bluesky/hyperion/external_interaction/exceptions.py +13 -0
  101. mx_bluesky/hyperion/external_interaction/ispyb/__init__.py +0 -0
  102. mx_bluesky/hyperion/external_interaction/ispyb/data_model.py +95 -0
  103. mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +125 -0
  104. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +276 -0
  105. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_utils.py +29 -0
  106. mx_bluesky/hyperion/external_interaction/nexus/__init__.py +0 -0
  107. mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +148 -0
  108. mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +114 -0
  109. mx_bluesky/hyperion/log.py +99 -0
  110. mx_bluesky/hyperion/parameters/__init__.py +2 -0
  111. mx_bluesky/hyperion/parameters/cli.py +68 -0
  112. mx_bluesky/hyperion/parameters/components.py +253 -0
  113. mx_bluesky/hyperion/parameters/constants.py +158 -0
  114. mx_bluesky/hyperion/parameters/gridscan.py +216 -0
  115. mx_bluesky/hyperion/parameters/rotation.py +160 -0
  116. mx_bluesky/hyperion/resources/panda/panda-gridscan.yaml +964 -0
  117. mx_bluesky/hyperion/tracing.py +28 -0
  118. mx_bluesky/hyperion/utils/context.py +84 -0
  119. mx_bluesky/hyperion/utils/utils.py +25 -0
  120. mx_bluesky/hyperion/utils/validation.py +196 -0
  121. mx_bluesky/jupyter_example.ipynb +3 -2
  122. {mx_bluesky-0.0.2.dist-info → mx_bluesky-1.1.0.dist-info}/METADATA +53 -32
  123. mx_bluesky-1.1.0.dist-info/RECORD +136 -0
  124. {mx_bluesky-0.0.2.dist-info → mx_bluesky-1.1.0.dist-info}/WHEEL +1 -1
  125. mx_bluesky-1.1.0.dist-info/entry_points.txt +8 -0
  126. mx_bluesky/I24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +0 -476
  127. mx_bluesky/I24/serial/fixed_target/FT-gui-edm/ME14E-motors.edl +0 -1874
  128. mx_bluesky/I24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +0 -706
  129. mx_bluesky/I24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +0 -463
  130. mx_bluesky/I24/serial/parameters/__init__.py +0 -5
  131. mx_bluesky/I24/serial/parameters/constants.py +0 -39
  132. mx_bluesky/I24/serial/parameters/fixed_target/cs/cs_maker.json +0 -9
  133. mx_bluesky/I24/serial/parameters/fixed_target/cs/fiducial_1.txt +0 -4
  134. mx_bluesky/I24/serial/parameters/fixed_target/cs/fiducial_2.txt +0 -4
  135. mx_bluesky/I24/serial/parameters/fixed_target/litemaps/currentchip.map +0 -81
  136. mx_bluesky/I24/serial/parameters/fixed_target/parameters.txt +0 -13
  137. mx_bluesky/I24/serial/run_serial.py +0 -52
  138. mx_bluesky/I24/serial/write_nexus.py +0 -113
  139. mx_bluesky-0.0.2.dist-info/RECORD +0 -58
  140. mx_bluesky-0.0.2.dist-info/entry_points.txt +0 -4
  141. /mx_bluesky/{I24 → beamlines}/__init__.py +0 -0
  142. /mx_bluesky/{I24/serial → beamlines/i24}/__init__.py +0 -0
  143. /mx_bluesky/{I24 → beamlines/i24}/serial/extruder/__init__.py +0 -0
  144. /mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/__init__.py +0 -0
  145. /mx_bluesky/{I24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/oxford.pvar +0 -0
  146. /mx_bluesky/{I24 → beamlines/i24}/serial/run_ssx.sh +0 -0
  147. /mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/__init__.py +0 -0
  148. /mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/ca.py +0 -0
  149. {mx_bluesky-0.0.2.dist-info → mx_bluesky-1.1.0.dist-info}/LICENSE +0 -0
  150. {mx_bluesky-0.0.2.dist-info → mx_bluesky-1.1.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,68 @@
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
+ from collections.abc import Generator
10
+ from typing import Any
11
+
12
+ from bluesky import plan_stubs as bps
13
+ from bluesky.utils import Msg
14
+ from dodal.devices.attenuator import Attenuator
15
+ from dodal.devices.dcm import DCM
16
+ from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, VFMMirrorVoltages
17
+ from dodal.devices.undulator_dcm import UndulatorDCM
18
+ from dodal.devices.xbpm_feedback import XBPMFeedback
19
+
20
+ from mx_bluesky.hyperion.device_setup_plans import dcm_pitch_roll_mirror_adjuster
21
+ from mx_bluesky.hyperion.device_setup_plans.xbpm_feedback import (
22
+ transmission_and_xbpm_feedback_for_collection_wrapper,
23
+ )
24
+
25
+ DESIRED_TRANSMISSION_FRACTION = 0.1
26
+
27
+ UNDULATOR_GROUP = "UNDULATOR_GROUP"
28
+
29
+
30
+ @dataclasses.dataclass
31
+ class SetEnergyComposite:
32
+ vfm: FocusingMirrorWithStripes
33
+ vfm_mirror_voltages: VFMMirrorVoltages
34
+ dcm: DCM
35
+ undulator_dcm: UndulatorDCM
36
+ xbpm_feedback: XBPMFeedback
37
+ attenuator: Attenuator
38
+
39
+
40
+ def _set_energy_plan(
41
+ energy_kev,
42
+ composite: SetEnergyComposite,
43
+ ):
44
+ yield from bps.abs_set(composite.undulator_dcm, energy_kev, group=UNDULATOR_GROUP)
45
+ yield from dcm_pitch_roll_mirror_adjuster.adjust_dcm_pitch_roll_vfm_from_lut(
46
+ composite.undulator_dcm,
47
+ composite.vfm,
48
+ composite.vfm_mirror_voltages,
49
+ energy_kev,
50
+ )
51
+ yield from bps.wait(group=UNDULATOR_GROUP)
52
+
53
+
54
+ def read_energy(composite: SetEnergyComposite) -> Generator[Msg, Any, float]:
55
+ """Obtain the energy in kev"""
56
+ return (yield from bps.rd(composite.dcm.energy_in_kev)) # type: ignore
57
+
58
+
59
+ def set_energy_plan(
60
+ energy_kev,
61
+ composite: SetEnergyComposite,
62
+ ):
63
+ yield from transmission_and_xbpm_feedback_for_collection_wrapper(
64
+ _set_energy_plan(energy_kev, composite),
65
+ composite.xbpm_feedback,
66
+ composite.attenuator,
67
+ DESIRED_TRANSMISSION_FRACTION,
68
+ )
@@ -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