mx-bluesky 1.2.0__py3-none-any.whl → 1.4.1a0__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 (94) hide show
  1. mx_bluesky/__init__.py +8 -3
  2. mx_bluesky/__main__.py +12 -7
  3. mx_bluesky/_version.py +2 -2
  4. mx_bluesky/beamlines/i04/callbacks/murko_callback.py +14 -4
  5. mx_bluesky/beamlines/i04/thawing_plan.py +49 -11
  6. mx_bluesky/beamlines/i24/serial/__init__.py +3 -0
  7. mx_bluesky/beamlines/i24/serial/dcid.py +19 -21
  8. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +69 -91
  9. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +2 -5
  10. mx_bluesky/beamlines/i24/serial/fixed_target/ft_utils.py +0 -1
  11. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +111 -143
  12. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +141 -222
  13. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +7 -216
  14. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +18 -17
  15. mx_bluesky/beamlines/i24/serial/log.py +58 -49
  16. mx_bluesky/beamlines/i24/serial/parameters/constants.py +0 -1
  17. mx_bluesky/beamlines/i24/serial/parameters/fixed_target/cs/cs_maker.json +3 -3
  18. mx_bluesky/beamlines/i24/serial/run_extruder.sh +30 -5
  19. mx_bluesky/beamlines/i24/serial/run_fixed_target.sh +30 -5
  20. mx_bluesky/beamlines/i24/serial/run_serial.py +24 -8
  21. mx_bluesky/beamlines/i24/serial/setup_beamline/ca.py +0 -2
  22. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +79 -81
  23. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +9 -20
  24. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +26 -28
  25. mx_bluesky/beamlines/i24/serial/write_nexus.py +11 -11
  26. mx_bluesky/common/__init__.py +0 -0
  27. mx_bluesky/common/device_setup_plans/read_hardware_for_setup.py +14 -0
  28. mx_bluesky/common/external_interaction/config_server.py +46 -0
  29. mx_bluesky/common/parameters/components.py +258 -0
  30. mx_bluesky/common/parameters/constants.py +138 -0
  31. mx_bluesky/common/parameters/gridscan.py +94 -0
  32. mx_bluesky/common/parameters/robot_load.py +16 -0
  33. mx_bluesky/common/plans/__init__.py +1 -0
  34. mx_bluesky/common/plans/do_fgs.py +121 -0
  35. mx_bluesky/common/utils/log.py +118 -0
  36. mx_bluesky/{hyperion → common/utils}/tracing.py +2 -2
  37. mx_bluesky/hyperion/__main__.py +13 -10
  38. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +31 -26
  39. mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +6 -12
  40. mx_bluesky/hyperion/device_setup_plans/setup_oav.py +6 -12
  41. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +5 -6
  42. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +49 -18
  43. mx_bluesky/hyperion/device_setup_plans/smargon.py +6 -6
  44. mx_bluesky/hyperion/device_setup_plans/utils.py +2 -2
  45. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +4 -4
  46. mx_bluesky/hyperion/experiment_plans/__init__.py +4 -0
  47. mx_bluesky/hyperion/experiment_plans/change_aperture_then_move_plan.py +83 -0
  48. mx_bluesky/hyperion/experiment_plans/common/xrc_result.py +47 -0
  49. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +9 -9
  50. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +145 -161
  51. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +56 -22
  52. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +52 -10
  53. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +21 -20
  54. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +11 -14
  55. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +2 -2
  56. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +40 -21
  57. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +19 -19
  58. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +21 -21
  59. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +51 -13
  60. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +24 -7
  61. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +5 -6
  62. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +1 -2
  63. mx_bluesky/hyperion/external_interaction/callbacks/common/abstract_event.py +66 -0
  64. mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +1 -1
  65. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +30 -25
  66. mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +29 -12
  67. mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +1 -1
  68. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +1 -1
  69. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +7 -4
  70. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +5 -3
  71. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +28 -20
  72. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +5 -4
  73. mx_bluesky/hyperion/external_interaction/config_server.py +11 -28
  74. mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +1 -1
  75. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +1 -1
  76. mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +2 -2
  77. mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +1 -1
  78. mx_bluesky/hyperion/log.py +0 -84
  79. mx_bluesky/hyperion/parameters/components.py +4 -251
  80. mx_bluesky/hyperion/parameters/constants.py +22 -119
  81. mx_bluesky/hyperion/parameters/gridscan.py +35 -74
  82. mx_bluesky/hyperion/parameters/load_centre_collect.py +16 -11
  83. mx_bluesky/hyperion/parameters/rotation.py +23 -10
  84. mx_bluesky/hyperion/utils/utils.py +17 -0
  85. mx_bluesky/hyperion/utils/validation.py +5 -6
  86. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/METADATA +36 -33
  87. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/RECORD +91 -81
  88. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/WHEEL +1 -1
  89. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +0 -161
  90. mx_bluesky/example.py +0 -19
  91. mx_bluesky/hyperion/parameters/robot_load.py +0 -16
  92. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/LICENSE +0 -0
  93. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/entry_points.txt +0 -0
  94. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/top_level.txt +0 -0
@@ -1,19 +1,20 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import dataclasses
4
- from collections.abc import Callable
4
+ from collections.abc import Callable, Sequence
5
5
  from functools import partial
6
6
  from pathlib import Path
7
- from time import time
8
7
  from typing import Protocol
9
8
 
10
9
  import bluesky.plan_stubs as bps
11
10
  import bluesky.preprocessors as bpp
12
11
  import numpy as np
13
- from blueapi.core import BlueskyContext, MsgGenerator
12
+ import pydantic
13
+ from blueapi.core import BlueskyContext
14
+ from bluesky.callbacks import CallbackBase
15
+ from bluesky.utils import MsgGenerator
14
16
  from dodal.devices.aperturescatterguard import (
15
17
  ApertureScatterguard,
16
- ApertureValue,
17
18
  )
18
19
  from dodal.devices.attenuator import Attenuator
19
20
  from dodal.devices.backlight import Backlight
@@ -30,7 +31,7 @@ from dodal.devices.fast_grid_scan import (
30
31
  from dodal.devices.flux import Flux
31
32
  from dodal.devices.robot import BartRobot
32
33
  from dodal.devices.s4_slit_gaps import S4SlitGaps
33
- from dodal.devices.smargon import Smargon, StubPosition
34
+ from dodal.devices.smargon import Smargon
34
35
  from dodal.devices.synchrotron import Synchrotron
35
36
  from dodal.devices.undulator import Undulator
36
37
  from dodal.devices.xbpm_feedback import XBPMFeedback
@@ -39,17 +40,17 @@ from dodal.devices.zebra_controlled_shutter import ZebraShutter
39
40
  from dodal.devices.zocalo.zocalo_results import (
40
41
  ZOCALO_READING_PLAN_NAME,
41
42
  ZOCALO_STAGE_GROUP,
43
+ XrcResult,
42
44
  ZocaloResults,
43
- get_processing_result,
45
+ get_full_processing_results,
44
46
  )
45
- from dodal.plans.check_topup import check_topup_and_wait_if_necessary
47
+ from event_model import RunStart
46
48
  from ophyd_async.fastcs.panda import HDFPanda
47
- from scanspec.core import AxesPoints, Axis
48
49
 
49
- from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import move_x_y_z
50
+ from mx_bluesky.common.plans.do_fgs import kickoff_and_complete_gridscan
51
+ from mx_bluesky.common.utils.tracing import TRACER
50
52
  from mx_bluesky.hyperion.device_setup_plans.read_hardware_for_setup import (
51
53
  read_hardware_during_collection,
52
- read_hardware_for_zocalo,
53
54
  read_hardware_pre_collection,
54
55
  )
55
56
  from mx_bluesky.hyperion.device_setup_plans.setup_panda import (
@@ -66,10 +67,16 @@ from mx_bluesky.hyperion.device_setup_plans.xbpm_feedback import (
66
67
  transmission_and_xbpm_feedback_for_collection_decorator,
67
68
  )
68
69
  from mx_bluesky.hyperion.exceptions import WarningException
70
+ from mx_bluesky.hyperion.experiment_plans.change_aperture_then_move_plan import (
71
+ change_aperture_then_move_to_xtal,
72
+ )
73
+ from mx_bluesky.hyperion.experiment_plans.common.xrc_result import XRayCentreResult
74
+ from mx_bluesky.hyperion.external_interaction.callbacks.xray_centre.ispyb_callback import (
75
+ ispyb_activation_wrapper,
76
+ )
69
77
  from mx_bluesky.hyperion.log import LOGGER
70
78
  from mx_bluesky.hyperion.parameters.constants import CONST
71
- from mx_bluesky.hyperion.parameters.gridscan import ThreeDGridScan
72
- from mx_bluesky.hyperion.tracing import TRACER
79
+ from mx_bluesky.hyperion.parameters.gridscan import HyperionThreeDGridScan
73
80
  from mx_bluesky.hyperion.utils.context import device_composite_from_context
74
81
 
75
82
 
@@ -83,7 +90,7 @@ class CrystalNotFoundException(WarningException):
83
90
  pass
84
91
 
85
92
 
86
- @dataclasses.dataclass
93
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
87
94
  class FlyScanXRayCentreComposite:
88
95
  """All devices which are directly or indirectly required by this plan"""
89
96
 
@@ -112,30 +119,33 @@ class FlyScanXRayCentreComposite:
112
119
  return self.smargon
113
120
 
114
121
 
122
+ class XRayCentreEventHandler(CallbackBase):
123
+ def __init__(self):
124
+ super().__init__()
125
+ self.xray_centre_results: Sequence[XRayCentreResult] | None = None
126
+
127
+ def start(self, doc: RunStart) -> RunStart | None:
128
+ if "xray_centre_results" in doc:
129
+ self.xray_centre_results = [
130
+ XRayCentreResult(**result_dict)
131
+ for result_dict in doc["xray_centre_results"] # type: ignore
132
+ ]
133
+ return doc
134
+
135
+
115
136
  def create_devices(context: BlueskyContext) -> FlyScanXRayCentreComposite:
116
137
  """Creates the devices required for the plan and connect to them"""
117
138
  return device_composite_from_context(context, FlyScanXRayCentreComposite)
118
139
 
119
140
 
120
- def flyscan_xray_centre(
121
- composite: FlyScanXRayCentreComposite,
122
- parameters: ThreeDGridScan,
141
+ def flyscan_xray_centre_no_move(
142
+ composite: FlyScanXRayCentreComposite, parameters: HyperionThreeDGridScan
123
143
  ) -> MsgGenerator:
124
- """Create the plan to run the grid scan based on provided parameters.
125
-
126
- The ispyb handler should be added to the whole gridscan as we want to capture errors
127
- at any point in it.
128
-
129
- Args:
130
- parameters (ThreeDGridScan): The parameters to run the scan.
131
-
132
- Returns:
133
- Generator: The plan for the gridscan
134
- """
144
+ """Perform a flyscan and determine the centres of interest"""
135
145
  parameters.features.update_self_from_server()
136
146
  composite.eiger.set_detector_parameters(parameters.detector_params)
137
- composite.zocalo.zocalo_environment = parameters.zocalo_environment
138
- composite.zocalo.use_cpu_and_gpu = parameters.use_cpu_and_gpu_zocalo
147
+ composite.zocalo.zocalo_environment = CONST.ZOCALO_ENV
148
+ composite.zocalo.use_cpu_and_gpu = parameters.features.compare_cpu_and_gpu_zocalo
139
149
 
140
150
  feature_controlled = _get_feature_controlled(composite, parameters)
141
151
 
@@ -143,8 +153,6 @@ def flyscan_xray_centre(
143
153
  @bpp.run_decorator( # attach experiment metadata to the start document
144
154
  md={
145
155
  "subplan_name": CONST.PLAN.GRIDSCAN_OUTER,
146
- CONST.TRIGGER.ZOCALO: CONST.PLAN.DO_FGS,
147
- "zocalo_environment": parameters.zocalo_environment,
148
156
  "hyperion_parameters": parameters.model_dump_json(),
149
157
  "activate_callbacks": [
150
158
  "GridscanNexusFileCallback",
@@ -157,25 +165,66 @@ def flyscan_xray_centre(
157
165
  composite.attenuator,
158
166
  parameters.transmission_frac,
159
167
  )
160
- def run_gridscan_and_move_and_tidy(
168
+ def run_gridscan_and_fetch_and_tidy(
161
169
  fgs_composite: FlyScanXRayCentreComposite,
162
- params: ThreeDGridScan,
170
+ params: HyperionThreeDGridScan,
163
171
  feature_controlled: _FeatureControlled,
164
- ):
165
- yield from run_gridscan_and_move(fgs_composite, params, feature_controlled)
172
+ ) -> MsgGenerator:
173
+ yield from run_gridscan_and_fetch_results(
174
+ fgs_composite, params, feature_controlled
175
+ )
176
+
177
+ yield from run_gridscan_and_fetch_and_tidy(
178
+ composite, parameters, feature_controlled
179
+ )
180
+
181
+
182
+ def flyscan_xray_centre(
183
+ composite: FlyScanXRayCentreComposite,
184
+ parameters: HyperionThreeDGridScan,
185
+ ) -> MsgGenerator:
186
+ """Create the plan to run the grid scan based on provided parameters.
187
+
188
+ The ispyb handler should be added to the whole gridscan as we want to capture errors
189
+ at any point in it.
190
+
191
+ Args:
192
+ parameters (ThreeDGridScan): The parameters to run the scan.
193
+
194
+ Returns:
195
+ Generator: The plan for the gridscan
196
+ """
197
+ xrc_event_handler = XRayCentreEventHandler()
198
+
199
+ @bpp.subs_decorator(xrc_event_handler)
200
+ def flyscan_and_fetch_results() -> MsgGenerator:
201
+ yield from ispyb_activation_wrapper(
202
+ flyscan_xray_centre_no_move(composite, parameters), parameters
203
+ )
166
204
 
167
- return run_gridscan_and_move_and_tidy(composite, parameters, feature_controlled)
205
+ yield from flyscan_and_fetch_results()
206
+
207
+ xray_centre_results = xrc_event_handler.xray_centre_results
208
+ assert (
209
+ xray_centre_results
210
+ ), "Flyscan result event not received or no crystal found and exception not raised"
211
+ yield from change_aperture_then_move_to_xtal(
212
+ xray_centre_results[0],
213
+ composite.smargon,
214
+ composite.aperture_scatterguard,
215
+ parameters,
216
+ )
168
217
 
169
218
 
170
219
  @bpp.set_run_key_decorator(CONST.PLAN.GRIDSCAN_AND_MOVE)
171
220
  @bpp.run_decorator(md={"subplan_name": CONST.PLAN.GRIDSCAN_AND_MOVE})
172
- def run_gridscan_and_move(
221
+ def run_gridscan_and_fetch_results(
173
222
  fgs_composite: FlyScanXRayCentreComposite,
174
- parameters: ThreeDGridScan,
223
+ parameters: HyperionThreeDGridScan,
175
224
  feature_controlled: _FeatureControlled,
176
225
  ) -> MsgGenerator:
177
226
  """A multi-run plan which runs a gridscan, gets the results from zocalo
178
- and moves to the centre of mass determined by zocalo"""
227
+ and fires an event with the centres of mass determined by zocalo"""
179
228
 
180
229
  # We get the initial motor positions so we can return to them on zocalo failure
181
230
  initial_xyz = np.array(
@@ -202,51 +251,66 @@ def run_gridscan_and_move(
202
251
  [fgs_composite.zocalo], name=ZOCALO_READING_PLAN_NAME
203
252
  )
204
253
  LOGGER.info("Zocalo triggered and read, interpreting results.")
205
- xray_centre, bbox_size = yield from get_processing_result(
206
- fgs_composite.zocalo
207
- )
208
- LOGGER.info(f"Got xray centre: {xray_centre}, bbox size: {bbox_size}")
209
- if xray_centre is not None:
210
- xray_centre = parameters.FGS_params.grid_position_to_motor_position(
211
- xray_centre
212
- )
254
+ xrc_results = yield from get_full_processing_results(fgs_composite.zocalo)
255
+ LOGGER.info(f"Got xray centres, top 5: {xrc_results[:5]}")
256
+ if xrc_results:
257
+ flyscan_results = [
258
+ _xrc_result_in_boxes_to_result_in_mm(xr, parameters)
259
+ for xr in xrc_results
260
+ ]
213
261
  else:
214
262
  LOGGER.warning("No X-ray centre received")
215
263
  raise CrystalNotFoundException()
216
- if bbox_size is not None:
217
- with TRACER.start_span("change_aperture"):
218
- yield from set_aperture_for_bbox_size(
219
- fgs_composite.aperture_scatterguard, bbox_size
220
- )
221
- else:
222
- LOGGER.warning("No bounding box size received")
223
-
224
- # once we have the results, go to the appropriate position
225
- LOGGER.info("Moving to centre of mass.")
226
- with TRACER.start_span("move_to_result"):
227
- x, y, z = xray_centre
228
- yield from move_x_y_z(fgs_composite.sample_motors, x, y, z, wait=True)
229
-
230
- if parameters.FGS_params.set_stub_offsets:
231
- LOGGER.info("Recentring smargon co-ordinate system to this point.")
232
- yield from bps.mv(
233
- fgs_composite.sample_motors.stub_offsets, StubPosition.CURRENT_AS_CENTER
234
- )
264
+ yield from _fire_xray_centre_result_event(flyscan_results)
265
+
235
266
  finally:
236
267
  # Turn off dev/shm streaming to avoid filling disk, see https://github.com/DiamondLightSource/hyperion/issues/1395
237
268
  LOGGER.info("Turning off Eiger dev/shm streaming")
238
- yield from bps.abs_set(fgs_composite.eiger.odin.fan.dev_shm_enable, 0)
269
+ yield from bps.abs_set(fgs_composite.eiger.odin.fan.dev_shm_enable, 0) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
239
270
 
240
271
  # Wait on everything before returning to GDA (particularly apertures), can be removed
241
272
  # when we do not return to GDA here
242
273
  yield from bps.wait()
243
274
 
244
275
 
276
+ def _xrc_result_in_boxes_to_result_in_mm(
277
+ xrc_result: XrcResult, parameters: HyperionThreeDGridScan
278
+ ) -> XRayCentreResult:
279
+ fgs_params = parameters.FGS_params
280
+ xray_centre = fgs_params.grid_position_to_motor_position(
281
+ np.array(xrc_result["centre_of_mass"])
282
+ )
283
+ return XRayCentreResult(
284
+ centre_of_mass_mm=xray_centre,
285
+ bounding_box_mm=(
286
+ fgs_params.grid_position_to_motor_position(
287
+ np.array(xrc_result["bounding_box"][0])
288
+ ),
289
+ fgs_params.grid_position_to_motor_position(
290
+ np.array(xrc_result["bounding_box"][1])
291
+ ),
292
+ ),
293
+ max_count=xrc_result["max_count"],
294
+ total_count=xrc_result["total_count"],
295
+ )
296
+
297
+
298
+ @bpp.set_run_key_decorator(CONST.PLAN.FLYSCAN_RESULTS)
299
+ def _fire_xray_centre_result_event(results: Sequence[XRayCentreResult]):
300
+ def empty_plan():
301
+ return iter([])
302
+
303
+ yield from bpp.run_wrapper(
304
+ empty_plan(),
305
+ md={"xray_centre_results": [dataclasses.asdict(r) for r in results]},
306
+ )
307
+
308
+
245
309
  @bpp.set_run_key_decorator(CONST.PLAN.GRIDSCAN_MAIN)
246
310
  @bpp.run_decorator(md={"subplan_name": CONST.PLAN.GRIDSCAN_MAIN})
247
311
  def run_gridscan(
248
312
  fgs_composite: FlyScanXRayCentreComposite,
249
- parameters: ThreeDGridScan,
313
+ parameters: HyperionThreeDGridScan,
250
314
  feature_controlled: _FeatureControlled,
251
315
  md={ # noqa
252
316
  "plan_name": CONST.PLAN.GRIDSCAN_MAIN,
@@ -266,7 +330,7 @@ def run_gridscan(
266
330
  fgs_composite.undulator,
267
331
  fgs_composite.synchrotron,
268
332
  fgs_composite.s4_slit_gaps,
269
- fgs_composite.robot,
333
+ fgs_composite.dcm,
270
334
  fgs_composite.smargon,
271
335
  )
272
336
 
@@ -287,7 +351,7 @@ def run_gridscan(
287
351
 
288
352
  LOGGER.info("Waiting for arming to finish")
289
353
  yield from bps.wait(CONST.WAIT.GRID_READY_FOR_DC)
290
- yield from bps.stage(fgs_composite.eiger)
354
+ yield from bps.stage(fgs_composite.eiger) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
291
355
 
292
356
  yield from kickoff_and_complete_gridscan(
293
357
  feature_controlled.fgs_motors,
@@ -295,66 +359,11 @@ def run_gridscan(
295
359
  fgs_composite.synchrotron,
296
360
  [parameters.scan_points_first_grid, parameters.scan_points_second_grid],
297
361
  parameters.scan_indices,
298
- do_during_run=read_during_collection,
362
+ plan_during_collection=read_during_collection,
299
363
  )
300
364
  yield from bps.abs_set(feature_controlled.fgs_motors.z_steps, 0, wait=False)
301
365
 
302
366
 
303
- def kickoff_and_complete_gridscan(
304
- gridscan: FastGridScanCommon,
305
- eiger: EigerDetector,
306
- synchrotron: Synchrotron,
307
- scan_points: list[AxesPoints[Axis]],
308
- scan_start_indices: list[int],
309
- do_during_run: Callable[[], MsgGenerator] | None = None,
310
- ):
311
- @TRACER.start_as_current_span(CONST.PLAN.DO_FGS)
312
- @bpp.set_run_key_decorator(CONST.PLAN.DO_FGS)
313
- @bpp.run_decorator(
314
- md={
315
- "subplan_name": CONST.PLAN.DO_FGS,
316
- "scan_points": scan_points,
317
- "scan_start_indices": scan_start_indices,
318
- }
319
- )
320
- @bpp.contingency_decorator(
321
- except_plan=lambda e: (yield from bps.stop(eiger)),
322
- else_plan=lambda: (yield from bps.unstage(eiger)),
323
- )
324
- def do_fgs():
325
- # Check topup gate
326
- expected_images = yield from bps.rd(gridscan.expected_images)
327
- exposure_sec_per_image = yield from bps.rd(eiger.cam.acquire_time)
328
- LOGGER.info("waiting for topup if necessary...")
329
- yield from check_topup_and_wait_if_necessary(
330
- synchrotron,
331
- expected_images * exposure_sec_per_image,
332
- 30.0,
333
- )
334
- yield from read_hardware_for_zocalo(eiger)
335
- LOGGER.info("Wait for all moves with no assigned group")
336
- yield from bps.wait()
337
- LOGGER.info("kicking off FGS")
338
- yield from bps.kickoff(gridscan, wait=True)
339
- gridscan_start_time = time()
340
- LOGGER.info("Waiting for Zocalo device queue to have been cleared...")
341
- yield from bps.wait(
342
- ZOCALO_STAGE_GROUP
343
- ) # Make sure ZocaloResults queue is clear and ready to accept our new data
344
- if do_during_run:
345
- LOGGER.info(f"Running {do_during_run} during FGS")
346
- yield from do_during_run()
347
- LOGGER.info("completing FGS")
348
- yield from bps.complete(gridscan, wait=True)
349
-
350
- # Remove this logging statement once metrics have been added
351
- LOGGER.info(
352
- f"Gridscan motion program took {round(time()-gridscan_start_time,2)} to complete"
353
- )
354
-
355
- yield from do_fgs()
356
-
357
-
358
367
  def wait_for_gridscan_valid(fgs_motors: FastGridScanCommon, timeout=0.5):
359
368
  LOGGER.info("Waiting for valid fgs_params")
360
369
  SLEEP_PER_CHECK = 0.1
@@ -372,31 +381,6 @@ def wait_for_gridscan_valid(fgs_motors: FastGridScanCommon, timeout=0.5):
372
381
  raise WarningException("Scan invalid - pin too long/short/bent and out of range")
373
382
 
374
383
 
375
- def set_aperture_for_bbox_size(
376
- aperture_device: ApertureScatterguard,
377
- bbox_size: list[int] | np.ndarray,
378
- ):
379
- # bbox_size is [x,y,z], for i03 we only care about x
380
- new_selected_aperture = (
381
- ApertureValue.MEDIUM if bbox_size[0] < 2 else ApertureValue.LARGE
382
- )
383
- LOGGER.info(
384
- f"Setting aperture to {new_selected_aperture} based on bounding box size {bbox_size}."
385
- )
386
-
387
- @bpp.set_run_key_decorator("change_aperture")
388
- @bpp.run_decorator(
389
- md={
390
- "subplan_name": "change_aperture",
391
- "aperture_size": new_selected_aperture.value,
392
- }
393
- )
394
- def set_aperture():
395
- yield from bps.abs_set(aperture_device, new_selected_aperture)
396
-
397
- yield from set_aperture()
398
-
399
-
400
384
  @dataclasses.dataclass
401
385
  class _FeatureControlled:
402
386
  class _ZebraSetup(Protocol):
@@ -408,7 +392,7 @@ class _FeatureControlled:
408
392
  def __call__(
409
393
  self,
410
394
  fgs_composite: FlyScanXRayCentreComposite,
411
- parameters: ThreeDGridScan,
395
+ parameters: HyperionThreeDGridScan,
412
396
  initial_xyz: np.ndarray,
413
397
  ) -> MsgGenerator: ...
414
398
 
@@ -420,7 +404,7 @@ class _FeatureControlled:
420
404
 
421
405
  def _get_feature_controlled(
422
406
  fgs_composite: FlyScanXRayCentreComposite,
423
- parameters: ThreeDGridScan,
407
+ parameters: HyperionThreeDGridScan,
424
408
  ):
425
409
  if parameters.features.use_panda_for_gridscan:
426
410
  return _FeatureControlled(
@@ -469,7 +453,7 @@ def _panda_tidy(fgs_composite: FlyScanXRayCentreComposite):
469
453
 
470
454
  def _zebra_triggering_setup(
471
455
  fgs_composite: FlyScanXRayCentreComposite,
472
- parameters: ThreeDGridScan,
456
+ parameters: HyperionThreeDGridScan,
473
457
  initial_xyz: np.ndarray,
474
458
  ):
475
459
  yield from setup_zebra_for_gridscan(
@@ -479,7 +463,7 @@ def _zebra_triggering_setup(
479
463
 
480
464
  def _panda_triggering_setup(
481
465
  fgs_composite: FlyScanXRayCentreComposite,
482
- parameters: ThreeDGridScan,
466
+ parameters: HyperionThreeDGridScan,
483
467
  initial_xyz: np.ndarray,
484
468
  ):
485
469
  LOGGER.info("Setting up Panda for flyscan")
@@ -498,15 +482,15 @@ def _panda_triggering_setup(
498
482
  )
499
483
 
500
484
  sample_velocity_mm_per_s = (
501
- parameters.panda_FGS_params.x_step_size * 1e3 / time_between_x_steps_ms
485
+ parameters.panda_FGS_params.x_step_size_mm * 1e3 / time_between_x_steps_ms
502
486
  )
503
487
  if sample_velocity_mm_per_s > smargon_speed_limit_mm_per_s:
504
488
  raise SmargonSpeedException(
505
489
  f"Smargon speed was calculated from x step size\
506
- {parameters.panda_FGS_params.x_step_size} and\
490
+ {parameters.panda_FGS_params.x_step_size_mm}mm and\
507
491
  time_between_x_steps_ms {time_between_x_steps_ms} as\
508
- {sample_velocity_mm_per_s}. The smargon's speed limit is\
509
- {smargon_speed_limit_mm_per_s} mm/s."
492
+ {sample_velocity_mm_per_s}mm/s. The smargon's speed limit is\
493
+ {smargon_speed_limit_mm_per_s}mm/s."
510
494
  )
511
495
  else:
512
496
  LOGGER.info(
@@ -515,8 +499,8 @@ def _panda_triggering_setup(
515
499
  )
516
500
 
517
501
  yield from bps.mv(
518
- fgs_composite.panda_fast_grid_scan.time_between_x_steps_ms,
519
- time_between_x_steps_ms,
502
+ fgs_composite.panda_fast_grid_scan.time_between_x_steps_ms, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
503
+ time_between_x_steps_ms, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
520
504
  )
521
505
 
522
506
  directory_provider_root = Path(parameters.storage_directory)
@@ -1,11 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
- import dataclasses
4
3
  from pathlib import Path
5
4
 
6
- from blueapi.core import BlueskyContext, MsgGenerator
5
+ import pydantic
6
+ from blueapi.core import BlueskyContext
7
7
  from bluesky import plan_stubs as bps
8
8
  from bluesky import preprocessors as bpp
9
+ from bluesky.preprocessors import subs_decorator
10
+ from bluesky.utils import MsgGenerator
9
11
  from dodal.devices.aperturescatterguard import ApertureScatterguard
10
12
  from dodal.devices.attenuator import Attenuator
11
13
  from dodal.devices.backlight import Backlight, BacklightPosition
@@ -15,7 +17,7 @@ from dodal.devices.eiger import EigerDetector
15
17
  from dodal.devices.fast_grid_scan import PandAFastGridScan, ZebraFastGridScan
16
18
  from dodal.devices.flux import Flux
17
19
  from dodal.devices.oav.oav_detector import OAV
18
- from dodal.devices.oav.oav_parameters import OAV_CONFIG_JSON, OAVParameters
20
+ from dodal.devices.oav.oav_parameters import OAVParameters
19
21
  from dodal.devices.oav.pin_image_recognition import PinTipDetection
20
22
  from dodal.devices.robot import BartRobot
21
23
  from dodal.devices.s4_slit_gaps import S4SlitGaps
@@ -28,17 +30,23 @@ from dodal.devices.zebra_controlled_shutter import ZebraShutter
28
30
  from dodal.devices.zocalo import ZocaloResults
29
31
  from ophyd_async.fastcs.panda import HDFPanda
30
32
 
33
+ from mx_bluesky.common.parameters.constants import OavConstants
34
+ from mx_bluesky.common.parameters.gridscan import GridScanWithEdgeDetect
31
35
  from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import (
32
36
  move_aperture_if_required,
33
37
  )
34
38
  from mx_bluesky.hyperion.device_setup_plans.utils import (
35
39
  start_preparing_data_collection_then_do_plan,
36
40
  )
41
+ from mx_bluesky.hyperion.experiment_plans.change_aperture_then_move_plan import (
42
+ change_aperture_then_move_to_xtal,
43
+ )
37
44
  from mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan import (
38
45
  FlyScanXRayCentreComposite as FlyScanXRayCentreComposite,
39
46
  )
40
47
  from mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan import (
41
- flyscan_xray_centre,
48
+ XRayCentreEventHandler,
49
+ flyscan_xray_centre_no_move,
42
50
  )
43
51
  from mx_bluesky.hyperion.experiment_plans.oav_grid_detection_plan import (
44
52
  OavGridDetectionComposite,
@@ -54,13 +62,12 @@ from mx_bluesky.hyperion.external_interaction.callbacks.xray_centre.ispyb_callba
54
62
  from mx_bluesky.hyperion.log import LOGGER
55
63
  from mx_bluesky.hyperion.parameters.constants import CONST
56
64
  from mx_bluesky.hyperion.parameters.gridscan import (
57
- GridScanWithEdgeDetect,
58
- ThreeDGridScan,
65
+ HyperionThreeDGridScan,
59
66
  )
60
67
  from mx_bluesky.hyperion.utils.context import device_composite_from_context
61
68
 
62
69
 
63
- @dataclasses.dataclass
70
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
64
71
  class GridDetectThenXRayCentreComposite:
65
72
  """All devices which are directly or indirectly required by this plan"""
66
73
 
@@ -86,6 +93,10 @@ class GridDetectThenXRayCentreComposite:
86
93
  robot: BartRobot
87
94
  sample_shutter: ZebraShutter
88
95
 
96
+ @property
97
+ def sample_motors(self):
98
+ return self.smargon
99
+
89
100
 
90
101
  def create_devices(context: BlueskyContext) -> GridDetectThenXRayCentreComposite:
91
102
  return device_composite_from_context(context, GridDetectThenXRayCentreComposite)
@@ -94,10 +105,10 @@ def create_devices(context: BlueskyContext) -> GridDetectThenXRayCentreComposite
94
105
  def create_parameters_for_flyscan_xray_centre(
95
106
  grid_scan_with_edge_params: GridScanWithEdgeDetect,
96
107
  grid_parameters: GridParamUpdate,
97
- ) -> ThreeDGridScan:
108
+ ) -> HyperionThreeDGridScan:
98
109
  params_json = grid_scan_with_edge_params.model_dump()
99
110
  params_json.update(grid_parameters)
100
- flyscan_xray_centre_parameters = ThreeDGridScan(**params_json)
111
+ flyscan_xray_centre_parameters = HyperionThreeDGridScan(**params_json)
101
112
  LOGGER.info(f"Parameters for FGS: {flyscan_xray_centre_parameters}")
102
113
  return flyscan_xray_centre_parameters
103
114
 
@@ -109,7 +120,7 @@ def detect_grid_and_do_gridscan(
109
120
  ):
110
121
  snapshot_template = f"{parameters.detector_params.prefix}_{parameters.detector_params.run_number}_{{angle}}"
111
122
 
112
- grid_params_callback = GridDetectionCallback(composite.oav.parameters)
123
+ grid_params_callback = GridDetectionCallback()
113
124
 
114
125
  @bpp.subs_decorator([grid_params_callback])
115
126
  def run_grid_detection_plan(
@@ -129,7 +140,16 @@ def detect_grid_and_do_gridscan(
129
140
  oav_params,
130
141
  snapshot_template,
131
142
  str(snapshot_dir),
132
- grid_width_microns=parameters.grid_width_um,
143
+ parameters.grid_width_um,
144
+ parameters.box_size_um,
145
+ )
146
+
147
+ if parameters.selected_aperture:
148
+ # Start moving the aperture/scatterguard into position without moving it in
149
+ yield from bps.abs_set(
150
+ composite.aperture_scatterguard.aperture_outside_beam,
151
+ parameters.selected_aperture,
152
+ group=CONST.WAIT.GRID_READY_FOR_DC,
133
153
  )
134
154
 
135
155
  yield from run_grid_detection_plan(
@@ -148,7 +168,7 @@ def detect_grid_and_do_gridscan(
148
168
  group=CONST.WAIT.GRID_READY_FOR_DC,
149
169
  )
150
170
 
151
- yield from flyscan_xray_centre(
171
+ yield from flyscan_xray_centre_no_move(
152
172
  FlyScanXRayCentreComposite(
153
173
  aperture_scatterguard=composite.aperture_scatterguard,
154
174
  attenuator=composite.attenuator,
@@ -178,7 +198,7 @@ def detect_grid_and_do_gridscan(
178
198
  def grid_detect_then_xray_centre(
179
199
  composite: GridDetectThenXRayCentreComposite,
180
200
  parameters: GridScanWithEdgeDetect,
181
- oav_config: str = OAV_CONFIG_JSON,
201
+ oav_config: str = OavConstants.OAV_CONFIG_JSON,
182
202
  ) -> MsgGenerator:
183
203
  """
184
204
  A plan which combines the collection of snapshots from the OAV and the determination
@@ -191,19 +211,33 @@ def grid_detect_then_xray_centre(
191
211
 
192
212
  oav_params = OAVParameters("xrayCentring", oav_config)
193
213
 
194
- plan_to_perform = ispyb_activation_wrapper(
195
- detect_grid_and_do_gridscan(
196
- composite,
214
+ flyscan_event_handler = XRayCentreEventHandler()
215
+
216
+ @subs_decorator(flyscan_event_handler)
217
+ def plan_to_perform():
218
+ yield from ispyb_activation_wrapper(
219
+ detect_grid_and_do_gridscan(
220
+ composite,
221
+ parameters,
222
+ oav_params,
223
+ ),
197
224
  parameters,
198
- oav_params,
199
- ),
200
- parameters,
201
- )
225
+ )
202
226
 
203
- return start_preparing_data_collection_then_do_plan(
227
+ yield from start_preparing_data_collection_then_do_plan(
204
228
  eiger,
205
229
  composite.detector_motion,
206
230
  parameters.detector_params.detector_distance,
207
- plan_to_perform,
231
+ plan_to_perform(),
208
232
  group=CONST.WAIT.GRID_READY_FOR_DC,
209
233
  )
234
+
235
+ assert (
236
+ flyscan_event_handler.xray_centre_results
237
+ ), "Flyscan result event not received or no crystal found and exception not raised"
238
+
239
+ yield from change_aperture_then_move_to_xtal(
240
+ flyscan_event_handler.xray_centre_results[0],
241
+ composite.smargon,
242
+ composite.aperture_scatterguard,
243
+ )