mx-bluesky 0.3.1__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 (138) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/beamlines/i04/__init__.py +3 -0
  3. mx_bluesky/{i04 → beamlines/i04}/thawing_plan.py +5 -4
  4. mx_bluesky/{i24 → beamlines/i24}/serial/blueapi_config.yaml +1 -1
  5. mx_bluesky/{i24 → beamlines/i24}/serial/dcid.py +2 -2
  6. mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/DetStage.edl +3 -3
  7. mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +7 -7
  8. mx_bluesky/{i24 → beamlines/i24}/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +12 -9
  9. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/CustomChip_py3v1.edl +3 -3
  10. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DetStage.edl +3 -3
  11. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +245 -200
  12. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +4 -4
  13. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +8 -8
  14. mx_bluesky/beamlines/i24/serial/fixed_target/__init__.py +0 -0
  15. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +80 -70
  16. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +20 -21
  17. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +5 -5
  18. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +7 -4
  19. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_moveonclick.py +59 -39
  20. mx_bluesky/{i24 → beamlines/i24}/serial/log.py +1 -9
  21. mx_bluesky/beamlines/i24/serial/parameters/__init__.py +15 -0
  22. mx_bluesky/{i24 → beamlines/i24}/serial/parameters/constants.py +1 -1
  23. mx_bluesky/{i24 → beamlines/i24}/serial/parameters/experiment_parameters.py +4 -25
  24. mx_bluesky/{i24 → beamlines/i24}/serial/parameters/utils.py +5 -3
  25. mx_bluesky/{i24 → beamlines/i24}/serial/run_serial.py +1 -1
  26. mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/pv_abstract.py +1 -1
  27. mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_beamline.py +2 -2
  28. mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_detector.py +5 -5
  29. mx_bluesky/{i24 → beamlines/i24}/serial/write_nexus.py +6 -3
  30. mx_bluesky/hyperion/__init__.py +1 -0
  31. mx_bluesky/hyperion/__main__.py +374 -0
  32. mx_bluesky/hyperion/device_setup_plans/__init__.py +0 -0
  33. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +134 -0
  34. mx_bluesky/hyperion/device_setup_plans/manipulate_sample.py +110 -0
  35. mx_bluesky/hyperion/device_setup_plans/position_detector.py +16 -0
  36. mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +60 -0
  37. mx_bluesky/hyperion/device_setup_plans/setup_oav.py +87 -0
  38. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +210 -0
  39. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +214 -0
  40. mx_bluesky/hyperion/device_setup_plans/smargon.py +25 -0
  41. mx_bluesky/hyperion/device_setup_plans/utils.py +44 -0
  42. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +93 -0
  43. mx_bluesky/hyperion/exceptions.py +47 -0
  44. mx_bluesky/hyperion/experiment_plans/__init__.py +30 -0
  45. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +84 -0
  46. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +528 -0
  47. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +209 -0
  48. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +173 -0
  49. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +81 -0
  50. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +463 -0
  51. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +119 -0
  52. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +164 -0
  53. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +322 -0
  54. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +436 -0
  55. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +68 -0
  56. mx_bluesky/hyperion/external_interaction/__init__.py +9 -0
  57. mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +10 -0
  58. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +148 -0
  59. mx_bluesky/hyperion/external_interaction/callbacks/aperture_change_callback.py +22 -0
  60. mx_bluesky/hyperion/external_interaction/callbacks/common/__init__.py +0 -0
  61. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +46 -0
  62. mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +70 -0
  63. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +88 -0
  64. mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +203 -0
  65. mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +20 -0
  66. mx_bluesky/hyperion/external_interaction/callbacks/logging_callback.py +29 -0
  67. mx_bluesky/hyperion/external_interaction/callbacks/plan_reactive_callback.py +101 -0
  68. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +88 -0
  69. mx_bluesky/hyperion/external_interaction/callbacks/rotation/__init__.py +0 -0
  70. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +174 -0
  71. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +17 -0
  72. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +102 -0
  73. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/__init__.py +0 -0
  74. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +269 -0
  75. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_mapping.py +53 -0
  76. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +95 -0
  77. mx_bluesky/hyperion/external_interaction/callbacks/zocalo_callback.py +92 -0
  78. mx_bluesky/hyperion/external_interaction/config_server.py +35 -0
  79. mx_bluesky/hyperion/external_interaction/exceptions.py +13 -0
  80. mx_bluesky/hyperion/external_interaction/ispyb/__init__.py +0 -0
  81. mx_bluesky/hyperion/external_interaction/ispyb/data_model.py +95 -0
  82. mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +125 -0
  83. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +276 -0
  84. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_utils.py +29 -0
  85. mx_bluesky/hyperion/external_interaction/nexus/__init__.py +0 -0
  86. mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +148 -0
  87. mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +114 -0
  88. mx_bluesky/hyperion/log.py +99 -0
  89. mx_bluesky/hyperion/parameters/__init__.py +2 -0
  90. mx_bluesky/hyperion/parameters/cli.py +68 -0
  91. mx_bluesky/{parameters → hyperion/parameters}/components.py +77 -24
  92. mx_bluesky/hyperion/parameters/constants.py +158 -0
  93. mx_bluesky/hyperion/parameters/gridscan.py +216 -0
  94. mx_bluesky/hyperion/parameters/rotation.py +160 -0
  95. mx_bluesky/hyperion/resources/panda/panda-gridscan.yaml +964 -0
  96. mx_bluesky/hyperion/tracing.py +28 -0
  97. mx_bluesky/hyperion/utils/context.py +84 -0
  98. mx_bluesky/hyperion/utils/utils.py +25 -0
  99. mx_bluesky/hyperion/utils/validation.py +196 -0
  100. mx_bluesky/jupyter_example.ipynb +3 -2
  101. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.1.0.dist-info}/METADATA +26 -11
  102. mx_bluesky-1.1.0.dist-info/RECORD +136 -0
  103. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.1.0.dist-info}/WHEEL +1 -1
  104. mx_bluesky-1.1.0.dist-info/entry_points.txt +8 -0
  105. mx_bluesky/i04/__init__.py +0 -3
  106. mx_bluesky/i24/serial/parameters/__init__.py +0 -15
  107. mx_bluesky/parameters/__init__.py +0 -31
  108. mx_bluesky-0.3.1.dist-info/RECORD +0 -67
  109. mx_bluesky-0.3.1.dist-info/entry_points.txt +0 -4
  110. /mx_bluesky/{i24 → beamlines}/__init__.py +0 -0
  111. /mx_bluesky/{i04 → beamlines/i04}/callbacks/murko_callback.py +0 -0
  112. /mx_bluesky/{i24/serial/extruder → beamlines/i24}/__init__.py +0 -0
  113. /mx_bluesky/{i24 → beamlines/i24}/serial/__init__.py +0 -0
  114. /mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/microdrop_alignment.edl +0 -0
  115. /mx_bluesky/{i24/serial/fixed_target → beamlines/i24/serial/extruder}/__init__.py +0 -0
  116. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/ME14E-GeneralPurpose.edl +0 -0
  117. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/PMAC_Command.edl +0 -0
  118. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/Shutter_Control.edl +0 -0
  119. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/microdrop_alignment.edl +0 -0
  120. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/nudgechip.edl +0 -0
  121. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/short1-laser.png +0 -0
  122. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/short2-laser.png +0 -0
  123. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/ft_utils.py +0 -0
  124. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/cs/cs_maker.json +0 -0
  125. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/cs/motor_direction.txt +0 -0
  126. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/minichip-oxford.pvar +0 -0
  127. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/oxford.pvar +0 -0
  128. /mx_bluesky/{i24 → beamlines/i24}/serial/run_extruder.sh +0 -0
  129. /mx_bluesky/{i24 → beamlines/i24}/serial/run_fixed_target.sh +0 -0
  130. /mx_bluesky/{i24 → beamlines/i24}/serial/run_ssx.sh +0 -0
  131. /mx_bluesky/{i24 → beamlines/i24}/serial/set_visit_directory.sh +0 -0
  132. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/__init__.py +0 -0
  133. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/ca.py +0 -0
  134. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/pv.py +0 -0
  135. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_zebra_plans.py +0 -0
  136. /mx_bluesky/{i24 → beamlines/i24}/serial/start_blueapi.sh +0 -0
  137. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.1.0.dist-info}/LICENSE +0 -0
  138. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,463 @@
1
+ import dataclasses
2
+ from enum import Enum
3
+
4
+ import bluesky.plan_stubs as bps
5
+ import bluesky.preprocessors as bpp
6
+ import numpy as np
7
+ from blueapi.core import BlueskyContext
8
+ from dodal.devices.attenuator import Attenuator
9
+ from dodal.devices.xspress3.xspress3 import Xspress3
10
+ from dodal.devices.zebra_controlled_shutter import ZebraShutter, ZebraShutterState
11
+
12
+ from mx_bluesky.hyperion.log import LOGGER
13
+ from mx_bluesky.hyperion.utils.context import device_composite_from_context
14
+
15
+
16
+ class AttenuationOptimisationFailedException(Exception):
17
+ pass
18
+
19
+
20
+ class Direction(Enum):
21
+ POSITIVE = "positive"
22
+ NEGATIVE = "negative"
23
+
24
+
25
+ @dataclasses.dataclass
26
+ class OptimizeAttenuationComposite:
27
+ """All devices which are directly or indirectly required by this plan"""
28
+
29
+ attenuator: Attenuator
30
+ sample_shutter: ZebraShutter
31
+ xspress3mini: Xspress3
32
+
33
+
34
+ def create_devices(context: BlueskyContext) -> OptimizeAttenuationComposite:
35
+ return device_composite_from_context(context, OptimizeAttenuationComposite)
36
+
37
+
38
+ def check_parameters(
39
+ target,
40
+ upper_count_limit,
41
+ lower_count_limit,
42
+ default_high_roi,
43
+ default_low_roi,
44
+ initial_transmission,
45
+ upper_transmission,
46
+ lower_transmission,
47
+ ):
48
+ if target < lower_count_limit or target > upper_count_limit:
49
+ raise (
50
+ ValueError(
51
+ f"Target {target} is outside of lower and upper bounds: {lower_count_limit} to {upper_count_limit}"
52
+ )
53
+ )
54
+
55
+ if default_high_roi < default_low_roi:
56
+ raise ValueError(
57
+ f"Upper roi {default_high_roi} must be greater than lower roi {default_low_roi}"
58
+ )
59
+
60
+ if upper_transmission < lower_transmission:
61
+ raise ValueError(
62
+ f"Upper transmission limit {upper_transmission} must be greater than lower tranmission limit {lower_transmission}"
63
+ )
64
+
65
+ if not upper_transmission >= initial_transmission >= lower_transmission:
66
+ raise ValueError(
67
+ f"initial transmission {initial_transmission} is outside range {lower_transmission} - {upper_transmission}"
68
+ )
69
+
70
+
71
+ def is_counts_within_target(total_count, lower_count_limit, upper_count_limit) -> bool:
72
+ if lower_count_limit <= total_count and total_count <= upper_count_limit:
73
+ return True
74
+ else:
75
+ return False
76
+
77
+
78
+ def calculate_new_direction(direction: Direction, deadtime, deadtime_threshold):
79
+ if direction == Direction.POSITIVE:
80
+ if deadtime > deadtime_threshold:
81
+ direction = Direction.NEGATIVE
82
+ LOGGER.info(
83
+ "Found tranmission to go above deadtime threshold. Reducing transmission..."
84
+ )
85
+ return direction
86
+
87
+
88
+ def deadtime_calc_new_transmission(
89
+ direction: Direction,
90
+ transmission: float,
91
+ increment: float,
92
+ upper_transmission_limit: float,
93
+ lower_transmission_limit: float,
94
+ ) -> float:
95
+ """Calculate the new transmission value based on the current direction and increment. Raise error if transmission is too low.
96
+
97
+ Args:
98
+ direction (Direction):
99
+ If positive, increase transmission by a factor of the increment. If negative, divide it
100
+
101
+ transmission (float):
102
+ Current transmission value
103
+
104
+ increment (float):
105
+ Factor to multiply or divide transmission by
106
+
107
+ upper_transmission_limit (float):
108
+ Maximum allowed transmission, in order to protect sample.
109
+
110
+ lower_transmission_limit (float):
111
+ Minimum expected transmission. Raise an error if transmission goes lower.
112
+
113
+ Raises:
114
+ AttenuationOptimisationFailedException:
115
+ This error is thrown if the transmission goes below the expected value or if the maximum cycles are reached
116
+
117
+ Returns:
118
+ transmission (float): Optimised transmission value
119
+ """
120
+ if direction == Direction.POSITIVE:
121
+ transmission *= increment
122
+ if transmission > upper_transmission_limit:
123
+ transmission = upper_transmission_limit
124
+ else:
125
+ transmission /= increment
126
+ if transmission < lower_transmission_limit:
127
+ raise AttenuationOptimisationFailedException(
128
+ "Calculated transmission is below expected limit"
129
+ )
130
+ return transmission
131
+
132
+
133
+ def do_device_optimise_iteration(
134
+ composite: OptimizeAttenuationComposite,
135
+ transmission,
136
+ ):
137
+ def close_shutter():
138
+ yield from bps.abs_set(
139
+ composite.sample_shutter, ZebraShutterState.CLOSE, wait=True
140
+ )
141
+
142
+ @bpp.finalize_decorator(close_shutter)
143
+ def open_and_run():
144
+ """Set transmission, set number of images on xspress3mini, arm xspress3mini"""
145
+ yield from bps.abs_set(
146
+ composite.attenuator, transmission, group="set_transmission"
147
+ )
148
+ yield from bps.abs_set(composite.xspress3mini.set_num_images, 1, wait=True)
149
+ yield from bps.abs_set(
150
+ composite.sample_shutter, ZebraShutterState.OPEN, wait=True
151
+ )
152
+ yield from bps.stage(composite.xspress3mini, wait=True)
153
+ yield from bps.unstage(composite.xspress3mini, wait=True)
154
+
155
+ yield from open_and_run()
156
+
157
+
158
+ def is_deadtime_optimised(
159
+ deadtime: float,
160
+ deadtime_threshold: float,
161
+ transmission: float,
162
+ upper_transmission_limit: float,
163
+ direction: Direction,
164
+ ) -> bool:
165
+ if direction == Direction.POSITIVE:
166
+ if transmission == upper_transmission_limit:
167
+ LOGGER.warning(
168
+ f"Deadtime {deadtime} is above threshold {deadtime_threshold} at maximum transmission {upper_transmission_limit}. Using maximum transmission\
169
+ as optimised value."
170
+ )
171
+ return True
172
+ # Once direction is flipped and deadtime goes back above threshold, we consider attenuation to be optimised.
173
+ else:
174
+ if deadtime <= deadtime_threshold:
175
+ return True
176
+ return False
177
+
178
+
179
+ def deadtime_optimisation(
180
+ composite: OptimizeAttenuationComposite,
181
+ transmission: float,
182
+ increment: float,
183
+ deadtime_threshold: float,
184
+ max_cycles: int,
185
+ upper_transmission_limit: float,
186
+ lower_transmission_limit: float,
187
+ ):
188
+ """Optimises the attenuation for the Xspress3Mini based on the detector deadtime
189
+
190
+ Deadtime is the time after each event during which the detector cannot record another event. This loop adjusts the transmission of the attenuator
191
+ and checks the deadtime until the percentage deadtime is below the accepted threshold. To protect the sample, the transmission has a maximum value
192
+
193
+ Here we use the percentage deadtime - the percentage of time to which the detector is unable to process events.
194
+
195
+ This algorithm gradually increases the transmission until the percentage deadtime goes beneath the specified threshold. It then increases
196
+ the transmission and stops when the deadtime goes above the threshold. A smaller increment will provide a better optimised value, but take more
197
+ cycles to complete.
198
+
199
+ Args:
200
+ attenuator: (Attenuator) Ophyd device
201
+
202
+ xspress3mini: (Xspress3Mini) Ophyd device
203
+
204
+ sample_shutter: (ZebraShutter) Ophyd_async device for the fast shutter
205
+
206
+ transmission: (float)
207
+ The initial transmission value to use for the optimising
208
+
209
+ increment: (float)
210
+ The factor to increase / decrease the transmission by each iteration
211
+
212
+ deadtime_threshold: (float)
213
+ The maximum acceptable percentage deadtime
214
+
215
+ max_cycles: (int)
216
+ The maximum number of iterations before an error is thrown
217
+
218
+ upper_transmission_limit (float):
219
+ Maximum allowed transmission, in order to protect sample.
220
+
221
+ lower_transmission_limit (float):
222
+ Minimum expected transmission. Raise an error if transmission goes lower.
223
+
224
+ Raises:
225
+ AttenuationOptimisationFailedException:
226
+ This error is thrown if the transmission goes below the expected value or the maximum cycles are reached
227
+
228
+ Returns:
229
+ optimised_transmission: (float)
230
+ The final transmission value which produces an acceptable deadtime
231
+ """
232
+
233
+ direction = Direction.POSITIVE
234
+ LOGGER.info(f"Target deadtime is {deadtime_threshold}")
235
+ optimised_transmission: float = 0
236
+ for cycle in range(0, max_cycles):
237
+ yield from do_device_optimise_iteration(composite, transmission)
238
+
239
+ total_time = yield from bps.rd(composite.xspress3mini.channels[1].total_time)
240
+ reset_ticks = yield from bps.rd(composite.xspress3mini.channels[1].reset_ticks)
241
+
242
+ LOGGER.info(f"Current total time = {total_time}")
243
+ LOGGER.info(f"Current reset ticks = {reset_ticks}")
244
+ deadtime = 0
245
+
246
+ """
247
+ The reset ticks PV stops ticking while the detector is unable to process events, so the absolute difference between the total time and the
248
+ reset ticks time gives the deadtime in unit time. Divide by total time to get it as a percentage.
249
+ """
250
+
251
+ if total_time != reset_ticks:
252
+ deadtime = 1 - abs(total_time - reset_ticks) / (total_time)
253
+
254
+ LOGGER.info(f"Deadtime is now at {deadtime}")
255
+
256
+ # Check if new deadtime is OK
257
+
258
+ if is_deadtime_optimised(
259
+ deadtime,
260
+ deadtime_threshold,
261
+ transmission,
262
+ upper_transmission_limit,
263
+ direction,
264
+ ):
265
+ optimised_transmission = transmission
266
+ break
267
+
268
+ if cycle == max_cycles - 1:
269
+ raise AttenuationOptimisationFailedException(
270
+ f"Unable to optimise attenuation after maximum cycles.\
271
+ Deadtime did not get lower than threshold: {deadtime_threshold} in maximum cycles {max_cycles}"
272
+ )
273
+
274
+ direction = calculate_new_direction(direction, deadtime, deadtime_threshold)
275
+
276
+ transmission = deadtime_calc_new_transmission(
277
+ direction,
278
+ transmission,
279
+ increment,
280
+ upper_transmission_limit,
281
+ lower_transmission_limit,
282
+ )
283
+
284
+ return optimised_transmission
285
+
286
+
287
+ def total_counts_optimisation(
288
+ composite: OptimizeAttenuationComposite,
289
+ transmission: float,
290
+ low_roi: int,
291
+ high_roi: int,
292
+ lower_count_limit: float,
293
+ upper_count_limit: float,
294
+ target_count: float,
295
+ max_cycles: int,
296
+ upper_transmission_limit: float,
297
+ lower_transmission_limit: float,
298
+ ):
299
+ """Optimises the attenuation for the Xspress3Mini based on the total counts
300
+
301
+ This loop adjusts the transmission of the attenuator and checks the total counts of the detector until the total counts as in the acceptable range,
302
+ defined by the lower and upper limit. To protect the sample, the transmission has a maximum value of 10%.
303
+
304
+ Args:
305
+ attenuator: (Attenuator) Ophyd device
306
+
307
+ xspress3mini: (Xspress3Mini) Ophyd device
308
+
309
+ sample_shutter: (ZebraShutter) Ophyd_async device for the fast shutter
310
+
311
+ transmission: (float)
312
+ The initial transmission value to use for the optimising
313
+
314
+ low_roi: (float)
315
+ Lower region of interest at which to include in the counts
316
+
317
+ high_roi: (float)
318
+ Upper region of interest at which to include in the counts
319
+
320
+ lower_count_limit: (float)
321
+ The lowest acceptable value for count
322
+
323
+ upper_count_limit: (float)
324
+ The highest acceptable value for count
325
+
326
+ target_count: (int)
327
+ The ideal number of target counts - used to calculate the transmission for the subsequent iteration.
328
+
329
+ max_cycles: (int)
330
+ The maximum number of iterations before an error is thrown
331
+
332
+ upper_transmission_limit: (float)
333
+ The maximum allowed value for the transmission
334
+
335
+ lower_transmission_limit: (float)
336
+ The minimum allowed value for the transmission
337
+
338
+ Returns:
339
+ optimised_transmission: (float)
340
+ The final transmission value which produces an acceptable total_count value
341
+ """
342
+
343
+ LOGGER.info("Using total count optimisation")
344
+ optimised_transmission: float = 0
345
+ for cycle in range(0, max_cycles):
346
+ LOGGER.info(
347
+ f"Setting transmission to {transmission} for attenuation optimisation cycle {cycle}"
348
+ )
349
+
350
+ yield from do_device_optimise_iteration(composite, transmission)
351
+
352
+ data = np.array(
353
+ (yield from bps.rd(composite.xspress3mini.dt_corrected_latest_mca[1]))
354
+ )
355
+ total_count = sum(data[int(low_roi) : int(high_roi)])
356
+ LOGGER.info(f"Total count is {total_count}")
357
+
358
+ if is_counts_within_target(total_count, lower_count_limit, upper_count_limit):
359
+ optimised_transmission = transmission
360
+ LOGGER.info(
361
+ f"Total count is within accepted limits: {lower_count_limit}, {total_count}, {upper_count_limit}"
362
+ )
363
+ break
364
+ elif transmission == upper_transmission_limit:
365
+ LOGGER.warning(
366
+ f"Total count is not within limits: {lower_count_limit} <= {total_count} <= {upper_count_limit}\
367
+ after using maximum transmission {upper_transmission_limit}. Continuing\
368
+ with maximum transmission as optimised value..."
369
+ )
370
+ optimised_transmission = transmission
371
+ break
372
+
373
+ else:
374
+ transmission = (target_count / (total_count)) * transmission
375
+ if transmission > upper_transmission_limit:
376
+ transmission = upper_transmission_limit
377
+ elif transmission < lower_transmission_limit:
378
+ raise AttenuationOptimisationFailedException(
379
+ f"Transmission has gone below lower threshold {lower_transmission_limit}"
380
+ )
381
+
382
+ if cycle == max_cycles - 1:
383
+ raise AttenuationOptimisationFailedException(
384
+ f"Unable to optimise attenuation after maximum cycles.\
385
+ Total count is not within limits: {lower_count_limit} <= {total_count}\
386
+ <= {upper_count_limit}"
387
+ )
388
+
389
+ return optimised_transmission
390
+
391
+
392
+ def optimise_attenuation_plan(
393
+ composite: OptimizeAttenuationComposite,
394
+ collection_time=1, # Comes from self.parameters.acquisitionTime in fluorescence_spectrum.py
395
+ optimisation_type="deadtime",
396
+ low_roi=100,
397
+ high_roi=2048,
398
+ upper_transmission_limit=0.1,
399
+ lower_transmission_limit=1.0e-6,
400
+ initial_transmission=0.1,
401
+ target_count=20000,
402
+ lower_count_limit=20000,
403
+ upper_count_limit=50000,
404
+ max_cycles=10,
405
+ increment=2,
406
+ deadtime_threshold=0.002,
407
+ ):
408
+ check_parameters(
409
+ target_count,
410
+ upper_count_limit,
411
+ lower_count_limit,
412
+ high_roi,
413
+ low_roi,
414
+ initial_transmission,
415
+ upper_transmission_limit,
416
+ lower_transmission_limit,
417
+ )
418
+
419
+ yield from bps.abs_set(
420
+ composite.xspress3mini.acquire_time, collection_time, wait=True
421
+ ) # Don't necessarily need to wait here
422
+ optimised_transmission: float = 0
423
+ # Do the attenuation optimisation using count threshold
424
+ if optimisation_type == "total_counts":
425
+ LOGGER.info(
426
+ f"Starting Xspress3Mini total counts optimisation routine \nOptimisation will be performed across ROI channels {low_roi} - {high_roi}"
427
+ )
428
+
429
+ optimised_transmission = yield from total_counts_optimisation(
430
+ composite,
431
+ initial_transmission,
432
+ low_roi,
433
+ high_roi,
434
+ lower_count_limit,
435
+ upper_count_limit,
436
+ target_count,
437
+ max_cycles,
438
+ upper_transmission_limit,
439
+ lower_transmission_limit,
440
+ )
441
+
442
+ elif optimisation_type == "deadtime":
443
+ LOGGER.info(
444
+ f"Starting Xspress3Mini deadtime optimisation routine \nOptimisation will be performed across ROI channels {low_roi} - {high_roi}"
445
+ )
446
+ optimised_transmission = yield from deadtime_optimisation(
447
+ composite,
448
+ initial_transmission,
449
+ upper_transmission_limit,
450
+ lower_transmission_limit,
451
+ increment,
452
+ deadtime_threshold,
453
+ max_cycles,
454
+ )
455
+
456
+ yield from bps.abs_set(
457
+ composite.attenuator,
458
+ optimised_transmission,
459
+ group="set_transmission",
460
+ wait=True,
461
+ )
462
+
463
+ return optimised_transmission
@@ -0,0 +1,119 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+
5
+ from blueapi.core import BlueskyContext, MsgGenerator
6
+ from dodal.devices.eiger import EigerDetector
7
+ from dodal.devices.oav.oav_parameters import OAV_CONFIG_JSON, OAVParameters
8
+
9
+ from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import move_phi_chi_omega
10
+ from mx_bluesky.hyperion.device_setup_plans.utils import (
11
+ start_preparing_data_collection_then_do_plan,
12
+ )
13
+ from mx_bluesky.hyperion.experiment_plans.grid_detect_then_xray_centre_plan import (
14
+ GridDetectThenXRayCentreComposite,
15
+ detect_grid_and_do_gridscan,
16
+ )
17
+ from mx_bluesky.hyperion.experiment_plans.oav_snapshot_plan import (
18
+ setup_beamline_for_OAV,
19
+ )
20
+ from mx_bluesky.hyperion.experiment_plans.pin_tip_centring_plan import (
21
+ PinTipCentringComposite,
22
+ pin_tip_centre_plan,
23
+ )
24
+ from mx_bluesky.hyperion.external_interaction.callbacks.xray_centre.ispyb_callback import (
25
+ ispyb_activation_wrapper,
26
+ )
27
+ from mx_bluesky.hyperion.log import LOGGER
28
+ from mx_bluesky.hyperion.parameters.constants import CONST
29
+ from mx_bluesky.hyperion.parameters.gridscan import (
30
+ GridScanWithEdgeDetect,
31
+ PinTipCentreThenXrayCentre,
32
+ )
33
+ from mx_bluesky.hyperion.utils.context import device_composite_from_context
34
+
35
+
36
+ def create_devices(context: BlueskyContext) -> GridDetectThenXRayCentreComposite:
37
+ """
38
+ GridDetectThenXRayCentreComposite contains all the devices we need, reuse that.
39
+ """
40
+ return device_composite_from_context(context, GridDetectThenXRayCentreComposite)
41
+
42
+
43
+ def create_parameters_for_grid_detection(
44
+ pin_centre_parameters: PinTipCentreThenXrayCentre,
45
+ ) -> GridScanWithEdgeDetect:
46
+ params_json = json.loads(pin_centre_parameters.model_dump_json())
47
+ del params_json["tip_offset_um"]
48
+ grid_detect_and_xray_centre = GridScanWithEdgeDetect(**params_json)
49
+ LOGGER.info(
50
+ f"Parameters for grid detect and xray centre: {grid_detect_and_xray_centre.model_dump_json(indent=2)}"
51
+ )
52
+ return grid_detect_and_xray_centre
53
+
54
+
55
+ def pin_centre_then_xray_centre_plan(
56
+ composite: GridDetectThenXRayCentreComposite,
57
+ parameters: PinTipCentreThenXrayCentre,
58
+ oav_config_file: str = OAV_CONFIG_JSON,
59
+ ):
60
+ """Plan that perfoms a pin tip centre followed by an xray centre to completely
61
+ centre the sample"""
62
+ oav_config_file = parameters.oav_centring_file
63
+
64
+ pin_tip_centring_composite = PinTipCentringComposite(
65
+ oav=composite.oav,
66
+ smargon=composite.smargon,
67
+ backlight=composite.backlight,
68
+ pin_tip_detection=composite.pin_tip_detection,
69
+ )
70
+
71
+ def _pin_centre_then_xray_centre_plan():
72
+ yield from setup_beamline_for_OAV(
73
+ composite.smargon, composite.backlight, composite.aperture_scatterguard
74
+ )
75
+
76
+ yield from move_phi_chi_omega(
77
+ composite.smargon,
78
+ parameters.phi_start_deg,
79
+ parameters.chi_start_deg,
80
+ group=CONST.WAIT.READY_FOR_OAV,
81
+ )
82
+
83
+ yield from pin_tip_centre_plan(
84
+ pin_tip_centring_composite,
85
+ parameters.tip_offset_um,
86
+ oav_config_file,
87
+ )
88
+
89
+ grid_detect_params = create_parameters_for_grid_detection(parameters)
90
+
91
+ oav_params = OAVParameters("xrayCentring", oav_config_file)
92
+
93
+ yield from detect_grid_and_do_gridscan(
94
+ composite,
95
+ grid_detect_params,
96
+ oav_params,
97
+ )
98
+
99
+ yield from ispyb_activation_wrapper(_pin_centre_then_xray_centre_plan(), parameters)
100
+
101
+
102
+ def pin_tip_centre_then_xray_centre(
103
+ composite: GridDetectThenXRayCentreComposite,
104
+ parameters: PinTipCentreThenXrayCentre,
105
+ oav_config_file: str = OAV_CONFIG_JSON,
106
+ ) -> MsgGenerator:
107
+ """Starts preparing for collection then performs the pin tip centre and xray centre"""
108
+
109
+ eiger: EigerDetector = composite.eiger
110
+
111
+ eiger.set_detector_parameters(parameters.detector_params)
112
+
113
+ return start_preparing_data_collection_then_do_plan(
114
+ eiger,
115
+ composite.detector_motion,
116
+ parameters.detector_params.detector_distance,
117
+ pin_centre_then_xray_centre_plan(composite, parameters, oav_config_file),
118
+ group=CONST.WAIT.GRID_READY_FOR_DC,
119
+ )