mx-bluesky 1.4.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 (63) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/beamlines/i04/thawing_plan.py +1 -1
  3. mx_bluesky/beamlines/i24/serial/dcid.py +19 -21
  4. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +2 -2
  5. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +1 -4
  6. mx_bluesky/beamlines/i24/serial/fixed_target/ft_utils.py +0 -1
  7. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +14 -24
  8. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +18 -76
  9. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +0 -199
  10. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +4 -6
  11. mx_bluesky/beamlines/i24/serial/log.py +1 -1
  12. mx_bluesky/beamlines/i24/serial/parameters/constants.py +0 -1
  13. mx_bluesky/beamlines/i24/serial/run_fixed_target.sh +4 -3
  14. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +78 -80
  15. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +1 -2
  16. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +24 -26
  17. mx_bluesky/beamlines/i24/serial/write_nexus.py +11 -11
  18. mx_bluesky/common/external_interaction/config_server.py +46 -0
  19. mx_bluesky/common/parameters/components.py +52 -15
  20. mx_bluesky/common/parameters/constants.py +6 -1
  21. mx_bluesky/common/parameters/gridscan.py +94 -0
  22. mx_bluesky/{hyperion → common}/parameters/robot_load.py +2 -2
  23. mx_bluesky/common/plans/do_fgs.py +2 -2
  24. mx_bluesky/common/utils/log.py +2 -0
  25. mx_bluesky/hyperion/__main__.py +2 -1
  26. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +4 -4
  27. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +1 -1
  28. mx_bluesky/hyperion/experiment_plans/__init__.py +4 -0
  29. mx_bluesky/hyperion/experiment_plans/change_aperture_then_move_plan.py +83 -0
  30. mx_bluesky/hyperion/experiment_plans/common/xrc_result.py +47 -0
  31. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +9 -9
  32. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +131 -89
  33. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +50 -18
  34. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +52 -10
  35. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +2 -2
  36. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +3 -9
  37. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +2 -2
  38. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +36 -17
  39. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +2 -2
  40. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +6 -10
  41. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +46 -11
  42. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +18 -3
  43. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +2 -3
  44. mx_bluesky/hyperion/external_interaction/callbacks/common/abstract_event.py +66 -0
  45. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +15 -15
  46. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +3 -1
  47. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +5 -3
  48. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +5 -2
  49. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +5 -4
  50. mx_bluesky/hyperion/external_interaction/config_server.py +8 -37
  51. mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +1 -1
  52. mx_bluesky/hyperion/parameters/components.py +4 -9
  53. mx_bluesky/hyperion/parameters/constants.py +0 -1
  54. mx_bluesky/hyperion/parameters/gridscan.py +33 -76
  55. mx_bluesky/hyperion/parameters/load_centre_collect.py +14 -9
  56. mx_bluesky/hyperion/parameters/rotation.py +15 -6
  57. {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/METADATA +35 -34
  58. {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/RECORD +62 -58
  59. {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/WHEEL +1 -1
  60. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +0 -150
  61. {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/LICENSE +0 -0
  62. {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/entry_points.txt +0 -0
  63. {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
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
7
  from typing import Protocol
@@ -9,10 +9,12 @@ from typing import Protocol
9
9
  import bluesky.plan_stubs as bps
10
10
  import bluesky.preprocessors as bpp
11
11
  import numpy as np
12
- 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
13
16
  from dodal.devices.aperturescatterguard import (
14
17
  ApertureScatterguard,
15
- ApertureValue,
16
18
  )
17
19
  from dodal.devices.attenuator import Attenuator
18
20
  from dodal.devices.backlight import Backlight
@@ -29,7 +31,7 @@ from dodal.devices.fast_grid_scan import (
29
31
  from dodal.devices.flux import Flux
30
32
  from dodal.devices.robot import BartRobot
31
33
  from dodal.devices.s4_slit_gaps import S4SlitGaps
32
- from dodal.devices.smargon import Smargon, StubPosition
34
+ from dodal.devices.smargon import Smargon
33
35
  from dodal.devices.synchrotron import Synchrotron
34
36
  from dodal.devices.undulator import Undulator
35
37
  from dodal.devices.xbpm_feedback import XBPMFeedback
@@ -38,14 +40,15 @@ from dodal.devices.zebra_controlled_shutter import ZebraShutter
38
40
  from dodal.devices.zocalo.zocalo_results import (
39
41
  ZOCALO_READING_PLAN_NAME,
40
42
  ZOCALO_STAGE_GROUP,
43
+ XrcResult,
41
44
  ZocaloResults,
42
45
  get_full_processing_results,
43
46
  )
47
+ from event_model import RunStart
44
48
  from ophyd_async.fastcs.panda import HDFPanda
45
49
 
46
50
  from mx_bluesky.common.plans.do_fgs import kickoff_and_complete_gridscan
47
51
  from mx_bluesky.common.utils.tracing import TRACER
48
- from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import move_x_y_z
49
52
  from mx_bluesky.hyperion.device_setup_plans.read_hardware_for_setup import (
50
53
  read_hardware_during_collection,
51
54
  read_hardware_pre_collection,
@@ -64,9 +67,16 @@ from mx_bluesky.hyperion.device_setup_plans.xbpm_feedback import (
64
67
  transmission_and_xbpm_feedback_for_collection_decorator,
65
68
  )
66
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
+ )
67
77
  from mx_bluesky.hyperion.log import LOGGER
68
78
  from mx_bluesky.hyperion.parameters.constants import CONST
69
- from mx_bluesky.hyperion.parameters.gridscan import ThreeDGridScan
79
+ from mx_bluesky.hyperion.parameters.gridscan import HyperionThreeDGridScan
70
80
  from mx_bluesky.hyperion.utils.context import device_composite_from_context
71
81
 
72
82
 
@@ -80,7 +90,7 @@ class CrystalNotFoundException(WarningException):
80
90
  pass
81
91
 
82
92
 
83
- @dataclasses.dataclass
93
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
84
94
  class FlyScanXRayCentreComposite:
85
95
  """All devices which are directly or indirectly required by this plan"""
86
96
 
@@ -109,26 +119,29 @@ class FlyScanXRayCentreComposite:
109
119
  return self.smargon
110
120
 
111
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
+
112
136
  def create_devices(context: BlueskyContext) -> FlyScanXRayCentreComposite:
113
137
  """Creates the devices required for the plan and connect to them"""
114
138
  return device_composite_from_context(context, FlyScanXRayCentreComposite)
115
139
 
116
140
 
117
- def flyscan_xray_centre(
118
- composite: FlyScanXRayCentreComposite,
119
- parameters: ThreeDGridScan,
141
+ def flyscan_xray_centre_no_move(
142
+ composite: FlyScanXRayCentreComposite, parameters: HyperionThreeDGridScan
120
143
  ) -> MsgGenerator:
121
- """Create the plan to run the grid scan based on provided parameters.
122
-
123
- The ispyb handler should be added to the whole gridscan as we want to capture errors
124
- at any point in it.
125
-
126
- Args:
127
- parameters (ThreeDGridScan): The parameters to run the scan.
128
-
129
- Returns:
130
- Generator: The plan for the gridscan
131
- """
144
+ """Perform a flyscan and determine the centres of interest"""
132
145
  parameters.features.update_self_from_server()
133
146
  composite.eiger.set_detector_parameters(parameters.detector_params)
134
147
  composite.zocalo.zocalo_environment = CONST.ZOCALO_ENV
@@ -152,25 +165,66 @@ def flyscan_xray_centre(
152
165
  composite.attenuator,
153
166
  parameters.transmission_frac,
154
167
  )
155
- def run_gridscan_and_move_and_tidy(
168
+ def run_gridscan_and_fetch_and_tidy(
156
169
  fgs_composite: FlyScanXRayCentreComposite,
157
- params: ThreeDGridScan,
170
+ params: HyperionThreeDGridScan,
158
171
  feature_controlled: _FeatureControlled,
159
- ):
160
- 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.
161
190
 
162
- return run_gridscan_and_move_and_tidy(composite, parameters, feature_controlled)
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
+ )
204
+
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
+ )
163
217
 
164
218
 
165
219
  @bpp.set_run_key_decorator(CONST.PLAN.GRIDSCAN_AND_MOVE)
166
220
  @bpp.run_decorator(md={"subplan_name": CONST.PLAN.GRIDSCAN_AND_MOVE})
167
- def run_gridscan_and_move(
221
+ def run_gridscan_and_fetch_results(
168
222
  fgs_composite: FlyScanXRayCentreComposite,
169
- parameters: ThreeDGridScan,
223
+ parameters: HyperionThreeDGridScan,
170
224
  feature_controlled: _FeatureControlled,
171
225
  ) -> MsgGenerator:
172
226
  """A multi-run plan which runs a gridscan, gets the results from zocalo
173
- and moves to the centre of mass determined by zocalo"""
227
+ and fires an event with the centres of mass determined by zocalo"""
174
228
 
175
229
  # We get the initial motor positions so we can return to them on zocalo failure
176
230
  initial_xyz = np.array(
@@ -198,37 +252,17 @@ def run_gridscan_and_move(
198
252
  )
199
253
  LOGGER.info("Zocalo triggered and read, interpreting results.")
200
254
  xrc_results = yield from get_full_processing_results(fgs_composite.zocalo)
201
- LOGGER.info(f"Got xray centring results: {xrc_results}")
255
+ LOGGER.info(f"Got xray centres, top 5: {xrc_results[:5]}")
202
256
  if xrc_results:
203
- best_result = xrc_results[0]
204
- xrc_centre_grid_coords = best_result["centre_of_mass"]
205
- xray_centre = parameters.FGS_params.grid_position_to_motor_position(
206
- np.array(xrc_centre_grid_coords)
207
- )
208
- with TRACER.start_span("change_aperture"):
209
- bbox_size = np.abs(
210
- np.array(best_result["bounding_box"][1])
211
- - np.array(best_result["bounding_box"][0])
212
- )
213
- yield from set_aperture_for_bbox_size(
214
- fgs_composite.aperture_scatterguard, bbox_size
215
- )
257
+ flyscan_results = [
258
+ _xrc_result_in_boxes_to_result_in_mm(xr, parameters)
259
+ for xr in xrc_results
260
+ ]
216
261
  else:
217
262
  LOGGER.warning("No X-ray centre received")
218
263
  raise CrystalNotFoundException()
264
+ yield from _fire_xray_centre_result_event(flyscan_results)
219
265
 
220
- # once we have the results, go to the appropriate position
221
- LOGGER.info("Moving to centre of mass.")
222
- with TRACER.start_span("move_to_result"):
223
- x, y, z = xray_centre
224
- yield from move_x_y_z(fgs_composite.sample_motors, x, y, z, wait=True)
225
-
226
- if parameters.FGS_params.set_stub_offsets:
227
- LOGGER.info("Recentring smargon co-ordinate system to this point.")
228
- yield from bps.mv(
229
- fgs_composite.sample_motors.stub_offsets, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
230
- StubPosition.CURRENT_AS_CENTER, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
231
- )
232
266
  finally:
233
267
  # Turn off dev/shm streaming to avoid filling disk, see https://github.com/DiamondLightSource/hyperion/issues/1395
234
268
  LOGGER.info("Turning off Eiger dev/shm streaming")
@@ -239,11 +273,44 @@ def run_gridscan_and_move(
239
273
  yield from bps.wait()
240
274
 
241
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
+
242
309
  @bpp.set_run_key_decorator(CONST.PLAN.GRIDSCAN_MAIN)
243
310
  @bpp.run_decorator(md={"subplan_name": CONST.PLAN.GRIDSCAN_MAIN})
244
311
  def run_gridscan(
245
312
  fgs_composite: FlyScanXRayCentreComposite,
246
- parameters: ThreeDGridScan,
313
+ parameters: HyperionThreeDGridScan,
247
314
  feature_controlled: _FeatureControlled,
248
315
  md={ # noqa
249
316
  "plan_name": CONST.PLAN.GRIDSCAN_MAIN,
@@ -314,31 +381,6 @@ def wait_for_gridscan_valid(fgs_motors: FastGridScanCommon, timeout=0.5):
314
381
  raise WarningException("Scan invalid - pin too long/short/bent and out of range")
315
382
 
316
383
 
317
- def set_aperture_for_bbox_size(
318
- aperture_device: ApertureScatterguard,
319
- bbox_size: list[int] | np.ndarray,
320
- ):
321
- # bbox_size is [x,y,z], for i03 we only care about x
322
- new_selected_aperture = (
323
- ApertureValue.MEDIUM if bbox_size[0] < 2 else ApertureValue.LARGE
324
- )
325
- LOGGER.info(
326
- f"Setting aperture to {new_selected_aperture} based on bounding box size {bbox_size}."
327
- )
328
-
329
- @bpp.set_run_key_decorator("change_aperture")
330
- @bpp.run_decorator(
331
- md={
332
- "subplan_name": "change_aperture",
333
- "aperture_size": new_selected_aperture.value,
334
- }
335
- )
336
- def set_aperture():
337
- yield from bps.abs_set(aperture_device, new_selected_aperture)
338
-
339
- yield from set_aperture()
340
-
341
-
342
384
  @dataclasses.dataclass
343
385
  class _FeatureControlled:
344
386
  class _ZebraSetup(Protocol):
@@ -350,7 +392,7 @@ class _FeatureControlled:
350
392
  def __call__(
351
393
  self,
352
394
  fgs_composite: FlyScanXRayCentreComposite,
353
- parameters: ThreeDGridScan,
395
+ parameters: HyperionThreeDGridScan,
354
396
  initial_xyz: np.ndarray,
355
397
  ) -> MsgGenerator: ...
356
398
 
@@ -362,7 +404,7 @@ class _FeatureControlled:
362
404
 
363
405
  def _get_feature_controlled(
364
406
  fgs_composite: FlyScanXRayCentreComposite,
365
- parameters: ThreeDGridScan,
407
+ parameters: HyperionThreeDGridScan,
366
408
  ):
367
409
  if parameters.features.use_panda_for_gridscan:
368
410
  return _FeatureControlled(
@@ -411,7 +453,7 @@ def _panda_tidy(fgs_composite: FlyScanXRayCentreComposite):
411
453
 
412
454
  def _zebra_triggering_setup(
413
455
  fgs_composite: FlyScanXRayCentreComposite,
414
- parameters: ThreeDGridScan,
456
+ parameters: HyperionThreeDGridScan,
415
457
  initial_xyz: np.ndarray,
416
458
  ):
417
459
  yield from setup_zebra_for_gridscan(
@@ -421,7 +463,7 @@ def _zebra_triggering_setup(
421
463
 
422
464
  def _panda_triggering_setup(
423
465
  fgs_composite: FlyScanXRayCentreComposite,
424
- parameters: ThreeDGridScan,
466
+ parameters: HyperionThreeDGridScan,
425
467
  initial_xyz: np.ndarray,
426
468
  ):
427
469
  LOGGER.info("Setting up Panda for flyscan")
@@ -440,15 +482,15 @@ def _panda_triggering_setup(
440
482
  )
441
483
 
442
484
  sample_velocity_mm_per_s = (
443
- 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
444
486
  )
445
487
  if sample_velocity_mm_per_s > smargon_speed_limit_mm_per_s:
446
488
  raise SmargonSpeedException(
447
489
  f"Smargon speed was calculated from x step size\
448
- {parameters.panda_FGS_params.x_step_size} and\
490
+ {parameters.panda_FGS_params.x_step_size_mm}mm and\
449
491
  time_between_x_steps_ms {time_between_x_steps_ms} as\
450
- {sample_velocity_mm_per_s}. The smargon's speed limit is\
451
- {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."
452
494
  )
453
495
  else:
454
496
  LOGGER.info(
@@ -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
@@ -29,17 +31,22 @@ from dodal.devices.zocalo import ZocaloResults
29
31
  from ophyd_async.fastcs.panda import HDFPanda
30
32
 
31
33
  from mx_bluesky.common.parameters.constants import OavConstants
34
+ from mx_bluesky.common.parameters.gridscan import GridScanWithEdgeDetect
32
35
  from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import (
33
36
  move_aperture_if_required,
34
37
  )
35
38
  from mx_bluesky.hyperion.device_setup_plans.utils import (
36
39
  start_preparing_data_collection_then_do_plan,
37
40
  )
41
+ from mx_bluesky.hyperion.experiment_plans.change_aperture_then_move_plan import (
42
+ change_aperture_then_move_to_xtal,
43
+ )
38
44
  from mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan import (
39
45
  FlyScanXRayCentreComposite as FlyScanXRayCentreComposite,
40
46
  )
41
47
  from mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan import (
42
- flyscan_xray_centre,
48
+ XRayCentreEventHandler,
49
+ flyscan_xray_centre_no_move,
43
50
  )
44
51
  from mx_bluesky.hyperion.experiment_plans.oav_grid_detection_plan import (
45
52
  OavGridDetectionComposite,
@@ -55,13 +62,12 @@ from mx_bluesky.hyperion.external_interaction.callbacks.xray_centre.ispyb_callba
55
62
  from mx_bluesky.hyperion.log import LOGGER
56
63
  from mx_bluesky.hyperion.parameters.constants import CONST
57
64
  from mx_bluesky.hyperion.parameters.gridscan import (
58
- GridScanWithEdgeDetect,
59
- ThreeDGridScan,
65
+ HyperionThreeDGridScan,
60
66
  )
61
67
  from mx_bluesky.hyperion.utils.context import device_composite_from_context
62
68
 
63
69
 
64
- @dataclasses.dataclass
70
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
65
71
  class GridDetectThenXRayCentreComposite:
66
72
  """All devices which are directly or indirectly required by this plan"""
67
73
 
@@ -87,6 +93,10 @@ class GridDetectThenXRayCentreComposite:
87
93
  robot: BartRobot
88
94
  sample_shutter: ZebraShutter
89
95
 
96
+ @property
97
+ def sample_motors(self):
98
+ return self.smargon
99
+
90
100
 
91
101
  def create_devices(context: BlueskyContext) -> GridDetectThenXRayCentreComposite:
92
102
  return device_composite_from_context(context, GridDetectThenXRayCentreComposite)
@@ -95,10 +105,10 @@ def create_devices(context: BlueskyContext) -> GridDetectThenXRayCentreComposite
95
105
  def create_parameters_for_flyscan_xray_centre(
96
106
  grid_scan_with_edge_params: GridScanWithEdgeDetect,
97
107
  grid_parameters: GridParamUpdate,
98
- ) -> ThreeDGridScan:
108
+ ) -> HyperionThreeDGridScan:
99
109
  params_json = grid_scan_with_edge_params.model_dump()
100
110
  params_json.update(grid_parameters)
101
- flyscan_xray_centre_parameters = ThreeDGridScan(**params_json)
111
+ flyscan_xray_centre_parameters = HyperionThreeDGridScan(**params_json)
102
112
  LOGGER.info(f"Parameters for FGS: {flyscan_xray_centre_parameters}")
103
113
  return flyscan_xray_centre_parameters
104
114
 
@@ -134,6 +144,14 @@ def detect_grid_and_do_gridscan(
134
144
  parameters.box_size_um,
135
145
  )
136
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,
153
+ )
154
+
137
155
  yield from run_grid_detection_plan(
138
156
  oav_params,
139
157
  snapshot_template,
@@ -150,7 +168,7 @@ def detect_grid_and_do_gridscan(
150
168
  group=CONST.WAIT.GRID_READY_FOR_DC,
151
169
  )
152
170
 
153
- yield from flyscan_xray_centre(
171
+ yield from flyscan_xray_centre_no_move(
154
172
  FlyScanXRayCentreComposite(
155
173
  aperture_scatterguard=composite.aperture_scatterguard,
156
174
  attenuator=composite.attenuator,
@@ -193,19 +211,33 @@ def grid_detect_then_xray_centre(
193
211
 
194
212
  oav_params = OAVParameters("xrayCentring", oav_config)
195
213
 
196
- plan_to_perform = ispyb_activation_wrapper(
197
- detect_grid_and_do_gridscan(
198
- 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
+ ),
199
224
  parameters,
200
- oav_params,
201
- ),
202
- parameters,
203
- )
225
+ )
204
226
 
205
- return start_preparing_data_collection_then_do_plan(
227
+ yield from start_preparing_data_collection_then_do_plan(
206
228
  eiger,
207
229
  composite.detector_motion,
208
230
  parameters.detector_params.detector_distance,
209
- plan_to_perform, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809 and MsgGenerator should allow for return values
231
+ plan_to_perform(),
210
232
  group=CONST.WAIT.GRID_READY_FOR_DC,
211
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
+ )
@@ -1,25 +1,36 @@
1
- import dataclasses
1
+ from collections.abc import Sequence
2
2
 
3
- from blueapi.core import BlueskyContext
3
+ import pydantic
4
+ from blueapi.core import BlueskyContext, MsgGenerator
5
+ from bluesky.preprocessors import subs_wrapper
4
6
  from dodal.devices.oav.oav_parameters import OAVParameters
7
+ from dodal.devices.smargon import Smargon
5
8
 
9
+ import mx_bluesky.hyperion.experiment_plans.common.xrc_result as flyscan_result
10
+ from mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan import (
11
+ XRayCentreEventHandler,
12
+ )
6
13
  from mx_bluesky.hyperion.experiment_plans.robot_load_then_centre_plan import (
7
14
  RobotLoadThenCentreComposite,
8
- robot_load_then_centre,
15
+ robot_load_then_xray_centre,
9
16
  )
10
17
  from mx_bluesky.hyperion.experiment_plans.rotation_scan_plan import (
18
+ MultiRotationScan,
11
19
  RotationScanComposite,
12
20
  multi_rotation_scan,
13
21
  )
22
+ from mx_bluesky.hyperion.log import LOGGER
14
23
  from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
15
24
  from mx_bluesky.hyperion.utils.context import device_composite_from_context
16
25
 
17
26
 
18
- @dataclasses.dataclass
27
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
19
28
  class LoadCentreCollectComposite(RobotLoadThenCentreComposite, RotationScanComposite):
20
29
  """Composite that provides access to the required devices."""
21
30
 
22
- pass
31
+ @property
32
+ def sample_motors(self) -> Smargon:
33
+ return self.smargon
23
34
 
24
35
 
25
36
  def create_devices(context: BlueskyContext) -> LoadCentreCollectComposite:
@@ -27,11 +38,11 @@ def create_devices(context: BlueskyContext) -> LoadCentreCollectComposite:
27
38
  return device_composite_from_context(context, LoadCentreCollectComposite)
28
39
 
29
40
 
30
- def load_centre_collect_full_plan(
41
+ def load_centre_collect_full(
31
42
  composite: LoadCentreCollectComposite,
32
- params: LoadCentreCollect,
43
+ parameters: LoadCentreCollect,
33
44
  oav_params: OAVParameters | None = None,
34
- ):
45
+ ) -> MsgGenerator:
35
46
  """Attempt a complete data collection experiment, consisting of the following:
36
47
  * Load the sample if necessary
37
48
  * Move to the specified goniometer start angles
@@ -41,6 +52,37 @@ def load_centre_collect_full_plan(
41
52
  """
42
53
  if not oav_params:
43
54
  oav_params = OAVParameters(context="xrayCentring")
44
- yield from robot_load_then_centre(composite, params.robot_load_then_centre)
45
55
 
46
- yield from multi_rotation_scan(composite, params.multi_rotation_scan, oav_params)
56
+ flyscan_event_handler = XRayCentreEventHandler()
57
+ yield from subs_wrapper(
58
+ robot_load_then_xray_centre(composite, parameters.robot_load_then_centre),
59
+ flyscan_event_handler,
60
+ )
61
+
62
+ assert (
63
+ flyscan_event_handler.xray_centre_results
64
+ ), "Flyscan result event not received or no crystal found and exception not raised"
65
+
66
+ selection_func = flyscan_result.resolve_selection_fn(parameters.selection_params)
67
+ hits: Sequence[flyscan_result.XRayCentreResult] = selection_func(
68
+ flyscan_event_handler.xray_centre_results
69
+ )
70
+ LOGGER.info(
71
+ f"Selected hits {hits} using {selection_func}, args={parameters.selection_params}"
72
+ )
73
+
74
+ multi_rotation = parameters.multi_rotation_scan
75
+ rotation_template = multi_rotation.rotation_scans.copy()
76
+
77
+ multi_rotation.rotation_scans.clear()
78
+
79
+ for hit in hits:
80
+ for rot in rotation_template:
81
+ combination = rot.model_copy()
82
+ combination.x_start_um, combination.y_start_um, combination.z_start_um = (
83
+ axis * 1000 for axis in hit.centre_of_mass_mm
84
+ )
85
+ multi_rotation.rotation_scans.append(combination)
86
+ multi_rotation = MultiRotationScan.model_validate(multi_rotation)
87
+
88
+ yield from multi_rotation_scan(composite, multi_rotation, oav_params)
@@ -1,11 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
- import dataclasses
4
3
  import math
5
4
  from typing import TYPE_CHECKING
6
5
 
7
6
  import bluesky.plan_stubs as bps
8
7
  import numpy as np
8
+ import pydantic
9
9
  from blueapi.core import BlueskyContext
10
10
  from dodal.devices.backlight import Backlight
11
11
  from dodal.devices.oav.oav_detector import OAV
@@ -26,7 +26,7 @@ if TYPE_CHECKING:
26
26
  from dodal.devices.oav.oav_parameters import OAVParameters
27
27
 
28
28
 
29
- @dataclasses.dataclass
29
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
30
30
  class OavGridDetectionComposite:
31
31
  """All devices which are directly or indirectly required by this plan"""
32
32
 
@@ -1,9 +1,9 @@
1
1
  from datetime import datetime
2
2
  from typing import Protocol
3
3
 
4
- from blueapi.core import MsgGenerator
5
4
  from bluesky import plan_stubs as bps
6
- from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue
5
+ from bluesky.utils import MsgGenerator
6
+ from dodal.devices.aperturescatterguard import ApertureScatterguard
7
7
  from dodal.devices.backlight import Backlight, BacklightPosition
8
8
  from dodal.devices.oav.oav_detector import OAV
9
9
  from dodal.devices.oav.oav_parameters import OAVParameters
@@ -33,22 +33,16 @@ def setup_beamline_for_OAV(
33
33
  max_vel = yield from bps.rd(smargon.omega.max_velocity)
34
34
  yield from bps.abs_set(smargon.omega.velocity, max_vel, group=group)
35
35
  yield from bps.abs_set(backlight, BacklightPosition.IN, group=group)
36
- yield from bps.abs_set(
37
- aperture_scatterguard,
38
- ApertureValue.ROBOT_LOAD,
39
- group=group,
40
- )
36
+ yield from bps.trigger(aperture_scatterguard.move_out, group=group)
41
37
 
42
38
 
43
39
  def oav_snapshot_plan(
44
40
  composite: OavSnapshotComposite,
45
41
  parameters: WithSnapshot,
46
42
  oav_parameters: OAVParameters,
47
- wait: bool = True,
48
43
  ) -> MsgGenerator:
49
44
  if not parameters.take_snapshots:
50
45
  return
51
- yield from bps.wait(group=CONST.WAIT.READY_FOR_OAV)
52
46
  yield from _setup_oav(composite, parameters, oav_parameters)
53
47
  for omega in parameters.snapshot_omegas_deg or []:
54
48
  yield from _take_oav_snapshot(composite, omega)
@@ -1,9 +1,9 @@
1
- import dataclasses
2
1
  from enum import Enum
3
2
 
4
3
  import bluesky.plan_stubs as bps
5
4
  import bluesky.preprocessors as bpp
6
5
  import numpy as np
6
+ import pydantic
7
7
  from blueapi.core import BlueskyContext
8
8
  from dodal.devices.attenuator import Attenuator
9
9
  from dodal.devices.xspress3.xspress3 import Xspress3
@@ -22,7 +22,7 @@ class Direction(Enum):
22
22
  NEGATIVE = "negative"
23
23
 
24
24
 
25
- @dataclasses.dataclass
25
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
26
26
  class OptimizeAttenuationComposite:
27
27
  """All devices which are directly or indirectly required by this plan"""
28
28