dls-dodal 1.35.0__py3-none-any.whl → 1.36.1a0__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 (46) hide show
  1. {dls_dodal-1.35.0.dist-info → dls_dodal-1.36.1a0.dist-info}/METADATA +33 -31
  2. {dls_dodal-1.35.0.dist-info → dls_dodal-1.36.1a0.dist-info}/RECORD +46 -37
  3. {dls_dodal-1.35.0.dist-info → dls_dodal-1.36.1a0.dist-info}/WHEEL +1 -1
  4. dodal/_version.py +2 -2
  5. dodal/beamlines/b01_1.py +16 -31
  6. dodal/beamlines/i22.py +124 -265
  7. dodal/beamlines/i24.py +56 -7
  8. dodal/beamlines/p38.py +16 -1
  9. dodal/beamlines/p99.py +22 -53
  10. dodal/beamlines/training_rig.py +16 -26
  11. dodal/cli.py +54 -8
  12. dodal/common/beamlines/beamline_utils.py +32 -2
  13. dodal/common/beamlines/device_helpers.py +2 -0
  14. dodal/devices/aperture.py +7 -0
  15. dodal/devices/aperturescatterguard.py +195 -79
  16. dodal/devices/dcm.py +5 -4
  17. dodal/devices/eiger.py +88 -49
  18. dodal/devices/fast_grid_scan.py +21 -46
  19. dodal/devices/focusing_mirror.py +8 -3
  20. dodal/devices/i24/beam_center.py +12 -0
  21. dodal/devices/i24/focus_mirrors.py +60 -0
  22. dodal/devices/i24/pilatus_metadata.py +44 -0
  23. dodal/devices/linkam3.py +1 -1
  24. dodal/devices/motors.py +14 -10
  25. dodal/devices/oav/oav_detector.py +2 -2
  26. dodal/devices/oav/pin_image_recognition/__init__.py +4 -5
  27. dodal/devices/oav/utils.py +1 -0
  28. dodal/devices/p99/sample_stage.py +12 -16
  29. dodal/devices/pressure_jump_cell.py +299 -0
  30. dodal/devices/robot.py +1 -1
  31. dodal/devices/tetramm.py +1 -1
  32. dodal/devices/undulator.py +4 -1
  33. dodal/devices/undulator_dcm.py +3 -19
  34. dodal/devices/zocalo/zocalo_results.py +7 -7
  35. dodal/plan_stubs/__init__.py +0 -0
  36. dodal/{plans/data_session_metadata.py → plan_stubs/data_session.py} +2 -2
  37. dodal/{plans/motor_util_plans.py → plan_stubs/motor_utils.py} +2 -2
  38. dodal/plan_stubs/wrapped.py +150 -0
  39. dodal/plans/__init__.py +4 -0
  40. dodal/plans/scanspec.py +66 -0
  41. dodal/plans/wrapped.py +57 -0
  42. dodal/utils.py +151 -2
  43. {dls_dodal-1.35.0.dist-info → dls_dodal-1.36.1a0.dist-info}/LICENSE +0 -0
  44. {dls_dodal-1.35.0.dist-info → dls_dodal-1.36.1a0.dist-info}/entry_points.txt +0 -0
  45. {dls_dodal-1.35.0.dist-info → dls_dodal-1.36.1a0.dist-info}/top_level.txt +0 -0
  46. /dodal/{plans → plan_stubs}/check_topup.py +0 -0
dodal/devices/eiger.py CHANGED
@@ -1,4 +1,5 @@
1
1
  # type: ignore # Eiger will soon be ophyd-async https://github.com/DiamondLightSource/dodal/issues/700
2
+ from dataclasses import dataclass
2
3
  from enum import Enum
3
4
 
4
5
  from ophyd import Component, Device, EpicsSignalRO, Signal
@@ -14,6 +15,15 @@ from dodal.log import LOGGER
14
15
  FREE_RUN_MAX_IMAGES = 1000000
15
16
 
16
17
 
18
+ @dataclass
19
+ class EigerTimeouts:
20
+ stale_params_timeout: int = 60
21
+ general_status_timeout: int = 10
22
+ meta_file_ready_timeout: int = 30
23
+ all_frames_timeout: int = 120
24
+ arming_timeout: int = 60
25
+
26
+
17
27
  class InternalEigerTriggerMode(Enum):
18
28
  INTERNAL_SERIES = 0
19
29
  INTERNAL_ENABLE = 1
@@ -21,6 +31,17 @@ class InternalEigerTriggerMode(Enum):
21
31
  EXTERNAL_ENABLE = 3
22
32
 
23
33
 
34
+ AVAILABLE_TIMEOUTS = {
35
+ "i03": EigerTimeouts(
36
+ stale_params_timeout=60,
37
+ general_status_timeout=10,
38
+ meta_file_ready_timeout=30,
39
+ all_frames_timeout=120, # Long timeout for meta file to compensate for filesystem issues
40
+ arming_timeout=60,
41
+ )
42
+ }
43
+
44
+
24
45
  class EigerDetector(Device):
25
46
  class ArmingSignal(Signal):
26
47
  def set(self, value, *, timeout=None, settle_time=None, **kwargs):
@@ -34,13 +55,6 @@ class EigerDetector(Device):
34
55
  stale_params = Component(EpicsSignalRO, "CAM:StaleParameters_RBV")
35
56
  bit_depth = Component(EpicsSignalRO, "CAM:BitDepthImage_RBV")
36
57
 
37
- STALE_PARAMS_TIMEOUT = 60
38
- GENERAL_STATUS_TIMEOUT = 10
39
- # Long timeout for meta file to compensate for filesystem issues
40
- META_FILE_READY_TIMEOUT = 30
41
- ALL_FRAMES_TIMEOUT = 120
42
- ARMING_TIMEOUT = 60
43
-
44
58
  filewriters_finished: StatusBase
45
59
 
46
60
  detector_params: DetectorParams | None = None
@@ -48,13 +62,20 @@ class EigerDetector(Device):
48
62
  arming_status = Status()
49
63
  arming_status.set_finished()
50
64
 
65
+ def __init__(self, beamline: str = "i03", *args, **kwargs):
66
+ super().__init__(*args, **kwargs)
67
+ self.beamline = beamline
68
+ # using i03 timeouts as default
69
+ self.timeouts = AVAILABLE_TIMEOUTS.get(beamline, AVAILABLE_TIMEOUTS["i03"])
70
+
51
71
  @classmethod
52
72
  def with_params(
53
73
  cls,
54
74
  params: DetectorParams,
55
75
  name: str = "EigerDetector",
76
+ beamline: str = "i03",
56
77
  ):
57
- det = cls(name=name)
78
+ det = cls(name=name, beamline=beamline)
58
79
  det.set_detector_parameters(params)
59
80
  return det
60
81
 
@@ -82,7 +103,7 @@ class EigerDetector(Device):
82
103
  def async_stage(self):
83
104
  self.odin.nodes.clear_odin_errors()
84
105
  status_ok, error_message = self.odin.wait_for_odin_initialised(
85
- self.GENERAL_STATUS_TIMEOUT
106
+ self.timeouts.general_status_timeout
86
107
  )
87
108
  if not status_ok:
88
109
  raise Exception(f"Odin not initialised: {error_message}")
@@ -96,14 +117,14 @@ class EigerDetector(Device):
96
117
  def wait_on_arming_if_started(self):
97
118
  if not self.arming_status.done:
98
119
  LOGGER.info("Waiting for arming to finish")
99
- self.arming_status.wait(self.ARMING_TIMEOUT)
120
+ self.arming_status.wait(self.timeouts.arming_timeout)
100
121
 
101
122
  def stage(self):
102
123
  self.wait_on_arming_if_started()
103
124
  if not self.is_armed():
104
125
  LOGGER.info("Eiger not armed, arming")
105
126
 
106
- self.async_stage().wait(timeout=self.ARMING_TIMEOUT)
127
+ self.async_stage().wait(timeout=self.timeouts.arming_timeout)
107
128
 
108
129
  def stop_odin_when_all_frames_collected(self):
109
130
  LOGGER.info("Waiting on all frames")
@@ -111,7 +132,7 @@ class EigerDetector(Device):
111
132
  await_value(
112
133
  self.odin.file_writer.num_captured,
113
134
  self.detector_params.full_number_of_images,
114
- ).wait(self.ALL_FRAMES_TIMEOUT)
135
+ ).wait(self.timeouts.all_frames_timeout)
115
136
  finally:
116
137
  LOGGER.info("Stopping Odin")
117
138
  self.odin.stop().wait(5)
@@ -124,7 +145,9 @@ class EigerDetector(Device):
124
145
  # In free run mode we have to manually stop odin
125
146
  self.stop_odin_when_all_frames_collected()
126
147
 
127
- self.odin.file_writer.start_timeout.set(1).wait(self.GENERAL_STATUS_TIMEOUT)
148
+ self.odin.file_writer.start_timeout.set(1).wait(
149
+ self.timeouts.general_status_timeout
150
+ )
128
151
  LOGGER.info("Waiting on filewriter to finish")
129
152
  self.filewriters_finished.wait(30)
130
153
 
@@ -132,7 +155,7 @@ class EigerDetector(Device):
132
155
  finally:
133
156
  self.disarm_detector()
134
157
  status_ok = self.odin.check_and_wait_for_odin_state(
135
- self.GENERAL_STATUS_TIMEOUT
158
+ self.timeouts.general_status_timeout
136
159
  )
137
160
  self.disable_roi_mode()
138
161
  return status_ok
@@ -142,10 +165,12 @@ class EigerDetector(Device):
142
165
  LOGGER.info("Eiger stop() called - cleaning up...")
143
166
  self.wait_on_arming_if_started()
144
167
  stop_status = self.odin.stop()
145
- self.odin.file_writer.start_timeout.set(1).wait(self.GENERAL_STATUS_TIMEOUT)
168
+ self.odin.file_writer.start_timeout.set(1).wait(
169
+ self.timeouts.general_status_timeout
170
+ )
146
171
  self.disarm_detector()
147
172
  stop_status &= self.disable_roi_mode()
148
- stop_status.wait(self.GENERAL_STATUS_TIMEOUT)
173
+ stop_status.wait(self.timeouts.general_status_timeout)
149
174
  # See https://github.com/DiamondLightSource/hyperion/issues/1395
150
175
  LOGGER.info("Turning off Eiger dev/shm streaming")
151
176
  self.odin.fan.dev_shm_enable.set(0).wait()
@@ -166,19 +191,19 @@ class EigerDetector(Device):
166
191
  )
167
192
 
168
193
  status = self.cam.roi_mode.set(
169
- 1 if enable else 0, timeout=self.GENERAL_STATUS_TIMEOUT
194
+ 1 if enable else 0, timeout=self.timeouts.general_status_timeout
170
195
  )
171
196
  status &= self.odin.file_writer.image_height.set(
172
- detector_dimensions.height, timeout=self.GENERAL_STATUS_TIMEOUT
197
+ detector_dimensions.height, timeout=self.timeouts.general_status_timeout
173
198
  )
174
199
  status &= self.odin.file_writer.image_width.set(
175
- detector_dimensions.width, timeout=self.GENERAL_STATUS_TIMEOUT
200
+ detector_dimensions.width, timeout=self.timeouts.general_status_timeout
176
201
  )
177
202
  status &= self.odin.file_writer.num_row_chunks.set(
178
- detector_dimensions.height, timeout=self.GENERAL_STATUS_TIMEOUT
203
+ detector_dimensions.height, timeout=self.timeouts.general_status_timeout
179
204
  )
180
205
  status &= self.odin.file_writer.num_col_chunks.set(
181
- detector_dimensions.width, timeout=self.GENERAL_STATUS_TIMEOUT
206
+ detector_dimensions.width, timeout=self.timeouts.general_status_timeout
182
207
  )
183
208
 
184
209
  return status
@@ -186,25 +211,29 @@ class EigerDetector(Device):
186
211
  def set_cam_pvs(self) -> AndStatus:
187
212
  assert self.detector_params is not None
188
213
  status = self.cam.acquire_time.set(
189
- self.detector_params.exposure_time, timeout=self.GENERAL_STATUS_TIMEOUT
214
+ self.detector_params.exposure_time,
215
+ timeout=self.timeouts.general_status_timeout,
190
216
  )
191
217
  status &= self.cam.acquire_period.set(
192
- self.detector_params.exposure_time, timeout=self.GENERAL_STATUS_TIMEOUT
218
+ self.detector_params.exposure_time,
219
+ timeout=self.timeouts.general_status_timeout,
220
+ )
221
+ status &= self.cam.num_exposures.set(
222
+ 1, timeout=self.timeouts.general_status_timeout
193
223
  )
194
- status &= self.cam.num_exposures.set(1, timeout=self.GENERAL_STATUS_TIMEOUT)
195
224
  status &= self.cam.image_mode.set(
196
- self.cam.ImageMode.MULTIPLE, timeout=self.GENERAL_STATUS_TIMEOUT
225
+ self.cam.ImageMode.MULTIPLE, timeout=self.timeouts.general_status_timeout
197
226
  )
198
227
  status &= self.cam.trigger_mode.set(
199
228
  InternalEigerTriggerMode.EXTERNAL_SERIES.value,
200
- timeout=self.GENERAL_STATUS_TIMEOUT,
229
+ timeout=self.timeouts.general_status_timeout,
201
230
  )
202
231
  return status
203
232
 
204
233
  def set_odin_number_of_frame_chunks(self) -> Status:
205
234
  assert self.detector_params is not None
206
235
  status = self.odin.file_writer.num_frames_chunks.set(
207
- 1, timeout=self.GENERAL_STATUS_TIMEOUT
236
+ 1, timeout=self.timeouts.general_status_timeout
208
237
  )
209
238
  return status
210
239
 
@@ -212,16 +241,20 @@ class EigerDetector(Device):
212
241
  assert self.detector_params is not None
213
242
  file_prefix = self.detector_params.full_filename
214
243
  status = self.odin.file_writer.file_path.set(
215
- self.detector_params.directory, timeout=self.GENERAL_STATUS_TIMEOUT
244
+ self.detector_params.directory, timeout=self.timeouts.general_status_timeout
216
245
  )
217
246
  status &= self.odin.file_writer.file_name.set(
218
- file_prefix, timeout=self.GENERAL_STATUS_TIMEOUT
247
+ file_prefix, timeout=self.timeouts.general_status_timeout
219
248
  )
220
249
  status &= await_value(
221
- self.odin.meta.file_name, file_prefix, timeout=self.GENERAL_STATUS_TIMEOUT
250
+ self.odin.meta.file_name,
251
+ file_prefix,
252
+ timeout=self.timeouts.general_status_timeout,
222
253
  )
223
254
  status &= await_value(
224
- self.odin.file_writer.id, file_prefix, timeout=self.GENERAL_STATUS_TIMEOUT
255
+ self.odin.file_writer.id,
256
+ file_prefix,
257
+ timeout=self.timeouts.general_status_timeout,
225
258
  )
226
259
  return status
227
260
 
@@ -231,19 +264,22 @@ class EigerDetector(Device):
231
264
  self.detector_params.detector_distance
232
265
  )
233
266
  status = self.cam.beam_center_x.set(
234
- beam_x_pixels, timeout=self.GENERAL_STATUS_TIMEOUT
267
+ beam_x_pixels, timeout=self.timeouts.general_status_timeout
235
268
  )
236
269
  status &= self.cam.beam_center_y.set(
237
- beam_y_pixels, timeout=self.GENERAL_STATUS_TIMEOUT
270
+ beam_y_pixels, timeout=self.timeouts.general_status_timeout
238
271
  )
239
272
  status &= self.cam.det_distance.set(
240
- self.detector_params.detector_distance, timeout=self.GENERAL_STATUS_TIMEOUT
273
+ self.detector_params.detector_distance,
274
+ timeout=self.timeouts.general_status_timeout,
241
275
  )
242
276
  status &= self.cam.omega_start.set(
243
- self.detector_params.omega_start, timeout=self.GENERAL_STATUS_TIMEOUT
277
+ self.detector_params.omega_start,
278
+ timeout=self.timeouts.general_status_timeout,
244
279
  )
245
280
  status &= self.cam.omega_incr.set(
246
- self.detector_params.omega_increment, timeout=self.GENERAL_STATUS_TIMEOUT
281
+ self.detector_params.omega_increment,
282
+ timeout=self.timeouts.general_status_timeout,
247
283
  )
248
284
  return status
249
285
 
@@ -259,7 +295,7 @@ class EigerDetector(Device):
259
295
  current_energy = self.cam.photon_energy.get()
260
296
  if abs(current_energy - energy) > tolerance:
261
297
  return self.cam.photon_energy.set(
262
- energy, timeout=self.GENERAL_STATUS_TIMEOUT
298
+ energy, timeout=self.timeouts.general_status_timeout
263
299
  )
264
300
  else:
265
301
  status = Status()
@@ -275,45 +311,46 @@ class EigerDetector(Device):
275
311
  assert self.detector_params is not None
276
312
  status = self.cam.num_images.set(
277
313
  self.detector_params.num_images_per_trigger,
278
- timeout=self.GENERAL_STATUS_TIMEOUT,
314
+ timeout=self.timeouts.general_status_timeout,
279
315
  )
280
316
  if self.detector_params.trigger_mode == TriggerMode.FREE_RUN:
281
317
  # The Eiger can't actually free run so we set a very large number of frames
282
318
  status &= self.cam.num_triggers.set(
283
- FREE_RUN_MAX_IMAGES, timeout=self.GENERAL_STATUS_TIMEOUT
319
+ FREE_RUN_MAX_IMAGES, timeout=self.timeouts.general_status_timeout
284
320
  )
285
321
  # Setting Odin to write 0 frames tells it to write until externally stopped
286
322
  status &= self.odin.file_writer.num_capture.set(
287
- 0, timeout=self.GENERAL_STATUS_TIMEOUT
323
+ 0, timeout=self.timeouts.general_status_timeout
288
324
  )
289
325
  elif self.detector_params.trigger_mode == TriggerMode.SET_FRAMES:
290
326
  status &= self.cam.num_triggers.set(
291
- self.detector_params.num_triggers, timeout=self.GENERAL_STATUS_TIMEOUT
327
+ self.detector_params.num_triggers,
328
+ timeout=self.timeouts.general_status_timeout,
292
329
  )
293
330
  status &= self.odin.file_writer.num_capture.set(
294
331
  self.detector_params.full_number_of_images,
295
- timeout=self.GENERAL_STATUS_TIMEOUT,
332
+ timeout=self.timeouts.general_status_timeout,
296
333
  )
297
334
 
298
335
  return status
299
336
 
300
337
  def _wait_for_odin_status(self) -> StatusBase:
301
338
  self.forward_bit_depth_to_filewriter()
302
- await_value(self.odin.meta.active, 1).wait(self.GENERAL_STATUS_TIMEOUT)
339
+ await_value(self.odin.meta.active, 1).wait(self.timeouts.general_status_timeout)
303
340
 
304
341
  status = self.odin.file_writer.capture.set(
305
- 1, timeout=self.GENERAL_STATUS_TIMEOUT
342
+ 1, timeout=self.timeouts.general_status_timeout
306
343
  )
307
344
  LOGGER.info("Eiger staging: awaiting odin metadata")
308
345
  status &= await_value(
309
- self.odin.meta.ready, 1, timeout=self.META_FILE_READY_TIMEOUT
346
+ self.odin.meta.ready, 1, timeout=self.timeouts.meta_file_ready_timeout
310
347
  )
311
348
  return status
312
349
 
313
350
  def _wait_fan_ready(self) -> StatusBase:
314
351
  self.filewriters_finished = self.odin.create_finished_status()
315
352
  LOGGER.info("Eiger staging: awaiting odin fan ready")
316
- return await_value(self.odin.fan.ready, 1, self.GENERAL_STATUS_TIMEOUT)
353
+ return await_value(self.odin.fan.ready, 1, self.timeouts.general_status_timeout)
317
354
 
318
355
  def _finish_arm(self) -> Status:
319
356
  LOGGER.info("Eiger staging: Finishing arming")
@@ -324,7 +361,7 @@ class EigerDetector(Device):
324
361
  def forward_bit_depth_to_filewriter(self):
325
362
  bit_depth = self.bit_depth.get()
326
363
  self.odin.file_writer.data_type.set(f"UInt{bit_depth}").wait(
327
- self.GENERAL_STATUS_TIMEOUT
364
+ self.timeouts.general_status_timeout
328
365
  )
329
366
 
330
367
  def change_dev_shm(self, enable_dev_shm: bool):
@@ -332,7 +369,7 @@ class EigerDetector(Device):
332
369
  return self.odin.fan.dev_shm_enable.set(1 if enable_dev_shm else 0)
333
370
 
334
371
  def disarm_detector(self):
335
- self.cam.acquire.set(0).wait(self.GENERAL_STATUS_TIMEOUT)
372
+ self.cam.acquire.set(0).wait(self.timeouts.general_status_timeout)
336
373
 
337
374
  def do_arming_chain(self) -> Status:
338
375
  functions_to_do_arm = []
@@ -355,7 +392,9 @@ class EigerDetector(Device):
355
392
  self.set_num_triggers_and_captures,
356
393
  lambda: await_value(self.stale_params, 0, 60),
357
394
  self._wait_for_odin_status,
358
- lambda: self.cam.acquire.set(1, timeout=self.GENERAL_STATUS_TIMEOUT),
395
+ lambda: self.cam.acquire.set(
396
+ 1, timeout=self.timeouts.general_status_timeout
397
+ ),
359
398
  self._wait_fan_ready,
360
399
  self._finish_arm,
361
400
  ]
@@ -31,12 +31,12 @@ from dodal.parameters.experiment_parameter_base import AbstractExperimentWithBea
31
31
  @dataclass
32
32
  class GridAxis:
33
33
  start: float
34
- step_size: float
34
+ step_size_mm: float
35
35
  full_steps: int
36
36
 
37
37
  def steps_to_motor_position(self, steps):
38
38
  """Gives the motor position based on steps, where steps are 0 indexed"""
39
- return self.start + self.step_size * steps
39
+ return self.start + self.step_size_mm * steps
40
40
 
41
41
  @property
42
42
  def end(self):
@@ -62,44 +62,29 @@ class GridScanParamsCommon(AbstractExperimentWithBeamParams):
62
62
  x_steps: int = 1
63
63
  y_steps: int = 1
64
64
  z_steps: int = 0
65
- x_step_size: float = 0.1
66
- y_step_size: float = 0.1
67
- z_step_size: float = 0.1
68
- x_start: float = 0.1
69
- y1_start: float = 0.1
70
- y2_start: float = 0.1
71
- z1_start: float = 0.1
72
- z2_start: float = 0.1
65
+ x_step_size_mm: float = 0.1
66
+ y_step_size_mm: float = 0.1
67
+ z_step_size_mm: float = 0.1
68
+ x_start_mm: float = 0.1
69
+ y1_start_mm: float = 0.1
70
+ y2_start_mm: float = 0.1
71
+ z1_start_mm: float = 0.1
72
+ z2_start_mm: float = 0.1
73
73
 
74
74
  # Whether to set the stub offsets after centering
75
75
  set_stub_offsets: bool = False
76
76
 
77
- def get_param_positions(self) -> dict:
78
- return {
79
- "x_steps": self.x_steps,
80
- "y_steps": self.y_steps,
81
- "z_steps": self.z_steps,
82
- "x_step_size": self.x_step_size,
83
- "y_step_size": self.y_step_size,
84
- "z_step_size": self.z_step_size,
85
- "x_start": self.x_start,
86
- "y1_start": self.y1_start,
87
- "y2_start": self.y2_start,
88
- "z1_start": self.z1_start,
89
- "z2_start": self.z2_start,
90
- }
91
-
92
77
  @property
93
78
  def x_axis(self) -> GridAxis:
94
- return GridAxis(self.x_start, self.x_step_size, self.x_steps)
79
+ return GridAxis(self.x_start_mm, self.x_step_size_mm, self.x_steps)
95
80
 
96
81
  @property
97
82
  def y_axis(self) -> GridAxis:
98
- return GridAxis(self.y1_start, self.y_step_size, self.y_steps)
83
+ return GridAxis(self.y1_start_mm, self.y_step_size_mm, self.y_steps)
99
84
 
100
85
  @property
101
86
  def z_axis(self) -> GridAxis:
102
- return GridAxis(self.z2_start, self.z_step_size, self.z_steps)
87
+ return GridAxis(self.z2_start_mm, self.z_step_size_mm, self.z_steps)
103
88
 
104
89
  def get_num_images(self):
105
90
  return self.x_steps * (self.y_steps + self.z_steps)
@@ -140,11 +125,6 @@ class ZebraGridScanParams(GridScanParamsCommon):
140
125
 
141
126
  dwell_time_ms: float = 10
142
127
 
143
- def get_param_positions(self):
144
- param_positions = super().get_param_positions()
145
- param_positions["dwell_time_ms"] = self.dwell_time_ms
146
- return param_positions
147
-
148
128
  @field_validator("dwell_time_ms")
149
129
  @classmethod
150
130
  def non_integer_dwell_time(cls, dwell_time_ms: float) -> float:
@@ -166,11 +146,6 @@ class PandAGridScanParams(GridScanParamsCommon):
166
146
 
167
147
  run_up_distance_mm: float = 0.17
168
148
 
169
- def get_param_positions(self):
170
- param_positions = super().get_param_positions()
171
- param_positions["run_up_distance_mm"] = self.run_up_distance_mm
172
- return param_positions
173
-
174
149
 
175
150
  class MotionProgram(Device):
176
151
  def __init__(self, prefix: str, name: str = "") -> None:
@@ -241,14 +216,14 @@ class FastGridScanCommon(StandardReadable, Flyable, ABC, Generic[ParamType]):
241
216
  "x_steps": self.x_steps,
242
217
  "y_steps": self.y_steps,
243
218
  "z_steps": self.z_steps,
244
- "x_step_size": self.x_step_size,
245
- "y_step_size": self.y_step_size,
246
- "z_step_size": self.z_step_size,
247
- "x_start": self.x_start,
248
- "y1_start": self.y1_start,
249
- "y2_start": self.y2_start,
250
- "z1_start": self.z1_start,
251
- "z2_start": self.z2_start,
219
+ "x_step_size_mm": self.x_step_size,
220
+ "y_step_size_mm": self.y_step_size,
221
+ "z_step_size_mm": self.z_step_size,
222
+ "x_start_mm": self.x_start,
223
+ "y1_start_mm": self.y1_start,
224
+ "y2_start_mm": self.y2_start,
225
+ "z1_start_mm": self.z1_start,
226
+ "z2_start_mm": self.z2_start,
252
227
  }
253
228
  super().__init__(name)
254
229
 
@@ -130,7 +130,12 @@ class FocusingMirror(StandardReadable):
130
130
  """Focusing Mirror"""
131
131
 
132
132
  def __init__(
133
- self, name, prefix, bragg_to_lat_lut_path=None, x_suffix="X", y_suffix="Y"
133
+ self,
134
+ prefix: str,
135
+ name: str = "",
136
+ bragg_to_lat_lut_path: str | None = None,
137
+ x_suffix: str = "X",
138
+ y_suffix: str = "Y",
134
139
  ):
135
140
  self.bragg_to_lat_lookup_table_path = bragg_to_lat_lut_path
136
141
  self.yaw_mrad = Motor(prefix + "YAW")
@@ -161,12 +166,12 @@ class FocusingMirrorWithStripes(FocusingMirror):
161
166
  """A focusing mirror where the stripe material can be changed. This is usually done
162
167
  based on the energy of the beamline."""
163
168
 
164
- def __init__(self, name, prefix, *args, **kwargs):
169
+ def __init__(self, prefix: str, name: str = "", *args, **kwargs):
165
170
  self.stripe = epics_signal_rw(MirrorStripe, prefix + "STRP:DVAL")
166
171
  # apply the current set stripe setting
167
172
  self.apply_stripe = epics_signal_x(prefix + "CHANGE.PROC")
168
173
 
169
- super().__init__(name, prefix, *args, **kwargs)
174
+ super().__init__(prefix, name, *args, **kwargs)
170
175
 
171
176
  def energy_to_stripe(self, energy_kev) -> MirrorStripe:
172
177
  # In future, this should be configurable per-mirror
@@ -0,0 +1,12 @@
1
+ """A small temporary device to get the beam center positions from \
2
+ eiger or pilatus detector on i24"""
3
+
4
+ from ophyd_async.core import StandardReadable
5
+ from ophyd_async.epics.core import epics_signal_rw
6
+
7
+
8
+ class DetectorBeamCenter(StandardReadable):
9
+ def __init__(self, prefix: str, name: str = "") -> None:
10
+ self.beam_x = epics_signal_rw(float, prefix + "BeamX")
11
+ self.beam_y = epics_signal_rw(float, prefix + "BeamY")
12
+ super().__init__(name)
@@ -0,0 +1,60 @@
1
+ from ophyd_async.core import StandardReadable, StrictEnum
2
+ from ophyd_async.epics.core import epics_signal_rw
3
+
4
+ from dodal.common.signal_utils import create_hardware_backed_soft_signal
5
+
6
+
7
+ class HFocusMode(StrictEnum):
8
+ focus10 = "HMFMfocus10"
9
+ focus20d = "HMFMfocus20d"
10
+ focus30d = "HMFMfocus30d"
11
+ focus50d = "HMFMfocus50d"
12
+ focus1050d = "HMFMfocus1030d"
13
+ focus3010d = "HMFMfocus3010d"
14
+
15
+
16
+ class VFocusMode(StrictEnum):
17
+ focus10 = "VMFMfocus10"
18
+ focus20d = "VMFMfocus20d"
19
+ focus30d = "VMFMfocus30d"
20
+ focus50d = "VMFMfocus50d"
21
+ focus1030d = "VMFMfocus1030d"
22
+ focus3010d = "VMFMfocus3010d"
23
+
24
+
25
+ BEAM_SIZES = {
26
+ "focus10": [7, 7],
27
+ "focus20d": [20, 20],
28
+ "focus30d": [30, 30],
29
+ "focus50d": [50, 50],
30
+ "focus1030d": [10, 30],
31
+ "focus3010d": [30, 10],
32
+ }
33
+
34
+
35
+ class FocusMirrorsMode(StandardReadable):
36
+ """A small device to read the focus mode and work out the beam size."""
37
+
38
+ def __init__(self, prefix: str, name: str = "") -> None:
39
+ self.horizontal = epics_signal_rw(HFocusMode, prefix + "G1:TARGETAPPLY")
40
+ self.vertical = epics_signal_rw(VFocusMode, prefix + "G0:TARGETAPPLY")
41
+
42
+ with self.add_children_as_readables():
43
+ self.beam_size_x = create_hardware_backed_soft_signal(
44
+ int, self._get_beam_size_x, units="um"
45
+ )
46
+ self.beam_size_y = create_hardware_backed_soft_signal(
47
+ int, self._get_beam_size_y, units="um"
48
+ )
49
+
50
+ super().__init__(name)
51
+
52
+ async def _get_beam_size_x(self) -> int:
53
+ h_mode = await self.horizontal.get_value()
54
+ beam_x = BEAM_SIZES[h_mode.removeprefix("HMFM")][0]
55
+ return beam_x
56
+
57
+ async def _get_beam_size_y(self) -> int:
58
+ v_mode = await self.vertical.get_value()
59
+ beam_y = BEAM_SIZES[v_mode.removeprefix("VMFM")][1]
60
+ return beam_y
@@ -0,0 +1,44 @@
1
+ """A small temporary device to set and read the filename template from the pilatus"""
2
+
3
+ import re
4
+
5
+ from ophyd_async.core import StandardReadable
6
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
7
+
8
+ from dodal.common.signal_utils import create_hardware_backed_soft_signal
9
+
10
+
11
+ class PilatusMetadata(StandardReadable):
12
+ def __init__(self, prefix: str, name: str = "") -> None:
13
+ self.filename = epics_signal_rw(str, prefix + "cam1:FileName")
14
+ self.template = epics_signal_r(str, prefix + "cam1:FileTemplate_RBV")
15
+ self.filenumber = epics_signal_r(int, prefix + "cam1:FileNumber_RBV")
16
+ with self.add_children_as_readables():
17
+ self.filename_template = create_hardware_backed_soft_signal(
18
+ str, self._get_full_filename_template
19
+ )
20
+ super().__init__(name)
21
+
22
+ async def _get_full_filename_template(self) -> str:
23
+ """
24
+ Get the template file path by querying the detector PVs.
25
+ Mirror the construction that the PPU does.
26
+
27
+ Returns: A template string, with the image numbers replaced with '#'
28
+ """
29
+ filename = await self.filename.get_value()
30
+ filename_template = await self.template.get_value()
31
+ file_number = await self.filenumber.get_value()
32
+ # Exploit fact that passing negative numbers will put the - before the 0's
33
+ expected_filename = str(
34
+ filename_template % (filename, f"{file_number:05d}_", -9)
35
+ )
36
+ # Now, find the -09 part of this
37
+ numberpart = re.search(r"(-0+9)", expected_filename)
38
+ assert numberpart is not None
39
+ template_fill = "#" * len(numberpart.group(0))
40
+ return (
41
+ expected_filename[: numberpart.start()]
42
+ + template_fill
43
+ + expected_filename[numberpart.end() :]
44
+ )
dodal/devices/linkam3.py CHANGED
@@ -33,7 +33,7 @@ class Linkam3(StandardReadable):
33
33
  tolerance: float = 0.5
34
34
  settle_time: int = 0
35
35
 
36
- def __init__(self, prefix: str, name: str):
36
+ def __init__(self, prefix: str, name: str = ""):
37
37
  self.temp = epics_signal_r(float, prefix + "TEMP:")
38
38
  self.dsc = epics_signal_r(float, prefix + "DSC:")
39
39
  self.start_heat = epics_signal_rw(bool, prefix + "STARTHEAT:")
dodal/devices/motors.py CHANGED
@@ -1,8 +1,8 @@
1
- from ophyd_async.core import Device
1
+ from ophyd_async.core import StandardReadable
2
2
  from ophyd_async.epics.motor import Motor
3
3
 
4
4
 
5
- class XYZPositioner(Device):
5
+ class XYZPositioner(StandardReadable):
6
6
  """
7
7
 
8
8
  Standard ophyd_async xyz motor stage, by combining 3 Motors,
@@ -15,7 +15,7 @@ class XYZPositioner(Device):
15
15
  name:
16
16
  name for the stage.
17
17
  infix:
18
- EPICS PV, default is the ["X", "Y", "Z"].
18
+ EPICS PV, default is the ("X", "Y", "Z").
19
19
  Notes
20
20
  -----
21
21
  Example usage::
@@ -23,14 +23,18 @@ class XYZPositioner(Device):
23
23
  xyz_stage = XYZPositioner("BLXX-MO-STAGE-XX:")
24
24
  Or::
25
25
  with DeviceCollector():
26
- xyz_stage = XYZPositioner("BLXX-MO-STAGE-XX:", suffix = ["A", "B", "C"])
26
+ xyz_stage = XYZPositioner("BLXX-MO-STAGE-XX:", infix = ("A", "B", "C"))
27
27
 
28
28
  """
29
29
 
30
- def __init__(self, prefix: str, name: str, infix: list[str] | None = None):
31
- if infix is None:
32
- infix = ["X", "Y", "Z"]
33
- self.x = Motor(prefix + infix[0])
34
- self.y = Motor(prefix + infix[1])
35
- self.z = Motor(prefix + infix[2])
30
+ def __init__(
31
+ self,
32
+ prefix: str,
33
+ name: str = "",
34
+ infix: tuple[str, str, str] = ("X", "Y", "Z"),
35
+ ):
36
+ with self.add_children_as_readables():
37
+ self.x = Motor(prefix + infix[0])
38
+ self.y = Motor(prefix + infix[1])
39
+ self.z = Motor(prefix + infix[2])
36
40
  super().__init__(name=name)