mx-bluesky 1.4.7__py3-none-any.whl → 1.4.9__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 (89) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/beamlines/aithre_lasershaping/__init__.py +8 -0
  3. mx_bluesky/beamlines/aithre_lasershaping/beamline_safe.py +36 -0
  4. mx_bluesky/beamlines/aithre_lasershaping/goniometer_controls.py +43 -0
  5. mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +4 -4
  6. mx_bluesky/beamlines/i04/thawing_plan.py +8 -2
  7. mx_bluesky/beamlines/i23/__init__.py +3 -0
  8. mx_bluesky/beamlines/i23/serial.py +71 -0
  9. mx_bluesky/beamlines/i24/serial/__init__.py +2 -0
  10. mx_bluesky/beamlines/i24/serial/blueapi_config.yaml +2 -1
  11. mx_bluesky/beamlines/i24/serial/dcid.py +5 -5
  12. mx_bluesky/beamlines/i24/serial/extruder/EX-gui-edm/DetStage.edl +2 -2
  13. mx_bluesky/beamlines/i24/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +9 -9
  14. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +25 -5
  15. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DetStage.edl +2 -2
  16. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +14 -14
  17. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +5 -5
  18. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +29 -60
  19. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +7 -1
  20. mx_bluesky/beamlines/i24/serial/log.py +9 -10
  21. mx_bluesky/beamlines/i24/serial/parameters/utils.py +36 -7
  22. mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +0 -1
  23. mx_bluesky/beamlines/i24/serial/setup_beamline/pv_abstract.py +4 -4
  24. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +4 -12
  25. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +2 -1
  26. mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +71 -11
  27. mx_bluesky/beamlines/i24/serial/write_nexus.py +3 -3
  28. mx_bluesky/{hyperion → common}/device_setup_plans/manipulate_sample.py +6 -14
  29. mx_bluesky/{hyperion → common}/device_setup_plans/setup_oav.py +12 -6
  30. mx_bluesky/{hyperion → common}/experiment_plans/change_aperture_then_move_plan.py +4 -5
  31. mx_bluesky/{hyperion → common}/experiment_plans/oav_grid_detection_plan.py +6 -6
  32. mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py +6 -5
  33. mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py +16 -47
  34. mx_bluesky/common/external_interaction/ispyb/ispyb_store.py +4 -1
  35. mx_bluesky/common/external_interaction/ispyb/ispyb_utils.py +4 -4
  36. mx_bluesky/common/external_interaction/nexus/nexus_utils.py +2 -2
  37. mx_bluesky/common/parameters/components.py +22 -2
  38. mx_bluesky/common/parameters/constants.py +4 -16
  39. mx_bluesky/common/parameters/gridscan.py +36 -32
  40. mx_bluesky/common/plans/common_flyscan_xray_centre_plan.py +316 -0
  41. mx_bluesky/common/plans/inner_plans/__init__ .py +0 -0
  42. mx_bluesky/common/plans/read_hardware.py +3 -3
  43. mx_bluesky/common/utils/log.py +19 -15
  44. mx_bluesky/hyperion/__main__.py +6 -24
  45. mx_bluesky/hyperion/baton_handler.py +8 -3
  46. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +4 -4
  47. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +0 -33
  48. mx_bluesky/hyperion/device_setup_plans/smargon.py +2 -7
  49. mx_bluesky/hyperion/device_setup_plans/utils.py +6 -5
  50. mx_bluesky/hyperion/experiment_plans/__init__.py +1 -7
  51. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +3 -13
  52. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +80 -87
  53. mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +183 -0
  54. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +50 -15
  55. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +31 -7
  56. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +4 -4
  57. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +1 -1
  58. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +13 -14
  59. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +9 -8
  60. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +30 -71
  61. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +2 -2
  62. mx_bluesky/hyperion/external_interaction/agamemnon.py +78 -80
  63. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +8 -6
  64. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +3 -3
  65. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +6 -3
  66. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +2 -2
  67. mx_bluesky/hyperion/external_interaction/callbacks/snapshot_callback.py +183 -31
  68. mx_bluesky/hyperion/external_interaction/config_server.py +4 -1
  69. mx_bluesky/hyperion/parameters/cli.py +4 -19
  70. mx_bluesky/hyperion/parameters/constants.py +1 -5
  71. mx_bluesky/hyperion/parameters/device_composites.py +40 -5
  72. mx_bluesky/hyperion/parameters/gridscan.py +9 -58
  73. mx_bluesky/hyperion/parameters/load_centre_collect.py +4 -4
  74. mx_bluesky/hyperion/parameters/rotation.py +9 -12
  75. mx_bluesky/hyperion/utils/context.py +2 -2
  76. mx_bluesky/hyperion/utils/validation.py +15 -19
  77. {mx_bluesky-1.4.7.dist-info → mx_bluesky-1.4.9.dist-info}/METADATA +7 -6
  78. {mx_bluesky-1.4.7.dist-info → mx_bluesky-1.4.9.dist-info}/RECORD +86 -83
  79. {mx_bluesky-1.4.7.dist-info → mx_bluesky-1.4.9.dist-info}/WHEEL +1 -1
  80. mx_bluesky/common/external_interaction/test_config_server.py +0 -38
  81. mx_bluesky/hyperion/device_setup_plans/check_beamstop.py +0 -27
  82. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +0 -467
  83. /mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/{short1-laser.png → s1l.png} +0 -0
  84. /mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/{short2-laser.png → s2l.png} +0 -0
  85. /mx_bluesky/{hyperion → common}/device_setup_plans/position_detector.py +0 -0
  86. /mx_bluesky/common/plans/{do_fgs.py → inner_plans/do_fgs.py} +0 -0
  87. {mx_bluesky-1.4.7.dist-info → mx_bluesky-1.4.9.dist-info}/entry_points.txt +0 -0
  88. {mx_bluesky-1.4.7.dist-info → mx_bluesky-1.4.9.dist-info}/licenses/LICENSE +0 -0
  89. {mx_bluesky-1.4.7.dist-info → mx_bluesky-1.4.9.dist-info}/top_level.txt +0 -0
@@ -9,8 +9,8 @@ from dodal.devices.oav.oav_detector import OAV
9
9
  from dodal.devices.oav.oav_parameters import OAVParameters
10
10
  from dodal.devices.smargon import Smargon
11
11
 
12
+ from mx_bluesky.common.device_setup_plans.setup_oav import setup_general_oav_params
12
13
  from mx_bluesky.common.parameters.components import WithSnapshot
13
- from mx_bluesky.hyperion.device_setup_plans.setup_oav import setup_general_oav_params
14
14
  from mx_bluesky.hyperion.parameters.constants import CONST, DocDescriptorNames
15
15
 
16
16
  OAV_SNAPSHOT_SETUP_SHOT = "oav_snapshot_setup_shot"
@@ -29,13 +29,16 @@ def setup_beamline_for_OAV(
29
29
  backlight: Backlight,
30
30
  aperture_scatterguard: ApertureScatterguard,
31
31
  group=CONST.WAIT.READY_FOR_OAV,
32
+ wait=False,
32
33
  ):
33
34
  max_vel = yield from bps.rd(smargon.omega.max_velocity)
34
35
  yield from bps.abs_set(smargon.omega.velocity, max_vel, group=group)
35
36
  yield from bps.abs_set(backlight, BacklightPosition.IN, group=group)
36
37
  yield from bps.abs_set(
37
- aperture_scatterguard, ApertureValue.OUT_OF_BEAM, group=group
38
+ aperture_scatterguard.selected_aperture, ApertureValue.OUT_OF_BEAM, group=group
38
39
  )
40
+ if wait:
41
+ yield from bps.wait(group)
39
42
 
40
43
 
41
44
  def oav_snapshot_plan(
@@ -45,9 +48,12 @@ def oav_snapshot_plan(
45
48
  ) -> MsgGenerator:
46
49
  if not parameters.take_snapshots:
47
50
  return
48
- yield from _setup_oav(composite, parameters, oav_parameters)
49
- for omega in parameters.snapshot_omegas_deg or []:
50
- yield from _take_oav_snapshot(composite, omega)
51
+ if parameters.use_grid_snapshots:
52
+ yield from _generate_oav_snapshots(composite, parameters)
53
+ else:
54
+ yield from _setup_oav(composite, parameters, oav_parameters)
55
+ for omega in parameters.snapshot_omegas_deg or []:
56
+ yield from _take_oav_snapshot(composite, omega)
51
57
 
52
58
 
53
59
  def _setup_oav(
@@ -62,12 +68,24 @@ def _setup_oav(
62
68
  )
63
69
 
64
70
 
71
+ def _generate_oav_snapshots(composite: OavSnapshotComposite, params: WithSnapshot):
72
+ """Generate rotation snapshots from previously captured grid snapshots"""
73
+ yield from bps.abs_set(
74
+ composite.oav.snapshot.directory, str(params.snapshot_directory)
75
+ )
76
+ for _ in 0, 270:
77
+ yield from bps.create(DocDescriptorNames.OAV_ROTATION_SNAPSHOT_TRIGGERED)
78
+ yield from bps.read(composite.oav)
79
+ yield from bps.read(composite.smargon)
80
+ yield from bps.save()
81
+
82
+
65
83
  def _take_oav_snapshot(composite: OavSnapshotComposite, omega: float):
84
+ """Create new snapshots by triggering the OAV"""
66
85
  yield from bps.abs_set(
67
86
  composite.smargon.omega, omega, group=OAV_SNAPSHOT_SETUP_SHOT
68
87
  )
69
- time_now = datetime.now()
70
- filename = f"{time_now.strftime('%H%M%S%f')[:8]}_oav_snapshot_{omega:.0f}"
88
+ filename = _snapshot_filename(omega)
71
89
  yield from bps.abs_set(
72
90
  composite.oav.snapshot.filename,
73
91
  filename,
@@ -78,3 +96,9 @@ def _take_oav_snapshot(composite: OavSnapshotComposite, omega: float):
78
96
  yield from bps.create(DocDescriptorNames.OAV_ROTATION_SNAPSHOT_TRIGGERED)
79
97
  yield from bps.read(composite.oav)
80
98
  yield from bps.save()
99
+
100
+
101
+ def _snapshot_filename(omega):
102
+ time_now = datetime.now()
103
+ filename = f"{time_now.strftime('%H%M%S%f')[:8]}_oav_snapshot_{omega:.0f}"
104
+ return filename
@@ -8,6 +8,10 @@ from bluesky.utils import MsgGenerator
8
8
  from dodal.devices.eiger import EigerDetector
9
9
  from dodal.devices.oav.oav_parameters import OAVParameters
10
10
 
11
+ from mx_bluesky.common.device_setup_plans.manipulate_sample import move_phi_chi_omega
12
+ from mx_bluesky.common.experiment_plans.change_aperture_then_move_plan import (
13
+ change_aperture_then_move_to_xtal,
14
+ )
11
15
  from mx_bluesky.common.external_interaction.callbacks.xray_centre.ispyb_callback import (
12
16
  ispyb_activation_wrapper,
13
17
  )
@@ -15,13 +19,9 @@ from mx_bluesky.common.parameters.constants import OavConstants
15
19
  from mx_bluesky.common.utils.context import device_composite_from_context
16
20
  from mx_bluesky.common.utils.log import LOGGER
17
21
  from mx_bluesky.common.xrc_result import XRayCentreEventHandler
18
- from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import move_phi_chi_omega
19
22
  from mx_bluesky.hyperion.device_setup_plans.utils import (
20
23
  start_preparing_data_collection_then_do_plan,
21
24
  )
22
- from mx_bluesky.hyperion.experiment_plans.change_aperture_then_move_plan import (
23
- change_aperture_then_move_to_xtal,
24
- )
25
25
  from mx_bluesky.hyperion.experiment_plans.grid_detect_then_xray_centre_plan import (
26
26
  GridDetectThenXRayCentreComposite,
27
27
  detect_grid_and_do_gridscan,
@@ -16,10 +16,10 @@ from dodal.devices.oav.utils import (
16
16
  )
17
17
  from dodal.devices.smargon import Smargon
18
18
 
19
+ from mx_bluesky.common.device_setup_plans.setup_oav import pre_centring_setup_oav
19
20
  from mx_bluesky.common.utils.context import device_composite_from_context
20
21
  from mx_bluesky.common.utils.exceptions import SampleException, catch_exception_and_warn
21
22
  from mx_bluesky.common.utils.log import LOGGER
22
- from mx_bluesky.hyperion.device_setup_plans.setup_oav import pre_centring_setup_oav
23
23
  from mx_bluesky.hyperion.device_setup_plans.smargon import (
24
24
  move_smargon_warn_on_out_of_range,
25
25
  )
@@ -12,14 +12,15 @@ from blueapi.core import BlueskyContext
12
12
  from bluesky.utils import Msg
13
13
  from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue
14
14
  from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
15
- from dodal.devices.dcm import DCM
15
+ from dodal.devices.backlight import Backlight, BacklightPosition
16
16
  from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, MirrorVoltages
17
+ from dodal.devices.i03.dcm import DCM
18
+ from dodal.devices.i03.undulator_dcm import UndulatorDCM
17
19
  from dodal.devices.motors import XYZPositioner
18
20
  from dodal.devices.oav.oav_detector import OAV
19
21
  from dodal.devices.robot import BartRobot, SampleLocation
20
- from dodal.devices.smargon import Smargon, StubPosition
22
+ from dodal.devices.smargon import CombinedMove, Smargon, StubPosition
21
23
  from dodal.devices.thawer import Thawer
22
- from dodal.devices.undulator_dcm import UndulatorDCM
23
24
  from dodal.devices.webcam import Webcam
24
25
  from dodal.devices.xbpm_feedback import XBPMFeedback
25
26
  from dodal.plan_stubs.motor_utils import MoveTooLarge, home_and_reset_wrapper
@@ -51,6 +52,7 @@ class RobotLoadAndEnergyChangeComposite:
51
52
  oav: OAV
52
53
  smargon: Smargon
53
54
  aperture_scatterguard: ApertureScatterguard
55
+ backlight: Backlight
54
56
 
55
57
 
56
58
  def create_devices(context: BlueskyContext) -> RobotLoadAndEnergyChangeComposite:
@@ -95,21 +97,14 @@ def prepare_for_robot_load(
95
97
  aperture_scatterguard: ApertureScatterguard, smargon: Smargon
96
98
  ):
97
99
  yield from bps.abs_set(
98
- aperture_scatterguard, ApertureValue.OUT_OF_BEAM, group="prepare_robot_load"
100
+ aperture_scatterguard.selected_aperture,
101
+ ApertureValue.OUT_OF_BEAM,
102
+ group="prepare_robot_load",
99
103
  )
100
104
 
101
105
  yield from bps.mv(smargon.stub_offsets, StubPosition.RESET_TO_ROBOT_LOAD)
102
106
 
103
- # fmt: off
104
- yield from bps.mv(
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
- )
112
- # fmt: on
107
+ yield from bps.mv(smargon, CombinedMove(x=0, y=0, z=0, chi=0, phi=0, omega=0))
113
108
 
114
109
  yield from bps.wait("prepare_robot_load")
115
110
 
@@ -166,6 +161,8 @@ def robot_load_and_snapshots(
166
161
  thawing_time: float,
167
162
  demand_energy_ev: float | None,
168
163
  ):
164
+ yield from bps.abs_set(composite.backlight, BacklightPosition.IN, group="snapshot")
165
+
169
166
  robot_load_plan = do_robot_load(
170
167
  composite,
171
168
  location,
@@ -189,6 +186,8 @@ def robot_load_and_snapshots(
189
186
  except_plan=raise_exception_if_moved_out_of_cryojet,
190
187
  )
191
188
 
189
+ yield from bps.wait(group="snapshot")
190
+
192
191
  yield from take_robot_snapshots(composite.oav, composite.webcam, snapshot_directory)
193
192
 
194
193
  yield from bps.create(name=CONST.DESCRIPTORS.ROBOT_LOAD)
@@ -10,13 +10,14 @@ from bluesky.utils import MsgGenerator
10
10
  from dodal.devices.aperturescatterguard import ApertureScatterguard
11
11
  from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
12
12
  from dodal.devices.backlight import Backlight
13
- from dodal.devices.dcm import DCM
14
13
  from dodal.devices.detector.detector_motion import DetectorMotion
15
14
  from dodal.devices.eiger import EigerDetector
16
15
  from dodal.devices.fast_grid_scan import PandAFastGridScan, ZebraFastGridScan
17
16
  from dodal.devices.flux import Flux
18
17
  from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, MirrorVoltages
19
- from dodal.devices.i03.beamstop import Beamstop
18
+ from dodal.devices.i03 import Beamstop
19
+ from dodal.devices.i03.dcm import DCM
20
+ from dodal.devices.i03.undulator_dcm import UndulatorDCM
20
21
  from dodal.devices.motors import XYZPositioner
21
22
  from dodal.devices.oav.oav_detector import OAV
22
23
  from dodal.devices.oav.pin_image_recognition import PinTipDetection
@@ -26,7 +27,6 @@ from dodal.devices.smargon import Smargon
26
27
  from dodal.devices.synchrotron import Synchrotron
27
28
  from dodal.devices.thawer import Thawer
28
29
  from dodal.devices.undulator import Undulator
29
- from dodal.devices.undulator_dcm import UndulatorDCM
30
30
  from dodal.devices.webcam import Webcam
31
31
  from dodal.devices.xbpm_feedback import XBPMFeedback
32
32
  from dodal.devices.zebra.zebra import Zebra
@@ -112,6 +112,7 @@ def _flyscan_plan_from_robot_load_params(
112
112
  yield from pin_centre_then_flyscan_plan(
113
113
  cast(GridDetectThenXRayCentreComposite, composite),
114
114
  params.pin_centre_then_xray_centre_params,
115
+ oav_config_file,
115
116
  )
116
117
 
117
118
 
@@ -131,6 +132,7 @@ def _robot_load_then_flyscan_plan(
131
132
  def robot_load_then_xray_centre(
132
133
  composite: RobotLoadThenCentreComposite,
133
134
  parameters: RobotLoadThenCentre,
135
+ oav_config_file: str = OavConstants.OAV_CONFIG_JSON,
134
136
  ) -> MsgGenerator:
135
137
  """Perform pin-tip detection followed by a flyscan to determine centres of interest.
136
138
  Performs a robot load if necessary."""
@@ -154,10 +156,7 @@ def robot_load_then_xray_centre(
154
156
 
155
157
  if doing_sample_load:
156
158
  LOGGER.info("Pin not loaded, loading and centring")
157
- plan = _robot_load_then_flyscan_plan(
158
- composite,
159
- parameters,
160
- )
159
+ plan = _robot_load_then_flyscan_plan(composite, parameters, oav_config_file)
161
160
  else:
162
161
  # Robot load normally sets the energy so we should do this explicitly if no load is
163
162
  # being done
@@ -168,7 +167,9 @@ def robot_load_then_xray_centre(
168
167
  )
169
168
 
170
169
  if doing_chi_change:
171
- plan = _flyscan_plan_from_robot_load_params(composite, parameters)
170
+ plan = _flyscan_plan_from_robot_load_params(
171
+ composite, parameters, oav_config_file
172
+ )
172
173
  LOGGER.info("Pin already loaded but chi changed so centring")
173
174
  else:
174
175
  LOGGER.info("Pin already loaded and chi not changed so doing nothing")
@@ -10,16 +10,16 @@ from bluesky.utils import MsgGenerator
10
10
  from dodal.devices.aperturescatterguard import ApertureScatterguard
11
11
  from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
12
12
  from dodal.devices.backlight import Backlight
13
- from dodal.devices.dcm import DCM
14
13
  from dodal.devices.detector.detector_motion import DetectorMotion
15
14
  from dodal.devices.eiger import EigerDetector
16
15
  from dodal.devices.flux import Flux
17
- from dodal.devices.i03.beamstop import Beamstop
16
+ from dodal.devices.i03 import Beamstop
17
+ from dodal.devices.i03.dcm import DCM
18
18
  from dodal.devices.oav.oav_detector import OAV
19
19
  from dodal.devices.oav.oav_parameters import OAVParameters
20
20
  from dodal.devices.robot import BartRobot
21
21
  from dodal.devices.s4_slit_gaps import S4SlitGaps
22
- from dodal.devices.smargon import Smargon
22
+ from dodal.devices.smargon import CombinedMove, Smargon
23
23
  from dodal.devices.synchrotron import Synchrotron
24
24
  from dodal.devices.undulator import Undulator
25
25
  from dodal.devices.xbpm_feedback import XBPMFeedback
@@ -30,6 +30,10 @@ from dodal.plans.preprocessors.verify_undulator_gap import (
30
30
  verify_undulator_gap_before_run_decorator,
31
31
  )
32
32
 
33
+ from mx_bluesky.common.device_setup_plans.manipulate_sample import (
34
+ cleanup_sample_environment,
35
+ setup_sample_environment,
36
+ )
33
37
  from mx_bluesky.common.parameters.components import WithSnapshot
34
38
  from mx_bluesky.common.plans.read_hardware import (
35
39
  read_hardware_for_zocalo,
@@ -41,12 +45,6 @@ from mx_bluesky.common.preprocessors.preprocessors import (
41
45
  )
42
46
  from mx_bluesky.common.utils.context import device_composite_from_context
43
47
  from mx_bluesky.common.utils.log import LOGGER
44
- from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import (
45
- cleanup_sample_environment,
46
- move_phi_chi_omega,
47
- move_x_y_z,
48
- setup_sample_environment,
49
- )
50
48
  from mx_bluesky.hyperion.device_setup_plans.setup_zebra import (
51
49
  arm_zebra,
52
50
  setup_zebra_for_rotation,
@@ -62,8 +60,8 @@ from mx_bluesky.hyperion.experiment_plans.oav_snapshot_plan import (
62
60
  )
63
61
  from mx_bluesky.hyperion.parameters.constants import CONST
64
62
  from mx_bluesky.hyperion.parameters.rotation import (
65
- MultiRotationScan,
66
63
  RotationScan,
64
+ SingleRotationScan,
67
65
  )
68
66
 
69
67
 
@@ -118,7 +116,7 @@ class RotationMotionProfile:
118
116
 
119
117
 
120
118
  def calculate_motion_profile(
121
- params: RotationScan,
119
+ params: SingleRotationScan,
122
120
  motor_time_to_speed_s: float,
123
121
  max_velocity_deg_s: float,
124
122
  ) -> RotationMotionProfile:
@@ -212,7 +210,7 @@ def calculate_motion_profile(
212
210
 
213
211
  def rotation_scan_plan(
214
212
  composite: RotationScanComposite,
215
- params: RotationScan,
213
+ params: SingleRotationScan,
216
214
  motion_values: RotationMotionProfile,
217
215
  ):
218
216
  """A stub plan to collect diffraction images from a sample continuously rotating
@@ -319,7 +317,7 @@ def _cleanup_plan(composite: RotationScanComposite, **kwargs):
319
317
 
320
318
  def _move_and_rotation(
321
319
  composite: RotationScanComposite,
322
- params: RotationScan,
320
+ params: SingleRotationScan,
323
321
  oav_params: OAVParameters,
324
322
  ):
325
323
  motor_time_to_speed = yield from bps.rd(composite.smargon.omega.acceleration_time)
@@ -330,19 +328,18 @@ def _move_and_rotation(
330
328
  return num / 1000 if num else num
331
329
 
332
330
  LOGGER.info("moving to position (if specified)")
333
- yield from move_x_y_z(
334
- composite.smargon,
335
- _div_by_1000_if_not_none(params.x_start_um),
336
- _div_by_1000_if_not_none(params.y_start_um),
337
- _div_by_1000_if_not_none(params.z_start_um),
338
- group=CONST.WAIT.MOVE_GONIO_TO_START,
339
- )
340
- yield from move_phi_chi_omega(
331
+ yield from bps.abs_set(
341
332
  composite.smargon,
342
- params.phi_start_deg,
343
- params.chi_start_deg,
333
+ CombinedMove(
334
+ x=_div_by_1000_if_not_none(params.x_start_um),
335
+ y=_div_by_1000_if_not_none(params.y_start_um),
336
+ z=_div_by_1000_if_not_none(params.z_start_um),
337
+ phi=params.phi_start_deg,
338
+ chi=params.chi_start_deg,
339
+ ),
344
340
  group=CONST.WAIT.MOVE_GONIO_TO_START,
345
341
  )
342
+
346
343
  if params.take_snapshots:
347
344
  yield from bps.wait(CONST.WAIT.MOVE_GONIO_TO_START)
348
345
  yield from setup_beamline_for_OAV(
@@ -356,11 +353,7 @@ def _move_and_rotation(
356
353
  group=CONST.WAIT.ROTATION_READY_FOR_DC,
357
354
  )
358
355
  yield from oav_snapshot_plan(composite, params, oav_params)
359
- yield from rotation_scan_plan(
360
- composite,
361
- params,
362
- motion_values,
363
- )
356
+ yield from rotation_scan_plan(composite, params, motion_values)
364
357
 
365
358
 
366
359
  def rotation_scan(
@@ -368,58 +361,24 @@ def rotation_scan(
368
361
  parameters: RotationScan,
369
362
  oav_params: OAVParameters | None = None,
370
363
  ) -> MsgGenerator:
371
- parameters.features.update_self_from_server()
372
-
373
- if not oav_params:
374
- oav_params = OAVParameters(context="xrayCentring")
375
-
376
- @transmission_and_xbpm_feedback_for_collection_decorator(
377
- composite,
378
- parameters.transmission_frac,
379
- )
380
- @verify_undulator_gap_before_run_decorator(composite)
381
- @bpp.set_run_key_decorator("rotation_scan")
382
- @bpp.run_decorator( # attach experiment metadata to the start document
364
+ @bpp.set_run_key_decorator(CONST.PLAN.ROTATION_MULTI_OUTER)
365
+ @bpp.run_decorator(
383
366
  md={
384
- "subplan_name": CONST.PLAN.ROTATION_OUTER,
385
- "mx_bluesky_parameters": parameters.model_dump_json(),
367
+ "activate_callbacks": ["BeamDrawingCallback"],
386
368
  "with_snapshot": parameters.model_dump_json(
387
369
  include=WithSnapshot.model_fields.keys() # type: ignore
388
370
  ),
389
- "activate_callbacks": [
390
- "BeamDrawingCallback",
391
- "RotationISPyBCallback",
392
- "RotationNexusFileCallback",
393
- ],
394
371
  }
395
372
  )
396
- def rotation_scan_plan_with_stage_and_cleanup(
397
- params: RotationScan,
398
- ):
399
- eiger: EigerDetector = composite.eiger
400
- eiger.set_detector_parameters(params.detector_params)
401
-
402
- @bpp.finalize_decorator(lambda: _cleanup_plan(composite))
403
- def rotation_with_cleanup_and_stage(params: RotationScan):
404
- yield from _move_and_rotation(composite, params, oav_params)
405
-
406
- LOGGER.info("setting up and staging eiger...")
407
- yield from start_preparing_data_collection_then_do_plan(
408
- composite.beamstop,
409
- eiger,
410
- composite.detector_motion,
411
- params.detector_distance_mm,
412
- rotation_with_cleanup_and_stage(params),
413
- group=CONST.WAIT.ROTATION_READY_FOR_DC,
414
- )
415
- yield from bps.unstage(eiger)
373
+ def _wrapped_rotation_scan():
374
+ yield from rotation_scan_internal(composite, parameters, oav_params)
416
375
 
417
- yield from rotation_scan_plan_with_stage_and_cleanup(parameters)
376
+ yield from _wrapped_rotation_scan()
418
377
 
419
378
 
420
- def multi_rotation_scan(
379
+ def rotation_scan_internal(
421
380
  composite: RotationScanComposite,
422
- parameters: MultiRotationScan,
381
+ parameters: RotationScan,
423
382
  oav_params: OAVParameters | None = None,
424
383
  ) -> MsgGenerator:
425
384
  parameters.features.update_self_from_server()
@@ -457,7 +416,7 @@ def multi_rotation_scan(
457
416
  }
458
417
  )
459
418
  def rotation_scan_core(
460
- params: RotationScan,
419
+ params: SingleRotationScan,
461
420
  ):
462
421
  yield from _move_and_rotation(composite, params, oav_params)
463
422
 
@@ -9,10 +9,10 @@ import bluesky.preprocessors as bpp
9
9
  import pydantic
10
10
  from bluesky import plan_stubs as bps
11
11
  from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
12
- from dodal.devices.dcm import DCM
13
12
  from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, MirrorVoltages
13
+ from dodal.devices.i03.dcm import DCM
14
+ from dodal.devices.i03.undulator_dcm import UndulatorDCM
14
15
  from dodal.devices.undulator import Undulator
15
- from dodal.devices.undulator_dcm import UndulatorDCM
16
16
  from dodal.devices.xbpm_feedback import XBPMFeedback
17
17
 
18
18
  from mx_bluesky.common.parameters.constants import PlanNameConstants
@@ -1,6 +1,8 @@
1
1
  import dataclasses
2
2
  import json
3
3
  import re
4
+ import traceback
5
+ from collections.abc import Sequence
4
6
  from os import path
5
7
  from typing import Any, TypeVar
6
8
 
@@ -12,11 +14,6 @@ from pydantic_extra_types.semantic_version import SemanticVersion
12
14
 
13
15
  from mx_bluesky.common.parameters.components import (
14
16
  PARAMETER_VERSION,
15
- MxBlueskyParameters,
16
- TopNByMaxCountSelection,
17
- WithCentreSelection,
18
- WithOptionalEnergyChange,
19
- WithSample,
20
17
  WithVisit,
21
18
  )
22
19
  from mx_bluesky.common.parameters.constants import (
@@ -24,9 +21,7 @@ from mx_bluesky.common.parameters.constants import (
24
21
  )
25
22
  from mx_bluesky.common.utils.log import LOGGER
26
23
  from mx_bluesky.common.utils.utils import convert_angstrom_to_eV
27
- from mx_bluesky.hyperion.parameters.components import WithHyperionUDCFeatures
28
24
  from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
29
- from mx_bluesky.hyperion.parameters.robot_load import RobotLoadThenCentre
30
25
 
31
26
  T = TypeVar("T", bound=WithVisit)
32
27
  AGAMEMNON_URL = "http://agamemnon.diamond.ac.uk/"
@@ -36,19 +31,6 @@ MULTIPIN_REGEX = rf"^{MULTIPIN_PREFIX}_(\d+)x(\d+(?:\.\d+)?)\+(\d+(?:\.\d+)?)$"
36
31
  MX_GENERAL_ROOT_REGEX = r"^/dls/(?P<beamline>[^/]+)/data/[^/]*/(?P<visit>[^/]+)(?:/|$)"
37
32
 
38
33
 
39
- class AgamemnonLoadCentreCollect(
40
- MxBlueskyParameters,
41
- WithVisit,
42
- WithSample,
43
- WithCentreSelection,
44
- WithHyperionUDCFeatures,
45
- WithOptionalEnergyChange,
46
- ):
47
- """Experiment parameters to compare against GDA populated LoadCentreCollect."""
48
-
49
- robot_load_then_centre: RobotLoadThenCentre
50
-
51
-
52
34
  @dataclasses.dataclass
53
35
  class PinType:
54
36
  expected_number_of_crystals: int
@@ -131,15 +113,6 @@ def get_withvisit_parameters_from_agamemnon(parameters: dict) -> tuple:
131
113
  )
132
114
 
133
115
 
134
- def get_withsample_parameters_from_agamemnon(parameters: dict) -> dict[str, Any]:
135
- assert parameters.get("sample"), "instruction does not have a sample"
136
- return {
137
- "sample_id": parameters["sample"]["id"],
138
- "sample_puck": parameters["sample"]["container"],
139
- "sample_pin": parameters["sample"]["position"],
140
- }
141
-
142
-
143
116
  def get_withenergy_parameters_from_agamemnon(parameters: dict) -> dict[str, Any]:
144
117
  try:
145
118
  first_collection: dict = parameters["collection"][0]
@@ -155,69 +128,94 @@ def get_param_version() -> SemanticVersion:
155
128
  return SemanticVersion.validate_from_str(str(PARAMETER_VERSION))
156
129
 
157
130
 
158
- def create_robot_load_then_centre_params_from_agamemnon(
159
- parameters: dict,
160
- ) -> RobotLoadThenCentre:
161
- visit, detector_distance = get_withvisit_parameters_from_agamemnon(parameters)
162
- with_sample_params = get_withsample_parameters_from_agamemnon(parameters)
163
- with_energy_params = get_withenergy_parameters_from_agamemnon(parameters)
164
- visit_directory, file_name = path.split(parameters["prefix"])
165
- return RobotLoadThenCentre(
166
- parameter_model_version=get_param_version(),
167
- storage_directory=visit_directory + "/xraycentring",
168
- visit=visit,
169
- detector_distance_mm=detector_distance,
170
- snapshot_directory=visit_directory + "/snapshots",
171
- file_name=file_name,
172
- **with_energy_params,
173
- **with_sample_params,
174
- )
175
-
176
-
177
- def populate_parameters_from_agamemnon(agamemnon_params):
131
+ def populate_parameters_from_agamemnon(agamemnon_params) -> Sequence[LoadCentreCollect]:
178
132
  visit, detector_distance = get_withvisit_parameters_from_agamemnon(agamemnon_params)
179
- with_sample_params = get_withsample_parameters_from_agamemnon(agamemnon_params)
180
133
  with_energy_params = get_withenergy_parameters_from_agamemnon(agamemnon_params)
181
134
  pin_type = get_pin_type_from_agamemnon_parameters(agamemnon_params)
182
- robot_load_params = create_robot_load_then_centre_params_from_agamemnon(
183
- agamemnon_params
184
- )
185
- return AgamemnonLoadCentreCollect(
186
- parameter_model_version=SemanticVersion.validate_from_str(
187
- str(PARAMETER_VERSION)
188
- ),
189
- visit=visit,
190
- detector_distance_mm=detector_distance,
191
- select_centres=TopNByMaxCountSelection(n=pin_type.expected_number_of_crystals),
192
- robot_load_then_centre=robot_load_params,
193
- **with_sample_params,
194
- **with_energy_params,
195
- )
135
+ collections = agamemnon_params["collection"]
136
+ visit_directory, file_name = path.split(agamemnon_params["prefix"])
137
+
138
+ return [
139
+ LoadCentreCollect.model_validate(
140
+ {
141
+ "parameter_model_version": get_param_version(),
142
+ "visit": visit,
143
+ "detector_distance_mm": detector_distance,
144
+ "sample_id": agamemnon_params["sample"]["id"],
145
+ "sample_puck": agamemnon_params["sample"]["container"],
146
+ "sample_pin": agamemnon_params["sample"]["position"],
147
+ "select_centres": {
148
+ "name": "TopNByMaxCount",
149
+ "n": pin_type.expected_number_of_crystals,
150
+ },
151
+ "robot_load_then_centre": {
152
+ "storage_directory": str(visit_directory) + "/xraycentring",
153
+ "file_name": file_name,
154
+ "tip_offset_um": pin_type.full_width / 2,
155
+ "grid_width_um": pin_type.full_width,
156
+ "omega_start_deg": 0.0,
157
+ "chi_start_deg": collection["chi"],
158
+ "transmission_frac": 1.0,
159
+ "features": {"use_gpu_results": True},
160
+ **with_energy_params,
161
+ },
162
+ "multi_rotation_scan": {
163
+ "comment": collection["comment"],
164
+ "storage_directory": str(visit_directory),
165
+ "exposure_time_s": collection["exposure_time"],
166
+ "file_name": file_name,
167
+ "transmission_frac": collection["transmission"],
168
+ "rotation_increment_deg": collection["omega_increment"],
169
+ "ispyb_experiment_type": collection["experiment_type"],
170
+ "snapshot_omegas_deg": [0.0, 90.0, 180.0, 270.0],
171
+ "rotation_scans": [
172
+ {
173
+ "scan_width_deg": (
174
+ collection["number_of_images"]
175
+ * collection["omega_increment"]
176
+ ),
177
+ "omega_start_deg": collection["omega_start"],
178
+ "phi_start_deg": collection["phi_start"],
179
+ "chi_start_deg": collection["chi"],
180
+ "rotation_direction": "Positive",
181
+ }
182
+ ],
183
+ **with_energy_params,
184
+ },
185
+ }
186
+ )
187
+ for collection in collections
188
+ ]
196
189
 
197
190
 
198
- def create_parameters_from_agamemnon() -> AgamemnonLoadCentreCollect:
191
+ def create_parameters_from_agamemnon() -> Sequence[LoadCentreCollect]:
199
192
  beamline_name = get_beamline_name("i03")
200
193
  agamemnon_params = get_next_instruction(beamline_name)
201
-
202
- return populate_parameters_from_agamemnon(agamemnon_params)
194
+ return (
195
+ populate_parameters_from_agamemnon(agamemnon_params) if agamemnon_params else []
196
+ )
203
197
 
204
198
 
205
- def compare_params(load_centre_collect_params):
199
+ def compare_params(load_centre_collect_params: LoadCentreCollect):
206
200
  try:
207
- parameters = create_parameters_from_agamemnon()
208
-
201
+ lcc_requests = create_parameters_from_agamemnon()
209
202
  # Log differences against GDA populated parameters
210
- differences = DeepDiff(
211
- parameters, load_centre_collect_params, math_epsilon=1e-5
212
- )
213
- if differences:
214
- LOGGER.info(
215
- f"Different parameters found when directly reading from Hyperion: {differences}"
203
+ if not lcc_requests:
204
+ LOGGER.info("Agamemnon returned no instructions")
205
+ else:
206
+ differences = DeepDiff(
207
+ lcc_requests[0], load_centre_collect_params, math_epsilon=1e-5
216
208
  )
217
- except (ValueError, KeyError) as e:
218
- LOGGER.warning(f"Failed to compare parameters: {e}")
219
- except Exception as e:
220
- LOGGER.warning(f"Unexpected error occurred. Failed to compare parameters: {e}")
209
+ if differences:
210
+ LOGGER.info(
211
+ f"Different parameters found when directly reading from Hyperion: {differences}"
212
+ )
213
+ except (ValueError, KeyError):
214
+ LOGGER.warning(f"Failed to compare parameters: {traceback.format_exc()}")
215
+ except Exception:
216
+ LOGGER.warning(
217
+ f"Unexpected error occurred. Failed to compare parameters: {traceback.format_exc()}"
218
+ )
221
219
 
222
220
 
223
221
  def update_params_from_agamemnon(parameters: T) -> T: