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.
Files changed (103) hide show
  1. ophyd_async/_version.py +2 -2
  2. ophyd_async/core/__init__.py +86 -63
  3. ophyd_async/core/{detector.py → _detector.py} +18 -23
  4. ophyd_async/core/{device.py → _device.py} +19 -7
  5. ophyd_async/core/{device_save_loader.py → _device_save_loader.py} +3 -3
  6. ophyd_async/core/{flyer.py → _flyer.py} +6 -8
  7. ophyd_async/core/_hdf_dataset.py +97 -0
  8. ophyd_async/{log.py → core/_log.py} +11 -3
  9. ophyd_async/core/{mock_signal_backend.py → _mock_signal_backend.py} +3 -3
  10. ophyd_async/core/{mock_signal_utils.py → _mock_signal_utils.py} +3 -4
  11. ophyd_async/{protocols.py → core/_protocol.py} +1 -1
  12. ophyd_async/core/_providers.py +186 -24
  13. ophyd_async/core/{standard_readable.py → _readable.py} +6 -16
  14. ophyd_async/core/{signal.py → _signal.py} +39 -16
  15. ophyd_async/core/{signal_backend.py → _signal_backend.py} +4 -13
  16. ophyd_async/core/{soft_signal_backend.py → _soft_signal_backend.py} +24 -18
  17. ophyd_async/core/{async_status.py → _status.py} +3 -11
  18. ophyd_async/epics/adaravis/__init__.py +9 -0
  19. ophyd_async/epics/{areadetector/aravis.py → adaravis/_aravis.py} +12 -14
  20. ophyd_async/epics/{areadetector/controllers/aravis_controller.py → adaravis/_aravis_controller.py} +8 -10
  21. ophyd_async/epics/{areadetector/drivers/aravis_driver.py → adaravis/_aravis_io.py} +6 -3
  22. ophyd_async/epics/adcore/__init__.py +36 -0
  23. ophyd_async/epics/adcore/_core_io.py +114 -0
  24. ophyd_async/epics/{areadetector/drivers/ad_base.py → adcore/_core_logic.py} +17 -52
  25. ophyd_async/epics/{areadetector/writers/hdf_writer.py → adcore/_hdf_writer.py} +36 -18
  26. ophyd_async/epics/{areadetector/single_trigger_det.py → adcore/_single_trigger.py} +5 -6
  27. ophyd_async/epics/{areadetector/utils.py → adcore/_utils.py} +29 -0
  28. ophyd_async/epics/adkinetix/__init__.py +9 -0
  29. ophyd_async/epics/{areadetector/kinetix.py → adkinetix/_kinetix.py} +12 -14
  30. ophyd_async/epics/{areadetector/controllers/kinetix_controller.py → adkinetix/_kinetix_controller.py} +6 -9
  31. ophyd_async/epics/{areadetector/drivers/kinetix_driver.py → adkinetix/_kinetix_io.py} +5 -4
  32. ophyd_async/epics/adpilatus/__init__.py +11 -0
  33. ophyd_async/epics/{areadetector/pilatus.py → adpilatus/_pilatus.py} +12 -16
  34. ophyd_async/epics/{areadetector/controllers/pilatus_controller.py → adpilatus/_pilatus_controller.py} +14 -16
  35. ophyd_async/epics/{areadetector/drivers/pilatus_driver.py → adpilatus/_pilatus_io.py} +5 -3
  36. ophyd_async/epics/adsimdetector/__init__.py +7 -0
  37. ophyd_async/epics/adsimdetector/_sim.py +34 -0
  38. ophyd_async/epics/{areadetector/controllers/ad_sim_controller.py → adsimdetector/_sim_controller.py} +8 -14
  39. ophyd_async/epics/advimba/__init__.py +9 -0
  40. ophyd_async/epics/advimba/_vimba.py +43 -0
  41. ophyd_async/epics/{areadetector/controllers/vimba_controller.py → advimba/_vimba_controller.py} +6 -14
  42. ophyd_async/epics/{areadetector/drivers/vimba_driver.py → advimba/_vimba_io.py} +5 -4
  43. ophyd_async/epics/demo/__init__.py +9 -132
  44. ophyd_async/epics/demo/_mover.py +97 -0
  45. ophyd_async/epics/demo/_sensor.py +36 -0
  46. ophyd_async/epics/motor.py +228 -0
  47. ophyd_async/epics/pvi/__init__.py +2 -2
  48. ophyd_async/epics/pvi/{pvi.py → _pvi.py} +17 -14
  49. ophyd_async/epics/signal/__init__.py +7 -1
  50. ophyd_async/epics/{_backend → signal}/_aioca.py +6 -2
  51. ophyd_async/epics/{_backend/common.py → signal/_common.py} +4 -2
  52. ophyd_async/epics/signal/_epics_transport.py +3 -3
  53. ophyd_async/epics/{_backend → signal}/_p4p.py +53 -4
  54. ophyd_async/epics/signal/{signal.py → _signal.py} +10 -9
  55. ophyd_async/fastcs/odin/__init__.py +0 -0
  56. ophyd_async/{panda → fastcs/panda}/__init__.py +28 -9
  57. ophyd_async/{panda → fastcs/panda}/_common_blocks.py +24 -3
  58. ophyd_async/{panda → fastcs/panda}/_hdf_panda.py +6 -9
  59. ophyd_async/{panda/writers → fastcs/panda}/_hdf_writer.py +24 -14
  60. ophyd_async/{panda → fastcs/panda}/_panda_controller.py +2 -1
  61. ophyd_async/{panda → fastcs/panda}/_table.py +20 -18
  62. ophyd_async/fastcs/panda/_trigger.py +90 -0
  63. ophyd_async/plan_stubs/__init__.py +2 -2
  64. ophyd_async/plan_stubs/_ensure_connected.py +26 -0
  65. ophyd_async/plan_stubs/{fly.py → _fly.py} +67 -12
  66. ophyd_async/sim/__init__.py +0 -11
  67. ophyd_async/sim/demo/__init__.py +18 -2
  68. ophyd_async/sim/demo/_pattern_detector/__init__.py +13 -0
  69. ophyd_async/sim/demo/_pattern_detector/_pattern_detector.py +42 -0
  70. ophyd_async/sim/{sim_pattern_detector_control.py → demo/_pattern_detector/_pattern_detector_controller.py} +6 -7
  71. ophyd_async/sim/{sim_pattern_detector_writer.py → demo/_pattern_detector/_pattern_detector_writer.py} +12 -8
  72. ophyd_async/sim/demo/_pattern_detector/_pattern_generator.py +211 -0
  73. ophyd_async/sim/demo/{sim_motor.py → _sim_motor.py} +7 -5
  74. ophyd_async/sim/testing/__init__.py +0 -0
  75. ophyd_async/tango/__init__.py +0 -0
  76. {ophyd_async-0.3.4a1.dist-info → ophyd_async-0.5.0.dist-info}/METADATA +7 -2
  77. ophyd_async-0.5.0.dist-info/RECORD +89 -0
  78. {ophyd_async-0.3.4a1.dist-info → ophyd_async-0.5.0.dist-info}/WHEEL +1 -1
  79. ophyd_async/epics/areadetector/__init__.py +0 -23
  80. ophyd_async/epics/areadetector/controllers/__init__.py +0 -5
  81. ophyd_async/epics/areadetector/drivers/__init__.py +0 -23
  82. ophyd_async/epics/areadetector/vimba.py +0 -43
  83. ophyd_async/epics/areadetector/writers/__init__.py +0 -5
  84. ophyd_async/epics/areadetector/writers/_hdfdataset.py +0 -10
  85. ophyd_async/epics/areadetector/writers/_hdffile.py +0 -54
  86. ophyd_async/epics/areadetector/writers/nd_file_hdf.py +0 -40
  87. ophyd_async/epics/areadetector/writers/nd_plugin.py +0 -38
  88. ophyd_async/epics/demo/demo_ad_sim_detector.py +0 -35
  89. ophyd_async/epics/motion/__init__.py +0 -3
  90. ophyd_async/epics/motion/motor.py +0 -97
  91. ophyd_async/panda/_trigger.py +0 -39
  92. ophyd_async/panda/writers/__init__.py +0 -3
  93. ophyd_async/panda/writers/_panda_hdf_file.py +0 -54
  94. ophyd_async/plan_stubs/ensure_connected.py +0 -22
  95. ophyd_async/sim/pattern_generator.py +0 -318
  96. ophyd_async/sim/sim_pattern_generator.py +0 -35
  97. ophyd_async-0.3.4a1.dist-info/RECORD +0 -86
  98. /ophyd_async/core/{utils.py → _utils.py} +0 -0
  99. /ophyd_async/{epics/_backend → fastcs}/__init__.py +0 -0
  100. /ophyd_async/{panda → fastcs/panda}/_utils.py +0 -0
  101. {ophyd_async-0.3.4a1.dist-info → ophyd_async-0.5.0.dist-info}/LICENSE +0 -0
  102. {ophyd_async-0.3.4a1.dist-info → ophyd_async-0.5.0.dist-info}/entry_points.txt +0 -0
  103. {ophyd_async-0.3.4a1.dist-info → ophyd_async-0.5.0.dist-info}/top_level.txt +0 -0
@@ -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, Sequence, Union
8
+ from typing import List, Optional, Protocol
5
9
 
6
10
 
7
11
  @dataclass
8
- class DirectoryInfo:
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 prefix: Optional filename prefix to add to all files
21
- :param suffix: Optional filename suffix to add to all files
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
- prefix: Optional[str] = ""
27
- suffix: Optional[str] = ""
31
+ filename: str
32
+ create_dir_depth: int = 0
28
33
 
29
34
 
30
- class DirectoryProvider(Protocol):
35
+ class FilenameProvider(Protocol):
31
36
  @abstractmethod
32
- def __call__(self) -> DirectoryInfo:
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 StaticDirectoryProvider(DirectoryProvider):
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
- directory_path: Union[str, Path],
40
- filename_prefix: str = "",
41
- filename_suffix: str = "",
42
- resource_dir: Optional[Path] = None,
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
- if resource_dir is None:
45
- resource_dir = Path(".")
46
- if isinstance(directory_path, str):
47
- directory_path = Path(directory_path)
48
- self._directory_info = DirectoryInfo(
49
- root=directory_path,
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
- prefix=filename_prefix,
52
- suffix=filename_suffix,
178
+ filename=filename,
179
+ create_dir_depth=self._create_dir_depth,
53
180
  )
54
181
 
55
- def __call__(self) -> DirectoryInfo:
56
- return self._directory_info
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) -> Sequence[int]:
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 ophyd_async.protocols import AsyncConfigurable, AsyncReadable, AsyncStageable
17
-
18
- from .async_status import AsyncStatus
19
- from .device import Device, DeviceVector
20
- from .signal import SignalR
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 ophyd_async.core.mock_signal_backend import MockSignalBackend
29
- from ophyd_async.protocols import AsyncConfigurable, AsyncReadable, AsyncStageable
30
-
31
- from .async_status import AsyncStatus
32
- from .device import Device
33
- from .signal_backend import SignalBackend
34
- from .soft_signal_backend import SignalMetadata, SoftSignalBackend
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._initial_backend = self._backend = backend
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._initial_backend and backend is not self._initial_backend:
77
- raise ValueError(
78
- "Backend at connection different from initialised one."
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 mock and not isinstance(self._backend, MockSignalBackend):
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
- self.log.debug(f"Connecting to {self.source}")
88
- await self._backend.connect(timeout=timeout)
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
- TYPE_CHECKING,
4
- ClassVar,
5
- Generic,
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 .signal_backend import RuntimeSubsetEnum, SignalBackend
23
- from .utils import DEFAULT_TIMEOUT, ReadingValueCallback, T, get_dtype
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
- return {"source": source, "dtype": "array", "shape": [len(value)], **metadata}
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
- if isinstance(value, Enum):
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 ..protocols import Watcher
20
- from .utils import Callback, P, T, WatcherUpdate
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")
@@ -0,0 +1,9 @@
1
+ from ._aravis import AravisDetector
2
+ from ._aravis_controller import AravisController
3
+ from ._aravis_io import AravisDriverIO
4
+
5
+ __all__ = [
6
+ "AravisDetector",
7
+ "AravisController",
8
+ "AravisDriverIO",
9
+ ]
@@ -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 DirectoryProvider, StandardDetector
6
- from ophyd_async.epics.areadetector.controllers.aravis_controller import (
7
- AravisController,
8
- )
9
- from ophyd_async.epics.areadetector.drivers import ADBaseShapeProvider
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: HDFWriter
20
+ _writer: adcore.ADHDFWriter
23
21
 
24
22
  def __init__(
25
23
  self,
26
24
  prefix: str,
27
- directory_provider: DirectoryProvider,
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 = AravisDriver(prefix + drv_suffix)
34
- self.hdf = NDFileHDF(prefix + hdf_suffix)
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
- HDFWriter(
36
+ adcore.ADHDFWriter(
39
37
  self.hdf,
40
- directory_provider,
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,
@@ -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.areadetector.drivers.aravis_driver import (
11
- AravisDriver,
12
- AravisTriggerMode,
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: AravisDriver, gpio_number: GPIO_NUMBER) -> None:
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.areadetector.drivers import ADBase
5
- from ophyd_async.epics.signal.signal import epics_signal_rw_rbv
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 AravisDriver(ADBase):
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
+ ]