mx-bluesky 1.4.7__py3-none-any.whl → 1.4.8__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 (70) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +4 -4
  3. mx_bluesky/beamlines/i04/thawing_plan.py +8 -2
  4. mx_bluesky/beamlines/i23/__init__.py +3 -0
  5. mx_bluesky/beamlines/i23/serial.py +71 -0
  6. mx_bluesky/beamlines/i24/serial/__init__.py +2 -0
  7. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +7 -2
  8. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +3 -3
  9. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +6 -56
  10. mx_bluesky/beamlines/i24/serial/log.py +9 -10
  11. mx_bluesky/beamlines/i24/serial/parameters/utils.py +36 -7
  12. mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +0 -1
  13. mx_bluesky/beamlines/i24/serial/setup_beamline/pv_abstract.py +4 -4
  14. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +4 -4
  15. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +2 -1
  16. mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +71 -11
  17. mx_bluesky/beamlines/i24/serial/write_nexus.py +3 -3
  18. mx_bluesky/{hyperion → common}/device_setup_plans/check_beamstop.py +1 -1
  19. mx_bluesky/{hyperion → common}/device_setup_plans/manipulate_sample.py +1 -1
  20. mx_bluesky/{hyperion → common}/device_setup_plans/setup_oav.py +12 -6
  21. mx_bluesky/{hyperion → common}/experiment_plans/change_aperture_then_move_plan.py +4 -5
  22. mx_bluesky/{hyperion → common}/experiment_plans/oav_grid_detection_plan.py +6 -6
  23. mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py +6 -5
  24. mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py +16 -47
  25. mx_bluesky/common/external_interaction/ispyb/ispyb_store.py +4 -1
  26. mx_bluesky/common/external_interaction/ispyb/ispyb_utils.py +4 -4
  27. mx_bluesky/common/parameters/components.py +22 -2
  28. mx_bluesky/common/parameters/constants.py +4 -16
  29. mx_bluesky/common/parameters/gridscan.py +36 -32
  30. mx_bluesky/common/plans/common_flyscan_xray_centre_plan.py +316 -0
  31. mx_bluesky/common/plans/inner_plans/__init__ .py +0 -0
  32. mx_bluesky/common/plans/read_hardware.py +3 -3
  33. mx_bluesky/common/utils/log.py +15 -12
  34. mx_bluesky/hyperion/__main__.py +2 -15
  35. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +4 -4
  36. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +0 -33
  37. mx_bluesky/hyperion/device_setup_plans/utils.py +4 -4
  38. mx_bluesky/hyperion/experiment_plans/__init__.py +0 -6
  39. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +0 -9
  40. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +71 -88
  41. mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +183 -0
  42. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +12 -7
  43. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +28 -7
  44. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +4 -4
  45. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +1 -1
  46. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +11 -3
  47. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +9 -8
  48. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +18 -56
  49. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +2 -2
  50. mx_bluesky/hyperion/external_interaction/agamemnon.py +62 -70
  51. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +8 -6
  52. mx_bluesky/hyperion/external_interaction/callbacks/snapshot_callback.py +183 -31
  53. mx_bluesky/hyperion/parameters/cli.py +2 -10
  54. mx_bluesky/hyperion/parameters/constants.py +0 -5
  55. mx_bluesky/hyperion/parameters/device_composites.py +40 -5
  56. mx_bluesky/hyperion/parameters/gridscan.py +9 -58
  57. mx_bluesky/hyperion/parameters/rotation.py +0 -4
  58. mx_bluesky/hyperion/utils/context.py +2 -5
  59. mx_bluesky/hyperion/utils/validation.py +13 -10
  60. {mx_bluesky-1.4.7.dist-info → mx_bluesky-1.4.8.dist-info}/METADATA +5 -4
  61. {mx_bluesky-1.4.7.dist-info → mx_bluesky-1.4.8.dist-info}/RECORD +69 -65
  62. {mx_bluesky-1.4.7.dist-info → mx_bluesky-1.4.8.dist-info}/WHEEL +1 -1
  63. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +0 -467
  64. /mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/{short1-laser.png → s1l.png} +0 -0
  65. /mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/{short2-laser.png → s2l.png} +0 -0
  66. /mx_bluesky/{hyperion → common}/device_setup_plans/position_detector.py +0 -0
  67. /mx_bluesky/common/plans/{do_fgs.py → inner_plans/do_fgs.py} +0 -0
  68. {mx_bluesky-1.4.7.dist-info → mx_bluesky-1.4.8.dist-info}/entry_points.txt +0 -0
  69. {mx_bluesky-1.4.7.dist-info → mx_bluesky-1.4.8.dist-info}/licenses/LICENSE +0 -0
  70. {mx_bluesky-1.4.7.dist-info → mx_bluesky-1.4.8.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,9 @@
1
+ import dataclasses
1
2
  import re
3
+ from collections.abc import Iterator
4
+ from datetime import datetime
5
+ from math import cos, radians, sin
6
+ from pathlib import Path
2
7
 
3
8
  from dodal.devices.oav.snapshots.snapshot_image_processing import (
4
9
  compute_beam_centre_pixel_xy_for_mm_position,
@@ -10,17 +15,35 @@ from PIL import Image
10
15
  from mx_bluesky.common.external_interaction.callbacks.common.plan_reactive_callback import (
11
16
  PlanReactiveCallback,
12
17
  )
13
- from mx_bluesky.common.parameters.constants import DocDescriptorNames
18
+ from mx_bluesky.common.parameters.components import WithSnapshot
19
+ from mx_bluesky.common.parameters.constants import DocDescriptorNames, PlanNameConstants
14
20
  from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER as CALLBACK_LOGGER
15
21
 
22
+ COMPRESSION_LEVEL = 6 # 6 is the default compression level for PIL if not specified
23
+
24
+
25
+ @dataclasses.dataclass
26
+ class _SnapshotInfo:
27
+ beam_centre: tuple[int, int]
28
+ microns_per_pixel: tuple[float, float]
29
+ snapshot_path: str
30
+ omega: int
31
+ sample_pos_mm: tuple[float, float, float]
32
+
33
+ @property
34
+ def snapshot_basename(self) -> str:
35
+ match = re.match("(.*)\\.png", self.snapshot_path)
36
+ assert match, f"Snapshot {self.snapshot_path} was not a .png file"
37
+ return match.groups()[0]
38
+
16
39
 
17
40
  class BeamDrawingCallback(PlanReactiveCallback):
18
41
  """
19
42
  Callback that monitors for OAV_ROTATION_SNAPSHOT_TRIGGERED events and
20
43
  draws a crosshair at the beam centre, saving the snapshot to a file.
21
- The callback assumes an OAV device "oav"
44
+ The callback assumes an OAV device "oav" and Smargon "smargon"
22
45
  Examples:
23
- Take a snapshot at the current location
46
+ Take a rotation snapshot at the current location
24
47
  >>> from bluesky.run_engine import RunEngine
25
48
  >>> import bluesky.preprocessors as bpp
26
49
  >>> import bluesky.plan_stubs as bps
@@ -39,69 +62,198 @@ class BeamDrawingCallback(PlanReactiveCallback):
39
62
  ... yield from bps.create(DocDescriptorNames.OAV_ROTATION_SNAPSHOT_TRIGGERED)
40
63
  ... yield from bps.read(oav)
41
64
  ... yield from bps.save()
65
+
66
+ Generate rotation snapshots from a previously taken base gridscan snapshot.
67
+ WithSnapshot.snapshot_omegas_deg is ignored and snapshots are generated for the previously captured
68
+ 0, 90 base images named "my_snapshot_prefix_0" and "my_snapshot_prefix_90"
69
+ >>> from dodal.devices.smargon import Smargon
70
+ >>> def take_snapshot(params: WithSnapshot, oav: OAV, smargon: Smargon, run_engine: RunEngine):
71
+ ... run_engine.subscribe(BeamDrawingCallback())
72
+ ... @bpp.run_decorator(md={
73
+ ... "activate_callbacks": ["BeamDrawingCallback"],
74
+ ... "with_snapshot": params.model_dump_json(),
75
+ ... })
76
+ ... def inner_plan():
77
+ ... for omega in (0, 90,):
78
+ ... yield from bps.abs_set(smargon.omega, omega, wait=True)
79
+ ... yield from bps.abs_set(oav.grid_snapshot.directory, "/path/to/grid_snapshot_folder", wait=True)
80
+ ... yield from bps.abs_set(oav.grid_snapshot.filename, f"my_grid_snapshot_prefix_{omega}", wait=True)
81
+ ... yield from bps.trigger(oav.grid_snapshot, wait=True)
82
+ ... yield from bps.create(DocDescriptorNames.OAV_GRID_SNAPSHOT_TRIGGERED)
83
+ ... yield from bps.read(oav) # Capture base image path
84
+ ... yield from bps.read(smargon) # Capture base image sample x, y, z, omega
85
+ ... yield from bps.save()
86
+ ... # Rest of gridscan here...
87
+ ... # Later on...
88
+ ... for omega in (0, 90,):
89
+ ... yield from bps.abs_set(oav.snapshot.last_saved_path,
90
+ ... f"/path/to/snapshot_folder/my_snapshot_prefix_{omega}.png", wait=True)
91
+ ... yield from bps.create(DocDescriptorNames.OAV_ROTATION_SNAPSHOT_TRIGGERED)
92
+ ... yield from bps.read(oav) # Capture path info for generated snapshot
93
+ ... yield from bps.read(smargon) # Capture the current sample x, y, z
94
+ ... yield from bps.save()
42
95
  """
43
96
 
44
97
  def __init__(self, *args, **kwargs):
45
98
  super().__init__(*args, log=CALLBACK_LOGGER, **kwargs)
46
- self._snapshot_files: list[str] = []
47
- self._microns_per_pixel: tuple[float, float]
48
- self._beam_centre: tuple[int, int]
99
+ self._base_snapshots: list[_SnapshotInfo] = []
49
100
  self._rotation_snapshot_descriptor: str = ""
101
+ self._grid_snapshot_descriptor: str = ""
102
+ self._next_snapshot_info: Iterator | None = None
103
+ self._use_grid_snapshots: bool = False
104
+
105
+ def _reset(self):
106
+ self._base_snapshots = []
50
107
 
51
108
  def activity_gated_start(self, doc: RunStart):
52
109
  if self.activity_uid == doc.get("uid"):
53
- with_snapshot_json = doc.get("with_snapshot") # type: ignore
54
- assert with_snapshot_json, (
55
- "run start event did not have expected snapshot json"
56
- )
110
+ self._reset()
111
+ with_snapshot = WithSnapshot.model_validate_json(doc.get("with_snapshot")) # type: ignore
112
+ self._use_grid_snapshots = with_snapshot.use_grid_snapshots
113
+ CALLBACK_LOGGER.info(f"Snapshot callback initialised with {with_snapshot}")
114
+ elif doc.get("subplan_name") == PlanNameConstants.ROTATION_MAIN:
115
+ self._next_snapshot_info = None
116
+ CALLBACK_LOGGER.info("Snapshot callback start rotation")
57
117
  return doc
58
118
 
59
119
  def activity_gated_descriptor(self, doc: EventDescriptor) -> EventDescriptor | None:
60
120
  if doc.get("name") == DocDescriptorNames.OAV_ROTATION_SNAPSHOT_TRIGGERED:
61
121
  self._rotation_snapshot_descriptor = doc["uid"]
122
+ elif doc.get("name") == DocDescriptorNames.OAV_GRID_SNAPSHOT_TRIGGERED:
123
+ self._grid_snapshot_descriptor = doc["uid"]
62
124
  return doc
63
125
 
64
126
  def activity_gated_event(self, doc: Event) -> Event:
65
127
  if doc["descriptor"] == self._rotation_snapshot_descriptor:
66
128
  self._handle_rotation_snapshot(doc)
129
+ elif doc["descriptor"] == self._grid_snapshot_descriptor:
130
+ self._handle_grid_snapshot(doc)
67
131
  return doc
68
132
 
69
- def _extract_base_snapshot_params(self, doc: Event):
133
+ def _extract_base_snapshot_params(
134
+ self, snapshot_device_prefix: str, doc: Event
135
+ ) -> _SnapshotInfo:
70
136
  data = doc["data"]
71
- self._snapshot_files.append(data["oav-snapshot-last_saved_path"])
72
- self._microns_per_pixel = (
73
- data["oav-microns_per_pixel_x"],
74
- data["oav-microns_per_pixel_y"],
137
+ base_snapshot_path = data[f"oav-{snapshot_device_prefix}-last_saved_path"]
138
+ return _SnapshotInfo(
139
+ beam_centre=(data["oav-beam_centre_i"], data["oav-beam_centre_j"]),
140
+ microns_per_pixel=(
141
+ data["oav-microns_per_pixel_x"],
142
+ data["oav-microns_per_pixel_y"],
143
+ ),
144
+ snapshot_path=base_snapshot_path,
145
+ sample_pos_mm=(
146
+ data.get("smargon-x", 0.0),
147
+ data.get("smargon-y", 0.0),
148
+ data.get("smargon-z", 0.0),
149
+ ),
150
+ omega=round(data.get("smargon-omega", 0.0)),
75
151
  )
76
- self._beam_centre = (data["oav-beam_centre_i"], data["oav-beam_centre_j"])
77
152
 
78
- def _handle_rotation_snapshot(self, doc: Event):
79
- self._extract_base_snapshot_params(doc)
153
+ def _handle_grid_snapshot(self, doc: Event):
154
+ snapshot_info = self._extract_base_snapshot_params("grid_snapshot", doc)
155
+ self._base_snapshots.append(snapshot_info)
156
+
157
+ def _handle_rotation_snapshot(self, doc: Event) -> Event:
80
158
  data = doc["data"]
81
- snapshot_path = data["oav-snapshot-last_saved_path"]
82
- match = re.match("(.*)\\.png", snapshot_path)
83
- assert match, f"Snapshot {snapshot_path} was not a .png file"
84
- snapshot_base = match.groups()[0]
85
- output_snapshot_path = f"{snapshot_base}_with_beam_centre.png"
86
- self._generate_snapshot_at(snapshot_path, output_snapshot_path, 0, 0)
159
+ if self._use_grid_snapshots:
160
+ if not self._next_snapshot_info:
161
+ self._next_snapshot_info = iter(self._base_snapshots)
162
+ snapshot_info = next(self._next_snapshot_info, None)
163
+ assert snapshot_info, (
164
+ "Insufficient base gridscan snapshots to generate required rotation snapshots"
165
+ )
166
+ current_sample_pos_mm = (
167
+ data["smargon-x"],
168
+ data["smargon-y"],
169
+ data["smargon-z"],
170
+ )
171
+ CALLBACK_LOGGER.info(
172
+ f"Generating snapshot at {current_sample_pos_mm} from base snapshot {snapshot_info}"
173
+ )
174
+ output_snapshot_directory = data["oav-snapshot-directory"]
175
+ base_file_stem = Path(snapshot_info.snapshot_path).stem
176
+ output_snapshot_filename = _snapshot_filename(base_file_stem)
177
+ output_snapshot_path = (
178
+ f"{output_snapshot_directory}/{output_snapshot_filename}.png"
179
+ )
180
+ self._generate_snapshot_at(
181
+ snapshot_info,
182
+ output_snapshot_path,
183
+ *self._image_plane_offset_mm(snapshot_info, current_sample_pos_mm),
184
+ )
185
+ else:
186
+ snapshot_info = self._extract_base_snapshot_params("snapshot", doc)
187
+ output_snapshot_path = (
188
+ f"{snapshot_info.snapshot_basename}_with_beam_centre.png"
189
+ )
190
+ CALLBACK_LOGGER.info(
191
+ f"Annotating snapshot {output_snapshot_path} from base snapshot {snapshot_info}"
192
+ )
193
+ self._generate_snapshot_zero_offset(
194
+ snapshot_info,
195
+ output_snapshot_path,
196
+ )
87
197
  data["oav-snapshot-last_saved_path"] = output_snapshot_path
88
198
  return doc
89
199
 
200
+ def _image_plane_offset_mm(
201
+ self,
202
+ snapshot_info: _SnapshotInfo,
203
+ current_sample_pos_mm: tuple[float, float, float],
204
+ ) -> tuple[float, float]:
205
+ return self._project_xyz_to_xy(
206
+ (
207
+ (current_sample_pos_mm[0] - snapshot_info.sample_pos_mm[0]),
208
+ (current_sample_pos_mm[1] - snapshot_info.sample_pos_mm[1]),
209
+ (current_sample_pos_mm[2] - snapshot_info.sample_pos_mm[2]),
210
+ ),
211
+ snapshot_info.omega,
212
+ )
213
+
214
+ def _project_xyz_to_xy(
215
+ self, xyz: tuple[float, float, float], omega_deg: float
216
+ ) -> tuple[float, float]:
217
+ return (
218
+ xyz[0],
219
+ xyz[1] * cos(-radians(omega_deg)) + xyz[2] * sin(-radians(omega_deg)),
220
+ )
221
+
222
+ def _generate_snapshot_zero_offset(
223
+ self,
224
+ base_snapshot_info: _SnapshotInfo,
225
+ output_snapshot_path: str,
226
+ ):
227
+ self._generate_snapshot_at(base_snapshot_info, output_snapshot_path, 0, 0)
228
+
90
229
  def _generate_snapshot_at(
91
- self, input_snapshot_path: str, output_snapshot_path: str, x_mm: int, y_mm: int
230
+ self,
231
+ base_snapshot_info: _SnapshotInfo,
232
+ output_snapshot_path: str,
233
+ image_plane_dx_mm: float,
234
+ image_plane_dy_mm: float,
92
235
  ):
93
236
  """
94
237
  Save a snapshot to the specified path, with an annotated crosshair at the specified
95
238
  position
96
239
  Args:
97
- input_snapshot_path: The non-annotated image path.
240
+ base_snapshot_info: Metadata about the base snapshot image from which the annotated
241
+ image will be derived.
98
242
  output_snapshot_path: The path to the image that will be annotated.
99
- x_mm: Relative x location of the sample to the original image (mm)
100
- y_mm: Relative y location of the sample to the original image (mm)
243
+ image_plane_dx_mm: Relative x location of the sample to the original image in the image plane (mm)
244
+ image_plane_dy_mm: Relative y location of the sample to the original image in the image plane (mm)
101
245
  """
102
- image = Image.open(input_snapshot_path)
246
+ image = Image.open(base_snapshot_info.snapshot_path)
103
247
  x_px, y_px = compute_beam_centre_pixel_xy_for_mm_position(
104
- (x_mm, y_mm), self._beam_centre, self._microns_per_pixel
248
+ (image_plane_dx_mm, image_plane_dy_mm),
249
+ base_snapshot_info.beam_centre,
250
+ base_snapshot_info.microns_per_pixel,
105
251
  )
106
252
  draw_crosshair(image, x_px, y_px)
107
- image.save(output_snapshot_path, format="png")
253
+ image.save(output_snapshot_path, format="png", compress_level=COMPRESSION_LEVEL)
254
+
255
+
256
+ def _snapshot_filename(grid_snapshot_name):
257
+ time_now = datetime.now()
258
+ filename = f"{time_now.strftime('%H%M%S%f')[:8]}_oav_snapshot_{grid_snapshot_name}"
259
+ return filename
@@ -9,7 +9,6 @@ from mx_bluesky._version import version
9
9
  class HyperionArgs:
10
10
  dev_mode: bool = False
11
11
  verbose_event_logging: bool = False
12
- skip_startup_connection: bool = False
13
12
 
14
13
 
15
14
  def _add_callback_relevant_args(parser: argparse.ArgumentParser) -> None:
@@ -17,7 +16,7 @@ def _add_callback_relevant_args(parser: argparse.ArgumentParser) -> None:
17
16
  parser.add_argument(
18
17
  "--dev",
19
18
  action="store_true",
20
- help="Use dev options, such as local graylog instances and S03",
19
+ help="Use dev options, such as local graylog instances",
21
20
  )
22
21
 
23
22
 
@@ -32,8 +31,7 @@ def parse_callback_dev_mode_arg() -> bool:
32
31
  def parse_cli_args() -> HyperionArgs:
33
32
  """Parses all arguments relevant to hyperion. Returns an HyperionArgs dataclass with
34
33
  the fields: (verbose_event_logging: bool,
35
- dev_mode: bool,
36
- skip_startup_connection: bool)"""
34
+ dev_mode: bool)"""
37
35
  parser = argparse.ArgumentParser()
38
36
  _add_callback_relevant_args(parser)
39
37
  parser.add_argument(
@@ -41,11 +39,6 @@ def parse_cli_args() -> HyperionArgs:
41
39
  action="store_true",
42
40
  help="Log all bluesky event documents to graylog",
43
41
  )
44
- parser.add_argument(
45
- "--skip-startup-connection",
46
- action="store_true",
47
- help="Skip connecting to EPICS PVs on startup",
48
- )
49
42
  parser.add_argument(
50
43
  "--version",
51
44
  help="Print hyperion version string",
@@ -56,5 +49,4 @@ def parse_cli_args() -> HyperionArgs:
56
49
  return HyperionArgs(
57
50
  verbose_event_logging=args.verbose_event_logging or False,
58
51
  dev_mode=args.dev or False,
59
- skip_startup_connection=args.skip_startup_connection or False,
60
52
  )
@@ -12,8 +12,6 @@ from mx_bluesky.common.parameters.constants import (
12
12
  OavConstants,
13
13
  PlanGroupCheckpointConstants,
14
14
  PlanNameConstants,
15
- SimConstants,
16
- TriggerConstants,
17
15
  )
18
16
 
19
17
  TEST_MODE = os.environ.get("HYPERION_TEST_MODE")
@@ -42,15 +40,12 @@ class I03Constants:
42
40
  @dataclass(frozen=True)
43
41
  class HyperionConstants:
44
42
  DESCRIPTORS = DocDescriptorNames()
45
- TRIGGER = TriggerConstants()
46
43
  ZOCALO_ENV = EnvironmentConstants.ZOCALO_ENV
47
44
  HARDWARE = HardwareConstants()
48
45
  I03 = I03Constants()
49
46
  PARAM = ExperimentParamConstants()
50
47
  PLAN = PlanNameConstants()
51
48
  WAIT = PlanGroupCheckpointConstants()
52
- SIM = SimConstants()
53
- TRIGGER = TriggerConstants()
54
49
  CALLBACK_0MQ_PROXY_PORTS = (5577, 5578)
55
50
  DESCRIPTORS = DocDescriptorNames()
56
51
  CONFIG_SERVER_URL = (
@@ -6,13 +6,17 @@ from dodal.devices.aperturescatterguard import (
6
6
  )
7
7
  from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
8
8
  from dodal.devices.backlight import Backlight
9
- from dodal.devices.dcm import DCM
9
+ from dodal.devices.common_dcm import BaseDCM
10
+ from dodal.devices.detector.detector_motion import DetectorMotion
10
11
  from dodal.devices.eiger import EigerDetector
11
12
  from dodal.devices.fast_grid_scan import (
12
13
  PandAFastGridScan,
13
14
  ZebraFastGridScan,
14
15
  )
15
16
  from dodal.devices.flux import Flux
17
+ from dodal.devices.i03 import Beamstop
18
+ from dodal.devices.oav.oav_detector import OAV
19
+ from dodal.devices.oav.pin_image_recognition import PinTipDetection
16
20
  from dodal.devices.robot import BartRobot
17
21
  from dodal.devices.s4_slit_gaps import S4SlitGaps
18
22
  from dodal.devices.smargon import Smargon
@@ -24,22 +28,53 @@ from dodal.devices.zebra.zebra_controlled_shutter import ZebraShutter
24
28
  from dodal.devices.zocalo import ZocaloResults
25
29
  from ophyd_async.fastcs.panda import HDFPanda
26
30
 
31
+ from mx_bluesky.common.plans.common_flyscan_xray_centre_plan import (
32
+ FlyScanEssentialDevices,
33
+ )
34
+
27
35
 
28
36
  @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
29
- class HyperionFlyScanXRayCentreComposite:
37
+ class HyperionFlyScanXRayCentreComposite(FlyScanEssentialDevices):
30
38
  """All devices which are directly or indirectly required by this plan"""
31
39
 
32
40
  aperture_scatterguard: ApertureScatterguard
33
41
  attenuator: BinaryFilterAttenuator
42
+ dcm: BaseDCM
43
+ eiger: EigerDetector
44
+ flux: Flux
45
+ s4_slit_gaps: S4SlitGaps
46
+ undulator: Undulator
47
+ synchrotron: Synchrotron
48
+ zebra: Zebra
49
+ zocalo: ZocaloResults
50
+ panda: HDFPanda
51
+ panda_fast_grid_scan: PandAFastGridScan
52
+ robot: BartRobot
53
+ sample_shutter: ZebraShutter
34
54
  backlight: Backlight
35
- dcm: DCM
55
+ xbpm_feedback: XBPMFeedback
56
+ zebra_fast_grid_scan: ZebraFastGridScan
57
+
58
+
59
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
60
+ class GridDetectThenXRayCentreComposite:
61
+ """All devices which are directly or indirectly required by this plan"""
62
+
63
+ aperture_scatterguard: ApertureScatterguard
64
+ attenuator: BinaryFilterAttenuator
65
+ backlight: Backlight
66
+ beamstop: Beamstop
67
+ dcm: BaseDCM
68
+ detector_motion: DetectorMotion
36
69
  eiger: EigerDetector
37
70
  zebra_fast_grid_scan: ZebraFastGridScan
38
71
  flux: Flux
39
- s4_slit_gaps: S4SlitGaps
72
+ oav: OAV
73
+ pin_tip_detection: PinTipDetection
40
74
  smargon: Smargon
41
- undulator: Undulator
42
75
  synchrotron: Synchrotron
76
+ s4_slit_gaps: S4SlitGaps
77
+ undulator: Undulator
43
78
  xbpm_feedback: XBPMFeedback
44
79
  zebra: Zebra
45
80
  zocalo: ZocaloResults
@@ -1,8 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- from dodal.devices.detector import (
4
- DetectorParams,
5
- )
6
3
  from dodal.devices.fast_grid_scan import (
7
4
  PandAGridScanParams,
8
5
  ZebraGridScanParams,
@@ -13,7 +10,6 @@ from mx_bluesky.common.parameters.gridscan import (
13
10
  SpecifiedThreeDGridScan,
14
11
  )
15
12
  from mx_bluesky.hyperion.parameters.components import WithHyperionUDCFeatures
16
- from mx_bluesky.hyperion.parameters.constants import CONST, I03Constants
17
13
 
18
14
 
19
15
  class GridCommonWithHyperionDetectorParams(GridCommon, WithHyperionUDCFeatures):
@@ -23,34 +19,11 @@ class GridCommonWithHyperionDetectorParams(GridCommon, WithHyperionUDCFeatures):
23
19
  # https://github.com/DiamondLightSource/hyperion/issues/1395"""
24
20
  @property
25
21
  def detector_params(self):
26
- self.det_dist_to_beam_converter_path = (
27
- self.det_dist_to_beam_converter_path
28
- or CONST.PARAM.DETECTOR.BEAM_XY_LUT_PATH
29
- )
30
- optional_args = {}
31
- if self.run_number:
32
- optional_args["run_number"] = self.run_number
33
- assert self.detector_distance_mm is not None, (
34
- "Detector distance must be filled before generating DetectorParams"
35
- )
36
- return DetectorParams(
37
- detector_size_constants=I03Constants.DETECTOR,
38
- expected_energy_ev=self.demand_energy_ev,
39
- exposure_time_s=self.exposure_time_s,
40
- directory=self.storage_directory,
41
- prefix=self.file_name,
42
- detector_distance=self.detector_distance_mm,
43
- omega_start=self.omega_start_deg or 0,
44
- omega_increment=0,
45
- num_images_per_trigger=1,
46
- num_triggers=self.num_images,
47
- use_roi_mode=self.use_roi_mode,
48
- det_dist_to_beam_converter_path=self.det_dist_to_beam_converter_path,
49
- trigger_mode=self.trigger_mode,
50
- enable_dev_shm=self.features.compare_cpu_and_gpu_zocalo
51
- or self.features.use_gpu_results,
52
- **optional_args,
22
+ params = super().detector_params
23
+ params.enable_dev_shm = (
24
+ self.features.compare_cpu_and_gpu_zocalo or self.features.use_gpu_results
53
25
  )
26
+ return params
54
27
 
55
28
 
56
29
  class HyperionSpecifiedThreeDGridScan(WithHyperionUDCFeatures, SpecifiedThreeDGridScan):
@@ -58,36 +31,14 @@ class HyperionSpecifiedThreeDGridScan(WithHyperionUDCFeatures, SpecifiedThreeDGr
58
31
 
59
32
  # These detector params only exist so that we can properly select enable_dev_shm. Remove in
60
33
  # https://github.com/DiamondLightSource/hyperion/issues/1395"""
34
+
61
35
  @property
62
36
  def detector_params(self):
63
- self.det_dist_to_beam_converter_path = (
64
- self.det_dist_to_beam_converter_path
65
- or CONST.PARAM.DETECTOR.BEAM_XY_LUT_PATH
66
- )
67
- optional_args = {}
68
- if self.run_number:
69
- optional_args["run_number"] = self.run_number
70
- assert self.detector_distance_mm is not None, (
71
- "Detector distance must be filled before generating DetectorParams"
72
- )
73
- return DetectorParams(
74
- detector_size_constants=I03Constants.DETECTOR,
75
- expected_energy_ev=self.demand_energy_ev,
76
- exposure_time_s=self.exposure_time_s,
77
- directory=self.storage_directory,
78
- prefix=self.file_name,
79
- detector_distance=self.detector_distance_mm,
80
- omega_start=self.omega_start_deg or 0,
81
- omega_increment=0,
82
- num_images_per_trigger=1,
83
- num_triggers=self.num_images,
84
- use_roi_mode=self.use_roi_mode,
85
- det_dist_to_beam_converter_path=self.det_dist_to_beam_converter_path,
86
- trigger_mode=self.trigger_mode,
87
- enable_dev_shm=self.features.compare_cpu_and_gpu_zocalo
88
- or self.features.use_gpu_results,
89
- **optional_args,
37
+ params = super().detector_params
38
+ params.enable_dev_shm = (
39
+ self.features.compare_cpu_and_gpu_zocalo or self.features.use_gpu_results
90
40
  )
41
+ return params
91
42
 
92
43
  # Relative to common grid scan, stub offsets are defined by config server
93
44
  @property
@@ -192,7 +192,3 @@ class MultiRotationScan(RotationExperiment, SplitScan):
192
192
  self._num_images_per_scan()[0],
193
193
  len(self._num_images_per_scan()),
194
194
  )
195
-
196
- @property
197
- def ispyb_params(self): # pyright: ignore
198
- raise ValueError("Please get ispyb params from one of the individual scans")
@@ -5,14 +5,11 @@ import mx_bluesky.hyperion.experiment_plans as hyperion_plans
5
5
  from mx_bluesky.common.utils.log import LOGGER
6
6
 
7
7
 
8
- def setup_context(wait_for_connection: bool = True) -> BlueskyContext:
8
+ def setup_context() -> BlueskyContext:
9
9
  context = BlueskyContext()
10
10
  context.with_plan_module(hyperion_plans)
11
11
 
12
- context.with_dodal_module(
13
- get_beamline_based_on_environment_variable(),
14
- wait_for_connection=wait_for_connection,
15
- )
12
+ context.with_dodal_module(get_beamline_based_on_environment_variable())
16
13
 
17
14
  LOGGER.info(f"Plans found in context: {context.plan_functions.keys()}")
18
15
 
@@ -21,7 +21,7 @@ from mx_bluesky.hyperion.external_interaction.callbacks.rotation.nexus_callback
21
21
  RotationNexusFileCallback,
22
22
  )
23
23
  from mx_bluesky.hyperion.parameters.constants import CONST
24
- from mx_bluesky.hyperion.parameters.rotation import RotationScan
24
+ from mx_bluesky.hyperion.parameters.rotation import MultiRotationScan
25
25
 
26
26
  DISPLAY_CONFIGURATION = "tests/test_data/test_display.configuration"
27
27
  ZOOM_LEVELS_XML = "tests/test_data/test_jCameraManZoomLevels.xml"
@@ -36,33 +36,36 @@ def test_params(filename_stub, dir):
36
36
  with open(filename) as f:
37
37
  return json.loads(f.read())
38
38
 
39
- params = RotationScan(
39
+ params = MultiRotationScan(
40
40
  **get_params(
41
- "tests/test_data/parameter_json_files/good_test_rotation_scan_parameters.json"
41
+ "tests/test_data/parameter_json_files/good_test_one_multi_rotation_scan_parameters.json"
42
42
  )
43
43
  )
44
+ for scan_params in params.rotation_scans:
45
+ scan_params.x_start_um = 0
46
+ scan_params.y_start_um = 0
47
+ scan_params.z_start_um = 0
48
+ scan_params.scan_width_deg = 360
44
49
  params.file_name = filename_stub
45
- params.scan_width_deg = 360
46
50
  params.demand_energy_ev = 12700
47
51
  params.storage_directory = str(dir)
48
- params.x_start_um = 0
49
- params.y_start_um = 0
50
- params.z_start_um = 0
51
52
  params.exposure_time_s = 0.004
52
53
  return params
53
54
 
54
55
 
55
56
  def fake_rotation_scan(
56
- parameters: RotationScan,
57
+ parameters: MultiRotationScan,
57
58
  subscription: RotationNexusFileCallback,
58
59
  rotation_devices: RotationScanComposite,
59
60
  ):
61
+ single_scan_parameters = next(parameters.single_rotation_scans)
62
+
60
63
  @bpp.subs_decorator(subscription)
61
64
  @bpp.set_run_key_decorator("rotation_scan_with_cleanup_and_subs")
62
65
  @bpp.run_decorator( # attach experiment metadata to the start document
63
66
  md={
64
67
  "subplan_name": CONST.PLAN.ROTATION_OUTER,
65
- "mx_bluesky_parameters": parameters.model_dump_json(),
68
+ "mx_bluesky_parameters": single_scan_parameters.model_dump_json(),
66
69
  "activate_callbacks": "RotationNexusFileCallback",
67
70
  }
68
71
  )
@@ -129,7 +132,7 @@ def fake_create_rotation_devices():
129
132
 
130
133
 
131
134
  def sim_rotation_scan_to_create_nexus(
132
- test_params: RotationScan,
135
+ test_params: MultiRotationScan,
133
136
  fake_create_rotation_devices: RotationScanComposite,
134
137
  filename_stub,
135
138
  RE,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mx-bluesky
3
- Version: 1.4.7
3
+ Version: 1.4.8
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
@@ -236,10 +236,10 @@ Requires-Dist: semver
236
236
  Requires-Dist: matplotlib
237
237
  Requires-Dist: blueapi>=0.5.0
238
238
  Requires-Dist: daq-config-server>=0.1.1
239
- Requires-Dist: ophyd==1.9.0
240
- Requires-Dist: ophyd-async>=0.9.0a2
239
+ Requires-Dist: ophyd>=1.10.5
240
+ Requires-Dist: ophyd-async>=0.10.0a2
241
241
  Requires-Dist: bluesky>=1.13.1
242
- Requires-Dist: dls-dodal==1.44.0
242
+ Requires-Dist: dls-dodal==1.47.0
243
243
  Provides-Extra: dev
244
244
  Requires-Dist: black; extra == "dev"
245
245
  Requires-Dist: build; extra == "dev"
@@ -257,6 +257,7 @@ Requires-Dist: pyright; extra == "dev"
257
257
  Requires-Dist: pytest-asyncio; extra == "dev"
258
258
  Requires-Dist: pytest-cov; extra == "dev"
259
259
  Requires-Dist: pytest-random-order; extra == "dev"
260
+ Requires-Dist: pytest-timeout; extra == "dev"
260
261
  Requires-Dist: pytest; extra == "dev"
261
262
  Requires-Dist: ruff; extra == "dev"
262
263
  Requires-Dist: sphinx-autobuild; extra == "dev"