mx-bluesky 1.5.8__py3-none-any.whl → 1.5.10__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 (40) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +35 -12
  3. mx_bluesky/beamlines/i24/jungfrau_commissioning/experiment_plans/__init__.py +0 -0
  4. mx_bluesky/beamlines/i24/jungfrau_commissioning/experiment_plans/do_darks.py +78 -0
  5. mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/__init__.py +1 -0
  6. mx_bluesky/beamlines/i24/jungfrau_commissioning/{do_external_acquisition.py → plan_stubs/do_external_acquisition.py} +7 -6
  7. mx_bluesky/beamlines/i24/jungfrau_commissioning/{do_internal_acquisition.py → plan_stubs/do_internal_acquisition.py} +4 -3
  8. mx_bluesky/beamlines/i24/jungfrau_commissioning/{plan_utils.py → plan_stubs/plan_utils.py} +21 -28
  9. mx_bluesky/common/device_setup_plans/__init__.py +0 -0
  10. mx_bluesky/common/device_setup_plans/manipulate_sample.py +2 -2
  11. mx_bluesky/common/device_setup_plans/robot_load_unload.py +10 -6
  12. mx_bluesky/common/device_setup_plans/setup_oav.py +1 -1
  13. mx_bluesky/common/device_setup_plans/setup_zebra_and_shutter.py +245 -0
  14. mx_bluesky/common/device_setup_plans/xbpm_feedback.py +4 -4
  15. mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +10 -22
  16. mx_bluesky/common/experiment_plans/inner_plans/__init__.py +0 -0
  17. mx_bluesky/common/experiment_plans/inner_plans/do_fgs.py +5 -3
  18. mx_bluesky/common/experiment_plans/inner_plans/read_hardware.py +3 -3
  19. mx_bluesky/common/experiment_plans/oav_grid_detection_plan.py +2 -2
  20. mx_bluesky/common/external_interaction/callbacks/common/ispyb_mapping.py +4 -3
  21. mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py +34 -25
  22. mx_bluesky/common/parameters/device_composites.py +2 -2
  23. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +2 -100
  24. mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +13 -12
  25. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +52 -38
  26. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +2 -2
  27. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +5 -3
  28. mx_bluesky/hyperion/external_interaction/callbacks/alert_on_container_change.py +14 -5
  29. mx_bluesky/hyperion/external_interaction/callbacks/robot_actions/__init__.py +0 -0
  30. mx_bluesky/hyperion/parameters/device_composites.py +2 -2
  31. mx_bluesky/hyperion/parameters/rotation.py +4 -6
  32. mx_bluesky/hyperion/utils/context.py +6 -1
  33. {mx_bluesky-1.5.8.dist-info → mx_bluesky-1.5.10.dist-info}/METADATA +4 -4
  34. {mx_bluesky-1.5.8.dist-info → mx_bluesky-1.5.10.dist-info}/RECORD +39 -33
  35. mx_bluesky/phase1_zebra/device_setup_plans/setup_zebra.py +0 -112
  36. /mx_bluesky/{common/experiment_plans/inner_plans/__init__ .py → beamlines/i04/callbacks/__init__.py} +0 -0
  37. {mx_bluesky-1.5.8.dist-info → mx_bluesky-1.5.10.dist-info}/WHEEL +0 -0
  38. {mx_bluesky-1.5.8.dist-info → mx_bluesky-1.5.10.dist-info}/entry_points.txt +0 -0
  39. {mx_bluesky-1.5.8.dist-info → mx_bluesky-1.5.10.dist-info}/licenses/LICENSE +0 -0
  40. {mx_bluesky-1.5.8.dist-info → mx_bluesky-1.5.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,245 @@
1
+ from typing import Protocol, runtime_checkable
2
+
3
+ import bluesky.plan_stubs as bps
4
+ from bluesky.utils import MsgGenerator
5
+ from dodal.devices.zebra.zebra import (
6
+ ArmDemand,
7
+ EncEnum,
8
+ I03Axes,
9
+ RotationDirection,
10
+ Zebra,
11
+ )
12
+ from dodal.devices.zebra.zebra_controlled_shutter import (
13
+ ZebraShutter,
14
+ ZebraShutterControl,
15
+ )
16
+
17
+ from mx_bluesky.common.parameters.constants import ZEBRA_STATUS_TIMEOUT
18
+ from mx_bluesky.common.utils.log import LOGGER
19
+
20
+ """Plans in this file will work as intended if the zebra has the following configuration:
21
+ - A fast shutter is connected through TTL inputs from the Zebra.
22
+ - When the zebra shutter is set to auto mode, the IOC sets the Zebra's SOFT_IN1 signal high.
23
+ - When the zebra shutter is set to manual mode, the IOC sets the Zebra's SOFT_IN1 signal low.
24
+ """
25
+
26
+
27
+ @runtime_checkable
28
+ class GridscanSetupDevices(Protocol):
29
+ zebra: Zebra
30
+ sample_shutter: ZebraShutter
31
+
32
+
33
+ def setup_zebra_for_gridscan(
34
+ composite: GridscanSetupDevices, # XRC gridscan's generic trigger setup expects a composite rather than individual devices
35
+ group="setup_zebra_for_gridscan",
36
+ wait=True,
37
+ ttl_input_for_detector_to_use: None | int = None,
38
+ ) -> MsgGenerator:
39
+ """
40
+ Configure the zebra for an MX XRC gridscan by allowing the zebra to trigger the fast shutter and detector via signals
41
+ sent from the motion controller.
42
+
43
+ Args:
44
+ composite: Composite device containing a zebra and zebra shutter
45
+ group: Bluesky group to use when waiting on completion
46
+ wait: If true, block until completion
47
+ ttl_input_for_detector_to_use: If the zebra isn't using the TTL_DETECTOR zebra input, manually
48
+ specify which TTL input is being used for the desired detector
49
+
50
+ This plan assumes that the motion controller, as part of its gridscan PLC, will send triggers as required to the zebra's
51
+ IN4_TTL and IN3_TTL to control the fast_shutter and detector respectively
52
+
53
+ """
54
+ zebra = composite.zebra
55
+ ttl_detector = ttl_input_for_detector_to_use or zebra.mapping.outputs.TTL_DETECTOR
56
+ # Set shutter to automatic and to trigger via motion controller GPIO signal (IN4_TTL)
57
+ yield from configure_zebra_and_shutter_for_auto_shutter(
58
+ zebra, composite.sample_shutter, zebra.mapping.sources.IN4_TTL, group=group
59
+ )
60
+
61
+ yield from bps.abs_set(
62
+ zebra.output.out_pvs[ttl_detector],
63
+ zebra.mapping.sources.IN3_TTL,
64
+ group=group,
65
+ )
66
+
67
+ if wait:
68
+ yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
69
+
70
+
71
+ def set_shutter_auto_input(zebra: Zebra, input: int, group="set_shutter_trigger"):
72
+ """Set the signal that controls the shutter. We use the second input to the
73
+ Zebra's AND_GATE_FOR_AUTO_SHUTTER for this input. ZebraShutter control mode must be in auto for this input to take control
74
+
75
+ For more details see the ZebraShutter device."""
76
+ auto_gate = zebra.mapping.AND_GATE_FOR_AUTO_SHUTTER
77
+ auto_shutter_control = zebra.logic_gates.and_gates[auto_gate]
78
+ yield from bps.abs_set(auto_shutter_control.sources[2], input, group)
79
+
80
+
81
+ def configure_zebra_and_shutter_for_auto_shutter(
82
+ zebra: Zebra, zebra_shutter: ZebraShutter, input: int, group="use_automatic_shutter"
83
+ ):
84
+ """Set the shutter to auto mode, and configure the zebra to trigger the shutter on
85
+ an input source. For the input, use one of the source constants in zebra.py
86
+
87
+ When the shutter is in auto/manual, logic in EPICS sets the Zebra's
88
+ SOFT_IN1 to low/high respectively. The Zebra's AND_GATE_FOR_AUTO_SHUTTER should be used to control the shutter while in auto mode.
89
+ To do this, we need (AND_GATE_FOR_AUTO_SHUTTER = SOFT_IN1 AND input), where input is the zebra signal we want to control the shutter when in auto mode.
90
+ """
91
+
92
+ # Set shutter to auto mode
93
+ yield from bps.abs_set(
94
+ zebra_shutter.control_mode, ZebraShutterControl.AUTO, group=group
95
+ )
96
+
97
+ auto_gate = zebra.mapping.AND_GATE_FOR_AUTO_SHUTTER
98
+
99
+ # Set first input of AND_GATE_FOR_AUTO_SHUTTER to SOFT_IN1, which is high when shutter is in auto mode
100
+ # Note the Zebra should ALWAYS be setup this way. See https://github.com/DiamondLightSource/mx-bluesky/issues/551
101
+ yield from bps.abs_set(
102
+ zebra.logic_gates.and_gates[auto_gate].sources[1],
103
+ zebra.mapping.sources.SOFT_IN1,
104
+ group=group,
105
+ )
106
+
107
+ # Set the second input of AND_GATE_FOR_AUTO_SHUTTER to the requested zebra input source
108
+ yield from set_shutter_auto_input(zebra, input, group=group)
109
+
110
+
111
+ def tidy_up_zebra_after_gridscan(
112
+ zebra: Zebra,
113
+ zebra_shutter: ZebraShutter,
114
+ group="tidy_up_zebra_after_gridscan",
115
+ wait=True,
116
+ ttl_input_for_detector_to_use: int | None = None,
117
+ ) -> MsgGenerator:
118
+ """
119
+ Set the zebra back to a state which is expected by GDA.
120
+
121
+ Args:
122
+ zebra: Zebra device.
123
+ zebra_shutter: Zebra shutter device.
124
+ group: Bluesky group to use when waiting on completion.
125
+ wait: If true, block until completion.
126
+ ttl_input_for_detector_to_use: If the zebra isn't using the TTL_DETECTOR zebra input, manually
127
+ specify which TTL input is being used for the desired detector.
128
+ """
129
+
130
+ LOGGER.info("Tidying up Zebra")
131
+
132
+ ttl_detector = ttl_input_for_detector_to_use or zebra.mapping.outputs.TTL_DETECTOR
133
+
134
+ yield from bps.abs_set(
135
+ zebra.output.out_pvs[ttl_detector],
136
+ zebra.mapping.sources.PC_PULSE,
137
+ group=group,
138
+ )
139
+ yield from bps.abs_set(
140
+ zebra_shutter.control_mode, ZebraShutterControl.MANUAL, group=group
141
+ )
142
+ yield from set_shutter_auto_input(zebra, zebra.mapping.sources.PC_GATE, group=group)
143
+
144
+ if wait:
145
+ yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
146
+
147
+
148
+ def setup_zebra_for_rotation(
149
+ zebra: Zebra,
150
+ zebra_shutter: ZebraShutter,
151
+ axis: EncEnum = I03Axes.OMEGA,
152
+ start_angle: float = 0,
153
+ scan_width: float = 360,
154
+ shutter_opening_deg: float = 2.5,
155
+ shutter_opening_s: float = 0.04,
156
+ direction: RotationDirection = RotationDirection.POSITIVE,
157
+ group: str = "setup_zebra_for_rotation",
158
+ wait: bool = True,
159
+ ttl_input_for_detector_to_use: int | None = None,
160
+ ):
161
+ """Set up the Zebra to collect a rotation dataset. Any plan using this is
162
+ responsible for setting the smargon velocity appropriately so that the desired
163
+ image width is achieved with the exposure time given here.
164
+
165
+ Parameters:
166
+ zebra: The zebra device to use
167
+ axis: Encoder enum representing which axis to use for position
168
+ compare. Currently always omega.
169
+ start_angle: Position at which the scan should begin, in degrees.
170
+ scan_width: Total angle through which to collect, in degrees.
171
+ shutter_opening_deg:How many degrees of rotation it takes for the fast shutter
172
+ to open. Increases the gate width.
173
+ shutter_opening_s: How many seconds it takes for the fast shutter to open. The
174
+ detector pulse is delayed after the shutter signal by this
175
+ amount.
176
+ direction: RotationDirection enum for positive or negative.
177
+ Defaults to Positive.
178
+ group: A name for the group of statuses generated
179
+ wait: Block until all the settings have completed
180
+ ttl_input_for_detector_to_use: If the zebra isn't using the TTL_DETECTOR zebra input,
181
+ manually specify which TTL input is being used for the desired detector
182
+ """
183
+
184
+ ttl_detector = ttl_input_for_detector_to_use or zebra.mapping.outputs.TTL_DETECTOR
185
+
186
+ if not isinstance(direction, RotationDirection):
187
+ raise ValueError(
188
+ "Disallowed rotation direction provided to Zebra setup plan. "
189
+ "Use RotationDirection.POSITIVE or RotationDirection.NEGATIVE."
190
+ )
191
+ yield from bps.abs_set(zebra.pc.dir, direction.value, group=group)
192
+ LOGGER.info("ZEBRA SETUP: START")
193
+ # Set gate start, adjust for shutter opening time if necessary
194
+ LOGGER.info(f"ZEBRA SETUP: degrees to adjust for shutter = {shutter_opening_deg}")
195
+ LOGGER.info(f"ZEBRA SETUP: start angle start: {start_angle}")
196
+ LOGGER.info(f"ZEBRA SETUP: start angle adjusted, gate start set to: {start_angle}")
197
+ yield from bps.abs_set(zebra.pc.gate_start, start_angle, group=group)
198
+ # set gate width to total width
199
+ yield from bps.abs_set(
200
+ zebra.pc.gate_width, scan_width + shutter_opening_deg, group=group
201
+ )
202
+ LOGGER.info(
203
+ f"Pulse start set to shutter open time, set to: {abs(shutter_opening_s)}"
204
+ )
205
+ yield from bps.abs_set(zebra.pc.pulse_start, abs(shutter_opening_s), group=group)
206
+ # Set gate position to be angle of interest
207
+ yield from bps.abs_set(zebra.pc.gate_trigger, axis.value, group=group)
208
+ # Set shutter to automatic and to trigger via PC_GATE
209
+ yield from configure_zebra_and_shutter_for_auto_shutter(
210
+ zebra, zebra_shutter, zebra.mapping.sources.PC_GATE, group=group
211
+ )
212
+ # Trigger the detector with a pulse
213
+ yield from bps.abs_set(
214
+ zebra.output.out_pvs[ttl_detector],
215
+ zebra.mapping.sources.PC_PULSE,
216
+ group=group,
217
+ )
218
+
219
+ LOGGER.info(f"ZEBRA SETUP: END - {'' if wait else 'not'} waiting for completion")
220
+ if wait:
221
+ yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
222
+
223
+
224
+ def tidy_up_zebra_after_rotation_scan(
225
+ zebra: Zebra,
226
+ zebra_shutter: ZebraShutter,
227
+ group="tidy_up_zebra_after_rotation",
228
+ wait=True,
229
+ ):
230
+ """
231
+ Set the zebra back to a state which is expected by GDA.
232
+
233
+ Args:
234
+ zebra: Zebra device.
235
+ zebra_shutter: Zebra shutter device.
236
+ group: Bluesky group to use when waiting on completion.
237
+ wait: If true, block until completion.
238
+ """
239
+
240
+ yield from bps.abs_set(zebra.pc.arm, ArmDemand.DISARM, group=group)
241
+ yield from bps.abs_set(
242
+ zebra_shutter.control_mode, ZebraShutterControl.MANUAL, group=group
243
+ )
244
+ if wait:
245
+ yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
@@ -16,7 +16,7 @@ def unpause_xbpm_feedback_and_set_transmission_to_1(
16
16
  the beam in position
17
17
  attenuator (BinaryFilterAttenuator): The attenuator used to set transmission
18
18
  """
19
- yield from bps.mv(xbpm_feedback.pause_feedback, Pause.RUN, attenuator, 1.0) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
19
+ yield from bps.mv(xbpm_feedback.pause_feedback, Pause.RUN, attenuator, 1.0)
20
20
 
21
21
 
22
22
  def check_and_pause_feedback(
@@ -35,11 +35,11 @@ def check_and_pause_feedback(
35
35
  turning XBPM feedback off.
36
36
 
37
37
  """
38
- yield from bps.mv(attenuator, 1.0) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
38
+ yield from bps.mv(attenuator, 1.0)
39
39
  LOGGER.info("Waiting for XBPM feedback to be stable")
40
40
  yield from bps.trigger(xbpm_feedback, wait=True)
41
41
  LOGGER.info(
42
42
  f"XPBM feedback in position, pausing and setting transmission to {desired_transmission_fraction}"
43
43
  )
44
- yield from bps.mv(xbpm_feedback.pause_feedback, Pause.PAUSE) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
45
- yield from bps.mv(attenuator, desired_transmission_fraction) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
44
+ yield from bps.mv(xbpm_feedback.pause_feedback, Pause.PAUSE)
45
+ yield from bps.mv(attenuator, desired_transmission_fraction)
@@ -8,11 +8,12 @@ import bluesky.plan_stubs as bps
8
8
  import bluesky.preprocessors as bpp
9
9
  import numpy as np
10
10
  from bluesky.protocols import Readable
11
- from bluesky.utils import MsgGenerator
11
+ from bluesky.utils import FailedStatus, MsgGenerator
12
12
  from dodal.common.beamlines.commissioning_mode import read_commissioning_mode
13
13
  from dodal.devices.fast_grid_scan import (
14
14
  FastGridScanCommon,
15
15
  FastGridScanThreeD,
16
+ GridScanInvalidError,
16
17
  )
17
18
  from dodal.devices.zocalo import ZocaloResults
18
19
  from dodal.devices.zocalo.zocalo_results import (
@@ -270,14 +271,18 @@ def run_gridscan(
270
271
  yield from beamline_specific.read_pre_flyscan_plan()
271
272
 
272
273
  LOGGER.info("Setting fgs params")
273
- yield from beamline_specific.set_flyscan_params_plan()
274
274
 
275
- LOGGER.info("Waiting for gridscan validity check")
276
- yield from wait_for_gridscan_valid(beamline_specific.fgs_motors)
275
+ try:
276
+ yield from beamline_specific.set_flyscan_params_plan()
277
+ except FailedStatus as e:
278
+ if isinstance(e.__cause__, GridScanInvalidError):
279
+ raise SampleException(
280
+ "Scan invalid - gridscan not valid for detected pin position"
281
+ ) from e
277
282
 
278
283
  LOGGER.info("Waiting for arming to finish")
279
284
  yield from bps.wait(PlanGroupCheckpointConstants.GRID_READY_FOR_DC)
280
- yield from bps.stage(fgs_composite.eiger) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
285
+ yield from bps.stage(fgs_composite.eiger, wait=True)
281
286
 
282
287
  yield from kickoff_and_complete_gridscan(
283
288
  beamline_specific.fgs_motors,
@@ -293,23 +298,6 @@ def run_gridscan(
293
298
  yield from bps.abs_set(beamline_specific.fgs_motors.z_steps, 0, wait=False)
294
299
 
295
300
 
296
- def wait_for_gridscan_valid(fgs_motors: FastGridScanCommon, timeout=0.5):
297
- LOGGER.info("Waiting for valid fgs_params")
298
- SLEEP_PER_CHECK = 0.1
299
- times_to_check = int(timeout / SLEEP_PER_CHECK)
300
- for _ in range(times_to_check):
301
- scan_invalid = yield from bps.rd(fgs_motors.scan_invalid)
302
- pos_counter = yield from bps.rd(fgs_motors.position_counter)
303
- LOGGER.debug(
304
- f"Scan invalid: {scan_invalid} and position counter: {pos_counter}"
305
- )
306
- if not scan_invalid and pos_counter == 0:
307
- LOGGER.info("Gridscan scan valid and position counter reset")
308
- return
309
- yield from bps.sleep(SLEEP_PER_CHECK)
310
- raise SampleException("Scan invalid - pin too long/short/bent and out of range")
311
-
312
-
313
301
  def _xrc_result_in_boxes_to_result_in_mm(
314
302
  xrc_result: XrcResult, parameters: SpecifiedThreeDGridScan
315
303
  ) -> XRayCentreResult:
@@ -94,14 +94,16 @@ def kickoff_and_complete_gridscan(
94
94
  md={
95
95
  "subplan_name": plan_name,
96
96
  "omega_to_scan_spec": {
97
- GridscanPlane.OMEGA_XY: scan_points[0],
98
- GridscanPlane.OMEGA_XZ: scan_points[1],
97
+ # These have to be cast to strings due to a bug in orsjon. See
98
+ # https://github.com/ijl/orjson/issues/414
99
+ str(GridscanPlane.OMEGA_XY): scan_points[0],
100
+ str(GridscanPlane.OMEGA_XZ): scan_points[1],
99
101
  },
100
102
  }
101
103
  )
102
104
  @bpp.contingency_decorator(
103
105
  except_plan=lambda e: (yield from bps.stop(detector)), # type: ignore # Fix types in ophyd-async (https://github.com/DiamondLightSource/mx-bluesky/issues/855)
104
- else_plan=lambda: (yield from bps.unstage(detector)),
106
+ else_plan=lambda: (yield from bps.unstage(detector, wait=True)),
105
107
  )
106
108
  def _decorated_do_fgs():
107
109
  yield from _wait_for_zocalo_to_stage_then_do_fgs(
@@ -4,7 +4,7 @@ import bluesky.plan_stubs as bps
4
4
  from bluesky.protocols import Readable
5
5
  from dodal.devices.aperturescatterguard import ApertureScatterguard
6
6
  from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
7
- from dodal.devices.common_dcm import BaseDCM
7
+ from dodal.devices.common_dcm import DoubleCrystalMonochromator
8
8
  from dodal.devices.eiger import EigerDetector
9
9
  from dodal.devices.flux import Flux
10
10
  from dodal.devices.s4_slit_gaps import S4SlitGaps
@@ -43,7 +43,7 @@ def standard_read_hardware_pre_collection(
43
43
  undulator: Undulator,
44
44
  synchrotron: Synchrotron,
45
45
  s4_slit_gaps: S4SlitGaps,
46
- dcm: BaseDCM,
46
+ dcm: DoubleCrystalMonochromator,
47
47
  smargon: Smargon,
48
48
  ):
49
49
  LOGGER.info("Reading status of beamline for callbacks, pre collection.")
@@ -63,7 +63,7 @@ def standard_read_hardware_during_collection(
63
63
  aperture_scatterguard: ApertureScatterguard,
64
64
  attenuator: BinaryFilterAttenuator,
65
65
  flux: Flux,
66
- dcm: BaseDCM,
66
+ dcm: DoubleCrystalMonochromator,
67
67
  detector: EigerDetector,
68
68
  ):
69
69
  signals_to_read_during_collection = [
@@ -10,7 +10,7 @@ from bluesky.utils import MsgGenerator
10
10
  from dodal.devices.oav.oav_detector import OAV
11
11
  from dodal.devices.oav.pin_image_recognition import PinTipDetection
12
12
  from dodal.devices.oav.pin_image_recognition.utils import NONE_VALUE
13
- from dodal.devices.oav.utils import PinNotFoundException, wait_for_tip_to_be_found
13
+ from dodal.devices.oav.utils import PinNotFoundError, wait_for_tip_to_be_found
14
14
  from dodal.devices.smargon import Smargon
15
15
 
16
16
  from mx_bluesky.common.device_setup_plans.setup_oav import (
@@ -108,7 +108,7 @@ def grid_detection_plan(
108
108
  yield from bps.sleep(HardwareConstants.OAV_REFRESH_DELAY)
109
109
 
110
110
  tip_x_px, tip_y_px = yield from catch_exception_and_warn(
111
- PinNotFoundException, wait_for_tip_to_be_found, pin_tip_detection
111
+ PinNotFoundError, wait_for_tip_to_be_found, pin_tip_detection
112
112
  )
113
113
 
114
114
  LOGGER.info(f"Tip is at x,y: {tip_x_px},{tip_y_px}")
@@ -50,9 +50,10 @@ def populate_remaining_data_collection_info(
50
50
  data_collection_info.xbeam = beam_position[0]
51
51
  data_collection_info.ybeam = beam_position[1]
52
52
  data_collection_info.start_time = get_current_time_string()
53
- # temporary file template until nxs filewriting is integrated and we can use
54
- # that file name
55
- data_collection_info.file_template = f"{params.detector_params.prefix}_{data_collection_info.data_collection_number}_master.h5"
53
+ if data_collection_info.data_collection_number is not None:
54
+ # Do not write the file template if we don't have sufficient information - for gridscans we may not
55
+ # know the data collection number until later
56
+ data_collection_info.file_template = f"{params.detector_params.prefix}_{data_collection_info.data_collection_number}_master.h5"
56
57
  return data_collection_info
57
58
 
58
59
 
@@ -105,6 +105,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
105
105
  self._start_of_fgs_uid: str | None = None
106
106
  self._processing_start_time: float | None = None
107
107
  self._grid_plane_to_id_map: dict[GridscanPlane, int] = {}
108
+ self._grid_plane_to_width_map: dict[GridscanPlane, int] = {}
108
109
  self.data_collection_group_info: DataCollectionGroupInfo | None
109
110
 
110
111
  def activity_gated_start(self, doc: RunStart):
@@ -131,9 +132,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
131
132
  data_collection_info=populate_remaining_data_collection_info(
132
133
  "MX-Bluesky: Xray centring 1 -",
133
134
  None,
134
- DataCollectionInfo(
135
- data_collection_number=self.params.detector_params.run_number,
136
- ),
135
+ DataCollectionInfo(),
137
136
  self.params,
138
137
  ),
139
138
  ),
@@ -141,11 +140,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
141
140
  data_collection_info=populate_remaining_data_collection_info(
142
141
  "MX-Bluesky: Xray centring 2 -",
143
142
  None,
144
- DataCollectionInfo(
145
- data_collection_number=(
146
- self.params.detector_params.run_number + 1
147
- ),
148
- ),
143
+ DataCollectionInfo(),
149
144
  self.params,
150
145
  )
151
146
  ),
@@ -192,6 +187,15 @@ class GridscanISPyBCallback(BaseISPyBCallback):
192
187
  assert self.params, "ISPyB handler didn't receive parameters!"
193
188
  assert self.data_collection_group_info, "No data collection group"
194
189
  data = doc["data"]
190
+ omega = doc["data"]["smargon-omega"]
191
+ grid_plane = _smargon_omega_to_xyxz_plane(omega)
192
+ ISPYB_ZOCALO_CALLBACK_LOGGER.info(
193
+ f"Generating dc info for gridplane {grid_plane}, omega {omega}"
194
+ )
195
+ data_collection_number = self.data_collection_number_from_gridplane(grid_plane)
196
+ file_template = (
197
+ f"{self.params.detector_params.prefix}_{data_collection_number}_master.h5"
198
+ )
195
199
  data_collection_info = DataCollectionInfo(
196
200
  xtal_snapshot1=data.get("oav-grid_snapshot-last_path_full_overlay"),
197
201
  xtal_snapshot2=data.get("oav-grid_snapshot-last_path_outer"),
@@ -200,6 +204,8 @@ class GridscanISPyBCallback(BaseISPyBCallback):
200
204
  data["oav-grid_snapshot-num_boxes_x"]
201
205
  * data["oav-grid_snapshot-num_boxes_y"]
202
206
  ),
207
+ data_collection_number=data_collection_number,
208
+ file_template=file_template,
203
209
  )
204
210
  microns_per_pixel_x = data["oav-microns_per_pixel_x"]
205
211
  microns_per_pixel_y = data["oav-microns_per_pixel_y"]
@@ -219,21 +225,22 @@ class GridscanISPyBCallback(BaseISPyBCallback):
219
225
  data_collection_grid_info
220
226
  )
221
227
 
222
- if self.data_collection_group_info.comments:
223
- self.data_collection_group_info.comments += (
224
- f"by {data_collection_grid_info.steps_y}."
225
- )
226
- else:
227
- self.data_collection_group_info.comments = (
228
- f"Diffraction grid scan of "
229
- f"{data_collection_grid_info.steps_x} "
230
- f"by {data_collection_grid_info.steps_y} "
231
- )
232
-
228
+ # Snapshots may be triggered in a different order to gridscans, so save
229
+ # the mapping to the data collection id in order to trigger Zocalo correctly.
233
230
  data_collection_id = self.ispyb_ids.data_collection_ids[
234
- self._oav_snapshot_event_idx
231
+ 0 if grid_plane == GridscanPlane.OMEGA_XY else 1
235
232
  ]
236
- self._populate_axis_info(data_collection_info, doc["data"]["smargon-omega"])
233
+ self._grid_plane_to_id_map[grid_plane] = data_collection_id
234
+ self._grid_plane_to_width_map[grid_plane] = data_collection_grid_info.steps_y
235
+
236
+ y_steps = self._grid_plane_to_width_map.get(GridscanPlane.OMEGA_XY, "_")
237
+ z_steps = self._grid_plane_to_width_map.get(GridscanPlane.OMEGA_XZ, "_")
238
+ self.data_collection_group_info.comments = (
239
+ f"Diffraction grid scan of {data_collection_grid_info.steps_x} by "
240
+ f"{y_steps} by {z_steps}."
241
+ )
242
+
243
+ self._populate_axis_info(data_collection_info, omega)
237
244
 
238
245
  scan_data_info = ScanDataInfo(
239
246
  data_collection_info=data_collection_info,
@@ -243,10 +250,6 @@ class GridscanISPyBCallback(BaseISPyBCallback):
243
250
  ISPYB_ZOCALO_CALLBACK_LOGGER.info(
244
251
  "Updating ispyb data collection after oav snapshot."
245
252
  )
246
- grid_plane = _smargon_omega_to_xyxz_plane(doc["data"]["smargon-omega"])
247
- # Snapshots may be triggered in a different order to gridscans, so save
248
- # the mapping to the data collection id in order to trigger Zocalo correctly.
249
- self._grid_plane_to_id_map[grid_plane] = data_collection_id
250
253
 
251
254
  self._oav_snapshot_event_idx += 1
252
255
  return [scan_data_info]
@@ -315,6 +318,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
315
318
  )
316
319
  self.data_collection_group_info = None
317
320
  self._grid_plane_to_id_map.clear()
321
+ self._grid_plane_to_width_map.clear()
318
322
  return super().activity_gated_stop(doc)
319
323
  return self.tag_doc(doc)
320
324
 
@@ -325,6 +329,11 @@ class GridscanISPyBCallback(BaseISPyBCallback):
325
329
  doc["grid_plane_to_id_map"] = self._grid_plane_to_id_map
326
330
  return doc # type: ignore
327
331
 
332
+ def data_collection_number_from_gridplane(self, plane) -> int:
333
+ assert self.params
334
+ base_number = self.params.detector_params.run_number
335
+ return base_number if plane == GridscanPlane.OMEGA_XY else base_number + 1
336
+
328
337
 
329
338
  def generate_start_info_from_omega_map() -> ZocaloInfoGenerator:
330
339
  """
@@ -4,7 +4,7 @@ from dodal.devices.aperturescatterguard import (
4
4
  )
5
5
  from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
6
6
  from dodal.devices.backlight import Backlight
7
- from dodal.devices.common_dcm import BaseDCM
7
+ from dodal.devices.common_dcm import DoubleCrystalMonochromator
8
8
  from dodal.devices.detector.detector_motion import DetectorMotion
9
9
  from dodal.devices.eiger import EigerDetector
10
10
  from dodal.devices.fast_grid_scan import (
@@ -51,7 +51,7 @@ class GridDetectThenXRayCentreComposite(FlyScanEssentialDevices):
51
51
  attenuator: BinaryFilterAttenuator
52
52
  backlight: Backlight
53
53
  beamstop: Beamstop
54
- dcm: BaseDCM
54
+ dcm: DoubleCrystalMonochromator
55
55
  detector_motion: DetectorMotion
56
56
  zebra_fast_grid_scan: ZebraFastGridScanThreeD
57
57
  flux: Flux
@@ -1,120 +1,22 @@
1
1
  import bluesky.plan_stubs as bps
2
2
  from dodal.devices.zebra.zebra import (
3
3
  ArmDemand,
4
- EncEnum,
5
- I03Axes,
6
- RotationDirection,
7
4
  Zebra,
8
5
  )
9
6
  from dodal.devices.zebra.zebra_controlled_shutter import (
10
7
  ZebraShutter,
11
- ZebraShutterControl,
12
8
  )
13
9
 
14
- from mx_bluesky.common.parameters.constants import ZEBRA_STATUS_TIMEOUT
15
- from mx_bluesky.common.utils.log import LOGGER
16
- from mx_bluesky.phase1_zebra.device_setup_plans.setup_zebra import (
10
+ from mx_bluesky.common.device_setup_plans.setup_zebra_and_shutter import (
17
11
  configure_zebra_and_shutter_for_auto_shutter,
18
12
  )
13
+ from mx_bluesky.common.parameters.constants import ZEBRA_STATUS_TIMEOUT
19
14
 
20
15
 
21
16
  def arm_zebra(zebra: Zebra):
22
17
  yield from bps.abs_set(zebra.pc.arm, ArmDemand.ARM, wait=True)
23
18
 
24
19
 
25
- def tidy_up_zebra_after_rotation_scan(
26
- zebra: Zebra,
27
- zebra_shutter: ZebraShutter,
28
- group="tidy_up_zebra_after_rotation",
29
- wait=True,
30
- ):
31
- yield from bps.abs_set(zebra.pc.arm, ArmDemand.DISARM, group=group)
32
- yield from bps.abs_set(
33
- zebra_shutter.control_mode, ZebraShutterControl.MANUAL, group=group
34
- )
35
- if wait:
36
- yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
37
-
38
-
39
- def setup_zebra_for_rotation(
40
- zebra: Zebra,
41
- zebra_shutter: ZebraShutter,
42
- axis: EncEnum = I03Axes.OMEGA,
43
- start_angle: float = 0,
44
- scan_width: float = 360,
45
- shutter_opening_deg: float = 2.5,
46
- shutter_opening_s: float = 0.04,
47
- direction: RotationDirection = RotationDirection.POSITIVE,
48
- group: str = "setup_zebra_for_rotation",
49
- wait: bool = True,
50
- ):
51
- """Set up the Zebra to collect a rotation dataset. Any plan using this is
52
- responsible for setting the smargon velocity appropriately so that the desired
53
- image width is achieved with the exposure time given here.
54
-
55
- Parameters:
56
- zebra: The zebra device to use
57
- axis: I03 axes enum representing which axis to use for position
58
- compare. Currently always omega.
59
- start_angle: Position at which the scan should begin, in degrees.
60
- scan_width: Total angle through which to collect, in degrees.
61
- shutter_opening_deg:How many degrees of rotation it takes for the fast shutter
62
- to open. Increases the gate width.
63
- shutter_opening_s: How many seconds it takes for the fast shutter to open. The
64
- detector pulse is delayed after the shutter signal by this
65
- amount.
66
- direction: RotationDirection enum for positive or negative.
67
- Defaults to Positive.
68
- group: A name for the group of statuses generated
69
- wait: Block until all the settings have completed
70
- """
71
-
72
- if not isinstance(direction, RotationDirection):
73
- raise ValueError(
74
- "Disallowed rotation direction provided to Zebra setup plan. "
75
- "Use RotationDirection.POSITIVE or RotationDirection.NEGATIVE."
76
- )
77
- yield from bps.abs_set(zebra.pc.dir, direction.value, group=group)
78
- LOGGER.info("ZEBRA SETUP: START")
79
- # Set gate start, adjust for shutter opening time if necessary
80
- LOGGER.info(f"ZEBRA SETUP: degrees to adjust for shutter = {shutter_opening_deg}")
81
- LOGGER.info(f"ZEBRA SETUP: start angle start: {start_angle}")
82
- LOGGER.info(f"ZEBRA SETUP: start angle adjusted, gate start set to: {start_angle}")
83
- yield from bps.abs_set(zebra.pc.gate_start, start_angle, group=group)
84
- # set gate width to total width
85
- yield from bps.abs_set(
86
- zebra.pc.gate_width, scan_width + shutter_opening_deg, group=group
87
- )
88
- LOGGER.info(
89
- f"Pulse start set to shutter open time, set to: {abs(shutter_opening_s)}"
90
- )
91
- yield from bps.abs_set(zebra.pc.pulse_start, abs(shutter_opening_s), group=group)
92
- # Set gate position to be angle of interest
93
- yield from bps.abs_set(zebra.pc.gate_trigger, axis.value, group=group)
94
- # Set shutter to automatic and to trigger via PC_GATE
95
- yield from configure_zebra_and_shutter_for_auto_shutter(
96
- zebra, zebra_shutter, zebra.mapping.sources.PC_GATE, group=group
97
- )
98
- # Trigger the detector with a pulse
99
- yield from bps.abs_set(
100
- zebra.output.out_pvs[zebra.mapping.outputs.TTL_DETECTOR],
101
- zebra.mapping.sources.PC_PULSE,
102
- group=group,
103
- )
104
- # Don't use the fluorescence detector
105
- yield from bps.abs_set(
106
- zebra.output.out_pvs[zebra.mapping.outputs.TTL_XSPRESS3],
107
- zebra.mapping.sources.DISCONNECT,
108
- group=group,
109
- )
110
- yield from bps.abs_set(
111
- zebra.output.pulse_1.input, zebra.mapping.sources.DISCONNECT, group=group
112
- )
113
- LOGGER.info(f"ZEBRA SETUP: END - {'' if wait else 'not'} waiting for completion")
114
- if wait:
115
- yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
116
-
117
-
118
20
  def setup_zebra_for_panda_flyscan(
119
21
  zebra: Zebra,
120
22
  zebra_shutter: ZebraShutter,