ophyd-async 0.3.4a1__py3-none-any.whl → 0.5.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.
- ophyd_async/_version.py +2 -2
- ophyd_async/core/__init__.py +86 -63
- ophyd_async/core/{detector.py → _detector.py} +18 -23
- ophyd_async/core/{device.py → _device.py} +19 -7
- ophyd_async/core/{device_save_loader.py → _device_save_loader.py} +3 -3
- ophyd_async/core/{flyer.py → _flyer.py} +6 -8
- ophyd_async/core/_hdf_dataset.py +97 -0
- ophyd_async/{log.py → core/_log.py} +11 -3
- ophyd_async/core/{mock_signal_backend.py → _mock_signal_backend.py} +3 -3
- ophyd_async/core/{mock_signal_utils.py → _mock_signal_utils.py} +3 -4
- ophyd_async/{protocols.py → core/_protocol.py} +1 -1
- ophyd_async/core/_providers.py +186 -24
- ophyd_async/core/{standard_readable.py → _readable.py} +6 -16
- ophyd_async/core/{signal.py → _signal.py} +39 -16
- ophyd_async/core/{signal_backend.py → _signal_backend.py} +4 -13
- ophyd_async/core/{soft_signal_backend.py → _soft_signal_backend.py} +24 -18
- ophyd_async/core/{async_status.py → _status.py} +3 -11
- ophyd_async/epics/adaravis/__init__.py +9 -0
- ophyd_async/epics/{areadetector/aravis.py → adaravis/_aravis.py} +12 -14
- ophyd_async/epics/{areadetector/controllers/aravis_controller.py → adaravis/_aravis_controller.py} +8 -10
- ophyd_async/epics/{areadetector/drivers/aravis_driver.py → adaravis/_aravis_io.py} +6 -3
- ophyd_async/epics/adcore/__init__.py +36 -0
- ophyd_async/epics/adcore/_core_io.py +114 -0
- ophyd_async/epics/{areadetector/drivers/ad_base.py → adcore/_core_logic.py} +17 -52
- ophyd_async/epics/{areadetector/writers/hdf_writer.py → adcore/_hdf_writer.py} +36 -18
- ophyd_async/epics/{areadetector/single_trigger_det.py → adcore/_single_trigger.py} +5 -6
- ophyd_async/epics/{areadetector/utils.py → adcore/_utils.py} +29 -0
- ophyd_async/epics/adkinetix/__init__.py +9 -0
- ophyd_async/epics/{areadetector/kinetix.py → adkinetix/_kinetix.py} +12 -14
- ophyd_async/epics/{areadetector/controllers/kinetix_controller.py → adkinetix/_kinetix_controller.py} +6 -9
- ophyd_async/epics/{areadetector/drivers/kinetix_driver.py → adkinetix/_kinetix_io.py} +5 -4
- ophyd_async/epics/adpilatus/__init__.py +11 -0
- ophyd_async/epics/{areadetector/pilatus.py → adpilatus/_pilatus.py} +12 -16
- ophyd_async/epics/{areadetector/controllers/pilatus_controller.py → adpilatus/_pilatus_controller.py} +14 -16
- ophyd_async/epics/{areadetector/drivers/pilatus_driver.py → adpilatus/_pilatus_io.py} +5 -3
- ophyd_async/epics/adsimdetector/__init__.py +7 -0
- ophyd_async/epics/adsimdetector/_sim.py +34 -0
- ophyd_async/epics/{areadetector/controllers/ad_sim_controller.py → adsimdetector/_sim_controller.py} +8 -14
- ophyd_async/epics/advimba/__init__.py +9 -0
- ophyd_async/epics/advimba/_vimba.py +43 -0
- ophyd_async/epics/{areadetector/controllers/vimba_controller.py → advimba/_vimba_controller.py} +6 -14
- ophyd_async/epics/{areadetector/drivers/vimba_driver.py → advimba/_vimba_io.py} +5 -4
- ophyd_async/epics/demo/__init__.py +9 -132
- ophyd_async/epics/demo/_mover.py +97 -0
- ophyd_async/epics/demo/_sensor.py +36 -0
- ophyd_async/epics/motor.py +228 -0
- ophyd_async/epics/pvi/__init__.py +2 -2
- ophyd_async/epics/pvi/{pvi.py → _pvi.py} +17 -14
- ophyd_async/epics/signal/__init__.py +7 -1
- ophyd_async/epics/{_backend → signal}/_aioca.py +6 -2
- ophyd_async/epics/{_backend/common.py → signal/_common.py} +4 -2
- ophyd_async/epics/signal/_epics_transport.py +3 -3
- ophyd_async/epics/{_backend → signal}/_p4p.py +53 -4
- ophyd_async/epics/signal/{signal.py → _signal.py} +10 -9
- ophyd_async/fastcs/odin/__init__.py +0 -0
- ophyd_async/{panda → fastcs/panda}/__init__.py +28 -9
- ophyd_async/{panda → fastcs/panda}/_common_blocks.py +24 -3
- ophyd_async/{panda → fastcs/panda}/_hdf_panda.py +6 -9
- ophyd_async/{panda/writers → fastcs/panda}/_hdf_writer.py +24 -14
- ophyd_async/{panda → fastcs/panda}/_panda_controller.py +2 -1
- ophyd_async/{panda → fastcs/panda}/_table.py +20 -18
- ophyd_async/fastcs/panda/_trigger.py +90 -0
- ophyd_async/plan_stubs/__init__.py +2 -2
- ophyd_async/plan_stubs/_ensure_connected.py +26 -0
- ophyd_async/plan_stubs/{fly.py → _fly.py} +67 -12
- ophyd_async/sim/__init__.py +0 -11
- ophyd_async/sim/demo/__init__.py +18 -2
- ophyd_async/sim/demo/_pattern_detector/__init__.py +13 -0
- ophyd_async/sim/demo/_pattern_detector/_pattern_detector.py +42 -0
- ophyd_async/sim/{sim_pattern_detector_control.py → demo/_pattern_detector/_pattern_detector_controller.py} +6 -7
- ophyd_async/sim/{sim_pattern_detector_writer.py → demo/_pattern_detector/_pattern_detector_writer.py} +12 -8
- ophyd_async/sim/demo/_pattern_detector/_pattern_generator.py +211 -0
- ophyd_async/sim/demo/{sim_motor.py → _sim_motor.py} +7 -5
- ophyd_async/sim/testing/__init__.py +0 -0
- ophyd_async/tango/__init__.py +0 -0
- {ophyd_async-0.3.4a1.dist-info → ophyd_async-0.5.0.dist-info}/METADATA +7 -2
- ophyd_async-0.5.0.dist-info/RECORD +89 -0
- {ophyd_async-0.3.4a1.dist-info → ophyd_async-0.5.0.dist-info}/WHEEL +1 -1
- ophyd_async/epics/areadetector/__init__.py +0 -23
- ophyd_async/epics/areadetector/controllers/__init__.py +0 -5
- ophyd_async/epics/areadetector/drivers/__init__.py +0 -23
- ophyd_async/epics/areadetector/vimba.py +0 -43
- ophyd_async/epics/areadetector/writers/__init__.py +0 -5
- ophyd_async/epics/areadetector/writers/_hdfdataset.py +0 -10
- ophyd_async/epics/areadetector/writers/_hdffile.py +0 -54
- ophyd_async/epics/areadetector/writers/nd_file_hdf.py +0 -40
- ophyd_async/epics/areadetector/writers/nd_plugin.py +0 -38
- ophyd_async/epics/demo/demo_ad_sim_detector.py +0 -35
- ophyd_async/epics/motion/__init__.py +0 -3
- ophyd_async/epics/motion/motor.py +0 -97
- ophyd_async/panda/_trigger.py +0 -39
- ophyd_async/panda/writers/__init__.py +0 -3
- ophyd_async/panda/writers/_panda_hdf_file.py +0 -54
- ophyd_async/plan_stubs/ensure_connected.py +0 -22
- ophyd_async/sim/pattern_generator.py +0 -318
- ophyd_async/sim/sim_pattern_generator.py +0 -35
- ophyd_async-0.3.4a1.dist-info/RECORD +0 -86
- /ophyd_async/core/{utils.py → _utils.py} +0 -0
- /ophyd_async/{epics/_backend → fastcs}/__init__.py +0 -0
- /ophyd_async/{panda → fastcs/panda}/_utils.py +0 -0
- {ophyd_async-0.3.4a1.dist-info → ophyd_async-0.5.0.dist-info}/LICENSE +0 -0
- {ophyd_async-0.3.4a1.dist-info → ophyd_async-0.5.0.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.3.4a1.dist-info → ophyd_async-0.5.0.dist-info}/top_level.txt +0 -0
ophyd_async/core/_providers.py
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import uuid
|
|
1
3
|
from abc import abstractmethod
|
|
4
|
+
from collections.abc import Callable
|
|
2
5
|
from dataclasses import dataclass
|
|
6
|
+
from datetime import date
|
|
3
7
|
from pathlib import Path
|
|
4
|
-
from typing import Optional, Protocol
|
|
8
|
+
from typing import List, Optional, Protocol
|
|
5
9
|
|
|
6
10
|
|
|
7
11
|
@dataclass
|
|
8
|
-
class
|
|
12
|
+
class PathInfo:
|
|
9
13
|
"""
|
|
10
14
|
Information about where and how to write a file.
|
|
11
15
|
|
|
@@ -17,43 +21,201 @@ class DirectoryInfo:
|
|
|
17
21
|
|
|
18
22
|
:param root: Path of a root directory, relevant only for the file writer
|
|
19
23
|
:param resource_dir: Directory into which files should be written, relative to root
|
|
20
|
-
:param
|
|
21
|
-
:param
|
|
24
|
+
:param filename: Base filename to use generated by FilenameProvider, w/o extension
|
|
25
|
+
:param create_dir_depth: Optional depth of directories to create if they do not
|
|
26
|
+
exist
|
|
22
27
|
"""
|
|
23
28
|
|
|
24
29
|
root: Path
|
|
25
30
|
resource_dir: Path
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
filename: str
|
|
32
|
+
create_dir_depth: int = 0
|
|
28
33
|
|
|
29
34
|
|
|
30
|
-
class
|
|
35
|
+
class FilenameProvider(Protocol):
|
|
31
36
|
@abstractmethod
|
|
32
|
-
def __call__(self) ->
|
|
37
|
+
def __call__(self) -> str:
|
|
38
|
+
"""Get a filename to use for output data, w/o extension"""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class PathProvider(Protocol):
|
|
42
|
+
_filename_provider: FilenameProvider
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def __call__(self, device_name: Optional[str] = None) -> PathInfo:
|
|
33
46
|
"""Get the current directory to write files into"""
|
|
34
47
|
|
|
35
48
|
|
|
36
|
-
class
|
|
49
|
+
class StaticFilenameProvider(FilenameProvider):
|
|
50
|
+
def __init__(self, filename: str):
|
|
51
|
+
self._static_filename = filename
|
|
52
|
+
|
|
53
|
+
def __call__(self) -> str:
|
|
54
|
+
return self._static_filename
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class UUIDFilenameProvider(FilenameProvider):
|
|
37
58
|
def __init__(
|
|
38
59
|
self,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
60
|
+
uuid_call_func: Callable = uuid.uuid4,
|
|
61
|
+
uuid_call_args: Optional[List] = None,
|
|
62
|
+
):
|
|
63
|
+
self._uuid_call_func = uuid_call_func
|
|
64
|
+
self._uuid_call_args = uuid_call_args or []
|
|
65
|
+
|
|
66
|
+
def __call__(self) -> str:
|
|
67
|
+
if (
|
|
68
|
+
self._uuid_call_func in [uuid.uuid3, uuid.uuid5]
|
|
69
|
+
and len(self._uuid_call_args) < 2
|
|
70
|
+
):
|
|
71
|
+
raise ValueError(
|
|
72
|
+
f"To use {self._uuid_call_func} to generate UUID filenames,"
|
|
73
|
+
" UUID namespace and name must be passed as args!"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
uuid_str = self._uuid_call_func(*self._uuid_call_args)
|
|
77
|
+
return f"{uuid_str}"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class AutoIncrementFilenameProvider(FilenameProvider):
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
base_filename: str = "",
|
|
84
|
+
max_digits: int = 5,
|
|
85
|
+
starting_value: int = 0,
|
|
86
|
+
increment: int = 1,
|
|
87
|
+
inc_delimeter: str = "_",
|
|
88
|
+
):
|
|
89
|
+
self._base_filename = base_filename
|
|
90
|
+
self._max_digits = max_digits
|
|
91
|
+
self._current_value = starting_value
|
|
92
|
+
self._increment = increment
|
|
93
|
+
self._inc_delimeter = inc_delimeter
|
|
94
|
+
|
|
95
|
+
def __call__(self):
|
|
96
|
+
if len(str(self._current_value)) > self._max_digits:
|
|
97
|
+
raise ValueError(
|
|
98
|
+
f"Auto incrementing filename counter \
|
|
99
|
+
exceeded maximum of {self._max_digits} digits!"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
padded_counter = f"{self._current_value:0{self._max_digits}}"
|
|
103
|
+
|
|
104
|
+
filename = f"{self._base_filename}{self._inc_delimeter}{padded_counter}"
|
|
105
|
+
|
|
106
|
+
self._current_value += self._increment
|
|
107
|
+
return filename
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class StaticPathProvider(PathProvider):
|
|
111
|
+
def __init__(
|
|
112
|
+
self,
|
|
113
|
+
filename_provider: FilenameProvider,
|
|
114
|
+
directory_path: Path,
|
|
115
|
+
resource_dir: Path = Path("."),
|
|
116
|
+
create_dir_depth: int = 0,
|
|
43
117
|
) -> None:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
118
|
+
self._filename_provider = filename_provider
|
|
119
|
+
self._directory_path = directory_path
|
|
120
|
+
self._resource_dir = resource_dir
|
|
121
|
+
self._create_dir_depth = create_dir_depth
|
|
122
|
+
|
|
123
|
+
def __call__(self, device_name: Optional[str] = None) -> PathInfo:
|
|
124
|
+
filename = self._filename_provider()
|
|
125
|
+
|
|
126
|
+
return PathInfo(
|
|
127
|
+
root=self._directory_path,
|
|
128
|
+
resource_dir=self._resource_dir,
|
|
129
|
+
filename=filename,
|
|
130
|
+
create_dir_depth=self._create_dir_depth,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class AutoIncrementingPathProvider(PathProvider):
|
|
135
|
+
def __init__(
|
|
136
|
+
self,
|
|
137
|
+
filename_provider: FilenameProvider,
|
|
138
|
+
directory_path: Path,
|
|
139
|
+
create_dir_depth: int = 0,
|
|
140
|
+
max_digits: int = 5,
|
|
141
|
+
starting_value: int = 0,
|
|
142
|
+
num_calls_per_inc: int = 1,
|
|
143
|
+
increment: int = 1,
|
|
144
|
+
inc_delimeter: str = "_",
|
|
145
|
+
base_name: str = None,
|
|
146
|
+
) -> None:
|
|
147
|
+
self._filename_provider = filename_provider
|
|
148
|
+
self._directory_path = directory_path
|
|
149
|
+
self._create_dir_depth = create_dir_depth
|
|
150
|
+
self._base_name = base_name
|
|
151
|
+
self._starting_value = starting_value
|
|
152
|
+
self._current_value = starting_value
|
|
153
|
+
self._num_calls_per_inc = num_calls_per_inc
|
|
154
|
+
self._inc_counter = 0
|
|
155
|
+
self._max_digits = max_digits
|
|
156
|
+
self._increment = increment
|
|
157
|
+
self._inc_delimeter = inc_delimeter
|
|
158
|
+
|
|
159
|
+
def __call__(self, device_name: Optional[str] = None) -> PathInfo:
|
|
160
|
+
filename = self._filename_provider()
|
|
161
|
+
|
|
162
|
+
padded_counter = f"{self._current_value:0{self._max_digits}}"
|
|
163
|
+
|
|
164
|
+
resource_dir = str(padded_counter)
|
|
165
|
+
if self._base_name is not None:
|
|
166
|
+
resource_dir = f"{self._base_name}{self._inc_delimeter}{padded_counter}"
|
|
167
|
+
elif device_name is not None:
|
|
168
|
+
resource_dir = f"{device_name}{self._inc_delimeter}{padded_counter}"
|
|
169
|
+
|
|
170
|
+
self._inc_counter += 1
|
|
171
|
+
if self._inc_counter == self._num_calls_per_inc:
|
|
172
|
+
self._inc_counter = 0
|
|
173
|
+
self._current_value += self._increment
|
|
174
|
+
|
|
175
|
+
return PathInfo(
|
|
176
|
+
root=self._directory_path,
|
|
50
177
|
resource_dir=resource_dir,
|
|
51
|
-
|
|
52
|
-
|
|
178
|
+
filename=filename,
|
|
179
|
+
create_dir_depth=self._create_dir_depth,
|
|
53
180
|
)
|
|
54
181
|
|
|
55
|
-
|
|
56
|
-
|
|
182
|
+
|
|
183
|
+
class YMDPathProvider(PathProvider):
|
|
184
|
+
def __init__(
|
|
185
|
+
self,
|
|
186
|
+
filename_provider: FilenameProvider,
|
|
187
|
+
directory_path: Path,
|
|
188
|
+
create_dir_depth: int = -3, # Default to -3 to create YMD dirs
|
|
189
|
+
device_name_as_base_dir: bool = False,
|
|
190
|
+
) -> None:
|
|
191
|
+
self._filename_provider = filename_provider
|
|
192
|
+
self._directory_path = Path(directory_path)
|
|
193
|
+
self._create_dir_depth = create_dir_depth
|
|
194
|
+
self._device_name_as_base_dir = device_name_as_base_dir
|
|
195
|
+
|
|
196
|
+
def __call__(self, device_name: Optional[str] = None) -> PathInfo:
|
|
197
|
+
sep = os.path.sep
|
|
198
|
+
current_date = date.today().strftime(f"%Y{sep}%m{sep}%d")
|
|
199
|
+
if device_name is None:
|
|
200
|
+
resource_dir = current_date
|
|
201
|
+
elif self._device_name_as_base_dir:
|
|
202
|
+
resource_dir = os.path.join(
|
|
203
|
+
current_date,
|
|
204
|
+
device_name,
|
|
205
|
+
)
|
|
206
|
+
else:
|
|
207
|
+
resource_dir = os.path.join(
|
|
208
|
+
device_name,
|
|
209
|
+
current_date,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
filename = self._filename_provider()
|
|
213
|
+
return PathInfo(
|
|
214
|
+
root=self._directory_path,
|
|
215
|
+
resource_dir=resource_dir,
|
|
216
|
+
filename=filename,
|
|
217
|
+
create_dir_depth=self._create_dir_depth,
|
|
218
|
+
)
|
|
57
219
|
|
|
58
220
|
|
|
59
221
|
class NameProvider(Protocol):
|
|
@@ -64,5 +226,5 @@ class NameProvider(Protocol):
|
|
|
64
226
|
|
|
65
227
|
class ShapeProvider(Protocol):
|
|
66
228
|
@abstractmethod
|
|
67
|
-
async def __call__(self) ->
|
|
229
|
+
async def __call__(self) -> tuple:
|
|
68
230
|
"""Get the shape of the data collection"""
|
|
@@ -1,24 +1,14 @@
|
|
|
1
1
|
import warnings
|
|
2
2
|
from contextlib import contextmanager
|
|
3
|
-
from typing import
|
|
4
|
-
Callable,
|
|
5
|
-
Dict,
|
|
6
|
-
Generator,
|
|
7
|
-
Optional,
|
|
8
|
-
Sequence,
|
|
9
|
-
Tuple,
|
|
10
|
-
Type,
|
|
11
|
-
Union,
|
|
12
|
-
)
|
|
3
|
+
from typing import Callable, Dict, Generator, Optional, Sequence, Tuple, Type, Union
|
|
13
4
|
|
|
14
5
|
from bluesky.protocols import DataKey, HasHints, Hints, Reading
|
|
15
6
|
|
|
16
|
-
from
|
|
17
|
-
|
|
18
|
-
from .
|
|
19
|
-
from .
|
|
20
|
-
from .
|
|
21
|
-
from .utils import merge_gathered_dicts
|
|
7
|
+
from ._device import Device, DeviceVector
|
|
8
|
+
from ._protocol import AsyncConfigurable, AsyncReadable, AsyncStageable
|
|
9
|
+
from ._signal import SignalR
|
|
10
|
+
from ._status import AsyncStatus
|
|
11
|
+
from ._utils import merge_gathered_dicts
|
|
22
12
|
|
|
23
13
|
ReadableChild = Union[AsyncReadable, AsyncConfigurable, AsyncStageable, HasHints]
|
|
24
14
|
ReadableChildWrapper = Union[
|
|
@@ -25,14 +25,13 @@ from bluesky.protocols import (
|
|
|
25
25
|
Subscribable,
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
-
from
|
|
29
|
-
from
|
|
30
|
-
|
|
31
|
-
from .
|
|
32
|
-
from .
|
|
33
|
-
from .
|
|
34
|
-
from .
|
|
35
|
-
from .utils import DEFAULT_TIMEOUT, CalculatableTimeout, CalculateTimeout, Callback, T
|
|
28
|
+
from ._device import Device
|
|
29
|
+
from ._mock_signal_backend import MockSignalBackend
|
|
30
|
+
from ._protocol import AsyncConfigurable, AsyncReadable, AsyncStageable
|
|
31
|
+
from ._signal_backend import SignalBackend
|
|
32
|
+
from ._soft_signal_backend import SignalMetadata, SoftSignalBackend
|
|
33
|
+
from ._status import AsyncStatus
|
|
34
|
+
from ._utils import DEFAULT_TIMEOUT, CalculatableTimeout, CalculateTimeout, Callback, T
|
|
36
35
|
|
|
37
36
|
|
|
38
37
|
def _add_timeout(func):
|
|
@@ -62,7 +61,7 @@ class Signal(Device, Generic[T]):
|
|
|
62
61
|
name: str = "",
|
|
63
62
|
) -> None:
|
|
64
63
|
self._timeout = timeout
|
|
65
|
-
self.
|
|
64
|
+
self._backend = backend
|
|
66
65
|
super().__init__(name)
|
|
67
66
|
|
|
68
67
|
async def connect(
|
|
@@ -73,19 +72,43 @@ class Signal(Device, Generic[T]):
|
|
|
73
72
|
backend: Optional[SignalBackend[T]] = None,
|
|
74
73
|
):
|
|
75
74
|
if backend:
|
|
76
|
-
if self.
|
|
77
|
-
raise ValueError(
|
|
78
|
-
|
|
79
|
-
)
|
|
75
|
+
if self._backend and backend is not self._backend:
|
|
76
|
+
raise ValueError("Backend at connection different from previous one.")
|
|
77
|
+
|
|
80
78
|
self._backend = backend
|
|
81
|
-
if
|
|
79
|
+
if (
|
|
80
|
+
self._previous_connect_was_mock is not None
|
|
81
|
+
and self._previous_connect_was_mock != mock
|
|
82
|
+
):
|
|
83
|
+
raise RuntimeError(
|
|
84
|
+
f"`connect(mock={mock})` called on a `Signal` where the previous "
|
|
85
|
+
f"connect was `mock={self._previous_connect_was_mock}`. Changing mock "
|
|
86
|
+
"value between connects is not permitted."
|
|
87
|
+
)
|
|
88
|
+
self._previous_connect_was_mock = mock
|
|
89
|
+
|
|
90
|
+
if mock and not issubclass(type(self._backend), MockSignalBackend):
|
|
82
91
|
# Using a soft backend, look to the initial value
|
|
83
92
|
self._backend = MockSignalBackend(initial_backend=self._backend)
|
|
84
93
|
|
|
85
94
|
if self._backend is None:
|
|
86
95
|
raise RuntimeError("`connect` called on signal without backend")
|
|
87
|
-
|
|
88
|
-
|
|
96
|
+
|
|
97
|
+
can_use_previous_connection: bool = self._connect_task is not None and not (
|
|
98
|
+
self._connect_task.done() and self._connect_task.exception()
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if force_reconnect or not can_use_previous_connection:
|
|
102
|
+
self.log.debug(f"Connecting to {self.source}")
|
|
103
|
+
self._connect_task = asyncio.create_task(
|
|
104
|
+
self._backend.connect(timeout=timeout)
|
|
105
|
+
)
|
|
106
|
+
else:
|
|
107
|
+
self.log.debug(f"Reusing previous connection to {self.source}")
|
|
108
|
+
assert (
|
|
109
|
+
self._connect_task
|
|
110
|
+
), "this assert is for type analysis and will never fail"
|
|
111
|
+
await self._connect_task
|
|
89
112
|
|
|
90
113
|
@property
|
|
91
114
|
def source(self) -> str:
|
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
from abc import abstractmethod
|
|
2
|
-
from typing import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Literal,
|
|
7
|
-
Optional,
|
|
8
|
-
Tuple,
|
|
9
|
-
Type,
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
from bluesky.protocols import DataKey, Reading
|
|
13
|
-
|
|
14
|
-
from .utils import DEFAULT_TIMEOUT, ReadingValueCallback, T
|
|
2
|
+
from typing import TYPE_CHECKING, ClassVar, Generic, Literal, Optional, Tuple, Type
|
|
3
|
+
|
|
4
|
+
from ._protocol import DataKey, Reading
|
|
5
|
+
from ._utils import DEFAULT_TIMEOUT, ReadingValueCallback, T
|
|
15
6
|
|
|
16
7
|
|
|
17
8
|
class SignalBackend(Generic[T]):
|
|
@@ -4,23 +4,14 @@ import inspect
|
|
|
4
4
|
import time
|
|
5
5
|
from collections import abc
|
|
6
6
|
from enum import Enum
|
|
7
|
-
from typing import
|
|
8
|
-
Dict,
|
|
9
|
-
Generic,
|
|
10
|
-
Optional,
|
|
11
|
-
Tuple,
|
|
12
|
-
Type,
|
|
13
|
-
TypedDict,
|
|
14
|
-
Union,
|
|
15
|
-
cast,
|
|
16
|
-
get_origin,
|
|
17
|
-
)
|
|
7
|
+
from typing import Dict, Generic, Optional, Tuple, Type, Union, cast, get_origin
|
|
18
8
|
|
|
19
9
|
import numpy as np
|
|
20
10
|
from bluesky.protocols import DataKey, Dtype, Reading
|
|
11
|
+
from typing_extensions import TypedDict
|
|
21
12
|
|
|
22
|
-
from .
|
|
23
|
-
from .
|
|
13
|
+
from ._signal_backend import RuntimeSubsetEnum, SignalBackend
|
|
14
|
+
from ._utils import DEFAULT_TIMEOUT, ReadingValueCallback, T, get_dtype
|
|
24
15
|
|
|
25
16
|
primitive_dtypes: Dict[type, Dtype] = {
|
|
26
17
|
str: "string",
|
|
@@ -60,6 +51,10 @@ class SoftConverter(Generic[T]):
|
|
|
60
51
|
dtype in primitive_dtypes
|
|
61
52
|
), f"invalid converter for value of type {type(value)}"
|
|
62
53
|
dk["dtype"] = primitive_dtypes[dtype]
|
|
54
|
+
try:
|
|
55
|
+
dk["dtype_numpy"] = np.dtype(dtype).descr[0][1]
|
|
56
|
+
except TypeError:
|
|
57
|
+
dk["dtype_numpy"] = ""
|
|
63
58
|
return dk
|
|
64
59
|
|
|
65
60
|
def make_initial_value(self, datatype: Optional[Type[T]]) -> T:
|
|
@@ -71,7 +66,20 @@ class SoftConverter(Generic[T]):
|
|
|
71
66
|
|
|
72
67
|
class SoftArrayConverter(SoftConverter):
|
|
73
68
|
def get_datakey(self, source: str, value, **metadata) -> DataKey:
|
|
74
|
-
|
|
69
|
+
dtype_numpy = ""
|
|
70
|
+
if isinstance(value, list):
|
|
71
|
+
if len(value) > 0:
|
|
72
|
+
dtype_numpy = np.dtype(type(value[0])).descr[0][1]
|
|
73
|
+
else:
|
|
74
|
+
dtype_numpy = np.dtype(value.dtype).descr[0][1]
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
"source": source,
|
|
78
|
+
"dtype": "array",
|
|
79
|
+
"dtype_numpy": dtype_numpy,
|
|
80
|
+
"shape": [len(value)],
|
|
81
|
+
**metadata,
|
|
82
|
+
}
|
|
75
83
|
|
|
76
84
|
def make_initial_value(self, datatype: Optional[Type[T]]) -> T:
|
|
77
85
|
if datatype is None:
|
|
@@ -93,15 +101,13 @@ class SoftEnumConverter(SoftConverter):
|
|
|
93
101
|
self.choices = datatype.choices
|
|
94
102
|
|
|
95
103
|
def write_value(self, value: Union[Enum, str]) -> str:
|
|
96
|
-
|
|
97
|
-
return value.value
|
|
98
|
-
else: # Runtime enum
|
|
99
|
-
return value
|
|
104
|
+
return value
|
|
100
105
|
|
|
101
106
|
def get_datakey(self, source: str, value, **metadata) -> DataKey:
|
|
102
107
|
return {
|
|
103
108
|
"source": source,
|
|
104
109
|
"dtype": "string",
|
|
110
|
+
"dtype_numpy": "|S40",
|
|
105
111
|
"shape": [],
|
|
106
112
|
"choices": self.choices,
|
|
107
113
|
**metadata,
|
|
@@ -4,20 +4,12 @@ import asyncio
|
|
|
4
4
|
import functools
|
|
5
5
|
import time
|
|
6
6
|
from dataclasses import asdict, replace
|
|
7
|
-
from typing import
|
|
8
|
-
AsyncIterator,
|
|
9
|
-
Awaitable,
|
|
10
|
-
Callable,
|
|
11
|
-
Generic,
|
|
12
|
-
Type,
|
|
13
|
-
TypeVar,
|
|
14
|
-
cast,
|
|
15
|
-
)
|
|
7
|
+
from typing import AsyncIterator, Awaitable, Callable, Generic, Type, TypeVar, cast
|
|
16
8
|
|
|
17
9
|
from bluesky.protocols import Status
|
|
18
10
|
|
|
19
|
-
from
|
|
20
|
-
from .
|
|
11
|
+
from ._protocol import Watcher
|
|
12
|
+
from ._utils import Callback, P, T, WatcherUpdate
|
|
21
13
|
|
|
22
14
|
AS = TypeVar("AS", bound="AsyncStatus")
|
|
23
15
|
WAS = TypeVar("WAS", bound="WatchableAsyncStatus")
|
|
@@ -2,13 +2,11 @@ from typing import get_args
|
|
|
2
2
|
|
|
3
3
|
from bluesky.protocols import HasHints, Hints
|
|
4
4
|
|
|
5
|
-
from ophyd_async.core import
|
|
6
|
-
from ophyd_async.epics
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
from
|
|
10
|
-
from ophyd_async.epics.areadetector.drivers.aravis_driver import AravisDriver
|
|
11
|
-
from ophyd_async.epics.areadetector.writers import HDFWriter, NDFileHDF
|
|
5
|
+
from ophyd_async.core import PathProvider, StandardDetector
|
|
6
|
+
from ophyd_async.epics import adcore
|
|
7
|
+
|
|
8
|
+
from ._aravis_controller import AravisController
|
|
9
|
+
from ._aravis_io import AravisDriverIO
|
|
12
10
|
|
|
13
11
|
|
|
14
12
|
class AravisDetector(StandardDetector, HasHints):
|
|
@@ -19,27 +17,27 @@ class AravisDetector(StandardDetector, HasHints):
|
|
|
19
17
|
"""
|
|
20
18
|
|
|
21
19
|
_controller: AravisController
|
|
22
|
-
_writer:
|
|
20
|
+
_writer: adcore.ADHDFWriter
|
|
23
21
|
|
|
24
22
|
def __init__(
|
|
25
23
|
self,
|
|
26
24
|
prefix: str,
|
|
27
|
-
|
|
25
|
+
path_provider: PathProvider,
|
|
28
26
|
drv_suffix="cam1:",
|
|
29
27
|
hdf_suffix="HDF1:",
|
|
30
28
|
name="",
|
|
31
29
|
gpio_number: AravisController.GPIO_NUMBER = 1,
|
|
32
30
|
):
|
|
33
|
-
self.drv =
|
|
34
|
-
self.hdf =
|
|
31
|
+
self.drv = AravisDriverIO(prefix + drv_suffix)
|
|
32
|
+
self.hdf = adcore.NDFileHDFIO(prefix + hdf_suffix)
|
|
35
33
|
|
|
36
34
|
super().__init__(
|
|
37
35
|
AravisController(self.drv, gpio_number=gpio_number),
|
|
38
|
-
|
|
36
|
+
adcore.ADHDFWriter(
|
|
39
37
|
self.hdf,
|
|
40
|
-
|
|
38
|
+
path_provider,
|
|
41
39
|
lambda: self.name,
|
|
42
|
-
ADBaseShapeProvider(self.drv),
|
|
40
|
+
adcore.ADBaseShapeProvider(self.drv),
|
|
43
41
|
),
|
|
44
42
|
config_sigs=(self.drv.acquire_time,),
|
|
45
43
|
name=name,
|
ophyd_async/epics/{areadetector/controllers/aravis_controller.py → adaravis/_aravis_controller.py}
RENAMED
|
@@ -7,12 +7,9 @@ from ophyd_async.core import (
|
|
|
7
7
|
DetectorTrigger,
|
|
8
8
|
set_and_wait_for_value,
|
|
9
9
|
)
|
|
10
|
-
from ophyd_async.epics
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
AravisTriggerSource,
|
|
14
|
-
)
|
|
15
|
-
from ophyd_async.epics.areadetector.utils import ImageMode, stop_busy_record
|
|
10
|
+
from ophyd_async.epics import adcore
|
|
11
|
+
|
|
12
|
+
from ._aravis_io import AravisDriverIO, AravisTriggerMode, AravisTriggerSource
|
|
16
13
|
|
|
17
14
|
# The deadtime of an ADaravis controller varies depending on the exact model of camera.
|
|
18
15
|
# Ideally we would maximize performance by dynamically retrieving the deadtime at
|
|
@@ -23,7 +20,7 @@ _HIGHEST_POSSIBLE_DEADTIME = 1961e-6
|
|
|
23
20
|
class AravisController(DetectorControl):
|
|
24
21
|
GPIO_NUMBER = Literal[1, 2, 3, 4]
|
|
25
22
|
|
|
26
|
-
def __init__(self, driver:
|
|
23
|
+
def __init__(self, driver: AravisDriverIO, gpio_number: GPIO_NUMBER) -> None:
|
|
27
24
|
self._drv = driver
|
|
28
25
|
self.gpio_number = gpio_number
|
|
29
26
|
|
|
@@ -37,9 +34,9 @@ class AravisController(DetectorControl):
|
|
|
37
34
|
exposure: Optional[float] = None,
|
|
38
35
|
) -> AsyncStatus:
|
|
39
36
|
if num == 0:
|
|
40
|
-
image_mode = ImageMode.continuous
|
|
37
|
+
image_mode = adcore.ImageMode.continuous
|
|
41
38
|
else:
|
|
42
|
-
image_mode = ImageMode.multiple
|
|
39
|
+
image_mode = adcore.ImageMode.multiple
|
|
43
40
|
if exposure is not None:
|
|
44
41
|
await self._drv.acquire_time.set(exposure)
|
|
45
42
|
|
|
@@ -62,6 +59,7 @@ class AravisController(DetectorControl):
|
|
|
62
59
|
supported_trigger_types = (
|
|
63
60
|
DetectorTrigger.constant_gate,
|
|
64
61
|
DetectorTrigger.edge_trigger,
|
|
62
|
+
DetectorTrigger.internal,
|
|
65
63
|
)
|
|
66
64
|
if trigger not in supported_trigger_types:
|
|
67
65
|
raise ValueError(
|
|
@@ -75,4 +73,4 @@ class AravisController(DetectorControl):
|
|
|
75
73
|
return (AravisTriggerMode.on, f"Line{self.gpio_number}")
|
|
76
74
|
|
|
77
75
|
async def disarm(self):
|
|
78
|
-
await stop_busy_record(self._drv.acquire, False, timeout=1)
|
|
76
|
+
await adcore.stop_busy_record(self._drv.acquire, False, timeout=1)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
2
|
|
|
3
3
|
from ophyd_async.core import SubsetEnum
|
|
4
|
-
from ophyd_async.epics
|
|
5
|
-
from ophyd_async.epics.signal
|
|
4
|
+
from ophyd_async.epics import adcore
|
|
5
|
+
from ophyd_async.epics.signal import epics_signal_rw_rbv
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class AravisTriggerMode(str, Enum):
|
|
@@ -22,12 +22,15 @@ class AravisTriggerMode(str, Enum):
|
|
|
22
22
|
AravisTriggerSource = SubsetEnum["Freerun", "Line1"]
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
class
|
|
25
|
+
class AravisDriverIO(adcore.ADBaseIO):
|
|
26
26
|
# If instantiating a new instance, ensure it is supported in the _deadtimes dict
|
|
27
27
|
"""Generic Driver supporting the Manta and Mako drivers.
|
|
28
28
|
Fetches deadtime prior to use in a Streaming scan.
|
|
29
|
+
|
|
29
30
|
Requires driver firmware up to date:
|
|
30
31
|
- Model_RBV must be of the form "^(Mako|Manta) (model)$"
|
|
32
|
+
|
|
33
|
+
This mirrors the interface provided by ADAravis/db/aravisCamera.template.
|
|
31
34
|
"""
|
|
32
35
|
|
|
33
36
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from ._core_io import ADBaseIO, DetectorState, NDFileHDFIO, NDPluginStatsIO
|
|
2
|
+
from ._core_logic import (
|
|
3
|
+
DEFAULT_GOOD_STATES,
|
|
4
|
+
ADBaseShapeProvider,
|
|
5
|
+
set_exposure_time_and_acquire_period_if_supplied,
|
|
6
|
+
start_acquiring_driver_and_ensure_status,
|
|
7
|
+
)
|
|
8
|
+
from ._hdf_writer import ADHDFWriter
|
|
9
|
+
from ._single_trigger import SingleTriggerDetector
|
|
10
|
+
from ._utils import (
|
|
11
|
+
ADBaseDataType,
|
|
12
|
+
FileWriteMode,
|
|
13
|
+
ImageMode,
|
|
14
|
+
NDAttributeDataType,
|
|
15
|
+
NDAttributesXML,
|
|
16
|
+
stop_busy_record,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"ADBaseIO",
|
|
21
|
+
"DetectorState",
|
|
22
|
+
"NDFileHDFIO",
|
|
23
|
+
"NDPluginStatsIO",
|
|
24
|
+
"DEFAULT_GOOD_STATES",
|
|
25
|
+
"ADBaseShapeProvider",
|
|
26
|
+
"set_exposure_time_and_acquire_period_if_supplied",
|
|
27
|
+
"start_acquiring_driver_and_ensure_status",
|
|
28
|
+
"ADHDFWriter",
|
|
29
|
+
"SingleTriggerDetector",
|
|
30
|
+
"ADBaseDataType",
|
|
31
|
+
"FileWriteMode",
|
|
32
|
+
"ImageMode",
|
|
33
|
+
"NDAttributeDataType",
|
|
34
|
+
"NDAttributesXML",
|
|
35
|
+
"stop_busy_record",
|
|
36
|
+
]
|