dls-dodal 1.29.4__py3-none-any.whl → 1.31.0__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.29.4.dist-info → dls_dodal-1.31.0.dist-info}/METADATA +29 -44
- dls_dodal-1.31.0.dist-info/RECORD +134 -0
- {dls_dodal-1.29.4.dist-info → dls_dodal-1.31.0.dist-info}/WHEEL +1 -1
- dls_dodal-1.31.0.dist-info/entry_points.txt +3 -0
- dodal/__init__.py +1 -4
- dodal/_version.py +2 -2
- dodal/beamline_specific_utils/i03.py +1 -4
- dodal/beamlines/__init__.py +7 -1
- dodal/beamlines/i03.py +34 -29
- dodal/beamlines/i04.py +39 -16
- dodal/beamlines/i13_1.py +66 -0
- dodal/beamlines/i22.py +22 -22
- dodal/beamlines/i24.py +1 -1
- dodal/beamlines/p38.py +21 -21
- dodal/beamlines/p45.py +18 -16
- dodal/beamlines/p99.py +61 -0
- dodal/beamlines/training_rig.py +64 -0
- dodal/cli.py +6 -3
- dodal/common/beamlines/beamline_parameters.py +7 -6
- dodal/common/beamlines/beamline_utils.py +15 -14
- dodal/common/maths.py +1 -3
- dodal/common/types.py +6 -5
- dodal/common/udc_directory_provider.py +39 -21
- dodal/common/visit.py +60 -62
- dodal/devices/CTAB.py +22 -17
- dodal/devices/aperture.py +1 -1
- dodal/devices/aperturescatterguard.py +139 -209
- dodal/devices/areadetector/adaravis.py +8 -6
- dodal/devices/areadetector/adsim.py +2 -3
- dodal/devices/areadetector/adutils.py +20 -12
- dodal/devices/areadetector/plugins/MJPG.py +2 -1
- dodal/devices/backlight.py +12 -1
- dodal/devices/cryostream.py +19 -7
- dodal/devices/dcm.py +1 -1
- dodal/devices/detector/__init__.py +13 -2
- dodal/devices/detector/det_dim_constants.py +2 -2
- dodal/devices/detector/det_dist_to_beam_converter.py +1 -1
- dodal/devices/detector/detector.py +33 -32
- dodal/devices/detector/detector_motion.py +38 -31
- dodal/devices/eiger.py +11 -15
- dodal/devices/eiger_odin.py +9 -10
- dodal/devices/fast_grid_scan.py +18 -27
- dodal/devices/fluorescence_detector_motion.py +13 -4
- dodal/devices/focusing_mirror.py +6 -6
- dodal/devices/hutch_shutter.py +4 -4
- dodal/devices/i22/dcm.py +5 -4
- dodal/devices/i22/fswitch.py +10 -6
- dodal/devices/i22/nxsas.py +55 -43
- dodal/devices/i24/aperture.py +1 -1
- dodal/devices/i24/beamstop.py +1 -1
- dodal/devices/i24/dcm.py +1 -1
- dodal/devices/i24/{I24_detector_motion.py → i24_detector_motion.py} +1 -1
- dodal/devices/i24/pmac.py +67 -12
- dodal/devices/ipin.py +7 -4
- dodal/devices/linkam3.py +12 -6
- dodal/devices/logging_ophyd_device.py +1 -1
- dodal/devices/motors.py +32 -6
- dodal/devices/oav/grid_overlay.py +1 -0
- dodal/devices/oav/microns_for_zoom_levels.json +1 -1
- dodal/devices/oav/oav_detector.py +2 -1
- dodal/devices/oav/oav_parameters.py +18 -10
- dodal/devices/oav/oav_to_redis_forwarder.py +129 -0
- dodal/devices/oav/pin_image_recognition/__init__.py +6 -6
- dodal/devices/oav/pin_image_recognition/utils.py +5 -6
- dodal/devices/oav/utils.py +2 -2
- dodal/devices/p99/__init__.py +0 -0
- dodal/devices/p99/sample_stage.py +43 -0
- dodal/devices/robot.py +31 -20
- dodal/devices/scatterguard.py +1 -1
- dodal/devices/scintillator.py +8 -5
- dodal/devices/slits.py +1 -1
- dodal/devices/smargon.py +4 -4
- dodal/devices/status.py +2 -31
- dodal/devices/tetramm.py +23 -19
- dodal/devices/thawer.py +5 -3
- dodal/devices/training_rig/__init__.py +0 -0
- dodal/devices/training_rig/sample_stage.py +10 -0
- dodal/devices/turbo_slit.py +1 -1
- dodal/devices/undulator.py +1 -1
- dodal/devices/undulator_dcm.py +6 -8
- dodal/devices/util/adjuster_plans.py +3 -3
- dodal/devices/util/epics_util.py +5 -7
- dodal/devices/util/lookup_tables.py +2 -3
- dodal/devices/util/save_panda.py +87 -0
- dodal/devices/util/test_utils.py +17 -0
- dodal/devices/webcam.py +3 -3
- dodal/devices/xbpm_feedback.py +1 -25
- dodal/devices/xspress3/xspress3.py +1 -1
- dodal/devices/zebra.py +15 -10
- dodal/devices/zebra_controlled_shutter.py +26 -11
- dodal/devices/zocalo/zocalo_interaction.py +10 -2
- dodal/devices/zocalo/zocalo_results.py +36 -19
- dodal/log.py +46 -15
- dodal/plans/check_topup.py +65 -10
- dodal/plans/data_session_metadata.py +8 -9
- dodal/plans/motor_util_plans.py +117 -0
- dodal/utils.py +65 -22
- dls_dodal-1.29.4.dist-info/RECORD +0 -125
- dls_dodal-1.29.4.dist-info/entry_points.txt +0 -2
- dodal/devices/beamstop.py +0 -8
- dodal/devices/qbpm1.py +0 -8
- {dls_dodal-1.29.4.dist-info → dls_dodal-1.31.0.dist-info}/LICENSE +0 -0
- {dls_dodal-1.29.4.dist-info → dls_dodal-1.31.0.dist-info}/top_level.txt +0 -0
|
@@ -5,6 +5,7 @@ import numpy as np
|
|
|
5
5
|
from numpy.typing import NDArray
|
|
6
6
|
from ophyd_async.core import (
|
|
7
7
|
AsyncStatus,
|
|
8
|
+
HintedSignal,
|
|
8
9
|
StandardReadable,
|
|
9
10
|
observe_value,
|
|
10
11
|
soft_signal_r_and_setter,
|
|
@@ -77,12 +78,13 @@ class PinTipDetection(StandardReadable):
|
|
|
77
78
|
self.min_tip_height = soft_signal_rw(int, 5, name="min_tip_height")
|
|
78
79
|
self.validity_timeout = soft_signal_rw(float, 5.0, name="validity_timeout")
|
|
79
80
|
|
|
80
|
-
self.
|
|
81
|
-
|
|
81
|
+
self.add_readables(
|
|
82
|
+
[
|
|
82
83
|
self.triggered_tip,
|
|
83
84
|
self.triggered_top_edge,
|
|
84
85
|
self.triggered_bottom_edge,
|
|
85
86
|
],
|
|
87
|
+
wrapper=HintedSignal,
|
|
86
88
|
)
|
|
87
89
|
|
|
88
90
|
super().__init__(name=name)
|
|
@@ -134,9 +136,7 @@ class PinTipDetection(StandardReadable):
|
|
|
134
136
|
location = sample_detection.processArray(array_data)
|
|
135
137
|
end_time = time.time()
|
|
136
138
|
LOGGER.debug(
|
|
137
|
-
"Sample location detection took {}ms"
|
|
138
|
-
(end_time - start_time) * 1000.0
|
|
139
|
-
)
|
|
139
|
+
f"Sample location detection took {(end_time - start_time) * 1000.0}ms"
|
|
140
140
|
)
|
|
141
141
|
return location
|
|
142
142
|
|
|
@@ -154,7 +154,7 @@ class PinTipDetection(StandardReadable):
|
|
|
154
154
|
location = await self._get_tip_and_edge_data(value)
|
|
155
155
|
self._set_triggered_values(location)
|
|
156
156
|
except Exception as e:
|
|
157
|
-
LOGGER.
|
|
157
|
+
LOGGER.warning(
|
|
158
158
|
f"Failed to detect pin-tip location, will retry with next image: {e}"
|
|
159
159
|
)
|
|
160
160
|
else:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
1
2
|
from dataclasses import dataclass
|
|
2
3
|
from enum import Enum
|
|
3
|
-
from typing import
|
|
4
|
+
from typing import Final
|
|
4
5
|
|
|
5
6
|
import cv2
|
|
6
7
|
import numpy as np
|
|
@@ -103,7 +104,7 @@ class SampleLocation:
|
|
|
103
104
|
edge_bottom: np.ndarray
|
|
104
105
|
|
|
105
106
|
|
|
106
|
-
class MxSampleDetect
|
|
107
|
+
class MxSampleDetect:
|
|
107
108
|
def __init__(
|
|
108
109
|
self,
|
|
109
110
|
*,
|
|
@@ -161,7 +162,7 @@ class MxSampleDetect(object):
|
|
|
161
162
|
@staticmethod
|
|
162
163
|
def _first_and_last_nonzero_by_columns(
|
|
163
164
|
arr: np.ndarray,
|
|
164
|
-
) ->
|
|
165
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
165
166
|
"""
|
|
166
167
|
Finds the indexes of the first & last non-zero values by column in a 2d array.
|
|
167
168
|
|
|
@@ -243,9 +244,7 @@ class MxSampleDetect(object):
|
|
|
243
244
|
bottom[x + 1 :] = NONE_VALUE
|
|
244
245
|
|
|
245
246
|
LOGGER.info(
|
|
246
|
-
"pin-tip detection: Successfully located pin tip at (x={}, y={})"
|
|
247
|
-
tip_x, tip_y
|
|
248
|
-
)
|
|
247
|
+
f"pin-tip detection: Successfully located pin tip at (x={tip_x}, y={tip_y})"
|
|
249
248
|
)
|
|
250
249
|
return SampleLocation(
|
|
251
250
|
tip_x=tip_x, tip_y=tip_y, edge_bottom=bottom, edge_top=top
|
dodal/devices/oav/utils.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
from collections.abc import Generator
|
|
1
2
|
from enum import IntEnum
|
|
2
|
-
from typing import Generator, Tuple
|
|
3
3
|
|
|
4
4
|
import bluesky.plan_stubs as bps
|
|
5
5
|
import numpy as np
|
|
@@ -10,7 +10,7 @@ from dodal.devices.oav.oav_detector import OAVConfigParams
|
|
|
10
10
|
from dodal.devices.oav.pin_image_recognition import PinTipDetection
|
|
11
11
|
from dodal.devices.smargon import Smargon
|
|
12
12
|
|
|
13
|
-
Pixel =
|
|
13
|
+
Pixel = tuple[int, int]
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class PinNotFoundException(Exception):
|
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from ophyd_async.core import Device
|
|
4
|
+
from ophyd_async.epics.signal import epics_signal_rw
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SampleAngleStage(Device):
|
|
8
|
+
def __init__(self, prefix: str, name: str):
|
|
9
|
+
self.theta = epics_signal_rw(
|
|
10
|
+
float, prefix + "WRITETHETA:RBV", prefix + "WRITETHETA"
|
|
11
|
+
)
|
|
12
|
+
self.roll = epics_signal_rw(
|
|
13
|
+
float, prefix + "WRITEROLL:RBV", prefix + "WRITEROLL"
|
|
14
|
+
)
|
|
15
|
+
self.pitch = epics_signal_rw(
|
|
16
|
+
float, prefix + "WRITEPITCH:RBV", prefix + "WRITEPITCH"
|
|
17
|
+
)
|
|
18
|
+
super().__init__(name=name)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class p99StageSelections(str, Enum):
|
|
22
|
+
Empty = "Empty"
|
|
23
|
+
Mn5um = "Mn 5um"
|
|
24
|
+
Fe = "Fe (empty)"
|
|
25
|
+
Co5um = "Co 5um"
|
|
26
|
+
Ni5um = "Ni 5um"
|
|
27
|
+
Cu5um = "Cu 5um"
|
|
28
|
+
Zn5um = "Zn 5um"
|
|
29
|
+
Zr = "Zr (empty)"
|
|
30
|
+
Mo = "Mo (empty)"
|
|
31
|
+
Rh = "Rh (empty)"
|
|
32
|
+
Pd = "Pd (empty)"
|
|
33
|
+
Ag = "Ag (empty)"
|
|
34
|
+
Cd25um = "Cd 25um"
|
|
35
|
+
W = "W (empty)"
|
|
36
|
+
Pt = "Pt (empty)"
|
|
37
|
+
User = "User"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class FilterMotor(Device):
|
|
41
|
+
def __init__(self, prefix: str, name: str):
|
|
42
|
+
self.user_setpoint = epics_signal_rw(p99StageSelections, prefix)
|
|
43
|
+
super().__init__(name=name)
|
dodal/devices/robot.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from asyncio import FIRST_COMPLETED, Task
|
|
2
|
+
from asyncio import FIRST_COMPLETED, CancelledError, Task
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from enum import Enum
|
|
5
5
|
|
|
@@ -10,8 +10,7 @@ from ophyd_async.core import (
|
|
|
10
10
|
set_and_wait_for_value,
|
|
11
11
|
wait_for_value,
|
|
12
12
|
)
|
|
13
|
-
from ophyd_async.epics.signal import epics_signal_r, epics_signal_x
|
|
14
|
-
from ophyd_async.epics.signal.signal import epics_signal_rw_rbv
|
|
13
|
+
from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw_rbv, epics_signal_x
|
|
15
14
|
|
|
16
15
|
from dodal.log import LOGGER
|
|
17
16
|
|
|
@@ -45,6 +44,9 @@ class BartRobot(StandardReadable, Movable):
|
|
|
45
44
|
LOAD_TIMEOUT = 60
|
|
46
45
|
NO_PIN_ERROR_CODE = 25
|
|
47
46
|
|
|
47
|
+
# How far the gonio position can be out before loading will fail
|
|
48
|
+
LOAD_TOLERANCE_MM = 0.02
|
|
49
|
+
|
|
48
50
|
def __init__(
|
|
49
51
|
self,
|
|
50
52
|
name: str,
|
|
@@ -74,19 +76,28 @@ class BartRobot(StandardReadable, Movable):
|
|
|
74
76
|
await wait_for_value(self.error_code, self.NO_PIN_ERROR_CODE, None)
|
|
75
77
|
raise RobotLoadFailed(self.NO_PIN_ERROR_CODE, "Pin was not detected")
|
|
76
78
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
79
|
+
async def wfv():
|
|
80
|
+
await wait_for_value(self.gonio_pin_sensor, PinMounted.PIN_MOUNTED, None)
|
|
81
|
+
|
|
82
|
+
tasks = [
|
|
83
|
+
(Task(raise_if_no_pin())),
|
|
84
|
+
(Task(wfv())),
|
|
85
|
+
]
|
|
86
|
+
try:
|
|
87
|
+
finished, unfinished = await asyncio.wait(
|
|
88
|
+
tasks,
|
|
89
|
+
return_when=FIRST_COMPLETED,
|
|
90
|
+
)
|
|
91
|
+
for task in unfinished:
|
|
92
|
+
task.cancel()
|
|
93
|
+
for task in finished:
|
|
94
|
+
await task
|
|
95
|
+
except CancelledError:
|
|
96
|
+
# If the outer enclosing task cancels after LOAD_TIMEOUT, this causes CancelledError to be raised
|
|
97
|
+
# in the current task, when it propagates to here we should cancel all pending tasks before bubbling up
|
|
98
|
+
for task in tasks:
|
|
99
|
+
task.cancel()
|
|
100
|
+
raise
|
|
90
101
|
|
|
91
102
|
async def _load_pin_and_puck(self, sample_location: SampleLocation):
|
|
92
103
|
LOGGER.info(f"Loading pin {sample_location}")
|
|
@@ -108,12 +119,12 @@ class BartRobot(StandardReadable, Movable):
|
|
|
108
119
|
await self.pin_mounted_or_no_pin_found()
|
|
109
120
|
|
|
110
121
|
@AsyncStatus.wrap
|
|
111
|
-
async def set(self,
|
|
122
|
+
async def set(self, value: SampleLocation):
|
|
112
123
|
try:
|
|
113
124
|
await asyncio.wait_for(
|
|
114
|
-
self._load_pin_and_puck(
|
|
125
|
+
self._load_pin_and_puck(value), timeout=self.LOAD_TIMEOUT
|
|
115
126
|
)
|
|
116
|
-
except asyncio.TimeoutError:
|
|
127
|
+
except asyncio.TimeoutError as e:
|
|
117
128
|
error_code = await self.error_code.get_value()
|
|
118
129
|
error_string = await self.error_str.get_value()
|
|
119
|
-
raise RobotLoadFailed(error_code, error_string)
|
|
130
|
+
raise RobotLoadFailed(int(error_code), error_string) from e
|
dodal/devices/scatterguard.py
CHANGED
dodal/devices/scintillator.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
from
|
|
2
|
-
from
|
|
1
|
+
from ophyd_async.core import StandardReadable
|
|
2
|
+
from ophyd_async.epics.motor import Motor
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
class Scintillator(
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
class Scintillator(StandardReadable):
|
|
6
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
7
|
+
with self.add_children_as_readables():
|
|
8
|
+
self.y = Motor(prefix + "-MO-SCIN-01:Y")
|
|
9
|
+
self.z = Motor(prefix + "-MO-SCIN-01:Z")
|
|
10
|
+
super().__init__(name)
|
dodal/devices/slits.py
CHANGED
dodal/devices/smargon.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
from collections.abc import Generator
|
|
1
|
+
from collections.abc import Collection, Generator
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from enum import Enum
|
|
4
4
|
from math import isclose
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import cast
|
|
6
6
|
|
|
7
7
|
from bluesky import plan_stubs as bps
|
|
8
8
|
from bluesky.utils import Msg
|
|
9
9
|
from ophyd_async.core import AsyncStatus, Device, StandardReadable, wait_for_value
|
|
10
|
-
from ophyd_async.epics.
|
|
10
|
+
from ophyd_async.epics.motor import Motor
|
|
11
11
|
from ophyd_async.epics.signal import epics_signal_r
|
|
12
12
|
|
|
13
13
|
from dodal.devices.util.epics_util import SetWhenEnabled
|
|
@@ -87,7 +87,7 @@ class XYZLimits:
|
|
|
87
87
|
def position_valid(self, pos: Collection[float]) -> bool:
|
|
88
88
|
return all(
|
|
89
89
|
axis_limits.contains(value)
|
|
90
|
-
for axis_limits, value in zip([self.x, self.y, self.z], pos)
|
|
90
|
+
for axis_limits, value in zip([self.x, self.y, self.z], pos, strict=False)
|
|
91
91
|
)
|
|
92
92
|
|
|
93
93
|
|
dodal/devices/status.py
CHANGED
|
@@ -1,41 +1,12 @@
|
|
|
1
|
-
from typing import Any
|
|
1
|
+
from typing import Any
|
|
2
2
|
|
|
3
3
|
from ophyd.status import SubscriptionStatus
|
|
4
4
|
|
|
5
|
-
T = TypeVar("T")
|
|
6
|
-
|
|
7
5
|
|
|
8
6
|
def await_value(
|
|
9
|
-
subscribable: Any, expected_value:
|
|
7
|
+
subscribable: Any, expected_value: object, timeout: None | int = None
|
|
10
8
|
) -> SubscriptionStatus:
|
|
11
9
|
def value_is(value, **_):
|
|
12
10
|
return value == expected_value
|
|
13
11
|
|
|
14
12
|
return SubscriptionStatus(subscribable, value_is, timeout=timeout)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def await_value_in_list(
|
|
18
|
-
subscribable: Any, expected_value: list, timeout: None | int = None
|
|
19
|
-
) -> SubscriptionStatus:
|
|
20
|
-
"""Returns a status which is completed when the subscriptable contains a value
|
|
21
|
-
within the expected_value list"""
|
|
22
|
-
|
|
23
|
-
def value_is(value, **_):
|
|
24
|
-
return value in expected_value
|
|
25
|
-
|
|
26
|
-
if not isinstance(expected_value, list):
|
|
27
|
-
raise TypeError(f"expected value {expected_value} is not a list")
|
|
28
|
-
else:
|
|
29
|
-
return SubscriptionStatus(subscribable, value_is, timeout=timeout)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def await_approx_value(
|
|
33
|
-
subscribable: Any,
|
|
34
|
-
expected_value: T,
|
|
35
|
-
deadband: float = 1e-09,
|
|
36
|
-
timeout: None | int = None,
|
|
37
|
-
) -> SubscriptionStatus:
|
|
38
|
-
def value_is_approx(value, **_):
|
|
39
|
-
return abs(value - expected_value) <= deadband
|
|
40
|
-
|
|
41
|
-
return SubscriptionStatus(subscribable, value_is_approx, timeout=timeout)
|
dodal/devices/tetramm.py
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from enum import Enum
|
|
3
|
-
from typing import Sequence
|
|
4
3
|
|
|
5
4
|
from bluesky.protocols import Hints
|
|
6
5
|
from ophyd_async.core import (
|
|
7
6
|
AsyncStatus,
|
|
7
|
+
DatasetDescriber,
|
|
8
8
|
DetectorControl,
|
|
9
9
|
DetectorTrigger,
|
|
10
10
|
Device,
|
|
11
|
-
|
|
12
|
-
ShapeProvider,
|
|
11
|
+
PathProvider,
|
|
13
12
|
StandardDetector,
|
|
14
13
|
set_and_wait_for_value,
|
|
15
14
|
soft_signal_r_and_setter,
|
|
16
15
|
)
|
|
17
|
-
from ophyd_async.epics.
|
|
18
|
-
from ophyd_async.epics.
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
from ophyd_async.epics.adcore import ADHDFWriter, NDFileHDFIO, stop_busy_record
|
|
17
|
+
from ophyd_async.epics.signal import (
|
|
18
|
+
epics_signal_r,
|
|
19
|
+
epics_signal_rw,
|
|
20
|
+
epics_signal_rw_rbv,
|
|
21
|
+
)
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class TetrammRange(str, Enum):
|
|
@@ -108,14 +109,14 @@ class TetrammController(DetectorControl):
|
|
|
108
109
|
self.minimum_values_per_reading = minimum_values_per_reading
|
|
109
110
|
self.readings_per_frame = readings_per_frame
|
|
110
111
|
|
|
111
|
-
def get_deadtime(self, exposure: float) -> float:
|
|
112
|
+
def get_deadtime(self, exposure: float | None) -> float:
|
|
112
113
|
# 2 internal clock cycles. Best effort approximation
|
|
113
114
|
return 2 / self.base_sample_rate
|
|
114
115
|
|
|
115
116
|
async def arm(
|
|
116
117
|
self,
|
|
117
118
|
num: int,
|
|
118
|
-
trigger: DetectorTrigger,
|
|
119
|
+
trigger: DetectorTrigger = DetectorTrigger.edge_trigger,
|
|
119
120
|
exposure: float | None = None,
|
|
120
121
|
) -> AsyncStatus:
|
|
121
122
|
if exposure is None:
|
|
@@ -132,7 +133,7 @@ class TetrammController(DetectorControl):
|
|
|
132
133
|
self._drv.averaging_time.set(exposure), self.set_exposure(exposure)
|
|
133
134
|
)
|
|
134
135
|
|
|
135
|
-
status = await set_and_wait_for_value(self._drv.acquire,
|
|
136
|
+
status = await set_and_wait_for_value(self._drv.acquire, True)
|
|
136
137
|
|
|
137
138
|
return status
|
|
138
139
|
|
|
@@ -150,7 +151,7 @@ class TetrammController(DetectorControl):
|
|
|
150
151
|
)
|
|
151
152
|
|
|
152
153
|
async def disarm(self):
|
|
153
|
-
await stop_busy_record(self._drv.acquire,
|
|
154
|
+
await stop_busy_record(self._drv.acquire, False, timeout=1)
|
|
154
155
|
|
|
155
156
|
async def set_exposure(self, exposure: float):
|
|
156
157
|
"""Tries to set the exposure time of a single frame.
|
|
@@ -204,14 +205,17 @@ class TetrammController(DetectorControl):
|
|
|
204
205
|
)
|
|
205
206
|
|
|
206
207
|
|
|
207
|
-
class
|
|
208
|
+
class TetrammDatasetDescriber(DatasetDescriber):
|
|
208
209
|
max_channels = 11
|
|
209
210
|
|
|
210
211
|
def __init__(self, controller: TetrammController) -> None:
|
|
211
212
|
self.controller = controller
|
|
212
213
|
|
|
213
|
-
async def
|
|
214
|
-
return
|
|
214
|
+
async def np_datatype(self) -> str:
|
|
215
|
+
return "<f8" # IEEE 754 double precision floating point
|
|
216
|
+
|
|
217
|
+
async def shape(self) -> tuple[int, int]:
|
|
218
|
+
return (self.max_channels, self.controller.readings_per_frame)
|
|
215
219
|
|
|
216
220
|
|
|
217
221
|
# TODO: Support MeanValue signals https://github.com/DiamondLightSource/dodal/issues/337
|
|
@@ -219,13 +223,13 @@ class TetrammDetector(StandardDetector):
|
|
|
219
223
|
def __init__(
|
|
220
224
|
self,
|
|
221
225
|
prefix: str,
|
|
222
|
-
|
|
226
|
+
path_provider: PathProvider,
|
|
223
227
|
name: str,
|
|
224
228
|
type: str | None = None,
|
|
225
229
|
**scalar_sigs: str,
|
|
226
230
|
) -> None:
|
|
227
231
|
self.drv = TetrammDriver(prefix + "DRV:")
|
|
228
|
-
self.hdf =
|
|
232
|
+
self.hdf = NDFileHDFIO(prefix + "HDF5:")
|
|
229
233
|
controller = TetrammController(self.drv)
|
|
230
234
|
config_signals = [
|
|
231
235
|
self.drv.values_per_reading,
|
|
@@ -239,11 +243,11 @@ class TetrammDetector(StandardDetector):
|
|
|
239
243
|
self.type = None
|
|
240
244
|
super().__init__(
|
|
241
245
|
controller,
|
|
242
|
-
|
|
246
|
+
ADHDFWriter(
|
|
243
247
|
self.hdf,
|
|
244
|
-
|
|
248
|
+
path_provider,
|
|
245
249
|
lambda: self.name,
|
|
246
|
-
|
|
250
|
+
TetrammDatasetDescriber(controller),
|
|
247
251
|
**scalar_sigs,
|
|
248
252
|
),
|
|
249
253
|
config_signals,
|
dodal/devices/thawer.py
CHANGED
|
@@ -15,7 +15,7 @@ class ThawerStates(str, Enum):
|
|
|
15
15
|
ON = "On"
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
class ThawingTimer(Device):
|
|
18
|
+
class ThawingTimer(Device, Stoppable):
|
|
19
19
|
def __init__(self, control_signal: SignalRW[ThawerStates]) -> None:
|
|
20
20
|
self._control_signal = control_signal
|
|
21
21
|
self._thawing_task: Task | None = None
|
|
@@ -32,7 +32,8 @@ class ThawingTimer(Device):
|
|
|
32
32
|
finally:
|
|
33
33
|
await self._control_signal.set(ThawerStates.OFF)
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
@AsyncStatus.wrap
|
|
36
|
+
async def stop(self, *args, **kwargs):
|
|
36
37
|
if self._thawing_task:
|
|
37
38
|
self._thawing_task.cancel()
|
|
38
39
|
|
|
@@ -43,6 +44,7 @@ class Thawer(StandardReadable, Stoppable):
|
|
|
43
44
|
self.thaw_for_time_s = ThawingTimer(self.control)
|
|
44
45
|
super().__init__(name)
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
@AsyncStatus.wrap
|
|
48
|
+
async def stop(self, *args, **kwargs):
|
|
47
49
|
await self.thaw_for_time_s.stop()
|
|
48
50
|
await self.control.set(ThawerStates.OFF)
|
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from ophyd_async.core import StandardReadable
|
|
2
|
+
from ophyd_async.epics.motor import Motor
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TrainingRigSampleStage(StandardReadable):
|
|
6
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
7
|
+
with self.add_children_as_readables():
|
|
8
|
+
self.x = Motor(prefix + "X")
|
|
9
|
+
self.theta = Motor(prefix + "A")
|
|
10
|
+
super().__init__(name)
|
dodal/devices/turbo_slit.py
CHANGED
dodal/devices/undulator.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
2
|
|
|
3
3
|
from ophyd_async.core import ConfigSignal, StandardReadable, soft_signal_r_and_setter
|
|
4
|
-
from ophyd_async.epics.
|
|
4
|
+
from ophyd_async.epics.motor import Motor
|
|
5
5
|
from ophyd_async.epics.signal import epics_signal_r
|
|
6
6
|
|
|
7
7
|
# The acceptable difference, in mm, between the undulator gap and the DCM
|
dodal/devices/undulator_dcm.py
CHANGED
|
@@ -74,14 +74,12 @@ class UndulatorDCM(StandardReadable, Movable):
|
|
|
74
74
|
daq_configuration_path + "/domain/beamlineParameters"
|
|
75
75
|
)["DCM_Perp_Offset_FIXED"]
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return AsyncStatus(_set())
|
|
77
|
+
@AsyncStatus.wrap
|
|
78
|
+
async def set(self, value: float):
|
|
79
|
+
await asyncio.gather(
|
|
80
|
+
self._set_dcm_energy(value),
|
|
81
|
+
self._set_undulator_gap_if_required(value),
|
|
82
|
+
)
|
|
85
83
|
|
|
86
84
|
async def _set_dcm_energy(self, energy_kev: float) -> None:
|
|
87
85
|
access_level = await self.undulator.gap_access.get_value()
|
|
@@ -3,12 +3,12 @@ All the methods in this module return a bluesky plan generator that adjusts a va
|
|
|
3
3
|
according to some criteria either via feedback, preset positions, lookup tables etc.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from
|
|
6
|
+
from collections.abc import Callable, Generator
|
|
7
7
|
|
|
8
8
|
from bluesky import plan_stubs as bps
|
|
9
|
-
from bluesky.
|
|
9
|
+
from bluesky.utils import Msg
|
|
10
10
|
from ophyd.epics_motor import EpicsMotor
|
|
11
|
-
from ophyd_async.epics.
|
|
11
|
+
from ophyd_async.epics.motor import Motor
|
|
12
12
|
|
|
13
13
|
from dodal.log import LOGGER
|
|
14
14
|
|
dodal/devices/util/epics_util.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
from collections.abc import Callable, Sequence
|
|
1
2
|
from functools import partial
|
|
2
|
-
from typing import Callable, Sequence
|
|
3
3
|
|
|
4
4
|
from bluesky.protocols import Movable
|
|
5
5
|
from ophyd import Component, EpicsSignal
|
|
@@ -61,9 +61,9 @@ def run_functions_without_blocking(
|
|
|
61
61
|
# Wrap each function by first checking the previous status and attaching a callback
|
|
62
62
|
# to the next function in the chain
|
|
63
63
|
def wrap_func(
|
|
64
|
-
old_status: Status, current_func: Callable[[], StatusBase], next_func
|
|
64
|
+
old_status: Status | None, current_func: Callable[[], StatusBase], next_func
|
|
65
65
|
):
|
|
66
|
-
if old_status.exception() is not None:
|
|
66
|
+
if old_status is not None and old_status.exception() is not None:
|
|
67
67
|
set_global_exception_and_log(old_status)
|
|
68
68
|
return
|
|
69
69
|
|
|
@@ -96,7 +96,7 @@ def run_functions_without_blocking(
|
|
|
96
96
|
)
|
|
97
97
|
|
|
98
98
|
# Wrap each function in reverse
|
|
99
|
-
for
|
|
99
|
+
for func in list(reversed(functions_to_chain))[1:-1]:
|
|
100
100
|
wrapped_funcs.append(
|
|
101
101
|
partial(
|
|
102
102
|
wrap_func,
|
|
@@ -105,10 +105,8 @@ def run_functions_without_blocking(
|
|
|
105
105
|
)
|
|
106
106
|
)
|
|
107
107
|
|
|
108
|
-
starting_status = Status(done=True, success=True)
|
|
109
|
-
|
|
110
108
|
# Initiate the chain of functions
|
|
111
|
-
wrap_func(
|
|
109
|
+
wrap_func(None, functions_to_chain[0], wrapped_funcs[-1])
|
|
112
110
|
return full_status
|
|
113
111
|
|
|
114
112
|
|
|
@@ -3,9 +3,8 @@ All the public methods in this module return a lookup table of some kind that
|
|
|
3
3
|
converts the source value s to a target value t for different values of s.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from collections.abc import Sequence
|
|
6
|
+
from collections.abc import Callable, Sequence
|
|
7
7
|
from io import StringIO
|
|
8
|
-
from typing import Callable
|
|
9
8
|
|
|
10
9
|
import aiofiles
|
|
11
10
|
import numpy as np
|
|
@@ -36,7 +35,7 @@ async def energy_distance_table(lookup_table_path: str) -> np.ndarray:
|
|
|
36
35
|
def linear_interpolation_lut(filename: str) -> Callable[[float], float]:
|
|
37
36
|
"""Returns a callable that converts values by linear interpolation of lookup table values"""
|
|
38
37
|
LOGGER.info(f"Using lookup table {filename}")
|
|
39
|
-
s_and_t_vals = zip(*loadtxt(filename, comments=["#", "Units"]))
|
|
38
|
+
s_and_t_vals = zip(*loadtxt(filename, comments=["#", "Units"]), strict=False)
|
|
40
39
|
|
|
41
40
|
s_values: Sequence
|
|
42
41
|
t_values: Sequence
|