mx-bluesky 1.1.0__py3-none-any.whl → 1.2.0__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 (23) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/hyperion/device_setup_plans/utils.py +11 -0
  3. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +9 -0
  4. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +47 -38
  5. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +46 -0
  6. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +237 -0
  7. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +18 -178
  8. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +0 -8
  9. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +18 -0
  10. mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +0 -8
  11. mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +3 -3
  12. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +2 -4
  13. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_utils.py +0 -2
  14. mx_bluesky/hyperion/parameters/components.py +19 -18
  15. mx_bluesky/hyperion/parameters/gridscan.py +7 -2
  16. mx_bluesky/hyperion/parameters/load_centre_collect.py +50 -0
  17. mx_bluesky/hyperion/parameters/robot_load.py +16 -0
  18. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.2.0.dist-info}/METADATA +2 -2
  19. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.2.0.dist-info}/RECORD +23 -19
  20. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.2.0.dist-info}/LICENSE +0 -0
  21. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.2.0.dist-info}/WHEEL +0 -0
  22. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.2.0.dist-info}/entry_points.txt +0 -0
  23. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.2.0.dist-info}/top_level.txt +0 -0
mx_bluesky/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.1.0'
16
- __version_tuple__ = version_tuple = (1, 1, 0)
15
+ __version__ = version = '1.2.0'
16
+ __version_tuple__ = version_tuple = (1, 2, 0)
@@ -3,6 +3,10 @@ from collections.abc import Generator
3
3
  from bluesky import plan_stubs as bps
4
4
  from bluesky import preprocessors as bpp
5
5
  from bluesky.utils import Msg
6
+ from dodal.devices.dcm import DCM
7
+ from dodal.devices.detector import (
8
+ DetectorParams,
9
+ )
6
10
  from dodal.devices.detector.detector_motion import DetectorMotion, ShutterState
7
11
  from dodal.devices.eiger import EigerDetector
8
12
 
@@ -12,6 +16,13 @@ from mx_bluesky.hyperion.device_setup_plans.position_detector import (
12
16
  )
13
17
 
14
18
 
19
+ def fill_in_energy_if_not_supplied(dcm: DCM, detector_params: DetectorParams):
20
+ if not detector_params.expected_energy_ev:
21
+ actual_energy_ev = 1000 * (yield from bps.rd(dcm.energy_in_kev))
22
+ detector_params.expected_energy_ev = actual_energy_ev
23
+ return detector_params
24
+
25
+
15
26
  def start_preparing_data_collection_then_do_plan(
16
27
  eiger: EigerDetector,
17
28
  detector_motion: DetectorMotion,
@@ -7,12 +7,14 @@ import mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan as flyscan_
7
7
  import mx_bluesky.hyperion.experiment_plans.rotation_scan_plan as rotation_scan_plan
8
8
  from mx_bluesky.hyperion.experiment_plans import (
9
9
  grid_detect_then_xray_centre_plan,
10
+ load_centre_collect_full_plan,
10
11
  pin_centre_then_xray_centre_plan,
11
12
  robot_load_then_centre_plan,
12
13
  )
13
14
  from mx_bluesky.hyperion.external_interaction.callbacks.common.callback_util import (
14
15
  CallbacksFactory,
15
16
  create_gridscan_callbacks,
17
+ create_load_centre_collect_callbacks,
16
18
  create_robot_load_and_centre_callbacks,
17
19
  create_rotation_callbacks,
18
20
  )
@@ -22,6 +24,7 @@ from mx_bluesky.hyperion.parameters.gridscan import (
22
24
  RobotLoadThenCentre,
23
25
  ThreeDGridScan,
24
26
  )
27
+ from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
25
28
  from mx_bluesky.hyperion.parameters.rotation import MultiRotationScan, RotationScan
26
29
 
27
30
 
@@ -42,6 +45,7 @@ class ExperimentRegistryEntry(TypedDict):
42
45
  | MultiRotationScan
43
46
  | PinTipCentreThenXrayCentre
44
47
  | RobotLoadThenCentre
48
+ | LoadCentreCollect
45
49
  ]
46
50
  callbacks_factory: CallbacksFactory
47
51
 
@@ -77,6 +81,11 @@ PLAN_REGISTRY: dict[str, ExperimentRegistryEntry] = {
77
81
  "param_type": MultiRotationScan,
78
82
  "callbacks_factory": create_rotation_callbacks,
79
83
  },
84
+ "load_centre_collect_full_plan": {
85
+ "setup": load_centre_collect_full_plan.create_devices,
86
+ "param_type": LoadCentreCollect,
87
+ "callbacks_factory": create_load_centre_collect_callbacks,
88
+ },
80
89
  }
81
90
 
82
91
 
@@ -77,6 +77,12 @@ class SmargonSpeedException(Exception):
77
77
  pass
78
78
 
79
79
 
80
+ class CrystalNotFoundException(WarningException):
81
+ """Raised if grid detection completed normally but no crystal was found."""
82
+
83
+ pass
84
+
85
+
80
86
  @dataclasses.dataclass
81
87
  class FlyScanXRayCentreComposite:
82
88
  """All devices which are directly or indirectly required by this plan"""
@@ -190,47 +196,50 @@ def run_gridscan_and_move(
190
196
 
191
197
  LOGGER.info("Grid scan finished, getting results.")
192
198
 
193
- with TRACER.start_span("wait_for_zocalo"):
194
- yield from bps.trigger_and_read(
195
- [fgs_composite.zocalo], name=ZOCALO_READING_PLAN_NAME
196
- )
197
- LOGGER.info("Zocalo triggered and read, interpreting results.")
198
- xray_centre, bbox_size = yield from get_processing_result(fgs_composite.zocalo)
199
- LOGGER.info(f"Got xray centre: {xray_centre}, bbox size: {bbox_size}")
200
- if xray_centre is not None:
201
- xray_centre = parameters.FGS_params.grid_position_to_motor_position(
202
- xray_centre
199
+ try:
200
+ with TRACER.start_span("wait_for_zocalo"):
201
+ yield from bps.trigger_and_read(
202
+ [fgs_composite.zocalo], name=ZOCALO_READING_PLAN_NAME
203
203
  )
204
- else:
205
- xray_centre = initial_xyz
206
- LOGGER.warning("No X-ray centre received")
207
- if bbox_size is not None:
208
- with TRACER.start_span("change_aperture"):
209
- yield from set_aperture_for_bbox_size(
210
- fgs_composite.aperture_scatterguard, bbox_size
204
+ LOGGER.info("Zocalo triggered and read, interpreting results.")
205
+ xray_centre, bbox_size = yield from get_processing_result(
206
+ fgs_composite.zocalo
207
+ )
208
+ LOGGER.info(f"Got xray centre: {xray_centre}, bbox size: {bbox_size}")
209
+ if xray_centre is not None:
210
+ xray_centre = parameters.FGS_params.grid_position_to_motor_position(
211
+ xray_centre
211
212
  )
212
- else:
213
- LOGGER.warning("No bounding box size received")
214
-
215
- # once we have the results, go to the appropriate position
216
- LOGGER.info("Moving to centre of mass.")
217
- with TRACER.start_span("move_to_result"):
218
- x, y, z = xray_centre
219
- yield from move_x_y_z(fgs_composite.sample_motors, x, y, z, wait=True)
220
-
221
- if parameters.FGS_params.set_stub_offsets:
222
- LOGGER.info("Recentring smargon co-ordinate system to this point.")
223
- yield from bps.mv(
224
- fgs_composite.sample_motors.stub_offsets, StubPosition.CURRENT_AS_CENTER
225
- )
226
-
227
- # Turn off dev/shm streaming to avoid filling disk, see https://github.com/DiamondLightSource/hyperion/issues/1395
228
- LOGGER.info("Turning off Eiger dev/shm streaming")
229
- yield from bps.abs_set(fgs_composite.eiger.odin.fan.dev_shm_enable, 0)
213
+ else:
214
+ LOGGER.warning("No X-ray centre received")
215
+ raise CrystalNotFoundException()
216
+ if bbox_size is not None:
217
+ with TRACER.start_span("change_aperture"):
218
+ yield from set_aperture_for_bbox_size(
219
+ fgs_composite.aperture_scatterguard, bbox_size
220
+ )
221
+ else:
222
+ LOGGER.warning("No bounding box size received")
223
+
224
+ # once we have the results, go to the appropriate position
225
+ LOGGER.info("Moving to centre of mass.")
226
+ with TRACER.start_span("move_to_result"):
227
+ x, y, z = xray_centre
228
+ yield from move_x_y_z(fgs_composite.sample_motors, x, y, z, wait=True)
229
+
230
+ if parameters.FGS_params.set_stub_offsets:
231
+ LOGGER.info("Recentring smargon co-ordinate system to this point.")
232
+ yield from bps.mv(
233
+ fgs_composite.sample_motors.stub_offsets, StubPosition.CURRENT_AS_CENTER
234
+ )
235
+ finally:
236
+ # Turn off dev/shm streaming to avoid filling disk, see https://github.com/DiamondLightSource/hyperion/issues/1395
237
+ LOGGER.info("Turning off Eiger dev/shm streaming")
238
+ yield from bps.abs_set(fgs_composite.eiger.odin.fan.dev_shm_enable, 0)
230
239
 
231
- # Wait on everything before returning to GDA (particularly apertures), can be removed
232
- # when we do not return to GDA here
233
- yield from bps.wait()
240
+ # Wait on everything before returning to GDA (particularly apertures), can be removed
241
+ # when we do not return to GDA here
242
+ yield from bps.wait()
234
243
 
235
244
 
236
245
  @bpp.set_run_key_decorator(CONST.PLAN.GRIDSCAN_MAIN)
@@ -0,0 +1,46 @@
1
+ import dataclasses
2
+
3
+ from blueapi.core import BlueskyContext
4
+ from dodal.devices.oav.oav_parameters import OAVParameters
5
+
6
+ from mx_bluesky.hyperion.experiment_plans.robot_load_then_centre_plan import (
7
+ RobotLoadThenCentreComposite,
8
+ robot_load_then_centre,
9
+ )
10
+ from mx_bluesky.hyperion.experiment_plans.rotation_scan_plan import (
11
+ RotationScanComposite,
12
+ multi_rotation_scan,
13
+ )
14
+ from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
15
+ from mx_bluesky.hyperion.utils.context import device_composite_from_context
16
+
17
+
18
+ @dataclasses.dataclass
19
+ class LoadCentreCollectComposite(RobotLoadThenCentreComposite, RotationScanComposite):
20
+ """Composite that provides access to the required devices."""
21
+
22
+ pass
23
+
24
+
25
+ def create_devices(context: BlueskyContext) -> LoadCentreCollectComposite:
26
+ """Create the necessary devices for the plan."""
27
+ return device_composite_from_context(context, LoadCentreCollectComposite)
28
+
29
+
30
+ def load_centre_collect_full_plan(
31
+ composite: LoadCentreCollectComposite,
32
+ params: LoadCentreCollect,
33
+ oav_params: OAVParameters | None = None,
34
+ ):
35
+ """Attempt a complete data collection experiment, consisting of the following:
36
+ * Load the sample if necessary
37
+ * Move to the specified goniometer start angles
38
+ * Perform optical centring, then X-ray centring
39
+ * If X-ray centring finds a diffracting centre then move to that centre and
40
+ * do a collection with the specified parameters.
41
+ """
42
+ if not oav_params:
43
+ oav_params = OAVParameters(context="xrayCentring")
44
+ yield from robot_load_then_centre(composite, params.robot_load_then_centre)
45
+
46
+ yield from multi_rotation_scan(composite, params.multi_rotation_scan, oav_params)
@@ -0,0 +1,237 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ from collections.abc import Generator
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+ from typing import cast
8
+
9
+ import bluesky.plan_stubs as bps
10
+ import bluesky.preprocessors as bpp
11
+ from blueapi.core import BlueskyContext
12
+ from bluesky.utils import Msg
13
+ from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue
14
+ from dodal.devices.attenuator import Attenuator
15
+ from dodal.devices.dcm import DCM
16
+ from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, VFMMirrorVoltages
17
+ from dodal.devices.motors import XYZPositioner
18
+ from dodal.devices.oav.oav_detector import OAV
19
+ from dodal.devices.robot import BartRobot, SampleLocation
20
+ from dodal.devices.smargon import Smargon, StubPosition
21
+ from dodal.devices.thawer import Thawer
22
+ from dodal.devices.undulator_dcm import UndulatorDCM
23
+ from dodal.devices.webcam import Webcam
24
+ from dodal.devices.xbpm_feedback import XBPMFeedback
25
+ from dodal.plans.motor_util_plans import MoveTooLarge, home_and_reset_wrapper
26
+
27
+ from mx_bluesky.hyperion.experiment_plans.set_energy_plan import (
28
+ SetEnergyComposite,
29
+ set_energy_plan,
30
+ )
31
+ from mx_bluesky.hyperion.log import LOGGER
32
+ from mx_bluesky.hyperion.parameters.constants import CONST
33
+ from mx_bluesky.hyperion.parameters.robot_load import RobotLoadAndEnergyChange
34
+
35
+
36
+ @dataclasses.dataclass
37
+ class RobotLoadAndEnergyChangeComposite:
38
+ # SetEnergyComposite fields
39
+ vfm: FocusingMirrorWithStripes
40
+ vfm_mirror_voltages: VFMMirrorVoltages
41
+ dcm: DCM
42
+ undulator_dcm: UndulatorDCM
43
+ xbpm_feedback: XBPMFeedback
44
+ attenuator: Attenuator
45
+
46
+ # RobotLoad fields
47
+ robot: BartRobot
48
+ webcam: Webcam
49
+ lower_gonio: XYZPositioner
50
+ thawer: Thawer
51
+ oav: OAV
52
+ smargon: Smargon
53
+ aperture_scatterguard: ApertureScatterguard
54
+
55
+
56
+ def create_devices(context: BlueskyContext) -> RobotLoadAndEnergyChangeComposite:
57
+ from mx_bluesky.hyperion.utils.context import device_composite_from_context
58
+
59
+ return device_composite_from_context(context, RobotLoadAndEnergyChangeComposite)
60
+
61
+
62
+ def wait_for_smargon_not_disabled(smargon: Smargon, timeout=60):
63
+ """Waits for the smargon disabled flag to go low. The robot hardware is responsible
64
+ for setting this to low when it is safe to move. It does this through a physical
65
+ connection between the robot and the smargon.
66
+ """
67
+ LOGGER.info("Waiting for smargon enabled")
68
+ SLEEP_PER_CHECK = 0.1
69
+ times_to_check = int(timeout / SLEEP_PER_CHECK)
70
+ for _ in range(times_to_check):
71
+ smargon_disabled = yield from bps.rd(smargon.disabled)
72
+ if not smargon_disabled:
73
+ LOGGER.info("Smargon now enabled")
74
+ return
75
+ yield from bps.sleep(SLEEP_PER_CHECK)
76
+ raise TimeoutError(
77
+ "Timed out waiting for smargon to become enabled after robot load"
78
+ )
79
+
80
+
81
+ def take_robot_snapshots(oav: OAV, webcam: Webcam, directory: Path):
82
+ time_now = datetime.now()
83
+ snapshot_format = f"{time_now.strftime('%H%M%S')}_{{device}}_after_load"
84
+ for device in [oav.snapshot, webcam]:
85
+ yield from bps.abs_set(
86
+ device.filename, snapshot_format.format(device=device.name)
87
+ )
88
+ yield from bps.abs_set(device.directory, str(directory))
89
+ # Note: should be able to use `wait=True` after https://github.com/bluesky/bluesky/issues/1795
90
+ yield from bps.trigger(device, group="snapshots")
91
+ yield from bps.wait("snapshots")
92
+
93
+
94
+ def prepare_for_robot_load(
95
+ aperture_scatterguard: ApertureScatterguard, smargon: Smargon
96
+ ):
97
+ yield from bps.abs_set(
98
+ aperture_scatterguard,
99
+ ApertureValue.ROBOT_LOAD,
100
+ group="prepare_robot_load",
101
+ )
102
+
103
+ yield from bps.mv(smargon.stub_offsets, StubPosition.RESET_TO_ROBOT_LOAD)
104
+
105
+ # fmt: off
106
+ yield from bps.mv(smargon.x, 0,
107
+ smargon.y, 0,
108
+ smargon.z, 0,
109
+ smargon.omega, 0,
110
+ smargon.chi, 0,
111
+ smargon.phi, 0)
112
+ # fmt: on
113
+
114
+ yield from bps.wait("prepare_robot_load")
115
+
116
+
117
+ def do_robot_load(
118
+ composite: RobotLoadAndEnergyChangeComposite,
119
+ sample_location: SampleLocation,
120
+ demand_energy_ev: float | None,
121
+ thawing_time: float,
122
+ ):
123
+ yield from bps.abs_set(
124
+ composite.robot,
125
+ sample_location,
126
+ group="robot_load",
127
+ )
128
+
129
+ if demand_energy_ev:
130
+ yield from set_energy_plan(
131
+ demand_energy_ev / 1000,
132
+ cast(SetEnergyComposite, composite),
133
+ )
134
+
135
+ yield from bps.wait("robot_load")
136
+
137
+ yield from bps.abs_set(
138
+ composite.thawer.thaw_for_time_s, thawing_time, group="thawing_finished"
139
+ )
140
+ yield from wait_for_smargon_not_disabled(composite.smargon)
141
+
142
+
143
+ def raise_exception_if_moved_out_of_cryojet(exception):
144
+ yield from bps.null()
145
+ if isinstance(exception, MoveTooLarge):
146
+ raise Exception(
147
+ f"Moving {exception.axis} back to {exception.position} after \
148
+ robot load would move it out of the cryojet. The max safe \
149
+ distance is {exception.maximum_move}"
150
+ )
151
+
152
+
153
+ def pin_already_loaded(
154
+ robot: BartRobot, sample_location: SampleLocation
155
+ ) -> Generator[Msg, None, bool]:
156
+ current_puck = yield from bps.rd(robot.current_puck)
157
+ current_pin = yield from bps.rd(robot.current_pin)
158
+ return (
159
+ int(current_puck) == sample_location.puck
160
+ and int(current_pin) == sample_location.pin
161
+ )
162
+
163
+
164
+ def robot_load_and_snapshots(
165
+ composite: RobotLoadAndEnergyChangeComposite,
166
+ location: SampleLocation,
167
+ snapshot_directory: Path,
168
+ thawing_time: float,
169
+ demand_energy_ev: float | None,
170
+ ):
171
+ robot_load_plan = do_robot_load(
172
+ composite,
173
+ location,
174
+ demand_energy_ev,
175
+ thawing_time,
176
+ )
177
+
178
+ # The lower gonio must be in the correct position for the robot load and we
179
+ # want to put it back afterwards. Note we don't wait the robot is interlocked
180
+ # to the lower gonio and the move is quicker than the robot takes to get to the
181
+ # load position.
182
+ yield from bpp.contingency_wrapper(
183
+ home_and_reset_wrapper(
184
+ robot_load_plan,
185
+ composite.lower_gonio,
186
+ BartRobot.LOAD_TOLERANCE_MM,
187
+ CONST.HARDWARE.CRYOJET_MARGIN_MM,
188
+ "lower_gonio",
189
+ wait_for_all=False,
190
+ ),
191
+ except_plan=raise_exception_if_moved_out_of_cryojet,
192
+ )
193
+
194
+ yield from take_robot_snapshots(composite.oav, composite.webcam, snapshot_directory)
195
+
196
+ yield from bps.create(name=CONST.DESCRIPTORS.ROBOT_LOAD)
197
+ yield from bps.read(composite.robot.barcode)
198
+ yield from bps.read(composite.oav.snapshot)
199
+ yield from bps.read(composite.webcam)
200
+ yield from bps.save()
201
+
202
+ yield from bps.wait("reset-lower_gonio")
203
+
204
+
205
+ def robot_load_and_change_energy_plan(
206
+ composite: RobotLoadAndEnergyChangeComposite,
207
+ params: RobotLoadAndEnergyChange,
208
+ ):
209
+ assert params.sample_puck is not None
210
+ assert params.sample_pin is not None
211
+
212
+ sample_location = SampleLocation(params.sample_puck, params.sample_pin)
213
+
214
+ yield from prepare_for_robot_load(
215
+ composite.aperture_scatterguard, composite.smargon
216
+ )
217
+ yield from bpp.run_wrapper(
218
+ robot_load_and_snapshots(
219
+ composite,
220
+ sample_location,
221
+ params.snapshot_directory,
222
+ params.thawing_time,
223
+ params.demand_energy_ev,
224
+ ),
225
+ md={
226
+ "subplan_name": CONST.PLAN.ROBOT_LOAD,
227
+ "metadata": {
228
+ "visit": params.visit,
229
+ "sample_id": params.sample_id,
230
+ "sample_puck": sample_location.puck,
231
+ "sample_pin": sample_location.pin,
232
+ },
233
+ "activate_callbacks": [
234
+ "RobotLoadISPyBCallback",
235
+ ],
236
+ },
237
+ )
@@ -1,16 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import dataclasses
4
- from collections.abc import Generator
5
- from datetime import datetime
6
- from pathlib import Path
7
4
  from typing import cast
8
5
 
9
- import bluesky.plan_stubs as bps
10
- import bluesky.preprocessors as bpp
11
6
  from blueapi.core import BlueskyContext, MsgGenerator
12
- from bluesky.utils import Msg
13
- from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue
7
+ from dodal.devices.aperturescatterguard import ApertureScatterguard
14
8
  from dodal.devices.attenuator import Attenuator
15
9
  from dodal.devices.backlight import Backlight
16
10
  from dodal.devices.dcm import DCM
@@ -24,7 +18,7 @@ from dodal.devices.oav.oav_detector import OAV
24
18
  from dodal.devices.oav.pin_image_recognition import PinTipDetection
25
19
  from dodal.devices.robot import BartRobot, SampleLocation
26
20
  from dodal.devices.s4_slit_gaps import S4SlitGaps
27
- from dodal.devices.smargon import Smargon, StubPosition
21
+ from dodal.devices.smargon import Smargon
28
22
  from dodal.devices.synchrotron import Synchrotron
29
23
  from dodal.devices.thawer import Thawer
30
24
  from dodal.devices.undulator import Undulator
@@ -34,10 +28,11 @@ from dodal.devices.xbpm_feedback import XBPMFeedback
34
28
  from dodal.devices.zebra import Zebra
35
29
  from dodal.devices.zebra_controlled_shutter import ZebraShutter
36
30
  from dodal.devices.zocalo import ZocaloResults
37
- from dodal.plans.motor_util_plans import MoveTooLarge, home_and_reset_wrapper
31
+ from dodal.log import LOGGER
38
32
  from ophyd_async.fastcs.panda import HDFPanda
39
33
 
40
34
  from mx_bluesky.hyperion.device_setup_plans.utils import (
35
+ fill_in_energy_if_not_supplied,
41
36
  start_preparing_data_collection_then_do_plan,
42
37
  )
43
38
  from mx_bluesky.hyperion.experiment_plans.grid_detect_then_xray_centre_plan import (
@@ -46,12 +41,11 @@ from mx_bluesky.hyperion.experiment_plans.grid_detect_then_xray_centre_plan impo
46
41
  from mx_bluesky.hyperion.experiment_plans.pin_centre_then_xray_centre_plan import (
47
42
  pin_centre_then_xray_centre_plan,
48
43
  )
49
- from mx_bluesky.hyperion.experiment_plans.set_energy_plan import (
50
- SetEnergyComposite,
51
- read_energy,
52
- set_energy_plan,
44
+ from mx_bluesky.hyperion.experiment_plans.robot_load_and_change_energy import (
45
+ RobotLoadAndEnergyChangeComposite,
46
+ pin_already_loaded,
47
+ robot_load_and_change_energy_plan,
53
48
  )
54
- from mx_bluesky.hyperion.log import LOGGER
55
49
  from mx_bluesky.hyperion.parameters.constants import CONST
56
50
  from mx_bluesky.hyperion.parameters.gridscan import RobotLoadThenCentre
57
51
 
@@ -100,144 +94,6 @@ def create_devices(context: BlueskyContext) -> RobotLoadThenCentreComposite:
100
94
  return device_composite_from_context(context, RobotLoadThenCentreComposite)
101
95
 
102
96
 
103
- def wait_for_smargon_not_disabled(smargon: Smargon, timeout=60):
104
- """Waits for the smargon disabled flag to go low. The robot hardware is responsible
105
- for setting this to low when it is safe to move. It does this through a physical
106
- connection between the robot and the smargon.
107
- """
108
- LOGGER.info("Waiting for smargon enabled")
109
- SLEEP_PER_CHECK = 0.1
110
- times_to_check = int(timeout / SLEEP_PER_CHECK)
111
- for _ in range(times_to_check):
112
- smargon_disabled = yield from bps.rd(smargon.disabled)
113
- if not smargon_disabled:
114
- LOGGER.info("Smargon now enabled")
115
- return
116
- yield from bps.sleep(SLEEP_PER_CHECK)
117
- raise TimeoutError(
118
- "Timed out waiting for smargon to become enabled after robot load"
119
- )
120
-
121
-
122
- def take_robot_snapshots(oav: OAV, webcam: Webcam, directory: Path):
123
- time_now = datetime.now()
124
- snapshot_format = f"{time_now.strftime('%H%M%S')}_{{device}}_after_load"
125
- for device in [oav.snapshot, webcam]:
126
- yield from bps.abs_set(
127
- device.filename, snapshot_format.format(device=device.name)
128
- )
129
- yield from bps.abs_set(device.directory, str(directory))
130
- # Note: should be able to use `wait=True` after https://github.com/bluesky/bluesky/issues/1795
131
- yield from bps.trigger(device, group="snapshots")
132
- yield from bps.wait("snapshots")
133
-
134
-
135
- def prepare_for_robot_load(composite: RobotLoadThenCentreComposite):
136
- yield from bps.abs_set(
137
- composite.aperture_scatterguard,
138
- ApertureValue.ROBOT_LOAD,
139
- group="prepare_robot_load",
140
- )
141
-
142
- yield from bps.mv(composite.smargon.stub_offsets, StubPosition.RESET_TO_ROBOT_LOAD)
143
-
144
- # fmt: off
145
- yield from bps.mv(composite.smargon.x, 0,
146
- composite.smargon.y, 0,
147
- composite.smargon.z, 0,
148
- composite.smargon.omega, 0,
149
- composite.smargon.chi, 0,
150
- composite.smargon.phi, 0)
151
- # fmt: on
152
-
153
- yield from bps.wait("prepare_robot_load")
154
-
155
-
156
- def do_robot_load(
157
- composite: RobotLoadThenCentreComposite,
158
- sample_location: SampleLocation,
159
- demand_energy_ev: float | None,
160
- thawing_time: float,
161
- ):
162
- yield from bps.abs_set(
163
- composite.robot,
164
- sample_location,
165
- group="robot_load",
166
- )
167
-
168
- if demand_energy_ev:
169
- yield from set_energy_plan(
170
- demand_energy_ev / 1000,
171
- cast(SetEnergyComposite, composite),
172
- )
173
-
174
- yield from bps.wait("robot_load")
175
-
176
- yield from bps.abs_set(
177
- composite.thawer.thaw_for_time_s, thawing_time, group="thawing_finished"
178
- )
179
- yield from wait_for_smargon_not_disabled(composite.smargon)
180
-
181
-
182
- def raise_exception_if_moved_out_of_cryojet(exception):
183
- yield from bps.null()
184
- if isinstance(exception, MoveTooLarge):
185
- raise Exception(
186
- f"Moving {exception.axis} back to {exception.position} after \
187
- robot load would move it out of the cryojet. The max safe \
188
- distance is {exception.maximum_move}"
189
- )
190
-
191
-
192
- def _pin_already_loaded(
193
- robot: BartRobot, pin_to_load: int, puck_to_load: int
194
- ) -> Generator[Msg, None, bool]:
195
- current_puck = yield from bps.rd(robot.current_puck)
196
- current_pin = yield from bps.rd(robot.current_pin)
197
- return int(current_puck) == puck_to_load and int(current_pin) == pin_to_load
198
-
199
-
200
- def robot_load_and_snapshots(
201
- composite: RobotLoadThenCentreComposite,
202
- params: RobotLoadThenCentre,
203
- location: SampleLocation,
204
- ):
205
- robot_load_plan = do_robot_load(
206
- composite,
207
- location,
208
- params.demand_energy_ev,
209
- params.thawing_time,
210
- )
211
-
212
- # The lower gonio must be in the correct position for the robot load and we
213
- # want to put it back afterwards. Note we don't wait the robot is interlocked
214
- # to the lower gonio and the move is quicker than the robot takes to get to the
215
- # load position.
216
- yield from bpp.contingency_wrapper(
217
- home_and_reset_wrapper(
218
- robot_load_plan,
219
- composite.lower_gonio,
220
- BartRobot.LOAD_TOLERANCE_MM,
221
- CONST.HARDWARE.CRYOJET_MARGIN_MM,
222
- "lower_gonio",
223
- wait_for_all=False,
224
- ),
225
- except_plan=raise_exception_if_moved_out_of_cryojet,
226
- )
227
-
228
- yield from take_robot_snapshots(
229
- composite.oav, composite.webcam, params.snapshot_directory
230
- )
231
-
232
- yield from bps.create(name=CONST.DESCRIPTORS.ROBOT_LOAD)
233
- yield from bps.read(composite.robot.barcode)
234
- yield from bps.read(composite.oav.snapshot)
235
- yield from bps.read(composite.webcam)
236
- yield from bps.save()
237
-
238
- yield from bps.wait("reset-lower_gonio")
239
-
240
-
241
97
  def centring_plan_from_robot_load_params(
242
98
  composite: RobotLoadThenCentreComposite,
243
99
  params: RobotLoadThenCentre,
@@ -251,23 +107,10 @@ def centring_plan_from_robot_load_params(
251
107
  def robot_load_then_centre_plan(
252
108
  composite: RobotLoadThenCentreComposite,
253
109
  params: RobotLoadThenCentre,
254
- sample_location: SampleLocation,
255
110
  ):
256
- yield from prepare_for_robot_load(composite)
257
- yield from bpp.run_wrapper(
258
- robot_load_and_snapshots(composite, params, sample_location),
259
- md={
260
- "subplan_name": CONST.PLAN.ROBOT_LOAD,
261
- "metadata": {
262
- "visit_path": str(params.visit_directory),
263
- "sample_id": params.sample_id,
264
- "sample_puck": params.sample_puck,
265
- "sample_pin": params.sample_pin,
266
- },
267
- "activate_callbacks": [
268
- "RobotLoadISPyBCallback",
269
- ],
270
- },
111
+ yield from robot_load_and_change_energy_plan(
112
+ cast(RobotLoadAndEnergyChangeComposite, composite),
113
+ params.robot_load_params(),
271
114
  )
272
115
 
273
116
  yield from centring_plan_from_robot_load_params(composite, params)
@@ -283,10 +126,10 @@ def robot_load_then_centre(
283
126
  assert parameters.sample_puck is not None
284
127
  assert parameters.sample_pin is not None
285
128
 
129
+ sample_location = SampleLocation(parameters.sample_puck, parameters.sample_pin)
130
+
286
131
  doing_sample_load = not (
287
- yield from _pin_already_loaded(
288
- composite.robot, parameters.sample_pin, parameters.sample_puck
289
- )
132
+ yield from pin_already_loaded(composite.robot, sample_location)
290
133
  )
291
134
 
292
135
  doing_chi_change = parameters.chi_start_deg is not None
@@ -295,7 +138,6 @@ def robot_load_then_centre(
295
138
  plan = robot_load_then_centre_plan(
296
139
  composite,
297
140
  parameters,
298
- SampleLocation(parameters.sample_puck, parameters.sample_pin),
299
141
  )
300
142
  LOGGER.info("Pin not loaded, loading and centring")
301
143
  elif doing_chi_change:
@@ -305,12 +147,10 @@ def robot_load_then_centre(
305
147
  LOGGER.info("Pin already loaded and chi not changed so doing nothing")
306
148
  return
307
149
 
308
- detector_params = parameters.detector_params
309
- if not detector_params.expected_energy_ev:
310
- actual_energy_ev = 1000 * (
311
- yield from read_energy(cast(SetEnergyComposite, composite))
312
- )
313
- detector_params.expected_energy_ev = actual_energy_ev
150
+ detector_params = yield from fill_in_energy_if_not_supplied(
151
+ composite.dcm, parameters.detector_params
152
+ )
153
+
314
154
  eiger.set_detector_parameters(detector_params)
315
155
 
316
156
  yield from start_preparing_data_collection_then_do_plan(
@@ -6,11 +6,8 @@
6
6
  """
7
7
 
8
8
  import dataclasses
9
- from collections.abc import Generator
10
- from typing import Any
11
9
 
12
10
  from bluesky import plan_stubs as bps
13
- from bluesky.utils import Msg
14
11
  from dodal.devices.attenuator import Attenuator
15
12
  from dodal.devices.dcm import DCM
16
13
  from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, VFMMirrorVoltages
@@ -51,11 +48,6 @@ def _set_energy_plan(
51
48
  yield from bps.wait(group=UNDULATOR_GROUP)
52
49
 
53
50
 
54
- def read_energy(composite: SetEnergyComposite) -> Generator[Msg, Any, float]:
55
- """Obtain the energy in kev"""
56
- return (yield from bps.rd(composite.dcm.energy_in_kev)) # type: ignore
57
-
58
-
59
51
  def set_energy_plan(
60
52
  energy_kev,
61
53
  composite: SetEnergyComposite,
@@ -44,3 +44,21 @@ def create_rotation_callbacks() -> (
44
44
  tuple[RotationNexusFileCallback, RotationISPyBCallback]
45
45
  ):
46
46
  return (RotationNexusFileCallback(), RotationISPyBCallback(emit=ZocaloCallback()))
47
+
48
+
49
+ def create_load_centre_collect_callbacks() -> (
50
+ tuple[
51
+ GridscanNexusFileCallback,
52
+ GridscanISPyBCallback,
53
+ RobotLoadISPyBCallback,
54
+ RotationNexusFileCallback,
55
+ RotationISPyBCallback,
56
+ ]
57
+ ):
58
+ return (
59
+ GridscanNexusFileCallback(),
60
+ GridscanISPyBCallback(emit=ZocaloCallback()),
61
+ RobotLoadISPyBCallback(),
62
+ RotationNexusFileCallback(),
63
+ RotationISPyBCallback(emit=ZocaloCallback()),
64
+ )
@@ -1,7 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import re
4
-
5
3
  from mx_bluesky.hyperion.external_interaction.ispyb.data_model import (
6
4
  DataCollectionGroupInfo,
7
5
  DataCollectionInfo,
@@ -11,7 +9,6 @@ from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_store import (
11
9
  I03_EIGER_DETECTOR,
12
10
  )
13
11
  from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_utils import (
14
- VISIT_PATH_REGEX,
15
12
  get_current_time_string,
16
13
  )
17
14
  from mx_bluesky.hyperion.parameters.components import DiffractionExperimentWithSample
@@ -63,8 +60,3 @@ def get_proposal_and_session_from_visit_string(visit_string: str) -> tuple[str,
63
60
  visit_parts = visit_string.split("-")
64
61
  assert len(visit_parts) == 2, f"Unexpected visit string {visit_string}"
65
62
  return visit_parts[0], int(visit_parts[1])
66
-
67
-
68
- def get_visit_string_from_path(path: str | None) -> str | None:
69
- match = re.search(VISIT_PATH_REGEX, path) if path else None
70
- return str(match.group(1)) if match else None
@@ -110,9 +110,9 @@ class BaseISPyBCallback(PlanReactiveCallback):
110
110
  slitgap_vertical=doc["data"]["s4_slit_gaps_ygap"],
111
111
  )
112
112
  hwscan_position_info = DataCollectionPositionInfo(
113
- pos_x=doc["data"]["smargon-x"],
114
- pos_y=doc["data"]["smargon-y"],
115
- pos_z=doc["data"]["smargon-z"],
113
+ pos_x=float(doc["data"]["smargon-x"]),
114
+ pos_y=float(doc["data"]["smargon-y"]),
115
+ pos_z=float(doc["data"]["smargon-z"]),
116
116
  )
117
117
  scan_data_infos = self.populate_info_for_update(
118
118
  hwscan_data_collection_info, hwscan_position_info, self.params
@@ -6,7 +6,6 @@ from event_model.documents import EventDescriptor
6
6
 
7
7
  from mx_bluesky.hyperion.external_interaction.callbacks.common.ispyb_mapping import (
8
8
  get_proposal_and_session_from_visit_string,
9
- get_visit_string_from_path,
10
9
  )
11
10
  from mx_bluesky.hyperion.external_interaction.callbacks.plan_reactive_callback import (
12
11
  PlanReactiveCallback,
@@ -37,10 +36,9 @@ class RobotLoadISPyBCallback(PlanReactiveCallback):
37
36
  ISPYB_LOGGER.debug(f"ISPyB robot load callback received: {doc}")
38
37
  self.run_uid = doc.get("uid")
39
38
  assert isinstance(metadata := doc.get("metadata"), dict)
40
- assert isinstance(
41
- visit := get_visit_string_from_path(metadata["visit_path"]), str
39
+ proposal, session = get_proposal_and_session_from_visit_string(
40
+ metadata["visit"]
42
41
  )
43
- proposal, session = get_proposal_and_session_from_visit_string(visit)
44
42
  self.action_id = self.expeye.start_load(
45
43
  proposal,
46
44
  session,
@@ -9,8 +9,6 @@ from ispyb.sp.core import Core
9
9
 
10
10
  from mx_bluesky.hyperion.parameters.constants import CONST
11
11
 
12
- VISIT_PATH_REGEX = r".+/([a-zA-Z]{2}\d{4,5}-\d{1,3})(/?$)"
13
-
14
12
 
15
13
  def get_ispyb_config():
16
14
  return os.environ.get("ISPYB_CONFIG_PATH", CONST.SIM.ISPYB_CONFIG)
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import datetime
4
3
  import json
5
4
  from abc import abstractmethod
6
5
  from collections.abc import Sequence
@@ -142,24 +141,32 @@ class WithSnapshot(BaseModel):
142
141
  return bool(self.snapshot_omegas_deg)
143
142
 
144
143
 
145
- class DiffractionExperiment(HyperionParameters, WithSnapshot):
146
- """For all experiments which use beam"""
144
+ class WithOptionalEnergyChange(BaseModel):
145
+ demand_energy_ev: float | None = Field(default=None, gt=0)
147
146
 
147
+
148
+ class WithVisit(BaseModel):
148
149
  visit: str = Field(min_length=1)
149
- file_name: str
150
- exposure_time_s: float = Field(gt=0)
151
- comment: str = Field(default="")
150
+ zocalo_environment: str = Field(default=CONST.ZOCALO_ENV)
152
151
  beamline: str = Field(default=CONST.I03.BEAMLINE, pattern=r"BL\d{2}[BIJS]")
153
- insertion_prefix: str = Field(
154
- default=CONST.I03.INSERTION_PREFIX, pattern=r"SR\d{2}[BIJS]"
155
- )
156
152
  det_dist_to_beam_converter_path: str = Field(
157
153
  default=CONST.PARAM.DETECTOR.BEAM_XY_LUT_PATH
158
154
  )
159
- zocalo_environment: str = Field(default=CONST.ZOCALO_ENV)
160
- trigger_mode: TriggerMode = Field(default=TriggerMode.FREE_RUN)
155
+ insertion_prefix: str = Field(
156
+ default=CONST.I03.INSERTION_PREFIX, pattern=r"SR\d{2}[BIJS]"
157
+ )
161
158
  detector_distance_mm: float | None = Field(default=None, gt=0)
162
- demand_energy_ev: float | None = Field(default=None, gt=0)
159
+
160
+
161
+ class DiffractionExperiment(
162
+ HyperionParameters, WithSnapshot, WithOptionalEnergyChange, WithVisit
163
+ ):
164
+ """For all experiments which use beam"""
165
+
166
+ file_name: str
167
+ exposure_time_s: float = Field(gt=0)
168
+ comment: str = Field(default="")
169
+ trigger_mode: TriggerMode = Field(default=TriggerMode.FREE_RUN)
163
170
  run_number: int | None = Field(default=None, ge=0)
164
171
  selected_aperture: ApertureValue | None = Field(default=None)
165
172
  transmission_frac: float = Field(default=0.1)
@@ -177,12 +184,6 @@ class DiffractionExperiment(HyperionParameters, WithSnapshot):
177
184
  )
178
185
  return values
179
186
 
180
- @property
181
- def visit_directory(self) -> Path:
182
- return (
183
- Path(CONST.I03.BASE_DATA_DIR) / str(datetime.date.today().year) / self.visit
184
- )
185
-
186
187
  @property
187
188
  def num_images(self) -> int:
188
189
  return 0
@@ -20,10 +20,12 @@ from mx_bluesky.hyperion.parameters.components import (
20
20
  OptionalGonioAngleStarts,
21
21
  SplitScan,
22
22
  WithOavCentring,
23
+ WithOptionalEnergyChange,
23
24
  WithScan,
24
25
  XyzStarts,
25
26
  )
26
27
  from mx_bluesky.hyperion.parameters.constants import CONST, I03Constants
28
+ from mx_bluesky.hyperion.parameters.robot_load import RobotLoadAndEnergyChange
27
29
 
28
30
 
29
31
  class GridCommon(
@@ -85,6 +87,10 @@ class PinTipCentreThenXrayCentre(GridCommon):
85
87
  class RobotLoadThenCentre(GridCommon):
86
88
  thawing_time: float = Field(default=CONST.I03.THAWING_TIME)
87
89
 
90
+ def robot_load_params(self):
91
+ my_params = self.model_dump()
92
+ return RobotLoadAndEnergyChange(**my_params)
93
+
88
94
  def pin_centre_then_xray_centre_params(self):
89
95
  my_params = self.model_dump()
90
96
  del my_params["thawing_time"]
@@ -99,11 +105,10 @@ class SpecifiedGridScan(GridCommon, XyzStarts, WithScan):
99
105
  ...
100
106
 
101
107
 
102
- class ThreeDGridScan(SpecifiedGridScan, SplitScan):
108
+ class ThreeDGridScan(SpecifiedGridScan, SplitScan, WithOptionalEnergyChange):
103
109
  """Parameters representing a so-called 3D grid scan, which consists of doing a
104
110
  gridscan in X and Y, followed by one in X and Z."""
105
111
 
106
- demand_energy_ev: float | None = Field(default=None)
107
112
  grid1_omega_deg: float = Field(default=CONST.PARAM.GRIDSCAN.OMEGA_1) # type: ignore
108
113
  grid2_omega_deg: float = Field(default=CONST.PARAM.GRIDSCAN.OMEGA_2)
109
114
  x_step_size_um: float = Field(default=CONST.PARAM.GRIDSCAN.BOX_WIDTH_UM)
@@ -0,0 +1,50 @@
1
+ from typing import TypeVar
2
+
3
+ from pydantic import BaseModel, model_validator
4
+
5
+ from mx_bluesky.hyperion.parameters.components import (
6
+ HyperionParameters,
7
+ WithSample,
8
+ WithVisit,
9
+ )
10
+ from mx_bluesky.hyperion.parameters.gridscan import (
11
+ RobotLoadThenCentre,
12
+ )
13
+ from mx_bluesky.hyperion.parameters.rotation import MultiRotationScan
14
+
15
+ T = TypeVar("T", bound=BaseModel)
16
+
17
+
18
+ def construct_from_values(parent_context: dict, key: str, t: type[T]) -> T:
19
+ values = dict(parent_context)
20
+ values |= values[key]
21
+ return t(**values)
22
+
23
+
24
+ class LoadCentreCollect(HyperionParameters, WithVisit, WithSample):
25
+ """Experiment parameters to perform the combined robot load,
26
+ pin-tip centre and rotation scan operations."""
27
+
28
+ robot_load_then_centre: RobotLoadThenCentre
29
+ multi_rotation_scan: MultiRotationScan
30
+
31
+ @model_validator(mode="before")
32
+ @classmethod
33
+ def validate_model(cls, values):
34
+ allowed_keys = (
35
+ LoadCentreCollect.model_fields.keys()
36
+ | RobotLoadThenCentre.model_fields.keys()
37
+ | MultiRotationScan.model_fields.keys()
38
+ )
39
+ disallowed_keys = values.keys() - allowed_keys
40
+ assert (
41
+ disallowed_keys == set()
42
+ ), f"Unexpected fields found in LoadCentreCollect {disallowed_keys}"
43
+
44
+ values["robot_load_then_centre"] = construct_from_values(
45
+ values, "robot_load_then_centre", RobotLoadThenCentre
46
+ )
47
+ values["multi_rotation_scan"] = construct_from_values(
48
+ values, "multi_rotation_scan", MultiRotationScan
49
+ )
50
+ return values
@@ -0,0 +1,16 @@
1
+ from pydantic import Field
2
+
3
+ from mx_bluesky.hyperion.parameters.components import (
4
+ HyperionParameters,
5
+ WithOptionalEnergyChange,
6
+ WithSample,
7
+ WithSnapshot,
8
+ WithVisit,
9
+ )
10
+ from mx_bluesky.hyperion.parameters.constants import CONST
11
+
12
+
13
+ class RobotLoadAndEnergyChange(
14
+ HyperionParameters, WithSample, WithSnapshot, WithOptionalEnergyChange, WithVisit
15
+ ):
16
+ thawing_time: float = Field(default=CONST.I03.THAWING_TIME)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mx-bluesky
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: Bluesky tools for MX Beamlines at DLS
5
5
  Author-email: Dominic Oram <dominic.oram@diamond.ac.uk>
6
6
  License: Apache License
@@ -237,7 +237,7 @@ Requires-Dist: daq-config-server >=0.1.1
237
237
  Requires-Dist: ophyd ==1.9.0
238
238
  Requires-Dist: ophyd-async >=0.3a5
239
239
  Requires-Dist: bluesky >=1.13.0a4
240
- Requires-Dist: dls-dodal ==1.32.0
240
+ Requires-Dist: dls-dodal ==1.33.0
241
241
  Provides-Extra: dev
242
242
  Requires-Dist: black ; extra == 'dev'
243
243
  Requires-Dist: build ; extra == 'dev'
@@ -1,6 +1,6 @@
1
1
  mx_bluesky/__init__.py,sha256=twUymKa_5teVCbYPgxRDu87WyQhJa8nMRb8HLTCFJZ8,115
2
2
  mx_bluesky/__main__.py,sha256=bmFrxcsN8zGadkDLRnE3qV3-iXJcEWVPp5GNx2rsTJ0,383
3
- mx_bluesky/_version.py,sha256=CqDGE4B1ZqZ-56mxeOFcXRTmlxrdOh4ayrjbcPjziE4,411
3
+ mx_bluesky/_version.py,sha256=zMnMemknXglcJs59xkicNzeEJTVgYd1omSfLWj76yWw,411
4
4
  mx_bluesky/example.py,sha256=_zBoGrKAI1ReOvBz51F9PDorKWQufrb0_ydmoqJ2UAA,478
5
5
  mx_bluesky/jupyter_example.ipynb,sha256=wpwvPrBvwtRMS5AIFk8F54cIlUoD0o4ji8tKK5cZHA4,1672
6
6
  mx_bluesky/beamlines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -72,20 +72,22 @@ mx_bluesky/hyperion/device_setup_plans/setup_oav.py,sha256=LkhhcAyiNiTpeHCzW7vbz
72
72
  mx_bluesky/hyperion/device_setup_plans/setup_panda.py,sha256=ZnH2hTMR-Q35D0Gu00cwr-aobIRrfRxoQKW3A0j3fk4,7783
73
73
  mx_bluesky/hyperion/device_setup_plans/setup_zebra.py,sha256=rzzxU1d3ahe5tIk0h2TULvabKlY57OhrtO4mM8oKyuc,8049
74
74
  mx_bluesky/hyperion/device_setup_plans/smargon.py,sha256=sSqrURldwwqPvbS-y7y-9Y1LM6Rq3AO0JgA9cHCPais,788
75
- mx_bluesky/hyperion/device_setup_plans/utils.py,sha256=Im_9MYCzIv5A9wAVjD5PeIRRqhn3UHZrrmnUgFSX2Eo,1430
75
+ mx_bluesky/hyperion/device_setup_plans/utils.py,sha256=5xuz4SFYfAA14pvYycvw3zQiwUq2_gscn3LiTdKRz8M,1813
76
76
  mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py,sha256=tx9Sl3LMw4r9ls8J8TGf6COz__ISgLjJaEpInzlmnP8,3679
77
77
  mx_bluesky/hyperion/experiment_plans/__init__.py,sha256=Tq9XXh8-N4obZMizxi5H1laV-toJQ9olu4K05gMFo8E,936
78
- mx_bluesky/hyperion/experiment_plans/experiment_registry.py,sha256=bYQtPAVlsS3f4vM57MXIhr_ltTJoMFpn1ZQYtsZ_jvo,2592
79
- mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py,sha256=Dhw5BYh4MU2oby344JGya1qsFSXvipDaxhQcBoqxSCM,18973
78
+ mx_bluesky/hyperion/experiment_plans/experiment_registry.py,sha256=n7A0yK4ftpwB0GoNop4ZM1jcFvdqEQuq6qLabzprLKo,2995
79
+ mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py,sha256=mGxz_6rMR3_z9hihP-e_DDtWzMulxdJrgIU2qoWGNEc,19321
80
80
  mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py,sha256=C03cKIQLyDAeGnI05tW6Z2kzItTIfHL_wlXqd0X8TcM,7186
81
+ mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py,sha256=vmqqlZANQB9LAl9QgMNmGU3yB624_gMuM7pJlglPOOc,1711
81
82
  mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py,sha256=lzkC1mZCuXW_GmGhgWeTjKGTgAP1uqNWlcEpY5_dOgc,6943
82
83
  mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py,sha256=7OcQ3nm7m-xLdPkct41-HrrQKrs9q_U8qOy80QqhnFg,2821
83
84
  mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py,sha256=JZJW3hOzcxFyCEmsMYWWGwA9tAgGgg85nBKxBxKXeX0,16276
84
85
  mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py,sha256=vV9UOubh2v6clL4LHFSQhqVBGcKPaNVWqK2rDrdPcQI,4146
85
86
  mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py,sha256=YcGGckesXe_5NhaIEd0SFOPbtTYkpDKLSi3g7_2d52o,5969
86
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py,sha256=PKQf5hdaiXWXNL53MYc0289RuDEc0eyBzdCxEWP0m34,10988
87
+ mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py,sha256=jogvdhNYIb5B2dZihPX3EgL_o3iFUYMTNumNWZNRHRI,7802
88
+ mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py,sha256=lIGqj_ugGUfiS-1eTbO_5Q6MRfhA5QgINbUvY9JMhjI,5422
87
89
  mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py,sha256=iYsLw3nlIlpdccK1EeWH1S7nN1JoINvVxNRfZG52DE4,15802
88
- mx_bluesky/hyperion/experiment_plans/set_energy_plan.py,sha256=Q5mlujyR0odDkf2BRurFiWhSfEHJtIPqW904EjUOxLA,2011
90
+ mx_bluesky/hyperion/experiment_plans/set_energy_plan.py,sha256=sBCwDxtBb-XzWPIYeIOqjKSeeOmdLzDvDihONA_Qcwg,1729
89
91
  mx_bluesky/hyperion/external_interaction/__init__.py,sha256=95DwXDmKsx36RgAL-AtLZl2LQoLPKbzeYdlkkP_4Coc,559
90
92
  mx_bluesky/hyperion/external_interaction/config_server.py,sha256=hA5r4v071gkV91F9YPwTVQGLPq3iMb1pjcBEE14Uxh4,1077
91
93
  mx_bluesky/hyperion/external_interaction/exceptions.py,sha256=XBDES13VaW9fhxRZYNvSfLC_Mvg4M4RqMseW_u18Mb8,342
@@ -93,15 +95,15 @@ mx_bluesky/hyperion/external_interaction/callbacks/__init__.py,sha256=njGhxi0Qf0
93
95
  mx_bluesky/hyperion/external_interaction/callbacks/__main__.py,sha256=Ke_3nKJXOnPu06rq3r9oULoTqgfwfbQYQgt5GDEidYE,4860
94
96
  mx_bluesky/hyperion/external_interaction/callbacks/aperture_change_callback.py,sha256=pWd84QldTGLoxPdyqXZk6_SLhvF7O-CQRsiXqWDQEkg,837
95
97
  mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py,sha256=tWtbpdq3dcTpoVpuDIeSAf-naziky38wgMeW6S1Dh7c,2914
96
- mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py,sha256=SQ9FDrQ02BN3YYDU3kNBvZ28louPBMaRVipEb6OQId8,8854
98
+ mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py,sha256=ynztJNtM-Njs_kTIFI6aM1YzcX7TK3c_7p4gge2QUCg,8875
97
99
  mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py,sha256=MnpHKTwBbI5_xDtm5VK1cSObrfkWBuWP0_OXnU1qQCA,622
98
100
  mx_bluesky/hyperion/external_interaction/callbacks/logging_callback.py,sha256=W5jO7uHvi84FmEwVC_wNk93oc3-SJ8pZq61cYS-qalU,708
99
101
  mx_bluesky/hyperion/external_interaction/callbacks/plan_reactive_callback.py,sha256=7UtkXZluAag_Mx_JnESd7ODPSN3WrcNCgN8mquIpxjE,4086
100
102
  mx_bluesky/hyperion/external_interaction/callbacks/zocalo_callback.py,sha256=Jwf_jFLnC1lRm-WIlZ6ZozbQyw_SHtXK_5HUA5RT7Fw,3960
101
103
  mx_bluesky/hyperion/external_interaction/callbacks/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
102
- mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py,sha256=Db3ZfUPaOFv53umd8b8kVIHOb4Pxugw2hmBGA75yB08,1512
103
- mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py,sha256=8cgmMDGTNuD8Y-bNWytzBRv8Kjy8lrVO4Mq0dnEtUFE,2797
104
- mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py,sha256=PGM3fFwM0iCOiEiAlSMSkr79TD-M6lSXOocvoA4LsX0,3696
104
+ mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py,sha256=CjB3SOITmwv9I7pokwN0pmPomDAri7fJrg7Ww2kpXPo,1981
105
+ mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py,sha256=g7g_YmW3ZqFSN_uIXV-gONoMWS_mrQUdv51vJ2UOe0s,2584
106
+ mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py,sha256=R60oJdMwx8l0qqfRoyNrcj7Qg0fL0VU9IDm0YzR8xi0,3580
105
107
  mx_bluesky/hyperion/external_interaction/callbacks/rotation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
106
108
  mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py,sha256=tYc5B1UN8K_69rRBhx1KjpLv_r6htRJxjG05sAIFKvg,7220
107
109
  mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py,sha256=gXub8lt04ClC4sU_QGaBW4w3wrH8hJK7Tjy0AY4zf7g,718
@@ -114,23 +116,25 @@ mx_bluesky/hyperion/external_interaction/ispyb/__init__.py,sha256=47DEQpj8HBSa-_
114
116
  mx_bluesky/hyperion/external_interaction/ispyb/data_model.py,sha256=7P2Tz3Y9k-vKSGsdk8ppvKg15z1iBGGKbw3RNv_8y-E,2632
115
117
  mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py,sha256=ekMaDRkshbC72_hY0VWjaBA5b1vQgUfHoYBW22wEzNw,4487
116
118
  mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py,sha256=K71lAHoqMBh-ayqFdfafIwRd8AIqnyj6LR6rzk00nl8,10318
117
- mx_bluesky/hyperion/external_interaction/ispyb/ispyb_utils.py,sha256=WbfYhECi44bgaR0ciF8ME8T-leQ8WoczqiNpGGLRzOU,780
119
+ mx_bluesky/hyperion/external_interaction/ispyb/ispyb_utils.py,sha256=2Vl5qvGoCvbZ-lJFJSSNpTkbE4YfHAhy79jP7Oap6gg,720
118
120
  mx_bluesky/hyperion/external_interaction/nexus/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
119
121
  mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py,sha256=dVeKUc92TQcO1rpssqTsufwzlIdlHUu2e6zWm4mUYqs,5131
120
122
  mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py,sha256=L67UUdauzbXqsf2GJveGT6NP5YxbSzvcD0RjK5zrErI,4136
121
123
  mx_bluesky/hyperion/parameters/__init__.py,sha256=kf2wfcILBUBpT0tJ8-W39BywQUkn67yxl9IVsfrr1LE,115
122
124
  mx_bluesky/hyperion/parameters/cli.py,sha256=FGzvEB76YnQvBydUMPUNHJuqxvEe7Zc627w7gA2KODY,2125
123
- mx_bluesky/hyperion/parameters/components.py,sha256=5na3ka5xhPLrQcgVGbkIhZLFPtkEGAA3iPjagC5jqpw,7239
125
+ mx_bluesky/hyperion/parameters/components.py,sha256=ayIH6QLX15Qeebbz5W66kXUpLpH70JuwdF2E3YyjEmE,7171
124
126
  mx_bluesky/hyperion/parameters/constants.py,sha256=U21htONRj9Tvr7nyRbnrSyW6SRAADIkS0wLU0A3gvUQ,4458
125
- mx_bluesky/hyperion/parameters/gridscan.py,sha256=iWKxMPI2piEzC5Hsr2uBzBq7tSndzTpgxPQX9vGPOAw,8107
127
+ mx_bluesky/hyperion/parameters/gridscan.py,sha256=eTBsjMmzRpS5BacbDmRhH4uncLS96EKrMgKQ1u4Chpg,8310
128
+ mx_bluesky/hyperion/parameters/load_centre_collect.py,sha256=Q0TpVoAZeqPvRBayWPacU3wOWdawmlqhB7361xRKVvc,1583
129
+ mx_bluesky/hyperion/parameters/robot_load.py,sha256=nWiFrakYIn55Y9FDM10TN3Leq5TsAVFpDfY59FU3pIw,435
126
130
  mx_bluesky/hyperion/parameters/rotation.py,sha256=HP25ZPlM0U7rRsUxUQFvBPs5pI5NWE2PHSlCg3fI_2Y,5602
127
131
  mx_bluesky/hyperion/resources/panda/panda-gridscan.yaml,sha256=xrhgok-9X2VXdyLEWiYOUEeRCBqhYM6IJFAefnNdgzw,26521
128
132
  mx_bluesky/hyperion/utils/context.py,sha256=TYG3e4lu88ssngoNbsR5pfEXI0DveDemLWaBuThtvbI,2710
129
133
  mx_bluesky/hyperion/utils/utils.py,sha256=GoxupsUcAhGg0TmK_KMgGSB2rDDSd8tV5zGGLOfiAKk,731
130
134
  mx_bluesky/hyperion/utils/validation.py,sha256=Cw8TDODble039-lqnSUmFu01p9jdgf-bchcTkByRxok,6569
131
- mx_bluesky-1.1.0.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
132
- mx_bluesky-1.1.0.dist-info/METADATA,sha256=jHAAxwbqoobEvOQmiiceJoRVKYTFZDyjPoJwrWGv92g,17163
133
- mx_bluesky-1.1.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
134
- mx_bluesky-1.1.0.dist-info/entry_points.txt,sha256=Bt3iHCqzZtHYnnSKoh_qM2k3K4LbK1Uxr0gF8B34F98,528
135
- mx_bluesky-1.1.0.dist-info/top_level.txt,sha256=S4rrzXIUef58ulf_04wn01XGZ3xeJjXs4LPEJ_xoF-I,11
136
- mx_bluesky-1.1.0.dist-info/RECORD,,
135
+ mx_bluesky-1.2.0.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
136
+ mx_bluesky-1.2.0.dist-info/METADATA,sha256=6yz0owOp839jruq_KMLvJ2WlDWnlHZ1pgz7mLenyKKI,17163
137
+ mx_bluesky-1.2.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
138
+ mx_bluesky-1.2.0.dist-info/entry_points.txt,sha256=Bt3iHCqzZtHYnnSKoh_qM2k3K4LbK1Uxr0gF8B34F98,528
139
+ mx_bluesky-1.2.0.dist-info/top_level.txt,sha256=S4rrzXIUef58ulf_04wn01XGZ3xeJjXs4LPEJ_xoF-I,11
140
+ mx_bluesky-1.2.0.dist-info/RECORD,,