mx-bluesky 1.4.4__py3-none-any.whl → 1.4.6__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 (74) hide show
  1. mx_bluesky/_version.py +9 -4
  2. mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +14 -3
  3. mx_bluesky/beamlines/i04/thawing_plan.py +9 -13
  4. mx_bluesky/beamlines/i24/serial/__init__.py +14 -0
  5. mx_bluesky/beamlines/i24/serial/dcid.py +3 -1
  6. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +1 -1
  7. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +6 -3
  8. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +11 -11
  9. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +3 -3
  10. mx_bluesky/beamlines/i24/serial/log.py +0 -1
  11. mx_bluesky/beamlines/i24/serial/parameters/constants.py +1 -1
  12. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +3 -3
  13. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +1 -1
  14. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +2 -2
  15. mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +109 -0
  16. mx_bluesky/beamlines/i24/serial/write_nexus.py +2 -2
  17. mx_bluesky/common/device_setup_plans/setup_panda.py +9 -0
  18. mx_bluesky/common/external_interaction/callbacks/common/plan_reactive_callback.py +2 -2
  19. mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py +11 -3
  20. mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_mapping.py +1 -1
  21. mx_bluesky/common/external_interaction/ispyb/exp_eye_store.py +6 -2
  22. mx_bluesky/common/external_interaction/ispyb/ispyb_store.py +7 -0
  23. mx_bluesky/common/parameters/constants.py +16 -0
  24. mx_bluesky/common/parameters/gridscan.py +36 -1
  25. mx_bluesky/common/plans/do_fgs.py +4 -6
  26. mx_bluesky/common/plans/read_hardware.py +78 -0
  27. mx_bluesky/common/utils/context.py +68 -0
  28. mx_bluesky/common/utils/exceptions.py +2 -1
  29. mx_bluesky/{hyperion/experiment_plans/common → common}/xrc_result.py +16 -0
  30. mx_bluesky/definitions.py +4 -0
  31. mx_bluesky/hyperion/__main__.py +11 -42
  32. mx_bluesky/hyperion/device_setup_plans/setup_oav.py +5 -5
  33. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +9 -8
  34. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +2 -2
  35. mx_bluesky/hyperion/device_setup_plans/smargon.py +6 -6
  36. mx_bluesky/hyperion/device_setup_plans/utils.py +2 -2
  37. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +14 -4
  38. mx_bluesky/hyperion/experiment_plans/change_aperture_then_move_plan.py +2 -6
  39. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +0 -15
  40. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +42 -93
  41. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +14 -6
  42. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +26 -21
  43. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +11 -11
  44. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +5 -9
  45. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +1 -1
  46. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +2 -4
  47. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +10 -10
  48. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +11 -18
  49. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +2 -4
  50. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +19 -10
  51. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +2 -0
  52. mx_bluesky/hyperion/external_interaction/agamemnon.py +104 -0
  53. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +19 -2
  54. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +1 -1
  55. mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/sample_handling_callback.py +14 -9
  56. mx_bluesky/hyperion/external_interaction/config_server.py +13 -2
  57. mx_bluesky/hyperion/parameters/cli.py +1 -9
  58. mx_bluesky/hyperion/parameters/constants.py +6 -1
  59. mx_bluesky/hyperion/parameters/device_composites.py +49 -0
  60. mx_bluesky/hyperion/parameters/gridscan.py +5 -3
  61. mx_bluesky/hyperion/resources/panda/panda-gridscan.yaml +1006 -964
  62. mx_bluesky/hyperion/utils/__init__.py +1 -0
  63. mx_bluesky/hyperion/utils/context.py +0 -65
  64. mx_bluesky/hyperion/utils/validation.py +23 -20
  65. {mx_bluesky-1.4.4.dist-info → mx_bluesky-1.4.6.dist-info}/METADATA +5 -4
  66. {mx_bluesky-1.4.4.dist-info → mx_bluesky-1.4.6.dist-info}/RECORD +71 -66
  67. {mx_bluesky-1.4.4.dist-info → mx_bluesky-1.4.6.dist-info}/WHEEL +1 -1
  68. {mx_bluesky-1.4.4.dist-info → mx_bluesky-1.4.6.dist-info}/entry_points.txt +1 -0
  69. mx_bluesky/common/device_setup_plans/read_hardware_for_setup.py +0 -14
  70. mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +0 -54
  71. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +0 -95
  72. /mx_bluesky/{hyperion/external_interaction/callbacks/common → beamlines/i24/serial/web_gui_plans}/__init__.py +0 -0
  73. {mx_bluesky-1.4.4.dist-info → mx_bluesky-1.4.6.dist-info}/LICENSE +0 -0
  74. {mx_bluesky-1.4.4.dist-info → mx_bluesky-1.4.6.dist-info}/top_level.txt +0 -0
@@ -15,6 +15,7 @@ from dodal.devices.oav.utils import (
15
15
  )
16
16
  from dodal.devices.smargon import Smargon
17
17
 
18
+ from mx_bluesky.common.utils.context import device_composite_from_context
18
19
  from mx_bluesky.common.utils.exceptions import SampleException
19
20
  from mx_bluesky.common.utils.log import LOGGER
20
21
  from mx_bluesky.hyperion.device_setup_plans.setup_oav import pre_centring_setup_oav
@@ -22,7 +23,6 @@ from mx_bluesky.hyperion.device_setup_plans.smargon import (
22
23
  move_smargon_warn_on_out_of_range,
23
24
  )
24
25
  from mx_bluesky.hyperion.parameters.constants import CONST
25
- from mx_bluesky.hyperion.utils.context import device_composite_from_context
26
26
 
27
27
  DEFAULT_STEP_SIZE = 0.5
28
28
 
@@ -53,7 +53,7 @@ def trigger_and_return_pin_tip(
53
53
  def move_pin_into_view(
54
54
  pin_tip_device: PinTipDetection,
55
55
  smargon: Smargon,
56
- step_size_mm: float = DEFAULT_STEP_SIZE,
56
+ step_magnitude_mm: float = DEFAULT_STEP_SIZE,
57
57
  max_steps: int = 2,
58
58
  ) -> Generator[Msg, None, Pixel]:
59
59
  """Attempt to move the pin into view and return the tip location in pixels if found.
@@ -63,7 +63,7 @@ def move_pin_into_view(
63
63
  Args:
64
64
  pin_tip_device (PinTipDetection): The device being used to detect the pin
65
65
  smargon (Smargon): The gonio to move the tip
66
- step_size (float, optional): Distance to move the gonio (in mm) for each
66
+ step_magnitude_mm (float, optional): Distance to move the gonio (in mm) for each
67
67
  step of the search. Defaults to 0.5.
68
68
  max_steps (int, optional): The number of steps to search with. Defaults to 2.
69
69
 
@@ -83,21 +83,21 @@ def move_pin_into_view(
83
83
  if pin_tip_valid(tip_xy_px):
84
84
  return (int(tip_xy_px[0]), int(tip_xy_px[1]))
85
85
 
86
- if tip_xy_px[0] == 0:
87
- # Pin is off in the -ve direction
88
- step_size_mm = -step_size_mm
86
+ # Pin is off in the -ve direction if the returned tip x pixel value is 0
87
+ direction_multiple = -1 if tip_xy_px[0] == 0 else 1
88
+ step_vector_mm = step_magnitude_mm * direction_multiple
89
89
 
90
90
  smargon_x = yield from bps.rd(smargon.x.user_readback)
91
- ideal_move_to_find_pin = float(smargon_x) + step_size_mm
91
+ ideal_move_to_find_pin = float(smargon_x) + step_vector_mm
92
92
  high_limit = yield from bps.rd(smargon.x.high_limit_travel)
93
93
  low_limit = yield from bps.rd(smargon.x.low_limit_travel)
94
94
  move_within_limits = max(min(ideal_move_to_find_pin, high_limit), low_limit)
95
95
  if move_within_limits != ideal_move_to_find_pin:
96
96
  LOGGER.warning(
97
- f"Pin tip is off screen, and moving {step_size_mm} mm would cross limits, "
97
+ f"Pin tip is off screen, and moving {step_vector_mm}mm would cross limits, "
98
98
  f"moving to {move_within_limits} instead"
99
99
  )
100
- yield from bps.mv(smargon.x, move_within_limits) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
100
+ yield from bps.mv(smargon.x, move_within_limits)
101
101
 
102
102
  # Some time for the view to settle after the move
103
103
  yield from bps.sleep(CONST.HARDWARE.OAV_REFRESH_DELAY)
@@ -154,7 +154,7 @@ def pin_tip_centre_plan(
154
154
  tip = yield from move_pin_into_view(pin_tip_detect, smargon)
155
155
  yield from offset_and_move(tip)
156
156
 
157
- yield from bps.mvr(smargon.omega, 90) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
157
+ yield from bps.mvr(smargon.omega, 90)
158
158
 
159
159
  # need to wait for the OAV image to update
160
160
  # See #673 for improvements
@@ -54,7 +54,7 @@ class RobotLoadAndEnergyChangeComposite:
54
54
 
55
55
 
56
56
  def create_devices(context: BlueskyContext) -> RobotLoadAndEnergyChangeComposite:
57
- from mx_bluesky.hyperion.utils.context import device_composite_from_context
57
+ from mx_bluesky.common.utils.context import device_composite_from_context
58
58
 
59
59
  return device_composite_from_context(context, RobotLoadAndEnergyChangeComposite)
60
60
 
@@ -95,21 +95,19 @@ def prepare_for_robot_load(
95
95
  aperture_scatterguard: ApertureScatterguard, smargon: Smargon
96
96
  ):
97
97
  yield from bps.abs_set(
98
- aperture_scatterguard,
99
- ApertureValue.ROBOT_LOAD,
100
- group="prepare_robot_load",
98
+ aperture_scatterguard, ApertureValue.OUT_OF_BEAM, group="prepare_robot_load"
101
99
  )
102
100
 
103
- yield from bps.mv(smargon.stub_offsets, StubPosition.RESET_TO_ROBOT_LOAD) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
101
+ yield from bps.mv(smargon.stub_offsets, StubPosition.RESET_TO_ROBOT_LOAD)
104
102
 
105
103
  # fmt: off
106
104
  yield from bps.mv(
107
- smargon.x, 0, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
108
- smargon.y, 0, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
109
- smargon.z, 0, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
110
- smargon.omega, 0, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
111
- smargon.chi, 0, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
112
- smargon.phi, 0 # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
105
+ smargon.x, 0,
106
+ smargon.y, 0,
107
+ smargon.z, 0,
108
+ smargon.omega, 0,
109
+ smargon.chi, 0,
110
+ smargon.phi, 0
113
111
  )
114
112
  # fmt: on
115
113
 
@@ -122,11 +120,6 @@ def do_robot_load(
122
120
  demand_energy_ev: float | None,
123
121
  thawing_time: float,
124
122
  ):
125
- error_code = yield from bps.rd(composite.robot.error_code)
126
- # Reset robot if light curtains were tripped
127
- if error_code == 40:
128
- yield from bps.trigger(composite.robot.reset, wait=True)
129
-
130
123
  yield from bps.abs_set(
131
124
  composite.robot,
132
125
  sample_location,
@@ -138,7 +131,7 @@ def do_robot_load(
138
131
  yield from bps.wait("robot_load")
139
132
 
140
133
  yield from bps.abs_set(
141
- composite.thawer.thaw_for_time_s, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
134
+ composite.thawer.thaw_for_time_s,
142
135
  thawing_time,
143
136
  group="thawing_finished",
144
137
  )
@@ -200,7 +193,7 @@ def robot_load_and_snapshots(
200
193
 
201
194
  yield from bps.create(name=CONST.DESCRIPTORS.ROBOT_LOAD)
202
195
  yield from bps.read(composite.robot.barcode)
203
- yield from bps.read(composite.oav.snapshot) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
196
+ yield from bps.read(composite.oav.snapshot)
204
197
  yield from bps.read(composite.webcam)
205
198
  yield from bps.save()
206
199
 
@@ -37,6 +37,7 @@ from dodal.log import LOGGER
37
37
  from ophyd_async.fastcs.panda import HDFPanda
38
38
 
39
39
  from mx_bluesky.common.parameters.constants import OavConstants
40
+ from mx_bluesky.common.xrc_result import XRayCentreEventHandler
40
41
  from mx_bluesky.hyperion.device_setup_plans.utils import (
41
42
  fill_in_energy_if_not_supplied,
42
43
  start_preparing_data_collection_then_do_plan,
@@ -44,9 +45,6 @@ from mx_bluesky.hyperion.device_setup_plans.utils import (
44
45
  from mx_bluesky.hyperion.experiment_plans.change_aperture_then_move_plan import (
45
46
  change_aperture_then_move_to_xtal,
46
47
  )
47
- from mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan import (
48
- XRayCentreEventHandler,
49
- )
50
48
  from mx_bluesky.hyperion.experiment_plans.grid_detect_then_xray_centre_plan import (
51
49
  GridDetectThenXRayCentreComposite,
52
50
  )
@@ -106,7 +104,7 @@ class RobotLoadThenCentreComposite:
106
104
 
107
105
 
108
106
  def create_devices(context: BlueskyContext) -> RobotLoadThenCentreComposite:
109
- from mx_bluesky.hyperion.utils.context import device_composite_from_context
107
+ from mx_bluesky.common.utils.context import device_composite_from_context
110
108
 
111
109
  return device_composite_from_context(context, RobotLoadThenCentreComposite)
112
110
 
@@ -27,9 +27,12 @@ from dodal.devices.zebra.zebra import RotationDirection, Zebra
27
27
  from dodal.devices.zebra.zebra_controlled_shutter import ZebraShutter
28
28
  from dodal.plan_stubs.check_topup import check_topup_and_wait_if_necessary
29
29
 
30
- from mx_bluesky.common.device_setup_plans.read_hardware_for_setup import (
30
+ from mx_bluesky.common.plans.read_hardware import (
31
31
  read_hardware_for_zocalo,
32
+ standard_read_hardware_during_collection,
33
+ standard_read_hardware_pre_collection,
32
34
  )
35
+ from mx_bluesky.common.utils.context import device_composite_from_context
33
36
  from mx_bluesky.common.utils.log import LOGGER
34
37
  from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import (
35
38
  cleanup_sample_environment,
@@ -37,10 +40,6 @@ from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import (
37
40
  move_x_y_z,
38
41
  setup_sample_environment,
39
42
  )
40
- from mx_bluesky.hyperion.device_setup_plans.read_hardware_for_setup import (
41
- read_hardware_during_collection,
42
- read_hardware_pre_collection,
43
- )
44
43
  from mx_bluesky.hyperion.device_setup_plans.setup_zebra import (
45
44
  arm_zebra,
46
45
  setup_zebra_for_rotation,
@@ -62,7 +61,6 @@ from mx_bluesky.hyperion.parameters.rotation import (
62
61
  MultiRotationScan,
63
62
  RotationScan,
64
63
  )
65
- from mx_bluesky.hyperion.utils.context import device_composite_from_context
66
64
 
67
65
 
68
66
  @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
@@ -267,7 +265,7 @@ def rotation_scan_plan(
267
265
  # get some information for the ispyb deposition and trigger the callback
268
266
  yield from read_hardware_for_zocalo(composite.eiger)
269
267
 
270
- yield from read_hardware_pre_collection(
268
+ yield from standard_read_hardware_pre_collection(
271
269
  composite.undulator,
272
270
  composite.synchrotron,
273
271
  composite.s4_slit_gaps,
@@ -293,7 +291,7 @@ def rotation_scan_plan(
293
291
  LOGGER.info("Executing rotation scan")
294
292
  yield from bps.rel_set(axis, motion_values.distance_to_move_deg, wait=True)
295
293
 
296
- yield from read_hardware_during_collection(
294
+ yield from standard_read_hardware_during_collection(
297
295
  composite.aperture_scatterguard,
298
296
  composite.attenuator,
299
297
  composite.flux,
@@ -346,6 +344,13 @@ def _move_and_rotation(
346
344
  yield from setup_beamline_for_OAV(
347
345
  composite.smargon, composite.backlight, composite.aperture_scatterguard
348
346
  )
347
+ yield from bps.wait(group=CONST.WAIT.READY_FOR_OAV)
348
+ if params.selected_aperture:
349
+ yield from bps.prepare(
350
+ composite.aperture_scatterguard,
351
+ params.selected_aperture,
352
+ group=CONST.WAIT.ROTATION_READY_FOR_DC,
353
+ )
349
354
  yield from oav_snapshot_plan(composite, params, oav_params)
350
355
  yield from rotation_scan_plan(
351
356
  composite,
@@ -376,8 +381,10 @@ def rotation_scan(
376
381
  }
377
382
  )
378
383
  @transmission_and_xbpm_feedback_for_collection_decorator(
384
+ composite.undulator,
379
385
  composite.xbpm_feedback,
380
386
  composite.attenuator,
387
+ composite.dcm,
381
388
  parameters.transmission_frac,
382
389
  )
383
390
  def rotation_scan_plan_with_stage_and_cleanup(
@@ -399,7 +406,7 @@ def rotation_scan(
399
406
  rotation_with_cleanup_and_stage(params),
400
407
  group=CONST.WAIT.ROTATION_READY_FOR_DC,
401
408
  )
402
- yield from bps.unstage(eiger) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
409
+ yield from bps.unstage(eiger)
403
410
 
404
411
  yield from rotation_scan_plan_with_stage_and_cleanup(parameters)
405
412
 
@@ -427,10 +434,11 @@ def multi_rotation_scan(
427
434
  ],
428
435
  }
429
436
  )
430
- @bpp.stage_decorator([eiger])
431
437
  @transmission_and_xbpm_feedback_for_collection_decorator(
438
+ composite.undulator,
432
439
  composite.xbpm_feedback,
433
440
  composite.attenuator,
441
+ composite.dcm,
434
442
  parameters.transmission_frac,
435
443
  )
436
444
  @bpp.finalize_decorator(lambda: _cleanup_plan(composite))
@@ -460,3 +468,4 @@ def multi_rotation_scan(
460
468
  _multi_rotation_scan(),
461
469
  group=CONST.WAIT.ROTATION_READY_FOR_DC,
462
470
  )
471
+ yield from bps.unstage(eiger)
@@ -54,7 +54,9 @@ def set_energy_plan(
54
54
  if energy_ev:
55
55
  yield from transmission_and_xbpm_feedback_for_collection_wrapper(
56
56
  _set_energy_plan(energy_ev / 1000, composite),
57
+ composite.undulator_dcm.undulator_ref(),
57
58
  composite.xbpm_feedback,
58
59
  composite.attenuator,
60
+ composite.dcm,
59
61
  DESIRED_TRANSMISSION_FRACTION,
60
62
  )
@@ -0,0 +1,104 @@
1
+ import dataclasses
2
+ import json
3
+ import re
4
+ from typing import TypeVar
5
+
6
+ import requests
7
+ from dodal.utils import get_beamline_name
8
+
9
+ from mx_bluesky.common.parameters.components import WithVisit
10
+ from mx_bluesky.common.parameters.constants import GridscanParamConstants
11
+ from mx_bluesky.common.utils.log import LOGGER
12
+ from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
13
+
14
+ T = TypeVar("T", bound=WithVisit)
15
+ AGAMEMNON_URL = "http://agamemnon.diamond.ac.uk/"
16
+ MULTIPIN_PREFIX = "multipin"
17
+ MULTIPIN_FORMAT_DESC = "Expected multipin format is multipin_{number_of_wells}x{well_size}+{distance_between_tip_and_first_well}"
18
+ MULTIPIN_REGEX = rf"^{MULTIPIN_PREFIX}_(\d+)x(\d+(?:\.\d+)?)\+(\d+(?:\.\d+)?)$"
19
+
20
+
21
+ @dataclasses.dataclass
22
+ class PinType:
23
+ expected_number_of_crystals: int
24
+ single_well_width_um: float
25
+ tip_to_first_well_um: float = 0
26
+
27
+ @property
28
+ def full_width(self) -> float:
29
+ """This is the "width" of the area where there may be samples.
30
+
31
+ From a pin perspective this is along the length of the pin but we use width here as
32
+ we mount the sample at 90 deg to the optical camera.
33
+
34
+ We calculate the full width by adding all the gaps between wells then assuming
35
+ there is a buffer of {tip_to_first_well_um} either side too. In reality the
36
+ calculation does not need to be very exact as long as we get a width that's good
37
+ enough to use for optical centring and XRC grid size.
38
+ """
39
+ return (self.expected_number_of_crystals - 1) * self.single_well_width_um + (
40
+ 2 * self.tip_to_first_well_um
41
+ )
42
+
43
+
44
+ class SinglePin(PinType):
45
+ def __init__(self):
46
+ super().__init__(1, GridscanParamConstants.WIDTH_UM)
47
+
48
+ @property
49
+ def full_width(self) -> float:
50
+ return self.single_well_width_um
51
+
52
+
53
+ def _get_parameters_from_url(url: str) -> dict:
54
+ response = requests.get(url, headers={"Accept": "application/json"})
55
+ response.raise_for_status()
56
+ response_json = json.loads(response.content)
57
+ try:
58
+ return response_json["collect"]
59
+ except KeyError as e:
60
+ raise KeyError(f"Unexpected json from agamemnon: {response_json}") from e
61
+
62
+
63
+ def _get_pin_type_from_agamemnon_parameters(parameters: dict) -> PinType:
64
+ loop_type_name: str | None = parameters["sample"]["loopType"]
65
+ if loop_type_name:
66
+ regex_search = re.search(MULTIPIN_REGEX, loop_type_name)
67
+ if regex_search:
68
+ wells, well_size, tip_to_first_well = regex_search.groups()
69
+ return PinType(int(wells), float(well_size), float(tip_to_first_well))
70
+ else:
71
+ loop_type_message = (
72
+ f"Agamemnon loop type of {loop_type_name} not recognised"
73
+ )
74
+ if loop_type_name.startswith(MULTIPIN_PREFIX):
75
+ raise ValueError(f"{loop_type_message}. {MULTIPIN_FORMAT_DESC}")
76
+ LOGGER.warning(f"{loop_type_message}, assuming single pin")
77
+ return SinglePin()
78
+
79
+
80
+ def get_next_instruction(beamline: str) -> dict:
81
+ return _get_parameters_from_url(AGAMEMNON_URL + f"getnextcollect/{beamline}")
82
+
83
+
84
+ def get_pin_type_from_agamemnon(beamline: str) -> PinType:
85
+ params = get_next_instruction(beamline)
86
+ return _get_pin_type_from_agamemnon_parameters(params)
87
+
88
+
89
+ def update_params_from_agamemnon(parameters: T) -> T:
90
+ try:
91
+ beamline_name = get_beamline_name("i03")
92
+ pin_type = get_pin_type_from_agamemnon(beamline_name)
93
+ if isinstance(parameters, LoadCentreCollect):
94
+ parameters.robot_load_then_centre.tip_offset_um = pin_type.full_width / 2
95
+ parameters.robot_load_then_centre.grid_width_um = pin_type.full_width
96
+ parameters.select_centres.n = pin_type.expected_number_of_crystals
97
+ if pin_type != SinglePin():
98
+ # Snapshots between each collection take a lot of time.
99
+ # Before we do https://github.com/DiamondLightSource/mx-bluesky/issues/226
100
+ # this will give no snapshots but that's preferable
101
+ parameters.multi_rotation_scan.snapshot_omegas_deg = []
102
+ except Exception as e:
103
+ LOGGER.warning(f"Failed to get pin type from agamemnon, using single pin {e}")
104
+ return parameters
@@ -3,6 +3,7 @@ from collections.abc import Callable, Sequence
3
3
  from threading import Thread
4
4
  from time import sleep
5
5
 
6
+ from bluesky.callbacks import CallbackBase
6
7
  from bluesky.callbacks.zmq import Proxy, RemoteDispatcher
7
8
  from dodal.log import LOGGER as dodal_logger
8
9
  from dodal.log import set_up_all_logging_handlers
@@ -48,17 +49,33 @@ LIVENESS_POLL_SECONDS = 1
48
49
  ERROR_LOG_BUFFER_LINES = 5000
49
50
 
50
51
 
51
- def setup_callbacks():
52
- return [
52
+ def create_gridscan_callbacks() -> tuple[
53
+ GridscanNexusFileCallback, GridscanISPyBCallback
54
+ ]:
55
+ return (
53
56
  GridscanNexusFileCallback(param_type=HyperionSpecifiedThreeDGridScan),
54
57
  GridscanISPyBCallback(
55
58
  param_type=GridCommonWithHyperionDetectorParams,
56
59
  emit=ZocaloCallback(CONST.PLAN.DO_FGS, CONST.ZOCALO_ENV),
57
60
  ),
61
+ )
62
+
63
+
64
+ def create_rotation_callbacks() -> tuple[
65
+ RotationNexusFileCallback, RotationISPyBCallback
66
+ ]:
67
+ return (
58
68
  RotationNexusFileCallback(),
59
69
  RotationISPyBCallback(
60
70
  emit=ZocaloCallback(CONST.PLAN.ROTATION_MAIN, CONST.ZOCALO_ENV)
61
71
  ),
72
+ )
73
+
74
+
75
+ def setup_callbacks() -> list[CallbackBase]:
76
+ return [
77
+ *create_gridscan_callbacks(),
78
+ *create_rotation_callbacks(),
62
79
  LogUidTaggingCallback(),
63
80
  RobotLoadISPyBCallback(),
64
81
  SampleHandlingCallback(),
@@ -136,7 +136,7 @@ class RotationISPyBCallback(BaseISPyBCallback):
136
136
  "handle_ispyb_hardware_read triggered before activity_gated_start"
137
137
  )
138
138
  motor_positions_um = [position * 1000 for position in motor_positions_mm]
139
- comment = f"Sample position (µm): ({motor_positions_um[0]:.0f}, {motor_positions_um[1]:.0f}, {motor_positions_um[2]:.0f}) {self.params.comment} "
139
+ comment = f"Sample position (µm): ({motor_positions_um[0]:.0f}, {motor_positions_um[1]:.0f}, {motor_positions_um[2]:.0f})"
140
140
  scan_data_infos[0].data_collection_info.comments = comment
141
141
  return scan_data_infos
142
142
 
@@ -19,22 +19,27 @@ class SampleHandlingCallback(PlanReactiveCallback):
19
19
  super().__init__(log=ISPYB_ZOCALO_CALLBACK_LOGGER)
20
20
  self._sample_id: int | None = None
21
21
  self._descriptor: str | None = None
22
+ self._run_id: str | None = None
22
23
 
23
24
  def activity_gated_start(self, doc: RunStart):
24
- if not self._sample_id:
25
+ if not self._sample_id and self.active:
25
26
  sample_id = doc.get("metadata", {}).get("sample_id")
26
27
  self.log.info(f"Recording sample ID at run start {sample_id}")
27
28
  self._sample_id = sample_id
29
+ self._run_id = self.activity_uid
28
30
 
29
31
  def activity_gated_stop(self, doc: RunStop) -> RunStop:
30
- if doc["exit_status"] != "success":
31
- exception_type, message = SampleException.type_and_message_from_reason(
32
- doc.get("reason", "")
33
- )
34
- self.log.info(
35
- f"Sample handling callback intercepted exception of type {exception_type}: {message}"
36
- )
37
- self._record_exception(exception_type)
32
+ if self._run_id == doc.get("run_start"):
33
+ if doc["exit_status"] != "success":
34
+ exception_type, message = SampleException.type_and_message_from_reason(
35
+ doc.get("reason", "")
36
+ )
37
+ self.log.info(
38
+ f"Sample handling callback intercepted exception of type {exception_type}: {message}"
39
+ )
40
+ self._record_exception(exception_type)
41
+ self._sample_id = None
42
+ self._run_id = None
38
43
  return doc
39
44
 
40
45
  def _record_exception(self, exception_type: str):
@@ -1,6 +1,7 @@
1
1
  from functools import cache
2
2
 
3
3
  from daq_config_server.client import ConfigServer
4
+ from pydantic import model_validator
4
5
 
5
6
  from mx_bluesky.common.external_interaction.config_server import FeatureFlags
6
7
  from mx_bluesky.common.utils.log import LOGGER
@@ -13,8 +14,10 @@ class HyperionFeatureFlags(FeatureFlags):
13
14
 
14
15
  Attributes:
15
16
  use_panda_for_gridscan: If True then the PandA is used for gridscans, otherwise the zebra is used
16
- compare_cpu_and_gpu_zocalo: If True then GPU result processing is enabled alongside CPU, if False then
17
- CPU only is used.
17
+ compare_cpu_and_gpu_zocalo: If True then GPU result processing is enabled
18
+ alongside CPU and the results are compared. The CPU result is still take.n
19
+ use_gpu_results: If True then GPU result processing is enabled
20
+ and the GPU result is taken.
18
21
  set_stub_offsets: If True then set the stub offsets after moving to the crystal (ignored for
19
22
  multi-centre)
20
23
  omega_flip: If True then invert the smargon omega motor rotation commands with respect to
@@ -26,7 +29,15 @@ class HyperionFeatureFlags(FeatureFlags):
26
29
  def get_config_server() -> ConfigServer:
27
30
  return ConfigServer(CONST.CONFIG_SERVER_URL, LOGGER)
28
31
 
32
+ @model_validator(mode="after")
33
+ def use_gpu_and_compare_cannot_both_be_true(self):
34
+ assert not (self.use_gpu_results and self.compare_cpu_and_gpu_zocalo), (
35
+ "Cannot both use GPU results and compare them to CPU"
36
+ )
37
+ return self
38
+
29
39
  use_panda_for_gridscan: bool = CONST.I03.USE_PANDA_FOR_GRIDSCAN
30
40
  compare_cpu_and_gpu_zocalo: bool = CONST.I03.COMPARE_CPU_AND_GPU_ZOCALO
41
+ use_gpu_results: bool = CONST.I03.USE_GPU_RESULTS
31
42
  set_stub_offsets: bool = CONST.I03.SET_STUB_OFFSETS
32
43
  omega_flip: bool = CONST.I03.OMEGA_FLIP
@@ -8,7 +8,6 @@ from mx_bluesky._version import version
8
8
  @dataclass
9
9
  class HyperionArgs:
10
10
  dev_mode: bool = False
11
- use_external_callbacks: bool = False
12
11
  verbose_event_logging: bool = False
13
12
  skip_startup_connection: bool = False
14
13
 
@@ -34,8 +33,7 @@ def parse_cli_args() -> HyperionArgs:
34
33
  """Parses all arguments relevant to hyperion. Returns an HyperionArgs dataclass with
35
34
  the fields: (verbose_event_logging: bool,
36
35
  dev_mode: bool,
37
- skip_startup_connection: bool,
38
- external_callbacks: bool)"""
36
+ skip_startup_connection: bool)"""
39
37
  parser = argparse.ArgumentParser()
40
38
  _add_callback_relevant_args(parser)
41
39
  parser.add_argument(
@@ -48,11 +46,6 @@ def parse_cli_args() -> HyperionArgs:
48
46
  action="store_true",
49
47
  help="Skip connecting to EPICS PVs on startup",
50
48
  )
51
- parser.add_argument(
52
- "--external-callbacks",
53
- action="store_true",
54
- help="Run the external hyperion-callbacks service and publish events over ZMQ",
55
- )
56
49
  parser.add_argument(
57
50
  "--version",
58
51
  help="Print hyperion version string",
@@ -64,5 +57,4 @@ def parse_cli_args() -> HyperionArgs:
64
57
  verbose_event_logging=args.verbose_event_logging or False,
65
58
  dev_mode=args.dev or False,
66
59
  skip_startup_connection=args.skip_startup_connection or False,
67
- use_external_callbacks=args.external_callbacks or False,
68
60
  )
@@ -4,6 +4,7 @@ from dodal.devices.detector import EIGER2_X_16M_SIZE
4
4
  from pydantic.dataclasses import dataclass
5
5
 
6
6
  from mx_bluesky.common.parameters.constants import (
7
+ DeviceSettingsConstants,
7
8
  DocDescriptorNames,
8
9
  EnvironmentConstants,
9
10
  ExperimentParamConstants,
@@ -31,9 +32,12 @@ class I03Constants:
31
32
  OMEGA_FLIP = True
32
33
 
33
34
  # Turns on GPU processing for zocalo and logs a comparison between GPU and CPU-
34
- # processed results. GPU results never used in analysis for now
35
+ # processed results.
35
36
  COMPARE_CPU_AND_GPU_ZOCALO = False
36
37
 
38
+ # Turns on GPU processing for zocalo and uses the results that come back
39
+ USE_GPU_RESULTS = False
40
+
37
41
 
38
42
  @dataclass(frozen=True)
39
43
  class HyperionConstants:
@@ -57,6 +61,7 @@ class HyperionConstants:
57
61
  GRAYLOG_PORT = 12232
58
62
  PARAMETER_SCHEMA_DIRECTORY = "src/hyperion/parameters/schemas/"
59
63
  LOG_FILE_NAME = "hyperion.log"
64
+ DEVICE_SETTINGS_CONSTANTS = DeviceSettingsConstants()
60
65
 
61
66
 
62
67
  CONST = HyperionConstants()
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ import pydantic
4
+ from dodal.devices.aperturescatterguard import (
5
+ ApertureScatterguard,
6
+ )
7
+ from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
8
+ from dodal.devices.backlight import Backlight
9
+ from dodal.devices.dcm import DCM
10
+ from dodal.devices.eiger import EigerDetector
11
+ from dodal.devices.fast_grid_scan import (
12
+ PandAFastGridScan,
13
+ ZebraFastGridScan,
14
+ )
15
+ from dodal.devices.flux import Flux
16
+ from dodal.devices.robot import BartRobot
17
+ from dodal.devices.s4_slit_gaps import S4SlitGaps
18
+ from dodal.devices.smargon import Smargon
19
+ from dodal.devices.synchrotron import Synchrotron
20
+ from dodal.devices.undulator import Undulator
21
+ from dodal.devices.xbpm_feedback import XBPMFeedback
22
+ from dodal.devices.zebra.zebra import Zebra
23
+ from dodal.devices.zebra.zebra_controlled_shutter import ZebraShutter
24
+ from dodal.devices.zocalo import ZocaloResults
25
+ from ophyd_async.fastcs.panda import HDFPanda
26
+
27
+
28
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
29
+ class HyperionFlyScanXRayCentreComposite:
30
+ """All devices which are directly or indirectly required by this plan"""
31
+
32
+ aperture_scatterguard: ApertureScatterguard
33
+ attenuator: BinaryFilterAttenuator
34
+ backlight: Backlight
35
+ dcm: DCM
36
+ eiger: EigerDetector
37
+ zebra_fast_grid_scan: ZebraFastGridScan
38
+ flux: Flux
39
+ s4_slit_gaps: S4SlitGaps
40
+ smargon: Smargon
41
+ undulator: Undulator
42
+ synchrotron: Synchrotron
43
+ xbpm_feedback: XBPMFeedback
44
+ zebra: Zebra
45
+ zocalo: ZocaloResults
46
+ panda: HDFPanda
47
+ panda_fast_grid_scan: PandAFastGridScan
48
+ robot: BartRobot
49
+ sample_shutter: ZebraShutter
@@ -47,12 +47,13 @@ class GridCommonWithHyperionDetectorParams(GridCommon, WithHyperionUDCFeatures):
47
47
  use_roi_mode=self.use_roi_mode,
48
48
  det_dist_to_beam_converter_path=self.det_dist_to_beam_converter_path,
49
49
  trigger_mode=self.trigger_mode,
50
- enable_dev_shm=self.features.compare_cpu_and_gpu_zocalo,
50
+ enable_dev_shm=self.features.compare_cpu_and_gpu_zocalo
51
+ or self.features.use_gpu_results,
51
52
  **optional_args,
52
53
  )
53
54
 
54
55
 
55
- class HyperionSpecifiedThreeDGridScan(SpecifiedThreeDGridScan, WithHyperionUDCFeatures):
56
+ class HyperionSpecifiedThreeDGridScan(WithHyperionUDCFeatures, SpecifiedThreeDGridScan):
56
57
  """Hyperion's 3D grid scan deviates from the common class due to: optionally using a PandA, optionally using dev_shm for GPU analysis, and using a config server for features"""
57
58
 
58
59
  # These detector params only exist so that we can properly select enable_dev_shm. Remove in
@@ -83,7 +84,8 @@ class HyperionSpecifiedThreeDGridScan(SpecifiedThreeDGridScan, WithHyperionUDCFe
83
84
  use_roi_mode=self.use_roi_mode,
84
85
  det_dist_to_beam_converter_path=self.det_dist_to_beam_converter_path,
85
86
  trigger_mode=self.trigger_mode,
86
- enable_dev_shm=self.features.compare_cpu_and_gpu_zocalo,
87
+ enable_dev_shm=self.features.compare_cpu_and_gpu_zocalo
88
+ or self.features.use_gpu_results,
87
89
  **optional_args,
88
90
  )
89
91