mx-bluesky 1.5.6__py3-none-any.whl → 1.5.7__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 (30) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/beamlines/i02_1/parameters/__init__.py +0 -0
  3. mx_bluesky/beamlines/i02_1/parameters/gridscan.py +35 -0
  4. mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +6 -3
  5. mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +3 -1
  6. mx_bluesky/beamlines/i04/thawing_plan.py +15 -8
  7. mx_bluesky/beamlines/i24/jungfrau_commissioning/do_external_acquisition.py +44 -0
  8. mx_bluesky/beamlines/i24/jungfrau_commissioning/do_internal_acquisition.py +46 -0
  9. mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_utils.py +73 -0
  10. mx_bluesky/common/device_setup_plans/robot_load_unload.py +2 -1
  11. mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +4 -3
  12. mx_bluesky/common/experiment_plans/inner_plans/do_fgs.py +7 -8
  13. mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py +6 -6
  14. mx_bluesky/common/external_interaction/callbacks/common/zocalo_callback.py +30 -22
  15. mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py +73 -15
  16. mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_mapping.py +0 -20
  17. mx_bluesky/common/parameters/device_composites.py +2 -2
  18. mx_bluesky/common/parameters/gridscan.py +67 -49
  19. mx_bluesky/hyperion/device_setup_plans/smargon.py +13 -8
  20. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +2 -2
  21. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +10 -2
  22. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +37 -1
  23. mx_bluesky/hyperion/parameters/device_composites.py +2 -2
  24. mx_bluesky/hyperion/parameters/gridscan.py +3 -3
  25. {mx_bluesky-1.5.6.dist-info → mx_bluesky-1.5.7.dist-info}/METADATA +2 -2
  26. {mx_bluesky-1.5.6.dist-info → mx_bluesky-1.5.7.dist-info}/RECORD +30 -25
  27. {mx_bluesky-1.5.6.dist-info → mx_bluesky-1.5.7.dist-info}/WHEEL +0 -0
  28. {mx_bluesky-1.5.6.dist-info → mx_bluesky-1.5.7.dist-info}/entry_points.txt +0 -0
  29. {mx_bluesky-1.5.6.dist-info → mx_bluesky-1.5.7.dist-info}/licenses/LICENSE +0 -0
  30. {mx_bluesky-1.5.6.dist-info → mx_bluesky-1.5.7.dist-info}/top_level.txt +0 -0
@@ -1,23 +1,28 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Callable, Sequence
4
+ from enum import StrEnum
5
+ from math import isclose
4
6
  from time import time
5
7
  from typing import TYPE_CHECKING, Any, TypeVar
6
8
 
7
9
  from bluesky import preprocessors as bpp
8
10
  from bluesky.utils import MsgGenerator, make_decorator
11
+ from dodal.devices.zocalo import ZocaloStartInfo
9
12
 
10
13
  from mx_bluesky.common.external_interaction.callbacks.common.ispyb_callback_base import (
11
14
  BaseISPyBCallback,
15
+ D,
12
16
  )
13
17
  from mx_bluesky.common.external_interaction.callbacks.common.ispyb_mapping import (
14
18
  populate_data_collection_group,
15
19
  populate_remaining_data_collection_info,
16
20
  )
21
+ from mx_bluesky.common.external_interaction.callbacks.common.zocalo_callback import (
22
+ ZocaloInfoGenerator,
23
+ )
17
24
  from mx_bluesky.common.external_interaction.callbacks.xray_centre.ispyb_mapping import (
18
25
  construct_comment_for_gridscan,
19
- populate_xy_data_collection_info,
20
- populate_xz_data_collection_info,
21
26
  )
22
27
  from mx_bluesky.common.external_interaction.ispyb.data_model import (
23
28
  DataCollectionGridInfo,
@@ -33,14 +38,21 @@ from mx_bluesky.common.external_interaction.ispyb.ispyb_store import (
33
38
  )
34
39
  from mx_bluesky.common.parameters.components import DiffractionExperimentWithSample
35
40
  from mx_bluesky.common.parameters.constants import DocDescriptorNames, PlanNameConstants
36
- from mx_bluesky.common.parameters.gridscan import (
37
- GridCommon,
38
- )
41
+ from mx_bluesky.common.parameters.gridscan import GridCommon
39
42
  from mx_bluesky.common.utils.exceptions import (
40
43
  ISPyBDepositionNotMade,
41
44
  SampleException,
42
45
  )
43
46
  from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER, set_dcgid_tag
47
+ from mx_bluesky.common.utils.utils import number_of_frames_from_scan_spec
48
+
49
+ OMEGA_TOLERANCE = 1
50
+
51
+
52
+ class GridscanPlane(StrEnum):
53
+ OMEGA_XY = "0"
54
+ OMEGA_XZ = "90"
55
+
44
56
 
45
57
  if TYPE_CHECKING:
46
58
  from event_model import Event, RunStart, RunStop
@@ -89,10 +101,10 @@ class GridscanISPyBCallback(BaseISPyBCallback):
89
101
  ) -> None:
90
102
  super().__init__(emit=emit)
91
103
  self.ispyb: StoreInIspyb
92
- self.ispyb_ids: IspybIds = IspybIds()
93
104
  self.param_type = param_type
94
105
  self._start_of_fgs_uid: str | None = None
95
106
  self._processing_start_time: float | None = None
107
+ self._grid_plane_to_id_map: dict[GridscanPlane, int] = {}
96
108
  self.data_collection_group_info: DataCollectionGroupInfo | None
97
109
 
98
110
  def activity_gated_start(self, doc: RunStart):
@@ -108,6 +120,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
108
120
  mx_bluesky_parameters = doc.get("mx_bluesky_parameters")
109
121
  assert isinstance(mx_bluesky_parameters, str)
110
122
  self.params = self.param_type.model_validate_json(mx_bluesky_parameters)
123
+ assert isinstance(self.params, DiffractionExperimentWithSample)
111
124
  self.ispyb = StoreInIspyb(self.ispyb_config)
112
125
  self.data_collection_group_info = populate_data_collection_group(
113
126
  self.params
@@ -118,8 +131,8 @@ class GridscanISPyBCallback(BaseISPyBCallback):
118
131
  data_collection_info=populate_remaining_data_collection_info(
119
132
  "MX-Bluesky: Xray centring 1 -",
120
133
  None,
121
- populate_xy_data_collection_info(
122
- self.params.detector_params,
134
+ DataCollectionInfo(
135
+ data_collection_number=self.params.detector_params.run_number,
123
136
  ),
124
137
  self.params,
125
138
  ),
@@ -128,7 +141,11 @@ class GridscanISPyBCallback(BaseISPyBCallback):
128
141
  data_collection_info=populate_remaining_data_collection_info(
129
142
  "MX-Bluesky: Xray centring 2 -",
130
143
  None,
131
- populate_xz_data_collection_info(self.params.detector_params),
144
+ DataCollectionInfo(
145
+ data_collection_number=(
146
+ self.params.detector_params.run_number + 1
147
+ ),
148
+ ),
132
149
  self.params,
133
150
  )
134
151
  ),
@@ -175,7 +192,6 @@ class GridscanISPyBCallback(BaseISPyBCallback):
175
192
  assert self.params, "ISPyB handler didn't receive parameters!"
176
193
  assert self.data_collection_group_info, "No data collection group"
177
194
  data = doc["data"]
178
- data_collection_id = None
179
195
  data_collection_info = DataCollectionInfo(
180
196
  xtal_snapshot1=data.get("oav-grid_snapshot-last_path_full_overlay"),
181
197
  xtal_snapshot2=data.get("oav-grid_snapshot-last_path_outer"),
@@ -214,10 +230,9 @@ class GridscanISPyBCallback(BaseISPyBCallback):
214
230
  f"by {data_collection_grid_info.steps_y} "
215
231
  )
216
232
 
217
- if len(self.ispyb_ids.data_collection_ids) > self._oav_snapshot_event_idx:
218
- data_collection_id = self.ispyb_ids.data_collection_ids[
219
- self._oav_snapshot_event_idx
220
- ]
233
+ data_collection_id = self.ispyb_ids.data_collection_ids[
234
+ self._oav_snapshot_event_idx
235
+ ]
221
236
  self._populate_axis_info(data_collection_info, doc["data"]["smargon-omega"])
222
237
 
223
238
  scan_data_info = ScanDataInfo(
@@ -228,6 +243,11 @@ class GridscanISPyBCallback(BaseISPyBCallback):
228
243
  ISPYB_ZOCALO_CALLBACK_LOGGER.info(
229
244
  "Updating ispyb data collection after oav snapshot."
230
245
  )
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
+
231
251
  self._oav_snapshot_event_idx += 1
232
252
  return [scan_data_info]
233
253
 
@@ -294,5 +314,43 @@ class GridscanISPyBCallback(BaseISPyBCallback):
294
314
  self.ispyb_ids.data_collection_group_id,
295
315
  )
296
316
  self.data_collection_group_info = None
317
+ self._grid_plane_to_id_map.clear()
297
318
  return super().activity_gated_stop(doc)
298
- return self._tag_doc(doc)
319
+ return self.tag_doc(doc)
320
+
321
+ def tag_doc(self, doc: D) -> D:
322
+ doc = super().tag_doc(doc)
323
+ assert isinstance(doc, dict)
324
+ if self._grid_plane_to_id_map:
325
+ doc["grid_plane_to_id_map"] = self._grid_plane_to_id_map
326
+ return doc # type: ignore
327
+
328
+
329
+ def generate_start_info_from_omega_map() -> ZocaloInfoGenerator:
330
+ """
331
+ Generate the zocalo trigger info from bluesky runs where the frame number is
332
+ computed using metadata added to the document by the ISPyB callback and the
333
+ run start which together can be used to determine the correct frame numbering.
334
+ """
335
+ doc = yield []
336
+ omega_to_scan_spec = doc["omega_to_scan_spec"]
337
+ start_frame = 0
338
+ infos = []
339
+ for i, omega in enumerate([GridscanPlane.OMEGA_XY, GridscanPlane.OMEGA_XZ]):
340
+ frames = number_of_frames_from_scan_spec(omega_to_scan_spec[omega])
341
+ infos.append(
342
+ ZocaloStartInfo(
343
+ doc["grid_plane_to_id_map"][omega], None, start_frame, frames, i
344
+ )
345
+ )
346
+ start_frame += frames
347
+ yield infos
348
+
349
+
350
+ def _smargon_omega_to_xyxz_plane(smargon_omega: float) -> GridscanPlane:
351
+ modulo_180 = abs(smargon_omega) % 180
352
+ is_xy = isclose(modulo_180, 0, abs_tol=OMEGA_TOLERANCE)
353
+ assert is_xy or isclose(modulo_180, 90, abs_tol=OMEGA_TOLERANCE), (
354
+ f"Smargon snapshot omega not in tolerance of compass point {smargon_omega}"
355
+ )
356
+ return GridscanPlane.OMEGA_XY if is_xy else GridscanPlane.OMEGA_XZ
@@ -1,33 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import numpy
4
- from dodal.devices.detector import DetectorParams
5
4
  from dodal.devices.oav import utils as oav_utils
6
5
 
7
6
  from mx_bluesky.common.external_interaction.ispyb.data_model import (
8
7
  DataCollectionGridInfo,
9
- DataCollectionInfo,
10
8
  )
11
9
 
12
10
 
13
- def populate_xz_data_collection_info(detector_params: DetectorParams):
14
- assert (
15
- detector_params.omega_start is not None
16
- and detector_params.run_number is not None
17
- ), "StoreGridscanInIspyb failed to get parameters"
18
- run_number = detector_params.run_number + 1
19
- info = DataCollectionInfo(
20
- data_collection_number=run_number,
21
- )
22
- return info
23
-
24
-
25
- def populate_xy_data_collection_info(detector_params: DetectorParams):
26
- return DataCollectionInfo(
27
- data_collection_number=detector_params.run_number,
28
- )
29
-
30
-
31
11
  def construct_comment_for_gridscan(grid_info: DataCollectionGridInfo) -> str:
32
12
  assert grid_info is not None, "StoreGridScanInIspyb failed to get parameters"
33
13
 
@@ -8,7 +8,7 @@ from dodal.devices.common_dcm import BaseDCM
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 (
11
- ZebraFastGridScan,
11
+ ZebraFastGridScanThreeD,
12
12
  )
13
13
  from dodal.devices.flux import Flux
14
14
  from dodal.devices.mx_phase1.beamstop import Beamstop
@@ -53,7 +53,7 @@ class GridDetectThenXRayCentreComposite(FlyScanEssentialDevices):
53
53
  beamstop: Beamstop
54
54
  dcm: BaseDCM
55
55
  detector_motion: DetectorMotion
56
- zebra_fast_grid_scan: ZebraFastGridScan
56
+ zebra_fast_grid_scan: ZebraFastGridScanThreeD
57
57
  flux: Flux
58
58
  oav: OAV
59
59
  pin_tip_detection: PinTipDetection
@@ -1,15 +1,19 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from abc import abstractmethod
4
+ from typing import Generic, TypeVar
5
+
3
6
  from dodal.devices.aperturescatterguard import ApertureValue
4
7
  from dodal.devices.detector.det_dim_constants import EIGER2_X_9M_SIZE, EIGER2_X_16M_SIZE
5
8
  from dodal.devices.detector.detector import DetectorParams
6
9
  from dodal.devices.fast_grid_scan import (
7
- ZebraGridScanParams,
10
+ GridScanParamsCommon,
11
+ ZebraGridScanParamsThreeD,
8
12
  )
9
13
  from dodal.utils import get_beamline_name
10
14
  from pydantic import Field, PrivateAttr
11
15
  from scanspec.core import Path as ScanPath
12
- from scanspec.specs import Line, Static
16
+ from scanspec.specs import Concat, Line, Product, Static
13
17
 
14
18
  from mx_bluesky.common.parameters.components import (
15
19
  DiffractionExperimentWithSample,
@@ -33,6 +37,10 @@ DETECTOR_SIZE_PER_BEAMLINE = {
33
37
  "i04": EIGER2_X_16M_SIZE,
34
38
  }
35
39
 
40
+ GridScanParamType = TypeVar(
41
+ "GridScanParamType", bound=GridScanParamsCommon, covariant=True
42
+ )
43
+
36
44
 
37
45
  class GridCommon(
38
46
  DiffractionExperimentWithSample,
@@ -87,36 +95,80 @@ class GridCommon(
87
95
  )
88
96
 
89
97
 
90
- class SpecifiedGrid(XyzStarts, WithScan):
98
+ class SpecifiedGrid(GridCommon, XyzStarts, WithScan, Generic[GridScanParamType]):
91
99
  """A specified grid is one which has defined values for the start position,
92
100
  grid and box sizes, etc., as opposed to parameters for a plan which will create
93
101
  those parameters at some point (e.g. through optical pin detection)."""
94
102
 
103
+ grid1_omega_deg: float = Field(default=GridscanParamConstants.OMEGA_1)
104
+ x_step_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM)
105
+ y_step_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM)
106
+ x_steps: int = Field(gt=0)
107
+ y_steps: int = Field(gt=0)
108
+ _set_stub_offsets: bool = PrivateAttr(default_factory=lambda: False)
109
+
110
+ @property
111
+ @abstractmethod
112
+ def FGS_params(self) -> GridScanParamType: ...
113
+
114
+ def do_set_stub_offsets(self, value: bool):
115
+ self._set_stub_offsets = value
116
+
117
+ @property
118
+ def grid_1_spec(self):
119
+ x_end = self.x_start_um + self.x_step_size_um * (self.x_steps - 1)
120
+ y1_end = self.y_start_um + self.y_step_size_um * (self.y_steps - 1)
121
+ grid_1_x = Line("sam_x", self.x_start_um, x_end, self.x_steps)
122
+ grid_1_y = Line("sam_y", self.y_start_um, y1_end, self.y_steps)
123
+ grid_1_z = Static("sam_z", self.z_start_um)
124
+ return grid_1_y.zip(grid_1_z) * ~grid_1_x
125
+
126
+ @property
127
+ def scan_indices(self) -> list[int]:
128
+ """The first index of each gridscan, useful for writing nexus files/VDS"""
129
+ return [
130
+ 0,
131
+ len(ScanPath(self.grid_1_spec.calculate()).consume().midpoints["sam_x"]),
132
+ ]
133
+
134
+ @property
135
+ @abstractmethod
136
+ def scan_spec(self) -> Product[str] | Concat[str]:
137
+ """A fully specified ScanSpec object representing all grids, with x, y, z and
138
+ omega positions."""
139
+
140
+ @property
141
+ def scan_points(self):
142
+ """A list of all the points in the scan_spec."""
143
+ return ScanPath(self.scan_spec.calculate()).consume().midpoints
144
+
145
+ @property
146
+ def scan_points_first_grid(self):
147
+ """A list of all the points in the first grid scan."""
148
+ return ScanPath(self.grid_1_spec.calculate()).consume().midpoints
149
+
150
+ @property
151
+ def num_images(self) -> int:
152
+ return len(self.scan_points["sam_x"])
153
+
95
154
 
96
155
  class SpecifiedThreeDGridScan(
97
- GridCommon,
98
- SpecifiedGrid,
156
+ SpecifiedGrid[ZebraGridScanParamsThreeD],
99
157
  SplitScan,
100
158
  WithOptionalEnergyChange,
101
159
  ):
102
160
  """Parameters representing a so-called 3D grid scan, which consists of doing a
103
161
  gridscan in X and Y, followed by one in X and Z."""
104
162
 
105
- grid1_omega_deg: float = Field(default=GridscanParamConstants.OMEGA_1)
106
- grid2_omega_deg: float = Field(default=GridscanParamConstants.OMEGA_2)
107
- x_step_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM)
108
- y_step_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM)
163
+ z_steps: int = Field(gt=0)
109
164
  z_step_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM)
110
165
  y2_start_um: float
111
166
  z2_start_um: float
112
- x_steps: int = Field(gt=0)
113
- y_steps: int = Field(gt=0)
114
- z_steps: int = Field(gt=0)
115
- _set_stub_offsets: bool = PrivateAttr(default_factory=lambda: False)
167
+ grid2_omega_deg: float = Field(default=GridscanParamConstants.OMEGA_2)
116
168
 
117
169
  @property
118
- def FGS_params(self) -> ZebraGridScanParams:
119
- return ZebraGridScanParams(
170
+ def FGS_params(self) -> ZebraGridScanParamsThreeD:
171
+ return ZebraGridScanParamsThreeD(
120
172
  x_steps=self.x_steps,
121
173
  y_steps=self.y_steps,
122
174
  z_steps=self.z_steps,
@@ -133,18 +185,6 @@ class SpecifiedThreeDGridScan(
133
185
  transmission_fraction=self.transmission_frac,
134
186
  )
135
187
 
136
- def do_set_stub_offsets(self, value: bool):
137
- self._set_stub_offsets = value
138
-
139
- @property
140
- def grid_1_spec(self):
141
- x_end = self.x_start_um + self.x_step_size_um * (self.x_steps - 1)
142
- y1_end = self.y_start_um + self.y_step_size_um * (self.y_steps - 1)
143
- grid_1_x = Line("sam_x", self.x_start_um, x_end, self.x_steps)
144
- grid_1_y = Line("sam_y", self.y_start_um, y1_end, self.y_steps)
145
- grid_1_z = Static("sam_z", self.z_start_um)
146
- return grid_1_y.zip(grid_1_z) * ~grid_1_x
147
-
148
188
  @property
149
189
  def grid_2_spec(self):
150
190
  x_end = self.x_start_um + self.x_step_size_um * (self.x_steps - 1)
@@ -154,35 +194,13 @@ class SpecifiedThreeDGridScan(
154
194
  grid_2_y = Static("sam_y", self.y2_start_um)
155
195
  return grid_2_z.zip(grid_2_y) * ~grid_2_x
156
196
 
157
- @property
158
- def scan_indices(self):
159
- """The first index of each gridscan, useful for writing nexus files/VDS"""
160
- return [
161
- 0,
162
- len(ScanPath(self.grid_1_spec.calculate()).consume().midpoints["sam_x"]),
163
- ]
164
-
165
197
  @property
166
198
  def scan_spec(self):
167
199
  """A fully specified ScanSpec object representing both grids, with x, y, z and
168
200
  omega positions."""
169
201
  return self.grid_1_spec.concat(self.grid_2_spec)
170
202
 
171
- @property
172
- def scan_points(self):
173
- """A list of all the points in the scan_spec."""
174
- return ScanPath(self.scan_spec.calculate()).consume().midpoints
175
-
176
- @property
177
- def scan_points_first_grid(self):
178
- """A list of all the points in the first grid scan."""
179
- return ScanPath(self.grid_1_spec.calculate()).consume().midpoints
180
-
181
203
  @property
182
204
  def scan_points_second_grid(self):
183
205
  """A list of all the points in the second grid scan."""
184
206
  return ScanPath(self.grid_2_spec.calculate()).consume().midpoints
185
-
186
- @property
187
- def num_images(self) -> int:
188
- return len(self.scan_points["sam_x"])
@@ -1,6 +1,8 @@
1
1
  import numpy as np
2
2
  from bluesky import plan_stubs as bps
3
+ from bluesky.utils import FailedStatus
3
4
  from dodal.devices.smargon import CombinedMove, Smargon
5
+ from ophyd_async.epics.motor import MotorLimitsException
4
6
 
5
7
  from mx_bluesky.common.utils.exceptions import SampleException
6
8
 
@@ -9,12 +11,15 @@ def move_smargon_warn_on_out_of_range(
9
11
  smargon: Smargon, position: np.ndarray | list[float] | tuple[float, float, float]
10
12
  ):
11
13
  """Throws a SampleException if the specified position is out of range for the
12
- smargon. Otherwise moves to that position."""
13
- limits = yield from smargon.get_xyz_limits()
14
- if not limits.position_valid(position):
15
- raise SampleException(
16
- "Pin tip centring failed - pin too long/short/bent and out of range"
14
+ smargon. Otherwise moves to that position. The check is from ophyd-async"""
15
+ try:
16
+ yield from bps.mv(
17
+ smargon, CombinedMove(x=position[0], y=position[1], z=position[2])
17
18
  )
18
- yield from bps.mv(
19
- smargon, CombinedMove(x=position[0], y=position[1], z=position[2])
20
- )
19
+ except FailedStatus as fs:
20
+ if isinstance(fs.__cause__, MotorLimitsException):
21
+ raise SampleException(
22
+ "Pin tip centring failed - pin too long/short/bent and out of range"
23
+ ) from fs.__cause__
24
+ else:
25
+ raise fs
@@ -12,7 +12,7 @@ from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
12
12
  from dodal.devices.backlight import Backlight
13
13
  from dodal.devices.detector.detector_motion import DetectorMotion
14
14
  from dodal.devices.eiger import EigerDetector
15
- from dodal.devices.fast_grid_scan import PandAFastGridScan, ZebraFastGridScan
15
+ from dodal.devices.fast_grid_scan import PandAFastGridScan, ZebraFastGridScanThreeD
16
16
  from dodal.devices.flux import Flux
17
17
  from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, MirrorVoltages
18
18
  from dodal.devices.i03 import Beamstop
@@ -72,7 +72,7 @@ class RobotLoadThenCentreComposite:
72
72
  backlight: Backlight
73
73
  detector_motion: DetectorMotion
74
74
  eiger: EigerDetector
75
- zebra_fast_grid_scan: ZebraFastGridScan
75
+ zebra_fast_grid_scan: ZebraFastGridScanThreeD
76
76
  flux: Flux
77
77
  oav: OAV
78
78
  pin_tip_detection: PinTipDetection
@@ -23,6 +23,7 @@ from mx_bluesky.common.external_interaction.callbacks.sample_handling.sample_han
23
23
  )
24
24
  from mx_bluesky.common.external_interaction.callbacks.xray_centre.ispyb_callback import (
25
25
  GridscanISPyBCallback,
26
+ generate_start_info_from_omega_map,
26
27
  )
27
28
  from mx_bluesky.common.external_interaction.callbacks.xray_centre.nexus_callback import (
28
29
  GridscanNexusFileCallback,
@@ -41,6 +42,7 @@ from mx_bluesky.hyperion.external_interaction.callbacks.robot_actions.ispyb_call
41
42
  )
42
43
  from mx_bluesky.hyperion.external_interaction.callbacks.rotation.ispyb_callback import (
43
44
  RotationISPyBCallback,
45
+ generate_start_info_from_ordered_runs,
44
46
  )
45
47
  from mx_bluesky.hyperion.external_interaction.callbacks.rotation.nexus_callback import (
46
48
  RotationNexusFileCallback,
@@ -66,7 +68,9 @@ def create_gridscan_callbacks() -> tuple[
66
68
  GridscanNexusFileCallback(param_type=HyperionSpecifiedThreeDGridScan),
67
69
  GridscanISPyBCallback(
68
70
  param_type=GridCommonWithHyperionDetectorParams,
69
- emit=ZocaloCallback(CONST.PLAN.DO_FGS, CONST.ZOCALO_ENV),
71
+ emit=ZocaloCallback(
72
+ CONST.PLAN.DO_FGS, CONST.ZOCALO_ENV, generate_start_info_from_omega_map
73
+ ),
70
74
  ),
71
75
  )
72
76
 
@@ -77,7 +81,11 @@ def create_rotation_callbacks() -> tuple[
77
81
  return (
78
82
  RotationNexusFileCallback(),
79
83
  RotationISPyBCallback(
80
- emit=ZocaloCallback(CONST.PLAN.ROTATION_MULTI, CONST.ZOCALO_ENV)
84
+ emit=ZocaloCallback(
85
+ CONST.PLAN.ROTATION_MULTI,
86
+ CONST.ZOCALO_ENV,
87
+ generate_start_info_from_ordered_runs,
88
+ )
81
89
  ),
82
90
  )
83
91
 
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  from collections.abc import Callable, Sequence
4
4
  from typing import TYPE_CHECKING, Any, cast
5
5
 
6
+ from dodal.devices.zocalo import ZocaloStartInfo
7
+
6
8
  from mx_bluesky.common.external_interaction.callbacks.common.ispyb_callback_base import (
7
9
  BaseISPyBCallback,
8
10
  )
@@ -10,6 +12,9 @@ from mx_bluesky.common.external_interaction.callbacks.common.ispyb_mapping impor
10
12
  populate_data_collection_group,
11
13
  populate_remaining_data_collection_info,
12
14
  )
15
+ from mx_bluesky.common.external_interaction.callbacks.common.zocalo_callback import (
16
+ ZocaloInfoGenerator,
17
+ )
13
18
  from mx_bluesky.common.external_interaction.ispyb.data_model import (
14
19
  DataCollectionInfo,
15
20
  DataCollectionPositionInfo,
@@ -21,6 +26,7 @@ from mx_bluesky.common.external_interaction.ispyb.ispyb_store import (
21
26
  )
22
27
  from mx_bluesky.common.parameters.components import IspybExperimentType
23
28
  from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER, set_dcgid_tag
29
+ from mx_bluesky.common.utils.utils import number_of_frames_from_scan_spec
24
30
  from mx_bluesky.hyperion.external_interaction.callbacks.rotation.ispyb_mapping import (
25
31
  populate_data_collection_info_for_rotation,
26
32
  )
@@ -175,4 +181,34 @@ class RotationISPyBCallback(BaseISPyBCallback):
175
181
  if doc.get("run_start") == self.uid_to_finalize_on:
176
182
  self.uid_to_finalize_on = None
177
183
  return super().activity_gated_stop(doc)
178
- return self._tag_doc(doc)
184
+ return self.tag_doc(doc)
185
+
186
+
187
+ def generate_start_info_from_ordered_runs() -> ZocaloInfoGenerator:
188
+ """
189
+ Generate the zocalo trigger info from bluesky runs where the frame number is
190
+ computed using the order in which the run start docs are received.
191
+ Yields:
192
+ A list of the ZocaloStartInfo objects extracted from the event
193
+ Send:
194
+ A dict containing the run start document
195
+ """
196
+ start_frame = 0
197
+ doc = yield []
198
+ while doc:
199
+ zocalo_info = []
200
+ if (
201
+ isinstance(scan_points := doc.get("scan_points"), list)
202
+ and isinstance(ispyb_ids := doc.get("ispyb_dcids"), tuple)
203
+ and len(ispyb_ids) > 0
204
+ ):
205
+ ISPYB_ZOCALO_CALLBACK_LOGGER.info(f"Zocalo triggering for {ispyb_ids}")
206
+ ids_and_shape = list(zip(ispyb_ids, scan_points, strict=False))
207
+ for idx, id_and_shape in enumerate(ids_and_shape):
208
+ id, shape = id_and_shape
209
+ num_frames = number_of_frames_from_scan_spec(shape)
210
+ zocalo_info.append(
211
+ ZocaloStartInfo(id, None, start_frame, num_frames, idx)
212
+ )
213
+ start_frame += num_frames
214
+ doc = yield zocalo_info
@@ -10,7 +10,7 @@ from dodal.devices.common_dcm import BaseDCM
10
10
  from dodal.devices.eiger import EigerDetector
11
11
  from dodal.devices.fast_grid_scan import (
12
12
  PandAFastGridScan,
13
- ZebraFastGridScan,
13
+ ZebraFastGridScanThreeD,
14
14
  )
15
15
  from dodal.devices.flux import Flux
16
16
  from dodal.devices.robot import BartRobot
@@ -51,7 +51,7 @@ class HyperionFlyScanXRayCentreComposite(FlyScanEssentialDevices):
51
51
  sample_shutter: ZebraShutter
52
52
  backlight: Backlight
53
53
  xbpm_feedback: XBPMFeedback
54
- zebra_fast_grid_scan: ZebraFastGridScan
54
+ zebra_fast_grid_scan: ZebraFastGridScanThreeD
55
55
 
56
56
 
57
57
  @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from dodal.devices.fast_grid_scan import (
4
4
  PandAGridScanParams,
5
- ZebraGridScanParams,
5
+ ZebraGridScanParamsThreeD,
6
6
  )
7
7
 
8
8
  from mx_bluesky.common.parameters.gridscan import (
@@ -44,8 +44,8 @@ class HyperionSpecifiedThreeDGridScan(SpecifiedThreeDGridScan):
44
44
 
45
45
  # Relative to common grid scan, stub offsets are defined by config server
46
46
  @property
47
- def FGS_params(self) -> ZebraGridScanParams:
48
- return ZebraGridScanParams(
47
+ def FGS_params(self) -> ZebraGridScanParamsThreeD:
48
+ return ZebraGridScanParamsThreeD(
49
49
  x_steps=self.x_steps,
50
50
  y_steps=self.y_steps,
51
51
  z_steps=self.z_steps,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mx-bluesky
3
- Version: 1.5.6
3
+ Version: 1.5.7
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
@@ -241,7 +241,7 @@ Requires-Dist: blueapi>=0.15.0
241
241
  Requires-Dist: ophyd>=1.10.5
242
242
  Requires-Dist: ophyd-async>=0.10.0a2
243
243
  Requires-Dist: bluesky>=1.13.1
244
- Requires-Dist: dls-dodal==1.58.0
244
+ Requires-Dist: dls-dodal==1.59.1
245
245
  Provides-Extra: dev
246
246
  Requires-Dist: black; extra == "dev"
247
247
  Requires-Dist: build; extra == "dev"