ophyd-async 0.9.0a2__py3-none-any.whl → 0.10.0a2__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.
- ophyd_async/__init__.py +5 -8
- ophyd_async/_docs_parser.py +12 -0
- ophyd_async/_version.py +9 -4
- ophyd_async/core/__init__.py +97 -62
- ophyd_async/core/_derived_signal.py +271 -0
- ophyd_async/core/_derived_signal_backend.py +300 -0
- ophyd_async/core/_detector.py +106 -125
- ophyd_async/core/_device.py +69 -63
- ophyd_async/core/_device_filler.py +65 -1
- ophyd_async/core/_flyer.py +14 -5
- ophyd_async/core/_hdf_dataset.py +29 -22
- ophyd_async/core/_log.py +14 -23
- ophyd_async/core/_mock_signal_backend.py +11 -3
- ophyd_async/core/_protocol.py +65 -45
- ophyd_async/core/_providers.py +28 -9
- ophyd_async/core/_readable.py +44 -35
- ophyd_async/core/_settings.py +36 -27
- ophyd_async/core/_signal.py +262 -170
- ophyd_async/core/_signal_backend.py +56 -13
- ophyd_async/core/_soft_signal_backend.py +16 -11
- ophyd_async/core/_status.py +72 -24
- ophyd_async/core/_table.py +41 -11
- ophyd_async/core/_utils.py +96 -49
- ophyd_async/core/_yaml_settings.py +2 -0
- ophyd_async/epics/__init__.py +1 -0
- ophyd_async/epics/adandor/_andor.py +2 -2
- ophyd_async/epics/adandor/_andor_controller.py +4 -2
- ophyd_async/epics/adandor/_andor_io.py +2 -4
- ophyd_async/epics/adaravis/__init__.py +5 -0
- ophyd_async/epics/adaravis/_aravis.py +4 -8
- ophyd_async/epics/adaravis/_aravis_controller.py +20 -43
- ophyd_async/epics/adaravis/_aravis_io.py +13 -28
- ophyd_async/epics/adcore/__init__.py +23 -8
- ophyd_async/epics/adcore/_core_detector.py +42 -2
- ophyd_async/epics/adcore/_core_io.py +124 -99
- ophyd_async/epics/adcore/_core_logic.py +106 -27
- ophyd_async/epics/adcore/_core_writer.py +12 -8
- ophyd_async/epics/adcore/_hdf_writer.py +21 -38
- ophyd_async/epics/adcore/_single_trigger.py +2 -2
- ophyd_async/epics/adcore/_utils.py +2 -2
- ophyd_async/epics/adkinetix/__init__.py +2 -1
- ophyd_async/epics/adkinetix/_kinetix.py +3 -3
- ophyd_async/epics/adkinetix/_kinetix_controller.py +4 -2
- ophyd_async/epics/adkinetix/_kinetix_io.py +12 -13
- ophyd_async/epics/adpilatus/__init__.py +5 -0
- ophyd_async/epics/adpilatus/_pilatus.py +1 -1
- ophyd_async/epics/adpilatus/_pilatus_controller.py +5 -24
- ophyd_async/epics/adpilatus/_pilatus_io.py +11 -9
- ophyd_async/epics/adsimdetector/__init__.py +8 -1
- ophyd_async/epics/adsimdetector/_sim.py +4 -14
- ophyd_async/epics/adsimdetector/_sim_controller.py +17 -0
- ophyd_async/epics/adsimdetector/_sim_io.py +10 -0
- ophyd_async/epics/advimba/__init__.py +10 -1
- ophyd_async/epics/advimba/_vimba.py +3 -2
- ophyd_async/epics/advimba/_vimba_controller.py +4 -2
- ophyd_async/epics/advimba/_vimba_io.py +23 -28
- ophyd_async/epics/core/_aioca.py +35 -16
- ophyd_async/epics/core/_epics_connector.py +4 -0
- ophyd_async/epics/core/_epics_device.py +2 -0
- ophyd_async/epics/core/_p4p.py +10 -2
- ophyd_async/epics/core/_pvi_connector.py +65 -8
- ophyd_async/epics/core/_signal.py +51 -51
- ophyd_async/epics/core/_util.py +4 -4
- ophyd_async/epics/demo/__init__.py +16 -0
- ophyd_async/epics/demo/__main__.py +31 -0
- ophyd_async/epics/demo/_ioc.py +32 -0
- ophyd_async/epics/demo/_motor.py +82 -0
- ophyd_async/epics/demo/_point_detector.py +42 -0
- ophyd_async/epics/demo/_point_detector_channel.py +22 -0
- ophyd_async/epics/demo/_stage.py +15 -0
- ophyd_async/epics/{sim/mover.db → demo/motor.db} +2 -1
- ophyd_async/epics/demo/point_detector.db +59 -0
- ophyd_async/epics/demo/point_detector_channel.db +21 -0
- ophyd_async/epics/eiger/_eiger.py +1 -3
- ophyd_async/epics/eiger/_eiger_controller.py +11 -4
- ophyd_async/epics/eiger/_eiger_io.py +2 -0
- ophyd_async/epics/eiger/_odin_io.py +1 -2
- ophyd_async/epics/motor.py +65 -28
- ophyd_async/epics/signal.py +4 -1
- ophyd_async/epics/testing/_example_ioc.py +21 -9
- ophyd_async/epics/testing/_utils.py +3 -0
- ophyd_async/epics/testing/test_records.db +8 -0
- ophyd_async/epics/testing/test_records_pva.db +17 -16
- ophyd_async/fastcs/__init__.py +1 -0
- ophyd_async/fastcs/core.py +6 -0
- ophyd_async/fastcs/odin/__init__.py +1 -0
- ophyd_async/fastcs/panda/__init__.py +8 -6
- ophyd_async/fastcs/panda/_block.py +29 -9
- ophyd_async/fastcs/panda/_control.py +5 -0
- ophyd_async/fastcs/panda/_hdf_panda.py +2 -0
- ophyd_async/fastcs/panda/_table.py +9 -6
- ophyd_async/fastcs/panda/_trigger.py +23 -9
- ophyd_async/fastcs/panda/_writer.py +27 -30
- ophyd_async/plan_stubs/__init__.py +2 -0
- ophyd_async/plan_stubs/_ensure_connected.py +1 -0
- ophyd_async/plan_stubs/_fly.py +2 -4
- ophyd_async/plan_stubs/_nd_attributes.py +2 -0
- ophyd_async/plan_stubs/_panda.py +1 -0
- ophyd_async/plan_stubs/_settings.py +43 -16
- ophyd_async/plan_stubs/_utils.py +3 -0
- ophyd_async/plan_stubs/_wait_for_awaitable.py +1 -1
- ophyd_async/sim/__init__.py +24 -14
- ophyd_async/sim/__main__.py +43 -0
- ophyd_async/sim/_blob_detector.py +33 -0
- ophyd_async/sim/_blob_detector_controller.py +48 -0
- ophyd_async/sim/_blob_detector_writer.py +105 -0
- ophyd_async/sim/_mirror_horizontal.py +46 -0
- ophyd_async/sim/_mirror_vertical.py +74 -0
- ophyd_async/sim/_motor.py +233 -0
- ophyd_async/sim/_pattern_generator.py +124 -0
- ophyd_async/sim/_point_detector.py +86 -0
- ophyd_async/sim/_stage.py +19 -0
- ophyd_async/tango/__init__.py +1 -0
- ophyd_async/tango/core/__init__.py +6 -1
- ophyd_async/tango/core/_base_device.py +41 -33
- ophyd_async/tango/core/_converters.py +81 -0
- ophyd_async/tango/core/_signal.py +18 -32
- ophyd_async/tango/core/_tango_readable.py +2 -19
- ophyd_async/tango/core/_tango_transport.py +136 -60
- ophyd_async/tango/core/_utils.py +47 -0
- ophyd_async/tango/{sim → demo}/_counter.py +2 -0
- ophyd_async/tango/{sim → demo}/_detector.py +2 -0
- ophyd_async/tango/{sim → demo}/_mover.py +5 -4
- ophyd_async/tango/{sim → demo}/_tango/_servers.py +4 -0
- ophyd_async/tango/testing/__init__.py +6 -0
- ophyd_async/tango/testing/_one_of_everything.py +200 -0
- ophyd_async/testing/__init__.py +29 -7
- ophyd_async/testing/_assert.py +145 -83
- ophyd_async/testing/_mock_signal_utils.py +56 -70
- ophyd_async/testing/_one_of_everything.py +41 -21
- ophyd_async/testing/_single_derived.py +89 -0
- ophyd_async/testing/_utils.py +3 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a2.dist-info}/METADATA +25 -26
- ophyd_async-0.10.0a2.dist-info/RECORD +149 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a2.dist-info}/WHEEL +1 -1
- ophyd_async/epics/sim/__init__.py +0 -54
- ophyd_async/epics/sim/_ioc.py +0 -29
- ophyd_async/epics/sim/_mover.py +0 -101
- ophyd_async/epics/sim/_sensor.py +0 -37
- ophyd_async/epics/sim/sensor.db +0 -19
- ophyd_async/sim/_pattern_detector/__init__.py +0 -13
- ophyd_async/sim/_pattern_detector/_pattern_detector.py +0 -42
- ophyd_async/sim/_pattern_detector/_pattern_detector_controller.py +0 -69
- ophyd_async/sim/_pattern_detector/_pattern_detector_writer.py +0 -41
- ophyd_async/sim/_pattern_detector/_pattern_generator.py +0 -214
- ophyd_async/sim/_sim_motor.py +0 -107
- ophyd_async-0.9.0a2.dist-info/RECORD +0 -129
- /ophyd_async/tango/{sim → demo}/__init__.py +0 -0
- /ophyd_async/tango/{sim → demo}/_tango/__init__.py +0 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a2.dist-info/licenses}/LICENSE +0 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a2.dist-info}/top_level.txt +0 -0
ophyd_async/core/_utils.py
CHANGED
|
@@ -11,10 +11,10 @@ from unittest.mock import Mock
|
|
|
11
11
|
import numpy as np
|
|
12
12
|
|
|
13
13
|
T = TypeVar("T")
|
|
14
|
+
V = TypeVar("V")
|
|
14
15
|
P = ParamSpec("P")
|
|
15
16
|
Callback = Callable[[T], None]
|
|
16
17
|
DEFAULT_TIMEOUT = 10.0
|
|
17
|
-
ErrorText = str | Mapping[str, Exception]
|
|
18
18
|
|
|
19
19
|
logger = logging.getLogger("ophyd_async")
|
|
20
20
|
|
|
@@ -29,18 +29,33 @@ class StrictEnumMeta(EnumMeta):
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
class StrictEnum(str, Enum, metaclass=StrictEnumMeta):
|
|
32
|
-
"""All members should exist in the Backend, and there will be no extras"""
|
|
32
|
+
"""All members should exist in the Backend, and there will be no extras."""
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
class SubsetEnumMeta(StrictEnumMeta):
|
|
36
36
|
def __call__(self, value, *args, **kwargs): # type: ignore
|
|
37
|
+
"""Return given value if it is a string and not a member of the enum.
|
|
38
|
+
|
|
39
|
+
If the value is not a string or is an enum member, default enum behavior
|
|
40
|
+
is applied. Type checking will complain if provided arbitrary string.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Union[str, SubsetEnum]: If the value is a string and not a member of the
|
|
44
|
+
enum, the string is returned as is. Otherwise, the corresponding enum
|
|
45
|
+
member is returned.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
ValueError: If the value is not a string and cannot be converted to an enum
|
|
49
|
+
member.
|
|
50
|
+
|
|
51
|
+
"""
|
|
37
52
|
if isinstance(value, str) and not isinstance(value, self):
|
|
38
53
|
return value
|
|
39
54
|
return super().__call__(value, *args, **kwargs)
|
|
40
55
|
|
|
41
56
|
|
|
42
57
|
class SubsetEnum(StrictEnum, metaclass=SubsetEnumMeta):
|
|
43
|
-
"""All members should exist in the Backend, but there may be extras"""
|
|
58
|
+
"""All members should exist in the Backend, but there may be extras."""
|
|
44
59
|
|
|
45
60
|
|
|
46
61
|
CALCULATE_TIMEOUT = "CALCULATE_TIMEOUT"
|
|
@@ -55,22 +70,16 @@ CalculatableTimeout = float | None | Literal["CALCULATE_TIMEOUT"]
|
|
|
55
70
|
|
|
56
71
|
|
|
57
72
|
class NotConnected(Exception):
|
|
58
|
-
"""Exception to be raised if a `Device.connect` is cancelled
|
|
73
|
+
"""Exception to be raised if a `Device.connect` is cancelled.
|
|
59
74
|
|
|
60
|
-
|
|
75
|
+
:param errors:
|
|
76
|
+
Mapping of device name to Exception or another NotConnected.
|
|
77
|
+
Alternatively a string with the signal error text.
|
|
78
|
+
"""
|
|
61
79
|
|
|
62
|
-
|
|
63
|
-
"""
|
|
64
|
-
NotConnected holds a mapping of device/signal names to
|
|
65
|
-
errors.
|
|
66
|
-
|
|
67
|
-
Parameters
|
|
68
|
-
----------
|
|
69
|
-
errors: ErrorText
|
|
70
|
-
Mapping of device name to Exception or another NotConnected.
|
|
71
|
-
Alternatively a string with the signal error text.
|
|
72
|
-
"""
|
|
80
|
+
_indent_width = " "
|
|
73
81
|
|
|
82
|
+
def __init__(self, errors: str | Mapping[str, Exception]):
|
|
74
83
|
self._errors = errors
|
|
75
84
|
|
|
76
85
|
@property
|
|
@@ -126,21 +135,38 @@ class NotConnected(Exception):
|
|
|
126
135
|
|
|
127
136
|
@dataclass(frozen=True)
|
|
128
137
|
class WatcherUpdate(Generic[T]):
|
|
129
|
-
"""A dataclass such that, when expanded, it provides the kwargs for a watcher"""
|
|
138
|
+
"""A dataclass such that, when expanded, it provides the kwargs for a watcher."""
|
|
130
139
|
|
|
131
140
|
current: T
|
|
141
|
+
"""The current value, where it currently is."""
|
|
142
|
+
|
|
132
143
|
initial: T
|
|
144
|
+
"""The initial value, where it was when it started."""
|
|
145
|
+
|
|
133
146
|
target: T
|
|
147
|
+
"""The target value, where it will be when it finishes."""
|
|
148
|
+
|
|
134
149
|
name: str | None = None
|
|
150
|
+
"""An optional name for the device, if available."""
|
|
151
|
+
|
|
135
152
|
unit: str | None = None
|
|
153
|
+
"""Units of the value, if applicable."""
|
|
154
|
+
|
|
136
155
|
precision: float | None = None
|
|
156
|
+
"""How many decimal places the value should be displayed to."""
|
|
157
|
+
|
|
137
158
|
fraction: float | None = None
|
|
159
|
+
"""The fraction of the way between initial and target."""
|
|
160
|
+
|
|
138
161
|
time_elapsed: float | None = None
|
|
162
|
+
"""The time elapsed since the start of the operation."""
|
|
163
|
+
|
|
139
164
|
time_remaining: float | None = None
|
|
165
|
+
"""The time remaining until the operation completes."""
|
|
140
166
|
|
|
141
167
|
|
|
142
168
|
async def wait_for_connection(**coros: Awaitable[None]):
|
|
143
|
-
"""Call many underlying signals, accumulating exceptions and returning them
|
|
169
|
+
"""Call many underlying signals, accumulating exceptions and returning them.
|
|
144
170
|
|
|
145
171
|
Expected kwargs should be a mapping of names to coroutine tasks to execute.
|
|
146
172
|
"""
|
|
@@ -164,12 +190,15 @@ async def wait_for_connection(**coros: Awaitable[None]):
|
|
|
164
190
|
|
|
165
191
|
|
|
166
192
|
def get_dtype(datatype: type) -> np.dtype:
|
|
167
|
-
"""Get the runtime dtype from a numpy ndarray type annotation
|
|
193
|
+
"""Get the runtime dtype from a numpy ndarray type annotation.
|
|
168
194
|
|
|
195
|
+
```python
|
|
169
196
|
>>> from ophyd_async.core import Array1D
|
|
170
197
|
>>> import numpy as np
|
|
171
198
|
>>> get_dtype(Array1D[np.int8])
|
|
172
199
|
dtype('int8')
|
|
200
|
+
|
|
201
|
+
```
|
|
173
202
|
"""
|
|
174
203
|
if not get_origin(datatype) == np.ndarray:
|
|
175
204
|
raise TypeError(f"Expected Array1D[dtype], got {datatype}")
|
|
@@ -179,12 +208,21 @@ def get_dtype(datatype: type) -> np.dtype:
|
|
|
179
208
|
|
|
180
209
|
|
|
181
210
|
def get_enum_cls(datatype: type | None) -> type[StrictEnum] | None:
|
|
182
|
-
"""Get the
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
>>>
|
|
187
|
-
|
|
211
|
+
"""Get the enum class from a datatype.
|
|
212
|
+
|
|
213
|
+
:raises TypeError: if type is not a [](#StrictEnum) or [](#SubsetEnum) subclass
|
|
214
|
+
```python
|
|
215
|
+
>>> from ophyd_async.core import StrictEnum
|
|
216
|
+
>>> from collections.abc import Sequence
|
|
217
|
+
>>> class MyEnum(StrictEnum):
|
|
218
|
+
... A = "A value"
|
|
219
|
+
>>> get_enum_cls(str)
|
|
220
|
+
>>> get_enum_cls(MyEnum)
|
|
221
|
+
<enum 'MyEnum'>
|
|
222
|
+
>>> get_enum_cls(Sequence[MyEnum])
|
|
223
|
+
<enum 'MyEnum'>
|
|
224
|
+
|
|
225
|
+
```
|
|
188
226
|
"""
|
|
189
227
|
if get_origin(datatype) is Sequence:
|
|
190
228
|
datatype = get_args(datatype)[0]
|
|
@@ -195,17 +233,21 @@ def get_enum_cls(datatype: type | None) -> type[StrictEnum] | None:
|
|
|
195
233
|
"or ophyd_async.core.StrictEnum"
|
|
196
234
|
)
|
|
197
235
|
return datatype
|
|
236
|
+
return None
|
|
198
237
|
|
|
199
238
|
|
|
200
239
|
def get_unique(values: dict[str, T], types: str) -> T:
|
|
201
|
-
"""If all values are the same, return that value, otherwise raise TypeError
|
|
240
|
+
"""If all values are the same, return that value, otherwise raise TypeError.
|
|
202
241
|
|
|
242
|
+
```python
|
|
203
243
|
>>> get_unique({"a": 1, "b": 1}, "integers")
|
|
204
244
|
1
|
|
205
245
|
>>> get_unique({"a": 1, "b": 2}, "integers")
|
|
206
246
|
Traceback (most recent call last):
|
|
207
247
|
...
|
|
208
248
|
TypeError: Differing integers: a has 1, b has 2
|
|
249
|
+
|
|
250
|
+
```
|
|
209
251
|
"""
|
|
210
252
|
set_values = set(values.values())
|
|
211
253
|
if len(set_values) != 1:
|
|
@@ -219,9 +261,12 @@ async def merge_gathered_dicts(
|
|
|
219
261
|
) -> dict[str, T]:
|
|
220
262
|
"""Merge dictionaries produced by a sequence of coroutines.
|
|
221
263
|
|
|
222
|
-
Can be used for merging
|
|
264
|
+
Can be used for merging `read()` or `describe()`.
|
|
223
265
|
|
|
224
|
-
|
|
266
|
+
:example:
|
|
267
|
+
```python
|
|
268
|
+
combined_read = await merge_gathered_dicts(s.read() for s in signals)
|
|
269
|
+
```
|
|
225
270
|
"""
|
|
226
271
|
ret: dict[str, T] = {}
|
|
227
272
|
for result in await asyncio.gather(*coros):
|
|
@@ -229,21 +274,18 @@ async def merge_gathered_dicts(
|
|
|
229
274
|
return ret
|
|
230
275
|
|
|
231
276
|
|
|
232
|
-
async def
|
|
233
|
-
return
|
|
277
|
+
async def gather_dict(coros: Mapping[T, Awaitable[V]]) -> dict[T, V]:
|
|
278
|
+
"""Take named coros and return a dict of their name to their return value."""
|
|
279
|
+
values = await asyncio.gather(*coros.values())
|
|
280
|
+
return dict(zip(coros, values, strict=True))
|
|
234
281
|
|
|
235
282
|
|
|
236
283
|
def in_micros(t: float) -> int:
|
|
237
|
-
"""
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
t (float): A time in seconds
|
|
243
|
-
Raises:
|
|
244
|
-
ValueError: if t < 0
|
|
245
|
-
Returns:
|
|
246
|
-
t (int): A time in microseconds, rounded up to the nearest whole microsecond,
|
|
284
|
+
"""Convert between a seconds and microseconds.
|
|
285
|
+
|
|
286
|
+
:param t: A time in seconds
|
|
287
|
+
:return: A time in microseconds, rounded up to the nearest whole microsecond
|
|
288
|
+
:raises ValueError: if t < 0
|
|
247
289
|
"""
|
|
248
290
|
if t < 0:
|
|
249
291
|
raise ValueError(f"Expected a positive time in seconds, got {t!r}")
|
|
@@ -254,6 +296,7 @@ def get_origin_class(annotatation: Any) -> type | None:
|
|
|
254
296
|
origin = get_origin(annotatation) or annotatation
|
|
255
297
|
if isinstance(origin, type):
|
|
256
298
|
return origin
|
|
299
|
+
return None
|
|
257
300
|
|
|
258
301
|
|
|
259
302
|
class Reference(Generic[T]):
|
|
@@ -261,16 +304,16 @@ class Reference(Generic[T]):
|
|
|
261
304
|
|
|
262
305
|
Used to opt out of the naming/parent-child relationship of `Device`.
|
|
263
306
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def set(self, value) -> AsyncStatus:
|
|
272
|
-
return self.signal_ref().set(value + 1)
|
|
307
|
+
:example:
|
|
308
|
+
```python
|
|
309
|
+
class DeviceWithRefToSignal(Device):
|
|
310
|
+
def __init__(self, signal: SignalRW[int]):
|
|
311
|
+
self.signal_ref = Reference(signal)
|
|
312
|
+
super().__init__()
|
|
273
313
|
|
|
314
|
+
def set(self, value) -> AsyncStatus:
|
|
315
|
+
return self.signal_ref().set(value + 1)
|
|
316
|
+
```
|
|
274
317
|
"""
|
|
275
318
|
|
|
276
319
|
def __init__(self, obj: T):
|
|
@@ -289,6 +332,7 @@ class LazyMock:
|
|
|
289
332
|
constructed so that when the leaf is created, so are its parents.
|
|
290
333
|
Any calls to the child are then accessible from the parent mock.
|
|
291
334
|
|
|
335
|
+
```python
|
|
292
336
|
>>> parent = LazyMock()
|
|
293
337
|
>>> child = parent.child("child")
|
|
294
338
|
>>> child_mock = child()
|
|
@@ -297,6 +341,8 @@ class LazyMock:
|
|
|
297
341
|
>>> parent_mock = parent()
|
|
298
342
|
>>> parent_mock.mock_calls
|
|
299
343
|
[call.child()]
|
|
344
|
+
|
|
345
|
+
```
|
|
300
346
|
"""
|
|
301
347
|
|
|
302
348
|
def __init__(self, name: str = "", parent: LazyMock | None = None) -> None:
|
|
@@ -305,6 +351,7 @@ class LazyMock:
|
|
|
305
351
|
self._mock: Mock | None = None
|
|
306
352
|
|
|
307
353
|
def child(self, name: str) -> LazyMock:
|
|
354
|
+
"""Return a child of this LazyMock with the given name."""
|
|
308
355
|
return LazyMock(name, self)
|
|
309
356
|
|
|
310
357
|
def __call__(self) -> Mock:
|
|
@@ -28,6 +28,8 @@ def enum_representer(dumper: yaml.Dumper, enum: Enum) -> yaml.Node:
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class YamlSettingsProvider(SettingsProvider):
|
|
31
|
+
"""For providing settings from yaml to signals."""
|
|
32
|
+
|
|
31
33
|
def __init__(self, directory: Path | str):
|
|
32
34
|
self._directory = Path(directory)
|
|
33
35
|
|
ophyd_async/epics/__init__.py
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""EPICS support for Signals, and Devices that use them."""
|
|
@@ -9,8 +9,8 @@ from ._andor_io import Andor2DriverIO
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class Andor2Detector(adcore.AreaDetector[Andor2Controller]):
|
|
12
|
-
"""
|
|
13
|
-
|
|
12
|
+
"""Andor 2 area detector device (CCD detector 56fps with full chip readout).
|
|
13
|
+
|
|
14
14
|
Andor model:DU897_BV.
|
|
15
15
|
"""
|
|
16
16
|
|
|
@@ -13,10 +13,12 @@ _MAX_NUM_IMAGE = 999_999
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class Andor2Controller(adcore.ADBaseController[Andor2DriverIO]):
|
|
16
|
+
"""For controlling the Andor 2 detector."""
|
|
17
|
+
|
|
16
18
|
def __init__(
|
|
17
19
|
self,
|
|
18
20
|
driver: Andor2DriverIO,
|
|
19
|
-
good_states: frozenset[adcore.
|
|
21
|
+
good_states: frozenset[adcore.ADState] = adcore.DEFAULT_GOOD_STATES,
|
|
20
22
|
) -> None:
|
|
21
23
|
super().__init__(driver, good_states=good_states)
|
|
22
24
|
|
|
@@ -32,7 +34,7 @@ class Andor2Controller(adcore.ADBaseController[Andor2DriverIO]):
|
|
|
32
34
|
self.driver.num_images.set(
|
|
33
35
|
trigger_info.total_number_of_triggers or _MAX_NUM_IMAGE
|
|
34
36
|
),
|
|
35
|
-
self.driver.image_mode.set(adcore.
|
|
37
|
+
self.driver.image_mode.set(adcore.ADImageMode.MULTIPLE),
|
|
36
38
|
)
|
|
37
39
|
|
|
38
40
|
def _get_trigger_mode(self, trigger: DetectorTrigger) -> Andor2TriggerMode:
|
|
@@ -23,14 +23,12 @@ class Andor2DataType(SubsetEnum):
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class Andor2DriverIO(ADBaseIO):
|
|
26
|
-
"""
|
|
27
|
-
Epics pv for andor model:DU897_BV as deployed on p99
|
|
28
|
-
"""
|
|
26
|
+
"""Epics pv for andor model:DU897_BV as deployed on p99."""
|
|
29
27
|
|
|
30
28
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
31
29
|
super().__init__(prefix, name=name)
|
|
32
30
|
self.trigger_mode = epics_signal_rw(Andor2TriggerMode, prefix + "TriggerMode")
|
|
33
|
-
self.data_type = epics_signal_r(Andor2DataType, prefix + "DataType_RBV")
|
|
31
|
+
self.data_type = epics_signal_r(Andor2DataType, prefix + "DataType_RBV") # type: ignore
|
|
34
32
|
self.andor_accumulate_period = epics_signal_r(
|
|
35
33
|
float, prefix + "AndorAccumulatePeriod_RBV"
|
|
36
34
|
)
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
"""Support for the ADAravis areaDetector driver.
|
|
2
|
+
|
|
3
|
+
https://github.com/areaDetector/ADAravis
|
|
4
|
+
"""
|
|
5
|
+
|
|
1
6
|
from ._aravis import AravisDetector
|
|
2
7
|
from ._aravis_controller import AravisController
|
|
3
8
|
from ._aravis_io import AravisDriverIO, AravisTriggerMode, AravisTriggerSource
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from collections.abc import Sequence
|
|
2
2
|
|
|
3
|
-
from ophyd_async.core import PathProvider
|
|
4
|
-
from ophyd_async.core._signal import SignalR
|
|
3
|
+
from ophyd_async.core import PathProvider, SignalR
|
|
5
4
|
from ophyd_async.epics import adcore
|
|
6
5
|
|
|
7
6
|
from ._aravis_controller import AravisController
|
|
@@ -9,8 +8,8 @@ from ._aravis_io import AravisDriverIO
|
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
class AravisDetector(adcore.AreaDetector[AravisController]):
|
|
12
|
-
"""
|
|
13
|
-
|
|
11
|
+
"""Implementation of an ADAravis Detector.
|
|
12
|
+
|
|
14
13
|
The detector may be configured for an external trigger on a GPIO port,
|
|
15
14
|
which must be done prior to preparing the detector
|
|
16
15
|
"""
|
|
@@ -23,13 +22,11 @@ class AravisDetector(adcore.AreaDetector[AravisController]):
|
|
|
23
22
|
writer_cls: type[adcore.ADWriter] = adcore.ADHDFWriter,
|
|
24
23
|
fileio_suffix: str | None = None,
|
|
25
24
|
name: str = "",
|
|
26
|
-
gpio_number: AravisController.GPIO_NUMBER = 1,
|
|
27
25
|
config_sigs: Sequence[SignalR] = (),
|
|
28
26
|
plugins: dict[str, adcore.NDPluginBaseIO] | None = None,
|
|
29
27
|
):
|
|
30
28
|
driver = AravisDriverIO(prefix + drv_suffix)
|
|
31
|
-
controller = AravisController(driver
|
|
32
|
-
|
|
29
|
+
controller = AravisController(driver)
|
|
33
30
|
writer = writer_cls.with_io(
|
|
34
31
|
prefix,
|
|
35
32
|
path_provider,
|
|
@@ -37,7 +34,6 @@ class AravisDetector(adcore.AreaDetector[AravisController]):
|
|
|
37
34
|
fileio_suffix=fileio_suffix,
|
|
38
35
|
plugins=plugins,
|
|
39
36
|
)
|
|
40
|
-
|
|
41
37
|
super().__init__(
|
|
42
38
|
controller=controller,
|
|
43
39
|
writer=writer,
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import Literal
|
|
3
2
|
|
|
4
|
-
from ophyd_async.core import
|
|
5
|
-
DetectorTrigger,
|
|
6
|
-
TriggerInfo,
|
|
7
|
-
)
|
|
3
|
+
from ophyd_async.core import DetectorTrigger, TriggerInfo
|
|
8
4
|
from ophyd_async.epics import adcore
|
|
9
5
|
|
|
10
6
|
from ._aravis_io import AravisDriverIO, AravisTriggerMode, AravisTriggerSource
|
|
@@ -16,53 +12,34 @@ _HIGHEST_POSSIBLE_DEADTIME = 1961e-6
|
|
|
16
12
|
|
|
17
13
|
|
|
18
14
|
class AravisController(adcore.ADBaseController[AravisDriverIO]):
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def __init__(
|
|
22
|
-
self,
|
|
23
|
-
driver: AravisDriverIO,
|
|
24
|
-
good_states: frozenset[adcore.DetectorState] = adcore.DEFAULT_GOOD_STATES,
|
|
25
|
-
gpio_number: GPIO_NUMBER = 1,
|
|
26
|
-
) -> None:
|
|
27
|
-
super().__init__(driver, good_states=good_states)
|
|
28
|
-
self.gpio_number = gpio_number
|
|
15
|
+
"""`DetectorController` for an `AravisDriverIO`."""
|
|
29
16
|
|
|
30
17
|
def get_deadtime(self, exposure: float | None) -> float:
|
|
31
18
|
return _HIGHEST_POSSIBLE_DEADTIME
|
|
32
19
|
|
|
33
|
-
async def prepare(self, trigger_info: TriggerInfo):
|
|
34
|
-
if trigger_info.total_number_of_triggers == 0:
|
|
35
|
-
image_mode = adcore.ImageMode.CONTINUOUS
|
|
36
|
-
else:
|
|
37
|
-
image_mode = adcore.ImageMode.MULTIPLE
|
|
20
|
+
async def prepare(self, trigger_info: TriggerInfo) -> None:
|
|
38
21
|
if (exposure := trigger_info.livetime) is not None:
|
|
39
22
|
await self.driver.acquire_time.set(exposure)
|
|
40
23
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
24
|
+
if trigger_info.trigger is DetectorTrigger.INTERNAL:
|
|
25
|
+
# Set trigger mode off to ignore the trigger source
|
|
26
|
+
await self.driver.trigger_mode.set(AravisTriggerMode.OFF)
|
|
27
|
+
elif trigger_info.trigger in {
|
|
28
|
+
DetectorTrigger.CONSTANT_GATE,
|
|
29
|
+
DetectorTrigger.EDGE_TRIGGER,
|
|
30
|
+
}:
|
|
31
|
+
# Trigger on the rising edge of Line1
|
|
32
|
+
# trigger mode must be set first and on it's own!
|
|
33
|
+
await self.driver.trigger_mode.set(AravisTriggerMode.ON)
|
|
34
|
+
await self.driver.trigger_source.set(AravisTriggerSource.LINE1)
|
|
35
|
+
else:
|
|
36
|
+
raise ValueError(f"ADAravis does not support {trigger_info.trigger}")
|
|
44
37
|
|
|
38
|
+
if trigger_info.total_number_of_triggers == 0:
|
|
39
|
+
image_mode = adcore.ADImageMode.CONTINUOUS
|
|
40
|
+
else:
|
|
41
|
+
image_mode = adcore.ADImageMode.MULTIPLE
|
|
45
42
|
await asyncio.gather(
|
|
46
|
-
self.driver.trigger_source.set(trigger_source),
|
|
47
43
|
self.driver.num_images.set(trigger_info.total_number_of_triggers),
|
|
48
44
|
self.driver.image_mode.set(image_mode),
|
|
49
45
|
)
|
|
50
|
-
|
|
51
|
-
def _get_trigger_info(
|
|
52
|
-
self, trigger: DetectorTrigger
|
|
53
|
-
) -> tuple[AravisTriggerMode, AravisTriggerSource]:
|
|
54
|
-
supported_trigger_types = (
|
|
55
|
-
DetectorTrigger.CONSTANT_GATE,
|
|
56
|
-
DetectorTrigger.EDGE_TRIGGER,
|
|
57
|
-
DetectorTrigger.INTERNAL,
|
|
58
|
-
)
|
|
59
|
-
if trigger not in supported_trigger_types:
|
|
60
|
-
raise ValueError(
|
|
61
|
-
f"{self.__class__.__name__} only supports the following trigger "
|
|
62
|
-
f"types: {supported_trigger_types} but was asked to "
|
|
63
|
-
f"use {trigger}"
|
|
64
|
-
)
|
|
65
|
-
if trigger == DetectorTrigger.INTERNAL:
|
|
66
|
-
return AravisTriggerMode.OFF, AravisTriggerSource.FREERUN
|
|
67
|
-
else:
|
|
68
|
-
return (AravisTriggerMode.ON, f"Line{self.gpio_number}") # type: ignore
|
|
@@ -1,46 +1,31 @@
|
|
|
1
|
-
from
|
|
1
|
+
from typing import Annotated as A
|
|
2
|
+
|
|
3
|
+
from ophyd_async.core import SignalRW, StrictEnum, SubsetEnum
|
|
2
4
|
from ophyd_async.epics import adcore
|
|
3
|
-
from ophyd_async.epics.core import
|
|
5
|
+
from ophyd_async.epics.core import PvSuffix
|
|
4
6
|
|
|
5
7
|
|
|
6
8
|
class AravisTriggerMode(StrictEnum):
|
|
7
|
-
"""GigEVision GenICAM standard
|
|
9
|
+
"""GigEVision GenICAM standard TriggerMode."""
|
|
8
10
|
|
|
9
11
|
ON = "On"
|
|
10
|
-
|
|
12
|
+
"""Use TriggerSource to trigger each frame"""
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
"""
|
|
14
|
-
To enable hardware triggered scanning, line_N must support each N in GPIO_NUMBER.
|
|
15
|
-
To enable software triggered scanning, freerun must be supported.
|
|
16
|
-
Other enumerated values may or may not be preset.
|
|
17
|
-
To prevent requiring one Enum class per possible configuration, we set as this Enum
|
|
18
|
-
but read from the underlying signal as a str.
|
|
19
|
-
"""
|
|
14
|
+
OFF = "Off"
|
|
15
|
+
"""Just trigger as fast as you can"""
|
|
20
16
|
|
|
21
17
|
|
|
22
18
|
class AravisTriggerSource(SubsetEnum):
|
|
23
|
-
|
|
19
|
+
"""Which trigger source to use when TriggerMode=On."""
|
|
20
|
+
|
|
24
21
|
LINE1 = "Line1"
|
|
25
22
|
|
|
26
23
|
|
|
27
24
|
class AravisDriverIO(adcore.ADBaseIO):
|
|
28
|
-
|
|
29
|
-
"""Generic Driver supporting the Manta and Mako drivers.
|
|
30
|
-
Fetches deadtime prior to use in a Streaming scan.
|
|
31
|
-
|
|
32
|
-
Requires driver firmware up to date:
|
|
33
|
-
- Model_RBV must be of the form "^(Mako|Manta) (model)$"
|
|
25
|
+
"""Generic Driver supporting all GiGE cameras.
|
|
34
26
|
|
|
35
27
|
This mirrors the interface provided by ADAravis/db/aravisCamera.template.
|
|
36
28
|
"""
|
|
37
29
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
AravisTriggerMode, prefix + "TriggerMode"
|
|
41
|
-
)
|
|
42
|
-
self.trigger_source = epics_signal_rw_rbv(
|
|
43
|
-
AravisTriggerSource, # type: ignore
|
|
44
|
-
prefix + "TriggerSource",
|
|
45
|
-
)
|
|
46
|
-
super().__init__(prefix, name=name)
|
|
30
|
+
trigger_mode: A[SignalRW[AravisTriggerMode], PvSuffix.rbv("TriggerMode")]
|
|
31
|
+
trigger_source: A[SignalRW[AravisTriggerSource], PvSuffix.rbv("TriggerSource")]
|
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
"""Core components of the areaDetector software.
|
|
2
|
+
|
|
3
|
+
https://github.com/areaDetector/ADCore
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from ._core_detector import AreaDetector, ContAcqAreaDetector
|
|
2
7
|
from ._core_io import (
|
|
3
8
|
ADBaseDatasetDescriber,
|
|
4
9
|
ADBaseIO,
|
|
5
|
-
|
|
10
|
+
ADCallbacks,
|
|
11
|
+
ADCompression,
|
|
12
|
+
ADState,
|
|
6
13
|
NDArrayBaseIO,
|
|
14
|
+
NDCBFlushOnSoftTrgMode,
|
|
7
15
|
NDFileHDFIO,
|
|
8
16
|
NDFileIO,
|
|
9
17
|
NDPluginBaseIO,
|
|
18
|
+
NDPluginCBIO,
|
|
10
19
|
NDPluginStatsIO,
|
|
11
20
|
)
|
|
12
|
-
from ._core_logic import DEFAULT_GOOD_STATES, ADBaseController
|
|
21
|
+
from ._core_logic import DEFAULT_GOOD_STATES, ADBaseContAcqController, ADBaseController
|
|
13
22
|
from ._core_writer import ADWriter
|
|
14
23
|
from ._hdf_writer import ADHDFWriter
|
|
15
24
|
from ._jpeg_writer import ADJPEGWriter
|
|
@@ -17,8 +26,8 @@ from ._single_trigger import SingleTriggerDetector
|
|
|
17
26
|
from ._tiff_writer import ADTIFFWriter
|
|
18
27
|
from ._utils import (
|
|
19
28
|
ADBaseDataType,
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
ADFileWriteMode,
|
|
30
|
+
ADImageMode,
|
|
22
31
|
NDAttributeDataType,
|
|
23
32
|
NDAttributeParam,
|
|
24
33
|
NDAttributePv,
|
|
@@ -28,8 +37,12 @@ from ._utils import (
|
|
|
28
37
|
|
|
29
38
|
__all__ = [
|
|
30
39
|
"ADBaseIO",
|
|
40
|
+
"ADCallbacks",
|
|
41
|
+
"ADCompression",
|
|
42
|
+
"ADBaseContAcqController",
|
|
31
43
|
"AreaDetector",
|
|
32
|
-
"
|
|
44
|
+
"ADState",
|
|
45
|
+
"ContAcqAreaDetector",
|
|
33
46
|
"NDArrayBaseIO",
|
|
34
47
|
"NDFileIO",
|
|
35
48
|
"NDFileHDFIO",
|
|
@@ -44,11 +57,13 @@ __all__ = [
|
|
|
44
57
|
"ADJPEGWriter",
|
|
45
58
|
"SingleTriggerDetector",
|
|
46
59
|
"ADBaseDataType",
|
|
47
|
-
"
|
|
48
|
-
"
|
|
60
|
+
"ADFileWriteMode",
|
|
61
|
+
"ADImageMode",
|
|
49
62
|
"NDAttributePv",
|
|
50
63
|
"NDAttributeParam",
|
|
51
64
|
"NDAttributeDataType",
|
|
52
65
|
"stop_busy_record",
|
|
53
66
|
"NDAttributePvDbrType",
|
|
67
|
+
"NDCBFlushOnSoftTrgMode",
|
|
68
|
+
"NDPluginCBIO",
|
|
54
69
|
]
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from collections.abc import Sequence
|
|
2
2
|
|
|
3
3
|
from ophyd_async.core import SignalR, StandardDetector
|
|
4
|
+
from ophyd_async.core._providers import PathProvider
|
|
4
5
|
|
|
5
|
-
from ._core_io import NDPluginBaseIO
|
|
6
|
-
from ._core_logic import ADBaseControllerT
|
|
6
|
+
from ._core_io import ADBaseIO, NDPluginBaseIO, NDPluginCBIO
|
|
7
|
+
from ._core_logic import ADBaseContAcqController, ADBaseControllerT
|
|
7
8
|
from ._core_writer import ADWriter
|
|
9
|
+
from ._hdf_writer import ADHDFWriter
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
class AreaDetector(StandardDetector[ADBaseControllerT, ADWriter]):
|
|
@@ -39,3 +41,41 @@ class AreaDetector(StandardDetector[ADBaseControllerT, ADWriter]):
|
|
|
39
41
|
f"Expected {self.name}.{name} to be a {plugin_type}, got {plugin}"
|
|
40
42
|
)
|
|
41
43
|
return plugin
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ContAcqAreaDetector(AreaDetector[ADBaseContAcqController]):
|
|
47
|
+
"""Ophyd-async implementation of a continuously acquiring AreaDetector."""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
prefix: str,
|
|
52
|
+
path_provider: PathProvider,
|
|
53
|
+
drv_cls: type[ADBaseIO] = ADBaseIO,
|
|
54
|
+
drv_suffix: str = "cam1:",
|
|
55
|
+
cb_suffix: str = "CB1:",
|
|
56
|
+
writer_cls: type[ADWriter] = ADHDFWriter,
|
|
57
|
+
fileio_suffix: str | None = None,
|
|
58
|
+
name: str = "",
|
|
59
|
+
plugins: dict[str, NDPluginBaseIO] | None = None,
|
|
60
|
+
config_sigs: Sequence[SignalR] = (),
|
|
61
|
+
):
|
|
62
|
+
self.cb_plugin = NDPluginCBIO(prefix + cb_suffix)
|
|
63
|
+
driver = drv_cls(prefix + drv_suffix)
|
|
64
|
+
controller = ADBaseContAcqController(driver, self.cb_plugin)
|
|
65
|
+
|
|
66
|
+
writer = writer_cls.with_io(
|
|
67
|
+
prefix,
|
|
68
|
+
path_provider,
|
|
69
|
+
# Since the CB plugin controls acq, use it when checking shape
|
|
70
|
+
dataset_source=self.cb_plugin,
|
|
71
|
+
fileio_suffix=fileio_suffix,
|
|
72
|
+
plugins=plugins,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
super().__init__(
|
|
76
|
+
controller=controller,
|
|
77
|
+
writer=writer,
|
|
78
|
+
plugins=plugins,
|
|
79
|
+
name=name,
|
|
80
|
+
config_sigs=config_sigs,
|
|
81
|
+
)
|