ophyd-async 0.4.0__py3-none-any.whl → 0.5.1__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 (95) hide show
  1. ophyd_async/_version.py +2 -2
  2. ophyd_async/core/__init__.py +86 -73
  3. ophyd_async/core/{detector.py → _detector.py} +42 -36
  4. ophyd_async/core/{device.py → _device.py} +1 -1
  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/{epics/areadetector/writers/general_hdffile.py → core/_hdf_dataset.py} +4 -8
  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} +7 -7
  11. ophyd_async/{protocols.py → core/_protocol.py} +1 -1
  12. ophyd_async/core/_providers.py +24 -37
  13. ophyd_async/core/{standard_readable.py → _readable.py} +6 -16
  14. ophyd_async/core/{signal.py → _signal.py} +79 -35
  15. ophyd_async/core/{signal_backend.py → _signal_backend.py} +4 -13
  16. ophyd_async/core/{soft_signal_backend.py → _soft_signal_backend.py} +3 -12
  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} +9 -11
  20. ophyd_async/epics/{areadetector/controllers/aravis_controller.py → adaravis/_aravis_controller.py} +7 -10
  21. ophyd_async/epics/{areadetector/drivers/aravis_driver.py → adaravis/_aravis_io.py} +6 -3
  22. ophyd_async/epics/adcore/__init__.py +47 -0
  23. ophyd_async/epics/adcore/_core_io.py +138 -0
  24. ophyd_async/epics/{areadetector/drivers/ad_base.py → adcore/_core_logic.py} +16 -52
  25. ophyd_async/epics/{areadetector/writers/hdf_writer.py → adcore/_hdf_writer.py} +54 -29
  26. ophyd_async/epics/{areadetector/single_trigger_det.py → adcore/_single_trigger.py} +5 -6
  27. ophyd_async/epics/adcore/_utils.py +132 -0
  28. ophyd_async/epics/adkinetix/__init__.py +9 -0
  29. ophyd_async/epics/{areadetector/kinetix.py → adkinetix/_kinetix.py} +9 -11
  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} +9 -5
  32. ophyd_async/epics/adpilatus/__init__.py +11 -0
  33. ophyd_async/epics/{areadetector/pilatus.py → adpilatus/_pilatus.py} +10 -14
  34. ophyd_async/epics/{areadetector/controllers/pilatus_controller.py → adpilatus/_pilatus_controller.py} +15 -17
  35. ophyd_async/epics/{areadetector/drivers/pilatus_driver.py → adpilatus/_pilatus_io.py} +6 -4
  36. ophyd_async/epics/adsimdetector/__init__.py +7 -0
  37. ophyd_async/epics/{demo/demo_ad_sim_detector.py → adsimdetector/_sim.py} +10 -11
  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/{areadetector/vimba.py → advimba/_vimba.py} +9 -9
  41. ophyd_async/epics/{areadetector/controllers/vimba_controller.py → advimba/_vimba_controller.py} +9 -17
  42. ophyd_async/epics/{areadetector/drivers/vimba_driver.py → advimba/_vimba_io.py} +11 -8
  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/{motion/motor.py → motor.py} +28 -14
  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 +15 -7
  51. ophyd_async/epics/{_backend/common.py → signal/_common.py} +2 -2
  52. ophyd_async/epics/signal/_epics_transport.py +3 -3
  53. ophyd_async/epics/{_backend → signal}/_p4p.py +18 -14
  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 +20 -15
  57. ophyd_async/{panda/_common_blocks.py → fastcs/panda/_block.py} +5 -3
  58. ophyd_async/{panda/_panda_controller.py → fastcs/panda/_control.py} +2 -1
  59. ophyd_async/{panda → fastcs/panda}/_hdf_panda.py +5 -10
  60. ophyd_async/{panda → fastcs/panda}/_trigger.py +3 -7
  61. ophyd_async/{panda/writers/_hdf_writer.py → fastcs/panda/_writer.py} +36 -28
  62. ophyd_async/plan_stubs/__init__.py +5 -2
  63. ophyd_async/plan_stubs/{ensure_connected.py → _ensure_connected.py} +1 -2
  64. ophyd_async/plan_stubs/{fly.py → _fly.py} +13 -9
  65. ophyd_async/plan_stubs/_nd_attributes.py +63 -0
  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/{sim_pattern_generator.py → demo/_pattern_detector/_pattern_detector.py} +8 -8
  70. ophyd_async/sim/{sim_pattern_detector_control.py → demo/_pattern_detector/_pattern_detector_controller.py} +9 -7
  71. ophyd_async/sim/{sim_pattern_detector_writer.py → demo/_pattern_detector/_pattern_detector_writer.py} +4 -4
  72. ophyd_async/sim/{pattern_generator.py → demo/_pattern_detector/_pattern_generator.py} +13 -11
  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.4.0.dist-info → ophyd_async-0.5.1.dist-info}/METADATA +46 -44
  77. ophyd_async-0.5.1.dist-info/RECORD +90 -0
  78. {ophyd_async-0.4.0.dist-info → ophyd_async-0.5.1.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/utils.py +0 -104
  83. ophyd_async/epics/areadetector/writers/__init__.py +0 -5
  84. ophyd_async/epics/areadetector/writers/nd_file_hdf.py +0 -43
  85. ophyd_async/epics/areadetector/writers/nd_plugin.py +0 -68
  86. ophyd_async/epics/motion/__init__.py +0 -3
  87. ophyd_async/panda/writers/__init__.py +0 -3
  88. ophyd_async-0.4.0.dist-info/RECORD +0 -84
  89. /ophyd_async/core/{utils.py → _utils.py} +0 -0
  90. /ophyd_async/{epics/_backend → fastcs}/__init__.py +0 -0
  91. /ophyd_async/{panda → fastcs/panda}/_table.py +0 -0
  92. /ophyd_async/{panda → fastcs/panda}/_utils.py +0 -0
  93. {ophyd_async-0.4.0.dist-info → ophyd_async-0.5.1.dist-info}/LICENSE +0 -0
  94. {ophyd_async-0.4.0.dist-info → ophyd_async-0.5.1.dist-info}/entry_points.txt +0 -0
  95. {ophyd_async-0.4.0.dist-info → ophyd_async-0.5.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,138 @@
1
+ from enum import Enum
2
+
3
+ from ophyd_async.core import Device
4
+ from ophyd_async.epics.signal import (
5
+ epics_signal_r,
6
+ epics_signal_rw,
7
+ epics_signal_rw_rbv,
8
+ )
9
+
10
+ from ._utils import ADBaseDataType, FileWriteMode, ImageMode
11
+
12
+
13
+ class Callback(str, Enum):
14
+ Enable = "Enable"
15
+ Disable = "Disable"
16
+
17
+
18
+ class NDArrayBaseIO(Device):
19
+ def __init__(self, prefix: str, name: str = "") -> None:
20
+ self.unique_id = epics_signal_r(int, prefix + "UniqueId_RBV")
21
+ self.nd_attributes_file = epics_signal_rw(str, prefix + "NDAttributesFile")
22
+ self.acquire = epics_signal_rw_rbv(bool, prefix + "Acquire")
23
+ self.array_size_x = epics_signal_r(int, prefix + "ArraySizeX_RBV")
24
+ self.array_size_y = epics_signal_r(int, prefix + "ArraySizeY_RBV")
25
+ self.data_type = epics_signal_r(ADBaseDataType, prefix + "DataType_RBV")
26
+ self.array_counter = epics_signal_rw_rbv(int, prefix + "ArrayCounter")
27
+ # There is no _RBV for this one
28
+ self.wait_for_plugins = epics_signal_rw(bool, prefix + "WaitForPlugins")
29
+ super().__init__(name=name)
30
+
31
+
32
+ class NDPluginBaseIO(NDArrayBaseIO):
33
+ def __init__(self, prefix: str, name: str = "") -> None:
34
+ self.nd_array_port = epics_signal_rw_rbv(str, prefix + "NDArrayPort")
35
+ self.enable_callbacks = epics_signal_rw_rbv(
36
+ Callback, prefix + "EnableCallbacks"
37
+ )
38
+ self.nd_array_address = epics_signal_rw_rbv(int, prefix + "NDArrayAddress")
39
+ self.array_size0 = epics_signal_r(int, prefix + "ArraySize0_RBV")
40
+ self.array_size1 = epics_signal_r(int, prefix + "ArraySize1_RBV")
41
+ super().__init__(prefix, name)
42
+
43
+
44
+ class NDPluginStatsIO(NDPluginBaseIO):
45
+ """
46
+ Plugin for computing statistics from an image or region of interest within an image.
47
+ """
48
+
49
+ def __init__(self, prefix: str, name: str = "") -> None:
50
+ # Basic statistics
51
+ self.compute_statistics = epics_signal_rw(bool, prefix + "ComputeStatistics")
52
+ self.bgd_width = epics_signal_rw(int, prefix + "BgdWidth")
53
+ self.total_array = epics_signal_rw(float, prefix + "TotalArray")
54
+ # Centroid statistics
55
+ self.compute_centroid = epics_signal_rw(bool, prefix + "ComputeCentroid")
56
+ self.centroid_threshold = epics_signal_rw(float, prefix + "CentroidThreshold")
57
+ # X and Y Profiles
58
+ self.compute_profiles = epics_signal_rw(bool, prefix + "ComputeProfiles")
59
+ self.profile_size_x = epics_signal_rw(int, prefix + "ProfileSizeX")
60
+ self.profile_size_y = epics_signal_rw(int, prefix + "ProfileSizeY")
61
+ self.cursor_x = epics_signal_rw(int, prefix + "CursorX")
62
+ self.cursor_y = epics_signal_rw(int, prefix + "CursorY")
63
+ # Array Histogram
64
+ self.compute_histogram = epics_signal_rw(bool, prefix + "ComputeHistogram")
65
+ self.hist_size = epics_signal_rw(int, prefix + "HistSize")
66
+ self.hist_min = epics_signal_rw(float, prefix + "HistMin")
67
+ self.hist_max = epics_signal_rw(float, prefix + "HistMax")
68
+ super().__init__(prefix, name)
69
+
70
+
71
+ class DetectorState(str, Enum):
72
+ """
73
+ Default set of states of an AreaDetector driver.
74
+ See definition in ADApp/ADSrc/ADDriver.h in https://github.com/areaDetector/ADCore
75
+ """
76
+
77
+ Idle = "Idle"
78
+ Acquire = "Acquire"
79
+ Readout = "Readout"
80
+ Correct = "Correct"
81
+ Saving = "Saving"
82
+ Aborting = "Aborting"
83
+ Error = "Error"
84
+ Waiting = "Waiting"
85
+ Initializing = "Initializing"
86
+ Disconnected = "Disconnected"
87
+ Aborted = "Aborted"
88
+
89
+
90
+ class ADBaseIO(NDArrayBaseIO):
91
+ def __init__(self, prefix: str, name: str = "") -> None:
92
+ # Define some signals
93
+ self.acquire_time = epics_signal_rw_rbv(float, prefix + "AcquireTime")
94
+ self.acquire_period = epics_signal_rw_rbv(float, prefix + "AcquirePeriod")
95
+ self.num_images = epics_signal_rw_rbv(int, prefix + "NumImages")
96
+ self.image_mode = epics_signal_rw_rbv(ImageMode, prefix + "ImageMode")
97
+ self.detector_state = epics_signal_r(
98
+ DetectorState, prefix + "DetectorState_RBV"
99
+ )
100
+ super().__init__(prefix, name=name)
101
+
102
+
103
+ class Compression(str, Enum):
104
+ none = "None"
105
+ nbit = "N-bit"
106
+ szip = "szip"
107
+ zlib = "zlib"
108
+ blosc = "Blosc"
109
+ bslz4 = "BSLZ4"
110
+ lz4 = "LZ4"
111
+ jpeg = "JPEG"
112
+
113
+
114
+ class NDFileHDFIO(NDPluginBaseIO):
115
+ def __init__(self, prefix: str, name="") -> None:
116
+ # Define some signals
117
+ self.position_mode = epics_signal_rw_rbv(bool, prefix + "PositionMode")
118
+ self.compression = epics_signal_rw_rbv(Compression, prefix + "Compression")
119
+ self.num_extra_dims = epics_signal_rw_rbv(int, prefix + "NumExtraDims")
120
+ self.file_path = epics_signal_rw_rbv(str, prefix + "FilePath")
121
+ self.file_name = epics_signal_rw_rbv(str, prefix + "FileName")
122
+ self.file_path_exists = epics_signal_r(bool, prefix + "FilePathExists_RBV")
123
+ self.file_template = epics_signal_rw_rbv(str, prefix + "FileTemplate")
124
+ self.full_file_name = epics_signal_r(str, prefix + "FullFileName_RBV")
125
+ self.file_write_mode = epics_signal_rw_rbv(
126
+ FileWriteMode, prefix + "FileWriteMode"
127
+ )
128
+ self.num_capture = epics_signal_rw_rbv(int, prefix + "NumCapture")
129
+ self.num_captured = epics_signal_r(int, prefix + "NumCaptured_RBV")
130
+ self.swmr_mode = epics_signal_rw_rbv(bool, prefix + "SWMRMode")
131
+ self.lazy_open = epics_signal_rw_rbv(bool, prefix + "LazyOpen")
132
+ self.capture = epics_signal_rw_rbv(bool, prefix + "Capture")
133
+ self.flush_now = epics_signal_rw(bool, prefix + "FlushNow")
134
+ self.xml_file_name = epics_signal_rw_rbv(str, prefix + "XMLFileName")
135
+ self.array_size0 = epics_signal_r(int, prefix + "ArraySize0")
136
+ self.array_size1 = epics_signal_r(int, prefix + "ArraySize1")
137
+ self.create_directory = epics_signal_rw(int, prefix + "CreateDirectory")
138
+ super().__init__(prefix, name)
@@ -1,5 +1,4 @@
1
1
  import asyncio
2
- from enum import Enum
3
2
  from typing import FrozenSet, Set
4
3
 
5
4
  from ophyd_async.core import (
@@ -10,53 +9,31 @@ from ophyd_async.core import (
10
9
  set_and_wait_for_value,
11
10
  )
12
11
 
13
- from ...signal.signal import epics_signal_r, epics_signal_rw_rbv
14
- from ..utils import ImageMode
15
- from ..writers.nd_plugin import NDArrayBase
12
+ from ._core_io import ADBaseIO, DetectorState
16
13
 
17
-
18
- class DetectorState(str, Enum):
19
- """
20
- Default set of states of an AreaDetector driver.
21
- See definition in ADApp/ADSrc/ADDriver.h in https://github.com/areaDetector/ADCore
22
- """
23
-
24
- Idle = "Idle"
25
- Acquire = "Acquire"
26
- Readout = "Readout"
27
- Correct = "Correct"
28
- Saving = "Saving"
29
- Aborting = "Aborting"
30
- Error = "Error"
31
- Waiting = "Waiting"
32
- Initializing = "Initializing"
33
- Disconnected = "Disconnected"
34
- Aborted = "Aborted"
35
-
36
-
37
- #: Default set of states that we should consider "good" i.e. the acquisition
14
+ # Default set of states that we should consider "good" i.e. the acquisition
38
15
  # is complete and went well
39
16
  DEFAULT_GOOD_STATES: FrozenSet[DetectorState] = frozenset(
40
17
  [DetectorState.Idle, DetectorState.Aborted]
41
18
  )
42
19
 
43
20
 
44
- class ADBase(NDArrayBase):
45
- def __init__(self, prefix: str, name: str = "") -> None:
46
- # Define some signals
47
- self.acquire_time = epics_signal_rw_rbv(float, prefix + "AcquireTime")
48
- self.acquire_period = epics_signal_rw_rbv(float, prefix + "AcquirePeriod")
49
- self.num_images = epics_signal_rw_rbv(int, prefix + "NumImages")
50
- self.image_mode = epics_signal_rw_rbv(ImageMode, prefix + "ImageMode")
51
- self.detector_state = epics_signal_r(
52
- DetectorState, prefix + "DetectorState_RBV"
21
+ class ADBaseShapeProvider(ShapeProvider):
22
+ def __init__(self, driver: ADBaseIO) -> None:
23
+ self._driver = driver
24
+
25
+ async def __call__(self) -> tuple:
26
+ shape = await asyncio.gather(
27
+ self._driver.array_size_y.get_value(),
28
+ self._driver.array_size_x.get_value(),
29
+ self._driver.data_type.get_value(),
53
30
  )
54
- super().__init__(prefix, name=name)
31
+ return shape
55
32
 
56
33
 
57
34
  async def set_exposure_time_and_acquire_period_if_supplied(
58
35
  controller: DetectorControl,
59
- driver: ADBase,
36
+ driver: ADBaseIO,
60
37
  exposure: float | None = None,
61
38
  timeout: float = DEFAULT_TIMEOUT,
62
39
  ) -> None:
@@ -70,7 +47,7 @@ async def set_exposure_time_and_acquire_period_if_supplied(
70
47
  controller:
71
48
  Controller that can supply a deadtime.
72
49
  driver:
73
- The driver to start acquiring. Must subclass ADBase.
50
+ The driver to start acquiring. Must subclass ADBaseIO.
74
51
  exposure:
75
52
  Desired exposure time, this is a noop if it is None.
76
53
  timeout:
@@ -85,7 +62,7 @@ async def set_exposure_time_and_acquire_period_if_supplied(
85
62
 
86
63
 
87
64
  async def start_acquiring_driver_and_ensure_status(
88
- driver: ADBase,
65
+ driver: ADBaseIO,
89
66
  good_states: Set[DetectorState] = set(DEFAULT_GOOD_STATES),
90
67
  timeout: float = DEFAULT_TIMEOUT,
91
68
  ) -> AsyncStatus:
@@ -99,7 +76,7 @@ async def start_acquiring_driver_and_ensure_status(
99
76
  Parameters
100
77
  ----------
101
78
  driver:
102
- The driver to start acquiring. Must subclass ADBase.
79
+ The driver to start acquiring. Must subclass ADBaseIO.
103
80
  good_states:
104
81
  set of states defined in DetectorState enum which are considered good states.
105
82
  timeout:
@@ -125,16 +102,3 @@ async def start_acquiring_driver_and_ensure_status(
125
102
  )
126
103
 
127
104
  return AsyncStatus(complete_acquisition())
128
-
129
-
130
- class ADBaseShapeProvider(ShapeProvider):
131
- def __init__(self, driver: ADBase) -> None:
132
- self._driver = driver
133
-
134
- async def __call__(self) -> tuple:
135
- shape = await asyncio.gather(
136
- self._driver.array_size_y.get_value(),
137
- self._driver.array_size_x.get_value(),
138
- self._driver.data_type.get_value(),
139
- )
140
- return shape
@@ -1,6 +1,7 @@
1
1
  import asyncio
2
2
  from pathlib import Path
3
3
  from typing import AsyncGenerator, AsyncIterator, Dict, List, Optional
4
+ from xml.etree import ElementTree as ET
4
5
 
5
6
  from bluesky.protocols import DataKey, Hints, StreamAsset
6
7
 
@@ -8,51 +9,61 @@ from ophyd_async.core import (
8
9
  DEFAULT_TIMEOUT,
9
10
  AsyncStatus,
10
11
  DetectorWriter,
12
+ HDFDataset,
13
+ HDFFile,
11
14
  NameProvider,
12
15
  PathProvider,
13
16
  ShapeProvider,
17
+ observe_value,
14
18
  set_and_wait_for_value,
15
19
  wait_for_value,
16
20
  )
17
- from ophyd_async.core.signal import observe_value
18
21
 
19
- from .general_hdffile import _HDFDataset, _HDFFile
20
- from .nd_file_hdf import FileWriteMode, NDFileHDF
21
- from .nd_plugin import convert_ad_dtype_to_np
22
+ from ._core_io import NDArrayBaseIO, NDFileHDFIO
23
+ from ._utils import (
24
+ FileWriteMode,
25
+ convert_ad_dtype_to_np,
26
+ convert_param_dtype_to_np,
27
+ convert_pv_dtype_to_np,
28
+ )
22
29
 
23
30
 
24
- class HDFWriter(DetectorWriter):
31
+ class ADHDFWriter(DetectorWriter):
25
32
  def __init__(
26
33
  self,
27
- hdf: NDFileHDF,
34
+ hdf: NDFileHDFIO,
28
35
  path_provider: PathProvider,
29
36
  name_provider: NameProvider,
30
37
  shape_provider: ShapeProvider,
31
- **scalar_datasets_paths: str,
38
+ *plugins: NDArrayBaseIO,
32
39
  ) -> None:
33
40
  self.hdf = hdf
34
41
  self._path_provider = path_provider
35
42
  self._name_provider = name_provider
36
43
  self._shape_provider = shape_provider
37
- self._scalar_datasets_paths = scalar_datasets_paths
44
+
45
+ self._plugins = plugins
38
46
  self._capture_status: Optional[AsyncStatus] = None
39
- self._datasets: List[_HDFDataset] = []
40
- self._file: Optional[_HDFFile] = None
47
+ self._datasets: List[HDFDataset] = []
48
+ self._file: Optional[HDFFile] = None
41
49
  self._multiplier = 1
42
50
 
43
51
  async def open(self, multiplier: int = 1) -> Dict[str, DataKey]:
44
52
  self._file = None
45
53
  info = self._path_provider(device_name=self.hdf.name)
46
- file_path = str(info.root / info.resource_dir)
54
+
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)
58
+
47
59
  await asyncio.gather(
48
60
  self.hdf.num_extra_dims.set(0),
49
61
  self.hdf.lazy_open.set(True),
50
62
  self.hdf.swmr_mode.set(True),
51
63
  # See https://github.com/bluesky/ophyd-async/issues/122
52
- self.hdf.file_path.set(file_path),
64
+ self.hdf.file_path.set(str(info.directory_path)),
53
65
  self.hdf.file_name.set(info.filename),
54
66
  self.hdf.file_template.set("%s/%s.h5"),
55
- self.hdf.create_dir_depth.set(info.create_dir_depth),
56
67
  self.hdf.file_write_mode.set(FileWriteMode.stream),
57
68
  # Never use custom xml layout file but use the one defined
58
69
  # in the source code file NDFileHDF5LayoutXML.cpp
@@ -61,7 +72,7 @@ class HDFWriter(DetectorWriter):
61
72
 
62
73
  assert (
63
74
  await self.hdf.file_path_exists.get_value()
64
- ), f"File path {file_path} for hdf plugin does not exist"
75
+ ), f"File path {info.directory_path} for hdf plugin does not exist"
65
76
 
66
77
  # Overwrite num_capture to go forever
67
78
  await self.hdf.num_capture.set(0)
@@ -80,7 +91,7 @@ class HDFWriter(DetectorWriter):
80
91
 
81
92
  # Add the main data
82
93
  self._datasets = [
83
- _HDFDataset(
94
+ HDFDataset(
84
95
  data_key=name,
85
96
  dataset="/entry/data/data",
86
97
  shape=frame_shape,
@@ -89,16 +100,31 @@ class HDFWriter(DetectorWriter):
89
100
  )
90
101
  ]
91
102
  # And all the scalar datasets
92
- for ds_name, ds_path in self._scalar_datasets_paths.items():
93
- self._datasets.append(
94
- _HDFDataset(
95
- f"{name}-{ds_name}",
96
- f"/entry/instrument/NDAttributes/{ds_path}",
97
- (),
98
- "",
99
- multiplier,
100
- )
101
- )
103
+ for plugin in self._plugins:
104
+ maybe_xml = await plugin.nd_attributes_file.get_value()
105
+ # This is the check that ADCore does to see if it is an XML string
106
+ # rather than a filename to parse
107
+ if "<Attributes>" in maybe_xml:
108
+ root = ET.fromstring(maybe_xml)
109
+ for child in root:
110
+ datakey = child.attrib["name"]
111
+ if child.attrib.get("type", "EPICS_PV") == "EPICS_PV":
112
+ np_datatype = convert_pv_dtype_to_np(
113
+ child.attrib.get("dbrtype", "DBR_NATIVE")
114
+ )
115
+ else:
116
+ np_datatype = convert_param_dtype_to_np(
117
+ child.attrib.get("datatype", "INT")
118
+ )
119
+ self._datasets.append(
120
+ HDFDataset(
121
+ datakey,
122
+ f"/entry/instrument/NDAttributes/{datakey}",
123
+ (),
124
+ np_datatype,
125
+ multiplier,
126
+ )
127
+ )
102
128
 
103
129
  describe = {
104
130
  ds.data_key: DataKey(
@@ -131,8 +157,7 @@ class HDFWriter(DetectorWriter):
131
157
  if indices_written:
132
158
  if not self._file:
133
159
  path = Path(await self.hdf.full_file_name.get_value())
134
- self._file = _HDFFile(
135
- self._path_provider(),
160
+ self._file = HDFFile(
136
161
  # See https://github.com/bluesky/ophyd-async/issues/122
137
162
  path,
138
163
  self._datasets,
@@ -148,8 +173,8 @@ class HDFWriter(DetectorWriter):
148
173
 
149
174
  async def close(self):
150
175
  # Already done a caput callback in _capture_status, so can't do one here
151
- await self.hdf.capture.set(0, wait=False)
152
- await wait_for_value(self.hdf.capture, 0, DEFAULT_TIMEOUT)
176
+ await self.hdf.capture.set(False, wait=False)
177
+ await wait_for_value(self.hdf.capture, False, DEFAULT_TIMEOUT)
153
178
  if self._capture_status:
154
179
  # We kicked off an open, so wait for it to return
155
180
  await self._capture_status
@@ -11,18 +11,17 @@ from ophyd_async.core import (
11
11
  StandardReadable,
12
12
  )
13
13
 
14
- from .drivers.ad_base import ADBase
15
- from .utils import ImageMode
16
- from .writers.nd_plugin import NDPluginBase
14
+ from ._core_io import ADBaseIO, NDPluginBaseIO
15
+ from ._utils import ImageMode
17
16
 
18
17
 
19
- class SingleTriggerDet(StandardReadable, Triggerable):
18
+ class SingleTriggerDetector(StandardReadable, Triggerable):
20
19
  def __init__(
21
20
  self,
22
- drv: ADBase,
21
+ drv: ADBaseIO,
23
22
  read_uncached: Sequence[SignalR] = (),
24
23
  name="",
25
- **plugins: NDPluginBase,
24
+ **plugins: NDPluginBaseIO,
26
25
  ) -> None:
27
26
  self.drv = drv
28
27
  self.__dict__.update(plugins)
@@ -0,0 +1,132 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+ from typing import Optional
4
+
5
+ from ophyd_async.core import DEFAULT_TIMEOUT, SignalRW, T, wait_for_value
6
+ from ophyd_async.core._signal import SignalR
7
+
8
+
9
+ class ADBaseDataType(str, Enum):
10
+ Int8 = "Int8"
11
+ UInt8 = "UInt8"
12
+ Int16 = "Int16"
13
+ UInt16 = "UInt16"
14
+ Int32 = "Int32"
15
+ UInt32 = "UInt32"
16
+ Int64 = "Int64"
17
+ UInt64 = "UInt64"
18
+ Float32 = "Float32"
19
+ Float64 = "Float64"
20
+
21
+
22
+ def convert_ad_dtype_to_np(ad_dtype: ADBaseDataType) -> str:
23
+ ad_dtype_to_np_dtype = {
24
+ ADBaseDataType.Int8: "|i1",
25
+ ADBaseDataType.UInt8: "|u1",
26
+ ADBaseDataType.Int16: "<i2",
27
+ ADBaseDataType.UInt16: "<u2",
28
+ ADBaseDataType.Int32: "<i4",
29
+ ADBaseDataType.UInt32: "<u4",
30
+ ADBaseDataType.Int64: "<i8",
31
+ ADBaseDataType.UInt64: "<u8",
32
+ ADBaseDataType.Float32: "<f4",
33
+ ADBaseDataType.Float64: "<f8",
34
+ }
35
+ return ad_dtype_to_np_dtype[ad_dtype]
36
+
37
+
38
+ def convert_pv_dtype_to_np(datatype: str) -> str:
39
+ _pvattribute_to_ad_datatype = {
40
+ "DBR_SHORT": ADBaseDataType.Int16,
41
+ "DBR_ENUM": ADBaseDataType.Int16,
42
+ "DBR_INT": ADBaseDataType.Int32,
43
+ "DBR_LONG": ADBaseDataType.Int32,
44
+ "DBR_FLOAT": ADBaseDataType.Float32,
45
+ "DBR_DOUBLE": ADBaseDataType.Float64,
46
+ }
47
+ if datatype in ["DBR_STRING", "DBR_CHAR"]:
48
+ np_datatype = "s40"
49
+ elif datatype == "DBR_NATIVE":
50
+ raise ValueError("Don't support DBR_NATIVE yet")
51
+ else:
52
+ try:
53
+ np_datatype = convert_ad_dtype_to_np(_pvattribute_to_ad_datatype[datatype])
54
+ except KeyError:
55
+ raise ValueError(f"Invalid dbr type {datatype}")
56
+ return np_datatype
57
+
58
+
59
+ def convert_param_dtype_to_np(datatype: str) -> str:
60
+ _paramattribute_to_ad_datatype = {
61
+ "INT": ADBaseDataType.Int32,
62
+ "INT64": ADBaseDataType.Int64,
63
+ "DOUBLE": ADBaseDataType.Float64,
64
+ }
65
+ if datatype in ["STRING"]:
66
+ np_datatype = "s40"
67
+ else:
68
+ try:
69
+ np_datatype = convert_ad_dtype_to_np(
70
+ _paramattribute_to_ad_datatype[datatype]
71
+ )
72
+ except KeyError:
73
+ raise ValueError(f"Invalid datatype {datatype}")
74
+ return np_datatype
75
+
76
+
77
+ class FileWriteMode(str, Enum):
78
+ single = "Single"
79
+ capture = "Capture"
80
+ stream = "Stream"
81
+
82
+
83
+ class ImageMode(str, Enum):
84
+ single = "Single"
85
+ multiple = "Multiple"
86
+ continuous = "Continuous"
87
+
88
+
89
+ class NDAttributeDataType(str, Enum):
90
+ INT = "INT"
91
+ DOUBLE = "DOUBLE"
92
+ STRING = "STRING"
93
+
94
+
95
+ class NDAttributePvDbrType(str, Enum):
96
+ DBR_SHORT = "DBR_SHORT"
97
+ DBR_ENUM = "DBR_ENUM"
98
+ DBR_INT = "DBR_INT"
99
+ DBR_LONG = "DBR_LONG"
100
+ DBR_FLOAT = "DBR_FLOAT"
101
+ DBR_DOUBLE = "DBR_DOUBLE"
102
+ DBR_STRING = "DBR_STRING"
103
+ DBR_CHAR = "DBR_CHAR"
104
+
105
+
106
+ @dataclass
107
+ class NDAttributePv:
108
+ name: str # name of attribute stamped on array, also scientifically useful name
109
+ # when appended to device.name
110
+ signal: SignalR # caget the pv given by signal.source and attach to each frame
111
+ dbrtype: NDAttributePvDbrType
112
+ description: str = "" # A description that appears in the HDF file as an attribute
113
+
114
+
115
+ @dataclass
116
+ class NDAttributeParam:
117
+ name: str # name of attribute stamped on array, also scientifically useful name
118
+ # when appended to device.name
119
+ param: str # The parameter string as seen in the INP link of the record
120
+ datatype: NDAttributeDataType # The datatype of the parameter
121
+ addr: int = 0 # The address as seen in the INP link of the record
122
+ description: str = "" # A description that appears in the HDF file as an attribute
123
+
124
+
125
+ async def stop_busy_record(
126
+ signal: SignalRW[T],
127
+ value: T,
128
+ timeout: float = DEFAULT_TIMEOUT,
129
+ status_timeout: Optional[float] = None,
130
+ ) -> None:
131
+ await signal.set(value, wait=False, timeout=status_timeout)
132
+ await wait_for_value(signal, value, timeout=timeout)
@@ -0,0 +1,9 @@
1
+ from ._kinetix import KinetixDetector
2
+ from ._kinetix_controller import KinetixController
3
+ from ._kinetix_io import KinetixDriverIO
4
+
5
+ __all__ = [
6
+ "KinetixDetector",
7
+ "KinetixController",
8
+ "KinetixDriverIO",
9
+ ]
@@ -1,12 +1,10 @@
1
1
  from bluesky.protocols import HasHints, Hints
2
2
 
3
3
  from ophyd_async.core import PathProvider, StandardDetector
4
- from ophyd_async.epics.areadetector.controllers.kinetix_controller import (
5
- KinetixController,
6
- )
7
- from ophyd_async.epics.areadetector.drivers import ADBaseShapeProvider
8
- from ophyd_async.epics.areadetector.drivers.kinetix_driver import KinetixDriver
9
- from ophyd_async.epics.areadetector.writers import HDFWriter, NDFileHDF
4
+ from ophyd_async.epics import adcore
5
+
6
+ from ._kinetix_controller import KinetixController
7
+ from ._kinetix_io import KinetixDriverIO
10
8
 
11
9
 
12
10
  class KinetixDetector(StandardDetector, HasHints):
@@ -16,7 +14,7 @@ class KinetixDetector(StandardDetector, HasHints):
16
14
  """
17
15
 
18
16
  _controller: KinetixController
19
- _writer: HDFWriter
17
+ _writer: adcore.ADHDFWriter
20
18
 
21
19
  def __init__(
22
20
  self,
@@ -26,16 +24,16 @@ class KinetixDetector(StandardDetector, HasHints):
26
24
  hdf_suffix="HDF1:",
27
25
  name="",
28
26
  ):
29
- self.drv = KinetixDriver(prefix + drv_suffix)
30
- self.hdf = NDFileHDF(prefix + hdf_suffix)
27
+ self.drv = KinetixDriverIO(prefix + drv_suffix)
28
+ self.hdf = adcore.NDFileHDFIO(prefix + hdf_suffix)
31
29
 
32
30
  super().__init__(
33
31
  KinetixController(self.drv),
34
- HDFWriter(
32
+ adcore.ADHDFWriter(
35
33
  self.hdf,
36
34
  path_provider,
37
35
  lambda: self.name,
38
- ADBaseShapeProvider(self.drv),
36
+ adcore.ADBaseShapeProvider(self.drv),
39
37
  ),
40
38
  config_sigs=(self.drv.acquire_time,),
41
39
  name=name,
@@ -2,12 +2,9 @@ import asyncio
2
2
  from typing import Optional
3
3
 
4
4
  from ophyd_async.core import AsyncStatus, DetectorControl, DetectorTrigger
5
- from ophyd_async.epics.areadetector.drivers.ad_base import (
6
- start_acquiring_driver_and_ensure_status,
7
- )
5
+ from ophyd_async.epics import adcore
8
6
 
9
- from ..drivers.kinetix_driver import KinetixDriver, KinetixTriggerMode
10
- from ..utils import ImageMode, stop_busy_record
7
+ from ._kinetix_io import KinetixDriverIO, KinetixTriggerMode
11
8
 
12
9
  KINETIX_TRIGGER_MODE_MAP = {
13
10
  DetectorTrigger.internal: KinetixTriggerMode.internal,
@@ -20,7 +17,7 @@ KINETIX_TRIGGER_MODE_MAP = {
20
17
  class KinetixController(DetectorControl):
21
18
  def __init__(
22
19
  self,
23
- driver: KinetixDriver,
20
+ driver: KinetixDriverIO,
24
21
  ) -> None:
25
22
  self._drv = driver
26
23
 
@@ -36,14 +33,14 @@ class KinetixController(DetectorControl):
36
33
  await asyncio.gather(
37
34
  self._drv.trigger_mode.set(KINETIX_TRIGGER_MODE_MAP[trigger]),
38
35
  self._drv.num_images.set(num),
39
- self._drv.image_mode.set(ImageMode.multiple),
36
+ self._drv.image_mode.set(adcore.ImageMode.multiple),
40
37
  )
41
38
  if exposure is not None and trigger not in [
42
39
  DetectorTrigger.variable_gate,
43
40
  DetectorTrigger.constant_gate,
44
41
  ]:
45
42
  await self._drv.acquire_time.set(exposure)
46
- return await start_acquiring_driver_and_ensure_status(self._drv)
43
+ return await adcore.start_acquiring_driver_and_ensure_status(self._drv)
47
44
 
48
45
  async def disarm(self):
49
- await stop_busy_record(self._drv.acquire, False, timeout=1)
46
+ await adcore.stop_busy_record(self._drv.acquire, False, timeout=1)