ophyd-async 0.9.0a1__py3-none-any.whl → 0.10.0a1__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 (157) hide show
  1. ophyd_async/__init__.py +5 -8
  2. ophyd_async/_docs_parser.py +12 -0
  3. ophyd_async/_version.py +9 -4
  4. ophyd_async/core/__init__.py +102 -74
  5. ophyd_async/core/_derived_signal.py +271 -0
  6. ophyd_async/core/_derived_signal_backend.py +300 -0
  7. ophyd_async/core/_detector.py +158 -153
  8. ophyd_async/core/_device.py +143 -115
  9. ophyd_async/core/_device_filler.py +82 -9
  10. ophyd_async/core/_flyer.py +16 -7
  11. ophyd_async/core/_hdf_dataset.py +29 -22
  12. ophyd_async/core/_log.py +14 -23
  13. ophyd_async/core/_mock_signal_backend.py +11 -3
  14. ophyd_async/core/_protocol.py +65 -45
  15. ophyd_async/core/_providers.py +28 -9
  16. ophyd_async/core/_readable.py +74 -58
  17. ophyd_async/core/_settings.py +113 -0
  18. ophyd_async/core/_signal.py +304 -174
  19. ophyd_async/core/_signal_backend.py +60 -14
  20. ophyd_async/core/_soft_signal_backend.py +18 -12
  21. ophyd_async/core/_status.py +72 -24
  22. ophyd_async/core/_table.py +54 -17
  23. ophyd_async/core/_utils.py +101 -52
  24. ophyd_async/core/_yaml_settings.py +66 -0
  25. ophyd_async/epics/__init__.py +1 -0
  26. ophyd_async/epics/adandor/__init__.py +9 -0
  27. ophyd_async/epics/adandor/_andor.py +45 -0
  28. ophyd_async/epics/adandor/_andor_controller.py +51 -0
  29. ophyd_async/epics/adandor/_andor_io.py +34 -0
  30. ophyd_async/epics/adaravis/__init__.py +8 -1
  31. ophyd_async/epics/adaravis/_aravis.py +23 -41
  32. ophyd_async/epics/adaravis/_aravis_controller.py +23 -55
  33. ophyd_async/epics/adaravis/_aravis_io.py +13 -28
  34. ophyd_async/epics/adcore/__init__.py +36 -14
  35. ophyd_async/epics/adcore/_core_detector.py +81 -0
  36. ophyd_async/epics/adcore/_core_io.py +145 -95
  37. ophyd_async/epics/adcore/_core_logic.py +179 -88
  38. ophyd_async/epics/adcore/_core_writer.py +223 -0
  39. ophyd_async/epics/adcore/_hdf_writer.py +51 -92
  40. ophyd_async/epics/adcore/_jpeg_writer.py +26 -0
  41. ophyd_async/epics/adcore/_single_trigger.py +6 -5
  42. ophyd_async/epics/adcore/_tiff_writer.py +26 -0
  43. ophyd_async/epics/adcore/_utils.py +3 -2
  44. ophyd_async/epics/adkinetix/__init__.py +2 -1
  45. ophyd_async/epics/adkinetix/_kinetix.py +32 -27
  46. ophyd_async/epics/adkinetix/_kinetix_controller.py +11 -21
  47. ophyd_async/epics/adkinetix/_kinetix_io.py +12 -13
  48. ophyd_async/epics/adpilatus/__init__.py +7 -2
  49. ophyd_async/epics/adpilatus/_pilatus.py +28 -40
  50. ophyd_async/epics/adpilatus/_pilatus_controller.py +25 -22
  51. ophyd_async/epics/adpilatus/_pilatus_io.py +11 -9
  52. ophyd_async/epics/adsimdetector/__init__.py +8 -1
  53. ophyd_async/epics/adsimdetector/_sim.py +22 -16
  54. ophyd_async/epics/adsimdetector/_sim_controller.py +9 -43
  55. ophyd_async/epics/adsimdetector/_sim_io.py +10 -0
  56. ophyd_async/epics/advimba/__init__.py +10 -1
  57. ophyd_async/epics/advimba/_vimba.py +26 -25
  58. ophyd_async/epics/advimba/_vimba_controller.py +12 -24
  59. ophyd_async/epics/advimba/_vimba_io.py +23 -28
  60. ophyd_async/epics/core/_aioca.py +66 -30
  61. ophyd_async/epics/core/_epics_connector.py +4 -0
  62. ophyd_async/epics/core/_epics_device.py +2 -0
  63. ophyd_async/epics/core/_p4p.py +50 -18
  64. ophyd_async/epics/core/_pvi_connector.py +65 -8
  65. ophyd_async/epics/core/_signal.py +51 -51
  66. ophyd_async/epics/core/_util.py +5 -5
  67. ophyd_async/epics/demo/__init__.py +11 -49
  68. ophyd_async/epics/demo/__main__.py +31 -0
  69. ophyd_async/epics/demo/_ioc.py +32 -0
  70. ophyd_async/epics/demo/_motor.py +82 -0
  71. ophyd_async/epics/demo/_point_detector.py +42 -0
  72. ophyd_async/epics/demo/_point_detector_channel.py +22 -0
  73. ophyd_async/epics/demo/_stage.py +15 -0
  74. ophyd_async/epics/demo/{mover.db → motor.db} +2 -1
  75. ophyd_async/epics/demo/point_detector.db +59 -0
  76. ophyd_async/epics/demo/point_detector_channel.db +21 -0
  77. ophyd_async/epics/eiger/_eiger.py +1 -3
  78. ophyd_async/epics/eiger/_eiger_controller.py +11 -4
  79. ophyd_async/epics/eiger/_eiger_io.py +2 -0
  80. ophyd_async/epics/eiger/_odin_io.py +1 -2
  81. ophyd_async/epics/motor.py +83 -38
  82. ophyd_async/epics/signal.py +4 -1
  83. ophyd_async/epics/testing/__init__.py +14 -14
  84. ophyd_async/epics/testing/_example_ioc.py +68 -73
  85. ophyd_async/epics/testing/_utils.py +19 -44
  86. ophyd_async/epics/testing/test_records.db +16 -0
  87. ophyd_async/epics/testing/test_records_pva.db +17 -16
  88. ophyd_async/fastcs/__init__.py +1 -0
  89. ophyd_async/fastcs/core.py +6 -0
  90. ophyd_async/fastcs/odin/__init__.py +1 -0
  91. ophyd_async/fastcs/panda/__init__.py +8 -8
  92. ophyd_async/fastcs/panda/_block.py +29 -9
  93. ophyd_async/fastcs/panda/_control.py +12 -2
  94. ophyd_async/fastcs/panda/_hdf_panda.py +5 -1
  95. ophyd_async/fastcs/panda/_table.py +13 -7
  96. ophyd_async/fastcs/panda/_trigger.py +23 -9
  97. ophyd_async/fastcs/panda/_writer.py +27 -30
  98. ophyd_async/plan_stubs/__init__.py +16 -0
  99. ophyd_async/plan_stubs/_ensure_connected.py +12 -17
  100. ophyd_async/plan_stubs/_fly.py +3 -5
  101. ophyd_async/plan_stubs/_nd_attributes.py +9 -5
  102. ophyd_async/plan_stubs/_panda.py +14 -0
  103. ophyd_async/plan_stubs/_settings.py +152 -0
  104. ophyd_async/plan_stubs/_utils.py +3 -0
  105. ophyd_async/plan_stubs/_wait_for_awaitable.py +13 -0
  106. ophyd_async/sim/__init__.py +29 -0
  107. ophyd_async/sim/__main__.py +43 -0
  108. ophyd_async/sim/_blob_detector.py +33 -0
  109. ophyd_async/sim/_blob_detector_controller.py +48 -0
  110. ophyd_async/sim/_blob_detector_writer.py +105 -0
  111. ophyd_async/sim/_mirror_horizontal.py +46 -0
  112. ophyd_async/sim/_mirror_vertical.py +74 -0
  113. ophyd_async/sim/_motor.py +233 -0
  114. ophyd_async/sim/_pattern_generator.py +124 -0
  115. ophyd_async/sim/_point_detector.py +86 -0
  116. ophyd_async/sim/_stage.py +19 -0
  117. ophyd_async/tango/__init__.py +1 -0
  118. ophyd_async/tango/core/__init__.py +6 -1
  119. ophyd_async/tango/core/_base_device.py +41 -33
  120. ophyd_async/tango/core/_converters.py +81 -0
  121. ophyd_async/tango/core/_signal.py +21 -33
  122. ophyd_async/tango/core/_tango_readable.py +2 -19
  123. ophyd_async/tango/core/_tango_transport.py +148 -74
  124. ophyd_async/tango/core/_utils.py +47 -0
  125. ophyd_async/tango/demo/_counter.py +2 -0
  126. ophyd_async/tango/demo/_detector.py +2 -0
  127. ophyd_async/tango/demo/_mover.py +10 -6
  128. ophyd_async/tango/demo/_tango/_servers.py +4 -0
  129. ophyd_async/tango/testing/__init__.py +6 -0
  130. ophyd_async/tango/testing/_one_of_everything.py +200 -0
  131. ophyd_async/testing/__init__.py +48 -7
  132. ophyd_async/testing/__pytest_assert_rewrite.py +4 -0
  133. ophyd_async/testing/_assert.py +200 -96
  134. ophyd_async/testing/_mock_signal_utils.py +59 -73
  135. ophyd_async/testing/_one_of_everything.py +146 -0
  136. ophyd_async/testing/_single_derived.py +87 -0
  137. ophyd_async/testing/_utils.py +3 -0
  138. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info}/METADATA +25 -26
  139. ophyd_async-0.10.0a1.dist-info/RECORD +149 -0
  140. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info}/WHEEL +1 -1
  141. ophyd_async/core/_device_save_loader.py +0 -274
  142. ophyd_async/epics/demo/_mover.py +0 -95
  143. ophyd_async/epics/demo/_sensor.py +0 -37
  144. ophyd_async/epics/demo/sensor.db +0 -19
  145. ophyd_async/fastcs/panda/_utils.py +0 -16
  146. ophyd_async/sim/demo/__init__.py +0 -19
  147. ophyd_async/sim/demo/_pattern_detector/__init__.py +0 -13
  148. ophyd_async/sim/demo/_pattern_detector/_pattern_detector.py +0 -42
  149. ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +0 -62
  150. ophyd_async/sim/demo/_pattern_detector/_pattern_detector_writer.py +0 -41
  151. ophyd_async/sim/demo/_pattern_detector/_pattern_generator.py +0 -207
  152. ophyd_async/sim/demo/_sim_motor.py +0 -107
  153. ophyd_async/sim/testing/__init__.py +0 -0
  154. ophyd_async-0.9.0a1.dist-info/RECORD +0 -119
  155. ophyd_async-0.9.0a1.dist-info/entry_points.txt +0 -2
  156. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info/licenses}/LICENSE +0 -0
  157. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info}/top_level.txt +0 -0
@@ -1,89 +1,71 @@
1
1
  import asyncio
2
- from collections.abc import AsyncGenerator, AsyncIterator
2
+ from collections.abc import AsyncIterator
3
3
  from pathlib import Path
4
4
  from xml.etree import ElementTree as ET
5
5
 
6
- from bluesky.protocols import Hints, StreamAsset
6
+ from bluesky.protocols import StreamAsset
7
7
  from event_model import DataKey
8
8
 
9
9
  from ophyd_async.core import (
10
- DEFAULT_TIMEOUT,
11
- AsyncStatus,
12
10
  DatasetDescriber,
13
- DetectorWriter,
14
- HDFDataset,
15
- HDFFile,
11
+ HDFDatasetDescription,
12
+ HDFDocumentComposer,
16
13
  NameProvider,
17
14
  PathProvider,
18
- observe_value,
19
- set_and_wait_for_value,
20
- wait_for_value,
21
15
  )
22
16
 
23
- from ._core_io import Callback, NDArrayBaseIO, NDFileHDFIO
17
+ from ._core_io import NDFileHDFIO, NDPluginBaseIO
18
+ from ._core_writer import ADWriter
24
19
  from ._utils import (
25
- FileWriteMode,
26
20
  convert_param_dtype_to_np,
27
21
  convert_pv_dtype_to_np,
28
22
  )
29
23
 
30
24
 
31
- class ADHDFWriter(DetectorWriter):
25
+ class ADHDFWriter(ADWriter[NDFileHDFIO]):
26
+ """Allow `NDFileHDFIO` to be used within `StandardDetector`."""
27
+
28
+ default_suffix: str = "HDF1:"
29
+
32
30
  def __init__(
33
31
  self,
34
- hdf: NDFileHDFIO,
32
+ fileio: NDFileHDFIO,
35
33
  path_provider: PathProvider,
36
34
  name_provider: NameProvider,
37
35
  dataset_describer: DatasetDescriber,
38
- *plugins: NDArrayBaseIO,
36
+ plugins: dict[str, NDPluginBaseIO] | None = None,
39
37
  ) -> None:
40
- self.hdf = hdf
41
- self._path_provider = path_provider
42
- self._name_provider = name_provider
43
- self._dataset_describer = dataset_describer
44
-
45
- self._plugins = plugins
46
- self._capture_status: AsyncStatus | None = None
47
- self._datasets: list[HDFDataset] = []
48
- self._file: HDFFile | None = None
49
- self._multiplier = 1
38
+ super().__init__(
39
+ fileio,
40
+ path_provider,
41
+ name_provider,
42
+ dataset_describer,
43
+ plugins=plugins,
44
+ file_extension=".h5",
45
+ mimetype="application/x-hdf5",
46
+ )
47
+ self._datasets: list[HDFDatasetDescription] = []
48
+ self._composer: HDFDocumentComposer | None = None
49
+ self._filename_template = "%s%s"
50
50
 
51
51
  async def open(self, multiplier: int = 1) -> dict[str, DataKey]:
52
- self._file = None
53
- info = self._path_provider(device_name=self._name_provider())
52
+ self._composer = None
54
53
 
55
- # Set the directory creation depth first, since dir creation callback happens
56
- # when directory path PV is processed.
57
- await self.hdf.create_directory.set(info.create_dir_depth)
54
+ # Setting HDF writer specific signals
58
55
 
59
56
  # Make sure we are using chunk auto-sizing
60
- await asyncio.gather(self.hdf.chunk_size_auto.set(True))
57
+ await asyncio.gather(self.fileio.chunk_size_auto.set(True))
61
58
 
62
59
  await asyncio.gather(
63
- self.hdf.num_extra_dims.set(0),
64
- self.hdf.lazy_open.set(True),
65
- self.hdf.swmr_mode.set(True),
66
- # See https://github.com/bluesky/ophyd-async/issues/122
67
- self.hdf.file_path.set(str(info.directory_path)),
68
- self.hdf.file_name.set(info.filename),
69
- self.hdf.file_template.set("%s/%s.h5"),
70
- self.hdf.file_write_mode.set(FileWriteMode.STREAM),
71
- # Never use custom xml layout file but use the one defined
72
- # in the source code file NDFileHDF5LayoutXML.cpp
73
- self.hdf.xml_file_name.set(""),
74
- self.hdf.enable_callbacks.set(Callback.ENABLE),
60
+ self.fileio.num_extra_dims.set(0),
61
+ self.fileio.lazy_open.set(True),
62
+ self.fileio.swmr_mode.set(True),
63
+ self.fileio.xml_file_name.set(""),
75
64
  )
76
65
 
77
- assert (
78
- await self.hdf.file_path_exists.get_value()
79
- ), f"File path {info.directory_path} for hdf plugin does not exist"
66
+ # Set common AD file plugin params, begin capturing
67
+ await self.begin_capture()
80
68
 
81
- # Overwrite num_capture to go forever
82
- await self.hdf.num_capture.set(0)
83
- # Wait for it to start, stashing the status that tells us when it finishes
84
- self._capture_status = await set_and_wait_for_value(
85
- self.hdf.capture, True, wait_for_set_completion=False
86
- )
87
69
  name = self._name_provider()
88
70
  detector_shape = await self._dataset_describer.shape()
89
71
  np_dtype = await self._dataset_describer.np_datatype()
@@ -91,11 +73,11 @@ class ADHDFWriter(DetectorWriter):
91
73
  outer_shape = (multiplier,) if multiplier > 1 else ()
92
74
 
93
75
  # Determine number of frames that will be saved per HDF chunk
94
- frames_per_chunk = await self.hdf.num_frames_chunks.get_value()
76
+ frames_per_chunk = await self.fileio.num_frames_chunks.get_value()
95
77
 
96
78
  # Add the main data
97
79
  self._datasets = [
98
- HDFDataset(
80
+ HDFDatasetDescription(
99
81
  data_key=name,
100
82
  dataset="/entry/data/data",
101
83
  shape=detector_shape,
@@ -105,14 +87,14 @@ class ADHDFWriter(DetectorWriter):
105
87
  )
106
88
  ]
107
89
  # And all the scalar datasets
108
- for plugin in self._plugins:
90
+ for plugin in self._plugins.values():
109
91
  maybe_xml = await plugin.nd_attributes_file.get_value()
110
92
  # This is the check that ADCore does to see if it is an XML string
111
93
  # rather than a filename to parse
112
94
  if "<Attributes>" in maybe_xml:
113
95
  root = ET.fromstring(maybe_xml)
114
96
  for child in root:
115
- datakey = child.attrib["name"]
97
+ data_key = child.attrib["name"]
116
98
  if child.attrib.get("type", "EPICS_PV") == "EPICS_PV":
117
99
  np_datatype = convert_pv_dtype_to_np(
118
100
  child.attrib.get("dbrtype", "DBR_NATIVE")
@@ -122,21 +104,21 @@ class ADHDFWriter(DetectorWriter):
122
104
  child.attrib.get("datatype", "INT")
123
105
  )
124
106
  self._datasets.append(
125
- HDFDataset(
126
- datakey,
127
- f"/entry/instrument/NDAttributes/{datakey}",
128
- (),
129
- np_datatype,
130
- multiplier,
107
+ HDFDatasetDescription(
108
+ data_key=data_key,
109
+ dataset=f"/entry/instrument/NDAttributes/{data_key}",
110
+ shape=(),
111
+ dtype_numpy=np_datatype,
131
112
  # NDAttributes appear to always be configured with
132
113
  # this chunk size
133
114
  chunk_shape=(16384,),
115
+ multiplier=multiplier,
134
116
  )
135
117
  )
136
118
 
137
119
  describe = {
138
120
  ds.data_key: DataKey(
139
- source=self.hdf.full_file_name.source,
121
+ source=self.fileio.full_file_name.source,
140
122
  shape=list(outer_shape + tuple(ds.shape)),
141
123
  dtype="array" if ds.shape else "number",
142
124
  dtype_numpy=ds.dtype_numpy,
@@ -146,26 +128,15 @@ class ADHDFWriter(DetectorWriter):
146
128
  }
147
129
  return describe
148
130
 
149
- async def observe_indices_written(
150
- self, timeout=DEFAULT_TIMEOUT
151
- ) -> AsyncGenerator[int, None]:
152
- """Wait until a specific index is ready to be collected"""
153
- async for num_captured in observe_value(self.hdf.num_captured, timeout):
154
- yield num_captured // self._multiplier
155
-
156
- async def get_indices_written(self) -> int:
157
- num_captured = await self.hdf.num_captured.get_value()
158
- return num_captured // self._multiplier
159
-
160
131
  async def collect_stream_docs(
161
132
  self, indices_written: int
162
133
  ) -> AsyncIterator[StreamAsset]:
163
134
  # TODO: fail if we get dropped frames
164
- await self.hdf.flush_now.set(True)
135
+ await self.fileio.flush_now.set(True)
165
136
  if indices_written:
166
- if not self._file:
167
- path = Path(await self.hdf.full_file_name.get_value())
168
- self._file = HDFFile(
137
+ if not self._composer:
138
+ path = Path(await self.fileio.full_file_name.get_value())
139
+ self._composer = HDFDocumentComposer(
169
140
  # See https://github.com/bluesky/ophyd-async/issues/122
170
141
  path,
171
142
  self._datasets,
@@ -174,19 +145,7 @@ class ADHDFWriter(DetectorWriter):
174
145
  # stream datum says "here are N frames in that stream resource",
175
146
  # you get one stream resource and many stream datums per scan
176
147
 
177
- for doc in self._file.stream_resources():
148
+ for doc in self._composer.stream_resources():
178
149
  yield "stream_resource", doc
179
- for doc in self._file.stream_data(indices_written):
150
+ for doc in self._composer.stream_data(indices_written):
180
151
  yield "stream_datum", doc
181
-
182
- async def close(self):
183
- # Already done a caput callback in _capture_status, so can't do one here
184
- await self.hdf.capture.set(False, wait=False)
185
- await wait_for_value(self.hdf.capture, False, DEFAULT_TIMEOUT)
186
- if self._capture_status:
187
- # We kicked off an open, so wait for it to return
188
- await self._capture_status
189
-
190
- @property
191
- def hints(self) -> Hints:
192
- return {"fields": [self._name_provider()]}
@@ -0,0 +1,26 @@
1
+ from ophyd_async.core import DatasetDescriber, NameProvider, PathProvider
2
+
3
+ from ._core_io import NDFileIO, NDPluginBaseIO
4
+ from ._core_writer import ADWriter
5
+
6
+
7
+ class ADJPEGWriter(ADWriter[NDFileIO]):
8
+ default_suffix: str = "JPEG1:"
9
+
10
+ def __init__(
11
+ self,
12
+ fileio: NDFileIO,
13
+ path_provider: PathProvider,
14
+ name_provider: NameProvider,
15
+ dataset_describer: DatasetDescriber,
16
+ plugins: dict[str, NDPluginBaseIO] | None = None,
17
+ ) -> None:
18
+ super().__init__(
19
+ fileio,
20
+ path_provider,
21
+ name_provider,
22
+ dataset_describer,
23
+ plugins=plugins,
24
+ file_extension=".jpg",
25
+ mimetype="multipart/related;type=image/jpeg",
26
+ )
@@ -7,7 +7,7 @@ from ophyd_async.core import AsyncStatus, SignalR, StandardReadable
7
7
  from ophyd_async.core import StandardReadableFormat as Format
8
8
 
9
9
  from ._core_io import ADBaseIO, NDPluginBaseIO
10
- from ._utils import ImageMode
10
+ from ._utils import ADImageMode
11
11
 
12
12
 
13
13
  class SingleTriggerDetector(StandardReadable, Triggerable):
@@ -16,11 +16,12 @@ class SingleTriggerDetector(StandardReadable, Triggerable):
16
16
  drv: ADBaseIO,
17
17
  read_uncached: Sequence[SignalR] = (),
18
18
  name="",
19
- **plugins: NDPluginBaseIO,
19
+ plugins: dict[str, NDPluginBaseIO] | None = None,
20
20
  ) -> None:
21
21
  self.drv = drv
22
- for k, v in plugins.items():
23
- setattr(self, k, v)
22
+ if plugins is not None:
23
+ for k, v in plugins.items():
24
+ setattr(self, k, v)
24
25
 
25
26
  self.add_readables(
26
27
  [self.drv.array_counter, *read_uncached],
@@ -34,7 +35,7 @@ class SingleTriggerDetector(StandardReadable, Triggerable):
34
35
  @AsyncStatus.wrap
35
36
  async def stage(self) -> None:
36
37
  await asyncio.gather(
37
- self.drv.image_mode.set(ImageMode.SINGLE),
38
+ self.drv.image_mode.set(ADImageMode.SINGLE),
38
39
  self.drv.wait_for_plugins.set(True),
39
40
  )
40
41
  await super().stage()
@@ -0,0 +1,26 @@
1
+ from ophyd_async.core import DatasetDescriber, NameProvider, PathProvider
2
+
3
+ from ._core_io import NDFileIO, NDPluginBaseIO
4
+ from ._core_writer import ADWriter
5
+
6
+
7
+ class ADTIFFWriter(ADWriter[NDFileIO]):
8
+ default_suffix: str = "TIFF1:"
9
+
10
+ def __init__(
11
+ self,
12
+ fileio: NDFileIO,
13
+ path_provider: PathProvider,
14
+ name_provider: NameProvider,
15
+ dataset_describer: DatasetDescriber,
16
+ plugins: dict[str, NDPluginBaseIO] | None = None,
17
+ ) -> None:
18
+ super().__init__(
19
+ fileio,
20
+ path_provider,
21
+ name_provider,
22
+ dataset_describer,
23
+ plugins=plugins,
24
+ file_extension=".tiff",
25
+ mimetype="multipart/related;type=image/tiff",
26
+ )
@@ -6,6 +6,7 @@ from ophyd_async.core import (
6
6
  SignalR,
7
7
  SignalRW,
8
8
  StrictEnum,
9
+ SubsetEnum,
9
10
  wait_for_value,
10
11
  )
11
12
 
@@ -78,13 +79,13 @@ def convert_param_dtype_to_np(datatype: str) -> str:
78
79
  return np_datatype
79
80
 
80
81
 
81
- class FileWriteMode(StrictEnum):
82
+ class ADFileWriteMode(StrictEnum):
82
83
  SINGLE = "Single"
83
84
  CAPTURE = "Capture"
84
85
  STREAM = "Stream"
85
86
 
86
87
 
87
- class ImageMode(StrictEnum):
88
+ class ADImageMode(SubsetEnum):
88
89
  SINGLE = "Single"
89
90
  MULTIPLE = "Multiple"
90
91
  CONTINUOUS = "Continuous"
@@ -1,10 +1,11 @@
1
1
  from ._kinetix import KinetixDetector
2
2
  from ._kinetix_controller import KinetixController
3
- from ._kinetix_io import KinetixDriverIO, KinetixTriggerMode
3
+ from ._kinetix_io import KinetixDriverIO, KinetixReadoutMode, KinetixTriggerMode
4
4
 
5
5
  __all__ = [
6
6
  "KinetixDetector",
7
7
  "KinetixController",
8
8
  "KinetixDriverIO",
9
9
  "KinetixTriggerMode",
10
+ "KinetixReadoutMode",
10
11
  ]
@@ -1,44 +1,49 @@
1
- from bluesky.protocols import HasHints, Hints
1
+ from collections.abc import Sequence
2
2
 
3
- from ophyd_async.core import PathProvider, StandardDetector
4
- from ophyd_async.epics import adcore
3
+ from ophyd_async.core import PathProvider, SignalR
4
+ from ophyd_async.epics.adcore import (
5
+ ADHDFWriter,
6
+ ADWriter,
7
+ AreaDetector,
8
+ NDPluginBaseIO,
9
+ )
5
10
 
6
11
  from ._kinetix_controller import KinetixController
7
12
  from ._kinetix_io import KinetixDriverIO
8
13
 
9
14
 
10
- class KinetixDetector(StandardDetector, HasHints):
11
- """
12
- Ophyd-async implementation of an ADKinetix Detector.
13
- https://github.com/NSLS-II/ADKinetix
14
- """
15
+ class KinetixDetector(AreaDetector[KinetixController]):
16
+ """Ophyd-async implementation of an ADKinetix Detector.
15
17
 
16
- _controller: KinetixController
17
- _writer: adcore.ADHDFWriter
18
+ https://github.com/NSLS-II/ADKinetix.
19
+ """
18
20
 
19
21
  def __init__(
20
22
  self,
21
23
  prefix: str,
22
24
  path_provider: PathProvider,
23
- drv_suffix="cam1:",
24
- hdf_suffix="HDF1:",
25
- name="",
25
+ drv_suffix: str = "cam1:",
26
+ writer_cls: type[ADWriter] = ADHDFWriter,
27
+ fileio_suffix: str | None = None,
28
+ name: str = "",
29
+ plugins: dict[str, NDPluginBaseIO] | None = None,
30
+ config_sigs: Sequence[SignalR] = (),
26
31
  ):
27
- self.drv = KinetixDriverIO(prefix + drv_suffix)
28
- self.hdf = adcore.NDFileHDFIO(prefix + hdf_suffix)
32
+ driver = KinetixDriverIO(prefix + drv_suffix)
33
+ controller = KinetixController(driver)
34
+
35
+ writer = writer_cls.with_io(
36
+ prefix,
37
+ path_provider,
38
+ dataset_source=driver,
39
+ fileio_suffix=fileio_suffix,
40
+ plugins=plugins,
41
+ )
29
42
 
30
43
  super().__init__(
31
- KinetixController(self.drv),
32
- adcore.ADHDFWriter(
33
- self.hdf,
34
- path_provider,
35
- lambda: self.name,
36
- adcore.ADBaseDatasetDescriber(self.drv),
37
- ),
38
- config_sigs=(self.drv.acquire_time,),
44
+ controller=controller,
45
+ writer=writer,
46
+ plugins=plugins,
39
47
  name=name,
48
+ config_sigs=config_sigs,
40
49
  )
41
-
42
- @property
43
- def hints(self) -> Hints:
44
- return self._writer.hints
@@ -1,8 +1,6 @@
1
1
  import asyncio
2
2
 
3
3
  from ophyd_async.core import (
4
- AsyncStatus,
5
- DetectorController,
6
4
  DetectorTrigger,
7
5
  TriggerInfo,
8
6
  )
@@ -18,37 +16,29 @@ KINETIX_TRIGGER_MODE_MAP = {
18
16
  }
19
17
 
20
18
 
21
- class KinetixController(DetectorController):
19
+ class KinetixController(adcore.ADBaseController[KinetixDriverIO]):
20
+ """Controller for adkinetix detector."""
21
+
22
22
  def __init__(
23
23
  self,
24
24
  driver: KinetixDriverIO,
25
+ good_states: frozenset[adcore.ADState] = adcore.DEFAULT_GOOD_STATES,
25
26
  ) -> None:
26
- self._drv = driver
27
- self._arm_status: AsyncStatus | None = None
27
+ super().__init__(driver, good_states=good_states)
28
28
 
29
29
  def get_deadtime(self, exposure: float | None) -> float:
30
30
  return 0.001
31
31
 
32
32
  async def prepare(self, trigger_info: TriggerInfo):
33
33
  await asyncio.gather(
34
- self._drv.trigger_mode.set(KINETIX_TRIGGER_MODE_MAP[trigger_info.trigger]),
35
- self._drv.num_images.set(trigger_info.total_number_of_triggers),
36
- self._drv.image_mode.set(adcore.ImageMode.MULTIPLE),
34
+ self.driver.trigger_mode.set(
35
+ KINETIX_TRIGGER_MODE_MAP[trigger_info.trigger]
36
+ ),
37
+ self.driver.num_images.set(trigger_info.total_number_of_triggers),
38
+ self.driver.image_mode.set(adcore.ADImageMode.MULTIPLE),
37
39
  )
38
40
  if trigger_info.livetime is not None and trigger_info.trigger not in [
39
41
  DetectorTrigger.VARIABLE_GATE,
40
42
  DetectorTrigger.CONSTANT_GATE,
41
43
  ]:
42
- await self._drv.acquire_time.set(trigger_info.livetime)
43
-
44
- async def arm(self):
45
- self._arm_status = await adcore.start_acquiring_driver_and_ensure_status(
46
- self._drv
47
- )
48
-
49
- async def wait_for_idle(self):
50
- if self._arm_status:
51
- await self._arm_status
52
-
53
- async def disarm(self):
54
- await adcore.stop_busy_record(self._drv.acquire, False, timeout=1)
44
+ await self.driver.acquire_time.set(trigger_info.livetime)
@@ -1,15 +1,21 @@
1
- from ophyd_async.core import StrictEnum
1
+ from typing import Annotated as A
2
+
3
+ from ophyd_async.core import SignalRW, StrictEnum
2
4
  from ophyd_async.epics import adcore
3
- from ophyd_async.epics.core import epics_signal_rw_rbv
5
+ from ophyd_async.epics.core import PvSuffix
4
6
 
5
7
 
6
8
  class KinetixTriggerMode(StrictEnum):
9
+ """Trigger mode for ADKinetix detector."""
10
+
7
11
  INTERNAL = "Internal"
8
12
  EDGE = "Rising Edge"
9
13
  GATE = "Exp. Gate"
10
14
 
11
15
 
12
16
  class KinetixReadoutMode(StrictEnum):
17
+ """Readout mode for ADKinetix detector."""
18
+
13
19
  SENSITIVITY = 1
14
20
  SPEED = 2
15
21
  DYNAMIC_RANGE = 3
@@ -17,14 +23,7 @@ class KinetixReadoutMode(StrictEnum):
17
23
 
18
24
 
19
25
  class KinetixDriverIO(adcore.ADBaseIO):
20
- """This mirrors the interface provided by ADKinetix/db/ADKinetix.template."""
21
-
22
- def __init__(self, prefix: str, name: str = "") -> None:
23
- # self.pixel_format = epics_signal_rw_rbv(PixelFormat, prefix + "PixelFormat")
24
- self.trigger_mode = epics_signal_rw_rbv(
25
- KinetixTriggerMode, prefix + "TriggerMode"
26
- )
27
- self.readout_port_idx = epics_signal_rw_rbv(
28
- KinetixReadoutMode, prefix + "ReadoutPortIdx"
29
- )
30
- super().__init__(prefix, name)
26
+ """Mirrors the interface provided by ADKinetix/db/ADKinetix.template."""
27
+
28
+ trigger_mode: A[SignalRW[KinetixTriggerMode], PvSuffix("TriggerMode")]
29
+ readout_port_idx: A[SignalRW[KinetixReadoutMode], PvSuffix("ReadoutPortIdx")]
@@ -1,5 +1,10 @@
1
- from ._pilatus import PilatusDetector, PilatusReadoutTime
2
- from ._pilatus_controller import PilatusController
1
+ """Support for the ADPilatus areaDetector driver.
2
+
3
+ https://github.com/areaDetector/ADPilatus
4
+ """
5
+
6
+ from ._pilatus import PilatusDetector
7
+ from ._pilatus_controller import PilatusController, PilatusReadoutTime
3
8
  from ._pilatus_io import PilatusDriverIO, PilatusTriggerMode
4
9
 
5
10
  __all__ = [
@@ -1,33 +1,18 @@
1
- from enum import Enum
1
+ from collections.abc import Sequence
2
2
 
3
- from bluesky.protocols import Hints
3
+ from ophyd_async.core import PathProvider
4
+ from ophyd_async.core._signal import SignalR
5
+ from ophyd_async.epics.adcore._core_detector import AreaDetector
6
+ from ophyd_async.epics.adcore._core_io import NDPluginBaseIO
7
+ from ophyd_async.epics.adcore._core_writer import ADWriter
8
+ from ophyd_async.epics.adcore._hdf_writer import ADHDFWriter
4
9
 
5
- from ophyd_async.core import PathProvider, StandardDetector
6
- from ophyd_async.epics import adcore
7
-
8
- from ._pilatus_controller import PilatusController
10
+ from ._pilatus_controller import PilatusController, PilatusReadoutTime
9
11
  from ._pilatus_io import PilatusDriverIO
10
12
 
11
13
 
12
- #: Cite: https://media.dectris.com/User_Manual-PILATUS2-V1_4.pdf
13
- #: The required minimum time difference between ExpPeriod and ExpTime
14
- #: (readout time) is 2.28 ms
15
- #: We provide an option to override for newer Pilatus models
16
- class PilatusReadoutTime(float, Enum):
17
- """Pilatus readout time per model in ms"""
18
-
19
- # Cite: https://media.dectris.com/User_Manual-PILATUS2-V1_4.pdf
20
- PILATUS2 = 2.28e-3
21
-
22
- # Cite: https://media.dectris.com/user-manual-pilatus3-2020.pdf
23
- PILATUS3 = 0.95e-3
24
-
25
-
26
- class PilatusDetector(StandardDetector):
27
- """A Pilatus StandardDetector writing HDF files"""
28
-
29
- _controller: PilatusController
30
- _writer: adcore.ADHDFWriter
14
+ class PilatusDetector(AreaDetector[PilatusController]):
15
+ """A Pilatus StandardDetector writing HDF files."""
31
16
 
32
17
  def __init__(
33
18
  self,
@@ -35,24 +20,27 @@ class PilatusDetector(StandardDetector):
35
20
  path_provider: PathProvider,
36
21
  readout_time: PilatusReadoutTime = PilatusReadoutTime.PILATUS3,
37
22
  drv_suffix: str = "cam1:",
38
- hdf_suffix: str = "HDF1:",
23
+ writer_cls: type[ADWriter] = ADHDFWriter,
24
+ fileio_suffix: str | None = None,
39
25
  name: str = "",
26
+ plugins: dict[str, NDPluginBaseIO] | None = None,
27
+ config_sigs: Sequence[SignalR] = (),
40
28
  ):
41
- self.drv = PilatusDriverIO(prefix + drv_suffix)
42
- self.hdf = adcore.NDFileHDFIO(prefix + hdf_suffix)
29
+ driver = PilatusDriverIO(prefix + drv_suffix)
30
+ controller = PilatusController(driver)
31
+
32
+ writer = writer_cls.with_io(
33
+ prefix,
34
+ path_provider,
35
+ dataset_source=driver,
36
+ fileio_suffix=fileio_suffix,
37
+ plugins=plugins,
38
+ )
43
39
 
44
40
  super().__init__(
45
- PilatusController(self.drv, readout_time=readout_time.value),
46
- adcore.ADHDFWriter(
47
- self.hdf,
48
- path_provider,
49
- lambda: self.name,
50
- adcore.ADBaseDatasetDescriber(self.drv),
51
- ),
52
- config_sigs=(self.drv.acquire_time,),
41
+ controller=controller,
42
+ writer=writer,
43
+ plugins=plugins,
53
44
  name=name,
45
+ config_sigs=config_sigs,
54
46
  )
55
-
56
- @property
57
- def hints(self) -> Hints:
58
- return self._writer.hints