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.
- {dls_dodal-1.35.0.dist-info → dls_dodal-1.36.1a0.dist-info}/METADATA +33 -31
- {dls_dodal-1.35.0.dist-info → dls_dodal-1.36.1a0.dist-info}/RECORD +46 -37
- {dls_dodal-1.35.0.dist-info → dls_dodal-1.36.1a0.dist-info}/WHEEL +1 -1
- dodal/_version.py +2 -2
- dodal/beamlines/b01_1.py +16 -31
- dodal/beamlines/i22.py +124 -265
- dodal/beamlines/i24.py +56 -7
- dodal/beamlines/p38.py +16 -1
- dodal/beamlines/p99.py +22 -53
- dodal/beamlines/training_rig.py +16 -26
- dodal/cli.py +54 -8
- dodal/common/beamlines/beamline_utils.py +32 -2
- dodal/common/beamlines/device_helpers.py +2 -0
- dodal/devices/aperture.py +7 -0
- dodal/devices/aperturescatterguard.py +195 -79
- dodal/devices/dcm.py +5 -4
- dodal/devices/eiger.py +88 -49
- dodal/devices/fast_grid_scan.py +21 -46
- dodal/devices/focusing_mirror.py +8 -3
- dodal/devices/i24/beam_center.py +12 -0
- dodal/devices/i24/focus_mirrors.py +60 -0
- dodal/devices/i24/pilatus_metadata.py +44 -0
- dodal/devices/linkam3.py +1 -1
- dodal/devices/motors.py +14 -10
- dodal/devices/oav/oav_detector.py +2 -2
- dodal/devices/oav/pin_image_recognition/__init__.py +4 -5
- dodal/devices/oav/utils.py +1 -0
- dodal/devices/p99/sample_stage.py +12 -16
- dodal/devices/pressure_jump_cell.py +299 -0
- dodal/devices/robot.py +1 -1
- dodal/devices/tetramm.py +1 -1
- dodal/devices/undulator.py +4 -1
- dodal/devices/undulator_dcm.py +3 -19
- dodal/devices/zocalo/zocalo_results.py +7 -7
- dodal/plan_stubs/__init__.py +0 -0
- dodal/{plans/data_session_metadata.py → plan_stubs/data_session.py} +2 -2
- dodal/{plans/motor_util_plans.py → plan_stubs/motor_utils.py} +2 -2
- dodal/plan_stubs/wrapped.py +150 -0
- dodal/plans/__init__.py +4 -0
- dodal/plans/scanspec.py +66 -0
- dodal/plans/wrapped.py +57 -0
- dodal/utils.py +151 -2
- {dls_dodal-1.35.0.dist-info → dls_dodal-1.36.1a0.dist-info}/LICENSE +0 -0
- {dls_dodal-1.35.0.dist-info → dls_dodal-1.36.1a0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.35.0.dist-info → dls_dodal-1.36.1a0.dist-info}/top_level.txt +0 -0
- /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.
|
|
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.
|
|
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.
|
|
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.
|
|
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(
|
|
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.
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
247
|
+
file_prefix, timeout=self.timeouts.general_status_timeout
|
|
219
248
|
)
|
|
220
249
|
status &= await_value(
|
|
221
|
-
self.odin.meta.file_name,
|
|
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,
|
|
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.
|
|
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.
|
|
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,
|
|
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,
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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(
|
|
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
|
]
|
dodal/devices/fast_grid_scan.py
CHANGED
|
@@ -31,12 +31,12 @@ from dodal.parameters.experiment_parameter_base import AbstractExperimentWithBea
|
|
|
31
31
|
@dataclass
|
|
32
32
|
class GridAxis:
|
|
33
33
|
start: float
|
|
34
|
-
|
|
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.
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
245
|
-
"
|
|
246
|
-
"
|
|
247
|
-
"
|
|
248
|
-
"
|
|
249
|
-
"
|
|
250
|
-
"
|
|
251
|
-
"
|
|
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
|
|
dodal/devices/focusing_mirror.py
CHANGED
|
@@ -130,7 +130,12 @@ class FocusingMirror(StandardReadable):
|
|
|
130
130
|
"""Focusing Mirror"""
|
|
131
131
|
|
|
132
132
|
def __init__(
|
|
133
|
-
self,
|
|
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,
|
|
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__(
|
|
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
|
|
1
|
+
from ophyd_async.core import StandardReadable
|
|
2
2
|
from ophyd_async.epics.motor import Motor
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
class XYZPositioner(
|
|
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
|
|
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:",
|
|
26
|
+
xyz_stage = XYZPositioner("BLXX-MO-STAGE-XX:", infix = ("A", "B", "C"))
|
|
27
27
|
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
|
-
def __init__(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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)
|