mx-bluesky 1.4.5__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 (63) 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 +1 -1
  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 +1 -1
  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/__init__.py +0 -0
  16. mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +109 -0
  17. mx_bluesky/beamlines/i24/serial/write_nexus.py +2 -2
  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 +2 -2
  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 +1 -1
  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/{hyperion/experiment_plans/common → common}/xrc_result.py +16 -0
  29. mx_bluesky/hyperion/__main__.py +4 -0
  30. mx_bluesky/hyperion/device_setup_plans/setup_oav.py +5 -5
  31. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +2 -2
  32. mx_bluesky/hyperion/device_setup_plans/smargon.py +6 -6
  33. mx_bluesky/hyperion/device_setup_plans/utils.py +2 -2
  34. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +4 -4
  35. mx_bluesky/hyperion/experiment_plans/change_aperture_then_move_plan.py +2 -6
  36. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +33 -87
  37. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +6 -6
  38. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +3 -5
  39. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +11 -11
  40. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +4 -4
  41. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +1 -1
  42. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +2 -4
  43. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +10 -10
  44. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +10 -10
  45. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +2 -4
  46. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +7 -9
  47. mx_bluesky/hyperion/external_interaction/agamemnon.py +104 -0
  48. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +1 -1
  49. mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/sample_handling_callback.py +14 -9
  50. mx_bluesky/hyperion/external_interaction/config_server.py +6 -6
  51. mx_bluesky/hyperion/parameters/device_composites.py +49 -0
  52. mx_bluesky/hyperion/parameters/gridscan.py +1 -1
  53. mx_bluesky/hyperion/utils/__init__.py +1 -0
  54. mx_bluesky/hyperion/utils/context.py +0 -65
  55. mx_bluesky/hyperion/utils/validation.py +3 -3
  56. {mx_bluesky-1.4.5.dist-info → mx_bluesky-1.4.6.dist-info}/METADATA +4 -3
  57. {mx_bluesky-1.4.5.dist-info → mx_bluesky-1.4.6.dist-info}/RECORD +61 -56
  58. {mx_bluesky-1.4.5.dist-info → mx_bluesky-1.4.6.dist-info}/WHEEL +1 -1
  59. {mx_bluesky-1.4.5.dist-info → mx_bluesky-1.4.6.dist-info}/entry_points.txt +1 -0
  60. mx_bluesky/common/device_setup_plans/read_hardware_for_setup.py +0 -14
  61. mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +0 -54
  62. {mx_bluesky-1.4.5.dist-info → mx_bluesky-1.4.6.dist-info}/LICENSE +0 -0
  63. {mx_bluesky-1.4.5.dist-info → mx_bluesky-1.4.6.dist-info}/top_level.txt +0 -0
@@ -14,13 +14,13 @@ from dodal.devices.oav.pin_image_recognition.utils import NONE_VALUE
14
14
  from dodal.devices.oav.utils import PinNotFoundException, wait_for_tip_to_be_found
15
15
  from dodal.devices.smargon import Smargon
16
16
 
17
+ from mx_bluesky.common.utils.context import device_composite_from_context
17
18
  from mx_bluesky.common.utils.exceptions import catch_exception_and_warn
18
19
  from mx_bluesky.common.utils.log import LOGGER
19
20
  from mx_bluesky.hyperion.device_setup_plans.setup_oav import (
20
21
  pre_centring_setup_oav,
21
22
  )
22
23
  from mx_bluesky.hyperion.parameters.constants import CONST
23
- from mx_bluesky.hyperion.utils.context import device_composite_from_context
24
24
 
25
25
  if TYPE_CHECKING:
26
26
  from dodal.devices.oav.oav_parameters import OAVParameters
@@ -100,7 +100,7 @@ def grid_detection_plan(
100
100
 
101
101
  # The FGS uses -90 so we need to match it
102
102
  for angle in [0, -90]:
103
- yield from bps.mv(smargon.omega, angle) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
103
+ yield from bps.mv(smargon.omega, angle)
104
104
  # need to wait for the OAV image to update
105
105
  # See #673 for improvements
106
106
  yield from bps.sleep(CONST.HARDWARE.OAV_REFRESH_DELAY)
@@ -152,20 +152,20 @@ def grid_detection_plan(
152
152
 
153
153
  upper_left = (tip_x_px, min_y)
154
154
 
155
- yield from bps.abs_set(oav.grid_snapshot.top_left_x, upper_left[0]) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
156
- yield from bps.abs_set(oav.grid_snapshot.top_left_y, upper_left[1]) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
157
- yield from bps.abs_set(oav.grid_snapshot.box_width, box_size_x_pixels) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
158
- yield from bps.abs_set(oav.grid_snapshot.num_boxes_x, x_steps) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
159
- yield from bps.abs_set(oav.grid_snapshot.num_boxes_y, y_steps) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
155
+ yield from bps.abs_set(oav.grid_snapshot.top_left_x, upper_left[0])
156
+ yield from bps.abs_set(oav.grid_snapshot.top_left_y, upper_left[1])
157
+ yield from bps.abs_set(oav.grid_snapshot.box_width, box_size_x_pixels)
158
+ yield from bps.abs_set(oav.grid_snapshot.num_boxes_x, x_steps)
159
+ yield from bps.abs_set(oav.grid_snapshot.num_boxes_y, y_steps)
160
160
 
161
161
  snapshot_filename = snapshot_template.format(angle=abs(angle))
162
162
 
163
- yield from bps.abs_set(oav.grid_snapshot.filename, snapshot_filename) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
164
- yield from bps.abs_set(oav.grid_snapshot.directory, snapshot_dir) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
165
- yield from bps.trigger(oav.grid_snapshot, wait=True) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
163
+ yield from bps.abs_set(oav.grid_snapshot.filename, snapshot_filename)
164
+ yield from bps.abs_set(oav.grid_snapshot.directory, snapshot_dir)
165
+ yield from bps.trigger(oav.grid_snapshot, wait=True)
166
166
  yield from bps.create(CONST.DESCRIPTORS.OAV_GRID_SNAPSHOT_TRIGGERED)
167
167
 
168
- yield from bps.read(oav) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
168
+ yield from bps.read(oav)
169
169
  yield from bps.read(smargon)
170
170
  yield from bps.save()
171
171
 
@@ -57,7 +57,7 @@ def _setup_oav(
57
57
  ):
58
58
  yield from setup_general_oav_params(composite.oav, oav_parameters)
59
59
  yield from bps.abs_set(
60
- composite.oav.snapshot.directory, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
60
+ composite.oav.snapshot.directory,
61
61
  str(parameters.snapshot_directory),
62
62
  )
63
63
 
@@ -69,12 +69,12 @@ def _take_oav_snapshot(composite: OavSnapshotComposite, omega: float):
69
69
  time_now = datetime.now()
70
70
  filename = f"{time_now.strftime('%H%M%S')}_oav_snapshot_{omega:.0f}"
71
71
  yield from bps.abs_set(
72
- composite.oav.snapshot.filename, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
72
+ composite.oav.snapshot.filename,
73
73
  filename,
74
74
  group=OAV_SNAPSHOT_SETUP_SHOT,
75
75
  )
76
76
  yield from bps.wait(group=OAV_SNAPSHOT_SETUP_SHOT)
77
- yield from bps.trigger(composite.oav.snapshot, wait=True) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
77
+ yield from bps.trigger(composite.oav.snapshot, wait=True)
78
78
  yield from bps.create(DocDescriptorNames.OAV_ROTATION_SNAPSHOT_TRIGGERED)
79
- yield from bps.read(composite.oav.snapshot) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
79
+ yield from bps.read(composite.oav.snapshot)
80
80
  yield from bps.save()
@@ -9,8 +9,8 @@ from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
9
9
  from dodal.devices.xspress3.xspress3 import Xspress3
10
10
  from dodal.devices.zebra.zebra_controlled_shutter import ZebraShutter, ZebraShutterState
11
11
 
12
+ from mx_bluesky.common.utils.context import device_composite_from_context
12
13
  from mx_bluesky.common.utils.log import LOGGER
13
- from mx_bluesky.hyperion.utils.context import device_composite_from_context
14
14
 
15
15
 
16
16
  class AttenuationOptimisationFailedException(Exception):
@@ -12,7 +12,9 @@ from mx_bluesky.common.external_interaction.callbacks.xray_centre.ispyb_callback
12
12
  ispyb_activation_wrapper,
13
13
  )
14
14
  from mx_bluesky.common.parameters.constants import OavConstants
15
+ from mx_bluesky.common.utils.context import device_composite_from_context
15
16
  from mx_bluesky.common.utils.log import LOGGER
17
+ from mx_bluesky.common.xrc_result import XRayCentreEventHandler
16
18
  from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import move_phi_chi_omega
17
19
  from mx_bluesky.hyperion.device_setup_plans.utils import (
18
20
  start_preparing_data_collection_then_do_plan,
@@ -20,9 +22,6 @@ from mx_bluesky.hyperion.device_setup_plans.utils import (
20
22
  from mx_bluesky.hyperion.experiment_plans.change_aperture_then_move_plan import (
21
23
  change_aperture_then_move_to_xtal,
22
24
  )
23
- from mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan import (
24
- XRayCentreEventHandler,
25
- )
26
25
  from mx_bluesky.hyperion.experiment_plans.grid_detect_then_xray_centre_plan import (
27
26
  GridDetectThenXRayCentreComposite,
28
27
  detect_grid_and_do_gridscan,
@@ -39,7 +38,6 @@ from mx_bluesky.hyperion.parameters.gridscan import (
39
38
  GridScanWithEdgeDetect,
40
39
  PinTipCentreThenXrayCentre,
41
40
  )
42
- from mx_bluesky.hyperion.utils.context import device_composite_from_context
43
41
 
44
42
 
45
43
  def create_devices(context: BlueskyContext) -> GridDetectThenXRayCentreComposite:
@@ -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
 
@@ -98,16 +98,16 @@ def prepare_for_robot_load(
98
98
  aperture_scatterguard, ApertureValue.OUT_OF_BEAM, group="prepare_robot_load"
99
99
  )
100
100
 
101
- 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)
102
102
 
103
103
  # fmt: off
104
104
  yield from bps.mv(
105
- smargon.x, 0, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
106
- smargon.y, 0, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
107
- smargon.z, 0, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
108
- smargon.omega, 0, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
109
- smargon.chi, 0, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
110
- 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
111
111
  )
112
112
  # fmt: on
113
113
 
@@ -131,7 +131,7 @@ def do_robot_load(
131
131
  yield from bps.wait("robot_load")
132
132
 
133
133
  yield from bps.abs_set(
134
- composite.thawer.thaw_for_time_s, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
134
+ composite.thawer.thaw_for_time_s,
135
135
  thawing_time,
136
136
  group="thawing_finished",
137
137
  )
@@ -193,7 +193,7 @@ def robot_load_and_snapshots(
193
193
 
194
194
  yield from bps.create(name=CONST.DESCRIPTORS.ROBOT_LOAD)
195
195
  yield from bps.read(composite.robot.barcode)
196
- 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)
197
197
  yield from bps.read(composite.webcam)
198
198
  yield from bps.save()
199
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,
@@ -408,7 +406,7 @@ def rotation_scan(
408
406
  rotation_with_cleanup_and_stage(params),
409
407
  group=CONST.WAIT.ROTATION_READY_FOR_DC,
410
408
  )
411
- yield from bps.unstage(eiger) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
409
+ yield from bps.unstage(eiger)
412
410
 
413
411
  yield from rotation_scan_plan_with_stage_and_cleanup(parameters)
414
412
 
@@ -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
@@ -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):
@@ -29,15 +29,15 @@ class HyperionFeatureFlags(FeatureFlags):
29
29
  def get_config_server() -> ConfigServer:
30
30
  return ConfigServer(CONST.CONFIG_SERVER_URL, LOGGER)
31
31
 
32
- use_panda_for_gridscan: bool = CONST.I03.USE_PANDA_FOR_GRIDSCAN
33
- compare_cpu_and_gpu_zocalo: bool = CONST.I03.COMPARE_CPU_AND_GPU_ZOCALO
34
- use_gpu_results: bool = CONST.I03.USE_GPU_RESULTS
35
- set_stub_offsets: bool = CONST.I03.SET_STUB_OFFSETS
36
- omega_flip: bool = CONST.I03.OMEGA_FLIP
37
-
38
32
  @model_validator(mode="after")
39
33
  def use_gpu_and_compare_cannot_both_be_true(self):
40
34
  assert not (self.use_gpu_results and self.compare_cpu_and_gpu_zocalo), (
41
35
  "Cannot both use GPU results and compare them to CPU"
42
36
  )
43
37
  return self
38
+
39
+ use_panda_for_gridscan: bool = CONST.I03.USE_PANDA_FOR_GRIDSCAN
40
+ compare_cpu_and_gpu_zocalo: bool = CONST.I03.COMPARE_CPU_AND_GPU_ZOCALO
41
+ use_gpu_results: bool = CONST.I03.USE_GPU_RESULTS
42
+ set_stub_offsets: bool = CONST.I03.SET_STUB_OFFSETS
43
+ omega_flip: bool = CONST.I03.OMEGA_FLIP
@@ -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
@@ -53,7 +53,7 @@ class GridCommonWithHyperionDetectorParams(GridCommon, WithHyperionUDCFeatures):
53
53
  )
54
54
 
55
55
 
56
- class HyperionSpecifiedThreeDGridScan(SpecifiedThreeDGridScan, WithHyperionUDCFeatures):
56
+ class HyperionSpecifiedThreeDGridScan(WithHyperionUDCFeatures, SpecifiedThreeDGridScan):
57
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"""
58
58
 
59
59
  # These detector params only exist so that we can properly select enable_dev_shm. Remove in
@@ -0,0 +1 @@
1
+ # placeholder file to start layout
@@ -1,74 +1,9 @@
1
- import dataclasses
2
- from typing import Any, ClassVar, Protocol, TypeVar, get_type_hints
3
-
4
1
  from blueapi.core import BlueskyContext
5
- from blueapi.core.bluesky_types import Device
6
2
  from dodal.utils import get_beamline_based_on_environment_variable
7
3
 
8
4
  import mx_bluesky.hyperion.experiment_plans as hyperion_plans
9
5
  from mx_bluesky.common.utils.log import LOGGER
10
6
 
11
- T = TypeVar("T", bound=Device)
12
-
13
-
14
- class _IsDataclass(Protocol):
15
- """Protocol followed by any dataclass"""
16
-
17
- __dataclass_fields__: ClassVar[dict]
18
-
19
-
20
- DT = TypeVar("DT", bound=_IsDataclass)
21
-
22
-
23
- def find_device_in_context(
24
- context: BlueskyContext,
25
- name: str,
26
- # Typing in here is wrong (see https://github.com/microsoft/pyright/issues/7228#issuecomment-1934500232)
27
- # but this whole thing will go away when we do https://github.com/DiamondLightSource/hyperion/issues/868
28
- expected_type: type[T] = Device, # type: ignore
29
- ) -> T:
30
- LOGGER.debug(f"Looking for device {name} of type {expected_type} in context")
31
-
32
- device = context.find_device(name)
33
- if device is None:
34
- raise ValueError(
35
- f"Cannot find device named '{name}' in bluesky context {context.devices}."
36
- )
37
-
38
- if not isinstance(device, expected_type):
39
- raise ValueError(
40
- f"Found device named '{name}' and expected it to be a '{expected_type}' but it was a '{device.__class__.__name__}'"
41
- )
42
-
43
- LOGGER.debug(f"Found matching device {device}")
44
- return device
45
-
46
-
47
- def device_composite_from_context(context: BlueskyContext, dc: type[DT]) -> DT:
48
- """
49
- Initializes all of the devices referenced in a given dataclass from a provided
50
- context, checking that the types of devices returned by the context are compatible
51
- with the type annotations of the dataclass.
52
-
53
- Note that if the context was not created with `wait_for_connection=True` devices may
54
- still be unconnected.
55
- """
56
- LOGGER.debug(
57
- f"Attempting to initialize devices referenced in dataclass {dc} from blueapi context"
58
- )
59
-
60
- devices: dict[str, Any] = {}
61
- dc_type_hints: dict[str, Any] = get_type_hints(dc)
62
-
63
- for field in dataclasses.fields(dc):
64
- device = find_device_in_context(
65
- context, field.name, expected_type=dc_type_hints.get(field.name, Device)
66
- )
67
-
68
- devices[field.name] = device
69
-
70
- return dc(**devices)
71
-
72
7
 
73
8
  def setup_context(wait_for_connection: bool = True) -> BlueskyContext:
74
9
  context = BlueskyContext()
@@ -11,8 +11,8 @@ from dodal.beamlines import i03
11
11
  from dodal.devices.oav.oav_parameters import OAVConfig
12
12
  from ophyd_async.testing import set_mock_value
13
13
 
14
- from mx_bluesky.hyperion.device_setup_plans.read_hardware_for_setup import (
15
- read_hardware_during_collection,
14
+ from mx_bluesky.common.plans.read_hardware import (
15
+ standard_read_hardware_during_collection,
16
16
  )
17
17
  from mx_bluesky.hyperion.experiment_plans.rotation_scan_plan import (
18
18
  RotationScanComposite,
@@ -67,7 +67,7 @@ def fake_rotation_scan(
67
67
  }
68
68
  )
69
69
  def plan():
70
- yield from read_hardware_during_collection(
70
+ yield from standard_read_hardware_during_collection(
71
71
  rotation_devices.aperture_scatterguard,
72
72
  rotation_devices.attenuator,
73
73
  rotation_devices.flux,