ophyd-async 0.3a4__py3-none-any.whl → 0.3a6__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/_version.py +1 -1
- ophyd_async/core/__init__.py +6 -1
- ophyd_async/core/async_status.py +83 -39
- ophyd_async/core/detector.py +23 -29
- ophyd_async/core/device.py +32 -11
- ophyd_async/core/flyer.py +1 -1
- ophyd_async/core/mock_signal_backend.py +14 -15
- ophyd_async/core/mock_signal_utils.py +9 -13
- ophyd_async/core/signal.py +71 -21
- ophyd_async/core/utils.py +30 -0
- ophyd_async/epics/areadetector/aravis.py +1 -5
- ophyd_async/epics/areadetector/controllers/aravis_controller.py +6 -1
- ophyd_async/epics/areadetector/drivers/ad_base.py +1 -7
- ophyd_async/epics/areadetector/drivers/aravis_driver.py +2 -120
- ophyd_async/epics/areadetector/writers/hdf_writer.py +3 -2
- ophyd_async/epics/areadetector/writers/nd_file_hdf.py +0 -2
- ophyd_async/epics/areadetector/writers/nd_plugin.py +9 -0
- ophyd_async/epics/demo/__init__.py +33 -34
- ophyd_async/epics/motion/motor.py +47 -42
- ophyd_async/epics/pvi/pvi.py +2 -2
- ophyd_async/epics/signal/__init__.py +8 -1
- ophyd_async/panda/__init__.py +2 -0
- ophyd_async/panda/writers/_hdf_writer.py +4 -4
- ophyd_async/plan_stubs/__init__.py +13 -0
- ophyd_async/plan_stubs/ensure_connected.py +22 -0
- ophyd_async/plan_stubs/fly.py +149 -0
- ophyd_async/protocols.py +32 -2
- ophyd_async/sim/demo/sim_motor.py +67 -82
- {ophyd_async-0.3a4.dist-info → ophyd_async-0.3a6.dist-info}/METADATA +1 -1
- {ophyd_async-0.3a4.dist-info → ophyd_async-0.3a6.dist-info}/RECORD +34 -33
- ophyd_async/planstubs/__init__.py +0 -5
- ophyd_async/planstubs/prepare_trigger_and_dets.py +0 -57
- {ophyd_async-0.3a4.dist-info → ophyd_async-0.3a6.dist-info}/LICENSE +0 -0
- {ophyd_async-0.3a4.dist-info → ophyd_async-0.3a6.dist-info}/WHEEL +0 -0
- {ophyd_async-0.3a4.dist-info → ophyd_async-0.3a6.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.3a4.dist-info → ophyd_async-0.3a6.dist-info}/top_level.txt +0 -0
ophyd_async/core/signal.py
CHANGED
|
@@ -21,6 +21,7 @@ from bluesky.protocols import (
|
|
|
21
21
|
Location,
|
|
22
22
|
Movable,
|
|
23
23
|
Reading,
|
|
24
|
+
Status,
|
|
24
25
|
Subscribable,
|
|
25
26
|
)
|
|
26
27
|
|
|
@@ -31,7 +32,7 @@ from .async_status import AsyncStatus
|
|
|
31
32
|
from .device import Device
|
|
32
33
|
from .signal_backend import SignalBackend
|
|
33
34
|
from .soft_signal_backend import SoftSignalBackend
|
|
34
|
-
from .utils import DEFAULT_TIMEOUT, Callback, T
|
|
35
|
+
from .utils import DEFAULT_TIMEOUT, CalculatableTimeout, CalculateTimeout, Callback, T
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
def _add_timeout(func):
|
|
@@ -64,7 +65,9 @@ class Signal(Device, Generic[T]):
|
|
|
64
65
|
self._initial_backend = self._backend = backend
|
|
65
66
|
super().__init__(name)
|
|
66
67
|
|
|
67
|
-
async def connect(
|
|
68
|
+
async def connect(
|
|
69
|
+
self, mock=False, timeout=DEFAULT_TIMEOUT, force_reconnect: bool = False
|
|
70
|
+
):
|
|
68
71
|
if mock and not isinstance(self._backend, MockSignalBackend):
|
|
69
72
|
# Using a soft backend, look to the initial value
|
|
70
73
|
self._backend = MockSignalBackend(
|
|
@@ -210,15 +213,14 @@ class SignalR(Signal[T], AsyncReadable, AsyncStageable, Subscribable):
|
|
|
210
213
|
self._del_cache(self._get_cache().set_staged(False))
|
|
211
214
|
|
|
212
215
|
|
|
213
|
-
USE_DEFAULT_TIMEOUT = "USE_DEFAULT_TIMEOUT"
|
|
214
|
-
|
|
215
|
-
|
|
216
216
|
class SignalW(Signal[T], Movable):
|
|
217
217
|
"""Signal that can be set"""
|
|
218
218
|
|
|
219
|
-
def set(
|
|
219
|
+
def set(
|
|
220
|
+
self, value: T, wait=True, timeout: CalculatableTimeout = CalculateTimeout
|
|
221
|
+
) -> AsyncStatus:
|
|
220
222
|
"""Set the value and return a status saying when it's done"""
|
|
221
|
-
if timeout is
|
|
223
|
+
if timeout is CalculateTimeout:
|
|
222
224
|
timeout = self._timeout
|
|
223
225
|
|
|
224
226
|
async def do_set():
|
|
@@ -245,9 +247,11 @@ class SignalRW(SignalR[T], SignalW[T], Locatable):
|
|
|
245
247
|
class SignalX(Signal):
|
|
246
248
|
"""Signal that puts the default value"""
|
|
247
249
|
|
|
248
|
-
def trigger(
|
|
250
|
+
def trigger(
|
|
251
|
+
self, wait=True, timeout: CalculatableTimeout = CalculateTimeout
|
|
252
|
+
) -> AsyncStatus:
|
|
249
253
|
"""Trigger the action and return a status saying when it's done"""
|
|
250
|
-
if timeout is
|
|
254
|
+
if timeout is CalculateTimeout:
|
|
251
255
|
timeout = self._timeout
|
|
252
256
|
coro = self._backend.put(None, wait=wait, timeout=timeout)
|
|
253
257
|
return AsyncStatus(coro)
|
|
@@ -267,7 +271,7 @@ def soft_signal_r_and_setter(
|
|
|
267
271
|
datatype: Optional[Type[T]] = None,
|
|
268
272
|
initial_value: Optional[T] = None,
|
|
269
273
|
name: str = "",
|
|
270
|
-
) -> Tuple[SignalR[T], Callable[[T]]]:
|
|
274
|
+
) -> Tuple[SignalR[T], Callable[[T], None]]:
|
|
271
275
|
"""Returns a tuple of a read-only Signal and a callable through
|
|
272
276
|
which the signal can be internally modified within the device. Use
|
|
273
277
|
soft_signal_rw if you want a device that is externally modifiable
|
|
@@ -278,6 +282,19 @@ def soft_signal_r_and_setter(
|
|
|
278
282
|
return (signal, backend.set_value)
|
|
279
283
|
|
|
280
284
|
|
|
285
|
+
def _generate_assert_error_msg(
|
|
286
|
+
name: str, expected_result: str, actuall_result: str
|
|
287
|
+
) -> str:
|
|
288
|
+
WARNING = "\033[93m"
|
|
289
|
+
FAIL = "\033[91m"
|
|
290
|
+
ENDC = "\033[0m"
|
|
291
|
+
return (
|
|
292
|
+
f"Expected {WARNING}{name}{ENDC} to produce"
|
|
293
|
+
+ f"\n{FAIL}{actuall_result}{ENDC}"
|
|
294
|
+
+ f"\nbut actually got \n{FAIL}{expected_result}{ENDC}"
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
|
|
281
298
|
async def assert_value(signal: SignalR[T], value: Any) -> None:
|
|
282
299
|
"""Assert a signal's value and compare it an expected signal.
|
|
283
300
|
|
|
@@ -294,11 +311,14 @@ async def assert_value(signal: SignalR[T], value: Any) -> None:
|
|
|
294
311
|
await assert_value(signal, value)
|
|
295
312
|
|
|
296
313
|
"""
|
|
297
|
-
|
|
314
|
+
actual_value = await signal.get_value()
|
|
315
|
+
assert actual_value == value, _generate_assert_error_msg(
|
|
316
|
+
signal.name, value, actual_value
|
|
317
|
+
)
|
|
298
318
|
|
|
299
319
|
|
|
300
320
|
async def assert_reading(
|
|
301
|
-
readable: AsyncReadable,
|
|
321
|
+
readable: AsyncReadable, expected_reading: Mapping[str, Reading]
|
|
302
322
|
) -> None:
|
|
303
323
|
"""Assert readings from readable.
|
|
304
324
|
|
|
@@ -316,7 +336,10 @@ async def assert_reading(
|
|
|
316
336
|
await assert_reading(readable, reading)
|
|
317
337
|
|
|
318
338
|
"""
|
|
319
|
-
|
|
339
|
+
actual_reading = await readable.read()
|
|
340
|
+
assert expected_reading == actual_reading, _generate_assert_error_msg(
|
|
341
|
+
readable.name, expected_reading, actual_reading
|
|
342
|
+
)
|
|
320
343
|
|
|
321
344
|
|
|
322
345
|
async def assert_configuration(
|
|
@@ -339,7 +362,10 @@ async def assert_configuration(
|
|
|
339
362
|
await assert_configuration(configurable configuration)
|
|
340
363
|
|
|
341
364
|
"""
|
|
342
|
-
|
|
365
|
+
actual_configurable = await configurable.read_configuration()
|
|
366
|
+
assert configuration == actual_configurable, _generate_assert_error_msg(
|
|
367
|
+
configurable.name, configuration, actual_configurable
|
|
368
|
+
)
|
|
343
369
|
|
|
344
370
|
|
|
345
371
|
def assert_emitted(docs: Mapping[str, list[dict]], **numbers: int):
|
|
@@ -359,11 +385,18 @@ def assert_emitted(docs: Mapping[str, list[dict]], **numbers: int):
|
|
|
359
385
|
assert_emitted(docs, start=1, descriptor=1,
|
|
360
386
|
resource=1, datum=1, event=1, stop=1)
|
|
361
387
|
"""
|
|
362
|
-
assert list(docs) == list(numbers)
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
388
|
+
assert list(docs) == list(numbers), _generate_assert_error_msg(
|
|
389
|
+
"documents", list(numbers), list(docs)
|
|
390
|
+
)
|
|
391
|
+
actual_numbers = {name: len(d) for name, d in docs.items()}
|
|
392
|
+
assert actual_numbers == numbers, _generate_assert_error_msg(
|
|
393
|
+
"emitted", numbers, actual_numbers
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
async def observe_value(
|
|
398
|
+
signal: SignalR[T], timeout: float | None = None, done_status: Status | None = None
|
|
399
|
+
) -> AsyncGenerator[T, None]:
|
|
367
400
|
"""Subscribe to the value of a signal so it can be iterated from.
|
|
368
401
|
|
|
369
402
|
Parameters
|
|
@@ -371,6 +404,12 @@ async def observe_value(signal: SignalR[T], timeout=None) -> AsyncGenerator[T, N
|
|
|
371
404
|
signal:
|
|
372
405
|
Call subscribe_value on this at the start, and clear_sub on it at the
|
|
373
406
|
end
|
|
407
|
+
timeout:
|
|
408
|
+
If given, how long to wait for each updated value in seconds. If an update
|
|
409
|
+
is not produced in this time then raise asyncio.TimeoutError
|
|
410
|
+
done_status:
|
|
411
|
+
If this status is complete, stop observing and make the iterator return.
|
|
412
|
+
If it raises an exception then this exception will be raised by the iterator.
|
|
374
413
|
|
|
375
414
|
Notes
|
|
376
415
|
-----
|
|
@@ -379,7 +418,8 @@ async def observe_value(signal: SignalR[T], timeout=None) -> AsyncGenerator[T, N
|
|
|
379
418
|
async for value in observe_value(sig):
|
|
380
419
|
do_something_with(value)
|
|
381
420
|
"""
|
|
382
|
-
|
|
421
|
+
|
|
422
|
+
q: asyncio.Queue[T | Status] = asyncio.Queue()
|
|
383
423
|
if timeout is None:
|
|
384
424
|
get_value = q.get
|
|
385
425
|
else:
|
|
@@ -387,10 +427,20 @@ async def observe_value(signal: SignalR[T], timeout=None) -> AsyncGenerator[T, N
|
|
|
387
427
|
async def get_value():
|
|
388
428
|
return await asyncio.wait_for(q.get(), timeout)
|
|
389
429
|
|
|
430
|
+
if done_status is not None:
|
|
431
|
+
done_status.add_callback(q.put_nowait)
|
|
432
|
+
|
|
390
433
|
signal.subscribe_value(q.put_nowait)
|
|
391
434
|
try:
|
|
392
435
|
while True:
|
|
393
|
-
|
|
436
|
+
item = await get_value()
|
|
437
|
+
if done_status and item is done_status:
|
|
438
|
+
if exc := done_status.exception():
|
|
439
|
+
raise exc
|
|
440
|
+
else:
|
|
441
|
+
break
|
|
442
|
+
else:
|
|
443
|
+
yield item
|
|
394
444
|
finally:
|
|
395
445
|
signal.clear_sub(q.put_nowait)
|
|
396
446
|
|
ophyd_async/core/utils.py
CHANGED
|
@@ -2,13 +2,16 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
+
from dataclasses import dataclass
|
|
5
6
|
from typing import (
|
|
6
7
|
Awaitable,
|
|
7
8
|
Callable,
|
|
8
9
|
Dict,
|
|
10
|
+
Generic,
|
|
9
11
|
Iterable,
|
|
10
12
|
List,
|
|
11
13
|
Optional,
|
|
14
|
+
ParamSpec,
|
|
12
15
|
Type,
|
|
13
16
|
TypeVar,
|
|
14
17
|
Union,
|
|
@@ -18,6 +21,7 @@ import numpy as np
|
|
|
18
21
|
from bluesky.protocols import Reading
|
|
19
22
|
|
|
20
23
|
T = TypeVar("T")
|
|
24
|
+
P = ParamSpec("P")
|
|
21
25
|
Callback = Callable[[T], None]
|
|
22
26
|
|
|
23
27
|
#: A function that will be called with the Reading and value when the
|
|
@@ -27,6 +31,17 @@ DEFAULT_TIMEOUT = 10.0
|
|
|
27
31
|
ErrorText = Union[str, Dict[str, Exception]]
|
|
28
32
|
|
|
29
33
|
|
|
34
|
+
class CalculateTimeout:
|
|
35
|
+
"""Sentinel class used to implement ``myfunc(timeout=CalculateTimeout)``
|
|
36
|
+
|
|
37
|
+
This signifies that the function should calculate a suitable non-zero
|
|
38
|
+
timeout itself
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
CalculatableTimeout = float | None | Type[CalculateTimeout]
|
|
43
|
+
|
|
44
|
+
|
|
30
45
|
class NotConnected(Exception):
|
|
31
46
|
"""Exception to be raised if a `Device.connect` is cancelled"""
|
|
32
47
|
|
|
@@ -77,6 +92,21 @@ class NotConnected(Exception):
|
|
|
77
92
|
return self.format_error_string(indent="")
|
|
78
93
|
|
|
79
94
|
|
|
95
|
+
@dataclass(frozen=True)
|
|
96
|
+
class WatcherUpdate(Generic[T]):
|
|
97
|
+
"""A dataclass such that, when expanded, it provides the kwargs for a watcher"""
|
|
98
|
+
|
|
99
|
+
current: T
|
|
100
|
+
initial: T
|
|
101
|
+
target: T
|
|
102
|
+
name: str | None = None
|
|
103
|
+
unit: str | None = None
|
|
104
|
+
precision: float | None = None
|
|
105
|
+
fraction: float | None = None
|
|
106
|
+
time_elapsed: float | None = None
|
|
107
|
+
time_remaining: float | None = None
|
|
108
|
+
|
|
109
|
+
|
|
80
110
|
async def wait_for_connection(**coros: Awaitable[None]):
|
|
81
111
|
"""Call many underlying signals, accumulating exceptions and returning them
|
|
82
112
|
|
|
@@ -2,7 +2,7 @@ from typing import get_args
|
|
|
2
2
|
|
|
3
3
|
from bluesky.protocols import HasHints, Hints
|
|
4
4
|
|
|
5
|
-
from ophyd_async.core import DirectoryProvider, StandardDetector
|
|
5
|
+
from ophyd_async.core import DirectoryProvider, StandardDetector
|
|
6
6
|
from ophyd_async.epics.areadetector.controllers.aravis_controller import (
|
|
7
7
|
AravisController,
|
|
8
8
|
)
|
|
@@ -45,10 +45,6 @@ class AravisDetector(StandardDetector, HasHints):
|
|
|
45
45
|
name=name,
|
|
46
46
|
)
|
|
47
47
|
|
|
48
|
-
async def _prepare(self, value: TriggerInfo) -> None:
|
|
49
|
-
await self.drv.fetch_deadtime()
|
|
50
|
-
await super()._prepare(value)
|
|
51
|
-
|
|
52
48
|
def get_external_trigger_gpio(self):
|
|
53
49
|
return self._controller.gpio_number
|
|
54
50
|
|
|
@@ -14,6 +14,11 @@ from ophyd_async.epics.areadetector.drivers.aravis_driver import (
|
|
|
14
14
|
)
|
|
15
15
|
from ophyd_async.epics.areadetector.utils import ImageMode, stop_busy_record
|
|
16
16
|
|
|
17
|
+
# The deadtime of an ADaravis controller varies depending on the exact model of camera.
|
|
18
|
+
# Ideally we would maximize performance by dynamically retrieving the deadtime at
|
|
19
|
+
# runtime. See https://github.com/bluesky/ophyd-async/issues/308
|
|
20
|
+
_HIGHEST_POSSIBLE_DEADTIME = 1961e-6
|
|
21
|
+
|
|
17
22
|
|
|
18
23
|
class AravisController(DetectorControl):
|
|
19
24
|
GPIO_NUMBER = Literal[1, 2, 3, 4]
|
|
@@ -23,7 +28,7 @@ class AravisController(DetectorControl):
|
|
|
23
28
|
self.gpio_number = gpio_number
|
|
24
29
|
|
|
25
30
|
def get_deadtime(self, exposure: float) -> float:
|
|
26
|
-
return
|
|
31
|
+
return _HIGHEST_POSSIBLE_DEADTIME
|
|
27
32
|
|
|
28
33
|
async def arm(
|
|
29
34
|
self,
|
|
@@ -9,7 +9,7 @@ from ophyd_async.core import (
|
|
|
9
9
|
set_and_wait_for_value,
|
|
10
10
|
)
|
|
11
11
|
|
|
12
|
-
from ...signal.signal import epics_signal_r,
|
|
12
|
+
from ...signal.signal import epics_signal_r, epics_signal_rw_rbv
|
|
13
13
|
from ..utils import ImageMode
|
|
14
14
|
from ..writers.nd_plugin import NDArrayBase
|
|
15
15
|
|
|
@@ -43,18 +43,12 @@ DEFAULT_GOOD_STATES: FrozenSet[DetectorState] = frozenset(
|
|
|
43
43
|
class ADBase(NDArrayBase):
|
|
44
44
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
45
45
|
# Define some signals
|
|
46
|
-
self.acquire = epics_signal_rw_rbv(bool, prefix + "Acquire")
|
|
47
46
|
self.acquire_time = epics_signal_rw_rbv(float, prefix + "AcquireTime")
|
|
48
47
|
self.num_images = epics_signal_rw_rbv(int, prefix + "NumImages")
|
|
49
48
|
self.image_mode = epics_signal_rw_rbv(ImageMode, prefix + "ImageMode")
|
|
50
|
-
self.array_counter = epics_signal_rw_rbv(int, prefix + "ArrayCounter")
|
|
51
|
-
self.array_size_x = epics_signal_r(int, prefix + "ArraySizeX_RBV")
|
|
52
|
-
self.array_size_y = epics_signal_r(int, prefix + "ArraySizeY_RBV")
|
|
53
49
|
self.detector_state = epics_signal_r(
|
|
54
50
|
DetectorState, prefix + "DetectorState_RBV"
|
|
55
51
|
)
|
|
56
|
-
# There is no _RBV for this one
|
|
57
|
-
self.wait_for_plugins = epics_signal_rw(bool, prefix + "WaitForPlugins")
|
|
58
52
|
super().__init__(prefix, name=name)
|
|
59
53
|
|
|
60
54
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Literal
|
|
3
3
|
|
|
4
4
|
from ophyd_async.epics.areadetector.drivers import ADBase
|
|
5
|
-
from ophyd_async.epics.signal.signal import
|
|
5
|
+
from ophyd_async.epics.signal.signal import epics_signal_rw_rbv
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class AravisTriggerMode(str, Enum):
|
|
@@ -22,113 +22,6 @@ class AravisTriggerMode(str, Enum):
|
|
|
22
22
|
AravisTriggerSource = Literal["Freerun", "Line1", "Line2", "Line3", "Line4"]
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
def _reverse_lookup(
|
|
26
|
-
model_deadtimes: Dict[float, Tuple[str, ...]],
|
|
27
|
-
) -> Callable[[str], float]:
|
|
28
|
-
def inner(pixel_format: str, model_name: str) -> float:
|
|
29
|
-
for deadtime, pixel_formats in model_deadtimes.items():
|
|
30
|
-
if pixel_format in pixel_formats:
|
|
31
|
-
return deadtime
|
|
32
|
-
raise ValueError(
|
|
33
|
-
f"Model {model_name} does not have a defined deadtime "
|
|
34
|
-
f"for pixel format {pixel_format}"
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
return inner
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
_deadtimes: Dict[str, Callable[[str, str], float]] = {
|
|
41
|
-
# cite: https://cdn.alliedvision.com/fileadmin/content/documents/products/cameras/Manta/techman/Manta_TechMan.pdf retrieved 2024-04-05 # noqa: E501
|
|
42
|
-
"Manta G-125": lambda _, __: 63e-6,
|
|
43
|
-
"Manta G-145": lambda _, __: 106e-6,
|
|
44
|
-
"Manta G-235": _reverse_lookup(
|
|
45
|
-
{
|
|
46
|
-
118e-6: (
|
|
47
|
-
"Mono8",
|
|
48
|
-
"Mono12Packed",
|
|
49
|
-
"BayerRG8",
|
|
50
|
-
"BayerRG12",
|
|
51
|
-
"BayerRG12Packed",
|
|
52
|
-
"YUV411Packed",
|
|
53
|
-
),
|
|
54
|
-
256e-6: ("Mono12", "BayerRG12", "YUV422Packed"),
|
|
55
|
-
390e-6: ("RGB8Packed", "BGR8Packed", "YUV444Packed"),
|
|
56
|
-
}
|
|
57
|
-
),
|
|
58
|
-
"Manta G-895": _reverse_lookup(
|
|
59
|
-
{
|
|
60
|
-
404e-6: (
|
|
61
|
-
"Mono8",
|
|
62
|
-
"Mono12Packed",
|
|
63
|
-
"BayerRG8",
|
|
64
|
-
"BayerRG12Packed",
|
|
65
|
-
"YUV411Packed",
|
|
66
|
-
),
|
|
67
|
-
542e-6: ("Mono12", "BayerRG12", "YUV422Packed"),
|
|
68
|
-
822e-6: ("RGB8Packed", "BGR8Packed", "YUV444Packed"),
|
|
69
|
-
}
|
|
70
|
-
),
|
|
71
|
-
"Manta G-2460": _reverse_lookup(
|
|
72
|
-
{
|
|
73
|
-
979e-6: (
|
|
74
|
-
"Mono8",
|
|
75
|
-
"Mono12Packed",
|
|
76
|
-
"BayerRG8",
|
|
77
|
-
"BayerRG12Packed",
|
|
78
|
-
"YUV411Packed",
|
|
79
|
-
),
|
|
80
|
-
1304e-6: ("Mono12", "BayerRG12", "YUV422Packed"),
|
|
81
|
-
1961e-6: ("RGB8Packed", "BGR8Packed", "YUV444Packed"),
|
|
82
|
-
}
|
|
83
|
-
),
|
|
84
|
-
# cite: https://cdn.alliedvision.com/fileadmin/content/documents/products/cameras/various/appnote/GigE/GigE-Cameras_AppNote_PIV-Min-Time-Between-Exposures.pdf retrieved 2024-04-05 # noqa: E501
|
|
85
|
-
"Manta G-609": lambda _, __: 47e-6,
|
|
86
|
-
# cite: https://cdn.alliedvision.com/fileadmin/content/documents/products/cameras/Mako/techman/Mako_TechMan_en.pdf retrieved 2024-04-05 # noqa: E501
|
|
87
|
-
"Mako G-040": _reverse_lookup(
|
|
88
|
-
{
|
|
89
|
-
101e-6: (
|
|
90
|
-
"Mono8",
|
|
91
|
-
"Mono12Packed",
|
|
92
|
-
"BayerRG8",
|
|
93
|
-
"BayerRG12Packed",
|
|
94
|
-
"YUV411Packed",
|
|
95
|
-
),
|
|
96
|
-
140e-6: ("Mono12", "BayerRG12", "YUV422Packed"),
|
|
97
|
-
217e-6: ("RGB8Packed", "BGR8Packed", "YUV444Packed"),
|
|
98
|
-
}
|
|
99
|
-
),
|
|
100
|
-
"Mako G-125": lambda _, __: 70e-6,
|
|
101
|
-
# Assume 12 bits: 10 bits = 275e-6
|
|
102
|
-
"Mako G-234": _reverse_lookup(
|
|
103
|
-
{
|
|
104
|
-
356e-6: (
|
|
105
|
-
"Mono8",
|
|
106
|
-
"BayerRG8",
|
|
107
|
-
"BayerRG12",
|
|
108
|
-
"BayerRG12Packed",
|
|
109
|
-
"YUV411Packed",
|
|
110
|
-
"YUV422Packed",
|
|
111
|
-
),
|
|
112
|
-
# Assume 12 bits: 10 bits = 563e-6
|
|
113
|
-
726e-6: ("RGB8Packed", "BRG8Packed", "YUV444Packed"),
|
|
114
|
-
}
|
|
115
|
-
),
|
|
116
|
-
"Mako G-507": _reverse_lookup(
|
|
117
|
-
{
|
|
118
|
-
270e-6: (
|
|
119
|
-
"Mono8",
|
|
120
|
-
"Mono12Packed",
|
|
121
|
-
"BayerRG8",
|
|
122
|
-
"BayerRG12Packed",
|
|
123
|
-
"YUV411Packed",
|
|
124
|
-
),
|
|
125
|
-
363e-6: ("Mono12", "BayerRG12", "YUV422Packed"),
|
|
126
|
-
554e-6: ("RGB8Packed", "BGR8Packed", "YUV444Packed"),
|
|
127
|
-
}
|
|
128
|
-
),
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
25
|
class AravisDriver(ADBase):
|
|
133
26
|
# If instantiating a new instance, ensure it is supported in the _deadtimes dict
|
|
134
27
|
"""Generic Driver supporting the Manta and Mako drivers.
|
|
@@ -142,15 +35,4 @@ class AravisDriver(ADBase):
|
|
|
142
35
|
AravisTriggerMode, prefix + "TriggerMode"
|
|
143
36
|
)
|
|
144
37
|
self.trigger_source = epics_signal_rw_rbv(str, prefix + "TriggerSource")
|
|
145
|
-
self.model = epics_signal_r(str, prefix + "Model_RBV")
|
|
146
|
-
self.pixel_format = epics_signal_rw_rbv(str, prefix + "PixelFormat")
|
|
147
|
-
self.dead_time: Optional[float] = None
|
|
148
38
|
super().__init__(prefix, name=name)
|
|
149
|
-
|
|
150
|
-
async def fetch_deadtime(self) -> None:
|
|
151
|
-
# All known in-use version B/C have same deadtime as non-B/C
|
|
152
|
-
model: str = (await self.model.get_value()).removesuffix("B").removesuffix("C")
|
|
153
|
-
if model not in _deadtimes:
|
|
154
|
-
raise ValueError(f"Model {model} does not have defined deadtimes")
|
|
155
|
-
pixel_format: str = await self.pixel_format.get_value()
|
|
156
|
-
self.dead_time = _deadtimes.get(model)(pixel_format, model)
|
|
@@ -43,12 +43,13 @@ class HDFWriter(DetectorWriter):
|
|
|
43
43
|
async def open(self, multiplier: int = 1) -> Dict[str, DataKey]:
|
|
44
44
|
self._file = None
|
|
45
45
|
info = self._directory_provider()
|
|
46
|
+
file_path = str(info.root / info.resource_dir)
|
|
46
47
|
await asyncio.gather(
|
|
47
48
|
self.hdf.num_extra_dims.set(0),
|
|
48
49
|
self.hdf.lazy_open.set(True),
|
|
49
50
|
self.hdf.swmr_mode.set(True),
|
|
50
51
|
# See https://github.com/bluesky/ophyd-async/issues/122
|
|
51
|
-
self.hdf.file_path.set(
|
|
52
|
+
self.hdf.file_path.set(file_path),
|
|
52
53
|
self.hdf.file_name.set(f"{info.prefix}{self.hdf.name}{info.suffix}"),
|
|
53
54
|
self.hdf.file_template.set("%s/%s.h5"),
|
|
54
55
|
self.hdf.file_write_mode.set(FileWriteMode.stream),
|
|
@@ -59,7 +60,7 @@ class HDFWriter(DetectorWriter):
|
|
|
59
60
|
|
|
60
61
|
assert (
|
|
61
62
|
await self.hdf.file_path_exists.get_value()
|
|
62
|
-
), f"File path {
|
|
63
|
+
), f"File path {file_path} for hdf plugin does not exist"
|
|
63
64
|
|
|
64
65
|
# Overwrite num_capture to go forever
|
|
65
66
|
await self.hdf.num_capture.set(0)
|
|
@@ -36,7 +36,5 @@ class NDFileHDF(NDPluginBase):
|
|
|
36
36
|
self.lazy_open = epics_signal_rw_rbv(bool, prefix + "LazyOpen")
|
|
37
37
|
self.capture = epics_signal_rw_rbv(bool, prefix + "Capture")
|
|
38
38
|
self.flush_now = epics_signal_rw(bool, prefix + "FlushNow")
|
|
39
|
-
self.array_size0 = epics_signal_r(int, prefix + "ArraySize0_RBV")
|
|
40
|
-
self.array_size1 = epics_signal_r(int, prefix + "ArraySize1_RBV")
|
|
41
39
|
self.xml_file_name = epics_signal_rw_rbv(str, prefix + "XMLFileName")
|
|
42
40
|
super().__init__(prefix, name)
|
|
@@ -14,6 +14,13 @@ class NDArrayBase(Device):
|
|
|
14
14
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
15
15
|
self.unique_id = epics_signal_r(int, prefix + "UniqueId_RBV")
|
|
16
16
|
self.nd_attributes_file = epics_signal_rw(str, prefix + "NDAttributesFile")
|
|
17
|
+
self.acquire = epics_signal_rw_rbv(bool, prefix + "Acquire")
|
|
18
|
+
self.array_size_x = epics_signal_r(int, prefix + "ArraySizeX_RBV")
|
|
19
|
+
self.array_size_y = epics_signal_r(int, prefix + "ArraySizeY_RBV")
|
|
20
|
+
self.array_counter = epics_signal_rw_rbv(int, prefix + "ArrayCounter")
|
|
21
|
+
# There is no _RBV for this one
|
|
22
|
+
self.wait_for_plugins = epics_signal_rw(bool, prefix + "WaitForPlugins")
|
|
23
|
+
|
|
17
24
|
super().__init__(name=name)
|
|
18
25
|
|
|
19
26
|
|
|
@@ -22,6 +29,8 @@ class NDPluginBase(NDArrayBase):
|
|
|
22
29
|
self.nd_array_port = epics_signal_rw_rbv(str, prefix + "NDArrayPort")
|
|
23
30
|
self.enable_callback = epics_signal_rw_rbv(Callback, prefix + "EnableCallbacks")
|
|
24
31
|
self.nd_array_address = epics_signal_rw_rbv(int, prefix + "NDArrayAddress")
|
|
32
|
+
self.array_size0 = epics_signal_r(int, prefix + "ArraySize0_RBV")
|
|
33
|
+
self.array_size1 = epics_signal_r(int, prefix + "ArraySize1_RBV")
|
|
25
34
|
super().__init__(prefix, name)
|
|
26
35
|
|
|
27
36
|
|
|
@@ -6,23 +6,28 @@ import random
|
|
|
6
6
|
import string
|
|
7
7
|
import subprocess
|
|
8
8
|
import sys
|
|
9
|
-
import time
|
|
10
9
|
from enum import Enum
|
|
11
10
|
from pathlib import Path
|
|
12
|
-
from typing import Callable, List, Optional
|
|
13
11
|
|
|
14
12
|
import numpy as np
|
|
15
13
|
from bluesky.protocols import Movable, Stoppable
|
|
16
14
|
|
|
17
15
|
from ophyd_async.core import (
|
|
18
|
-
AsyncStatus,
|
|
19
16
|
ConfigSignal,
|
|
20
17
|
Device,
|
|
21
18
|
DeviceVector,
|
|
22
19
|
HintedSignal,
|
|
23
20
|
StandardReadable,
|
|
21
|
+
WatchableAsyncStatus,
|
|
24
22
|
observe_value,
|
|
25
23
|
)
|
|
24
|
+
from ophyd_async.core.async_status import AsyncStatus
|
|
25
|
+
from ophyd_async.core.utils import (
|
|
26
|
+
DEFAULT_TIMEOUT,
|
|
27
|
+
CalculatableTimeout,
|
|
28
|
+
CalculateTimeout,
|
|
29
|
+
WatcherUpdate,
|
|
30
|
+
)
|
|
26
31
|
|
|
27
32
|
from ..signal.signal import epics_signal_r, epics_signal_rw, epics_signal_x
|
|
28
33
|
|
|
@@ -66,11 +71,9 @@ class Mover(StandardReadable, Movable, Stoppable):
|
|
|
66
71
|
# Define some signals
|
|
67
72
|
with self.add_children_as_readables(HintedSignal):
|
|
68
73
|
self.readback = epics_signal_r(float, prefix + "Readback")
|
|
69
|
-
|
|
70
74
|
with self.add_children_as_readables(ConfigSignal):
|
|
71
75
|
self.velocity = epics_signal_rw(float, prefix + "Velocity")
|
|
72
76
|
self.units = epics_signal_r(str, prefix + "Readback.EGU")
|
|
73
|
-
|
|
74
77
|
self.setpoint = epics_signal_rw(float, prefix + "Setpoint")
|
|
75
78
|
self.precision = epics_signal_r(int, prefix + "Readback.PREC")
|
|
76
79
|
# Signals that collide with standard methods should have a trailing underscore
|
|
@@ -85,47 +88,43 @@ class Mover(StandardReadable, Movable, Stoppable):
|
|
|
85
88
|
# Readback should be named the same as its parent in read()
|
|
86
89
|
self.readback.set_name(name)
|
|
87
90
|
|
|
88
|
-
|
|
91
|
+
@WatchableAsyncStatus.wrap
|
|
92
|
+
async def set(
|
|
93
|
+
self, new_position: float, timeout: CalculatableTimeout = CalculateTimeout
|
|
94
|
+
):
|
|
89
95
|
self._set_success = True
|
|
90
|
-
|
|
91
|
-
start = time.monotonic()
|
|
92
|
-
old_position, units, precision = await asyncio.gather(
|
|
96
|
+
old_position, units, precision, velocity = await asyncio.gather(
|
|
93
97
|
self.setpoint.get_value(),
|
|
94
98
|
self.units.get_value(),
|
|
95
99
|
self.precision.get_value(),
|
|
100
|
+
self.velocity.get_value(),
|
|
96
101
|
)
|
|
102
|
+
if timeout is CalculateTimeout:
|
|
103
|
+
assert velocity > 0, "Mover has zero velocity"
|
|
104
|
+
timeout = abs(new_position - old_position) / velocity + DEFAULT_TIMEOUT
|
|
105
|
+
# Make an Event that will be set on completion, and a Status that will
|
|
106
|
+
# error if not done in time
|
|
107
|
+
done = asyncio.Event()
|
|
108
|
+
done_status = AsyncStatus(asyncio.wait_for(done.wait(), timeout))
|
|
97
109
|
# Wait for the value to set, but don't wait for put completion callback
|
|
98
110
|
await self.setpoint.set(new_position, wait=False)
|
|
99
|
-
async for current_position in observe_value(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
111
|
+
async for current_position in observe_value(
|
|
112
|
+
self.readback, done_status=done_status
|
|
113
|
+
):
|
|
114
|
+
yield WatcherUpdate(
|
|
115
|
+
current=current_position,
|
|
116
|
+
initial=old_position,
|
|
117
|
+
target=new_position,
|
|
118
|
+
name=self.name,
|
|
119
|
+
unit=units,
|
|
120
|
+
precision=precision,
|
|
121
|
+
)
|
|
110
122
|
if np.isclose(current_position, new_position):
|
|
123
|
+
done.set()
|
|
111
124
|
break
|
|
112
125
|
if not self._set_success:
|
|
113
126
|
raise RuntimeError("Motor was stopped")
|
|
114
127
|
|
|
115
|
-
def move(self, new_position: float, timeout: Optional[float] = None):
|
|
116
|
-
"""Commandline only synchronous move of a Motor"""
|
|
117
|
-
from bluesky.run_engine import call_in_bluesky_event_loop, in_bluesky_event_loop
|
|
118
|
-
|
|
119
|
-
if in_bluesky_event_loop():
|
|
120
|
-
raise RuntimeError("Will deadlock run engine if run in a plan")
|
|
121
|
-
call_in_bluesky_event_loop(self._move(new_position), timeout) # type: ignore
|
|
122
|
-
|
|
123
|
-
# TODO: this fails if we call from the cli, but works if we "ipython await" it
|
|
124
|
-
def set(self, new_position: float, timeout: Optional[float] = None) -> AsyncStatus:
|
|
125
|
-
watchers: List[Callable] = []
|
|
126
|
-
coro = asyncio.wait_for(self._move(new_position, watchers), timeout=timeout)
|
|
127
|
-
return AsyncStatus(coro, watchers)
|
|
128
|
-
|
|
129
128
|
async def stop(self, success=True):
|
|
130
129
|
self._set_success = success
|
|
131
130
|
status = self.stop_.trigger()
|