ophyd-async 0.8.0a5__py3-none-any.whl → 0.9.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 (116) hide show
  1. ophyd_async/_version.py +2 -2
  2. ophyd_async/core/__init__.py +17 -46
  3. ophyd_async/core/_detector.py +68 -44
  4. ophyd_async/core/_device.py +120 -79
  5. ophyd_async/core/_device_filler.py +17 -8
  6. ophyd_async/core/_flyer.py +2 -2
  7. ophyd_async/core/_protocol.py +0 -28
  8. ophyd_async/core/_readable.py +30 -23
  9. ophyd_async/core/_settings.py +104 -0
  10. ophyd_async/core/_signal.py +164 -151
  11. ophyd_async/core/_signal_backend.py +4 -1
  12. ophyd_async/core/_soft_signal_backend.py +2 -1
  13. ophyd_async/core/_table.py +27 -14
  14. ophyd_async/core/_utils.py +30 -5
  15. ophyd_async/core/_yaml_settings.py +64 -0
  16. ophyd_async/epics/adandor/__init__.py +9 -0
  17. ophyd_async/epics/adandor/_andor.py +45 -0
  18. ophyd_async/epics/adandor/_andor_controller.py +49 -0
  19. ophyd_async/epics/adandor/_andor_io.py +36 -0
  20. ophyd_async/epics/adaravis/__init__.py +3 -1
  21. ophyd_async/epics/adaravis/_aravis.py +23 -37
  22. ophyd_async/epics/adaravis/_aravis_controller.py +21 -30
  23. ophyd_async/epics/adaravis/_aravis_io.py +4 -4
  24. ophyd_async/epics/adcore/__init__.py +15 -8
  25. ophyd_async/epics/adcore/_core_detector.py +41 -0
  26. ophyd_async/epics/adcore/_core_io.py +56 -31
  27. ophyd_async/epics/adcore/_core_logic.py +99 -84
  28. ophyd_async/epics/adcore/_core_writer.py +219 -0
  29. ophyd_async/epics/adcore/_hdf_writer.py +33 -59
  30. ophyd_async/epics/adcore/_jpeg_writer.py +26 -0
  31. ophyd_async/epics/adcore/_single_trigger.py +5 -4
  32. ophyd_async/epics/adcore/_tiff_writer.py +26 -0
  33. ophyd_async/epics/adcore/_utils.py +37 -36
  34. ophyd_async/epics/adkinetix/_kinetix.py +29 -24
  35. ophyd_async/epics/adkinetix/_kinetix_controller.py +15 -27
  36. ophyd_async/epics/adkinetix/_kinetix_io.py +7 -7
  37. ophyd_async/epics/adpilatus/__init__.py +2 -2
  38. ophyd_async/epics/adpilatus/_pilatus.py +28 -40
  39. ophyd_async/epics/adpilatus/_pilatus_controller.py +47 -25
  40. ophyd_async/epics/adpilatus/_pilatus_io.py +5 -5
  41. ophyd_async/epics/adsimdetector/__init__.py +3 -3
  42. ophyd_async/epics/adsimdetector/_sim.py +33 -17
  43. ophyd_async/epics/advimba/_vimba.py +23 -23
  44. ophyd_async/epics/advimba/_vimba_controller.py +21 -35
  45. ophyd_async/epics/advimba/_vimba_io.py +23 -23
  46. ophyd_async/epics/core/_aioca.py +52 -21
  47. ophyd_async/epics/core/_p4p.py +59 -16
  48. ophyd_async/epics/core/_pvi_connector.py +4 -2
  49. ophyd_async/epics/core/_signal.py +9 -2
  50. ophyd_async/epics/core/_util.py +10 -1
  51. ophyd_async/epics/eiger/_eiger_controller.py +10 -5
  52. ophyd_async/epics/eiger/_eiger_io.py +3 -3
  53. ophyd_async/epics/motor.py +26 -15
  54. ophyd_async/epics/sim/_ioc.py +29 -0
  55. ophyd_async/epics/{demo → sim}/_mover.py +12 -6
  56. ophyd_async/epics/{demo → sim}/_sensor.py +2 -2
  57. ophyd_async/epics/testing/__init__.py +24 -0
  58. ophyd_async/epics/testing/_example_ioc.py +91 -0
  59. ophyd_async/epics/testing/_utils.py +50 -0
  60. ophyd_async/epics/testing/test_records.db +174 -0
  61. ophyd_async/epics/testing/test_records_pva.db +177 -0
  62. ophyd_async/fastcs/core.py +2 -2
  63. ophyd_async/fastcs/panda/__init__.py +0 -2
  64. ophyd_async/fastcs/panda/_block.py +9 -9
  65. ophyd_async/fastcs/panda/_control.py +9 -4
  66. ophyd_async/fastcs/panda/_hdf_panda.py +7 -2
  67. ophyd_async/fastcs/panda/_table.py +4 -1
  68. ophyd_async/fastcs/panda/_trigger.py +7 -7
  69. ophyd_async/plan_stubs/__init__.py +14 -0
  70. ophyd_async/plan_stubs/_ensure_connected.py +11 -17
  71. ophyd_async/plan_stubs/_fly.py +2 -2
  72. ophyd_async/plan_stubs/_nd_attributes.py +7 -5
  73. ophyd_async/plan_stubs/_panda.py +13 -0
  74. ophyd_async/plan_stubs/_settings.py +125 -0
  75. ophyd_async/plan_stubs/_wait_for_awaitable.py +13 -0
  76. ophyd_async/sim/__init__.py +19 -0
  77. ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_detector_controller.py +9 -2
  78. ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_generator.py +13 -6
  79. ophyd_async/sim/{demo/_sim_motor.py → _sim_motor.py} +34 -32
  80. ophyd_async/tango/__init__.py +0 -43
  81. ophyd_async/tango/{signal → core}/__init__.py +7 -2
  82. ophyd_async/tango/{base_devices → core}/_base_device.py +38 -64
  83. ophyd_async/tango/{signal → core}/_signal.py +16 -4
  84. ophyd_async/tango/{base_devices → core}/_tango_readable.py +3 -4
  85. ophyd_async/tango/{signal → core}/_tango_transport.py +13 -15
  86. ophyd_async/tango/{demo → sim}/_counter.py +6 -7
  87. ophyd_async/tango/{demo → sim}/_mover.py +13 -9
  88. ophyd_async/testing/__init__.py +52 -0
  89. ophyd_async/testing/__pytest_assert_rewrite.py +4 -0
  90. ophyd_async/testing/_assert.py +176 -0
  91. ophyd_async/{core → testing}/_mock_signal_utils.py +15 -11
  92. ophyd_async/testing/_one_of_everything.py +126 -0
  93. ophyd_async/testing/_wait_for_pending.py +22 -0
  94. {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0.dist-info}/METADATA +50 -48
  95. ophyd_async-0.9.0.dist-info/RECORD +129 -0
  96. {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0.dist-info}/WHEEL +1 -1
  97. ophyd_async/core/_device_save_loader.py +0 -274
  98. ophyd_async/epics/adsimdetector/_sim_controller.py +0 -51
  99. ophyd_async/fastcs/panda/_utils.py +0 -16
  100. ophyd_async/sim/demo/__init__.py +0 -19
  101. ophyd_async/sim/testing/__init__.py +0 -0
  102. ophyd_async/tango/base_devices/__init__.py +0 -4
  103. ophyd_async-0.8.0a5.dist-info/RECORD +0 -112
  104. ophyd_async-0.8.0a5.dist-info/entry_points.txt +0 -2
  105. /ophyd_async/epics/{demo → sim}/__init__.py +0 -0
  106. /ophyd_async/epics/{demo → sim}/mover.db +0 -0
  107. /ophyd_async/epics/{demo → sim}/sensor.db +0 -0
  108. /ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/__init__.py +0 -0
  109. /ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_detector.py +0 -0
  110. /ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_detector_writer.py +0 -0
  111. /ophyd_async/tango/{demo → sim}/__init__.py +0 -0
  112. /ophyd_async/tango/{demo → sim}/_detector.py +0 -0
  113. /ophyd_async/tango/{demo → sim}/_tango/__init__.py +0 -0
  114. /ophyd_async/tango/{demo → sim}/_tango/_servers.py +0 -0
  115. {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0.dist-info}/LICENSE +0 -0
  116. {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
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
 
@@ -8,79 +8,64 @@ from event_model import DataKey
8
8
 
9
9
  from ophyd_async.core import (
10
10
  DEFAULT_TIMEOUT,
11
- AsyncStatus,
12
11
  DatasetDescriber,
13
- DetectorWriter,
14
12
  HDFDataset,
15
13
  HDFFile,
16
14
  NameProvider,
17
15
  PathProvider,
18
- observe_value,
19
- set_and_wait_for_value,
20
16
  wait_for_value,
21
17
  )
22
18
 
23
- from ._core_io import NDArrayBaseIO, NDFileHDFIO
19
+ from ._core_io import NDFileHDFIO, NDPluginBaseIO
20
+ from ._core_writer import ADWriter
24
21
  from ._utils import (
25
- FileWriteMode,
26
22
  convert_param_dtype_to_np,
27
23
  convert_pv_dtype_to_np,
28
24
  )
29
25
 
30
26
 
31
- class ADHDFWriter(DetectorWriter):
27
+ class ADHDFWriter(ADWriter[NDFileHDFIO]):
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
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
47
  self._datasets: list[HDFDataset] = []
48
48
  self._file: HDFFile | None = None
49
- self._multiplier = 1
49
+ self._filename_template = "%s%s"
50
50
 
51
51
  async def open(self, multiplier: int = 1) -> dict[str, DataKey]:
52
52
  self._file = None
53
- info = self._path_provider(device_name=self._name_provider())
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(""),
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(""),
74
64
  )
75
65
 
76
- assert (
77
- await self.hdf.file_path_exists.get_value()
78
- ), 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()
79
68
 
80
- # Overwrite num_capture to go forever
81
- await self.hdf.num_capture.set(0)
82
- # Wait for it to start, stashing the status that tells us when it finishes
83
- self._capture_status = await set_and_wait_for_value(self.hdf.capture, True)
84
69
  name = self._name_provider()
85
70
  detector_shape = await self._dataset_describer.shape()
86
71
  np_dtype = await self._dataset_describer.np_datatype()
@@ -88,7 +73,7 @@ class ADHDFWriter(DetectorWriter):
88
73
  outer_shape = (multiplier,) if multiplier > 1 else ()
89
74
 
90
75
  # Determine number of frames that will be saved per HDF chunk
91
- frames_per_chunk = await self.hdf.num_frames_chunks.get_value()
76
+ frames_per_chunk = await self.fileio.num_frames_chunks.get_value()
92
77
 
93
78
  # Add the main data
94
79
  self._datasets = [
@@ -102,7 +87,7 @@ class ADHDFWriter(DetectorWriter):
102
87
  )
103
88
  ]
104
89
  # And all the scalar datasets
105
- for plugin in self._plugins:
90
+ for plugin in self._plugins.values():
106
91
  maybe_xml = await plugin.nd_attributes_file.get_value()
107
92
  # This is the check that ADCore does to see if it is an XML string
108
93
  # rather than a filename to parse
@@ -133,7 +118,7 @@ class ADHDFWriter(DetectorWriter):
133
118
 
134
119
  describe = {
135
120
  ds.data_key: DataKey(
136
- source=self.hdf.full_file_name.source,
121
+ source=self.fileio.full_file_name.source,
137
122
  shape=list(outer_shape + tuple(ds.shape)),
138
123
  dtype="array" if ds.shape else "number",
139
124
  dtype_numpy=ds.dtype_numpy,
@@ -143,25 +128,14 @@ class ADHDFWriter(DetectorWriter):
143
128
  }
144
129
  return describe
145
130
 
146
- async def observe_indices_written(
147
- self, timeout=DEFAULT_TIMEOUT
148
- ) -> AsyncGenerator[int, None]:
149
- """Wait until a specific index is ready to be collected"""
150
- async for num_captured in observe_value(self.hdf.num_captured, timeout):
151
- yield num_captured // self._multiplier
152
-
153
- async def get_indices_written(self) -> int:
154
- num_captured = await self.hdf.num_captured.get_value()
155
- return num_captured // self._multiplier
156
-
157
131
  async def collect_stream_docs(
158
132
  self, indices_written: int
159
133
  ) -> AsyncIterator[StreamAsset]:
160
134
  # TODO: fail if we get dropped frames
161
- await self.hdf.flush_now.set(True)
135
+ await self.fileio.flush_now.set(True)
162
136
  if indices_written:
163
137
  if not self._file:
164
- path = Path(await self.hdf.full_file_name.get_value())
138
+ path = Path(await self.fileio.full_file_name.get_value())
165
139
  self._file = HDFFile(
166
140
  # See https://github.com/bluesky/ophyd-async/issues/122
167
141
  path,
@@ -178,8 +152,8 @@ class ADHDFWriter(DetectorWriter):
178
152
 
179
153
  async def close(self):
180
154
  # Already done a caput callback in _capture_status, so can't do one here
181
- await self.hdf.capture.set(False, wait=False)
182
- await wait_for_value(self.hdf.capture, False, DEFAULT_TIMEOUT)
155
+ await self.fileio.capture.set(False, wait=False)
156
+ await wait_for_value(self.fileio.capture, False, DEFAULT_TIMEOUT)
183
157
  if self._capture_status:
184
158
  # We kicked off an open, so wait for it to return
185
159
  await self._capture_status
@@ -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
+ )
@@ -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(ImageMode.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,47 +6,48 @@ 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
 
12
13
 
13
14
  class ADBaseDataType(StrictEnum):
14
- Int8 = "Int8"
15
- UInt8 = "UInt8"
16
- Int16 = "Int16"
17
- UInt16 = "UInt16"
18
- Int32 = "Int32"
19
- UInt32 = "UInt32"
20
- Int64 = "Int64"
21
- UInt64 = "UInt64"
22
- Float32 = "Float32"
23
- Float64 = "Float64"
15
+ INT8 = "Int8"
16
+ UINT8 = "UInt8"
17
+ INT16 = "Int16"
18
+ UINT16 = "UInt16"
19
+ INT32 = "Int32"
20
+ UINT32 = "UInt32"
21
+ INT64 = "Int64"
22
+ UINT64 = "UInt64"
23
+ FLOAT32 = "Float32"
24
+ FLOAT64 = "Float64"
24
25
 
25
26
 
26
27
  def convert_ad_dtype_to_np(ad_dtype: ADBaseDataType) -> str:
27
28
  ad_dtype_to_np_dtype = {
28
- ADBaseDataType.Int8: "|i1",
29
- ADBaseDataType.UInt8: "|u1",
30
- ADBaseDataType.Int16: "<i2",
31
- ADBaseDataType.UInt16: "<u2",
32
- ADBaseDataType.Int32: "<i4",
33
- ADBaseDataType.UInt32: "<u4",
34
- ADBaseDataType.Int64: "<i8",
35
- ADBaseDataType.UInt64: "<u8",
36
- ADBaseDataType.Float32: "<f4",
37
- ADBaseDataType.Float64: "<f8",
29
+ ADBaseDataType.INT8: "|i1",
30
+ ADBaseDataType.UINT8: "|u1",
31
+ ADBaseDataType.INT16: "<i2",
32
+ ADBaseDataType.UINT16: "<u2",
33
+ ADBaseDataType.INT32: "<i4",
34
+ ADBaseDataType.UINT32: "<u4",
35
+ ADBaseDataType.INT64: "<i8",
36
+ ADBaseDataType.UINT64: "<u8",
37
+ ADBaseDataType.FLOAT32: "<f4",
38
+ ADBaseDataType.FLOAT64: "<f8",
38
39
  }
39
40
  return ad_dtype_to_np_dtype[ad_dtype]
40
41
 
41
42
 
42
43
  def convert_pv_dtype_to_np(datatype: str) -> str:
43
44
  _pvattribute_to_ad_datatype = {
44
- "DBR_SHORT": ADBaseDataType.Int16,
45
- "DBR_ENUM": ADBaseDataType.Int16,
46
- "DBR_INT": ADBaseDataType.Int32,
47
- "DBR_LONG": ADBaseDataType.Int32,
48
- "DBR_FLOAT": ADBaseDataType.Float32,
49
- "DBR_DOUBLE": ADBaseDataType.Float64,
45
+ "DBR_SHORT": ADBaseDataType.INT16,
46
+ "DBR_ENUM": ADBaseDataType.INT16,
47
+ "DBR_INT": ADBaseDataType.INT32,
48
+ "DBR_LONG": ADBaseDataType.INT32,
49
+ "DBR_FLOAT": ADBaseDataType.FLOAT32,
50
+ "DBR_DOUBLE": ADBaseDataType.FLOAT64,
50
51
  }
51
52
  if datatype in ["DBR_STRING", "DBR_CHAR"]:
52
53
  np_datatype = "s40"
@@ -62,9 +63,9 @@ def convert_pv_dtype_to_np(datatype: str) -> str:
62
63
 
63
64
  def convert_param_dtype_to_np(datatype: str) -> str:
64
65
  _paramattribute_to_ad_datatype = {
65
- "INT": ADBaseDataType.Int32,
66
- "INT64": ADBaseDataType.Int64,
67
- "DOUBLE": ADBaseDataType.Float64,
66
+ "INT": ADBaseDataType.INT32,
67
+ "INT64": ADBaseDataType.INT64,
68
+ "DOUBLE": ADBaseDataType.FLOAT64,
68
69
  }
69
70
  if datatype in ["STRING"]:
70
71
  np_datatype = "s40"
@@ -79,15 +80,15 @@ def convert_param_dtype_to_np(datatype: str) -> str:
79
80
 
80
81
 
81
82
  class FileWriteMode(StrictEnum):
82
- single = "Single"
83
- capture = "Capture"
84
- stream = "Stream"
83
+ SINGLE = "Single"
84
+ CAPTURE = "Capture"
85
+ STREAM = "Stream"
85
86
 
86
87
 
87
- class ImageMode(StrictEnum):
88
- single = "Single"
89
- multiple = "Multiple"
90
- continuous = "Continuous"
88
+ class ImageMode(SubsetEnum):
89
+ SINGLE = "Single"
90
+ MULTIPLE = "Multiple"
91
+ CONTINUOUS = "Continuous"
91
92
 
92
93
 
93
94
  class NDAttributeDataType(StrictEnum):
@@ -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):
15
+ class KinetixDetector(AreaDetector[KinetixController]):
11
16
  """
12
17
  Ophyd-async implementation of an ADKinetix Detector.
13
18
  https://github.com/NSLS-II/ADKinetix
14
19
  """
15
20
 
16
- _controller: KinetixController
17
- _writer: adcore.ADHDFWriter
18
-
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
  )
@@ -11,44 +9,34 @@ from ophyd_async.epics import adcore
11
9
  from ._kinetix_io import KinetixDriverIO, KinetixTriggerMode
12
10
 
13
11
  KINETIX_TRIGGER_MODE_MAP = {
14
- DetectorTrigger.internal: KinetixTriggerMode.internal,
15
- DetectorTrigger.constant_gate: KinetixTriggerMode.gate,
16
- DetectorTrigger.variable_gate: KinetixTriggerMode.gate,
17
- DetectorTrigger.edge_trigger: KinetixTriggerMode.edge,
12
+ DetectorTrigger.INTERNAL: KinetixTriggerMode.INTERNAL,
13
+ DetectorTrigger.CONSTANT_GATE: KinetixTriggerMode.GATE,
14
+ DetectorTrigger.VARIABLE_GATE: KinetixTriggerMode.GATE,
15
+ DetectorTrigger.EDGE_TRIGGER: KinetixTriggerMode.EDGE,
18
16
  }
19
17
 
20
18
 
21
- class KinetixController(DetectorController):
19
+ class KinetixController(adcore.ADBaseController[KinetixDriverIO]):
22
20
  def __init__(
23
21
  self,
24
22
  driver: KinetixDriverIO,
23
+ good_states: frozenset[adcore.DetectorState] = adcore.DEFAULT_GOOD_STATES,
25
24
  ) -> None:
26
- self._drv = driver
27
- self._arm_status: AsyncStatus | None = None
25
+ super().__init__(driver, good_states=good_states)
28
26
 
29
27
  def get_deadtime(self, exposure: float | None) -> float:
30
28
  return 0.001
31
29
 
32
30
  async def prepare(self, trigger_info: TriggerInfo):
33
31
  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),
32
+ self.driver.trigger_mode.set(
33
+ KINETIX_TRIGGER_MODE_MAP[trigger_info.trigger]
34
+ ),
35
+ self.driver.num_images.set(trigger_info.total_number_of_triggers),
36
+ self.driver.image_mode.set(adcore.ImageMode.MULTIPLE),
37
37
  )
38
38
  if trigger_info.livetime is not None and trigger_info.trigger not in [
39
- DetectorTrigger.variable_gate,
40
- DetectorTrigger.constant_gate,
39
+ DetectorTrigger.VARIABLE_GATE,
40
+ DetectorTrigger.CONSTANT_GATE,
41
41
  ]:
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)
42
+ await self.driver.acquire_time.set(trigger_info.livetime)
@@ -4,16 +4,16 @@ from ophyd_async.epics.core import epics_signal_rw_rbv
4
4
 
5
5
 
6
6
  class KinetixTriggerMode(StrictEnum):
7
- internal = "Internal"
8
- edge = "Rising Edge"
9
- gate = "Exp. Gate"
7
+ INTERNAL = "Internal"
8
+ EDGE = "Rising Edge"
9
+ GATE = "Exp. Gate"
10
10
 
11
11
 
12
12
  class KinetixReadoutMode(StrictEnum):
13
- sensitivity = 1
14
- speed = 2
15
- dynamic_range = 3
16
- sub_electron = 4
13
+ SENSITIVITY = 1
14
+ SPEED = 2
15
+ DYNAMIC_RANGE = 3
16
+ SUB_ELECTRON = 4
17
17
 
18
18
 
19
19
  class KinetixDriverIO(adcore.ADBaseIO):
@@ -1,5 +1,5 @@
1
- from ._pilatus import PilatusDetector, PilatusReadoutTime
2
- from ._pilatus_controller import PilatusController
1
+ from ._pilatus import PilatusDetector
2
+ from ._pilatus_controller import PilatusController, PilatusReadoutTime
3
3
  from ._pilatus_io import PilatusDriverIO, PilatusTriggerMode
4
4
 
5
5
  __all__ = [
@@ -1,58 +1,46 @@
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):
14
+ class PilatusDetector(AreaDetector[PilatusController]):
27
15
  """A Pilatus StandardDetector writing HDF files"""
28
16
 
29
- _controller: PilatusController
30
- _writer: adcore.ADHDFWriter
31
-
32
17
  def __init__(
33
18
  self,
34
19
  prefix: str,
35
20
  path_provider: PathProvider,
36
- readout_time: PilatusReadoutTime = PilatusReadoutTime.pilatus3,
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